Tạo Lambda Function

Tạo Lambda Function

  1. Truy cập vào giao diện AWS Management Console
  • Tìm IAM
  • Chọn IAM

Create Lambda Function

  1. Trong giao diện IAM
  • Chọn Roles
  • Chọn Create role

Create Lambda Function

  1. Trong bước Select trusted entity
  • Trusted entity type, chọn AWS service
  • User case, chọn Lambda
  • Chọn Next

Create Lambda Function

  1. Trong bước Add permissions
  • Chọn Create inline policy
  • Sao chép và dán vào đoạn mã sau:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateKeyPair",
                "ec2:DescribeKeyPairs",
                "ssm:PutParameter"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteKeyPair",
                "ssm:DeleteParameter"
            ],
            "Resource": "*"
        }
    ]
}

Create Lambda Function

  1. Chọn Next
  1. Trong giao diện Name, review and create
  • Role name, nhập ssh-key-gen-role
  • Xem các dịch vụ được Allow
  • Chọn Create role

Create Lambda Function

Create Lambda Function

  1. Kiểm tra lại và chọn Create role

Create Lambda Function

  1. Trong giao diện IAM
  • Tìm ssh-key-gen-role
  • Xem role đã tạo

Create Lambda Function

  1. Trong giao diện AWS Management Console
  • Tìm Lambda
  • Chọn Lambda

Create Lambda Function

  1. Trong giao diện AWS Lambda
  • Chọn Functions
  • Chọn Create function

Create Lambda Function

  1. Trong giao diện Create function
  • Chọn Author from scratch
  • Function name, nhập ssh-key-gen
  • Run time, chọn Python 3.14

Create Lambda Function

  1. Nhấn mở rộng Custom settings
  • Chọn Custom execution role
  • Trong phần Existing role, chọn ssh-key-gen-role
  • Chọn Save

Create Lambda Function

  • Nhấn Create function
  1. Trong nội dung chỉnh sửa Function code, nhập vào nội dung mã lệnh như sau:
"""
This lambda implements the custom resource handler for creating an SSH key
and storing it in SSM parameter store.

e.g.

SSHKeyCR:
    Type: Custom::CreateSSHKey
    Version: "1.0"
    Properties:
      ServiceToken: !Ref FunctionArn
      KeyName: MyKey

An SSH key called MyKey will be created.

"""

import json
import urllib.request
import urllib.error
import traceback
import boto3

def log_exception():
    """Log a stack trace"""
    traceback.print_exc()

def send_response(event, context, response_status):
    """Send a response to CloudFormation to handle the custom resource lifecycle"""

    response_body = { 
        'Status': response_status,
        'Reason': f'See details in CloudWatch Log Stream: {context.log_stream_name}',
        'PhysicalResourceId': context.log_stream_name,
        'StackId': event['StackId'],
        'RequestId': event['RequestId'],
        'LogicalResourceId': event['LogicalResourceId'],
    }

    print('RESPONSE BODY: \n' + json.dumps(response_body))

    data = json.dumps(response_body).encode('utf-8')
    
    req = urllib.request.Request(
        event['ResponseURL'], 
        data=data,
        headers={'Content-Length': str(len(data)), 'Content-Type': ''},
        method='PUT'
    )

    try:
        with urllib.request.urlopen(req) as response:
            print(f'response.status: {response.status}, response.reason: {response.reason}')
            print('response from cfn: ' + response.read().decode('utf-8'))
    except urllib.error.URLError as e:
        print(f"Failed to send response to CloudFormation: {e}")
        log_exception()
        raise Exception('Received non-200 response while sending response to AWS CloudFormation')

    return True

def custom_resource_handler(event, context):
    '''
    This function creates a PEM key, commits it as a key pair in EC2, 
    and stores it, encrypted, in SSM.
    '''
    print("Event JSON: \n" + json.dumps(event))

    pem_key_name = event['ResourceProperties']['KeyName']
    response_status = 'FAILED'
    ec2 = boto3.client('ec2')
    ssm_client = boto3.client('ssm')

    if event['RequestType'] == 'Create':
        try:
            print(f"Creating key name {pem_key_name}")

            key = ec2.create_key_pair(KeyName=pem_key_name)
            key_material = key['KeyMaterial']
            
            param = ssm_client.put_parameter(
                Name=pem_key_name, 
                Value=key_material, 
                Type='SecureString'
            )

            print(param)
            print(f'The parameter {pem_key_name} has been created.')

            response_status = 'SUCCESS'

        except Exception as e:
            print(f'There was an error {e} creating and committing key {pem_key_name} to the parameter store')
            log_exception()
            response_status = 'FAILED'

        send_response(event, context, response_status)
        return

    if event['RequestType'] == 'Update':
        # Do nothing and send a success immediately
        send_response(event, context, 'SUCCESS')
        return

    if event['RequestType'] == 'Delete':
        # Delete the entry in SSM Parameter store and EC2
        try:
            print(f"Deleting key name {pem_key_name}")

            rm_param = ssm_client.delete_parameter(Name=pem_key_name)
            print(rm_param)

            ec2.delete_key_pair(KeyName=pem_key_name)

            response_status = 'SUCCESS'
        except Exception as e:
            print(f"There was an error {e} deleting the key {pem_key_name} from SSM Parameter store or EC2")
            log_exception()
            response_status = 'FAILED'
         
        send_response(event, context, response_status)

