AWS::SSM::Association - AWS CloudFormation
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

AWS::SSM::Association

AWS::SSM::Association 资源为您的托管实例创建 State Manager 关联。State Manager 关联定义了要在实例上维护的状态。例如,关联可以指定必须在实例上安装并运行防病毒软件,或必须关闭特定端口。对于静态目标,关联可指定将重新应用配置的计划。对于动态目标(如 AWS 资源组或 AWS AutoScaling 组),State Manager 在新实例添加到组时应用配置。关联还指定在应用配置时要执行的操作。例如,防病毒软件的关联可能每天运行一次。如果软件未安装,State Manager 将安装该软件。如果已安装该软件,但未运行服务,则关联会指示 State Manager 启动服务。

语法

要在 AWS CloudFormation 模板中声明此实体,请使用以下语法:

JSON

{ "Type" : "AWS::SSM::Association", "Properties" : { "ApplyOnlyAtCronInterval" : Boolean, "AssociationName" : String, "AutomationTargetParameterName" : String, "ComplianceSeverity" : String, "DocumentVersion" : String, "InstanceId" : String, "MaxConcurrency" : String, "MaxErrors" : String, "Name" : String, "OutputLocation" : InstanceAssociationOutputLocation, "Parameters" : {Key : Value, ...}, "ScheduleExpression" : String, "SyncCompliance" : String, "Targets" : [ Target, ... ], "WaitForSuccessTimeoutSeconds" : Integer } }

属性

ApplyOnlyAtCronInterval

默认情况下,创建新关联时,系统会在创建关联后立即运行它,然后根据您指定的计划运行它。如果您不希望在创建关联后立即运行它,请指定此选项。

必需:否

类型:布尔值

Update requires: No interruption

AssociationName

指定关联的描述性名称。

必需:否

类型:字符串

模式^[a-zA-Z0-9_\-.]{3,128}$

Update requires: No interruption

AutomationTargetParameterName

指定关联的目标。对于使用 Automation 文档的关联和使用费率控制的目标资源,此目标是必需的。

必需:否

类型:字符串

最低1

最高50

Update requires: No interruption

ComplianceSeverity

分配给关联的严重性级别。

必需:否

类型:字符串

允许的值CRITICAL | HIGH | LOW | MEDIUM | UNSPECIFIED

Update requires: No interruption

DocumentVersion

要与目标关联的 SSM 文档的版本。

必需:否

类型:字符串

模式([$]LATEST|[$]DEFAULT|^[1-9][0-9]*$)

Update requires: No interruption

InstanceId

与 SSM 文档关联的实例的 ID。您必须指定 InstanceIdTargets 属性。

注意

InstanceId 已被弃用。要指定关联的实例 ID,请使用 Targets 参数。如果您使用参数 InstanceId,则无法使用参数 AssociationNameDocumentVersionMaxErrorsMaxConcurrencyOutputLocationScheduleExpression。要使用这些参数,您必须使用 Targets 参数。

必需:条件

类型:字符串

模式(^i-(\w{8}|\w{17})$)|(^mi-\w{17}$)

Update requires: No interruption

MaxConcurrency

允许同时运行关联的最大目标数。您可以指定一个数字(例如 10)或目标集的百分比(例如 10%)。默认值为 100%,这意味着所有目标都同时运行关联。

如果新实例启动并尝试在 Systems Manager 运行 MaxConcurrency 关联时运行关联,则允许该关联运行。在下一个关联时间间隔内,新实例将在为 MaxConcurrency 指定的限制内处理其关联。

必需:否

类型:字符串

最低1

最高7

模式^([1-9][0-9]*|[1-9][0-9]%|[1-9]%|100%)$

Update requires: No interruption

MaxErrors

系统停止发送请求以在其他目标上运行关联之前允许的错误数。您可以指定绝对数量的错误 (如 10),也可以指定目标集百分比 (如 10%)。例如,如果您指定 3,系统将在收到第四个错误时停止发送请求。如果指定 0,则返回第一个错误后系统停止发送请求。如果您在 50 个实例上运行关联并将 MaxError 设置为 10%,则系统在收到第六个错误时停止发送请求。

将允许完成在到达 MaxErrors 时已在运行关联的执行,但其中一些执行也可能失败。如果您需要确保失败的执行次数不超过最大错误数,请将 MaxConcurrency 设置为 1,以便一次只有一个执行。

必需:否

类型:字符串

最低1

最高7

模式^([1-9][0-9]*|[0]|[1-9][0-9]%|[0-9]%|100%)$

Update requires: No interruption

Name

包含实例的配置信息的 SSM 文档的名称。您可以指定 CommandAutomation 文档。这些文档可以是 AWS 预定义文档、您创建的文档,也可以是从其他账户与您共享的文档。对于从其他 AWS 账户与您共享的 SSM 文档,您必须按以下格式指定完整的 SSM 文档 ARN:

arn:partition:ssm:region:account-id:document/document-name

例如:arn:aws:ssm:us-east-2:12345678912:document/My-Shared-Document

对于您在账户中创建的 AWS 预定义文档和 SSM 文档,您只需指定文档名称。例如,AWS-ApplyPatchBaseline 或 My-Document。

必需:是

类型:字符串

模式^[a-zA-Z0-9_\-.:/]{3,128}$

Update requires: No interruption

OutputLocation

您希望将请求的输出详细信息存储到的 S3 存储桶。

必需:否

类型InstanceAssociationOutputLocation

Update requires: No interruption

Parameters

文档的运行时配置的参数。

必需:否

类型ParameterValues 的映射

Update requires: No interruption

ScheduleExpression

一个 cron 表达式,指定关联何时运行的计划。

必需:否

类型:字符串

最低1

最高256

Update requires: No interruption

SyncCompliance

用于生成关联合规性的模式。您可以指定 AUTOMANUAL。在 AUTO 模式下,系统使用关联执行的状态来确定合规性状态。如果关联执行成功运行,则关联为 COMPLIANT。如果关联执行未成功运行,则关联为 NON-COMPLIANT

MANUAL 模式下,您必须将 AssociationId 指定为 PutComplianceItems API 操作的参数。在这种情况下,合规性数据不由状态管理器管理。它通过直接调用 PutComplianceItems API 操作进行管理。

默认情况下,所有关联都使用 AUTO 模式。

必需:否

类型:字符串

允许的值AUTO | MANUAL

Update requires: No interruption

Targets

关联的目标。您必须指定 InstanceIdTargets 属性。

必需:条件

类型目标的列表

最高5

Update requires: No interruption

WaitForSuccessTimeoutSeconds

服务在继续执行堆栈之前等待关联状态显示“成功”的秒数。如果关联状态在指定的秒数后未显示“成功”,说明堆栈创建失败。

必需:否

类型:整数

Update requires: No interruption

返回值

Fn::GetAtt

AssociationId

Not currently supported by AWS CloudFormation.

示例

为特定实例创建关联

以下示例创建一个使用 AWS-RunShellScript SSM 文档的关联。关联在特定实例上运行一个简单的命令。

JSON

{ "Resources": { "SpecificInstanceIdAssociation": { "Type": "AWS::SSM::Association", "Properties": { "Name": "AWS-RunShellScript", "Targets": [ { "Key": "InstanceIds", "Values": [ "i-1234567890abcdef0" ] } ], "Parameters": { "commands": [ "ls" ], "workingDirectory": [ "/" ] }, "WaitForSuccessTimeoutSeconds": 300 } } } }

YAML

--- Resources: SpecificInstanceIdAssociation: Type: AWS::SSM::Association Properties: Name: AWS-RunShellScript Targets: - Key: InstanceIds Values: - i-1234567890abcdef0 Parameters: commands: - ls workingDirectory: - "/" WaitForSuccessTimeoutSeconds: 300

为 AWS 账户中的所有托管实例创建关联

以下示例创建一个使用 AWS-UpdateSSMAgent SSM 文档的关联。关联会根据指定的 CRN 计划更新用户 AWS 账户中所有托管实例(为 Systems Manager 配置的实例)上的 SSM 代理。

JSON

{ "Resources": { "AllInstanceIdsAssociation": { "Type": "AWS::SSM::Association", "Properties": { "AssociationName": "UpdateSSMAgent", "Name": "AWS-UpdateSSMAgent", "ScheduleExpression": "cron(0 2 ? * SUN *)", "Targets": [ { "Key": "InstanceIds", "Values": [ "*" ] } ], "WaitForSuccessTimeoutSeconds": 300 } } } }

YAML

