


{
"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": "*"
}
]
}

ssh-key-gen-role





ssh-key-gen

"""
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
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
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


