AWSTemplateFormatVersion: '2010-09-09' Description: Scripts, Automation Documents, all components to perform configuration. (qs-1r91fg4is) Parameters: ConfigBucket: AllowedPattern: '^[a-z0-9]+[a-z0-9\.\-]*[a-z0-9]+$' ConstraintDescription: Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Description: S3 bucket name where PowerShell DSC Mof files exist and HTML web files. Config bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Type: String DomainJoinSecrets: AllowedPattern: '^arn:[^:]+:secretsmanager:[^:]+:\d{12}:secret:[\w\/\+\=\.\@\-]{1,512}$' Description: "The Secrets Manager name or ARN that will be used to perform all need domain actions." MaxLength: '2048' MinLength: '20' Type: "String" WriteS3LambdaRoleArn: AllowedPattern: '^arn:[^:]+:iam::\d{12}:role\/[\w\/\+\=\.\@\-]{1,512}$' Description: "The IAM Role ARN that will be used for the Lambda that writes file to Config Bucket." MaxLength: '2048' MinLength: '20' Type: "String" SetupConfigurationDocName: AllowedPattern: '^[a-zA-Z0-9\_\\\-\.]{3,128}$' Description: Document name of AWS Systems Manager Automation Document for Setup Config. MaxLength: "128" MinLength: "3" Type: String RemoveConfigurationDocName: AllowedPattern: '^[a-zA-Z0-9\_\\\-\.]{3,128}+$' Description: Document name of AWS Systems Manager Automation Document for Removal Config. MaxLength: "128" MinLength: "3" Type: String ECSClusterName: AllowedPattern: '^([\w\-]+)?$' Description: Name of ECS cluster. MaxLength: "255" Type: String WebBucketName: AllowedPattern: '(^[a-z0-9]+[a-z0-9\.\-]*[a-z0-9]+)?$' Description: Bucket name where html file is located for iis. Default: "" Type: String WebBucketKey: AllowedPattern: '^[0-9a-zA-Z\-\/\.]+$' Description: Bucket Key where html file is located for iis. Only change S3 Bucket Wepage is specified, otherwise leave as default. Default: "webfiles/index.html" Type: String Conditions: ECSDeploy: !Not - !Equals - !Ref ECSClusterName - '' Resources: WriteMOFFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import json import logging import threading import boto3 import cfnresponse def create_object(bucket, body, key): s3 = boto3.client('s3') s3.put_object(Body=body,Bucket=bucket, Key=key) def delete_objects(bucket, key): s3 = boto3.client('s3') objects = s3.list_objects_v2(Bucket=bucket) logsobjects = s3.list_objects_v2(Bucket=bucket, Prefix='logs') if logsobjects['KeyCount'] != 0: for object in logsobjects['Contents']: s3.delete_object(Bucket=bucket, Key=object['Key']) s3.delete_object(Bucket=bucket, Key=key) else: s3.delete_object(Bucket=bucket, Key=key) def timeout(event, context): logging.error('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: %s' % json.dumps(event)) status = cfnresponse.SUCCESS try: bucket = event['ResourceProperties']['Bucket'] body = event['ResourceProperties']['Body'] key = event['ResourceProperties']['Key'] if event['RequestType'] == 'Delete': delete_objects(bucket, key) else: create_object(bucket, body, key) except Exception as e: logging.error('Exception: %s' % e, exc_info=True) status = cfnresponse.FAILED finally: timer.cancel() object_url = f'https://{bucket}.s3.amazonaws.com/{key}' cfnresponse.send(event, context, status, {}, object_url) Handler: index.handler Role: !Ref 'WriteS3LambdaRoleArn' Runtime: python3.7 Timeout: 240 WriteDomainJoinIISMOF: Type: Custom::WriteMOFFile Properties: ServiceToken: !GetAtt WriteMOFFunction.Arn Bucket: !Ref ConfigBucket Key: "DomainJoinWebBuild.mof" Body: !Sub | /* @TargetNode='localhost' */ instance of MSFT_Credential as $MSFT_Credential1ref { Password = "stringdoesntmatter"; 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"; }; WriteWebBuildMOF: Type: Custom::WriteMOFFile Properties: ServiceToken: !GetAtt WriteMOFFunction.Arn Bucket: !Ref ConfigBucket Key: "WebSite.mof" Body: | /* @TargetNode='localhost' */ instance of MSFT_RoleResource as $MSFT_RoleResource1ref { ResourceID = "[WindowsFeature]WebServer"; Ensure = "Present"; Name = "Web-Server"; ModuleName = "PSDesiredStateConfiguration"; ModuleVersion = "1.0"; ConfigurationName = "WebsiteTest"; }; instance of MSFT_ScriptResource as $MSFT_ScriptResource1ref { ResourceID = "[Script]GetWebFiles"; GetScript = "\n $filelocation = \"c:\\webfiles\\index.html\"\n Return @{Result = [string]$(test-path $filelocation)}\n "; TestScript = "\n $filelocation = \"c:\\webfiles\\index.html\"\n if((test-path $filelocation) -eq $false) {\n Write-Verbose 'Files need to be Downloaded'\n Return $false\n } else {\n Write-Verbose 'Files are present locally'\n Return $true\n }\n "; SetScript = "\n Copy-Item -Path c:\\windows\\temp\\index.html -Destination c:\\inetpub\\wwwroot\\index.html -Force\n "; ModuleName = "PSDesiredStateConfiguration"; ModuleVersion = "1.0"; DependsOn = { "[WindowsFeature]WebServer"}; ConfigurationName = "WebsiteTest"; }; instance of OMI_ConfigurationDocument { Version="2.0.0"; MinimumCompatibleVersion = "1.0.0"; CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"}; Name="WebsiteTest"; }; WriteDomainJoinMOF: Type: Custom::WriteMOFFile Properties: ServiceToken: !GetAtt WriteMOFFunction.Arn Bucket: !Ref ConfigBucket Key: "DomainJoin.mof" Body: !Sub | /* @TargetNode='localhost' */ instance of MSFT_Credential as $MSFT_Credential1ref { Password = "stringdoesntmatter"; 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"; }; WriteDomainRemoveMOF: Type: Custom::WriteMOFFile Properties: ServiceToken: !GetAtt WriteMOFFunction.Arn Bucket: !Ref ConfigBucket Key: "DomainRemove.mof" Body: !Sub | /* @TargetNode='localhost' */ instance of MSFT_RoleResource as $MSFT_RoleResource1ref { ResourceID = "[WindowsFeature]RSAT-AD-PowerShell"; Ensure = "Present"; Name = "RSAT-AD-PowerShell"; ModuleName = "PSDesiredStateConfiguration"; ModuleVersion = "1.0"; ConfigurationName = "RemoveDomain"; }; instance of MSFT_Credential as $MSFT_Credential1ref { Password = "stringdoesntmatter"; UserName = "${DomainJoinSecrets}"; }; instance of MSFT_ADComputer as $MSFT_ADComputer1ref { ResourceID = "[ADComputer]RemoveDomain"; Ensure = "Absent"; Credential = $MSFT_Credential1ref; ComputerName = "{tag:Name}"; ModuleName = "ActiveDirectoryDsc"; ModuleVersion = "6.0.1"; DependsOn = { "[WindowsFeature]RSAT-AD-PowerShell"}; ConfigurationName = "RemoveDomain"; }; instance of OMI_ConfigurationDocument { Version="2.0.0"; MinimumCompatibleVersion = "1.0.0"; CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"}; Name="RemoveDomain"; }; WriteHtmlFile: Type: Custom::WriteMOFFile Properties: ServiceToken: !GetAtt WriteMOFFunction.Arn Bucket: !Ref ConfigBucket Key: "webfiles/index.html" Body: !Join - '' - - ' ' - '
' - >- - >- - ' ' - >-Your application is running on Amazon ' - !If - ECSDeploy - ECS - EC2 CloudWatchParam: Type: AWS::SSM::Parameter Properties: Name: !Sub 'AmazonCloudWatch-Windows-${AWS::StackName}' Type: String Value: !Join - '' - - | { "logs": { "logs_collected": { - !If - ECSDeploy - "" - | "files": { "collect_list": [ { "file_path": "C:\\inetpub\\logs\\LogFiles\\W3SVC1\\*.log", "log_group_name": "/aws/ec2/iis-quickstart/iis-logs", "log_stream_name": "{instance_id}" } ] }, - | "windows_events": { "collect_list": [ { "event_format": "xml", "event_levels": [ "VERBOSE", "INFORMATION", "WARNING", "ERROR", "CRITICAL" ], "event_name": "System", "log_group_name": "/aws/ec2/iis-quickstart/system-logs", "log_stream_name": "{instance_id}" } ] } } }, "metrics": { "metrics_collected": { "LogicalDisk": { "measurement": [ "% Free Space" ], "metrics_collection_interval": 60, "resources": [ "*" ] }, "Memory": { "measurement": [ "% Committed Bytes In Use" ], "metrics_collection_interval": 60 }, "statsd": { "metrics_aggregation_interval": 60, "metrics_collection_interval": 10, "service_address": ":8125" } } } } Description: SSM Parameter for CloudWatch Configuration. SetupConfigurationDoc: Type: AWS::SSM::Document Properties: Name: !Ref SetupConfigurationDocName DocumentType: Automation Content: schemaVersion: "0.3" description: "Configure Instances on Launch" assumeRole: "{{AutomationAssumeRole}}" parameters: DeploymentType: default: !If [ECSDeploy, 'ECS', 'EC2'] description: "Is this automation doc being run on EC2 with ECS or only EC2" type: "String" allowedValues: - EC2 - ECS InstanceId: allowedPattern: '^i-[a-f0-9]{8}(?:[a-f0-9]{9})?$' description: "ID of the Instance." type: "String" ASGName: allowedPattern: '^[\w\W\s]+$' description: "Auto Scaling Group Name" maxChars: '255' minChars: '2' type: "String" ConfigBucket: allowedPattern: '^[a-z0-9]+[a-z0-9\.\-]*[a-z0-9]+$' description: "Bucket Containing Mof Files" type: "String" LCHName: allowedPattern: '^[\w\/\-]+$' description: "Life Cycle Hook Name" maxChars: '255' minChars: '2' type: "String" AutomationAssumeRole: allowedPattern: '^(arn:[^:]+:iam::\d{12}:role\/[\w\/\+\=\.\@\-]{1,512})?$' default: "" description: "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf." type: "String" mainSteps: - name: waitUntilInstanceStateRunning action: aws:waitForAwsResourceProperty timeoutSeconds: 600 inputs: Service: ec2 Api: DescribeInstanceStatus InstanceIds: - "{{InstanceId}}" PropertySelector: "$.InstanceStatuses[0].InstanceState.Name" DesiredValues: - running - name: assertInstanceStateRunning action: aws:assertAwsResourceProperty inputs: Service: ec2 Api: DescribeInstanceStatus InstanceIds: - "{{InstanceId}}" PropertySelector: "$.InstanceStatuses[0].InstanceState.Name" DesiredValues: - running - name: "setNameTag" action: aws:runCommand onFailure: "step:abandonHookAction" inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: "/aws/ssm/iis-quickstart/SetupConfiguration" Parameters: commands: - | Import-Module AWSPowerShell Try { [string]$token = Invoke-RestMethod -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "600"} -Method PUT -Uri http://169.254.169.254/latest/api/token $instanceid = (Invoke-RestMethod -Headers @{"X-aws-ec2-metadata-token" = $token} -Method GET -Uri http://169.254.169.254/latest/meta-data/instance-id) Write-Output "Creating new ec2 name tag: $env:COMPUTERNAME on $instanceId " New-EC2Tag -Resource $instanceId -Tag @{Key="Name";Value=$env:COMPUTERNAME} }Catch [System.Exception] { Write-Output "Failed to set Name Tag $_" Exit 1 } - name: "installCloudWatchAgent" action: aws:runCommand onFailure: step:abandonHookAction inputs: DocumentName: AWS-ConfigureAWSPackage InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: "/aws/ssm/iis-quickstart/SetupConfiguration" Parameters: action: - "Install" installationType: - "Uninstall and reinstall" name: - "AmazonCloudWatchAgent" - name: "configureCloudWatchAgent" action: aws:runCommand onFailure: step:abandonHookAction inputs: DocumentName: AmazonCloudWatch-ManageAgent InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: "/aws/ssm/iis-quickstart/SetupConfiguration" Parameters: action: - "configure" mode: - 'ec2' optionalConfigurationLocation: - !Ref 'CloudWatchParam' optionalConfigurationSource: - 'ssm' optionalRestart: - 'yes' - name: "applyDomainJoin" action: aws:runCommand onFailure: step:abandonHookAction inputs: DocumentName: AWS-ApplyDSCMofs InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: "/aws/ssm/iis-quickstart/SetupConfiguration" Parameters: MofsToApply: - "s3:{{ConfigBucket}}:DomainJoin.mof" ServicePath: - default MofOperationMode: - Apply ModuleSourceBucketName: - "NONE" AllowPSGalleryModuleSource: - "True" RebootBehavior: - "AfterMof" UseComputerNameForReporting: - "False" EnableVerboseLogging: - "False" EnableDebugLogging: - "False" - name: "CopyS3Webpage" action: aws:runCommand onFailure: step:abandonHookAction inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: '/aws/ssm/iis-quickstart/SetupConfiguration' Parameters: commands: - !Sub | Import-Module AWSPowerShell Try { Copy-S3Object -BucketName ${WebBucketName} -Key ${WebBucketKey} -LocalFile c:\\windows\\temp\\index.html }Catch [System.Exception] { Write-Output "Failed to copy webpage ${WebBucketName}${WebBucketKey} $_" Exit 1 } - name: DeploymentTypeChoice action: aws:branch inputs: Choices: - NextStep: ECSClusterJoin Variable: "{{DeploymentType}}" StringEquals: 'ECS' - Variable: "{{DeploymentType}}" StringEquals: 'EC2' NextStep: applyIIS - name: "ECSClusterJoin" action: aws:runCommand nextStep: completeHookAction onFailure: step:abandonHookAction inputs: DocumentName: AWS-RunPowerShellScript InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: '/aws/ssm/iis-quickstart/SetupConfiguration' Parameters: commands: - !Sub | Import-Module ECSTools Initialize-ECSAgent -Cluster ${ECSClusterName} -EnableTaskIAMRole -LoggingDrivers '["json-file","awslogs"]' - name: "applyIIS" action: aws:runCommand onFailure: step:abandonHookAction inputs: DocumentName: AWS-ApplyDSCMofs InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: "/aws/ssm/iis-quickstart/SetupConfiguration" Parameters: MofsToApply: - "s3:{{ConfigBucket}}:WebSite.mof" ServicePath: - default MofOperationMode: - Apply ModuleSourceBucketName: - "NONE" AllowPSGalleryModuleSource: - "True" RebootBehavior: - "AfterMof" UseComputerNameForReporting: - "False" EnableVerboseLogging: - "False" EnableDebugLogging: - "False" - name: "completeHookAction" action: aws:executeAwsApi isEnd: true inputs: Service: autoscaling Api: CompleteLifecycleAction AutoScalingGroupName: "{{ASGName}}" InstanceId: "{{InstanceId}}" LifecycleActionResult: CONTINUE LifecycleHookName: "{{LCHName}}" - name: "abandonHookAction" action: aws:executeAwsApi isEnd: true inputs: Service: autoscaling Api: CompleteLifecycleAction AutoScalingGroupName: "{{ASGName}}" InstanceId: "{{InstanceId}}" LifecycleActionResult: ABANDON LifecycleHookName: "{{LCHName}}" RemoveConfigurationDoc: Type: AWS::SSM::Document Properties: Name: !Ref RemoveConfigurationDocName DocumentType: Automation Content: schemaVersion: "0.3" description: "Remove EC2 Instances from AD Domain" assumeRole: "{{AutomationAssumeRole}}" parameters: InstanceId: allowedPattern: '^i-[a-f0-9]{8}(?:[a-f0-9]{9})?$' description: "ID of the Instance." type: "String" ASGName: allowedPattern: '^[a-zA-Z0-9\_\W\s]+$' description: "Auto Scaling Group Name" maxChars: '255' minChars: '2' type: "String" ConfigBucket: allowedPattern: '^[a-z0-9]+[a-z0-9\.\-]*[a-z0-9]+$' description: "Bucket Containing Mof Files" type: "String" LCHName: allowedPattern: '^[A-Za-z0-9\-\_\/]+$' description: "Life Cycle Hook Name" maxChars: '255' minChars: '2' type: "String" AutomationAssumeRole: allowedPattern: '^(arn:[^:]+:iam::\d{12}:role\/[\w\/\+\=\.\@\-]{1,512})?$' default: "" description: "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf." type: "String" mainSteps: - name: "RemoveFromDomain" action: aws:runCommand onFailure: step:abandonHookAction inputs: DocumentName: AWS-ApplyDSCMofs InstanceIds: - "{{InstanceId}}" CloudWatchOutputConfig: CloudWatchOutputEnabled: "true" CloudWatchLogGroupName: "/aws/ssm/iis-quickstart/RemoveConfiguration" Parameters: MofsToApply: - "s3:{{ConfigBucket}}:DomainRemove.mof" ServicePath: - default MofOperationMode: - Apply ModuleSourceBucketName: - "NONE" AllowPSGalleryModuleSource: - "True" RebootBehavior: - "AfterMof" UseComputerNameForReporting: - "False" EnableVerboseLogging: - "False" EnableDebugLogging: - "False" - name: "completeHookAction" action: aws:executeAwsApi isEnd: true inputs: Service: autoscaling Api: CompleteLifecycleAction AutoScalingGroupName: "{{ASGName}}" InstanceId: "{{InstanceId}}" LifecycleActionResult: CONTINUE LifecycleHookName: "{{LCHName}}" - name: "abandonHookAction" action: aws:executeAwsApi isEnd: true inputs: Service: autoscaling Api: CompleteLifecycleAction AutoScalingGroupName: "{{ASGName}}" InstanceId: "{{InstanceId}}" LifecycleActionResult: ABANDON LifecycleHookName: "{{LCHName}}" Outputs: SetupConfigurationDocName: Value: !Ref SetupConfigurationDoc RemoveConfigurationDocName: Value: !Ref RemoveConfigurationDoc