Được viết bởi Jerome Van Der Linden, Senior Solutions Architect Builder
Lambda function URL là một HTTP Endpoint dành riêng cho AWS Lambda function. Khi đã cấu hình, bạn có thể gọi một Function trực tiếp chỉ với HTTP Request. Bạn có thể lựa chọn Public nó bằng cách cài đặt kiểu xác thực của open API thành NONE. Hoặc bạn có thể bảo vệ nó bằng AWS IAM với kiểu xác thực là AWS_IAM, trong trường hợp này, chỉ những User hoặc Role được cho phép mới có thể gọi Function thông qua Function URL.
Lambda@Edge là một tính năng của Amazon CloudFront giúp code có thể chạy gần hơn về phía người dùng cuối của ứng dụng, nó thường được sử dụng để thao tác HTTP Request và HTTP Response giữa User Client và gốc của hệ thống (Application’s Origin, trong ví dụ này là Lambda Function URL). Cụ thể, nó có thể thêm một vài Header vào HTTP Request như “Authorization”.
Bài viết này sẽ trình bày cách sử dụng CloudFront và Lambda@Edge để bảo vệ Lambda Function URL với kiểu xác thực là AWS_IAM, bằng cách thêm các Header phù hợp vào HTTP Request trước khi nó đến được ứng dụng gốc.
Tổng quan
Trong ví dụ này, ta có bốn thành phần sau:
- Các Lambda Function đã bật Function URL: Các Function chứa các đoạn Code liên quan đến nghiệp vụ mà Frontend có thể gọi. Function URL được cấu hình kiểu xác thực AWS_IAM nên chỉ User và Role đã được xác thực mới có thể gọi.
- Một CloudFront Distribution: CloudFront là một dịch vụ mạng lưới phân phối nội dung được sử dụng để phân phối nội dung đến người dùng với độ trễ thấp. Nó có thể cải thiện tính bảo mật với việc mã hoá traffic và hệ thống chống DDoS. Trong ví dụ này, đặt CloudFront ở phía trước của Lambda URL ta có thể tận dụng được lớp bảo mật và hệ thống Cache dữ liệu của CloudFront.
- Một Lambda Function tại biên (Edge): CloudFront hỗ trợ vận hành Lambda Function gần hơn với người dùng(về mặt khoảng cách) với tính năng Lambda@Edge. Ví dụ này sử dụng nó để duyệt các Request đến Lambda Function URL và thêm một số Header phù hợp vào Request để các lời gọi Function thông qua URL đều sẽ được xác thực với IAM.
- Một ứng dụng web gọi đến Lambda Function URL: Trong ví dụ này, ta có một ứng dụng SPA xây dựng bằng React, nơi để người dùng có thể tạo các Request đến các Lamdba Function URL. Các tài nguyên tĩnh như HTML, CSS hay JS sẽ được lưu trữ với Amazon S3 và sử dụng CloudFront để mọi người có thể truy cập.
Sau đây là kiến trúc mẫu:
Ta có luồng chạy như sau:
- User tạo ra các Request ở phía Client đến các tài nguyên tĩnh của ứng dụng React hay Lambda Function URL.
- Đối với các tài nguyên tĩnh, CloudFront sẽ lấy từ S3 Bucket hoặc Cache và trả về cho người dùng.
- Nếu là Request đến Lambda Function URL, đầu tiên nó sẽ đi đến Lambda@Edge. Lambda@Edge Function sẽ có quyền lambda:InvokeFunctionUrl đối với một Lambda Function URL và sử dụng nó để ký (Sign) các Request với chữ ký V4, nó thêm các Header như Authorization, X-Amz-Security-Token hay X-Amz-Date.
- Sau khi các Request được ký, CloudFront sẽ chuyển nó sang Lambda Function URL.
- Lambda sẽ vận hành các Function đễ thực hiện các hoạt động liên quan đến Business Logic. Trong ví dụ này là xử lý sách(Tạo, Xem, Xoá, Cập nhật)
- Lambda trả kết quả về cho CloudFront.
- Cuối cùng CloudFront trả kết quả về cho Client.
Ta có một số kiểu Event để kích hoạt Lambda@Edge function:
- Viewer Request: Sau khi CloudFront nhận được Request từ phía Client.
- Origin Request: Trước khi Request được chuyển tiếp đến Origin.
- Origin Response: Sau khi CloudFront nhận được Response từ Origin.
- Viewer Response: Trước khi Response được trả về cho Client.
Trong ví dụ này, để update Request trước khi chuyển đến Origin (Lambda Function URL) ta sẽ sử dụng Origin Request.
Bạn có thể tìm thấy ví dụ đầy đủ dựa trên AWS Cloud Development Kit(CDK), tại Github.
Backend Stack
Backend bao gồm nhiều Lambda Function và Lambda Function URL riêng biệt. Nó sử dụng kiểu xác thực AWS_IAM và CORS(Cross Origin Access Sharing) khi thêm một Function URL vào một Lambda Function. Sử dụng allowedOrigins mang tính bảo mật nghiêm ngặt 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 bao gồm CloudFront Distribution và Lambda@Edge Function. Dưới đây là đoạn code 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’)), }); |
Policy sau giúp cho Lambda@Edge Function có thể ký các Request với quyền thích hợp, cùng với đó là gọi Lambda Function:
| authFunction.addToRolePolicy(new PolicyStatement({ sid: ‘AllowInvokeFunctionUrl’, effect: Effect.ALLOW, actions: [‘lambda:InvokeFunctionUrl’], resources: [getBookArn, getBooksArn, createBookArn, updateBookArn, deleteBookArn], conditions: { “StringEquals”: {“lambda:FunctionUrlAuthType”: “AWS_IAM”} }})); |
Code của Function sử dụng AWS JavaScript SDK, chính xác hơn là V4 Signature. Có 2 thứ quan trọng ở đây:
- Dịch vụ mà ta muốn ký Request: Lambda
- Thông tin xác thực của Function (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ể tìm được toàn bộ code của Function tại đây.
CloudFront Distribution và Behaviors definition
CloudFront Distribution có một Behavior mặc định với S3 Origin dành cho các tài nguyên tĩnh của ứng dụng React.
Nó cũng có Behavior dành cho mỗi Function URL, được định nghĩa trong đoạn code sau. Bạn có thể xem cấu hình của Lambda@Edge Function với LamdbaEdgeEventType là ORIGIN_REQUEST và Behavior 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); |
Các vấn đề liên quan Region
Lambda@Edge Function phải ở trong Region us-east-1(N. Virginia), giống như Frontend Stack. Nếu bạn triển khai Backend Stack trong một Region khác, bạn phải thêm Lambda Function URL(và ARN) vào Frontend. Với việc sử dụng tài nguyên tuỳ chỉnh trong CDK, ta có thể tạo ra các tham số chứa thông tin trong AWS Systems Manager Parameter Store trong Region us-east-1. Chi tiết hơn, vui lòng xem code tại Repo này.
Hướng dẫn chi tiết
Trước khi triển khai giải pháp, bạn có thể xem qua README trong Github Repo và đảm bảo đã chuẩn bị đầy đủ.
Triển khai giải pháp
- Tại thư mục gốc, cài đặt các package
| npm install |
- Bắt đầu triển khai giải pháp, bước này có thể cần đến 15 phút
| cdk deploy –all |
- Khi triển khai thành công, output sẽ bao gồm Lamdba Function URL và các URL được “bảo vệ” phía sau CloudFront Distribution
Kiểm thử
- Sử dụng cURL, truy xuất đến Lambda Function URL để xem toàn bộ sách(GetBooksFunctionURL trong Output của CDK)
Bạn sẽ nhận được Output như sau. Như dự đoán, ta không thể truy cập trực tiếp đến Lambda Function URL mà không có xác thực từ IAM.
- Giờ ta hãy truy cập URL “được bảo vệ” để xem tất cả sách (GetBooksURL trong Output)
Tại thời điểm này bạn sẽ nhận được kết quả HTTP 200 OK với một danh sách rỗng.
Log của Lambda@Edge Function(Tìm AuthFunctionAtEdge trong
CloudWatch) cho thấy:
- Request đến:
- Các Request đã được ký xác thực, với các Header như Authorization, X-Amz-Security-Token và X-Amz-Date. Những Header này tạo nên sự khác biệt khi Lambda URL tiếp nhận Request và xác thực với IAM.
Bạn có thể kiểm thử toàn bộ toàn bộ giao diện người dùng với FrontendURL trong CDK Output.
Dọn dẹp tài nguyên
Lambda@Edge Function sẽ tự động nhân bản sang toàn bộ Region, bạn nên xoá toàn bộ nhân bản trước khi xoá phần còn lại của ví dụ.
Để xoá các tài nguyên đã triển khai, sử dụng lệnh:
| cdk destroy –all |
Kết luận
Bài viết này trình bày cách để bảo vệ Lambda Function Url, được cấu hình xác thực IAM, sử dụng CloudFront Distribution và Lambda@Edge. CloudFront giúp bảo vệ khỏi DDoS và các Function tại biên giúp thêm các Header cần thiết để xác thực với Lambda.
Lambda Function URL cung cấp cách thức đơn giản để gọi các Function thông qua HTTP. Tuy nhiên, nếu bạn muốn các tính năng nâng cao như xác thực người dùng với Amazon Cognito, Request Validation hay Rate Thottling, hãy xem xét sử dụng Amazon API Gateway.
Tìm hiểu thêm về Serverless tại Serverless Land.