Chắc hẳn trong chúng ta khi dựng multiple environment luôn gặp trường hợp làm sao public môi trường preprod (là môi trường gần giống prod nhất) để cho đội ngũ tester kiểm thử trước khi đẩy lên prod thay vì sử dụng VPN,… hay các bạn setup nginx reverse proxy thì hay dựng basic authentication thì đối với các hệ thống trên aws, ngoài việc dùng nginx, ta cũng có thể thông qua AWS Application Load Balancer kết hợp với Lambda.
Sau khi làm xong demo này bạn sẽ
- Biết Authorization header trong listener rule
WWW-Authenticate: Basic
trong Lambda- Xác thực đơn giản mà không cần triển khai code
Cơ bản thì sẽ cấu hình project như sau
Basic Authentication là gì
Hiểu cơ bản nó là hệ thống xác thực thông tin đăng nhập của bạn. Những thông tin này cho hệ thống biết bạn là ai. Cho phép hệ thống đảm bảo và xác nhận danh tính của bạn trước khi thực thi trên hệ thống….
Demo
Tạo IAM role cho Lambda
Chúng ta cần tạo 1 role cho lambda để nó có thể tác động đến AWS ALB
Chọn AWSLambdaBasicExecutionRole trong mục Add permission
Vậy là đã tạo xong role có tên là basic_auth_role
Tạo function Lambda
Chọn service Lambda vào cấu hình như sau:
- Handler: index.handler
- Role: Bạn sẽ chọn role vừa tạo ở Step 1
- Runtime: “nodejs10.x”
- MemorySize: 128
- Timeout: 15
Xong khi tạo Lambda, copy đoạn code sau và Deploy nó, hiểu cơ bản là Lambda sẽ kiểm tra request header có trả đúng với listener trong ALB hay không nếu không thì trả về Unauthorized, ngược lại đúng thì ALB được thực thi
const debug = (key, object) => { console.log(`DEBUG: ${key}\n`, JSON.stringify(object)); }
export const handler = async(event) => {
console.log("INFO: request Recieved.\nEvent:\n", JSON.stringify(event));
return {
statusCode: 401,
statusDescription: '401 Unauthorized',
body: 'Unauthorized',
isBase64Encoded: false,
headers: {
'WWW-Authenticate': 'Basic',
'Content-Type': 'text/html'
}
};
};
Tạo Target Group
Tiếp theo, trước khi tạo ALB thì ta cần tạo một Target Group, lưu ý Target Type sẽ là Lambda function
Chọn lambda function bạn đã tạo và version của bộ code
Tạo ALB
Chọn tiếp service Load Balancer, type Application Load Balancer và chọn target group mà bạn vừa tạo ở trên với HTTP và port 80
Sau khi tạo thành công ALB, trong hi đợi bạn tiếp tục Edit listener để thêm vào phần Basic Authen
Phần IF
- HttpHeaderName: Authorization
- Value: bạn sẽ hash cụm username và password là “admin:admin” thành “YWRtaW46YWRtaW4=”. Và thêm tiền tố “Basic”
Phần THEN
- Type: fixed-response
- StatusCode: 200
- MessageBody: Authorized
- ContentType: text/plain
Kiểm tra Lambda permission
Ta cần xem xéc Lambda đã có permission thực thi ALB hay chưa? Chọn Lambda function => Configuration => Permissions
Như này thì ok rồi.
Kiểm thử
Vào service ALB, copy paste DNS lên web browers
Nó hiển thị 1 popup như hình, bạn nhập admin:admin, để xem kết quả hoặc bấm cancel, nhập sai password…
Kết quả sai sẽ hiển thị Unauthorized vì đây là nội dung bạn setup trong phần code lambda
Kết quả đúng sẽ hiển thị Authorized vì đây là nội dung bạn setup trong phần Listener của ALB
Phần cơm thêm: Cloud Formation
Ngoài việc, tự cài tay như trên thì bạn có thể sử dụng template sau để setup thông qua Cloud Formation
Parameters:
Username:
Default: ""
Type: String
Password:
Default: ""
Type: String
Mappings:
SubnetConfig:
VPC:
CidrBlock: 10.0.0.0/16
Public1:
CidrBlock: 10.0.0.0/24
AvailabilityZone: us-east-1a
Public2:
CidrBlock: 10.0.1.0/24
AvailabilityZone: us-east-1c
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
EnableDnsSupport: true
EnableDnsHostnames: true
CidrBlock: !FindInMap [SubnetConfig, VPC, CidrBlock]
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref "VPC"
CidrBlock: !FindInMap ["SubnetConfig", "Public1", "CidrBlock"]
AvailabilityZone: !FindInMap ["SubnetConfig", "Public1", "AvailabilityZone"]
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref "VPC"
CidrBlock: !FindInMap ["SubnetConfig", "Public2", "CidrBlock"]
AvailabilityZone: !FindInMap ["SubnetConfig", "Public2", "AvailabilityZone"]
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachement:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref "VPC"
PublicRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachement
Properties:
RouteTableId: !Ref "PublicRouteTable"
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref "InternetGateway"
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
LoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the LoadBalancer from Internet
VpcId: !Ref "VPC"
LoadBalancerSecurityGroupIngressHTTP:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
Description: HTTP
GroupId: !Ref LoadBalancerSecurityGroup
CidrIp: 0.0.0.0/0
FromPort: 80
ToPort: 80
IpProtocol: tcp
LoadBalancer:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref LoadBalancerSecurityGroup
LoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn:
!Ref LoadBalancerTargetGroupUnauthorized
LoadBalancerArn: !Ref LoadBalancer
Port: 80
Protocol: HTTP
LoadBalancerListenerAuthorizedRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- Type: fixed-response
FixedResponseConfig:
StatusCode: 200
MessageBody: Authorized
ContentType: text/plain
Conditions:
- Field: http-header
HttpHeaderConfig:
HttpHeaderName: Authorization
Values:
- Fn::Join:
- " "
- - "Basic"
- Fn::Base64: !Sub ${Username}:${Password}
ListenerArn: !Ref LoadBalancerListener
Priority: 10
LoadBalancerTargetGroupUnauthorized:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
TargetType: lambda
Targets:
- Id: !GetAtt LambdaSendUnauthrized.Arn
DependsOn:
- LambdaPermissionSendUnauthrized
LambdaSendUnauthrized:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |+
const debug = (key, object) => { console.log(`DEBUG: ${key}\n`, JSON.stringify(object)); }
export const handler = async(event) => {
console.log("INFO: request Recieved.\nEvent:\n", JSON.stringify(event));
return {
statusCode: 401,
statusDescription: '401 Unauthorized',
body: 'Unauthorized',
isBase64Encoded: false,
headers: {
'WWW-Authenticate': 'Basic',
'Content-Type': 'text/html'
}
};
};
Handler: index.handler
Role: !GetAtt LambdaSendUnauthrizedExecutionRole.Arn
Runtime: "nodejs10.x"
MemorySize: 128
Timeout: 15
LambdaSendUnauthrizedExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- "sts:AssumeRole"
Path: /
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
LambdaPermissionSendUnauthrized:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt LambdaSendUnauthrized.Arn
Principal: elasticloadbalancing.amazonaws.com
Outputs:
LoadBalancerDNSName:
Value: !GetAtt LoadBalancer.DNSName
Chúc các bạn thàng công!!