--- Resources: AllInstanceIdsAssociation: Type: AWS::SSM::Association Properties: AssociationName: UpdateSSMAgent Name: AWS-UpdateSSMAgent ScheduleExpression: cron(0 2 ? * SUN *) Targets: - Key: InstanceIds Values: - "*" WaitForSuccessTimeoutSeconds: 300

为特定标签创建关联

以下示例创建一个使用 AWS-UpdateSSMAgent SSM 文档的关联。关联会在分配了标签键 Environment 和值 Production 的所有托管实例上更新 SSM 代理。关联根据指定的费率表达式每七天运行一次。

JSON

{ "Resources": { "TaggedInstancesAssociation": { "Type": "AWS::SSM::Association", "Properties": { "AssociationName": "UpdateSSMAgent", "Name": "AWS-UpdateSSMAgent", "ScheduleExpression": "rate(7 days)", "Targets": [ { "Key": "tag:Environment", "Values": [ "Production" ] } ], "WaitForSuccessTimeoutSeconds": 300 } } } }

YAML

--- Resources: TaggedInstancesAssociation: Type: AWS::SSM::Association Properties: AssociationName: UpdateSSMAgent Name: AWS-UpdateSSMAgent ScheduleExpression: rate(7 days) Targets: - Key: tag:Environment Values: - Production WaitForSuccessTimeoutSeconds: 300

创建将自动化文档与实例关联的关联

以下示例创建一个关联,该关联将 AWS-StopEC2Instance Automation 文档分配给特定实例。

注意

此示例指定以下 Amazon 资源名称 (ARN):arn:${AWS::Partition}:iam::aws:policy/AmazonEC2FullAccess。此策略提供的权限超出了停止实例所需的权限。我们建议您使用具有更严格权限的策略。

JSON

{ "Resources": { "AutomationExecutionRole": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ssm.amazonaws.com" }, "Action": [ "sts:AssumeRole" ] } ] }, "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/AmazonEC2FullAccess" ] } }, "AutomationAssociation": { "Type": "AWS::SSM::Association", "Properties": { "Name": "AWS-StopEC2Instance", "Parameters": { "AutomationAssumeRole": [ "AutomationExecutionRole.Arn" ] }, "Targets": [ { "Key": "ParameterValues", "Values": [ "i-1234567890abcdef0" ] } ], "AutomationTargetParameterName": "InstanceId" } } } }

YAML

--- Resources: AutomationExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ssm.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonEC2FullAccess AutomationAssociation: Type: AWS::SSM::Association Properties: Name: AWS-StopEC2Instance Parameters: AutomationAssumeRole: - !GetAtt AutomationExecutionRole.Arn Targets: - Key: ParameterValues Values: - i-1234567890abcdef0 AutomationTargetParameterName: InstanceId

创建一个使用费率控制并将日志输出发送到 Amazon S3 的关联

以下示例创建一个使用费率控制的关联。关联尝试一次仅在 20% 的实例上更新 SSM 代理。如果执行在占总数 5% 的实例上失败,Systems Manager 将停止在任何其他实例上运行关联。Systems Manager 还会将关联输出记录到 Amazon S3。

JSON

{ "Resources": { "RateControlAssociation": { "Type": "AWS::SSM::Association", "Properties": { "Name": "AWS-UpdateSSMAgent", "Targets": [ { "Key": "InstanceIds", "Values": [ "*" ] } ], "MaxConcurrency": "20%", "MaxErrors": "5%" }, "OutputLocation": { "S3Location": { "OutputS3BucketName": "MyAssociationOutputBucket", "OutputS3KeyPrefix": "my-agent-update-output" } }, "WaitForSuccessTimeoutSeconds": 300 } } }

YAML

--- Resources: RateControlAssociation: Type: AWS::SSM::Association Properties: Name: AWS-UpdateSSMAgent Targets: - Key: InstanceIds Values: - "*" MaxConcurrency: 20% MaxErrors: 5% OutputLocation: S3Location: OutputS3BucketName: MyAssociationOutputBucket OutputS3KeyPrefix: my-agent-update-output WaitForSuccessTimeoutSeconds: 300

创建用于 Ansible 的关联

以下示例创建一个使用 Ansible 和 Systems Manager 部署 Nginx 的关联。此模板从 Github 存储库中复制 Ansible 行动手册。目标基于实例 ID。

JSON

{ "Description": "Deploy Single EC2 Linux Instance", "Parameters": { "LatestAmiId": { "Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>", "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" }, "GitHubOwner": { "Type": "String" }, "GitHubRepo": { "Type": "String" }, "GitHubBranch": { "Type": "String" } }, "Resources": { "SSMAssocLogs": { "Type": "AWS::S3::Bucket" }, "SSMInstanceRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::aws-ssm-${AWS::Region}/*", "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*", "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*", "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*" ], "Effect": "Allow" } ] }, "PolicyName": "ssm-custom-s3-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" ], "Resource": [ "arn:${AWS::Partition}:s3:::${SSMAssocLogs}/*", "arn:${AWS::Partition}:s3:::${SSMAssocLogs}" ], "Effect": "Allow" } ] }, "PolicyName": "s3-instance-bucket-policy" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "Roles": [ "SSMInstanceRole" ] } }, "EC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "LatestAmiId", "InstanceType": "t3.small", "IamInstanceProfile": "SSMInstanceProfile" } }, "AnsibleAssociation": { "Type": "AWS::SSM::Association", "Properties": { "Name": "AWS-ApplyAnsiblePlaybooks", "WaitForSuccessTimeoutSeconds": 300, "Targets": [ { "Key": "InstanceIds", "Values": [ "EC2Instance" ] } ], "OutputLocation": { "S3Location": { "OutputS3BucketName": "SSMAssocLogs", "OutputS3KeyPrefix": "logs/" } }, "Parameters": { "SourceType": [ "GitHub" ], "SourceInfo": [ "{\"owner\":\"${GitHubOwner}\",\n\"repository\":\"${GitHubRepo}\",\n\"path\":\"\",\n\"getOptions\":\"branch:${GitHubBranch}\"}\n" ], "InstallDependencies": [ "True" ], "PlaybookFile": [ "playbook.yml" ], "ExtraVariables": [ "SSM=True" ], "Check": [ "False" ], "Verbose": [ "-v" ] } } } }, "Outputs": { "WebServerPublic": { "Value": "EC2Instance.PublicDnsName", "Description": "Public DNS for WebServer" } } }

YAML

--- Description: "Deploy Single EC2 Linux Instance" Parameters: # Using SSM Parameter Store to fetch the Latest AMI for Amazon Linux, eliminates the need for AMI Mappings LatestAmiId: Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" GitHubOwner: Type: 'String' GitHubRepo: Type: 'String' GitHubBranch: Type: 'String' Resources: SSMAssocLogs: Type: AWS::S3::Bucket SSMInstanceRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:aws:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:PutObject - s3:PutObjectAcl - s3:ListBucket Resource: - !Sub 'arn:${AWS::Partition}:s3:::${SSMAssocLogs}/*' - !Sub 'arn:${AWS::Partition}:s3:::${SSMAssocLogs}' Effect: Allow PolicyName: s3-instance-bucket-policy Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref SSMInstanceRole EC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: !Ref LatestAmiId InstanceType: "t3.small" IamInstanceProfile: !Ref SSMInstanceProfile AnsibleAssociation: Type: AWS::SSM::Association Properties: # Here using the AWS-ApplyAnsiblePlaybooks Name: AWS-ApplyAnsiblePlaybooks WaitForSuccessTimeoutSeconds: 300 # Targeting Instance by InstanceId passed from the Logical ID of Instance being created # in CloudFormation Targets: - Key: InstanceIds Values: [ !Ref EC2Instance ] OutputLocation: S3Location: OutputS3BucketName: !Ref SSMAssocLogs OutputS3KeyPrefix: 'logs/' Parameters: # Getting an Ansible Playbook from a GitHub Location SourceType: - 'GitHub' # At a minimum must include the following GitHub repo information, if using a private repo # would want to include the GitHub Token option SourceInfo: - !Sub | {"owner":"${GitHubOwner}", "repository":"${GitHubRepo}", "path":"", "getOptions":"branch:${GitHubBranch}"} # Installing Ansible and its dependencies InstallDependencies: - 'True' # Playbook file we want to run PlaybookFile: - 'playbook.yml' ExtraVariables: - 'SSM=True' Check: - 'False' Verbose: - '-v' Outputs: WebServerPublic: Value: !GetAtt 'EC2Instance.PublicDnsName' Description: Public DNS for WebServer

