AWSTemplateFormatVersion: '2010-09-09' Description: 1Click-HPC Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "" Parameters: - AdminPassword - VpcId - PublicSubnetAId - PublicSubnetBId - PrivateSubnetAId - FSx - AD ParameterLabels: AdminPassword: default: 'Password:' VpcId: default: 'VPC:' PublicSubnetAId: default: 'Public Subnet 1:' PublicSubnetBId: default: 'Public Subnet 2:' PrivateSubnetAId: default: 'Private Subnet:' FSx: default: 'FSx:' AD: default: 'Active Directory:' Parameters: AdminPassword: Description: 'Please, enter the Admin password for your Active Direcotry.This will also be the "ec2-user" password. (Min 8 chars, three of: lowercase, uppercase, number and symbols)' Type: String MinLength: '8' MaxLength: '255' AllowedPattern: (?=^.{8,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]))^.* NoEcho: 'true' VpcId: Description: 'Please, enter your VPC ID, or just leave "AUTO" if you want to re-use an existing one.' Type: String AllowedPattern: ^(AUTO|vpc-[0-9a-z]+)$ Default: AUTO PublicSubnetAId: Description: 'Please, enter the ID of the Public Subnet you wish to use, or just leave "AUTO" if you want to re-use an existing one.' Type: String AllowedPattern: ^(AUTO|subnet-[0-9a-z]+)$ Default : AUTO PublicSubnetBId: Description: 'Please, enter another ID of the Public Subnet you wish to use, or just leave "AUTO" if you want to re-use an existing one.' Type: String AllowedPattern: ^(AUTO|subnet-[0-9a-z]+)$ Default : AUTO PrivateSubnetAId: Description: 'Please, enter the ID of the Private Subnet you wish to use, or just leave "AUTO" if you want to re-use an existing one.' Type: String AllowedPattern: ^(AUTO|subnet-[0-9a-z]+)$ Default : AUTO FSx: Description: 'Please, enter your FSx ID, or just leave "AUTO" if you want to re-use an existing one.' Type: String AllowedPattern: ^(AUTO|fs-[0-9a-z]+)$ Default : AUTO AD: Description: 'Please, enter your ldaps://url of your Active Directory, or just leave "AUTO" if you want to create a new Active Directory. Note: ReadOnlyUser is mandatory' Type: String AllowedPattern: ^(AUTO|ldaps?://.+)$ Default : AUTO GitHubUrl: Description: 'Location of installation files' Type: String AllowedPattern: ^https://.*$ Default: https://github.com/aws-samples/1click-hpc Conditions: CreateVpc: !Equals [!Ref VpcId, AUTO] CreateAD: !Equals [!Ref AD, AUTO] Resources: HPCNetworkStack: Type: AWS::CloudFormation::Stack Condition: CreateVpc Properties: TemplateURL: https://enginframe.s3.amazonaws.com/HPC-Networking.yaml #lest use privateA , privateA, and privateB #be consistent with parameter names across CF templates HPCADStack: Type: AWS::CloudFormation::Stack Condition: CreateAD Properties: TemplateURL: https://enginframe.s3.amazonaws.com/HPC-AD.yaml Parameters: AdminPassword: !Ref AdminPassword Vpc: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.VPC, !Ref VpcId] PrivateSubnetOne: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PrivateSubnetA, !Ref PrivateSubnetAId] PublicSubnetOne: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetA, !Ref PublicSubnetAId] PublicSubnetTwo: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetB, !Ref PublicSubnetBId] Cloud9Role: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com - ssm.amazonaws.com - s3.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess Path: "/" Cloud9InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - Ref: Cloud9Role LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: Fn::Join: - '' - - LambdaExecutionRole- - Ref: AWS::Region PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:DescribeStackEvents - cloudformation:DescribeStackResource - cloudformation:DescribeStackResources - ec2:DescribeInstances - ec2:AssociateIamInstanceProfile - ec2:ModifyInstanceAttribute - ec2:ReplaceIamInstanceProfileAssociation - iam:ListInstanceProfiles - iam:PassRole Resource: "*" Cloud9BootstrapAssociation: Type: AWS::SSM::Association Properties: Name: !Ref Cloud9SSMDocument OutputLocation: S3Location: OutputS3BucketName: !Ref Cloud9OutputBucket OutputS3KeyPrefix: bootstrapoutput Targets: - Key: tag:SSMBootstrap Values: - !Sub - ${AWS::StackName}-${RANDOM} - RANDOM: !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]] ALBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Allow https and http' VpcId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.VPC, !Ref VpcId] SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 PCAdditionalSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Allow 8443 and 80 from the ALB' VpcId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.VPC, !Ref VpcId] SecurityGroupIngress: - IpProtocol: tcp FromPort: 8443 ToPort: 8443 SourceSecurityGroupId: !Ref ALBSecurityGroup - IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !Ref ALBSecurityGroup - IpProtocol: tcp FromPort: 389 ToPort: 389 SourceSecurityGroupId: !Ref ALBSecurityGroup - IpProtocol: udp FromPort: 389 ToPort: 389 SourceSecurityGroupId: !Ref ALBSecurityGroup DatabaseParameterGroup: Type: 'AWS::RDS::DBClusterParameterGroup' Properties: Description: Cluster parameter group for aurora-mysql5.7 Family: aurora-mysql5.7 Parameters: require_secure_transport: 'ON' innodb_lock_wait_timeout: '900' Tags: - Key: 'parallel-cluster:accounting' Value: rds-parameter-group - Key: 'parallel-cluster:accounting:scheduler' Value: slurm - Key: 'parallel-cluster:accounting:version' Value: '1.0' SlurmDBSubnetGroup: Type: 'AWS::RDS::DBSubnetGroup' Properties: DBSubnetGroupDescription: SubnetGroup SubnetIds: - !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetA, !Ref PublicSubnetAId] - !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetB, !Ref PublicSubnetBId] SlurmDBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Database SG' VpcId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.VPC, !Ref VpcId] SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: '-1' DatabaseSecurityGroupInboundRule: Type: 'AWS::EC2::SecurityGroupIngress' Properties: IpProtocol: tcp Description: Allow incoming connections from client security group FromPort: !GetAtt - SlurmDB - Endpoint.Port GroupId: !GetAtt - SlurmDBSecurityGroup - GroupId SourceSecurityGroupId: !GetAtt - DatabaseClientSecurityGroup - GroupId ToPort: !GetAtt - SlurmDB - Endpoint.Port SlurmDB: Type: 'AWS::RDS::DBInstance' Properties: DBInstanceIdentifier: !Sub '${AWS::StackName}-SlurmDB' DBInstanceClass: 'db.t4g.micro' MultiAZ: false AllocatedStorage: '20' StorageType: gp3 MaxAllocatedStorage: 2000 Engine: MySQL EngineVersion: 8.0.28 MasterUsername: 'admin' MasterUserPassword: !Sub '{{resolve:secretsmanager:${Password}:SecretString:::}}' DBSubnetGroupName: !Ref SlurmDBSubnetGroup VPCSecurityGroups: - !Ref 'SlurmDBSecurityGroup' DatabaseClientSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Security Group to allow connection to DB Tags: - Key: 'parallel-cluster:accounting' Value: client-security-group - Key: 'parallel-cluster:accounting:scheduler' Value: slurm - Key: 'parallel-cluster:accounting:version' Value: '1.0' VpcId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.VPC, !Ref VpcId] DatabaseClientSecurityGroupOutboundRule: Type: 'AWS::EC2::SecurityGroupEgress' Properties: GroupId: !GetAtt - DatabaseClientSecurityGroup - GroupId IpProtocol: tcp Description: Allow incoming connections from PCluster DestinationSecurityGroupId: !GetAtt - SlurmDBSecurityGroup - GroupId FromPort: !GetAtt - SlurmDB - Endpoint.Port ToPort: !GetAtt - SlurmDB - Endpoint.Port Cloud9: Type: 'AWS::Cloud9::EnvironmentEC2' DependsOn: - Cloud9BootstrapAssociation - SlurmDB Properties: Name: !Ref 'AWS::StackName' Description: 'Cloud9 IDE for AWS ParallelCluster' AutomaticStopTimeMinutes: 120 ImageId: amazonlinux-2-x86_64 InstanceType: c5.xlarge SubnetId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetA, !Ref PublicSubnetAId] Tags: - Key: SSMBootstrap Value: !Sub - ${AWS::StackName}-${RANDOM} - RANDOM: !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]] Password: Type: 'AWS::SecretsManager::Secret' Properties: Name: !Sub 'hpc-1click-${AWS::StackName}' Description: This the password used for RDS and ec2-user SecretString: !Ref AdminPassword Cloud9SSMDocument: # Clones 1click repo, adds environment variables, and uploads to s3 bucket. Then runs "scripts/Cloud9-Bootstrap.sh" to setup this cloud9 node Type: AWS::SSM::Document Properties: DocumentType: Command Content: schemaVersion: '2.2' description: Bootstrap Cloud9 Instance mainSteps: - action: aws:runShellScript name: C9bootstrap inputs: runCommand: - "#!/bin/bash" - if [[ -f /home/ec2-user/environment/lambda.log ]]; then exit 1; fi - set -x - exec >/home/ec2-user/environment/lambda.log; exec 2>&1 - echo LANG=en_US.utf-8 >> /etc/environment - echo LC_ALL=en_US.UTF-8 >> /etc/environment - cd /home/ec2-user/environment - !Sub git clone ${GitHubUrl} - !Sub echo "export AWS_DEFAULT_REGION=${AWS::Region}" >> cluster_env - !Sub echo "export AWS_REGION_NAME=${AWS::Region}" >> cluster_env - !Sub echo "export S3_BUCKET=${Cloud9OutputBucket}" >> cluster_env - !Sub echo "export FSX_ID=${FSx}" >> cluster_env - !Sub - echo "export PRIVATE_SUBNET_ID=${FinalPrivateSubnetAId}" >> cluster_env - FinalPrivateSubnetAId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PrivateSubnetA, !Ref PrivateSubnetAId] - !Sub echo "export CLUSTER_NAME=${AWS::StackName}" >> cluster_env - !Sub echo "export ADDITIONAL_SG=${PCAdditionalSecurityGroup}" >> cluster_env - !Sub echo "export DB_SG=${DatabaseClientSecurityGroup}" >> cluster_env - !Sub echo "export SECRET_ARN=${Password}" >> cluster_env - !Sub echo "export WAIT_HANDLE='${WaitHandle}'" >> cluster_env - !Sub - echo "export SLURM_DB_ENDPOINT='${DB_ENDPOINT}'" >> cluster_env - DB_ENDPOINT: !GetAtt SlurmDB.Endpoint.Address - !Sub - echo "export ALB_PUBLIC_DNS_NAME='${ALB}'" >> cluster_env - ALB: !GetAtt ApplicationLoadBalancer.DNSName - !Sub - echo "export NLB_PUBLIC_DNS_NAME='${LDAP}'" >> cluster_env - LDAP: !If [CreateAD, !GetAtt HPCADStack.Outputs.DomainAddrLdaps, !Ref AD] - !Sub - echo "export KEY_PAIR=\"${AWS::StackName}-${RANDOM}\"" >> cluster_env - RANDOM: !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]] - !Sub echo "export POST_INSTALL=\"s3://${Cloud9OutputBucket}/1click-hpc/scripts/post.install.sh\"" >> cluster_env - !Sub sudo -H -u ec2-user bash -c "aws s3 cp --quiet --recursive 1click-hpc \"s3://${Cloud9OutputBucket}/1click-hpc\" --region ${AWS::Region}" - chmod -x /home/ec2-user/environment/1click-hpc/scripts/Cloud9-Bootstrap.sh - sudo -H -u ec2-user bash -c "bash /home/ec2-user/environment/1click-hpc/scripts/Cloud9-Bootstrap.sh" #dummy, just wait if exist otherwise let Cloud9BootstrapInstanceLambda go NLBReady: Type: AWS::CloudFormation::WaitConditionHandle Metadata: IsThereNLB: !If [CreateAD, !Ref HPCADStack, NONE] Cloud9BootstrapInstanceLambda: Type: Custom::Cloud9BootstrapInstanceLambda DependsOn: - LambdaExecutionRole - ApplicationLoadBalancer - NLBReady - LogGroupCloud9BootstrapInstanceLambdaFunction Properties: ServiceToken: Fn::GetAtt: - Cloud9BootstrapInstanceLambdaFunction - Arn REGION: Ref: AWS::Region StackName: Ref: AWS::StackName EnvironmentId: Ref: Cloud9 LabIdeInstanceProfileName: Ref: Cloud9InstanceProfile LabIdeInstanceProfileArn: Fn::GetAtt: - Cloud9InstanceProfile - Arn Cloud9BootstrapInstanceLambdaFunction: # Attaches Instance profile to cloud9 instance Type: AWS::Lambda::Function Properties: Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: python3.9 MemorySize: 256 Timeout: 600 Code: ZipFile: | from __future__ import print_function import boto3 import json import os import time import traceback import cfnresponse def lambda_handler(event, context): # logger.info('event: {}'.format(event)) # logger.info('context: {}'.format(context)) responseData = {} if event['RequestType'] == 'Create': try: # Open AWS clients ec2 = boto3.client('ec2') # Get the InstanceId of the Cloud9 IDE instance = ec2.describe_instances(Filters=[{'Name': 'tag:Name','Values': ['aws-cloud9-'+event['ResourceProperties']['StackName']+'-'+event['ResourceProperties']['EnvironmentId']]}])['Reservations'][0]['Instances'][0] # logger.info('instance: {}'.format(instance)) # Create the IamInstanceProfile request object iam_instance_profile = { 'Arn': event['ResourceProperties']['LabIdeInstanceProfileArn'], 'Name': event['ResourceProperties']['LabIdeInstanceProfileName'] } # logger.info('iam_instance_profile: {}'.format(iam_instance_profile)) # Wait for Instance to become ready before adding Role instance_state = instance['State']['Name'] # logger.info('instance_state: {}'.format(instance_state)) while instance_state != 'running': time.sleep(5) instance_state = ec2.describe_instances(InstanceIds=[instance['InstanceId']]) # logger.info('instance_state: {}'.format(instance_state)) # attach instance profile response = ec2.associate_iam_instance_profile(IamInstanceProfile=iam_instance_profile, InstanceId=instance['InstanceId']) # logger.info('response - associate_iam_instance_profile: {}'.format(response)) r_ec2 = boto3.resource('ec2') responseData = {'Success': 'Started bootstrapping for instance: '+instance['InstanceId']} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') except Exception as e: # logger.error(e, exc_info=True) responseData = {'Error': traceback.format_exc(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') else: cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) LogGroupCloud9BootstrapInstanceLambdaFunction: # Creates log group for the Cloud9BootstrapInstanceLambdaFunction Type: AWS::Logs::LogGroup DeletionPolicy: Delete Properties: LogGroupName: !Sub /aws/lambda/${Cloud9BootstrapInstanceLambdaFunction} RetentionInDays: 7 WaitHandle: # Creates a wait handle, that the Cloud9Bootstrap script writes success to if the cluster creation and setup successful Type: AWS::CloudFormation::WaitConditionHandle WaitCondition: # Waits for the handle to say success Type: AWS::CloudFormation::WaitCondition DependsOn: - Cloud9BootstrapInstanceLambda Properties: Handle: !Ref WaitHandle Timeout: '3600' LambdaGetHeadNodeIP: # Wait until headnode and cluster setup completed Type: AWS::Lambda::Function Properties: Code: ZipFile: | import logging import boto3 import cfnresponse LOGGER = logging.getLogger() LOGGER.setLevel(logging.INFO) CFN = boto3.resource('cloudformation') def lambda_handler(event, context): if event['RequestType'] in ('Create', 'Update'): try: stack = CFN.Stack(event['ResourceProperties']['StackName']) outputs = { output['OutputKey']: output['OutputValue'] for output in stack.outputs } except Exception as error: LOGGER.exception(error) cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=str(error)) else: cfnresponse.send(event, context, cfnresponse.SUCCESS, outputs) else: cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: python3.7 Timeout: 10 MemorySize: 128 LogGroupLambdaGetHeadNodeIP: Type: AWS::Logs::LogGroup DeletionPolicy: Delete Properties: LogGroupName: !Sub /aws/lambda/${LambdaGetHeadNodeIP} RetentionInDays: 7 HeadNodeIP: # After cluster created (previous lambda reports success), gets HeadNode IP Type: Custom::HeadNodeIP DependsOn: - WaitCondition - LogGroupLambdaGetHeadNodeIP Properties: ServiceToken: !GetAtt LambdaGetHeadNodeIP.Arn StackName: !Sub 'hpc-1click-${AWS::StackName}' LowerCaseLambda: Type: 'AWS::Lambda::Function' Properties: Description: Returns the lowercase version of a string MemorySize: 256 Runtime: python3.8 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 30 Code: ZipFile: | import cfnresponse def lambda_handler(event, context): output = event['ResourceProperties'].get('InputString', '').lower() responseData = {'OutputString': output} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) LogGroupLowerCaseLambda: Type: AWS::Logs::LogGroup DeletionPolicy: Delete Properties: LogGroupName: !Sub /aws/lambda/${LowerCaseLambda} RetentionInDays: 7 S3BucketName: Type: Custom::Lowercase DependsOn: LogGroupLowerCaseLambda Properties: ServiceToken: !GetAtt LowerCaseLambda.Arn InputString: !Sub - ${AWS::StackName}-${RANDOM} - RANDOM: !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]] Cloud9OutputBucket: # Creates Bucket to hold postinstall scripts - need to retain bucket after delete, because bucket needs to be empty to successfully delete Type: AWS::S3::Bucket DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: BucketName: !Join - '' - - !GetAtt S3BucketName.OutputString LBInit: # Sets up Application Load Balancer, adding certificates etc. Type: Custom::LBInit DependsOn: - LogGroupLBInitLambda Properties: ServiceToken: !GetAtt LBInitLambda.Arn DNSName: !GetAtt ApplicationLoadBalancer.DNSName LogGroupLBInitLambda: Type: AWS::Logs::LogGroup DependsOn: LBInitLambda DeletionPolicy: Delete Properties: LogGroupName: !Sub /aws/lambda/${LBInitLambda} RetentionInDays: 7 LBInitLambda: # Create ALB certificate Type: AWS::Lambda::Function Properties: Description: Create ALB Certificate Timeout: 300 Runtime: python3.7 Handler: index.handler Role: !GetAtt 'LBInitRole.Arn' Code: ZipFile: | import boto3 import os import subprocess import time import cfnresponse from botocore.exceptions import ClientError def handler(event, context): print(event) data = {} try: if event['RequestType'] == 'Create': IamCertificateArn = create_certificate(event) data['IamCertificateArn'] = IamCertificateArn elif event['RequestType'] == 'Delete': delete_certificate(event) cfnresponse.send(event, context, cfnresponse.SUCCESS, data) except ClientError as e: data['ClientErrorCode'] = e.response['Error']['Code'] data['ClientErrorMessage'] = e.response['Error']['Message'] cfnresponse.send(event, context, cfnresponse.FAILED, data) except Exception as e: data['Exception'] = str(e) cfnresponse.send(event, context, cfnresponse.FAILED, data) def create_certificate(event): DNSName = event['ResourceProperties']['DNSName'] os.chdir('/tmp') config = open('/tmp/openssl.cnf', 'w+') config.write('[req]\nprompt=no\ndistinguished_name=enginframe\nx509_extensions=v3_req\n') config.write('[enginframe]\nC=US\nST=WA\nL=Seattle\nO=AWS WWSO\nOU=HPC\nCN=EnginFrame\n') config.write('[v3_req]\nkeyUsage=keyEncipherment,dataEncipherment,digitalSignature\nextendedKeyUsage=serverAuth\nsubjectAltName=@alt_names\n') config.write('[alt_names]\nDNS.1={}\n'.format(DNSName)) config.close() os.environ['RANDFILE']='/tmp/.rnd' subprocess.run([ 'openssl', 'req', '-new', '-x509', '-nodes', '-newkey', 'rsa:2048', '-days', '3650', '-keyout', '/tmp/key.pem', '-out', '/tmp/crt.pem', '-config', '/tmp/openssl.cnf' ]) os.remove('/tmp/openssl.cnf') keyfile = open('/tmp/key.pem', 'r') key = keyfile.read() keyfile.close() os.remove('/tmp/key.pem') crtfile = open('/tmp/crt.pem', 'r') crt = crtfile.read() crtfile.close() os.remove('/tmp/crt.pem') iam = boto3.client('iam') response = iam.upload_server_certificate( ServerCertificateName=DNSName, CertificateBody=crt, PrivateKey=key ) time.sleep(10) return response['ServerCertificateMetadata']['Arn'] def delete_certificate(event): time.sleep(60) DNSName = event['ResourceProperties']['DNSName'] iam = boto3.client('iam') response = iam.delete_server_certificate(ServerCertificateName=DNSName) LBInitRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - !Sub 'lambda.${AWS::URLSuffix}' ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: EFLoadBalancer PolicyDocument: Version: '2012-10-17' Statement: - Sid: iam Effect: Allow Action: - iam:UploadServerCertificate - iam:DeleteServerCertificate Resource: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:server-certificate/${ApplicationLoadBalancer.DNSName}' ApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}' Subnets: - !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetA, !Ref PublicSubnetAId] - !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.PublicSubnetB, !Ref PublicSubnetBId] SecurityGroups: - !Ref ALBSecurityGroup LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds Value: 180 HTTPSListener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 443 Protocol: "HTTPS" SslPolicy: "ELBSecurityPolicy-2016-08" Certificates: - CertificateArn: !GetAtt LBInit.IamCertificateArn DefaultActions: - Order: 1 TargetGroupArn: !Ref TargetGroupEF Type: "forward" HTTPListener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 80 Protocol: "HTTP" DefaultActions: - Order: 1 RedirectConfig: Protocol: "HTTPS" Port: "443" Host: "#{host}" Path: "/#{path}" Query: "#{query}" StatusCode: "HTTP_301" Type: "redirect" TargetGroupEF: Type: AWS::ElasticLoadBalancingV2::TargetGroup DependsOn: - HeadNodeIP Properties: Name: !Sub '${AWS::StackName}-EF' VpcId: !If [CreateVpc, !GetAtt HPCNetworkStack.Outputs.VPC, !Ref VpcId] Port: 8443 Protocol: HTTPS Targets: - Id: !Sub '${HeadNodeIP.HeadNodePrivateIP}' TargetType: ip Outputs: Cloud9URL: Description: Cloud9 Environment Value: !Sub 'https://${AWS::Region}.console.aws.amazon.com/cloud9/ide/${Cloud9}' WebURL: Description: "EnginFrame HPC Portal" Value: !Sub - 'https://${ALB}/' - ALB: !GetAtt ApplicationLoadBalancer.DNSName