AWSTemplateFormatVersion: "2010-09-09"
Parameters:
IdleSessionTimeout:
Type: Number
Default: 30
Resources:
SsmDocument:
Type: Custom::SsmDocument
Properties:
ServiceToken: !GetAtt Function.Arn
DocumentParams:
Name: SSM-SessionManagerRunShell
DocumentType: Session
DocumentFormat: JSON
Content: !Sub |
{
"schemaVersion": "1.0",
"inputs": {
"cloudWatchEncryptionEnabled": false,
"s3BucketName": "",
"s3KeyPrefix": "",
"s3EncryptionEnabled": false,
"runAsDefaultUser": "",
"cloudWatchStreamingEnabled": false,
"kmsKeyId": "",
"runAsEnabled": false,
"idleSessionTimeout": "${IdleSessionTimeout}",
"shellProfile": {
"linux": "",
"windows": ""
},
"cloudWatchLogGroupName": ""
},
"description": "Document to hold regional settings for Session Manager",
"sessionType": "Standard_Stream"
}
Function:
Type: AWS::Lambda::Function
DependsOn: FunctionLog
Properties:
FunctionName: !Join
- ''
- - ssm-document-custom-function-
- !Select [ '0', !Split [ '-', !Select [ '2', !Split [ '/', !Ref AWS::StackId ] ] ] ]
Code:
ZipFile: |
import logging
import os
import traceback
import boto3
import cfnresponse
from botocore.exceptions import ClientError
logger = logging.getLogger()
logger.setLevel(os.environ['LOG_LEVEL'])
ssm = boto3.client('ssm')
def lambda_handler(event, context):
try:
document_params = event['ResourceProperties']['DocumentParams']
account_id = event['StackId'].split(':')[4]
document_arn = f'arn:aws:ssm:{os.environ["AWS_REGION"]}:{account_id}:document/{document_params["Name"]}'
if event['RequestType'] in ['Create', 'Update']:
if document_exists(document_params):
update_document(document_params)
else:
create_document(document_params)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, physicalResourceId=document_arn)
except:
tb = traceback.format_exc()
logger.error(tb)
cfnresponse.send(event, context, cfnresponse.FAILED, {}, physicalResourceId=None, noEcho=False, reason=tb.replace('\n', r'\n'))
def document_exists(document_params:dict) -> bool:
document_identifiers = ssm.list_documents(
Filters=[
{
'Key': 'Name',
'Values': [ document_params['Name'] ]
},
{
'Key': 'Owner',
'Values': [ 'Self' ]
},
{
'Key': 'DocumentType',
'Values': [ document_params['DocumentType'] ]
},
],
)['DocumentIdentifiers']
if len(document_identifiers) == 0:
return False
elif len(document_identifiers) == 1:
return True
def create_document(document_params:dict) -> None:
response = ssm.create_document(
Content=document_params['Content'],
Name=document_params['Name'],
DocumentType=document_params['DocumentType'],
DocumentFormat=document_params['DocumentFormat'],
)
logger.info(response)
def update_document(document_params:dict) -> None:
try:
updated_version = ssm.update_document(
Content=document_params['Content'],
Name=document_params['Name'],
DocumentFormat=document_params['DocumentFormat'],
DocumentVersion='$LATEST',
)['DocumentDescription']['DocumentVersion']
except ClientError as e:
if e.response['Error']['Code'] == 'DuplicateDocumentContent':
logger.info('no update to the SSM document')
return None
else:
raise e
response = ssm.update_document_default_version(
Name=document_params['Name'],
DocumentVersion=updated_version
)
logger.info(response)
Environment:
Variables:
LOG_LEVEL: INFO
Handler: index.lambda_handler
MemorySize: 128
Role: !GetAtt FunctionRole.Arn
Runtime: python3.8
Timeout: 120
FunctionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Join
- ''
- - ssm-document-custom-function-role-
- !Select [ '0', !Split [ '-', !Select [ '2', !Split [ '/', !Ref AWS::StackId ] ] ] ]
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: ssm-document-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ssm:ListDocuments
Resource: "*"
- Effect: Allow
Action:
- ssm:UpdateDocument
- ssm:CreateDocument
- ssm:UpdateDocumentDefaultVersion
Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/SSM-SessionManagerRunShell
- PolicyName: lambda-logs-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Join
- ''
- - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/ssm-document-custom-function-
- !Select [ '0', !Split [ '-', !Select [ '2', !Split [ '/', !Ref AWS::StackId ] ] ] ]
- :*
FunctionLog:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Join
- ''
- - /aws/lambda/ssm-document-custom-function-
- !Select [ '0', !Split [ '-', !Select [ '2', !Split [ '/', !Ref AWS::StackId ] ] ] ]
RetentionInDays: 7