Create Lambda Function

Create Lambda Function

  1. Truy cập vào giao diện AWS Management Console
  • Find IAM
  • Select IAM

Create Lambda Function

  1. In the interface IAM
  • Select Roles
  • Select Create role

Create Lambda Function

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

Create Lambda Function

  1. Trong bước Add permissions
  • Select 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. In the interface Name, review and create
  • Role name, nhập ssh-key-gen-role
  • Xem các dịch vụ được Allow
  • Select Create role

Create Lambda Function

Create Lambda Function

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

Create Lambda Function

  1. In the interface IAM
  • Find ssh-key-gen-role
  • Xem role đã tạo

Create Lambda Function

  1. In the interface AWS Management Console
  • Find Lambda
  • Select Lambda

Create Lambda Function

  1. In the interface AWS Lambda
  • Select Functions
  • Select Create function

Create Lambda Function

  1. In the interface Create function
  • Select 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
  • Select Custom execution role
  • Trong phần Existing role, chọn ssh-key-gen-role
  • Select 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
  • Let us analyze what this code does

  • Thứ nhất: Hàm Handler - mọi lambda function đều có một hàm handler and they are called when any event occurs. The content of the Handler function simply calls another function containing specific processing content for the event that just occurred.

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 when an event occurs. Specifically, the function will determine the request type and send a response back to 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 based on the method HTTP PUT. (Has been upgraded to pass directly method='PUT' instead of the old style, making the code more concise and more compatible with 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
  • Edit code and select Deploy

Create Lambda Function

  1. By default, Lambda has a Timeout of 3 seconds (too short to call the API to create KeyPair and save SSM). We need to increase the Timeout so that Lambda does not shut down prematurely.
  • Switch to tab Configuration
  • Select General configuration on the left menu
  • Select Edit
  • Change Timeout to 1 min 0 sec
  • Select Save Create Lambda Function Create Lambda Function
  1. After saving the Function and configuring the Timeout, copy the Function ARN to a certain memo. This information will be used later in the tutorial.

Create Lambda Function