创建运行 bash 脚本的关联

以下示例创建一个运行 bash 脚本的关联。目标基于标签。

JSON

{ "Description": "Deploy Single EC2 Linux Instance Install and Install Nginx by a State Manager Association", "Parameters": { "LatestAmiId": { "Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>", "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" } }, "Resources": { "SSMAssocLogs": { "Type": "AWS::S3::Bucket" }, "SSMInstanceRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::aws-ssm-${AWS::Region}/*", "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*", "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*", "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*" ], "Effect": "Allow" } ] }, "PolicyName": "ssm-custom-s3-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" ], "Resource": [ "arn:${AWS::Partition}:s3:::${SSMAssocLogs}/*", "arn:${AWS::Partition}:s3:::${SSMAssocLogs}" ], "Effect": "Allow" } ] }, "PolicyName": "s3-instance-bucket-policy" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore", "arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "Roles": [ "SSMInstanceRole" ] } }, "EC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "LatestAmiId", "InstanceType": "t3.medium", "IamInstanceProfile": "SSMInstanceProfile", "Tags": [ { "Key": "nginx", "Value": "yes" } ] } }, "NginxAssociation": { "DependsOn": "EC2Instance", "Type": "AWS::SSM::Association", "Properties": { "Name": "AWS-RunShellScript", "WaitForSuccessTimeoutSeconds": 300, "Targets": [ { "Key": "tag:nginx", "Values": [ "yes" ] } ], "OutputLocation": { "S3Location": { "OutputS3BucketName": "SSMAssocLogs", "OutputS3KeyPrefix": "logs/" } }, "Parameters": { "commands": [ "sudo amazon-linux-extras install nginx1 -y\nsudo service nginx start\n" ] } } } }, "Outputs": { "WebServerPublic": { "Value": "EC2Instance.PublicDnsName", "Description": "Public DNS for WebServer" } } }

YAML

--- Description: "Deploy Single EC2 Linux Instance Install and Install Nginx by a State Manager Association" Parameters: LatestAmiId: Type: 'AWS::SSM::Parameter::Value*<AWS::EC2::Image::Id>' Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" Resources: SSMAssocLogs: Type: AWS::S3::Bucket # Role that allows SSM Agent to communicate with SSM and allows use of all features of SSM SSMInstanceRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:aws:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:PutObject - s3:PutObjectAcl - s3:ListBucket Resource: - !Sub 'arn:${AWS::Partition}:s3:::${SSMAssocLogs}/*' - !Sub 'arn:${AWS::Partition}:s3:::${SSMAssocLogs}' Effect: Allow PolicyName: s3-instance-bucket-policy Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref SSMInstanceRole EC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: !Ref LatestAmiId InstanceType: "t3.medium" IamInstanceProfile: !Ref SSMInstanceProfile Tags: - Key: 'nginx' Value: 'yes' NginxAssociation: DependsOn: EC2Instance # CloudFormation Resource Type that creates State Manager Associations Type: AWS::SSM::Association Properties: # Command Document that this Association will run Name: AWS-RunShellScript WaitForSuccessTimeoutSeconds: 300 # Targeting Instance by Tags Targets: - Key: tag:nginx Values: - 'yes' # The passing in the S3 Bucket that is created in the template that logs will be sent to OutputLocation: S3Location: OutputS3BucketName: !Ref SSMAssocLogs OutputS3KeyPrefix: 'logs/' # Parameters for the AWS-RunShellScript, in this case commands to install nginx Parameters: commands: - | sudo amazon-linux-extras install nginx1 -y sudo service nginx start Outputs: WebServerPublic: Value: !GetAtt 'EC2Instance.PublicDnsName' Description: Public DNS for WebServer

创建通过 Systems Manager Automation 运行 bash 脚本的关联

在下面的示例中,创建了一个使用 State Manager 和 Automation 通过多个步骤运行 bash 脚本的关联。目标基于标签。

JSON

{ "Description": "Deploy Single EC2 Linux Instance", "Parameters": { "LatestAmiId": { "Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>", "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" } }, "Resources": { "SSMAssocLogs": { "Type": "AWS::S3::Bucket" }, "nginxInstallAutomation": { "Type": "AWS::SSM::Document", "Properties": { "DocumentType": "Automation", "Content": { "schemaVersion": "0.3", "description": "Updates AMI with Linux distribution packages and installs Nginx software", "assumeRole": "{{AutomationAssumeRole}}", "parameters": { "InstanceId": { "description": "ID of the Instance.", "type": "String" }, "AutomationAssumeRole": { "default": "", "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", "type": "String" } }, "mainSteps": [ { "name": "updateOSSoftware", "action": "aws:runCommand", "maxAttempts": 3, "timeoutSeconds": 3600, "inputs": { "DocumentName": "AWS-RunShellScript", "InstanceIds": [ "{{InstanceId}}" ], "CloudWatchOutputConfig": { "CloudWatchOutputEnabled": "true" }, "Parameters": { "commands": [ "#!/bin/bash\nsudo yum update -y\nneeds-restarting -r\nif [ $? -eq 1 ]\nthen\n exit 194\nelse\n exit 0\nfi\n" ] } } }, { "name": "InstallNginx", "action": "aws:runCommand", "inputs": { "DocumentName": "AWS-RunShellScript", "InstanceIds": [ "{{InstanceId}}" ], "CloudWatchOutputConfig": { "CloudWatchOutputEnabled": "true" }, "Parameters": { "commands": [ "sudo amazon-linux-extras install nginx1 -y\nsudo service nginx start\n" ] } } }, { "name": "TestInstall", "action": "aws:runCommand", "maxAttempts": 3, "timeoutSeconds": 3600, "onFailure": "Abort", "inputs": { "DocumentName": "AWS-RunShellScript", "InstanceIds": [ "{{InstanceId}}" ], "Parameters": { "commands": [ "curl localhost\n" ] } } } ] } } }, "SSMExecutionRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "ssm:StartAssociationsOnce", "ssm:CreateAssociation", "ssm:CreateAssociationBatch", "ssm:UpdateAssociation" ], "Resource": "*", "Effect": "Allow" } ] }, "PolicyName": "ssm-association" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::aws-ssm-${AWS::Region}/*", "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*", "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*", "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*" ], "Effect": "Allow" } ] }, "PolicyName": "ssm-custom-s3-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" ], "Resource": [ "arn:${AWS::Partition}:s3:::${SSMAssocLogs}/*", "arn:${AWS::Partition}:s3:::${SSMAssocLogs}" ], "Effect": "Allow" } ] }, "PolicyName": "s3-instance-bucket-policy" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore", "arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "Roles": [ "SSMInstanceRole" ] } }, "EC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "LatestAmiId", "InstanceType": "t3.medium", "IamInstanceProfile": "SSMInstanceProfile", "Tags": [ { "Key": "nginx", "Value": true } ] } }, "NginxAssociation": { "DependsOn": "EC2Instance", "Type": "AWS::SSM::Association", "Properties": { "Name": "nginxInstallAutomation", "WaitForSuccessTimeoutSeconds": 300, "OutputLocation": { "S3Location": { "OutputS3BucketName": "SSMAssocLogs", "OutputS3KeyPrefix": "logs/" } }, "AutomationTargetParameterName": "InstanceId", "Parameters": { "AutomationAssumeRole": [ "SSMExecutionRole.Arn" ] }, "Targets": [ { "Key": "tag:nginx", "Values": [ true ] } ] } } }, "Outputs": { "WebServerPublic": { "Value": "EC2Instance.PublicDnsName", "Description": "Public DNS for WebServer" } } }

YAML

