AWSTemplateFormatVersion: '2010-09-09' Description: >- This template creates an end-to-end Amazon AppStream 2.0 imaging process for Microsoft Windows based images, utilizing AWS Lambda and AWS Step Functions. Parameters: SourceS3Bucket: Type: String Description: S3 Bucket name that contains the files required to import and build this AppStream 2.0 automation workflow. (Lambda functions and layer zip files) SNSEmailSubscriptionEndPoint: Type: String Description: The email address that receives the automation notifications. Default: username@domain.com AS2VPCId: Type: 'AWS::EC2::VPC::Id' Description: VPCID where AppStream 2.0 image builders and Lambda functions will reside. ConstraintDescription: Must be the VPC Id of an existing Virtual Private Cloud. AS2VPCSubnet1: Type: 'AWS::EC2::Subnet::Id' Description: Subnet ID where Lambda functions will reside. AS2VPCSubnet2: Type: 'AWS::EC2::Subnet::Id' Description: Subnet ID where Lambda functions will reside. AS2DefaultImage: Type: String Description: Default Windows image to use when creating the AppStream 2.0 image builder instance. For non-domain joined image builders, input your customized base image with the embedded startup script here. See blog documentation for instructions on creating this. Default: AppStream-WinServer2019-03-03-2022 DefaultDomain: Type: String Description: Default Active Directory FQDN to join image builder instances to. This should already be setup within the AppStream 2.0 console. Leave blank or enter 'none' to not join a domain. (onprem.corp.int) Default: none DefaultOU: Type: String Description: Default Active Directory OU Distinguished Name to place image builder instances in. Leave blank or enter 'none' to not join a domain. (OU=Image_Builders,OU=AppStream,OU=Virtual,OU=Production,OU=EUC,DC=onprem,DC=corp,DC=int) Default: none Conditions: IsNotJoinDomain: !Or [!Equals [!Ref DefaultDomain, "none"], !Equals [!Ref DefaultDomain, ""]] Resources: ImageBuilderSecret: Type: 'AWS::SecretsManager::Secret' Properties: Name: as2/builder/pw Description: "Local admin account on AppStream 2.0 image builders used in the image creation automation. This secret has a dynamically generated secret password." GenerateSecretString: SecretStringTemplate: '{"as2_builder_admin_user": "as2_builder_admin"}' GenerateStringKey: "as2_builder_admin_pw" PasswordLength: 30 ExcludeCharacters: '"@/\' Tags: - Key: AS2_Image_Automation Value: v1 WorkShopS3Bucket: Type: "AWS::S3::Bucket" #creates a bucket with a semi-random name as2-automation-blog-XXXXXX Properties: BucketName: !Join - "-" - - "as2-automation-blog" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" AccessControl: Private BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: "Delete previous versions" NoncurrentVersionExpiration: NewerNoncurrentVersions: 1 NoncurrentDays: 14 Status: Enabled LambdaFunctionLayer: Type: AWS::Lambda::LayerVersion Properties: LayerName: !Join - "_" - - "AS2_Automation_pywinrm" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Description: Contains pywinrm libraries. Dependencies for the AppStream 2.0 automation. Content: S3Bucket: Ref: SourceS3Bucket S3Key: Lambda_Layer_winrm_libraries.zip CompatibleRuntimes: - python3.9 - python3.8 - python3.7 - python3.6 SNSTopic: Type: AWS::SNS::Topic Properties: TopicName: !Join - "_" - - "AS2_Automation_Windows_Notification" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" KmsMasterKeyId: "alias/aws/sns" SNSTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: Id: SNSTopicPolicy Version: '2012-10-17' Statement: - Sid: "PublishEventsToSNSTopic" Effect: Allow Principal: Service: - events.amazonaws.com - lambda.amazonaws.com Action: - sns:Publish Resource: !Ref SNSTopic Topics: - !Ref SNSTopic SNSSubscription: Type: AWS::SNS::Subscription Properties: Endpoint: Ref: SNSEmailSubscriptionEndPoint Protocol: email TopicArn: Ref: SNSTopic LambdaFunctionSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allows AppStream 2.0 automation Lambda functions to communicate with image builder instances and other AWS services. GroupName: !Join - "_" - - "AS2_Automation_Windows_Lambdas" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" VpcId: Ref: AS2VPCId SecurityGroupEgress: - IpProtocol: 'tcp' FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 Description: "Allow HTTPS from Lambda functions" - IpProtocol: 'tcp' FromPort: 53 ToPort: 53 CidrIp: 0.0.0.0/0 Description: "Allow DNS lookup from Lambda functions" - IpProtocol: 'udp' FromPort: 53 ToPort: 53 CidrIp: 0.0.0.0/0 Description: "Allow DNS lookup from Lambda functions" LambdaFunctionSecurityGroupEgressRule1: Type: AWS::EC2::SecurityGroupEgress Properties: IpProtocol: 'tcp' FromPort: 5985 ToPort: 5985 DestinationSecurityGroupId: Ref: ImageBuilderSecurityGroup Description: "Allow remote WinRM from Lambda functions to image builders" GroupId: Ref: LambdaFunctionSecurityGroup LambdaFunctionSecurityGroupEgressRule2: Type: AWS::EC2::SecurityGroupEgress Properties: IpProtocol: 'tcp' FromPort: 5986 ToPort: 5986 DestinationSecurityGroupId: Ref: ImageBuilderSecurityGroup Description: "Allow remote WinRM from Lambda functions to image builders" GroupId: Ref: LambdaFunctionSecurityGroup ImageBuilderSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allows AppStream 2.0 automation Lambda functions to communicate with image builder instances, and image builders to talk to outside resources and AWS services. GroupName: !Join - "_" - - "AS2_Automation_Windows_ImageBuilders" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" VpcId: Ref: AS2VPCId SecurityGroupEgress: - IpProtocol: '-1' Description: "Allow outbound traffic from image builder" CidrIp: 0.0.0.0/0 ImageBuilderSecurityGroupIngressRule1: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: 'tcp' FromPort: 5985 ToPort: 5985 SourceSecurityGroupId: Ref: LambdaFunctionSecurityGroup Description: "Allow remote WinRM from Lambda functions" GroupId: Ref: ImageBuilderSecurityGroup ImageBuilderSecurityGroupIngressRule2: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: 'tcp' FromPort: 5986 ToPort: 5986 SourceSecurityGroupId: Ref: LambdaFunctionSecurityGroup Description: "Allow remote WinRM from Lambda functions" GroupId: Ref: ImageBuilderSecurityGroup LambdaFunctionIAMRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Join - "_" - - "AS2_Automation_Windows_Lambda_Role" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Description: IAM role for AS2 Automation Lambda Functions AssumeRolePolicyDocument: # What service can assume this role Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - 'lambda.amazonaws.com' Action: - 'sts:AssumeRole' LambdaFunctionIAMPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: Description: Permissions needed by Lambda functions to interact with AppStream 2.0 image builders and service. ManagedPolicyName: !Join - "_" - - "AS2_Automation_Windows_Lambda_Policy" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 's3:Get*' - 's3:List*' - ec2:DescribeNetworkInterfaces - ec2:CreateNetworkInterface - ec2:DeleteNetworkInterface - appstream:TagResource - appstream:DescribeImageBuilders - appstream:DescribeImages - appstream:CreateImageBuilder - appstream:DeleteImageBuilder - appstream:ListTagsForResource - appstream:StartImageBuilder - appstream:StopImageBuilder Resource: '*' - Effect: Allow Action: - sns:Publish - secretsmanager:GetSecretValue - secretsmanager:DescribeSecret Resource: - Ref: SNSTopic - Ref: ImageBuilderSecret - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt 'ImageBuilderIAMRole.Arn' Roles: - !Ref LambdaFunctionIAMRole StepFunctionIAMRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Join - "_" - - "AS2_Automation_Windows_StepFunction_Role" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Description: IAM role for AppStream 2.0 automation Step Function AssumeRolePolicyDocument: # What service can assume this role Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - 'states.amazonaws.com' Action: - 'sts:AssumeRole' StepFunctionIAMPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: Description: Permissions needed by Step Function functions to interact with AppStream 2.0 image builders, the AppStream 2.0 service, and SNS. ManagedPolicyName: !Join - "_" - - "AS2_Automation_Windows_StepFunction_Policy" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'lambda:InvokeFunction' Resource: - !GetAtt 'LambdaFunction01CreateBuilder.Arn' - !GetAtt 'LambdaFunction02ScriptedInstall.Arn' - !GetAtt 'LambdaFunction03RunImageAssistant.Arn' - !GetAtt 'LambdaFunction04ImageNotification.Arn' - Effect: Allow Action: - xray:PutTraceSegments - xray:PutTelemetryRecords - xray:GetSamplingRules - xray:GetSamplingTargets - logs:CreateLogDelivery - logs:GetLogDelivery - logs:UpdateLogDelivery - logs:DeleteLogDelivery - logs:ListLogDeliveries - logs:PutResourcePolicy - logs:DescribeResourcePolicies - logs:DescribeLogGroups - appstream:DescribeImageBuilders - appstream:GetImageBuilders - appstream:DescribeImages - appstream:CreateImageBuilder - appstream:DeleteImageBuilder - appstream:ListTagsForResource - appstream:StartImageBuilder - appstream:StopImageBuilder Resource: '*' Roles: - !Ref StepFunctionIAMRole ImageBuilderIAMRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Join - "_" - - "AS2_Automation_Windows_ImageBulder_Role" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Description: IAM role for AppStream 2.0 automation image builders AssumeRolePolicyDocument: # What service can assume this role Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - 'appstream.amazonaws.com' Action: - 'sts:AssumeRole' ImageBuilderIAMPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: Description: Permissions needed by AppStream 2.0 image builders to interact with Secrets Manager, S3, and Step Functions. ManagedPolicyName: !Join - "_" - - "AS2_Automation_Windows_ImageBuilder_Policy" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - states:SendTaskSuccess - states:SendTaskFailure - states:SendTaskHeartbeat Resource: '*' - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: - Ref: ImageBuilderSecret - Effect: Allow Action: - s3:ListBucket Resource: - !GetAtt 'WorkShopS3Bucket.Arn' - Effect: Allow Action: - s3:GetObject Resource: !Join - '' - - !GetAtt 'WorkShopS3Bucket.Arn' - '/*' Roles: - !Ref ImageBuilderIAMRole LambdaFunction01CreateBuilder: Type: AWS::Lambda::Function Properties: FunctionName: !Join - "_" - - "AS2_Automation_Windows_FN01_Create_Builder" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Handler: lambda_function.lambda_handler Code: S3Bucket: Ref: SourceS3Bucket S3Key: FN01_AS2_Windows_Automation_Create_Builder.zip Environment: Variables: Default_Description: Automated Image Builder Default_DisplayName : Automated Builder Default_Domain : Fn::If: - IsNotJoinDomain - none - Ref: DefaultDomain Default_OU : Fn::If: - IsNotJoinDomain - none - Ref: DefaultOU Default_IB_Name : Automated_Builder Default_Image : Ref: AS2DefaultImage Default_Method : Script Default_Prefix : Automated_Image Default_Role : !GetAtt 'ImageBuilderIAMRole.Arn' Default_SG : Ref: ImageBuilderSecurityGroup Default_Subnet : Ref: AS2VPCSubnet1 Default_Type : stream.standard.medium Default_S3_Bucket: !Ref WorkShopS3Bucket Runtime: python3.9 Role: !GetAtt 'LambdaFunctionIAMRole.Arn' Timeout: 30 LambdaFunction02ScriptedInstall: Type: AWS::Lambda::Function Properties: FunctionName: !Join - "_" - - "AS2_Automation_Windows_FN02_Scripted_Install" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Handler: lambda_function.lambda_handler Code: S3Bucket: Ref: SourceS3Bucket S3Key: FN02_AS2_Windows_Automation_Scripted_Install.zip Runtime: python3.9 Environment: Variables: Default_S3_Bucket: !Ref WorkShopS3Bucket Layers: - Ref: LambdaFunctionLayer Role: !GetAtt 'LambdaFunctionIAMRole.Arn' MemorySize: 256 Timeout: 600 VpcConfig: SecurityGroupIds: - Ref: LambdaFunctionSecurityGroup SubnetIds: - Ref: AS2VPCSubnet1 - Ref: AS2VPCSubnet2 DependsOn: - LambdaFunctionIAMRole - LambdaFunctionIAMPolicy LambdaFunction03RunImageAssistant: Type: AWS::Lambda::Function Properties: FunctionName: !Join - "_" - - "AS2_Automation_Windows_FN03_Run_Image_Assistant" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Handler: lambda_function.lambda_handler Code: S3Bucket: Ref: SourceS3Bucket S3Key: FN03_AS2_Windows_Automation_Run_Image_Assistant.zip Runtime: python3.9 Layers: - Ref: LambdaFunctionLayer Role: !GetAtt 'LambdaFunctionIAMRole.Arn' MemorySize: 256 Timeout: 60 VpcConfig: SecurityGroupIds: - Ref: LambdaFunctionSecurityGroup SubnetIds: - Ref: AS2VPCSubnet1 - Ref: AS2VPCSubnet2 DependsOn: - LambdaFunctionIAMRole - LambdaFunctionIAMPolicy LambdaFunction04ImageNotification: Type: AWS::Lambda::Function Properties: FunctionName: !Join - "_" - - "AS2_Automation_Windows_FN04_Image_Notification" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Handler: lambda_function.lambda_handler Code: S3Bucket: Ref: SourceS3Bucket S3Key: FN04_AS2_Windows_Automation_Image_Notification.zip Environment: Variables: NotificationARN: Ref: SNSTopic Runtime: python3.9 Role: !GetAtt 'LambdaFunctionIAMRole.Arn' Timeout: 30 StepFunction: Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: !Join - "_" - - "AS2_Automation_Windows_Scripted_App_Install" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" DefinitionString: Fn::Sub: |- { "Comment": "AS2 Windows Image Automation Step Function", "StartAt": "Create Image Builder", "States": { "Create Image Builder": { "Type": "Task", "Resource": "${LambdaFunction01CreateBuilder.Arn}", "ResultPath": "$", "Next": "Check Builder Status (Create)" }, "Check Builder Status (Create)": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:describeImageBuilders", "Parameters": { "Names.$": "States.Array($.AutomationParameters.ImageBuilderName)" }, "ResultPath": "$.BuilderStatus", "Next": "Is Builder Created and Running?" }, "Is Builder Created and Running?": { "Type": "Choice", "Choices": [ { "Variable": "$.BuilderStatus.ImageBuilders[0].State", "StringEquals": "STOPPED", "Next": "If Created and Stopped, Start Image Builder" }, { "Not": { "Variable": "$.BuilderStatus.ImageBuilders[0].State", "StringEquals": "RUNNING" }, "Next": "If Not Ready, Wait 3 Min" }, { "Not": { "Variable": "$.BuilderStatus.ImageBuilders[0].NetworkAccessConfiguration.EniPrivateIpAddress", "IsPresent": true }, "Next": "If Not Ready, Wait 3 Min" } ], "Default": "Stop Image Builder (Reboot)", "Comment": "Wait for Builder to enter RUNNING status." }, "Stop Image Builder (Reboot)": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:stopImageBuilder", "Parameters": { "Name.$": "$.AutomationParameters.ImageBuilderName" }, "ResultPath": "$.BuilderStatus", "Next": "Check Builder Status (Reboot)" }, "Check Builder Status (Reboot)": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:describeImageBuilders", "Parameters": { "Names.$": "States.Array($.AutomationParameters.ImageBuilderName)" }, "ResultPath": "$.BuilderStatus", "Next": "Is Builder Stopped?" }, "Is Builder Stopped?": { "Type": "Choice", "Choices": [ { "Variable": "$.BuilderStatus.ImageBuilders[0].State", "StringEquals": "STOPPED", "Next": "Start Image Builder (Reboot)" } ], "Default": "If Not Stopped, Wait 1 min", "Comment": "Wait for Builder to enter STOPPED status." }, "Start Image Builder (Reboot)": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:startImageBuilder", "Parameters": { "Name.$": "$.AutomationParameters.ImageBuilderName" }, "ResultPath": "$.BuilderStatus", "Next": "Check Builder Status (After Reboot)" }, "Check Builder Status (After Reboot)": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:describeImageBuilders", "Parameters": { "Names.$": "States.Array($.AutomationParameters.ImageBuilderName)" }, "ResultPath": "$.BuilderStatus", "Next": "Is Builder Running?" }, "Is Builder Running?": { "Type": "Choice", "Choices": [ { "Variable": "$.BuilderStatus.ImageBuilders[0].State", "StringEquals": "RUNNING", "Next": "Wait 2 Min" } ], "Default": "If Not Running, Wait 1 Min", "Comment": "Wait for Builder to enter RUNNING status." }, "If Not Running, Wait 1 Min": { "Type": "Wait", "Seconds": 60, "Next": "Check Builder Status (After Reboot)" }, "Wait 2 Min": { "Type": "Wait", "Seconds": 120, "Next": "Remote Software Install - Script", "Comment": "Wait for startup scripts to complete." }, "If Not Stopped, Wait 1 min": { "Type": "Wait", "Seconds": 60, "Next": "Check Builder Status (Reboot)" }, "If Created and Stopped, Start Image Builder": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:startImageBuilder", "Parameters": { "Name.$": "$.AutomationParameters.ImageBuilderName" }, "ResultPath": "$.BuilderStatus", "Next": "If Not Ready, Wait 3 Min" }, "If Not Ready, Wait 3 Min": { "Type": "Wait", "Seconds": 180, "Next": "Check Builder Status (Create)" }, "Remote Software Install - Script": { "Type": "Task", "Resource": "${LambdaFunction02ScriptedInstall.Arn}", "ResultPath": null, "Next": "Run Image Assistant" }, "Run Image Assistant": { "Type": "Task", "Resource": "${LambdaFunction03RunImageAssistant.Arn}", "ResultPath": "$.ImageStatus", "Next": "Check Image Status" }, "Check Image Status": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:describeImages", "Parameters": { "Names.$": "States.Array($.ImageStatus.Images[0].Name)" }, "ResultPath": "$.ImageStatus", "Next": "Is Image Ready?" }, "Is Image Ready?": { "Type": "Choice", "Choices": [ { "Variable": "$.ImageStatus.Images[0].State", "StringEquals": "AVAILABLE", "Next": "Wait 1 min" } ], "Default": "If Not Available, Wait 5 min", "Comment": "Wait for image to enter Available status." }, "If Not Available, Wait 5 min": { "Type": "Wait", "Seconds": 300, "Next": "Check Image Status" }, "Wait 1 min": { "Type": "Wait", "Seconds": 60, "Next": "Delete Builder?" }, "Delete Builder?": { "Type": "Choice", "Choices": [ { "Variable": "$.AutomationParameters.DeleteBuilder", "BooleanEquals": false, "Next": "Send Final Notification" } ], "Default": "Delete Image Builder" }, "Delete Image Builder": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:appstream:deleteImageBuilder", "Parameters": { "Name.$": "$.AutomationParameters.ImageBuilderName" }, "ResultPath": "$.BuilderStatus", "Next": "Send Final Notification" }, "Send Final Notification": { "Type": "Task", "Resource": "${LambdaFunction04ImageNotification.Arn}", "ResultPath": null, "End": true } } } RoleArn: !GetAtt 'StepFunctionIAMRole.Arn' StepFunctionEventRule: Type: AWS::Events::Rule Properties: Name: !Join - "_" - - "AS2_Automation_Windows_Failure_Notification" - !Select - 0 - !Split - "-" - !Select - 2 - !Split - "/" - !Ref "AWS::StackId" Description: "Rule to send notification to SNS topic when the AS2 Windows automation Step Function fails." EventPattern: source: - "aws.states" detail-type: - "Step Functions Execution Status Change" detail: status: - "FAILED" stateMachineArn: - !GetAtt 'StepFunction.Arn' Targets: - Arn: !Ref SNSTopic Id: "SNStopic" InputTransformer: InputPathsMap: "account": "$.account" "executionname": "$.detail.name" "input": "$.detail.input" "machine": "$.detail.stateMachineArn" "region": "$.region" "status": "$.detail.status" InputTemplate: | { "StepFunction" : , "Status" : , "Execution" : , "Input" : , "Account" : , "Region" : } Outputs: WorkShopS3BucketName: Description: The bucket in S3 to upload automation execution resources including installation installation packages. Value: Ref: WorkShopS3Bucket