From 59ff191be4260b969c15d298cbdd0247baa39461 Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Thu, 13 Jul 2017 16:25:14 -0700 Subject: [PATCH 1/7] Update validate_jwt to use new JWT header --- iap/validate_jwt.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/iap/validate_jwt.py b/iap/validate_jwt.py index b4de9c67f37..57e039910ce 100644 --- a/iap/validate_jwt.py +++ b/iap/validate_jwt.py @@ -27,18 +27,41 @@ import requests -def validate_iap_jwt(base_url, iap_jwt): +def validate_iap_jwt(cloud_project_number, iap_jwt, + app_engine_project_id=None, + compute_engine_backend_service_id=None): """Validate a JWT passed to your application by Identity-Aware Proxy. + One and only of of (app_engine_project_id, compute_engine_backend_service_id) + must be set. + Args: - base_url: The URL from the incoming request, minus any path, query, etc. - For instance: "https://example.com:8443" or - "https://example.appspot.com" . - iap_jwt: The contents of the X-Goog-Authenticated-User-JWT header. + cloud_project_number: The project *number* for your Google Cloud project. + This is returned by 'gcloud projects describe $PROJECT_ID', or + in the Project Info card in Cloud Console. + iap_jwt: The contents of the X-Goog-IAP-JWT-Assertion header. + app_engine_project_id: For App Engine resources, this must be set to + your cloud project ID. + compute_engine_backend_service_id: For Compute Engine and Container Engine + resources this must be set to the ID of the backend service used to + access the application. See + https://cloud.google.com/iap/docs/signed-headers-howto for details on + how to get this value. Returns: (user_id, user_email, error_str). """ + if not (bool(app_engine_project_id) ^ bool(compute_engine_backend_service_id)): + raise ValueError('One and only of of app_engine_project_id, ' + 'compute_engine_backend_service_id must be specified.') + + if app_engine_project_id: + expected_audience = '/projects/{}/apps/{}'.format( + cloud_project_number, app_engine_project_id) + else: + expected_audience = '/projects/{}/global/backendServices/{}'.format( + cloud_project_number, compute_engine_backend_service_id) + try: key_id = jwt.get_unverified_header(iap_jwt).get('kid') if not key_id: From 14fb4bae57ffe72528d1e23c6517afc46bafd268 Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Thu, 13 Jul 2017 16:32:11 -0700 Subject: [PATCH 2/7] Update validate_jwt.py --- iap/validate_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iap/validate_jwt.py b/iap/validate_jwt.py index 57e039910ce..4ee0036e26b 100644 --- a/iap/validate_jwt.py +++ b/iap/validate_jwt.py @@ -70,7 +70,7 @@ def validate_iap_jwt(cloud_project_number, iap_jwt, decoded_jwt = jwt.decode( iap_jwt, key, algorithms=['ES256'], - audience=base_url) + audience=expected_audience) return (decoded_jwt['sub'], decoded_jwt['email'], '') except (jwt.exceptions.InvalidTokenError, requests.exceptions.RequestException) as e: From c4ea93c6a6bb96620281e67ccea559c8ee636698 Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Fri, 14 Jul 2017 09:39:15 -0700 Subject: [PATCH 3/7] Update validate_jwt.py --- iap/validate_jwt.py | 56 +++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/iap/validate_jwt.py b/iap/validate_jwt.py index 4ee0036e26b..2f9d626444d 100644 --- a/iap/validate_jwt.py +++ b/iap/validate_jwt.py @@ -27,41 +27,47 @@ import requests -def validate_iap_jwt(cloud_project_number, iap_jwt, - app_engine_project_id=None, - compute_engine_backend_service_id=None): - """Validate a JWT passed to your application by Identity-Aware Proxy. - - One and only of of (app_engine_project_id, compute_engine_backend_service_id) - must be set. +def validate_iap_jwt_from_app_engine(iap_jwt, cloud_project_number, + cloud_project_id): + """Validate a JWT passed to your App Engine app by Identity-Aware Proxy. Args: + iap_jwt: The contents of the X-Goog-IAP-JWT-Assertion header. cloud_project_number: The project *number* for your Google Cloud project. This is returned by 'gcloud projects describe $PROJECT_ID', or in the Project Info card in Cloud Console. + cloud_project_id: The project *ID* for your Google Cloud project. + + Returns: + (user_id, user_email, error_str). + """ + expected_audience = '/projects/{}/apps/{}'.format( + cloud_project_number, cloud_project_id) + return _validate_iwp_jwt(iap_jwt, expected_audience) + + +def validate_iap_jwt_from_app_engine(iwp_jwt, cloud_project_number, + backend_service_id): + """Validate an Identity-Aware Proxy JWT for your (Compute|Container) Engine service. + + Args: iap_jwt: The contents of the X-Goog-IAP-JWT-Assertion header. - app_engine_project_id: For App Engine resources, this must be set to - your cloud project ID. - compute_engine_backend_service_id: For Compute Engine and Container Engine - resources this must be set to the ID of the backend service used to - access the application. See - https://cloud.google.com/iap/docs/signed-headers-howto for details on - how to get this value. + cloud_project_number: The project *number* for your Google Cloud project. + This is returned by 'gcloud projects describe $PROJECT_ID', or + in the Project Info card in Cloud Console. + backend_service_id: The ID of the backend service used to access the + application. See https://cloud.google.com/iap/docs/signed-headers-howto + for details on how to get this value. Returns: (user_id, user_email, error_str). """ - if not (bool(app_engine_project_id) ^ bool(compute_engine_backend_service_id)): - raise ValueError('One and only of of app_engine_project_id, ' - 'compute_engine_backend_service_id must be specified.') - - if app_engine_project_id: - expected_audience = '/projects/{}/apps/{}'.format( - cloud_project_number, app_engine_project_id) - else: - expected_audience = '/projects/{}/global/backendServices/{}'.format( - cloud_project_number, compute_engine_backend_service_id) - + expected_audience = '/projects/{}/global/backendServices/{}'.format( + cloud_project_number, backend_service_id) + return _validate_iap_jwt(iap_jwt, expected_audience) + + +def _validate_iap_jwt(iap_jwt, expected_audience): try: key_id = jwt.get_unverified_header(iap_jwt).get('kid') if not key_id: From 57b1ef9a2cf3609eae3ab6c6d73de4d93fdda5a1 Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Fri, 14 Jul 2017 10:41:59 -0700 Subject: [PATCH 4/7] Fix validate_iap_jwt_from_compute_engine function name --- iap/validate_jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iap/validate_jwt.py b/iap/validate_jwt.py index 2f9d626444d..a9649689d1f 100644 --- a/iap/validate_jwt.py +++ b/iap/validate_jwt.py @@ -46,8 +46,8 @@ def validate_iap_jwt_from_app_engine(iap_jwt, cloud_project_number, return _validate_iwp_jwt(iap_jwt, expected_audience) -def validate_iap_jwt_from_app_engine(iwp_jwt, cloud_project_number, - backend_service_id): +def validate_iap_jwt_from_compute_engine(iwp_jwt, cloud_project_number, + backend_service_id): """Validate an Identity-Aware Proxy JWT for your (Compute|Container) Engine service. Args: From 9faa1e688e4783e70a58c0dcf08e1c64f5237822 Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Fri, 14 Jul 2017 10:44:26 -0700 Subject: [PATCH 5/7] Update validate_jwt.py --- iap/validate_jwt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iap/validate_jwt.py b/iap/validate_jwt.py index a9649689d1f..eac62496762 100644 --- a/iap/validate_jwt.py +++ b/iap/validate_jwt.py @@ -44,11 +44,11 @@ def validate_iap_jwt_from_app_engine(iap_jwt, cloud_project_number, expected_audience = '/projects/{}/apps/{}'.format( cloud_project_number, cloud_project_id) return _validate_iwp_jwt(iap_jwt, expected_audience) - + def validate_iap_jwt_from_compute_engine(iwp_jwt, cloud_project_number, backend_service_id): - """Validate an Identity-Aware Proxy JWT for your (Compute|Container) Engine service. + """Validate an IAP JWT for your (Compute|Container) Engine service. Args: iap_jwt: The contents of the X-Goog-IAP-JWT-Assertion header. @@ -56,7 +56,8 @@ def validate_iap_jwt_from_compute_engine(iwp_jwt, cloud_project_number, This is returned by 'gcloud projects describe $PROJECT_ID', or in the Project Info card in Cloud Console. backend_service_id: The ID of the backend service used to access the - application. See https://cloud.google.com/iap/docs/signed-headers-howto + application. See + https://cloud.google.com/iap/docs/signed-headers-howto for details on how to get this value. Returns: From a8d81397468468ea17bda921edcb3ce96fd8683f Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Fri, 14 Jul 2017 10:46:39 -0700 Subject: [PATCH 6/7] Typo fix: iwp_jwt->iap_jwt --- iap/validate_jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iap/validate_jwt.py b/iap/validate_jwt.py index eac62496762..8a69e368874 100644 --- a/iap/validate_jwt.py +++ b/iap/validate_jwt.py @@ -43,10 +43,10 @@ def validate_iap_jwt_from_app_engine(iap_jwt, cloud_project_number, """ expected_audience = '/projects/{}/apps/{}'.format( cloud_project_number, cloud_project_id) - return _validate_iwp_jwt(iap_jwt, expected_audience) + return _validate_iap_jwt(iap_jwt, expected_audience) -def validate_iap_jwt_from_compute_engine(iwp_jwt, cloud_project_number, +def validate_iap_jwt_from_compute_engine(iap_jwt, cloud_project_number, backend_service_id): """Validate an IAP JWT for your (Compute|Container) Engine service. From 050833a40134f7bdfdd7878d026e80f1333c96f2 Mon Sep 17 00:00:00 2001 From: Matthew Sachs Date: Fri, 14 Jul 2017 10:50:49 -0700 Subject: [PATCH 7/7] Update test --- iap/app_engine_app/iap_demo.py | 2 +- iap/iap_test.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/iap/app_engine_app/iap_demo.py b/iap/app_engine_app/iap_demo.py index 04393d25330..a9ac74224dd 100644 --- a/iap/app_engine_app/iap_demo.py +++ b/iap/app_engine_app/iap_demo.py @@ -36,7 +36,7 @@ @app.route('/') def echo_jwt(): return 'x-goog-authenticated-user-jwt: {}'.format( - flask.request.headers.get('x-goog-authenticated-user-jwt')) + flask.request.headers.get('x-goog-iap-jwt-assertion')) @app.route('/identity') diff --git a/iap/iap_test.py b/iap/iap_test.py index a6119d62b70..0d43998c87f 100644 --- a/iap/iap_test.py +++ b/iap/iap_test.py @@ -30,6 +30,8 @@ REFLECT_SERVICE_HOSTNAME = 'gcp-devrel-iap-reflect.appspot.com' IAP_CLIENT_ID = ('320431926067-ldm6839p8l2sei41nlsfc632l4d0v2u1' '.apps.googleusercontent.com') +IAP_APP_ID = 'gcp-devrel-iap-reflect' +IAP_PROJECT_NUMBER = '320431926067' @flaky @@ -42,8 +44,8 @@ def test_main(capsys): 'https://{}/'.format(REFLECT_SERVICE_HOSTNAME), IAP_CLIENT_ID) iap_jwt = iap_jwt.split(': ').pop() - jwt_validation_result = validate_jwt.validate_iap_jwt( - 'https://{}'.format(REFLECT_SERVICE_HOSTNAME), iap_jwt) + jwt_validation_result = validate_jwt.validate_iap_jwt_from_app_engine( + iap_jwt, IAP_PROJECT_NUMBER, IAP_APP_ID) assert jwt_validation_result[0] assert jwt_validation_result[1]