概要
以下のようなSession Managerの設定をCloudFormationで行います。
また、そもそもどうやってSession Managerの設定をAPIで行うの?といった点も説明します。
結論のCloudFormationテンプレート
以下テンプレートでSession Managerの設定が可能です。
cloudWatchLogGroupNameなどの項目を書き換えて利用します。
CloudFormationスタックを更新することでSession Managerの設定も更新できますが、スタックを削除した時は何も起こりません。(設定が初期値に戻る、とかも無い)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
AWSTemplateFormatVersion: "2010-09-09" Resources: SsmDocument: Type: Custom::SsmDocument Properties: ServiceToken: !GetAtt Function.Arn DocumentName: SSM-SessionManagerRunShell DocumentType: Session DocumentFormat: YAML DocumentContent: | schemaVersion: '1.0' inputs: cloudWatchEncryptionEnabled: false s3EncryptionEnabled: false runAsDefaultUser: '' s3BucketName: '' cloudWatchStreamingEnabled: true kmsKeyId: '' runAsEnabled: false idleSessionTimeout: '15' s3KeyPrefix: '' shellProfile: linux: '' windows: '' cloudWatchLogGroupName: "/aws/ssm/sessionlog" description: Document to hold regional settings for Session Manager sessionType: Standard_Stream Function: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 import cfnresponse from botocore.exceptions import ClientError ssm = boto3.client('ssm') def lambda_handler(event, context): try: document_params = { 'Name': event['ResourceProperties']['DocumentName'], 'Type': event['ResourceProperties']['DocumentType'], 'Format': event['ResourceProperties']['DocumentFormat'], 'Content': event['ResourceProperties']['DocumentContent'], } 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, {}) except: cfnresponse.send(event, context, cfnresponse.FAILED, {}) 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['Type'], ] }, ], )['DocumentIdentifiers'] if len(document_identifiers) == 1: return True else: return False def create_document(document_params:dict) -> None: response = ssm.create_document( Content=document_params['Content'], Name=document_params['Name'], DocumentType=document_params['Type'], DocumentFormat=document_params['Format'], ) print(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['Format'], DocumentVersion='$LATEST', )['DocumentDescription']['DocumentVersion'] except ClientError as e: if e.response['Error']['Code'] == 'DuplicateDocumentContent': print('no update to the SSM document') return None response = ssm.update_document_default_version( Name=document_params['Name'], DocumentVersion=updated_version ) print(response) Handler: index.lambda_handler MemorySize: 128 Role: !GetAtt FunctionRole.Arn Runtime: python3.7 Timeout: 120 FunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: delete-document PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ssm:ListDocuments - ssm:UpdateDocument - ssm:CreateDocument - ssm:UpdateDocumentDefaultVersion Resource: - "*" |
CloudFormation経由で行う方法を模索
まずCloudFormationで設定を行おうとした場合、ドキュメントを見てもそれらしきリソースが見当たりません。
そこでカスタムリソースを視野に入れてAPI経由で設定を行う方法が無いか探してみると、ドキュメントに以下が記載されていました。
アカウントに Session Manager を設定すると、Session タイプの SSM ドキュメント SSM-SessionManagerRunShell が作成されます。
この SSM ドキュメントには、~するかどうかなど、セッションの設定が保存されます。
つまりSession Managerの設定を行うためにはSSM-SessionManagerRunShellという名前でSSMドキュメントを作成すればよい、ということになります。
CloudFormationでAWS::SSM::Documentリソースを作成してみる
以下のように、AWS::SSM::Documentを利用してSSMドキュメントを作成するのがシンプルで良いと思っていました。
実際、CloudFormationスタックの作成=SSMドキュメントの作成=Session Managerの初回設定まではうまくいきました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
AWSTemplateFormatVersion: "2010-09-09" Resources: SsmDocument: Type: AWS::SSM::Document Properties: Name: SSM-SessionManagerRunShell DocumentType: Session Content: schemaVersion: '1.0' description: Document to hold regional settings for Session Manager sessionType: Standard_Stream inputs: s3BucketName: '' s3KeyPrefix: '' s3EncryptionEnabled: false cloudWatchLogGroupName: '/aws/ssm/sessionlog' cloudWatchEncryptionEnabled: false idleSessionTimeout: '20' cloudWatchStreamingEnabled: true kmsKeyId: '' runAsEnabled: false runAsDefaultUser: '' shellProfile: windows: '' linux: '' |
ただし、Session Managerの設定(cloudWatchLogGroupNameなど)を書き換えてスタックを更新すると以下エラーが発生します。
CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename SSM-SessionManagerRunShell and update the stack again.
これ自体はよく知られた(?)エラーで、エラーメッセージにある通り SSM-SessionManagerRunShell という名前を変更することで解消されます。
ただし「Session Managerの設定は SSM-SessionManagerRunShell という名前のSSMドキュメントに保存される」という仕様上、今回の場合は名前を変更することは出来ません。
(変更するとSession Managerのデフォルト設定としては利かなくなってしまう)
CloudFormationカスタムリソースでSSMドキュメントを作成してみる
ということで、最終的にこの形に落ち着きました。
カスタムリソース(Lambda)の処理の中で特筆すべき点は、既にドキュメントが存在する場合の update_document を実行する部分です。
update_document 実施後に update_document_default_version を実施することで最新の設定内容をドキュメントのデフォルトバージョンとして登録します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def update_document(document_params:dict) -> None: try: updated_version = ssm.update_document( Content=document_params['Content'], Name=document_params['Name'], DocumentFormat=document_params['Format'], DocumentVersion='$LATEST', )['DocumentDescription']['DocumentVersion'] except ClientError as e: if e.response['Error']['Code'] == 'DuplicateDocumentContent': print('no update to the SSM document') return None response = ssm.update_document_default_version( Name=document_params['Name'], DocumentVersion=updated_version ) print(response) |
ほか、周辺知識
今回設定したのはカスタムセッションドキュメントを利用しない場合のSession Managerの設定です。
Session Manager 設定を作成する (コマンドライン)
この手順を使用して、アカウントレベルの設定を上書きする Session Manager の詳細設定のカスタムセッションドキュメントを作成できます。カスタムセッションドキュメントを作成するときは、name パラメータに SSM-SessionManagerRunShell 以外の値を指定し、必要に応じて入力を変更します。カスタムセッションドキュメントを使用するには、AWS Command Line Interface (AWS CLI) からセッションを開始するときに、--document-name パラメータにカスタムセッションドキュメントの名前を指定する必要があります。コンソールからセッションを開始する場合、カスタムセッションドキュメントを指定することはできません。
つまり、AWSマネジメントコンソールからSession Managerを利用する限りにおいては、今回CloudFormationで組んだSession Managerの設定が必ず活きることになります。
投稿者プロフィール
- 2015年8月入社。弊社はインフラ屋ですが、アプリも作ってみたいです。