Skip to content

Commit 5ef84ba

Browse files
[iap] use google-auth for id token samples (#3444)
1 parent 4f88d47 commit 5ef84ba

File tree

3 files changed

+19
-109
lines changed

3 files changed

+19
-109
lines changed

iap/make_iap_request.py

Lines changed: 6 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,9 @@
1515
"""Example use of a service account to authenticate to Identity-Aware Proxy."""
1616

1717
# [START iap_make_request]
18-
import google.auth
19-
import google.auth.app_engine
20-
import google.auth.compute_engine.credentials
21-
import google.auth.iam
2218
from google.auth.transport.requests import Request
23-
import google.oauth2.credentials
24-
import google.oauth2.service_account
19+
from google.oauth2 import id_token
2520
import requests
26-
import requests_toolbelt.adapters.appengine
27-
28-
29-
IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
30-
OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'
3121

3222

3323
def make_iap_request(url, client_id, method='GET', **kwargs):
@@ -49,56 +39,9 @@ def make_iap_request(url, client_id, method='GET', **kwargs):
4939
if 'timeout' not in kwargs:
5040
kwargs['timeout'] = 90
5141

52-
# Figure out what environment we're running in and get some preliminary
53-
# information about the service account.
54-
bootstrap_credentials, _ = google.auth.default(
55-
scopes=[IAM_SCOPE])
56-
if isinstance(bootstrap_credentials,
57-
google.oauth2.credentials.Credentials):
58-
raise Exception('make_iap_request is only supported for service '
59-
'accounts.')
60-
elif isinstance(bootstrap_credentials,
61-
google.auth.app_engine.Credentials):
62-
requests_toolbelt.adapters.appengine.monkeypatch()
63-
64-
# For service account's using the Compute Engine metadata service,
65-
# service_account_email isn't available until refresh is called.
66-
bootstrap_credentials.refresh(Request())
67-
68-
signer_email = bootstrap_credentials.service_account_email
69-
if isinstance(bootstrap_credentials,
70-
google.auth.compute_engine.credentials.Credentials):
71-
# Since the Compute Engine metadata service doesn't expose the service
72-
# account key, we use the IAM signBlob API to sign instead.
73-
# In order for this to work:
74-
#
75-
# 1. Your VM needs the https://www.googleapis.com/auth/iam scope.
76-
# You can specify this specific scope when creating a VM
77-
# through the API or gcloud. When using Cloud Console,
78-
# you'll need to specify the "full access to all Cloud APIs"
79-
# scope. A VM's scopes can only be specified at creation time.
80-
#
81-
# 2. The VM's default service account needs the "Service Account Actor"
82-
# role. This can be found under the "Project" category in Cloud
83-
# Console, or roles/iam.serviceAccountActor in gcloud.
84-
signer = google.auth.iam.Signer(
85-
Request(), bootstrap_credentials, signer_email)
86-
else:
87-
# A Signer object can sign a JWT using the service account's key.
88-
signer = bootstrap_credentials.signer
89-
90-
# Construct OAuth 2.0 service account credentials using the signer
91-
# and email acquired from the bootstrap credentials.
92-
service_account_credentials = google.oauth2.service_account.Credentials(
93-
signer, signer_email, token_uri=OAUTH_TOKEN_URI, additional_claims={
94-
'target_audience': client_id
95-
})
96-
97-
# service_account_credentials gives us a JWT signed by the service
98-
# account. Next, we use that to obtain an OpenID Connect token,
99-
# which is a JWT signed by Google.
100-
google_open_id_connect_token = get_google_open_id_connect_token(
101-
service_account_credentials)
42+
# Obtain an OpenID Connect (OIDC) token from metadata server or using service
43+
# account.
44+
google_open_id_connect_token = id_token.fetch_id_token(Request(), client_id)
10245

10346
# Fetch the Identity-Aware Proxy-protected URL, including an
10447
# Authorization header containing "Bearer " followed by a
@@ -108,48 +51,13 @@ def make_iap_request(url, client_id, method='GET', **kwargs):
10851
headers={'Authorization': 'Bearer {}'.format(
10952
google_open_id_connect_token)}, **kwargs)
11053
if resp.status_code == 403:
111-
raise Exception('Service account {} does not have permission to '
112-
'access the IAP-protected application.'.format(
113-
signer_email))
54+
raise Exception('Service account does not have permission to '
55+
'access the IAP-protected application.')
11456
elif resp.status_code != 200:
11557
raise Exception(
11658
'Bad response from application: {!r} / {!r} / {!r}'.format(
11759
resp.status_code, resp.headers, resp.text))
11860
else:
11961
return resp.text
12062

