by James Beswick | on 23 AUG 2023 | in Amazon CloudFront, AWS Lambda, Lambda@Edge, Serverless | Permalink | Share
Bài đăng này được viết bởi Jerome Van Der Linden, Người xây dựng Kiến trúc sư Giải pháp Cấp cao.
Một Lambda function URL là một HTTPS endpoint chuyên dụng cho một AWS Lambda function. Khi cấu hình, bạn có thể gọi function trực tiếp bằng HTTP request. Bạn có thể chọn tạo public bằng cách cài đặt loại xác thực là NONE cho một API mở. Hoặc bạn có thể bảo vệ nó với AWS IAM bằng cài đặt loại xác thực là AWS_IAM. Trọng trường hợp này, chỉ có các user đã xác thực và role có thể gọi function thông qua function URL.
Lambda@Edge là một tính năng của Amazon CloudFront có thể chạy code gần hơn với người dùng cuối của ứng dụng. Nó được dùng rộng rãi để thao tác các HTTP request đến và HTTP response đi giữa người dùng khách và ứng dụng gốc. Cụ thể là nó có thể thêm header vào request (ví dụ ‘Authorization’).
Bài blog này chỉ cách dùng CloudFront và Lambda@Edge để bảo vệ một Lambda function URL được cấu hình với xác thực loại AWS_IAM bằng cách thêm header phù hợp vào request trước khi nó đến origin.
Tổng quan
Có bốn thành phần chính trong ví dụ này:
- Lambda function đã bật function URL: Đây là trái tim của ứng dụng, các function chứa business code được hiển thị ở frontend. Function URL được cấu hình với loại xác thực AWS_IAM để chỉ có người dùng / role đã xác thực có thể gọi nó.
- Một CloudFront distribution: CloudFront là một dịch vụ mạng lưới truyền tải nội dung (CDN) dùng để truyền nội dung tới người dùng với độ trễ thấp. Nó cũng cải thiện bảo mật với thông lượng mã hóa và bảo vệ DDoS tích hợp sẵn. Trong ví dụ này, sử dụng CloudFront trước Lambda function URL có thể thêm lớp bảo mật và cache nội dung tiềm năng gần với người dùng hơn.
- Một Lambda function tại edge: CloudFront cũng cung cấp khả năng chạy Lambda function gần với người dùng: Lambda@Edge. Ví dụ này dùng nó để đăng ký request đến Lambda function URL và thêm các header phù hợp vào request để việc thực thi của URL được xác thực với IAM.
- Một ứng dụng web để gọi các Lambda function URL: Ví dụ này chứa một ứng dụng đơn trang với React, từ đó người dùng tạo các request đến một hoặc nhiều Lambda function URL. Các tài nguyên tĩnh (ví dụ các file HTML và JavaScript) được lưu trong Amazon S3 và cũng được hiển thị và cache bởi CloudFront.
Đây là kiến trúc mẫu:
Luồng của request là:
- Người dùng thực thi request thông qua khác tới các tài nguyên tĩnh từ ứng dụng React hoặc Lambda function URL.
- Với tài nguyên tĩnh, CloudFront gọi nó từ S3 hoặc cache và trả về cho khách.
- Nếu request cho Lambda function URL, đầu tiền nó đi tới Lambda@Edge. Lambda@Edge function có quyền lambda:InvokeFunctionUrl trên dối tượng Lambda function URL và dùng nó để đăng ký request với chữ ký V4. Nó thêm Authorization, X-Amz-Security-Token, và X-Amz-Date header vào request.
- Sau khi request được đăng ký, CloudFront truyền nó dến Lambda function URL.
- Lambda kích hoạt việc thực thi function thực hiện bất kỳ loại business logic. Giải pháp hiện tại là xử lý sách (create, get, update, delete)
- Lambda trả về response của function đến CloudFront.
- Cuối cùng, CloudFront trả response đến client.
Có một vài loại event mà Lambda@Edge function có thể kích hoạt:
- Viewer request: Sau khi CloudFront nhận request từ khách hàng.
- Origin request: Trước khi request được chuyển đến origin.
- Origin response: Sau khi CloudFront nhận response từ origin.
- Viewer response: Trước khi response được gửi về lại khách hàng.
Để cập nhật request trước khi nó được gửi đến origin (Lambda function URL), ví dụ hiện tại dùng loại “Origin request”.
Bạn có thể tìm ví dụng hoàn chỉnh dựa trên AWS Cloud Development Kit (CDK) trên GitHub.
Backend stack
Phần backend chứa các Lambda function và Lambda function URL khác nhau. Nó sử dụng loại xác thực AWS_IAM và định nghĩa CORS (Cross Origin Resource Sharing) khi thêm function URL vào hàm Lambda. Dùng allowedOrigins hạn chế hơn cho ứng dụng thực tế.
| const getBookFunction = new NodejsFunction(this, ‘GetBookFunction’, { runtime: Runtime.NODEJS_18_X, memorySize: 256, timeout: Duration.seconds(30), entry: path.join(__dirname, ‘../functions/books/books.ts’), environment: { TABLE_NAME: bookTable.tableName }, handler: ‘getBookHandler’, description: ‘Retrieve one book by id’, }); bookTable.grantReadData(getBookFunction); const getBookUrl = getBookFunction.addFunctionUrl({ authType: FunctionUrlAuthType.AWS_IAM, cors: { allowedOrigins: [‘*’], allowedMethods: [HttpMethod.GET], allowedHeaders: [‘*’], allowCredentials: true, } }); |
Frontend stack
Frontend stack chứa CloudFront distribution và Lambda@Edge function . Đây là định nghĩa của Lambda@Edge:
| const authFunction = new cloudfront.experimental.EdgeFunction(this, ‘AuthFunctionAtEdge’, { handler: ‘auth.handler’, runtime: Runtime.NODEJS_16_X, code: Code.fromAsset(path.join(__dirname, ‘../functions/auth’)), }); |
Chính sách sau đây cho phép hàm Lambda@Edge ký yêu cầu với quyền thích hợp và gọi các URL của hàm:
| authFunction.addToRolePolicy(new PolicyStatement({ sid: ‘AllowInvokeFunctionUrl’, effect: Effect.ALLOW, actions: [‘lambda:InvokeFunctionUrl’], resources: [getBookArn, getBooksArn, createBookArn, updateBookArn, deleteBookArn], conditions: { “StringEquals”: {“lambda:FunctionUrlAuthType”: “AWS_IAM”} } })); |
Mã chức năng sử dụng AWS JavaScript SDK và chính xác hơn là phần Chữ ký V4 của nó. Có hai điều quan trọng ở đây:
- Dịch vụ mà chúng tôi muốn ký yêu cầu: Lambda
- Thông tin xác thực của hàm (với quyền InvokeFunctionUrl)
| const request = new AWS.HttpRequest(new AWS.Endpoint(`https://${host}${path}`), region); // … set the headers, body and method … const signer = new AWS.Signers.V4(request, ‘lambda’, true); signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate()); |
Bạn có thể lấy mã đầy đủ của hàm tại đây.
CloudFront distribution và định nghĩa hành vi
CloudFront distribution có hành vi mặc định với S3 origin cho nội dung tĩnh của ứng dụng React.
Nó cũng có một hành vi cho mỗi function URL, như được định nghĩa trong đoạn code sau. Bạn có thể nhận thấy cấu hình của hàm Lambda@Edge với kiểu ORIGIN_REQUEST và hành vi tham chiếu đến function URL
:
| const getBehaviorOptions: AddBehaviorOptions = { viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY, cachePolicy: CachePolicy.CACHING_DISABLED, originRequestPolicy: OriginRequestPolicy.CORS_CUSTOM_ORIGIN, responseHeadersPolicy: ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT, edgeLambdas: [{ functionVersion: authFunction.currentVersion, eventType: LambdaEdgeEventType.ORIGIN_REQUEST, includeBody: false, // GET, no body }], allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS, } this.distribution.addBehavior(‘/getBook/*’, new HttpOrigin(Fn.select(2, Fn.split(‘/’, getBookUrl)),), getBehaviorOptions); |
Xem xét khu vực
Lambda@Edge function phải nằm trong us-east-1 Region (N. Virginia), cũng như frontend stack. Nếu triển khai backend stack ở Region khác, bạn phải chuyển URL (và ARN) của hàm Lambda tới frontend. Sử dụng tài nguyên tùy chỉnh trong CDK, có thể tạo tham số trong AWS Systems Manager Parameter Store trong us-east-1 Region chứ thông tin này. Để biết thêm chi tiết, hãy xem lại code trong GitHub repo.
Hướng dẫn
Trước khi triển khai giải pháp, hãy làm theo README trong GitHub repo và đảm bảo đáp ứng các điều kiện tiên quyết.
Triển khai giải pháp
- Từ thư mục giải pháp, cài đặt các dependency:
| npm install |
- Bắt đầu triển khai giải pháp (có thể mất tới 15 phút):
| cdk deploy –all |
- Sau khi triển khai thành công, kết quả đầu ra sẽ chứa cả Lambda function URL và URL “được bảo vệ” đằng sau CloudFront distribution:
Kiểm tra giải pháp
- Sử dụng cURL, truy vấn URL Hàm Lambda để truy xuất tất cả sách (GetBooksFunctionURL trong đầu ra CDK):
Bạn sẽ nhận được kết quả đầu ra sau đây. Đúng như dự đoán, không được phép truy cập trực tiếp vào Lambda function URL mà không có xác thực IAM thích hợp:
- Bây giờ hãy truy vấn URL “được bảo vệ” để truy xuất tất cả sách (GetBooksURL trong đầu ra CDK):
Lần này bạn sẽ nhận được HTTP 200 OK với danh sách trống.
Nhật ký của hàm Lambda@Edge (tìm kiếm “AuthFunctionAtEdge” trong CloudWatch Logs ở Region gần nhất) hiển thị:
- Request tới:
- Request đã ký, với các tiêu đề bổ sung (Authorization, X-Amz-Security-Token, và X-Amz-Date). Các header này tạo nên sự khác biệt khi Lambda URL nhận được yêu cầu và xác thực yêu cầu đó bằng IAM.
Bạn có thể kiểm tra giải pháp hoàn chỉnh trong frontend bằng FrontendURL trong CDK đầu ra.
Dọn dẹp
Hàm Lambda@Edge được sao chép ở tất cả các Region mà bạn có người dùng. Bạn phải xóa các bản sao trước khi xóa phần còn lại của giải pháp.
Để xóa các tài nguyên đã triển khai, hãy chạy lệnh cdk destroy –all từ thư mục giải pháp.
Kết luận
Bài blog này cho biết cách bảo vệ Lambda function URL, được định cấu hình bằng xác thực IAM, sử dụng CloudFront distribution và Lambda@Edge. CloudFront giúp bảo vệ khỏi DDoS và function ở edge sẽ thêm các header thích hợp vào yêu cầu xác thực cho Lambda.
Lambda function URL cung cấp một cách đơn giản hơn để gọi hàm của bạn bằng lệnh gọi HTTP. Tuy nhiên, nếu bạn cần các tính năng nâng cao hơn như xác thực người dùng bằng Amazon Cognito, thẩm định request hoặc điều chỉnh rate, cân nhắc dùng Amazon API Gateway.
Để biết thêm tài nguyên học tập không có máy chủ, hãy truy cập Serverless Land.