diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 0000000..c03a91c --- /dev/null +++ b/auth/README.md @@ -0,0 +1,98 @@ +# Generate custom JWT for service-to-service authentication +There are two forms of JSON Web Token (JWT) signed by Google: A Google ID token or a custom JWT that +is signed only by the service account of the caller. The following two endpoint security definitions +can be used: +``` +securityDefinitions: + [ISSUER_NAME]: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + x-google-issuer: "[YOUR-SERVICE-ACCOUNT-EMAIL]" + x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/[YOUR-SERVICE-ACCOUNT-EMAIL]" +``` +``` +securityDefinitions: + google_id_token: + authorizationUrl: "" + flow: "implicit" + type: "oauth2" + x-google-issuer: "https://accounts.google.com" +``` +This is based on the following endpoint documentation (https://cloud.google.com/endpoints/docs/service-to-service-auth). + + +## Setup +Before using the script please run the following command to install python dependences: +``` +$pip install google-cloud +``` +You will also need to create and save the service account json file. + + +## Usage +To generate a custom JWT that is signed only by the service account use the following: +``` +$ python generate-jwt.py -h +usage: generate-jwt.py [-h] [-e EMAIL] [-g GROUPID] [-iss ISSUER] + aud service_account_file + +Python script generates a signed JWT token based on the input payload + +positional arguments: + aud Audience . This must match 'audience' in the security + configuration in the swagger spec. It can be any + string + service_account_file The path to your service account json file. + +optional arguments: + -h, --help show this help message and exit + -e EMAIL, --email EMAIL + Email claim in JWT + -g GROUPID, --groupId GROUPID + GroupId claim in JWT + -iss ISSUER, --issuer ISSUER + Issuer claim. This will also be used for sub claim +``` +To generate a Google ID token JWT use the following: +``` +$ python generate-google-id-jwt.py -h +usage: generate-google-id-jwt.py [-h] [-iss ISSUER] + aud service_account_file + +Python script generates a signed Google ID JWT token based on the input payload + +positional arguments: + aud Audience . This must match 'audience' in the security + configuration in the swagger spec. It can be any + string + service_account_file The path to your service account json file. + +optional arguments: + -h, --help show this help message and exit + -iss ISSUER, --issuer ISSUER + Issuer claim. This will also be used for sub claim +``` + + +## Examples +1. Generate JWT token without any custom claims +``` +$ python generate-jwt.py /path/to/service_account.json +``` +2. Generate a Google ID JWT token without any custom claims +``` +$ python generate-google-id-jwt.py /path/to/service_account.json +``` +3. Generate JWT token with email claim +``` +$ python generate-jwt.py -e alice@yahoo.com /path/to/service_account.json +``` +4. Generate JWT token with groupId claim +``` +$ python generate-jwt.py -g acme /path/to/service_account.json +``` +5. Generate JWT token with both email and groupId claim +``` +$ python generate-jwt.py -e alice@yahoo.com -g acme /path/to/service_account.json +``` diff --git a/auth/generate-google-id-jwt.py b/auth/generate-google-id-jwt.py new file mode 100755 index 0000000..ce347a6 --- /dev/null +++ b/auth/generate-google-id-jwt.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python script generates a Google ID token based on the input payload""" + +import base64 +import httplib +import json +import argparse +import time +import urllib + +import oauth2client.crypt +import googleapiclient.discovery +from oauth2client.service_account import ServiceAccountCredentials +from oauth2client.client import GoogleCredentials + +def generate_jwt(args): + """Generates a signed JSON Web Token using a service account. Based on https://cloud.google.com/endpoints/docs/service-to-service-auth""" + # Make sure the service account has "Service Account Token Creator" permissions in Google IAM + credentials = ServiceAccountCredentials.from_json_keyfile_name( + args.service_account_file).create_scoped(['https://www.googleapis.com/auth/cloud-platform']) + + service = googleapiclient.discovery.build( + serviceName='iam', version='v1', credentials=credentials) + + now = int(time.time()) + header_json = json.dumps({ + "typ": "JWT", + "alg": "RS256"}) + + payload_json = json.dumps({ + 'iat': now, + "exp": now + 3600, + 'iss': args.issuer if args.issuer else credentials.service_account_email, + "target_audience": 'https://' + args.aud, + "aud": "https://www.googleapis.com/oauth2/v4/token" + }) + + header_and_payload = '{}.{}'.format( + base64.urlsafe_b64encode(header_json), + base64.urlsafe_b64encode(payload_json)) + slist = service.projects().serviceAccounts().signBlob( + name="projects/-/serviceAccounts/" + credentials.service_account_email, + body={'bytesToSign': base64.b64encode(header_and_payload)}) + res = slist.execute() + signature = base64.urlsafe_b64encode( + base64.decodestring(res['signature'])) + signed_jwt = '{}.{}'.format(header_and_payload, signature) + + return signed_jwt + +def main(args): + """Request a Google ID token using a JWT.""" + params = urllib.urlencode({ + 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion': generate_jwt(args)}) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + conn = httplib.HTTPSConnection("www.googleapis.com") + conn.request("POST", "/oauth2/v4/token", params, headers) + res = json.loads(conn.getresponse().read()) + conn.close() + return res['id_token'] + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + # positional arguments + parser.add_argument( + "aud", + help="Audience . This must match 'audience' in the security configuration" + " in the swagger spec. It can be any string") + parser.add_argument( + 'service_account_file', + help='The path to your service account json file.') + + #optional arguments + parser.add_argument("-iss", "--issuer", help="Issuer claim. This will also be used for sub claim") + print main(parser.parse_args()) diff --git a/auth/generate-jwt.py b/auth/generate-jwt.py new file mode 100755 index 0000000..50cec83 --- /dev/null +++ b/auth/generate-jwt.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python script generates a signed JWT token based on the input payload""" + +import argparse +import time + +import oauth2client.crypt +from oauth2client.service_account import ServiceAccountCredentials + +def main(args): + """Generates a signed JSON Web Token using a Google API Service Account.""" + credentials = ServiceAccountCredentials.from_json_keyfile_name( + args.service_account_file) + + now = int(time.time()) + + payload = { + "exp": now + credentials.MAX_TOKEN_LIFETIME_SECS, + "iat": now, + "aud": args.aud, + } + + if args.email: + payload["email"] = args.email + if args.groupId: + payload["groupId"] = args.groupId + + if args.issuer: + payload["iss"] = args.issuer + payload["sub"] = args.issuer + else: + payload["iss"] = credentials.service_account_email + payload["sub"] = credentials.service_account_email + + signed_jwt = oauth2client.crypt.make_signed_jwt( + credentials._signer, payload, key_id=credentials._private_key_id) + + return signed_jwt + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + # positional arguments + parser.add_argument( + "aud", + help="Audience . This must match 'audience' in the security configuration" + " in the swagger spec. It can be any string") + parser.add_argument( + 'service_account_file', + help='The path to your service account json file.') + + #optional arguments + parser.add_argument("-e", "--email", help="Email claim in JWT") + parser.add_argument("-g", "--groupId", help="GroupId claim in JWT") + parser.add_argument("-iss", "--issuer", help="Issuer claim. This will also be used for sub claim") + print main(parser.parse_args())