Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1af1c30

Browse filesBrowse files
matthewgwaprin
authored andcommitted
Code samples for Identity-Aware Proxy (GoogleCloudPlatform#845)
1 parent 53bebcc commit 1af1c30
Copy full SHA for 1af1c30

File tree

Expand file treeCollapse file tree

7 files changed

+422
-0
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+422
-0
lines changed

‎iap/README.md

Copy file name to clipboard
+77Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Identity-Aware Proxy Samples
2+
3+
<!-- auto-doc-link -->
4+
These samples are used on the following documentation pages:
5+
6+
>
7+
* https://cloud.google.com/iap/docs/authentication-howto
8+
* https://cloud.google.com/iap/docs/signed-headers-howto
9+
10+
<!-- end-auto-doc-link -->
11+
12+
## Using make_iap_request
13+
14+
### Google App Engine flexible environment
15+
16+
1. Add the contents of this directory's `requirements.txt` file to the one
17+
inside your application.
18+
2. Copy `make_iap_request.py` into your application.
19+
20+
### Google App Engine standard environment
21+
22+
1. Follow the instructions
23+
in
24+
[Installing a third-party library](https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27#installing_a_third-party_library) to
25+
install the `google-auth` and `requests` libraries into your application.
26+
2. Copy `make_iap_request.py` into the same folder as app.yaml .
27+
28+
### Google Compute Engine or Google Container Engine
29+
30+
1. Enable the IAM API on your project.
31+
2. Create a VM with the IAM scope:
32+
```gcloud compute instances create INSTANCE_NAME
33+
--scopes=https://www.googleapis.com/auth/iam```
34+
3. Give your VM's default service account the `Service Account Actor` role:
35+
```gcloud projects add-iam-policy-binding PROJET_ID
36+
--role=roles/iam.serviceAccountActor
37+
--member=serviceAccount:SERVICE_ACCOUNT```
38+
4. Install the libraries listed in `requirements.txt`, e.g. by running:
39+
```virtualenv/bin/pip install -r requirements.txt```
40+
5. Copy `make_iap_request.py` into your application.
41+
42+
### Using a downloaded service account private key
43+
44+
1. Create a service account and download its private key.
45+
See https://cloud.google.com/iam/docs/creating-managing-service-account-keys
46+
for more information on how to do this.
47+
2. Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to the path
48+
to your service account's `.json` file.
49+
3. Install the libraries listed in `requirements.txt`, e.g. by running:
50+
```virtualenv/bin/pip install -r requirements.txt```
51+
4. Copy `make_iap_request.py` into your application.
52+
53+
If you prefer to manage service account credentials manually, this method can
54+
also be used in the App Engine flexible environment, Compute Engine, and
55+
Container Engine. Note that this may be less secure, as anyone who obtains the
56+
service account private key can impersonate that account!
57+
58+
## Using validate_jwt
59+
60+
`validate_jwt` is not compatible with App Engine standard environment;
61+
use App Engine's Users API instead. (See `app_engine_app` for an example
62+
of how to do this.)
63+
64+
For all other environments:
65+
66+
1. Install the libraries listed in `requirements.txt`, e.g. by running:
67+
```virtualenv/bin/pip install -r requirements.txt```
68+
2. Copy `validate_jwt.py` into your application.
69+
70+
## Running Tests
71+
72+
1. Deploy `app_engine_app` to a project.
73+
2. Enable Identity-Aware Proxy on that project's App Engine app.
74+
3. Add the service account you'll be running the test as to the
75+
Identity-Aware Proxy access list for the project.
76+
4. Update iap_test.py with the hostname for your project.
77+
5. Run the command: ```GOOGLE_CLOUD_PROJECT=project-id pytest iap_test.py```

‎iap/app_engine_app/app.yaml

Copy file name to clipboard
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
runtime: python27
2+
api_version: 1
3+
threadsafe: true
4+
5+
handlers:
6+
- url: /.*
7+
script: iap_demo.app
8+
9+
libraries:
10+
- name: webapp2
11+
version: latest

‎iap/app_engine_app/iap_demo.py

Copy file name to clipboard
+45Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Demo App Engine standard environment app for Identity-Aware Proxy.
16+
17+
The / handler returns the contents of the JWT header, for use in
18+
iap_test.py.
19+
20+
The /identity handler demonstrates how to use the Google App Engine
21+
standard environment's Users API to obtain the identity of users
22+
authenticated by Identity-Aware Proxy.
23+
24+
To deploy this app, follow the instructions in
25+
https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27#installing_a_third-party_library
26+
to install the flask library into your application.
27+
"""
28+
import flask
29+
30+
from google.appengine.api import users
31+
32+
33+
app = flask.Flask(__name__)
34+
35+
36+
@app.route('/')
37+
def echo_jwt():
38+
return flask.request.headers.get('x-goog-authenticated-user-jwt')
39+
40+
41+
@app.route('/identity')
42+
def show_identity():
43+
user = users.get_current_user()
44+
return '<p>Authenticated as {} ({}).</p>'.format(
45+
user.email(), user.user_id())

‎iap/iap_test.py

Copy file name to clipboard
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test script for Identity-Aware Proxy code samples."""
16+
import os
17+
18+
from gcp.testing.flaky import flaky
19+
20+
import make_iap_request
21+
import validate_jwt
22+
23+
24+
# The hostname of an application protected by Identity-Aware Proxy.
25+
# When a request is made to https://${JWT_REFLECT_HOSTNAME}/, the
26+
# application should respond with the value of the
27+
# X-Goog-Authenticated-User-JWT (and nothing else.) The
28+
# app_engine_app/ subdirectory contains an App Engine standard
29+
# environment app that does this.
30+
GOOGLE_CLOUD_PROJECT = os.getenv('GOOGLE_CLOUD_PROJECT')
31+
assert GOOGLE_CLOUD_PROJECT
32+
JWT_REFLECT_HOSTNAME = '{}.appspot.com'.format(GOOGLE_CLOUD_PROJECT)
33+
34+
35+
@flaky
36+
def test_main(cloud_config, capsys):
37+
# JWTs are obtained by IAP-protected applications whenever an
38+
# end-user makes a request. We've set up an app that echoes back
39+
# the JWT in order to expose it to this test. Thus, this test
40+
# exercises both make_iap_request and validate_jwt.
41+
iap_jwt = make_iap_request.make_iap_request(
42+
'https://{}/'.format(JWT_REFLECT_HOSTNAME))
43+
jwt_validation_result = validate_jwt.validate_iap_jwt(
44+
'https://{}'.format(JWT_REFLECT_HOSTNAME), iap_jwt)
45+
assert jwt_validation_result[0]
46+
assert jwt_validation_result[1]
47+
assert not jwt_validation_result[2]

‎iap/make_iap_request.py

Copy file name to clipboard
+154Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Example use of a service account to authenticate to Identity-Aware Proxy."""
16+
17+
# [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
22+
from google.auth.transport.requests import Request
23+
import google.oauth2.credentials
24+
import google.oauth2.service_account
25+
import requests
26+
import requests_toolbelt.adapters.appengine
27+
from six.moves import urllib_parse as urlparse
28+
29+
30+
IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
31+
OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'
32+
33+
34+
def make_iap_request(url):
35+
"""Makes a request to an application protected by Identity-Aware Proxy.
36+
37+
Args:
38+
url: The Identity-Aware Proxy-protected URL to fetch.
39+
40+
Returns:
41+
The page body, or raises an exception if the page couldn't be retrieved.
42+
"""
43+
# Take the input URL and remove everything except the protocol, domain,
44+
# and port. Examples:
45+
# https://foo.example.com/ => https://foo.example.com
46+
# https://example.com:8443/foo/bar?quuz=quux#lorem =>
47+
# https://example.com:8443
48+
base_url = urlparse.urlunparse(
49+
urlparse.urlparse(url)._replace(path='', query='', fragment=''))
50+
51+
# Figure out what environment we're running in and get some preliminary
52+
# information about the service account.
53+
bootstrap_credentials, _ = google.auth.default(
54+
scopes=[IAM_SCOPE])
55+
if isinstance(bootstrap_credentials,
56+
google.oauth2.credentials.Credentials):
57+
raise Exception('make_iap_request is only supported for service '
58+
'accounts.')
59+
elif isinstance(bootstrap_credentials,
60+
google.auth.app_engine.Credentials):
61+
requests_toolbelt.adapters.appengine.monkeypatch()
62+
63+
# For service account's using the Compute Engine metadata service,
64+
# service_account_email isn't available until refresh is called.
65+
bootstrap_credentials.refresh(Request())
66+
67+
signer_email = bootstrap_credentials.service_account_email
68+
if isinstance(bootstrap_credentials,
69+
google.auth.compute_engine.credentials.Credentials):
70+
# Since the Compute Engine metadata service doesn't expose the service
71+
# account key, we use the IAM signBlob API to sign instead.
72+
# In order for this to work:
73+
#
74+
# 1. Your VM needs the https://www.googleapis.com/auth/iam scope.
75+
# You can specify this specific scope when creating a VM
76+
# through the API or gcloud. When using Cloud Console,
77+
# you'll need to specify the "full access to all Cloud APIs"
78+
# scope. A VM's scopes can only be specified at creation time.
79+
#
80+
# 2. The VM's default service account needs the "Service Account Actor"
81+
# role. This can be found under the "Project" category in Cloud
82+
# Console, or roles/iam.serviceAccountActor in gcloud.
83+
signer = google.auth.iam.Signer(
84+
Request(), bootstrap_credentials, signer_email)
85+
else:
86+
# A Signer object can sign a JWT using the service account's key.
87+
signer = bootstrap_credentials.signer
88+
89+
# Construct OAuth 2.0 service account credentials using the signer
90+
# and email acquired from the bootstrap credentials.
91+
service_account_credentials = google.oauth2.service_account.Credentials(
92+
signer, signer_email, token_uri=OAUTH_TOKEN_URI, additional_claims={
93+
'target_audience': base_url
94+
})
95+
96+
# service_account_credentials gives us a JWT signed by the service
97+
# account. Next, we use that to obtain an OpenID Connect token,
98+
# which is a JWT signed by Google.
99+
google_open_id_connect_token = get_google_open_id_connect_token(
100+
service_account_credentials)
101+
102+
# Fetch the Identity-Aware Proxy-protected URL, including an
103+
# Authorization header containing "Bearer " followed by a
104+
# Google-issued OpenID Connect token for the service account.
105+
resp = requests.get(
106+
url,
107+
headers={'Authorization': 'Bearer {}'.format(
108+
google_open_id_connect_token)})
109+
if resp.status_code == 403:
110+
raise Exception('Service account {} does not have permission to '
111+
'access the IAP-protected application.'.format(
112+
signer_email))
113+
elif resp.status_code != 200:
114+
raise Exception(
115+
'Bad response from application: {!r} / {!r} / {!r}'.format(
116+
resp.status_code, resp.headers, resp.text))
117+
else:
118+
return resp.text
119+
120+
121+
def get_google_open_id_connect_token(service_account_credentials):
122+
"""Get an OpenID Connect token issued by Google for the service account.
123+
124+
This function:
125+
126+
1. Generates a JWT signed with the service account's private key
127+
containing a special "target_audience" claim.
128+
129+
2. Sends it to the OAUTH_TOKEN_URI endpoint. Because the JWT in #1
130+
has a target_audience claim, that endpoint will respond with
131+
an OpenID Connect token for the service account -- in other words,
132+
a JWT signed by *Google*. The aud claim in this JWT will be
133+
set to the value from the target_audience claim in #1.
134+
135+
For more information, see
136+
https://developers.google.com/identity/protocols/OAuth2ServiceAccount .
137+
The HTTP/REST example on that page describes the JWT structure and
138+
demonstrates how to call the token endpoint. (The example on that page
139+
shows how to get an OAuth2 access token; this code is using a
140+
modified version of it to get an OpenID Connect token.)
141+
"""
142+
143+
service_account_jwt = (
144+
service_account_credentials._make_authorization_grant_assertion())
145+
request = google.auth.transport.requests.Request()
146+
body = {
147+
'assertion': service_account_jwt,
148+
'grant_type': google.oauth2._client._JWT_GRANT_TYPE,
149+
}
150+
token_response = google.oauth2._client._token_endpoint_request(
151+
request, OAUTH_TOKEN_URI, body)
152+
return token_response['id_token']
153+
154+
# [END iap_make_request]

‎iap/requirements.txt

Copy file name to clipboard
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PyJWT==1.4.2
2+
cryptography==1.7.2
3+
google-auth==0.8.0
4+
requests==2.13.0
5+
requests_toolbelt==0.7.1

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.