gpt4 book ai didi

amazon-web-services - 如何通过特定用户的 Bearer token 访问与 S3 Bucket 连接的 AWS CloudFront(JWT 自定义身份验证)

转载 作者:行者123 更新时间:2023-12-04 04:06:06 26 4
gpt4 key购买 nike

我正在使用无服务器框架将无服务器堆栈部署到 AWS。我的堆栈由一些 lambda 函数、DynamoDB 表和 API 网关组成。
我使用所谓的 lambda authorizer 保护 API 网关.另外,我有一个可以生成 token 的自定义独立自托管 Auth 服务。
所以场景是用户可以从该服务请求一个 token (它是托管在 Azure 上的 IdentityServer4),然后用户可以使用承载 token 向 API 网关发送请求,因此 API 网关将要求 lambda 授权方生成 iam 角色,如果 token 是正确的。所有这些都是有效的并且按预期工作。
这是我的 serverless.yml 中 lambda 授权器定义的示例,以及我如何使用它来保护其他 API 网关端点:(您可以看到 addUserInfo 函数具有使用自定义授权器保护的 API)


functions:
# =================================================================
# API Gateway event handlers
# ================================================================
auth:
handler: api/auth/mda-auth-server.handler

addUserInfo:
handler: api/user/create-replace-user-info.handler
description: Create Or Replace user section
events:
- http:
path: user
method: post
authorizer:
name: auth
resultTtlInSeconds: ${self:custom.resultTtlInSeconds}
identitySource: method.request.header.Authorization
type: token
cors:
origin: '*'
headers: ${self:custom.allowedHeaders}
现在我想扩展我的 API,所以我将允许用户添加图像,所以我遵循了 approach .因此,在这种方法中,用户将启动所谓的签名 S3 URL,我可以使用这个 S3 签名 URL 将图像放入我的存储桶。
此外,S3 存储桶不可公开访问,而是连接到 CloudFront 分配。现在我错过了这里的东西,我无法理解如何保护我的图像。无论如何,我可以使用我的自定义身份验证服务保护 CloudFront CDN 中的图像,以便拥有有效 token 的用户可以访问这些资源?如何使用自定义身份验证服务保护我的 CDN (CloudFront) 并使用无服务器框架进行配置?

最佳答案