--- Description: "Deploy Single EC2 Linux Instance" Parameters: LatestAmiId: Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" Resources: SSMAssocLogs: Type: AWS::S3::Bucket nginxInstallAutomation: Type: AWS::SSM::Document Properties: DocumentType: Automation Content: schemaVersion: "0.3" description: "Updates AMI with Linux distribution packages and installs Nginx software" assumeRole: "{{AutomationAssumeRole}}" parameters: InstanceId: description: "ID of the Instance." type: "String" AutomationAssumeRole: default: "" description: "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf." type: "String" mainSteps: - name: "updateOSSoftware" action: "aws:runCommand" maxAttempts: 3 timeoutSeconds: 3600 inputs: DocumentName: "AWS-RunShellScript" InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" Parameters: commands: - | #!/bin/bash sudo yum update -y needs-restarting -r if [ $? -eq 1 ] then exit 194 else exit 0 fi - name: "InstallNginx" action: "aws:runCommand" inputs: DocumentName: "AWS-RunShellScript" InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" Parameters: commands: - | sudo amazon-linux-extras install nginx1 -y sudo service nginx start - name: "TestInstall" action: "aws:runCommand" maxAttempts: 3 timeoutSeconds: 3600 onFailure: "Abort" inputs: DocumentName: "AWS-RunShellScript" InstanceIds: - "{{InstanceId}}" Parameters: commands: - | curl localhost SSMExecutionRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - ssm:StartAssociationsOnce - ssm:CreateAssociation - ssm:CreateAssociationBatch - ssm:UpdateAssociation Resource: '*' Effect: Allow PolicyName: ssm-association Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:aws:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:PutObject - s3:PutObjectAcl - s3:ListBucket Resource: - !Sub 'arn:${AWS::Partition}:s3:::${SSMAssocLogs}/*' - !Sub 'arn:${AWS::Partition}:s3:::${SSMAssocLogs}' Effect: Allow PolicyName: s3-instance-bucket-policy Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref SSMInstanceRole EC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: !Ref LatestAmiId InstanceType: "t3.medium" IamInstanceProfile: !Ref SSMInstanceProfile Tags: - Key: nginx Value: Yes NginxAssociation: DependsOn: EC2Instance Type: AWS::SSM::Association Properties: Name: !Ref nginxInstallAutomation WaitForSuccessTimeoutSeconds: 300 OutputLocation: S3Location: OutputS3BucketName: !Ref SSMAssocLogs OutputS3KeyPrefix: 'logs/' AutomationTargetParameterName: InstanceId Parameters: AutomationAssumeRole: - !GetAtt 'SSMExecutionRole.Arn' Targets: - Key: tag:nginx Values: - Yes Outputs: WebServerPublic: Value: !GetAtt 'EC2Instance.PublicDnsName' Description: Public DNS for WebServer

创建将目标加入到 Windows Active Directory 域的关联

以下示例创建使用 State Manager 将目标加入到 Windows Active Directory 域的关联。目标基于标签。

JSON

{ "Description": "Deploy single windows EC2 Instance and join domain with SSM Association", "Parameters": { "DomainAdminPassword": { "AllowedPattern": "(?=^.{6,255}$)((?=.*\\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*", "Description": "Password for the domain admin user. Must be at least 8 characters, containing letters, numbers, and symbols.", "MaxLength": "32", "MinLength": "8", "NoEcho": "true", "Type": "String" }, "DomainAdminUser": { "AllowedPattern": "[a-zA-Z0-9]*", "Default": "Admin", "Description": "User name for the account that will be used as domain administrator. This is separate from the default \"Administrator\" account.", "MaxLength": "25", "MinLength": "5", "Type": "String" }, "DomainDNSName": { "AllowedPattern": "[a-zA-Z0-9\\-]+\\..+", "Default": "example.com", "Description": "Fully qualified domain name (FQDN).", "MaxLength": "255", "MinLength": "2", "Type": "String" }, "DomainMemberSGID": { "Description": "ID of the domain member security group (e.g., sg-7f16e910).", "Type": "AWS::EC2::SecurityGroup::Id" }, "DomainNetBIOSName": { "AllowedPattern": "[a-zA-Z0-9\\-]+", "Default": "EXAMPLE", "Description": "NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows.", "MaxLength": "15", "MinLength": "1", "Type": "String" }, "EC2InstanceType": { "AllowedValues": [ "t3.nano", "t3.micro", "t3.small", "t3.medium", "t3.large", "t3.xlarge", "t3.2xlarge", "m5.large", "m5.xlarge", "m5.2xlarge" ], "Default": "m5.large", "Description": "Amazon EC2 instance type", "Type": "String" }, "LatestAmiId": { "Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>", "Default": "/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base" }, "SubnetID": { "Description": "ID of a Subnet.", "Type": "AWS::EC2::Subnet::Id" } }, "Resources": { "DSCBucket": { "Type": "AWS::S3::Bucket", "Properties": { "LifecycleConfiguration": { "Rules": [ { "Id": "DeleteAfter30Days", "ExpirationInDays": 30, "Status": "Enabled", "Prefix": "logs/" } ] } } }, "DomainJoinSecrets": { "Type": "AWS::SecretsManager::Secret", "Properties": { "Name": "DomainJoinSecrets-${AWS::StackName}", "Description": "Secrets to join AD domain", "SecretString": "{\"username\":\"${DomainNetBIOSName}\\\\${DomainAdminUser}\",\"password\":\"${DomainAdminPassword}\"}" } }, "LambdaSSMRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": [ "${DSCBucket.Arn}", "${DSCBucket.Arn}/*" ] } ] }, "PolicyName": "write-mof-s3" } ], "Path": "/", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }, "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] } }, "WriteMOFFunction": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "ZipFile": "var AWS = require('aws-sdk'), s3 = new AWS.S3(); const response = require(\"cfn-response\"); exports.handler = async (event, context) => {\n console.log(JSON.stringify(event));\n if (event.RequestType === 'Delete') {\n await postResponse(event, context, response.SUCCESS, {})\n return;\n }\n function postResponse(event, context, status, data){\n return new Promise((resolve, reject) => {\n setTimeout(() => response.send(event, context, status, data), 5000)\n });\n }\n await s3.putObject({\n Body: event.ResourceProperties.Body,\n Bucket: event.ResourceProperties.Bucket,\n Key: event.ResourceProperties.Key\n }).promise();\n await postResponse(event, context, response.SUCCESS, {});\n};\n" }, "Handler": "index.handler", "Role": "LambdaSSMRole.Arn", "Runtime": "nodejs10.x", "Timeout": 10 } }, "WriteDomainJoinMOF": { "Type": "Custom::WriteMOFFile", "Properties": { "ServiceToken": "WriteMOFFunction.Arn", "Bucket": "DSCBucket", "Key": "DomainJoin-${AWS::StackName}.mof", "Body": "/*\n@TargetNode='localhost'\n*/\ninstance of MSFT_Credential as $MSFT_Credential1ref\n{\nPassword = \"managementgovernancesample\";\n UserName = \"${DomainJoinSecrets}\";\n\n};\ninstance of DSC_Computer as $DSC_Computer1ref\n{\nResourceID = \"[Computer]JoinDomain\";\n Credential = $MSFT_Credential1ref;\n DomainName = \"{tag:DomainToJoin}\";\n Name = \"{tag:Name}\";\n ModuleName = \"ComputerManagementDsc\";\n ModuleVersion = \"8.0.0\";\n ConfigurationName = \"DomainJoin\";\n};\ninstance of OMI_ConfigurationDocument\n {\n Version=\"2.0.0\";\n MinimumCompatibleVersion = \"1.0.0\";\n CompatibleVersionAdditionalProperties= {\"Omi_BaseResource:ConfigurationName\"};\n Name=\"DomainJoin\";\n }; \n" } }, "SSMInstanceRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::aws-ssm-${AWS::Region}/*", "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*", "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*", "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*" ], "Effect": "Allow" } ] }, "PolicyName": "ssm-custom-s3-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret" ], "Resource": [ "DomainJoinSecrets" ] } ] }, "PolicyName": "ssm-secrets-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" ], "Resource": [ "arn:${AWS::Partition}:s3:::${DSCBucket}/*", "arn:${AWS::Partition}:s3:::${DSCBucket}" ], "Effect": "Allow" } ] }, "PolicyName": "s3-instance-bucket-policy" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore", "arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ReadOnlyAccess" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "Roles": [ "SSMInstanceRole" ] } }, "WINEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "LatestAmiId", "InstanceType": "EC2InstanceType", "IamInstanceProfile": "SSMInstanceProfile", "NetworkInterfaces": [ { "DeleteOnTermination": true, "DeviceIndex": "0", "SubnetId": "SubnetID", "GroupSet": [ "DomainMemberSGID" ] } ], "Tags": [ { "Key": "Name", "Value": "WindowsBox0" }, { "Key": "DomainToJoin", "Value": "DomainDNSName" } ] } }, "JoinDomainAssociation": { "DependsOn": [ "WINEC2Instance", "WriteDomainJoinMOF" ], "Type": "AWS::SSM::Association", "Properties": { "WaitForSuccessTimeoutSeconds": 300, "Name": "AWS-ApplyDSCMofs", "Targets": [ { "Key": "tag:DomainToJoin", "Values": [ "DomainDNSName" ] } ], "OutputLocation": { "S3Location": { "OutputS3BucketName": "DSCBucket", "OutputS3KeyPrefix": "logs/" } }, "ScheduleExpression": "cron(30 23 * * ? *)", "MaxErrors": 1, "MaxConcurrency": 1, "Parameters": { "MofsToApply": [ "s3:${DSCBucket}:DomainJoin-${AWS::StackName}.mof" ], "ServicePath": [ "default" ], "MofOperationMode": [ "Apply" ], "ComplianceType": [ "Custom:DomainJoinSample" ], "ModuleSourceBucketName": [ "NONE" ], "AllowPSGalleryModuleSource": [ "True" ], "RebootBehavior": [ "AfterMof" ], "UseComputerNameForReporting": [ "False" ], "EnableVerboseLogging": [ "False" ], "EnableDebugLogging": [ "False" ] } } } } }

