From ccc6dd477e18fe26773064693fc42559fb4cac56 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Sun, 5 Apr 2020 23:45:54 -0400 Subject: [PATCH 1/4] Add code snippet and tests for Signed Cookies. --- cdn/snippets.py | 54 ++++++++++++++++++++++++++++++++++++++++++++ cdn/snippets_test.py | 23 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/cdn/snippets.py b/cdn/snippets.py index 027b5cb04f9..aee3819dcd5 100644 --- a/cdn/snippets.py +++ b/cdn/snippets.py @@ -71,6 +71,41 @@ def sign_url(http://github.com/url,%20key_name,%20base64_key,%20expiration_time): # [END sign_url] +# [START sign_cookie] +def sign_cookie(url_prefix, key_name, base64_key, expiration_time): + """Gets the Signed cookie value for the specified URL prefix and configuration. + + Args: + url_prefix: URL prefix to sign as a string. + key_name: name of the signing key as a string. + base64_key: signing key as a base64 encoded string. + expiration_time: expiration time as a UTC datetime object. + + Returns: + Returns the Cloud-CDN-Cookie value based on the specified configuration. + """ + encoded_url_prefix = base64.urlsafe_b64encode( + url_prefix.strip()).decode('utf-8') + epoch = datetime.datetime.utcfromtimestamp(0) + expiration_timestamp = int((expiration_time - epoch).total_seconds()) + decoded_key = base64.urlsafe_b64decode(base64_key) + + policy_pattern = u'URLPrefix={encoded_url_prefix}:Expires={expires}:KeyName={key_name}' + policy = policy_pattern.format( + encoded_url_prefix=encoded_url_prefix, + expires=expiration_timestamp, + key_name=key_name) + + digest = hmac.new( + decoded_key, policy.encode('utf-8'), hashlib.sha1).digest() + signature = base64.urlsafe_b64encode(digest).decode('utf-8') + + signed_policy = u'Cloud-CDN-Cookie={policy}:Signature={signature}'.format( + policy=policy, signature=signature) + print(signed_policy) +# [END sign_cookie] + + if __name__ == '__main__': parser = argparse.ArgumentParser( description=__doc__, @@ -94,8 +129,27 @@ def sign_url(http://github.com/url,%20key_name,%20base64_key,%20expiration_time): type=lambda d: datetime.datetime.utcfromtimestamp(float(d)), help='Expiration time expessed as seconds since the epoch.') + sign_cookie_parser = subparsers.add_parser( + 'sign-cookie', + help="Generate a signed cookie to grant temporary authorized access.") + sign_cookie_parser.add_argument( + 'url_prefix', help='The URL prefix to sign.') + sign_cookie_parser.add_argument( + 'key_name', + help='Key name for the signing key.') + sign_cookie_parser.add_argument( + 'base64_key', + help='The base64 encoded signing key.') + sign_cookie_parser.add_argument( + 'expiration_time', + type=lambda d: datetime.datetime.utcfromtimestamp(float(d)), + help='Expiration time expessed as seconds since the epoch.') + args = parser.parse_args() if args.command == 'sign-url': sign_url( args.url, args.key_name, args.base64_key, args.expiration_time) + elif args.command == 'sign-cookie': + sign_cookie( + args.url_prefix, args.key_name, args.base64_key, args.expiration_time) diff --git a/cdn/snippets_test.py b/cdn/snippets_test.py index 245daea13cd..f447ece7690 100644 --- a/cdn/snippets_test.py +++ b/cdn/snippets_test.py @@ -50,3 +50,26 @@ def test_sign_url(http://github.com/capsys): assert results[2] == ( 'http://www.example.com/some/path?some=query&another=param&Expires=' '1549751401&KeyName=my-key&Signature=9Q9TCxSju8-W5nUkk5CuTrun2_o=') + + +def test_sign_cookie(capsys): + snippets.sign_cookie( + 'http://35.186.234.33/index.html', + 'my-key', + 'nZtRohdNF9m3cKM24IcK4w==', + datetime.datetime.utcfromtimestamp(1549751401)) + snippets.sign_cookie( + 'http://www.example.com/foo/', + 'my-key', + 'nZtRohdNF9m3cKM24IcK4w==', + datetime.datetime.utcfromtimestamp(1549751401)) + + out, _ = capsys.readouterr() + + results = out.splitlines() + assert results[0] == ( + 'Cloud-CDN-Cookie=URLPrefix=aHR0cDovLzM1LjE4Ni4yMzQuMzMvaW5kZXguaHRtbA==:' + 'Expires=1549751401:KeyName=my-key:Signature=uImwlOBCPs91mlCyG9vyyZRrNWU=') + assert results[1] == ( + 'Cloud-CDN-Cookie=URLPrefix=aHR0cDovL3d3dy5leGFtcGxlLmNvbS9mb28v:' + 'Expires=1549751401:KeyName=my-key:Signature=Z9uYAu73YHioRScZDxnP-TnS274=') From 25c9794fc607bf193cac383a9c5f6c09f5897417 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Fri, 10 Apr 2020 01:13:22 -0400 Subject: [PATCH 2/4] Address code review comments, and add code example for Signed URL prefix. --- .github/CODEOWNERS | 2 +- cdn/snippets.py | 76 +++++++++++++++++++++++++++++++++++++++++--- cdn/snippets_test.py | 35 ++++++++++++++++++++ 3 files changed, 107 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bd31cd59fdd..a1137e16859 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,7 +15,7 @@ /bigquery_storage/ @shollyman @GoogleCloudPlatform/python-samples-owners /bigtable/ @billyjacobson @GoogleCloudPlatform/python-samples-owners /blog/ @GoogleCloudPlatform/python-samples-owners -/cdn/ @GoogleCloudPlatform/python-samples-owners +/cdn/ @mpwarres @elithrar @GoogleCloudPlatform/python-samples-owners /cloud-sql/ @kvg @GoogleCloudPlatform/python-samples-owners /codelabs/ @GoogleCloudPlatform/python-samples-owners /composer/ @leahecole @GoogleCloudPlatform/python-samples-owners diff --git a/cdn/snippets.py b/cdn/snippets.py index aee3819dcd5..52b70a00201 100644 --- a/cdn/snippets.py +++ b/cdn/snippets.py @@ -30,7 +30,7 @@ from six.moves import urllib -# [START sign_url] +# [START cdn_sign_url] def sign_url(http://github.com/url,%20key_name,%20base64_key,%20expiration_time): """Gets the Signed URL string for the specified URL and configuration. @@ -68,10 +68,55 @@ def sign_url(http://github.com/url,%20key_name,%20base64_key,%20expiration_time): url=url_to_sign, signature=signature) print(signed_url) -# [END sign_url] +# [END cdn_sign_url] -# [START sign_cookie] +# [START cdn_sign_url_prefix] +def sign_url_prefix(url, url_prefix, key_name, base64_key, expiration_time): + """Gets the Signed URL string for the specified URL prefix and configuration. + + Args: + url: URL of request. + url_prefix: URL prefix to sign as a string. + key_name: name of the signing key as a string. + base64_key: signing key as a base64 encoded string. + expiration_time: expiration time as a UTC datetime object. + + Returns: + Returns the Signed URL appended with the query parameters based on the + specified URL prefix and configuration. + """ + stripped_url = url.strip() + parsed_url = urllib.parse.urlsplit(stripped_url) + query_params = urllib.parse.parse_qs( + parsed_url.query, keep_blank_values=True) + encoded_url_prefix = base64.urlsafe_b64encode( + url_prefix.strip().encode('utf-8')).decode('utf-8') + epoch = datetime.datetime.utcfromtimestamp(0) + expiration_timestamp = int((expiration_time - epoch).total_seconds()) + decoded_key = base64.urlsafe_b64decode(base64_key) + + policy_pattern = u'URLPrefix={encoded_url_prefix}&Expires={expires}&KeyName={key_name}' + policy = policy_pattern.format( + encoded_url_prefix=encoded_url_prefix, + expires=expiration_timestamp, + key_name=key_name) + + digest = hmac.new( + decoded_key, policy.encode('utf-8'), hashlib.sha1).digest() + signature = base64.urlsafe_b64encode(digest).decode('utf-8') + + signed_url = u'{url}{separator}{policy}&Signature={signature}'.format( + url=stripped_url, + separator='&' if query_params else '?', + policy=policy, + signature=signature) + + print(signed_url) +# [END cdn_sign_url_prefix] + + +# [START cdn_sign_cookie] def sign_cookie(url_prefix, key_name, base64_key, expiration_time): """Gets the Signed cookie value for the specified URL prefix and configuration. @@ -85,7 +130,7 @@ def sign_cookie(url_prefix, key_name, base64_key, expiration_time): Returns the Cloud-CDN-Cookie value based on the specified configuration. """ encoded_url_prefix = base64.urlsafe_b64encode( - url_prefix.strip()).decode('utf-8') + url_prefix.strip().encode('utf-8')).decode('utf-8') epoch = datetime.datetime.utcfromtimestamp(0) expiration_timestamp = int((expiration_time - epoch).total_seconds()) decoded_key = base64.urlsafe_b64decode(base64_key) @@ -103,7 +148,7 @@ def sign_cookie(url_prefix, key_name, base64_key, expiration_time): signed_policy = u'Cloud-CDN-Cookie={policy}:Signature={signature}'.format( policy=policy, signature=signature) print(signed_policy) -# [END sign_cookie] +# [END cdn_sign_cookie] if __name__ == '__main__': @@ -129,6 +174,24 @@ def sign_cookie(url_prefix, key_name, base64_key, expiration_time): type=lambda d: datetime.datetime.utcfromtimestamp(float(d)), help='Expiration time expessed as seconds since the epoch.') + sign_url_prefix_parser = subparsers.add_parser( + 'sign-url-prefix', + help="Sign a URL prefix to grant temporary authorized access.") + sign_url_prefix_parser.add_argument( + 'url', help='The request URL.') + sign_cookie_parser.add_argument( + 'url_prefix', help='The URL prefix to sign.') + sign_url_prefix_parser.add_argument( + 'key_name', + help='Key name for the signing key.') + sign_url_prefix_parser.add_argument( + 'base64_key', + help='The base64 encoded signing key.') + sign_url_prefix_parser.add_argument( + 'expiration_time', + type=lambda d: datetime.datetime.utcfromtimestamp(float(d)), + help='Expiration time expessed as seconds since the epoch.') + sign_cookie_parser = subparsers.add_parser( 'sign-cookie', help="Generate a signed cookie to grant temporary authorized access.") @@ -150,6 +213,9 @@ def sign_cookie(url_prefix, key_name, base64_key, expiration_time): if args.command == 'sign-url': sign_url( args.url, args.key_name, args.base64_key, args.expiration_time) + elif args.command == 'sign-url-prefix': + sign_url_prefix( + args.url, args.url_prefix, args.key_name, args.base64_key, args.expiration_time) elif args.command == 'sign-cookie': sign_cookie( args.url_prefix, args.key_name, args.base64_key, args.expiration_time) diff --git a/cdn/snippets_test.py b/cdn/snippets_test.py index f447ece7690..c14dd5dc141 100644 --- a/cdn/snippets_test.py +++ b/cdn/snippets_test.py @@ -52,6 +52,41 @@ def test_sign_url(http://github.com/capsys): '1549751401&KeyName=my-key&Signature=9Q9TCxSju8-W5nUkk5CuTrun2_o=') +def test_sign_url_prefix(capsys): + snippets.sign_url_prefix( + 'http://35.186.234.33/index.html', + 'http://35.186.234.33/', + 'my-key', + 'nZtRohdNF9m3cKM24IcK4w==', + datetime.datetime.utcfromtimestamp(1549751401)) + snippets.sign_url_prefix( + 'http://www.example.com/', + 'http://www.example.com/', + 'my-key', + 'nZtRohdNF9m3cKM24IcK4w==', + datetime.datetime.utcfromtimestamp(1549751401)) + snippets.sign_url_prefix( + 'http://www.example.com/some/path?some=query&another=param', + 'http://www.example.com/some/', + 'my-key', + 'nZtRohdNF9m3cKM24IcK4w==', + datetime.datetime.utcfromtimestamp(1549751401)) + + out, _ = capsys.readouterr() + + results = out.splitlines() + assert results[0] == ( + 'http://35.186.234.33/index.html?URLPrefix=aHR0cDovLzM1LjE4Ni4yMzQuMzMv&' + 'Expires=1549751401&KeyName=my-key&Signature=j7HYgoQ8dIOVsW3Rw4cpkjWfRMA=') + assert results[1] == ( + 'http://www.example.com/?URLPrefix=aHR0cDovL3d3dy5leGFtcGxlLmNvbS8=&' + 'Expires=1549751401&KeyName=my-key&Signature=UdT5nVks6Hh8QFMJI9kmXuXYBk0=') + assert results[2] == ( + 'http://www.example.com/some/path?some=query&another=param&' + 'URLPrefix=aHR0cDovL3d3dy5leGFtcGxlLmNvbS9zb21lLw==&' + 'Expires=1549751401&KeyName=my-key&Signature=3th4ThmpS95I1TAKYyYSCSq3dnQ=') + + def test_sign_cookie(capsys): snippets.sign_cookie( 'http://35.186.234.33/index.html', From 0b65e20c3d5f5b9d64472c493fac69ae9fe3d19b Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Mon, 27 Apr 2020 21:55:19 -0400 Subject: [PATCH 3/4] Fix lint error. --- cdn/snippets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdn/snippets.py b/cdn/snippets.py index 52b70a00201..74d61830cf1 100644 --- a/cdn/snippets.py +++ b/cdn/snippets.py @@ -179,7 +179,7 @@ def sign_cookie(url_prefix, key_name, base64_key, expiration_time): help="Sign a URL prefix to grant temporary authorized access.") sign_url_prefix_parser.add_argument( 'url', help='The request URL.') - sign_cookie_parser.add_argument( + sign_url_prefix_parser.add_argument( 'url_prefix', help='The URL prefix to sign.') sign_url_prefix_parser.add_argument( 'key_name', From 450710215c780ee134a22ccf58b899b0f08ecb22 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Mon, 27 Apr 2020 22:07:55 -0400 Subject: [PATCH 4/4] Group signed URL examples in single region, and adjust region tag to match existing documentation. --- cdn/snippets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cdn/snippets.py b/cdn/snippets.py index 74d61830cf1..2873118f4ce 100644 --- a/cdn/snippets.py +++ b/cdn/snippets.py @@ -30,7 +30,7 @@ from six.moves import urllib -# [START cdn_sign_url] +# [START sign_url] def sign_url(http://github.com/url,%20key_name,%20base64_key,%20expiration_time): """Gets the Signed URL string for the specified URL and configuration. @@ -68,10 +68,8 @@ def sign_url(http://github.com/url,%20key_name,%20base64_key,%20expiration_time): url=url_to_sign, signature=signature) print(signed_url) -# [END cdn_sign_url] -# [START cdn_sign_url_prefix] def sign_url_prefix(url, url_prefix, key_name, base64_key, expiration_time): """Gets the Signed URL string for the specified URL prefix and configuration. @@ -113,7 +111,7 @@ def sign_url_prefix(url, url_prefix, key_name, base64_key, expiration_time): signature=signature) print(signed_url) -# [END cdn_sign_url_prefix] +# [END sign_url] # [START cdn_sign_cookie]