这有点棘手,我需要大约一天的时间才能完成所有设置。
首先,我们在这里有选项:

  • 我们可以签署 URL 并返回签名的 CloudFront URL 或签名的 S3 URL,而不是身份验证,这非常简单,但显然这不是我想要的。
  • 第二个选项是使用 Lambda@Edge 来授权 CloudFront 的请求以及我遵循的请求。

  • 所以我最终创建了一个单独的堆栈来处理所有 S3、CloudFront 和 Lambda@Edge 的东西,因为它们都部署在边缘上,这意味着区域无关紧要,但对于 lambda 边缘,我们需要将其部署到主 AWS region ((N. Virginia), us-east-1) 所以我最终为所有这些创建了一个堆栈。
    首先,我的 auth-service.js 中有以下代码(这只是一些帮助我验证我的自定义 jwt):
    import * as jwtDecode from 'jwt-decode';
    import * as util from 'util';
    import * as jwt from 'jsonwebtoken';
    import * as jwksClient from 'jwks-rsa';


    export function getToken(bearerToken) {
    if(bearerToken && bearerToken.startsWith("Bearer "))
    {
    return bearerToken.replace(/^Bearer\s/, '');
    }
    throw new Error("Invalid Bearer Token.");
    };

    export function getDecodedHeader(token) {
    return jwtDecode(token, { header: true });
    };

    export async function getSigningKey(decodedJwtTokenHeader, jwksclient){
    const key = await util.promisify(jwksclient.getSigningKey)(decodedJwtTokenHeader.kid);
    const signingKey = key.publicKey || key.rsaPublicKey;
    if (!signingKey) {
    throw new Error('could not get signing key');
    }
    return signingKey;
    };

    export async function verifyToken(token,signingKey){
    return await jwt.verify(token, signingKey);
    };

    export function getJwksClient(jwksEndpoint){
    return jwksClient({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 10,
    jwksUri: jwksEndpoint
    });
    };
    然后在 serverless.yml 里面是我的文件:
    service: mda-app-uploads

    plugins:
    - serverless-offline
    - serverless-pseudo-parameters
    - serverless-iam-roles-per-function
    - serverless-bundle


    custom:
    stage: ${opt:stage, self:provider.stage}
    resourcesBucketName: ${self:custom.stage}-mda-resources-bucket
    resourcesStages:
    prod: prod
    dev: dev
    resourcesStage: ${self:custom.resourcesStages.${self:custom.stage}, self:custom.resourcesStages.dev}


    provider:
    name: aws
    runtime: nodejs12.x
    stage: ${opt:stage, 'dev'}
    region: us-east-1
    versionFunctions: true

    functions:
    oauthEdge:
    handler: src/mda-edge-auth.handler
    role: LambdaEdgeFunctionRole
    memorySize: 128
    timeout: 5


    resources:
    - ${file(resources/s3-cloudfront.yml)}
    这里的快速点:
  • us-east-1 在这里很重要。
  • 使用无服务器框架创建任何 lambda 边缘有点棘手且不切实际,所以我用它来配置函数,然后在这个云形成模板中 resources/s3-cloudfront.yml我添加了所有需要的位。

  • 那么这里是 resources/s3-cloudfront.yml的内容:
    Resources:

    AuthEdgeLambdaVersion:
    Type: Custom::LatestLambdaVersion
    Properties:
    ServiceToken: !GetAtt PublishLambdaVersion.Arn
    FunctionName: !Ref OauthEdgeLambdaFunction
    Nonce: "Test"

    PublishLambdaVersion:
    Type: AWS::Lambda::Function
    Properties:
    Handler: index.handler
    Runtime: nodejs12.x
    Role: !GetAtt PublishLambdaVersionRole.Arn
    Code:
    ZipFile: |
    const {Lambda} = require('aws-sdk')
    const {send, SUCCESS, FAILED} = require('cfn-response')
    const lambda = new Lambda()
    exports.handler = (event, context) => {
    const {RequestType, ResourceProperties: {FunctionName}} = event
    if (RequestType == 'Delete') return send(event, context, SUCCESS)
    lambda.publishVersion({FunctionName}, (err, {FunctionArn}) => {
    err
    ? send(event, context, FAILED, err)
    : send(event, context, SUCCESS, {FunctionArn})
    })
    }

    PublishLambdaVersionRole:
    Type: AWS::IAM::Role
    Properties:
    AssumeRolePolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
    Principal:
    Service: lambda.amazonaws.com
    Action: sts:AssumeRole
    ManagedPolicyArns:
    - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    Policies:
    - PolicyName: PublishVersion
    PolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
    Action: lambda:PublishVersion
    Resource: '*'

    LambdaEdgeFunctionRole:
    Type: "AWS::IAM::Role"
    Properties:
    Path: "/"
    ManagedPolicyArns:
    - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
    AssumeRolePolicyDocument:
    Version: "2012-10-17"
    Statement:
    -
    Sid: "AllowLambdaServiceToAssumeRole"
    Effect: "Allow"
    Action:
    - "sts:AssumeRole"
    Principal:
    Service:
    - "lambda.amazonaws.com"
    - "edgelambda.amazonaws.com"
    LambdaEdgeFunctionPolicy:
    Type: "AWS::IAM::Policy"
    Properties:
    PolicyName: MainEdgePolicy
    PolicyDocument:
    Version: "2012-10-17"
    Statement:
    Effect: "Allow"
    Action:
    - "lambda:GetFunction"
    - "lambda:GetFunctionConfiguration"
    Resource: !GetAtt AuthEdgeLambdaVersion.FunctionArn
    Roles:
    - !Ref LambdaEdgeFunctionRole


    ResourcesBucket:
    Type: AWS::S3::Bucket
    Properties:
    BucketName: ${self:custom.resourcesBucketName}
    AccessControl: Private
    CorsConfiguration:
    CorsRules:
    - AllowedHeaders: ['*']
    AllowedMethods: ['PUT']
    AllowedOrigins: ['*']

    ResourcesBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
    Bucket:
    Ref: ResourcesBucket
    PolicyDocument:
    Statement:
    # Read permission for CloudFront
    - Action: s3:GetObject
    Effect: "Allow"
    Resource:
    Fn::Join:
    - ""
    -
    - "arn:aws:s3:::"
    -
    Ref: "ResourcesBucket"
    - "/*"
    Principal:
    CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
    - Action: s3:PutObject
    Effect: "Allow"
    Resource:
    Fn::Join:
    - ""
    -
    - "arn:aws:s3:::"
    -
    Ref: "ResourcesBucket"
    - "/*"
    Principal:
    AWS: !GetAtt LambdaEdgeFunctionRole.Arn

    - Action: s3:GetObject
    Effect: "Allow"
    Resource:
    Fn::Join:
    - ""
    -
    - "arn:aws:s3:::"
    -
    Ref: "ResourcesBucket"
    - "/*"
    Principal:
    AWS: !GetAtt LambdaEdgeFunctionRole.Arn


    CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
    CloudFrontOriginAccessIdentityConfig:
    Comment:
    Fn::Join:
    - ""
    -
    - "Identity for accessing CloudFront from S3 within stack "
    -
    Ref: "AWS::StackName"
    - ""


    # Cloudfront distro backed by ResourcesBucket
    ResourcesCdnDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
    DistributionConfig:
    Origins:
    # S3 origin for private resources
    - DomainName: !Sub '${self:custom.resourcesBucketName}.s3.amazonaws.com'
    Id: S3OriginPrivate
    S3OriginConfig:
    OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
    # S3 origin for public resources
    - DomainName: !Sub '${self:custom.resourcesBucketName}.s3.amazonaws.com'
    Id: S3OriginPublic
    S3OriginConfig:
    OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
    Enabled: true
    Comment: CDN for public and provate static content.
    DefaultRootObject: index.html
    HttpVersion: http2
    DefaultCacheBehavior:
    AllowedMethods:
    - DELETE
    - GET
    - HEAD
    - OPTIONS
    - PATCH
    - POST
    - PUT
    Compress: true
    TargetOriginId: S3OriginPublic
    ForwardedValues:
    QueryString: false
    Headers:
    - Origin
    Cookies:
    Forward: none
    ViewerProtocolPolicy: redirect-to-https
    CacheBehaviors:
    -
    PathPattern: 'private/*'
    TargetOriginId: S3OriginPrivate
    AllowedMethods:
    - DELETE
    - GET
    - HEAD
    - OPTIONS
    - PATCH
    - POST
    - PUT
    Compress: true
    LambdaFunctionAssociations:
    -
    EventType: viewer-request
    LambdaFunctionARN: !GetAtt AuthEdgeLambdaVersion.FunctionArn
    ForwardedValues:
    QueryString: false
    Headers:
    - Origin
    Cookies:
    Forward: none
    ViewerProtocolPolicy: redirect-to-https
    -
    PathPattern: 'public/*'
    TargetOriginId: S3OriginPublic
    AllowedMethods:
    - DELETE
    - GET
    - HEAD
    - OPTIONS
    - PATCH
    - POST
    - PUT
    Compress: true
    ForwardedValues:
    QueryString: false
    Headers:
    - Origin
    Cookies:
    Forward: none
    ViewerProtocolPolicy: redirect-to-https

    PriceClass: PriceClass_200
    与此文件相关的一些要点:
  • 在这里,我创建了包含所有私有(private)和公共(public)资源的 S3 存储桶。
  • 此存储桶是私有(private)的且不可访问,您将找到一个角色,该角色仅授予 CDN 和 lambda 边缘访问它的权限。
  • 我决定创建一个 CloudFront (CDN),它有两个来源,公共(public)指向 S3 的公用文件夹,私有(private)指向 S3 的私有(private)文件夹,并配置 CloudFront 私有(private)来源的行为以使用我的 lambda 边缘功能进行身份验证查看者请求事件类型。
  • 您还可以找到创建函数版本的代码和另一个名为 PublishLambdaVersion 的函数。凭借其作用,它有助于在部署时为 lambda 边缘提供正确的权限。

  • 最后,这里是用于 CDN 身份验证的 lambda 边缘函数的实际代码:
    import {getJwksClient, getToken, getDecodedHeader, getSigningKey, verifyToken} from '../../../../libs/services/auth-service';
    import config from '../../../../config';

    const response401 = {
    status: '401',
    statusDescription: 'Unauthorized'
    };

    exports.handler = async (event) => {
    try{
    const cfrequest = event.Records[0].cf.request;
    const headers = cfrequest.headers;
    if(!headers.authorization) {
    console.log("no auth header");
    return response401;
    }
    const jwtValue = getToken(headers.authorization);
    const client = getJwksClient(`https://${config.authDomain}/.well-known/openid-configuration/jwks`);
    const decodedJwtHeader = getDecodedHeader(jwtValue);
    if(decodedJwtHeader)
    {
    const signingKey = await getSigningKey(decodedJwtHeader, client);
    const verifiedToken = await verifyToken(jwtValue, signingKey);
    if(verifiedToken)
    {
    return cfrequest;
    }
    }else{
    throw Error("Unauthorized");
    }

    }catch(err){
    console.log(err);
    return response401;
    }
    };
    如果您有兴趣,我正在使用 IdentityServer4 并将其作为 docker 镜像托管在 Azure 中,并将其用作自定义授权方。
    因此,现在我们拥有一个完全私有(private)的 S3 存储桶,这就是完整的场景。它只能通过 CloudFront 源访问。如果请求通过公共(public)源提供,则不需要身份验证,但如果它是通过私有(private)源提供的,那么我将触发所谓的 lambda 边缘对其进行身份验证并验证承载 token 。
    在深入了解所有这些之前,我对 AWS 堆栈完全陌生,但 AWS 非常简单,所以我最终以完美的方式配置了所有内容。如果有不清楚的地方或有任何问题,请告诉我。

    关于amazon-web-services - 如何通过特定用户的 Bearer token 访问与 S3 Bucket 连接的 AWS CloudFront(JWT 自定义身份验证),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62492604/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com