From 6a0409501fe120e128fa3df0627fb1283d05f201 Mon Sep 17 00:00:00 2001 From: Nathan Gillett Date: Fri, 20 Oct 2023 08:28:08 -0500 Subject: [PATCH 1/2] Signatures in redirect requests: minor fixes [RHELDST-18071] This commit includes some small fixes to the recent addition of CloudFront signatures in redirect requests. - Path cookie header: wildcard removed - Domain cookie header: URL scheme removed - Set-Cookies querystring param renamed to avoid confusion - Adds separate cookie ttl setting --- exodus_gw/routers/cdn.py | 28 ++++++++------- exodus_gw/settings.py | 3 ++ tests/routers/test_cdn.py | 74 +++++++++++---------------------------- 3 files changed, 40 insertions(+), 65 deletions(-) diff --git a/exodus_gw/routers/cdn.py b/exodus_gw/routers/cdn.py index 7b85a6ef..938192f2 100644 --- a/exodus_gw/routers/cdn.py +++ b/exodus_gw/routers/cdn.py @@ -75,7 +75,7 @@ def cf_cookie(url: str, env: Environment, expires: datetime, username: str): } -def sign_url(url: str, timeout: int, env: Environment, username: str): +def sign_url(url: str, settings: Settings, env: Environment, username: str): if not env.cdn_url: LOG.error( "Missing cdn_url in exodus-gw environment settings", @@ -104,7 +104,12 @@ def sign_url(url: str, timeout: int, env: Environment, username: str): ) dest_url = os.path.join(env.cdn_url, url) - expires = datetime.now(timezone.utc) + timedelta(seconds=timeout) + signature_expires = datetime.now(timezone.utc) + timedelta( + seconds=settings.cdn_signature_timeout + ) + cookie_expires = datetime.now(timezone.utc) + timedelta( + seconds=settings.cdn_cookie_ttl + ) LOG.info( "redirecting %s to %s. . .", @@ -113,26 +118,25 @@ def sign_url(url: str, timeout: int, env: Environment, username: str): extra={"event": "cdn", "success": True}, ) - policy = build_policy(dest_url, expires) + policy = build_policy(dest_url, signature_expires) signature = rsa_signer(env.cdn_private_key, policy) cookies = [] - for resource in ("/content/*", "/origin/*"): + for resource in ("/content/", "/origin/"): parsed_url = urlparse(env.cdn_url) - base_url = f"{parsed_url.scheme}://{parsed_url.netloc}" - policy_url = f"{base_url}{resource}" - cookie = cf_cookie(policy_url, env, expires, username) + policy_url = f"{parsed_url.scheme}://{parsed_url.netloc}{resource}*" + cookie = cf_cookie(policy_url, env, cookie_expires, username) append = ( - f"; Secure; HttpOnly; SameSite=lax; Domain={base_url}; " - f"Path={resource}; Max-Age={timeout}" + f"; Secure; HttpOnly; SameSite=lax; Domain={parsed_url.netloc}; " + f"Path={resource}; Max-Age={settings.cdn_cookie_ttl}" ) cookies.extend([f"{k}={v}{append}" for k, v in cookie.items()]) cookies_bytes = bytes(json.dumps(cookies), "utf-8") params = [ - "Expires=%s" % int(datetime2timestamp(expires)), + "Expires=%s" % int(datetime2timestamp(signature_expires)), "Signature=%s" % cf_b64(signature).decode("utf8"), - "Set-Cookies=%s" % cf_b64(cookies_bytes).decode("utf-8"), + "CloudFront-Cookies=%s" % cf_b64(cookies_bytes).decode("utf-8"), "Key-Pair-Id=%s" % env.cdn_key_id, ] separator = "&" if "?" in url else "?" @@ -195,7 +199,7 @@ def cdn_redirect( or call_context.user.internalUsername or "" ) - signed_url = sign_url(url, settings.cdn_signature_timeout, env, username) + signed_url = sign_url(url, settings, env, username) return Response( content=None, headers={"location": signed_url}, status_code=302 ) diff --git a/exodus_gw/settings.py b/exodus_gw/settings.py index cb697011..dc229752 100644 --- a/exodus_gw/settings.py +++ b/exodus_gw/settings.py @@ -207,6 +207,9 @@ class Settings(BaseSettings): """Delay, in minutes, after exodus-gw workers start up before any scheduled tasks should run.""" + cdn_cookie_ttl: int = 60 * 720 + """Time (in seconds) cookies generated by ``cdn-redirect`` remain valid.""" + cdn_signature_timeout: int = 60 * 30 """Time (in seconds) signed URLs remain valid.""" diff --git a/tests/routers/test_cdn.py b/tests/routers/test_cdn.py index 975428b2..c033ca39 100644 --- a/tests/routers/test_cdn.py +++ b/tests/routers/test_cdn.py @@ -61,42 +61,12 @@ def test_cdn_redirect_(monkeypatch, dummy_private_key, caplog): get_r = client.get("/test/cdn/some/url", follow_redirects=False) head_r = client.head("/test/cdn/some/url", follow_redirects=False) - expected_cookies = ( - "WyJDbG91ZEZyb250LUtleS1QYWlyLUlkPVhYWFhYWFhYWFhYWFhYOyBTZWN1cmU7IEh0d" - "HBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1odHRwOi8vbG9jYWxob3N0OjgwNDk7IF" - "BhdGg9L2NvbnRlbnQvKjsgTWF4LUFnZT0xODAwIiwgIkNsb3VkRnJvbnQtUG9saWN5PWV" - "5SlRkR0YwWlcxbGJuUWlPbHQ3SWxKbGMyOTFjbU5sSWpvaWFIUjBjRG92TDJ4dlkyRnNh" - "Rzl6ZERvNE1EUTVMMk52Ym5SbGJuUXZLaUlzSWtOdmJtUnBkR2x2YmlJNmV5SkVZWFJsV" - "EdWemMxUm9ZVzRpT25zaVFWZFRPa1Z3YjJOb1ZHbHRaU0k2TVRZME5EazNNVFF3TUgxOW" - "ZWMTk7IFNlY3VyZTsgSHR0cE9ubHk7IFNhbWVTaXRlPWxheDsgRG9tYWluPWh0dHA6Ly9" - "sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vY29udGVudC8qOyBNYXgtQWdlPTE4MDAiLCAiQ2xv" - "dWRGcm9udC1TaWduYXR1cmU9UjZrLUJHdk5nSlJzMy1HVzBCdmd0ZlF3RUJ0Z1R4R3Z5W" - "WRXVTAzZlBDR2xRTUY4RVpWRFdJcVRkSE9Qem5yQ3RuVFlWWnZ2Y3c0VX5WRXNTaWY3NV" - "c0eC1xZW9xbn5ZSDlZVmFnMnZOQVlwQkV0d21JMDhNcnZOUGlyQ2dPYmNMdnlXMU9yeEx" - "oSFZEeWNzQWRGRmFudW91RUF5a0J0c0dNbkh2bVJvR1JBXzsgU2VjdXJlOyBIdHRwT25s" - "eTsgU2FtZVNpdGU9bGF4OyBEb21haW49aHR0cDovL2xvY2FsaG9zdDo4MDQ5OyBQYXRoP" - "S9jb250ZW50Lyo7IE1heC1BZ2U9MTgwMCIsICJDbG91ZEZyb250LUtleS1QYWlyLUlkPV" - "hYWFhYWFhYWFhYWFhYOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWF" - "pbj1odHRwOi8vbG9jYWxob3N0OjgwNDk7IFBhdGg9L29yaWdpbi8qOyBNYXgtQWdlPTE4" - "MDAiLCAiQ2xvdWRGcm9udC1Qb2xpY3k9ZXlKVGRHRjBaVzFsYm5RaU9sdDdJbEpsYzI5M" - "WNtTmxJam9pYUhSMGNEb3ZMMnh2WTJGc2FHOXpkRG80TURRNUwyOXlhV2RwYmk4cUlpd2" - "lRMjl1WkdsMGFXOXVJanA3SWtSaGRHVk1aWE56VkdoaGJpSTZleUpCVjFNNlJYQnZZMmh" - "VYVcxbElqb3hOalEwT1RjeE5EQXdmWDE5WFgwXzsgU2VjdXJlOyBIdHRwT25seTsgU2Ft" - "ZVNpdGU9bGF4OyBEb21haW49aHR0cDovL2xvY2FsaG9zdDo4MDQ5OyBQYXRoPS9vcmlna" - "W4vKjsgTWF4LUFnZT0xODAwIiwgIkNsb3VkRnJvbnQtU2lnbmF0dXJlPVFoSFFDZlNWUH" - "lGOXJwbDNBY1ZyYkJ0eFBZN3c4Rm55Y0k2V3dKTE5Od0h2MEoxYmM0NGhUQjhpSW53Skd" - "IU1U0b3hGQnlFSFUyeFl4akxQSjdSdkJjNGllM3NSQmNXV00wb0hjd0lOdHA2U0FHWVRP" - "U3FCN3NDNlZMblIxfmN3SThkcTRGTEZpZElVVElDSGpFbk8taDVrUnBYaTFXbXpsZGVvc" - "GZ1ZHR-WV87IFNlY3VyZTsgSHR0cE9ubHk7IFNhbWVTaXRlPWxheDsgRG9tYWluPWh0dH" - "A6Ly9sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLyo7IE1heC1BZ2U9MTgwMCJd" - ) + expected_cookies = "WyJDbG91ZEZyb250LUtleS1QYWlyLUlkPVhYWFhYWFhYWFhYWFhYOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vY29udGVudC87IE1heC1BZ2U9NDMyMDAiLCAiQ2xvdWRGcm9udC1Qb2xpY3k9ZXlKVGRHRjBaVzFsYm5RaU9sdDdJbEpsYzI5MWNtTmxJam9pYUhSMGNEb3ZMMnh2WTJGc2FHOXpkRG80TURRNUwyTnZiblJsYm5RdktpSXNJa052Ym1ScGRHbHZiaUk2ZXlKRVlYUmxUR1Z6YzFSb1lXNGlPbnNpUVZkVE9rVndiMk5vVkdsdFpTSTZNVFkwTlRBeE1qZ3dNSDE5ZlYxOTsgU2VjdXJlOyBIdHRwT25seTsgU2FtZVNpdGU9bGF4OyBEb21haW49bG9jYWxob3N0OjgwNDk7IFBhdGg9L2NvbnRlbnQvOyBNYXgtQWdlPTQzMjAwIiwgIkNsb3VkRnJvbnQtU2lnbmF0dXJlPU5XUGZnb3REdTJEa0g0ZjRkNjhlVWtMTk5hVmZKR2hpenp4UlJleGI1NVh0Y0o3Qzk2cEF4ekd3cX56UWJoNndyMHhhMlh4Zll3UjV5dEs1MmJXQ3JCTGJWVHI5WWd0M2Z3Z3FDZTl1cWl1dnJoU3V-WDd3Z0VPbkVvT053Sng2WGw1VkFERU4yYXBVblBMQ1hJVEQybXYtNnJDaFhmemdaMXg0UER5OGo4MF87IFNlY3VyZTsgSHR0cE9ubHk7IFNhbWVTaXRlPWxheDsgRG9tYWluPWxvY2FsaG9zdDo4MDQ5OyBQYXRoPS9jb250ZW50LzsgTWF4LUFnZT00MzIwMCIsICJDbG91ZEZyb250LUtleS1QYWlyLUlkPVhYWFhYWFhYWFhYWFhYOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLzsgTWF4LUFnZT00MzIwMCIsICJDbG91ZEZyb250LVBvbGljeT1leUpUZEdGMFpXMWxiblFpT2x0N0lsSmxjMjkxY21ObElqb2lhSFIwY0RvdkwyeHZZMkZzYUc5emREbzRNRFE1TDI5eWFXZHBiaThxSWl3aVEyOXVaR2wwYVc5dUlqcDdJa1JoZEdWTVpYTnpWR2hoYmlJNmV5SkJWMU02UlhCdlkyaFVhVzFsSWpveE5qUTFNREV5T0RBd2ZYMTlYWDBfOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLzsgTWF4LUFnZT00MzIwMCIsICJDbG91ZEZyb250LVNpZ25hdHVyZT1NaW8za2w5enpCZXE2WUtjREY0aFdHNGlIRFhnLWRwSnV-VmtkWklYZVhPM0lsZzE3OTZUWlFBZGpLLWN6Tm5aQzBUNWVmVzNEbGlKQWVMSmhYd351MVZoTkpSQ0lvUTZmTGJDVnV4MVRHMzAtUC1FVzR-a1JmU2dlWjV2RVcydTBNWXpsQ0pNZndZSUoxQ1ZlejlMdTJ3a2NIMjFQTkNjc2liS25tTmZjbk1fOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLzsgTWF4LUFnZT00MzIwMCJd" expected_url = ( "http://localhost:8049/_/cookie/some/url?" "Expires=1644971400&" - "Signature=QXdMBQNyDLYeIsJzV7bKHnqYQSErcz9OYdJTuIYKVHCDaDiqPOUjqkSXX4f" - "m7A-Fi2roZSlWhyd4emrlC8hvNdPLZb3-7LHMVqau1QK9qFlhZz~aP1i4~Zud-kTot4JO" - "4ewE8LdCkQL1pda-on~wVTXhiAtB7EaX8aR3dnBZmYo_&" - f"Set-Cookies={expected_cookies}&" + "Signature=QXdMBQNyDLYeIsJzV7bKHnqYQSErcz9OYdJTuIYKVHCDaDiqPOUjqkSXX4fm7A-Fi2roZSlWhyd4emrlC8hvNdPLZb3-7LHMVqau1QK9qFlhZz~aP1i4~Zud-kTot4JO4ewE8LdCkQL1pda-on~wVTXhiAtB7EaX8aR3dnBZmYo_&" + f"CloudFront-Cookies={expected_cookies}&" "Key-Pair-Id=XXXXXXXXXXXXXX" ) @@ -113,28 +83,26 @@ def test_cdn_redirect_(monkeypatch, dummy_private_key, caplog): cookies = json.loads(b64decode(cookies)) assert cookies == [ "CloudFront-Key-Pair-Id=XXXXXXXXXXXXXX; Secure; HttpOnly; " - "SameSite=lax; Domain=http://localhost:8049; Path=/content/*; Max-Age=1800", + "SameSite=lax; Domain=localhost:8049; Path=/content/; Max-Age=43200", "CloudFront-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cDovL2xvY2F" "saG9zdDo4MDQ5L2NvbnRlbnQvKiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsi" - "QVdTOkVwb2NoVGltZSI6MTY0NDk3MTQwMH19fV19; Secure; HttpOnly; " - "SameSite=lax; Domain=http://localhost:8049; Path=/content/*; Max-Age=1800", - "CloudFront-Signature=R6k-BGvNgJRs3-GW0BvgtfQwEBtgTxGvyYdWU03fPCGlQMF8" - "EZVDWIqTdHOPznrCtnTYVZvvcw4U~VEsSif75W4x-qeoqn~YH9YVag2vNAYpBEtwmI08M" - "rvNPirCgObcLvyW1OrxLhHVDycsAdFFanuouEAykBtsGMnHvmRoGRA_; Secure; " - "HttpOnly; SameSite=lax; Domain=http://localhost:8049; Path=/content/*; " - "Max-Age=1800", + "QVdTOkVwb2NoVGltZSI6MTY0NTAxMjgwMH19fV19; Secure; HttpOnly; " + "SameSite=lax; Domain=localhost:8049; Path=/content/; Max-Age=43200", + "CloudFront-Signature=NWPfgotDu2DkH4f4d68eUkLNNaVfJGhizzxRRexb55XtcJ7C" + "96pAxzGwq~zQbh6wr0xa2XxfYwR5ytK52bWCrBLbVTr9Ygt3fwgqCe9uqiuvrhSu~X7wg" + "EOnEoONwJx6Xl5VADEN2apUnPLCXITD2mv-6rChXfzgZ1x4PDy8j80_; Secure; " + "HttpOnly; SameSite=lax; Domain=localhost:8049; Path=/content/; " + "Max-Age=43200", "CloudFront-Key-Pair-Id=XXXXXXXXXXXXXX; Secure; " - "HttpOnly; SameSite=lax; Domain=http://localhost:8049; Path=/origin/*; " - "Max-Age=1800", - "CloudFront-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cDovL2xvY2F" - "saG9zdDo4MDQ5L29yaWdpbi8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJB" - "V1M6RXBvY2hUaW1lIjoxNjQ0OTcxNDAwfX19XX0_; Secure; HttpOnly; " - "SameSite=lax; Domain=http://localhost:8049; Path=/origin/*; Max-Age=1800", - "CloudFront-Signature=QhHQCfSVPyF9rpl3AcVrbBtxPY7w8FnycI6WwJLNNwHv0J1b" - "c44hTB8iInwJGHSU4oxFByEHU2xYxjLPJ7RvBc4ie3sRBcWWM0oHcwINtp6SAGYTOSqB7" - "sC6VLnR1~cwI8dq4FLFidIUTICHjEnO-h5kRpXi1Wmzldeopfudt~Y_; Secure; " - "HttpOnly; SameSite=lax; Domain=http://localhost:8049; Path=/origin/*; " - "Max-Age=1800", + "HttpOnly; SameSite=lax; Domain=localhost:8049; Path=/origin/; " + "Max-Age=43200", + "CloudFront-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cDovL2xvY2FsaG9zdDo4MDQ5L29yaWdpbi8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjQ1MDEyODAwfX19XX0_; Secure; HttpOnly; " + "SameSite=lax; Domain=localhost:8049; Path=/origin/; Max-Age=43200", + "CloudFront-Signature=Mio3kl9zzBeq6YKcDF4hWG4iHDXg-dpJu~VkdZIXeXO3Ilg1" + "796TZQAdjK-czNnZC0T5efW3DliJAeLJhXw~u1VhNJRCIoQ6fLbCVux1TG30-P-EW4~kR" + "fSgeZ5vEW2u0MYzlCJMfwYIJ1CVez9Lu2wkcH21PNCcsibKnmNfcnM_; Secure; " + "HttpOnly; SameSite=lax; Domain=localhost:8049; Path=/origin/; " + "Max-Age=43200", ] # Sanity check at least one policy content_policy = b64decode( @@ -144,7 +112,7 @@ def test_cdn_redirect_(monkeypatch, dummy_private_key, caplog): "Statement": [ { "Resource": "http://localhost:8049/content/*", - "Condition": {"DateLessThan": {"AWS:EpochTime": 1644971400}}, + "Condition": {"DateLessThan": {"AWS:EpochTime": 1645012800}}, } ] } From d3bf81f5b55956c4c03d46bc2e246e46d0715bdc Mon Sep 17 00:00:00 2001 From: Nathan Gillett Date: Wed, 25 Oct 2023 06:49:19 -0500 Subject: [PATCH 2/2] CDN Redirect: Include CloudFront-Cookies in policy [RHELDST-18071] Previously, cookies generated by exodus-gw for redirect requests were just tacked onto the final URL as a querystring parameter. They must also be included in the policy's URL/Resource. This commit makes this correction. --- exodus_gw/routers/cdn.py | 19 ++++++++++--------- tests/routers/test_cdn.py | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/exodus_gw/routers/cdn.py b/exodus_gw/routers/cdn.py index 938192f2..638ce99e 100644 --- a/exodus_gw/routers/cdn.py +++ b/exodus_gw/routers/cdn.py @@ -118,9 +118,6 @@ def sign_url(url: str, settings: Settings, env: Environment, username: str): extra={"event": "cdn", "success": True}, ) - policy = build_policy(dest_url, signature_expires) - signature = rsa_signer(env.cdn_private_key, policy) - cookies = [] for resource in ("/content/", "/origin/"): parsed_url = urlparse(env.cdn_url) @@ -131,16 +128,20 @@ def sign_url(url: str, settings: Settings, env: Environment, username: str): f"Path={resource}; Max-Age={settings.cdn_cookie_ttl}" ) cookies.extend([f"{k}={v}{append}" for k, v in cookie.items()]) + cookies_bytes = bytes(json.dumps(cookies), "utf-8") + cookies_encoded = cf_b64(cookies_bytes).decode("utf-8") + + dest_url = f"{dest_url}?CloudFront-Cookies={cookies_encoded}" + policy = build_policy(dest_url, signature_expires) + signature = rsa_signer(env.cdn_private_key, policy) params = [ - "Expires=%s" % int(datetime2timestamp(signature_expires)), - "Signature=%s" % cf_b64(signature).decode("utf8"), - "CloudFront-Cookies=%s" % cf_b64(cookies_bytes).decode("utf-8"), - "Key-Pair-Id=%s" % env.cdn_key_id, + f"Expires={int(datetime2timestamp(signature_expires))}", + f"Signature={cf_b64(signature).decode('utf8')}", + f"Key-Pair-Id={env.cdn_key_id}", ] - separator = "&" if "?" in url else "?" - return dest_url + separator + "&".join(params) + return f"{dest_url}&{'&'.join(params)}" Url = Path( diff --git a/tests/routers/test_cdn.py b/tests/routers/test_cdn.py index c033ca39..8a0fad6d 100644 --- a/tests/routers/test_cdn.py +++ b/tests/routers/test_cdn.py @@ -64,9 +64,9 @@ def test_cdn_redirect_(monkeypatch, dummy_private_key, caplog): expected_cookies = "WyJDbG91ZEZyb250LUtleS1QYWlyLUlkPVhYWFhYWFhYWFhYWFhYOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vY29udGVudC87IE1heC1BZ2U9NDMyMDAiLCAiQ2xvdWRGcm9udC1Qb2xpY3k9ZXlKVGRHRjBaVzFsYm5RaU9sdDdJbEpsYzI5MWNtTmxJam9pYUhSMGNEb3ZMMnh2WTJGc2FHOXpkRG80TURRNUwyTnZiblJsYm5RdktpSXNJa052Ym1ScGRHbHZiaUk2ZXlKRVlYUmxUR1Z6YzFSb1lXNGlPbnNpUVZkVE9rVndiMk5vVkdsdFpTSTZNVFkwTlRBeE1qZ3dNSDE5ZlYxOTsgU2VjdXJlOyBIdHRwT25seTsgU2FtZVNpdGU9bGF4OyBEb21haW49bG9jYWxob3N0OjgwNDk7IFBhdGg9L2NvbnRlbnQvOyBNYXgtQWdlPTQzMjAwIiwgIkNsb3VkRnJvbnQtU2lnbmF0dXJlPU5XUGZnb3REdTJEa0g0ZjRkNjhlVWtMTk5hVmZKR2hpenp4UlJleGI1NVh0Y0o3Qzk2cEF4ekd3cX56UWJoNndyMHhhMlh4Zll3UjV5dEs1MmJXQ3JCTGJWVHI5WWd0M2Z3Z3FDZTl1cWl1dnJoU3V-WDd3Z0VPbkVvT053Sng2WGw1VkFERU4yYXBVblBMQ1hJVEQybXYtNnJDaFhmemdaMXg0UER5OGo4MF87IFNlY3VyZTsgSHR0cE9ubHk7IFNhbWVTaXRlPWxheDsgRG9tYWluPWxvY2FsaG9zdDo4MDQ5OyBQYXRoPS9jb250ZW50LzsgTWF4LUFnZT00MzIwMCIsICJDbG91ZEZyb250LUtleS1QYWlyLUlkPVhYWFhYWFhYWFhYWFhYOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLzsgTWF4LUFnZT00MzIwMCIsICJDbG91ZEZyb250LVBvbGljeT1leUpUZEdGMFpXMWxiblFpT2x0N0lsSmxjMjkxY21ObElqb2lhSFIwY0RvdkwyeHZZMkZzYUc5emREbzRNRFE1TDI5eWFXZHBiaThxSWl3aVEyOXVaR2wwYVc5dUlqcDdJa1JoZEdWTVpYTnpWR2hoYmlJNmV5SkJWMU02UlhCdlkyaFVhVzFsSWpveE5qUTFNREV5T0RBd2ZYMTlYWDBfOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLzsgTWF4LUFnZT00MzIwMCIsICJDbG91ZEZyb250LVNpZ25hdHVyZT1NaW8za2w5enpCZXE2WUtjREY0aFdHNGlIRFhnLWRwSnV-VmtkWklYZVhPM0lsZzE3OTZUWlFBZGpLLWN6Tm5aQzBUNWVmVzNEbGlKQWVMSmhYd351MVZoTkpSQ0lvUTZmTGJDVnV4MVRHMzAtUC1FVzR-a1JmU2dlWjV2RVcydTBNWXpsQ0pNZndZSUoxQ1ZlejlMdTJ3a2NIMjFQTkNjc2liS25tTmZjbk1fOyBTZWN1cmU7IEh0dHBPbmx5OyBTYW1lU2l0ZT1sYXg7IERvbWFpbj1sb2NhbGhvc3Q6ODA0OTsgUGF0aD0vb3JpZ2luLzsgTWF4LUFnZT00MzIwMCJd" expected_url = ( "http://localhost:8049/_/cookie/some/url?" - "Expires=1644971400&" - "Signature=QXdMBQNyDLYeIsJzV7bKHnqYQSErcz9OYdJTuIYKVHCDaDiqPOUjqkSXX4fm7A-Fi2roZSlWhyd4emrlC8hvNdPLZb3-7LHMVqau1QK9qFlhZz~aP1i4~Zud-kTot4JO4ewE8LdCkQL1pda-on~wVTXhiAtB7EaX8aR3dnBZmYo_&" f"CloudFront-Cookies={expected_cookies}&" + "Expires=1644971400&" + "Signature=DxQExeKUk0OJ~qafWOIow1OM8Nil9x4JBjpgtODY1AoIuH-FcW4nt~AcAQmJ1WHRqYIuC79INWk9RTyOokj-Ao6e6i5r6AcPKvhTTyOgRkg9Ywfzf~fUdBENi3k9q4sWgbvND5kiZRZwj3DBc4s0bX82rYYuuSGnjNyjshYhlVU_&" "Key-Pair-Id=XXXXXXXXXXXXXX" )