def lambda_handler(event, context):
    """Lambda handler for the custom resource"""
    try:
        return custom_resource_handler(event, context)
    except Exception:
        log_exception()
        # Ensure CloudFormation receives a FAILED signal if an unexpected error occurs
        send_response(event, context, 'FAILED')
        raise
  • Hãy cùng nhau phân tích đoạn mã lệnh này có những nội dung gì nhé

  • Thứ nhất: Hàm Handler - mọi lambda function đều có một hàm handler và chúng được gọi khi có bất kỳ một sự kiện nào xảy ra. Nội dung của hàm Handler chỉ đơn giản gọi tới một hàm khác chứa nội dung xử lý cụ thể với sự kiện vừa xảy ra.

def lambda_handler(event, context):
    """Lambda handler for the custom resource"""
    try:
        return custom_resource_handler(event, context)
    except Exception:
        log_exception()
        # Ensure CloudFormation receives a FAILED signal if an unexpected error occurs
        send_response(event, context, 'FAILED')
        raise
  • Thứ hai: hàm custom_resource_handler - là hàm chứa nội dung xử lý chi tiết khi có sự kiện xảy ra. Cụ thể, hàm sẽ thực hiện xác định loại yêu cầu và gửi trả phản hồi lại cho CloudFormation.
    if event['RequestType'] == 'Create':
        try:
            print(f"Creating key name {pem_key_name}")

            key = ec2.create_key_pair(KeyName=pem_key_name)
            key_material = key['KeyMaterial']
            
            param = ssm_client.put_parameter(
                Name=pem_key_name, 
                Value=key_material, 
                Type='SecureString'
            )

            print(param)
            print(f'The parameter {pem_key_name} has been created.')

            response_status = 'SUCCESS'

        except Exception as e:
            print(f'There was an error {e} creating and committing key {pem_key_name} to the parameter store')
            log_exception()
            response_status = 'FAILED'

        send_response(event, context, response_status)
        return
  • Thứ ba: hàm send_response - là hàm gửi trả kết quả phản hồi cho CloudFormation endpoint dựa trên phương thức HTTP PUT. (Đã được nâng cấp để truyền trực tiếp method='PUT' thay cho kiểu cũ, giúp code ngắn gọn và tương thích tốt hơn với Python 3).
def send_response(event, context, response_status):
    """Send a response to CloudFormation to handle the custom resource lifecycle"""

    response_body = { 
        'Status': response_status,
        'Reason': f'See details in CloudWatch Log Stream: {context.log_stream_name}',
        'PhysicalResourceId': context.log_stream_name,
        'StackId': event['StackId'],
        'RequestId': event['RequestId'],
        'LogicalResourceId': event['LogicalResourceId'],
    }

    print('RESPONSE BODY: \n' + json.dumps(response_body))

    data = json.dumps(response_body).encode('utf-8')
    
    req = urllib.request.Request(
        event['ResponseURL'], 
        data=data,
        headers={'Content-Length': str(len(data)), 'Content-Type': ''},
        method='PUT'
    )

    try:
        with urllib.request.urlopen(req) as response:
            print(f'response.status: {response.status}, response.reason: {response.reason}')
            print('response from cfn: ' + response.read().decode('utf-8'))
    except urllib.error.URLError as e:
        print(f"Failed to send response to CloudFormation: {e}")
        log_exception()
        raise Exception('Received non-200 response while sending response to AWS CloudFormation')

    return True
  • Chỉnh sửa code và chọn Deploy

Create Lambda Function

  1. Mặc định, Lambda có thời gian Timeout là 3 giây (quá ngắn để gọi API tạo KeyPair và lưu SSM). Chúng ta cần tăng Timeout lên để Lambda không bị tắt giữa chừng.
  • Chuyển sang tab Configuration
  • Chọn General configuration ở menu bên trái
  • Chọn Edit
  • Chỉnh Timeout lên thành 1 min 0 sec
  • Chọn Save Create Lambda Function Create Lambda Function
  1. Sau khi lưu lại Function và cấu hình xong Timeout, thực hiện sao chép Function ARN ra mục ghi nhớ nào đó. Thông tin này sẽ được sử dụng ở phần sau của bài hướng dẫn.

Create Lambda Function