121-
122-
def get_google_open_id_connect_token(service_account_credentials):
123-
"""Get an OpenID Connect token issued by Google for the service account.
124-
125-
This function:
126-
127-
1. Generates a JWT signed with the service account's private key
128-
containing a special "target_audience" claim.
129-
130-
2. Sends it to the OAUTH_TOKEN_URI endpoint. Because the JWT in #1
131-
has a target_audience claim, that endpoint will respond with
132-
an OpenID Connect token for the service account -- in other words,
133-
a JWT signed by *Google*. The aud claim in this JWT will be
134-
set to the value from the target_audience claim in #1.
135-
136-
For more information, see
137-
https://developers.google.com/identity/protocols/OAuth2ServiceAccount .
138-
The HTTP/REST example on that page describes the JWT structure and
139-
demonstrates how to call the token endpoint. (The example on that page
140-
shows how to get an OAuth2 access token; this code is using a
141-
modified version of it to get an OpenID Connect token.)
142-
"""
143-
144-
service_account_jwt = (
145-
service_account_credentials._make_authorization_grant_assertion())
146-
request = google.auth.transport.requests.Request()
147-
body = {
148-
'assertion': service_account_jwt,
149-
'grant_type': google.oauth2._client._JWT_GRANT_TYPE,
150-
}
151-
token_response = google.oauth2._client._token_endpoint_request(
152-
request, OAUTH_TOKEN_URI, body)
153-
return token_response['id_token']
154-
15563
# [END iap_make_request]

iap/requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
PyJWT==1.7.1
21
cryptography==2.9.1
32
flask==1.1.2
4-
google-auth==1.14.0
3+
google-auth==1.14.1
54
gunicorn==20.0.4
65
requests==2.23.0
76
requests_toolbelt==0.9.1

iap/validate_jwt.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
App Engine's Users API instead.
2424
"""
2525
# [START iap_validate_jwt]
26-
import jwt
26+
from google.auth import jwt
2727
import requests
2828

2929

@@ -70,18 +70,21 @@ def validate_iap_jwt_from_compute_engine(iap_jwt, cloud_project_number,
7070

7171
def _validate_iap_jwt(iap_jwt, expected_audience):
7272
try:
73-
key_id = jwt.get_unverified_header(iap_jwt).get('kid')
73+
# Retrieve public key for token signature verification.
74+
key_id = jwt.decode_header(iap_jwt).get('kid')
7475
if not key_id:
7576
return (None, None, '**ERROR: no key ID**')
7677
key = get_iap_key(key_id)
77-
decoded_jwt = jwt.decode(
78-
iap_jwt, key,
79-
algorithms=['ES256'],
80-
issuer='https://cloud.google.com/iap',
81-
audience=expected_audience)
78+
79+
# Verify token signature, expiry and audience.
80+
decoded_jwt = jwt.decode(iap_jwt, certs=key, audience=expected_audience)
81+
82+
# Verify token issuer.
83+
if decoded_jwt.get('iss') != 'https://cloud.google.com/iap':
84+
return (None, None, '**ERROR: invalid issuer**')
85+
8286
return (decoded_jwt['sub'], decoded_jwt['email'], '')
83-
except (jwt.exceptions.InvalidTokenError,
84-
requests.exceptions.RequestException) as e:
87+
except (ValueError, requests.exceptions.RequestException) as e:
8588
return (None, None, '**ERROR: JWT validation error {}**'.format(e))
8689

8790

0 commit comments

Comments
 (0)