From 2e754c777316cb33c96442641b5fe9798fd31fae Mon Sep 17 00:00:00 2001 From: Andrew Johnston Date: Thu, 4 Aug 2022 14:55:23 -0800 Subject: [PATCH] distribute via signed s3 urls rather than signed cloudfront urls --- .github/workflows/deploy.yml | 5 -- cloudformation.yaml | 21 --------- cloudfront/cloudformation.yaml | 83 ---------------------------------- door/cloudformation.yaml | 15 ------ door/requirements.txt | 3 +- door/src/door/routes.py | 35 ++++---------- 6 files changed, 9 insertions(+), 153 deletions(-) delete mode 100644 cloudfront/cloudformation.yaml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a2e62e0..9828e4b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,7 +17,6 @@ jobs: domain: grfn.asf.alaska.edu auth_url: "https://urs.earthdata.nasa.gov/oauth/authorize?response_type=code&client_id=BO_n7nTIlMljdvU6kRRB3g&redirect_uri=https://auth.asf.alaska.edu/login" jwt_cookie_name: asf-urs - log_bucket: grfn-logs private_bucket: grfn-content-prod deploy_ref: refs/heads/prod @@ -26,7 +25,6 @@ jobs: domain: grfn-test.asf.alaska.edu auth_url: "https://uat.urs.earthdata.nasa.gov/oauth/authorize?client_id=Qkd0Z9KbhG86qedkRC7nSA&response_type=code&redirect_uri=https://auth-test-jenk.asf.alaska.edu/login" jwt_cookie_name: asf-urs - log_bucket: grfn-logs private_bucket: grfn-content-test deploy_ref: refs/heads/test @@ -70,11 +68,8 @@ jobs: --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides \ CertificateArn='${{ secrets.CETRIFICATE_ARN }}' \ - CloudFrontKeyPairId='${{ secrets.CLOUDFRONT_KEY_PAIR_ID }}' \ DomainName='${{ matrix.domain }}' \ - LogBucket='${{ matrix.log_bucket }}' \ PrivateBucket='${{ matrix.private_bucket }}' \ - PrivateKeySecretName='${{ secrets.PRIVATE_KEY_SECRET_NAME }}' \ AuthUrl='${{ matrix.auth_url }}' \ JwtCookieName='${{ matrix.jwt_cookie_name }}' \ JwtPublicKey='${{ secrets.JWT_PUBLIC_KEY }}' diff --git a/cloudformation.yaml b/cloudformation.yaml index b70b565..ae9337f 100644 --- a/cloudformation.yaml +++ b/cloudformation.yaml @@ -6,21 +6,12 @@ Parameters: Description: Name of S3 bucket in which zipped output products will be archived Type: String - LogBucket: - Type: String - CertificateArn: Type: String DomainName: Type: String - CloudFrontKeyPairId: - Type: String - - PrivateKeySecretName: - Type: String - AuthUrl: Type: String @@ -37,9 +28,6 @@ Resources: Properties: Parameters: Name: !Sub "${AWS::StackName}-door" - CloudFrontDomainName: !GetAtt CloudFrontStack.Outputs.DomainName - CloudFrontKeyPairId: !Ref CloudFrontKeyPairId - PrivateKeySecretName: !Ref PrivateKeySecretName PrivateBucket: !Ref PrivateBucket CertificateArn: !Ref CertificateArn DomainName: !Ref DomainName @@ -47,12 +35,3 @@ Resources: JwtCookieName: !Ref JwtCookieName JwtPublicKey: !Ref JwtPublicKey TemplateURL: door/cloudformation.yaml - - CloudFrontStack: - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - Name: !Ref AWS::StackName - Bucket: !Ref PrivateBucket - LogBucket: !Ref LogBucket - TemplateURL: cloudfront/cloudformation.yaml diff --git a/cloudfront/cloudformation.yaml b/cloudfront/cloudformation.yaml deleted file mode 100644 index 6924ea3..0000000 --- a/cloudfront/cloudformation.yaml +++ /dev/null @@ -1,83 +0,0 @@ -AWSTemplateFormatVersion: 2010-09-09 - -Parameters: - - Name: - Type: String - - Bucket: - Type: String - - LogBucket: - Type: String - - LogPrefix: - Type: String - Default: cloudfront-access/ - -Outputs: - - DomainName: - Value: !GetAtt Distribution.DomainName - - CanonicalUserId: - Value: !GetAtt OriginAccessIdentity.S3CanonicalUserId - -Resources: - - OriginAccessIdentity: - Type: AWS::CloudFront::CloudFrontOriginAccessIdentity - Properties: - CloudFrontOriginAccessIdentityConfig: - Comment: !Ref Name - - CachePolicy: - Type: AWS::CloudFront::CachePolicy - Properties: - CachePolicyConfig: - Name: !Ref Name - DefaultTTL: 86400 - MinTTL: 0 - MaxTTL: 31536000 - ParametersInCacheKeyAndForwardedToOrigin: - EnableAcceptEncodingGzip: true - HeadersConfig: - HeaderBehavior: whitelist - Headers: - - Access-Control-Request-Headers - - Access-Control-Request-Method - - Origin - CookiesConfig: - CookieBehavior: none - QueryStringsConfig: - QueryStringBehavior: none - - Distribution: - Type: AWS::CloudFront::Distribution - Properties: - DistributionConfig: - Comment: !Ref Name - Enabled: True - PriceClass: PriceClass_100 - Origins: - - Id: !Ref Bucket - DomainName: !Sub "${Bucket}.s3.amazonaws.com" - S3OriginConfig: - OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${OriginAccessIdentity}" - DefaultCacheBehavior: - CachePolicyId: !Ref CachePolicy - TargetOriginId: !Ref Bucket - ViewerProtocolPolicy: redirect-to-https - TrustedSigners: - - !Ref AWS::AccountId - Restrictions: - GeoRestriction: - RestrictionType: blacklist - Locations: - - CU - - IR - - KP - - SY - Logging: - Bucket: !Sub "${LogBucket}.s3.amazonaws.com" - Prefix: !Sub "${LogPrefix}${Name}/" diff --git a/door/cloudformation.yaml b/door/cloudformation.yaml index 91d1c6d..524d983 100644 --- a/door/cloudformation.yaml +++ b/door/cloudformation.yaml @@ -5,15 +5,6 @@ Parameters: Name: Type: String - CloudFrontDomainName: - Type: String - - CloudFrontKeyPairId: - Type: String - - PrivateKeySecretName: - Type: String - PrivateBucket: Type: String @@ -116,9 +107,6 @@ Resources: - logs:CreateLogStream - logs:PutLogEvents Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*" - - Effect: Allow - Action: secretsmanager:GetSecretValue - Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${PrivateKeySecretName}*" - Effect: Allow Action: s3:ListBucket Resource: !Sub "arn:aws:s3:::${PrivateBucket}" @@ -131,9 +119,6 @@ Resources: Properties: Environment: Variables: - CLOUDFRONT_DOMAIN_NAME: !Ref CloudFrontDomainName - CLOUDFRONT_KEY_PAIR_ID: !Ref CloudFrontKeyPairId - PRIVATE_KEY_SECRET_NAME: !Ref PrivateKeySecretName EXPIRE_TIME_IN_SECONDS: 15 BUCKET: !Ref PrivateBucket AUTH_URL: !Ref AuthUrl diff --git a/door/requirements.txt b/door/requirements.txt index 26371e9..129b93d 100644 --- a/door/requirements.txt +++ b/door/requirements.txt @@ -1,6 +1,5 @@ Flask Flask-Cors -rsa serverless_wsgi PyJWT -cryptography \ No newline at end of file +cryptography diff --git a/door/src/door/routes.py b/door/src/door/routes.py index bf82c69..fd83a7f 100644 --- a/door/src/door/routes.py +++ b/door/src/door/routes.py @@ -1,13 +1,9 @@ -import json import os -from datetime import datetime, timedelta, timezone from urllib.parse import quote_plus import boto3 import jwt -import rsa from botocore.exceptions import ClientError -from botocore.signers import CloudFrontSigner from flask import abort, g, redirect, request from flask_cors import CORS @@ -25,12 +21,6 @@ def decode_token(token): return None -@app.before_first_request -def init_app(): - private_key = get_secret(os.environ['PRIVATE_KEY_SECRET_NAME'])['private_key'] - os.environ['CLOUDFRONT_PRIVATE_KEY'] = str(private_key) - - @app.before_request def authenticate_user(): cookie = request.cookies.get(os.environ['JWT_COOKIE_NAME']) @@ -57,24 +47,15 @@ def download_redirect(object_key): abort(404) raise - signed_url = get_signed_url(object_key, g.user_id, os.environ['CLOUDFRONT_PRIVATE_KEY']) + signed_url = get_signed_url(object_key, g.user_id) return redirect(signed_url) -def get_signed_url(object_key, user_id, private_key): - def rsa_signer(message): - key = rsa.PrivateKey.load_pkcs1(private_key.encode(), 'PEM') - return rsa.sign(message, key, 'SHA-1') - - base_url = f'https://{os.environ["CLOUDFRONT_DOMAIN_NAME"]}/{object_key}?userid={user_id}' - expiration_datetime = datetime.now(tz=timezone.utc) + timedelta(seconds=int(os.environ['EXPIRE_TIME_IN_SECONDS'])) - cf_signer = CloudFrontSigner(os.environ['CLOUDFRONT_KEY_PAIR_ID'], rsa_signer) - signed_url = cf_signer.generate_presigned_url(base_url, date_less_than=expiration_datetime) +def get_signed_url(object_key, user_id): + signed_url = s3.generate_presigned_url( + ClientMethod='get_object', + Params={'Bucket': os.environ['BUCKET'], 'Key': object_key}, + ExpiresIn=int(os.environ['EXPIRE_TIME_IN_SECONDS']), + ) + signed_url += f'&user_id={user_id}' return signed_url - - -def get_secret(secret_name): - sm = boto3.client('secretsmanager', os.environ['AWS_REGION']) - response = sm.get_secret_value(SecretId=secret_name) - secret = json.loads(response['SecretString']) - return secret