YAML

--- Description: "Deploy single windows EC2 Instance and join domain with SSM Association" Parameters: DomainAdminPassword: AllowedPattern: (?=^.{6,255}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.* Description: Password for the domain admin user. Must be at least 8 characters, containing letters, numbers, and symbols. MaxLength: '32' MinLength: '8' NoEcho: 'true' Type: String DomainAdminUser: AllowedPattern: '[a-zA-Z0-9]*' Default: Admin Description: User name for the account that will be used as domain administrator. This is separate from the default "Administrator" account. MaxLength: '25' MinLength: '5' Type: String DomainDNSName: AllowedPattern: '[a-zA-Z0-9\-]+\..+' Default: example.com Description: Fully qualified domain name (FQDN). MaxLength: '255' MinLength: '2' Type: String DomainMemberSGID: Description: ID of the domain member security group (e.g., sg-7f16e910). Type: AWS::EC2::SecurityGroup::Id DomainNetBIOSName: AllowedPattern: '[a-zA-Z0-9\-]+' Default: EXAMPLE Description: NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows. MaxLength: '15' MinLength: '1' Type: String EC2InstanceType: AllowedValues: - t3.nano - t3.micro - t3.small - t3.medium - t3.large - t3.xlarge - t3.2xlarge - m5.large - m5.xlarge - m5.2xlarge Default: m5.large Description: Amazon EC2 instance type Type: String LatestAmiId: Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: "/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base" SubnetID: Description: ID of a Subnet. Type: AWS::EC2::Subnet::Id Resources: DSCBucket: Type: AWS::S3::Bucket Properties: LifecycleConfiguration: Rules: - Id: DeleteAfter30Days ExpirationInDays: 30 Status: Enabled Prefix: 'logs/' DomainJoinSecrets: Type: AWS::SecretsManager::Secret Properties: Name: !Sub 'DomainJoinSecrets-${AWS::StackName}' Description: Secrets to join AD domain SecretString: !Sub '{"username":"${DomainNetBIOSName}\\${DomainAdminUser}","password":"${DomainAdminPassword}"}' LambdaSSMRole: Type: AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:PutObject Resource: - !Sub "${DSCBucket.Arn}" - !Sub "${DSCBucket.Arn}/*" PolicyName: write-mof-s3 Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' WriteMOFFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: > var AWS = require('aws-sdk'), s3 = new AWS.S3(); const response = require("cfn-response"); exports.handler = async (event, context) => { console.log(JSON.stringify(event)); if (event.RequestType === 'Delete') { await postResponse(event, context, response.SUCCESS, {}) return; } function postResponse(event, context, status, data){ return new Promise((resolve, reject) => { setTimeout(() => response.send(event, context, status, data), 5000) }); } await s3.putObject({ Body: event.ResourceProperties.Body, Bucket: event.ResourceProperties.Bucket, Key: event.ResourceProperties.Key }).promise(); await postResponse(event, context, response.SUCCESS, {}); }; Handler: index.handler Role: !GetAtt LambdaSSMRole.Arn Runtime: nodejs10.x Timeout: 10 WriteDomainJoinMOF: Type: Custom::WriteMOFFile Properties: ServiceToken: !GetAtt WriteMOFFunction.Arn Bucket: !Ref DSCBucket Key: !Sub "DomainJoin-${AWS::StackName}.mof" Body: !Sub | /* @TargetNode='localhost' */ instance of MSFT_Credential as $MSFT_Credential1ref { Password = "managementgovernancesample"; UserName = "${DomainJoinSecrets}"; }; instance of DSC_Computer as $DSC_Computer1ref { ResourceID = "[Computer]JoinDomain"; Credential = $MSFT_Credential1ref; DomainName = "{tag:DomainToJoin}"; Name = "{tag:Name}"; ModuleName = "ComputerManagementDsc"; ModuleVersion = "8.0.0"; ConfigurationName = "DomainJoin"; }; instance of OMI_ConfigurationDocument { Version="2.0.0"; MinimumCompatibleVersion = "1.0.0"; CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"}; Name="DomainJoin"; }; SSMInstanceRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:aws:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue - secretsmanager:DescribeSecret Resource: - !Ref 'DomainJoinSecrets' PolicyName: ssm-secrets-policy - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:PutObject - s3:PutObjectAcl - s3:ListBucket Resource: - !Sub 'arn:${AWS::Partition}:s3:::${DSCBucket}/*' - !Sub 'arn:${AWS::Partition}:s3:::${DSCBucket}' Effect: Allow PolicyName: s3-instance-bucket-policy Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ReadOnlyAccess' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref SSMInstanceRole WINEC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: !Ref LatestAmiId InstanceType: !Ref EC2InstanceType IamInstanceProfile: !Ref SSMInstanceProfile NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: '0' SubnetId: !Ref 'SubnetID' GroupSet: - !Ref DomainMemberSGID Tags: - Key: "Name" Value: "WindowsBox0" - Key: "DomainToJoin" Value: !Ref "DomainDNSName" JoinDomainAssociation: DependsOn: - WINEC2Instance - WriteDomainJoinMOF Type: AWS::SSM::Association Properties: WaitForSuccessTimeoutSeconds: 300 Name: AWS-ApplyDSCMofs Targets: - Key: "tag:DomainToJoin" Values: - !Ref "DomainDNSName" OutputLocation: S3Location: OutputS3BucketName: !Ref DSCBucket OutputS3KeyPrefix: 'logs/' ScheduleExpression: "cron(30 23 * * ? *)" MaxErrors: 1 MaxConcurrency: 1 Parameters: MofsToApply: - !Sub "s3:${DSCBucket}:DomainJoin-${AWS::StackName}.mof" ServicePath: - default MofOperationMode: - Apply ComplianceType: - Custom:DomainJoinSample ModuleSourceBucketName: - "NONE" AllowPSGalleryModuleSource: - "True" RebootBehavior: - "AfterMof" UseComputerNameForReporting: - "False" EnableVerboseLogging: - "False" EnableDebugLogging: - "False"

创建将目标加入到 Windows Active Directory 域并使用 Systems Manager Automation 的关联

以下示例创建使用 State Manager 和 Automation 将目标加入到 Windows Active Directory 域的关联。目标基于标签。

JSON

{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "Deploy single windows EC2 Instance and join domain with SSM Association", "Parameters": { "DomainAdminPassword": { "AllowedPattern": "(?=^.{6,255}$)((?=.*\\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*", "Description": "Password for the domain admin user. Must be at least 8 characters, containing letters, numbers, and symbols.", "MaxLength": "32", "MinLength": "8", "NoEcho": "true", "Type": "String" }, "DomainAdminUser": { "AllowedPattern": "[a-zA-Z0-9]*", "Default": "Admin", "Description": "User name for the account that will be used as domain administrator. This is separate from the default \"Administrator\" account.", "MaxLength": "25", "MinLength": "5", "Type": "String" }, "DomainDNSName": { "AllowedPattern": "[a-zA-Z0-9\\-]+\\..+", "Default": "example.com", "Description": "Fully qualified domain name (FQDN).", "MaxLength": "255", "MinLength": "2", "Type": "String" }, "DomainMemberSGID": { "Description": "ID of the domain member security group (e.g., sg-7f16e910).", "Type": "AWS::EC2::SecurityGroup::Id" }, "DomainNetBIOSName": { "AllowedPattern": "[a-zA-Z0-9\\-]+", "Default": "EXAMPLE", "Description": "NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows.", "MaxLength": "15", "MinLength": "1", "Type": "String" }, "EC2InstanceType": { "AllowedValues": [ "t3.nano", "t3.micro", "t3.small", "t3.medium", "t3.large", "t3.xlarge", "t3.2xlarge", "m5.large", "m5.xlarge", "m5.2xlarge" ], "Default": "m5.large", "Description": "Amazon EC2 instance type", "Type": "String" }, "LatestAmiId": { "Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>", "Default": "/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base" }, "SubnetID": { "Description": "ID of a Subnet.", "Type": "AWS::EC2::Subnet::Id" } }, "Resources": { "DSCBucket": { "Type": "AWS::S3::Bucket", "Properties": { "LifecycleConfiguration": { "Rules": [ { "Id": "DeleteAfter30Days", "ExpirationInDays": 30, "Status": "Enabled", "Prefix": "logs/" } ] } } }, "DomainJoinSecrets": { "Type": "AWS::SecretsManager::Secret", "Properties": { "Name": "DomainJoinSecrets-${AWS::StackName}", "Description": "Secrets to join AD domain", "SecretString": "{\"username\":\"${DomainAdminUser}\",\"password\":\"${DomainAdminPassword}\"}" } }, "LambdaSSMRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": [ "${DSCBucket.Arn}", "${DSCBucket.Arn}/*" ] } ] }, "PolicyName": "write-mof-s3" } ], "Path": "/", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }, "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] } }, "WriteScriptFunction": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "ZipFile": "var AWS = require('aws-sdk'), s3 = new AWS.S3(); const response = require(\"cfn-response\"); exports.handler = async (event, context) => {\n console.log(JSON.stringify(event));\n if (event.RequestType === 'Delete') {\n await postResponse(event, context, response.SUCCESS, {})\n return;\n }\n function postResponse(event, context, status, data){\n return new Promise((resolve, reject) => {\n setTimeout(() => response.send(event, context, status, data), 5000)\n });\n }\n await s3.putObject({\n Body: event.ResourceProperties.Body,\n Bucket: event.ResourceProperties.Bucket,\n Key: event.ResourceProperties.Key\n }).promise();\n await postResponse(event, context, response.SUCCESS, {});\n};\n" }, "Handler": "index.handler", "Role": "LambdaSSMRole.Arn", "Runtime": "nodejs10.x", "Timeout": 10 } }, "WriteDomainJoinScript": { "Type": "Custom::WriteScript", "Properties": { "ServiceToken": "WriteScriptFunction.Arn", "Bucket": "DSCBucket", "Key": "DomainJoin.ps1", "Body": "[CmdletBinding()]\n# Incoming Parameters for Script, CloudFormation\\SSM Parameters being passed in\nparam(\n [Parameter(Mandatory=$true)]\n [string]$DomainNetBIOSName,\n\n [Parameter(Mandatory=$true)]\n [string]$DomainDNSName,\n\n [Parameter(Mandatory=$true)]\n [string]$AdminSecret\n)\n\n# Formatting AD Admin User to proper format for JoinDomain DSC Resources in this Script\n$DomainAdmin = 'Domain\\User' -replace 'Domain',$DomainNetBIOSName -replace 'User',$UserName\n$Admin = ConvertFrom-Json -InputObject (Get-SECSecretValue -SecretId $AdminSecret).SecretString\n$AdminUser = $DomainNetBIOSName + '\\' + $Admin.UserName\n# Creating Credential Object for Administrator\n$Credentials = (New-Object PSCredential($AdminUser,(ConvertTo-SecureString $Admin.Password -AsPlainText -Force)))\n# Getting the DSC Cert Encryption Thumbprint to Secure the MOF File\n$DscCertThumbprint = (get-childitem -path cert:\\LocalMachine\\My | where { $_.subject -eq \"CN=SampleDscEncryptCert\" }).Thumbprint\n# Getting the Name Tag of the Instance\n$NameTag = (Get-EC2Tag -Filter @{ Name=\"resource-id\";Values=(Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id)}| Where-Object { $_.Key -eq \"Name\" })\n$NewName = $NameTag.Value\n\n# Creating Configuration Data Block that has the Certificate Information for DSC Configuration Processing\n$ConfigurationData = @{\n AllNodes = @(\n @{\n NodeName=\"*\"\n CertificateFile = \"C:\\awssample\\publickeys\\SamplePublicKey.cer\"\n Thumbprint = $DscCertThumbprint\n PSDscAllowDomainUser = $true\n },\n @{\n NodeName = 'localhost'\n }\n )\n}\n\nConfiguration DomainJoin {\n param(\n [PSCredential] $Credentials\n )\n\n Import-Module -Name PSDesiredStateConfiguration\n Import-Module -Name ComputerManagementDsc\n \n Import-DscResource -Module PSDesiredStateConfiguration\n Import-DscResource -Module ComputerManagementDsc\n\n Node 'localhost' {\n\n Computer JoinDomain {\n Name = $NewName\n DomainName = $DomainDNSName\n Credential = $Credentials\n }\n }\n}\n\nDomainJoin -OutputPath 'C:\\awssample\\DomainJoin' -ConfigurationData $ConfigurationData -Credentials $Credentials\n" } }, "WriteInstallModuleScript": { "Type": "Custom::WriteScript", "Properties": { "ServiceToken": "WriteScriptFunction.Arn", "Bucket": "DSCBucket", "Key": "install-modules.ps1", "Body": "[CmdletBinding()]\nparam()\n\n\"Setting up Powershell Gallery to Install DSC Modules\"\nInstall-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force\nSet-PSRepository -Name PSGallery -InstallationPolicy Trusted\n\n\"Installing the needed Powershell DSC modules for this Quick Start\"\nInstall-Module -Name ComputerManagementDsc\nInstall-Module -Name PSDscResources\n\n\"Creating Directory for DSC Public Cert\"\nNew-Item -Path C:\\awssample\\publickeys -ItemType directory -Force\n\n\"Setting up DSC Certificate to Encrypt Credentials in MOF File\"\n$cert = New-SelfSignedCertificate -Type DocumentEncryptionCertLegacyCsp -DnsName 'SampleDscEncryptCert' -HashAlgorithm SHA256\n# Exporting the public key certificate\n$cert | Export-Certificate -FilePath \"C:\\awssample\\publickeys\\SamplePublicKey.cer\" -Force\n" } }, "WriteLCMConfigScript": { "Type": "Custom::WriteScript", "Properties": { "ServiceToken": "WriteScriptFunction.Arn", "Bucket": "DSCBucket", "Key": "LCM-Config.ps1", "Body": "# This block sets the LCM configuration to what we need for QS\n[DSCLocalConfigurationManager()]\nconfiguration LCMConfig\n{\n Node 'localhost' {\n Settings {\n RefreshMode = 'Push'\n ActionAfterReboot = 'StopConfiguration' \n RebootNodeIfNeeded = $false\n CertificateId = $DscCertThumbprint \n }\n }\n}\n\n$DscCertThumbprint = [string](get-childitem -path cert:\\LocalMachine\\My | where { $_.subject -eq \"CN=SampleDscEncryptCert\" }).Thumbprint\n \n#Generates MOF File for LCM\nLCMConfig -OutputPath 'C:\\awssample\\LCMConfig'\n \n# Sets LCM Configuration to MOF generated in previous command\nSet-DscLocalConfigurationManager -Path 'C:\\awssample\\LCMConfig' \n" } }, "DomainJoinAutomation": { "Type": "AWS::SSM::Document", "Properties": { "DocumentType": "Automation", "Content": { "schemaVersion": "0.3", "description": "Join a Windows Domain", "assumeRole": "{{AutomationAssumeRole}}", "parameters": { "InstanceId": { "description": "ID of the Instance.", "type": "StringList" }, "DomainDNSName": { "default": "example.com", "description": "Fully qualified domain name (FQDN) of the forest root domain e.g. example.com", "type": "String" }, "DomainNetBIOSName": { "default": "example", "description": "NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows e.g. EXAMPLE", "type": "String" }, "AdminSecrets": { "description": "AWS Secrets Parameter Name that has Password and User name for a domain administrator.", "type": "String" }, "S3BucketName": { "description": "S3 bucket name for the Quick Start assets. Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-).", "type": "String" }, "AutomationAssumeRole": { "default": "", "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", "type": "String" } }, "mainSteps": [ { "name": "InstallDSCModules", "action": "aws:runCommand", "inputs": { "DocumentName": "AWS-RunRemoteScript", "InstanceIds": [ "{{InstanceId}}" ], "CloudWatchOutputConfig": { "CloudWatchOutputEnabled": "true", "CloudWatchLogGroupName": "/ssm/${AWS::StackName}" }, "Parameters": { "sourceType": "S3", "sourceInfo": "{\"path\": \"https://{{S3BucketName}}.s3.amazonaws.com/install-modules.ps1\"}", "commandLine": "./install-modules.ps1" } } }, { "name": "ConfigureLCM", "action": "aws:runCommand", "inputs": { "DocumentName": "AWS-RunRemoteScript", "InstanceIds": [ "{{InstanceId}}" ], "CloudWatchOutputConfig": { "CloudWatchOutputEnabled": "true", "CloudWatchLogGroupName": "/ssm/${AWS::StackName}" }, "Parameters": { "sourceType": "S3", "sourceInfo": "{\"path\": \"https://{{S3BucketName}}.s3.amazonaws.com/LCM-Config.ps1\"}", "commandLine": "./LCM-Config.ps1" } } }, { "name": "GenerateDomainJoinMof", "action": "aws:runCommand", "inputs": { "DocumentName": "AWS-RunRemoteScript", "InstanceIds": [ "{{InstanceId}}" ], "CloudWatchOutputConfig": { "CloudWatchOutputEnabled": "true", "CloudWatchLogGroupName": "/ssm/${AWS::StackName}" }, "Parameters": { "sourceType": "S3", "sourceInfo": "{\"path\": \"https://{{S3BucketName}}.s3.amazonaws.com/DomainJoin.ps1\"}", "commandLine": "./DomainJoin.ps1 -DomainNetBIOSName {{DomainNetBIOSName}} -DomainDNSName {{DomainDNSName}} -AdminSecret {{AdminSecrets}}" } } }, { "name": "DomainJoin", "action": "aws:runCommand", "inputs": { "DocumentName": "AWS-RunPowerShellScript", "InstanceIds": [ "{{InstanceId}}" ], "CloudWatchOutputConfig": { "CloudWatchOutputEnabled": "true", "CloudWatchLogGroupName": "/ssm/${AWS::StackName}" }, "Parameters": { "commands": [ "function DscStatusCheck () {\n $LCMState = (Get-DscLocalConfigurationManager).LCMState\n if ($LCMState -eq 'PendingConfiguration' -Or $LCMState -eq 'PendingReboot') {\n 'returning 3010, should continue after reboot'\n exit 3010\n } else {\n 'Completed'\n }\n}\n\nStart-DscConfiguration 'C:\\awssample\\DomainJoin' -Wait -Verbose -Force\n\nDscStatusCheck\n" ] } } } ] } } }, "SSMExecutionRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "ssm:StartAssociationsOnce", "ssm:CreateAssociation", "ssm:CreateAssociationBatch", "ssm:UpdateAssociation" ], "Resource": "*", "Effect": "Allow" } ] }, "PolicyName": "ssm-association" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceRole": { "Type": "AWS::IAM::Role", "Properties": { "Policies": [ { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::aws-ssm-${AWS::Region}/*", "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-${AWS::Region}/*", "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*", "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*", "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*" ], "Effect": "Allow" } ] }, "PolicyName": "ssm-custom-s3-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret" ], "Resource": [ "DomainJoinSecrets" ] } ] }, "PolicyName": "ssm-secrets-policy" }, { "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" ], "Resource": [ "arn:${AWS::Partition}:s3:::${DSCBucket}/*", "arn:${AWS::Partition}:s3:::${DSCBucket}" ], "Effect": "Allow" } ] }, "PolicyName": "s3-instance-bucket-policy" } ], "Path": "/", "ManagedPolicyArns": [ "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore", "arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy" ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "ec2.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } } }, "SSMInstanceProfile": { "Type": "AWS::IAM::InstanceProfile", "Properties": { "Roles": [ "SSMInstanceRole" ] } }, "WINEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "LatestAmiId", "InstanceType": "EC2InstanceType", "IamInstanceProfile": "SSMInstanceProfile", "NetworkInterfaces": [ { "DeleteOnTermination": true, "DeviceIndex": "0", "SubnetId": "SubnetID", "GroupSet": [ "DomainMemberSGID" ] } ], "Tags": [ { "Key": "Name", "Value": "WindowsBox1" } ] } }, "DomainAssociation": { "Type": "AWS::SSM::Association", "Properties": { "AssociationName": "DomainJoin", "Name": "DomainJoinAutomation", "WaitForSuccessTimeoutSeconds": 600, "AutomationTargetParameterName": "InstanceId", "Targets": [ { "Key": "ParameterValues", "Values": [ "WINEC2Instance" ] } ], "OutputLocation": { "S3Location": { "OutputS3BucketName": "DSCBucket", "OutputS3KeyPrefix": "logs/" } }, "Parameters": { "DomainDNSName": [ "DomainDNSName" ], "DomainNetBIOSName": [ "DomainNetBIOSName" ], "AdminSecrets": [ "DomainJoinSecrets" ], "S3BucketName": [ "DSCBucket" ], "AutomationAssumeRole": [ "SSMExecutionRole.Arn" ] } } } } }

YAML

--- Description: "Deploy single windows EC2 Instance and join domain with SSM Association" Parameters: DomainAdminPassword: AllowedPattern: (?=^.{6,255}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.* Description: Password for the domain admin user. Must be at least 8 characters, containing letters, numbers, and symbols. MaxLength: '32' MinLength: '8' NoEcho: 'true' Type: String DomainAdminUser: AllowedPattern: '[a-zA-Z0-9]*' Default: Admin Description: User name for the account that will be used as domain administrator. This is separate from the default "Administrator" account. MaxLength: '25' MinLength: '5' Type: String DomainDNSName: AllowedPattern: '[a-zA-Z0-9\-]+\..+' Default: example.com Description: Fully qualified domain name (FQDN). MaxLength: '255' MinLength: '2' Type: String DomainMemberSGID: Description: ID of the domain member security group (e.g., sg-7f16e910). Type: AWS::EC2::SecurityGroup::Id DomainNetBIOSName: AllowedPattern: '[a-zA-Z0-9\-]+' Default: EXAMPLE Description: NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows. MaxLength: '15' MinLength: '1' Type: String EC2InstanceType: AllowedValues: - t3.nano - t3.micro - t3.small - t3.medium - t3.large - t3.xlarge - t3.2xlarge - m5.large - m5.xlarge - m5.2xlarge Default: m5.large Description: Amazon EC2 instance type Type: String LatestAmiId: Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: "/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base" SubnetID: Description: ID of a Subnet. Type: AWS::EC2::Subnet::Id Resources: DSCBucket: Type: AWS::S3::Bucket Properties: LifecycleConfiguration: Rules: - Id: DeleteAfter30Days ExpirationInDays: 30 Status: Enabled Prefix: 'logs/' DomainJoinSecrets: Type: AWS::SecretsManager::Secret Properties: Name: !Sub 'DomainJoinSecrets-${AWS::StackName}' Description: Secrets to join AD domain SecretString: !Sub '{"username":"${DomainAdminUser}","password":"${DomainAdminPassword}"}' LambdaSSMRole: Type: AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:PutObject Resource: - !Sub "${DSCBucket.Arn}" - !Sub "${DSCBucket.Arn}/*" PolicyName: write-mof-s3 Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' WriteScriptFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: > var AWS = require('aws-sdk'), s3 = new AWS.S3(); const response = require("cfn-response"); exports.handler = async (event, context) => { console.log(JSON.stringify(event)); if (event.RequestType === 'Delete') { await postResponse(event, context, response.SUCCESS, {}) return; } function postResponse(event, context, status, data){ return new Promise((resolve, reject) => { setTimeout(() => response.send(event, context, status, data), 5000) }); } await s3.putObject({ Body: event.ResourceProperties.Body, Bucket: event.ResourceProperties.Bucket, Key: event.ResourceProperties.Key }).promise(); await postResponse(event, context, response.SUCCESS, {}); }; Handler: index.handler Role: !GetAtt LambdaSSMRole.Arn Runtime: nodejs10.x Timeout: 10 WriteDomainJoinScript: Type: Custom::WriteScript Properties: ServiceToken: !GetAtt WriteScriptFunction.Arn Bucket: !Ref DSCBucket Key: "DomainJoin.ps1" Body: | [CmdletBinding()] # Incoming Parameters for Script, CloudFormation\SSM Parameters being passed in param( [Parameter(Mandatory=$true)] [string]$DomainNetBIOSName, [Parameter(Mandatory=$true)] [string]$DomainDNSName, [Parameter(Mandatory=$true)] [string]$AdminSecret ) # Formatting AD Admin User to proper format for JoinDomain DSC Resources in this Script $DomainAdmin = 'Domain\User' -replace 'Domain',$DomainNetBIOSName -replace 'User',$UserName $Admin = ConvertFrom-Json -InputObject (Get-SECSecretValue -SecretId $AdminSecret).SecretString $AdminUser = $DomainNetBIOSName + '\' + $Admin.UserName # Creating Credential Object for Administrator $Credentials = (New-Object PSCredential($AdminUser,(ConvertTo-SecureString $Admin.Password -AsPlainText -Force))) # Getting the DSC Cert Encryption Thumbprint to Secure the MOF File $DscCertThumbprint = (get-childitem -path cert:\LocalMachine\My | where { $_.subject -eq "CN=SampleDscEncryptCert" }).Thumbprint # Getting the Name Tag of the Instance $NameTag = (Get-EC2Tag -Filter @{ Name="resource-id";Values=(Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id)}| Where-Object { $_.Key -eq "Name" }) $NewName = $NameTag.Value # Creating Configuration Data Block that has the Certificate Information for DSC Configuration Processing $ConfigurationData = @{ AllNodes = @( @{ NodeName="*" CertificateFile = "C:\awssample\publickeys\SamplePublicKey.cer" Thumbprint = $DscCertThumbprint PSDscAllowDomainUser = $true }, @{ NodeName = 'localhost' } ) } Configuration DomainJoin { param( [PSCredential] $Credentials ) Import-Module -Name PSDesiredStateConfiguration Import-Module -Name ComputerManagementDsc Import-DscResource -Module PSDesiredStateConfiguration Import-DscResource -Module ComputerManagementDsc Node 'localhost' { Computer JoinDomain { Name = $NewName DomainName = $DomainDNSName Credential = $Credentials } } } DomainJoin -OutputPath 'C:\awssample\DomainJoin' -ConfigurationData $ConfigurationData -Credentials $Credentials WriteInstallModuleScript: Type: Custom::WriteScript Properties: ServiceToken: !GetAtt WriteScriptFunction.Arn Bucket: !Ref DSCBucket Key: "install-modules.ps1" Body: | [CmdletBinding()] param() "Setting up Powershell Gallery to Install DSC Modules" Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Set-PSRepository -Name PSGallery -InstallationPolicy Trusted "Installing the needed Powershell DSC modules for this Quick Start" Install-Module -Name ComputerManagementDsc Install-Module -Name PSDscResources "Creating Directory for DSC Public Cert" New-Item -Path C:\awssample\publickeys -ItemType directory -Force "Setting up DSC Certificate to Encrypt Credentials in MOF File" $cert = New-SelfSignedCertificate -Type DocumentEncryptionCertLegacyCsp -DnsName 'SampleDscEncryptCert' -HashAlgorithm SHA256 # Exporting the public key certificate $cert | Export-Certificate -FilePath "C:\awssample\publickeys\SamplePublicKey.cer" -Force WriteLCMConfigScript: Type: Custom::WriteScript Properties: ServiceToken: !GetAtt WriteScriptFunction.Arn Bucket: !Ref DSCBucket Key: "LCM-Config.ps1" Body: | # This block sets the LCM configuration to what we need for QS [DSCLocalConfigurationManager()] configuration LCMConfig { Node 'localhost' { Settings { RefreshMode = 'Push' ActionAfterReboot = 'StopConfiguration' RebootNodeIfNeeded = $false CertificateId = $DscCertThumbprint } } } $DscCertThumbprint = [string](get-childitem -path cert:\LocalMachine\My | where { $_.subject -eq "CN=SampleDscEncryptCert" }).Thumbprint #Generates MOF File for LCM LCMConfig -OutputPath 'C:\awssample\LCMConfig' # Sets LCM Configuration to MOF generated in previous command Set-DscLocalConfigurationManager -Path 'C:\awssample\LCMConfig' DomainJoinAutomation: Type: AWS::SSM::Document Properties: DocumentType: Automation Content: schemaVersion: "0.3" description: "Join a Windows Domain" # Role that is utilized to perform the steps within the Automation Document. assumeRole: "{{AutomationAssumeRole}}" # Gathering parameters needed to configure DCs in the Quick Start parameters: InstanceId: description: "ID of the Instance." type: "StringList" DomainDNSName: default: "example.com" description: "Fully qualified domain name (FQDN) of the forest root domain e.g. example.com" type: "String" DomainNetBIOSName: default: "example" description: "NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows e.g. EXAMPLE" type: "String" AdminSecrets: description: "AWS Secrets Parameter Name that has Password and User name for a domain administrator." type: "String" S3BucketName: description: "S3 bucket name for the Quick Start assets. Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." type: "String" AutomationAssumeRole: default: "" description: "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf." type: "String" mainSteps: # This step Demonstrates how to run a local script on an Instance. It can be defined or pointed to a local script. - name: "InstallDSCModules" action: "aws:runCommand" inputs: DocumentName: "AWS-RunRemoteScript" InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: !Sub '/ssm/${AWS::StackName}' Parameters: sourceType: "S3" sourceInfo: '{"path": "https://{{S3BucketName}}.s3.amazonaws.com/install-modules.ps1"}' commandLine: "./install-modules.ps1" - name: "ConfigureLCM" action: "aws:runCommand" inputs: DocumentName: "AWS-RunRemoteScript" InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: !Sub '/ssm/${AWS::StackName}' Parameters: sourceType: "S3" sourceInfo: '{"path": "https://{{S3BucketName}}.s3.amazonaws.com/LCM-Config.ps1"}' commandLine: "./LCM-Config.ps1" - name: "GenerateDomainJoinMof" action: "aws:runCommand" inputs: DocumentName: "AWS-RunRemoteScript" InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: !Sub '/ssm/${AWS::StackName}' Parameters: sourceType: "S3" sourceInfo: '{"path": "https://{{S3BucketName}}.s3.amazonaws.com/DomainJoin.ps1"}' commandLine: "./DomainJoin.ps1 -DomainNetBIOSName {{DomainNetBIOSName}} -DomainDNSName {{DomainDNSName}} -AdminSecret {{AdminSecrets}}" - name: "DomainJoin" action: aws:runCommand inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: !Sub '/ssm/${AWS::StackName}' Parameters: commands: - | function DscStatusCheck () { $LCMState = (Get-DscLocalConfigurationManager).LCMState if ($LCMState -eq 'PendingConfiguration' -Or $LCMState -eq 'PendingReboot') { 'returning 3010, should continue after reboot' exit 3010 } else { 'Completed' } } Start-DscConfiguration 'C:\awssample\DomainJoin' -Wait -Verbose -Force DscStatusCheck SSMExecutionRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - ssm:StartAssociationsOnce - ssm:CreateAssociation - ssm:CreateAssociationBatch - ssm:UpdateAssociation Resource: '*' Effect: Allow PolicyName: ssm-association Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceRole: Type : AWS::IAM::Role Properties: Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub 'arn:aws:s3:::aws-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-${AWS::Region}/*' - !Sub 'arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*' - !Sub 'arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*' - !Sub 'arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*' Effect: Allow PolicyName: ssm-custom-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue - secretsmanager:DescribeSecret Resource: - !Ref 'DomainJoinSecrets' PolicyName: ssm-secrets-policy - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:PutObject - s3:PutObjectAcl - s3:ListBucket Resource: - !Sub 'arn:${AWS::Partition}:s3:::${DSCBucket}/*' - !Sub 'arn:${AWS::Partition}:s3:::${DSCBucket}' Effect: Allow PolicyName: s3-instance-bucket-policy Path: / ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy' AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" - "ssm.amazonaws.com" Action: "sts:AssumeRole" SSMInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref SSMInstanceRole WINEC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: !Ref LatestAmiId InstanceType: !Ref EC2InstanceType IamInstanceProfile: !Ref SSMInstanceProfile NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: '0' SubnetId: !Ref 'SubnetID' GroupSet: - !Ref DomainMemberSGID Tags: - Key: "Name" Value: "WindowsBox1" DomainAssociation: Type: AWS::SSM::Association Properties: AssociationName: DomainJoin # We are using the AWS-ApplyDSCMofs Document Name: !Ref DomainJoinAutomation WaitForSuccessTimeoutSeconds: 600 AutomationTargetParameterName: InstanceId Targets: - Key: ParameterValues Values: - !Ref WINEC2Instance OutputLocation: S3Location: OutputS3BucketName: !Ref DSCBucket OutputS3KeyPrefix: 'logs/' Parameters: DomainDNSName: - !Ref DomainDNSName DomainNetBIOSName: - !Ref DomainNetBIOSName AdminSecrets: - !Ref DomainJoinSecrets S3BucketName: - !Ref DSCBucket AutomationAssumeRole: - !GetAtt 'SSMExecutionRole.Arn'

另请参阅