AWSTemplateFormatVersion: "2010-09-09" Description: "Deploys the EC2 Autoscaling, LaunchConfig and Instance for Artifactory (qs-1qpmmjh5o)" Parameters: SmCertName: Description: Secret name created in AWS Secrets Manager, which contains the SSL certificate and certificate key. Default: '' Type: String LogicalId : Description : Logical Id of the MODULE Type: String PrivateSubnet2Id: Description: ID of the private subnet in Availability Zone 2 of your existing VPC (e.g., subnet-z0376dab). Type: AWS::EC2::Subnet::Id PrivateSubnet1Id: Description: ID of the private subnet in Availability Zone 1 of your existing VPC (e.g., subnet-z0376dab). Type: AWS::EC2::Subnet::Id MinScalingNodes: Type: Number MaxScalingNodes: Type: Number DeploymentTag: Type: String HostRole: Type: String ArtifactoryProduct: Description: JFrog Artifactory product you want to install into an AMI. AllowedValues: - JFrog-Artifactory-Pro - JFrog-Artifactory-Enterprise - JFrog-Container-Registry Default: JFrog-Artifactory-Enterprise Type: String QsS3BucketName: Type: String QsS3KeyPrefix: Type: String QsS3Uri: Type: String ArtifactoryLicensesSecretName: Type: String ArtifactoryServerName: Type: String ArtifactoryS3Bucket: Type: String DatabaseUrl: Type: String DatabaseDriver: Type: String DatabasePluginUrl: Type: String DatabasePlugin: Type: String DatabaseType: Type: String DatabaseUser: Type: String DatabasePassword: Type: String NoEcho: 'true' ArtifactoryPrimary: Type: String MasterKey: Type: String NoEcho: 'true' ExtraJavaOptions: Type: String ArtifactoryVersion: Type: String KeyPairName: Type: AWS::EC2::KeyPair::KeyName TargetGroupARN: Type: String SSLTargetGroupARN: Type: String InternalTargetGroupARN: Type: String HostProfile: Type: String SecurityGroups: Type: String InstanceType: Type: String PrimaryVolume: Type: String VolumeSize: Type: Number UserDataDirectory: Description: Directory to store Artifactory data. Can be used to store data (via symlink) in detachable volume Type: String Default: '/artifactory-user-data' # To populate additional mappings use the following with the desired --region # aws --region us-west-2 ec2 describe-images --owners amazon --filters 'Name=name,Values=amzn-ami-hvm-2018.03.0.20181129-x86_64-gp2' 'Name=state,Values=available' --output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId' Mappings: AWSAMIRegionMap: ap-northeast-1: CentOS7HVM: "ami-00a5245b4816c38e6" ap-northeast-2: CentOS7HVM: "ami-00dc207f8ba6dc919" ap-south-1: CentOS7HVM: "ami-0ad42f4f66f6c1cc9" ap-southeast-1: CentOS7HVM: "ami-05b3bcf7f311194b3" ap-southeast-2: CentOS7HVM: "ami-02fd0b06f06d93dfc" ca-central-1: CentOS7HVM: "ami-07423fb63ea0a0930" eu-central-1: CentOS7HVM: "ami-0cfbf4f6db41068ac" eu-west-1: CentOS7HVM: "ami-08935252a36e25f85" sa-east-1: CentOS7HVM: "ami-05145e0b28ad8e0b2" us-east-1: CentOS7HVM: "ami-0affd4508a5d2481b" us-east-2: CentOS7HVM: "ami-01e36b7901e884a10" us-west-1: CentOS7HVM: "ami-098f55b4287a885ba" us-west-2: CentOS7HVM: "ami-0bc06212a56393ee1" ArtifactoryProductMap: JFrog-Container-Registry: "7153": "Jcr7153" flavor: "jcr" haEabled: false product: "jcr" JFrog-Artifactory-Enterprise: "7153": "Artifactory7153" flavor: "pro" haEabled: true product: "artifactory" JFrog-Artifactory-Pro: "7153": "Artifactory7153" flavor: "pro" haEabled: false product: "artifactory" Conditions: IsSecondary: !Equals [!Ref ArtifactoryPrimary, 'false'] SmCertNameExists: !Not [!Equals [!Ref 'SmCertName', '']] Resources: ArtifactoryScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: LaunchConfigurationName: !Ref ArtifactoryLaunchConfiguration VPCZoneIdentifier: - !Ref PrivateSubnet1Id - !Ref PrivateSubnet2Id MinSize: !Ref MinScalingNodes MaxSize: !Ref MaxScalingNodes Cooldown: '300' DesiredCapacity: !Ref MinScalingNodes TargetGroupARNs: - !Ref TargetGroupARN - !Ref SSLTargetGroupARN - !Ref InternalTargetGroupARN HealthCheckType: ELB HealthCheckGracePeriod: 1800 Tags: - Key: Name Value: !Ref DeploymentTag PropagateAtLaunch: true - Key: ArtifactoryVersion Value: !Ref ArtifactoryVersion PropagateAtLaunch: true TerminationPolicies: - OldestInstance - Default CreationPolicy: ResourceSignal: Count: !Ref MinScalingNodes Timeout: PT60M ArtifactoryLaunchConfiguration: Type: AWS::AutoScaling::LaunchConfiguration Metadata: AWS::CloudFormation::Authentication: S3AccessCreds: type: S3 roleName: - !Ref HostRole # !Ref ArtifactoryHostRole buckets: - !Ref QsS3BucketName AWS::CloudFormation::Init: configSets: jfrog_ami_setup: - "config-cloudwatch" - "config-ansible-art-ami" - "config-artifactory-primary" - "secure-cert" - "secure-artifactory" artifactory_install: - "config-cloudwatch" - "config-artifactory-primary" - "secure-cert" - "secure-artifactory" config-cloudwatch: files: /root/cloudwatch.conf: content: | [general] state_file = /var/awslogs/state/agent-state [/var/log/messages] file = /var/log/messages log_group_name = /artifactory/instances/{instance_id} log_stream_name = /var/log/messages/ datetime_format = %b %d %H:%M:%S [/var/log/jfrog-ami-setup.log] file = /var/log/messages log_group_name = /artifactory/instances/{instance_id} log_stream_name = /var/log/jfrog-ami-setup.log datetime_format = %b %d %H:%M:%S [/var/log/jfrog-ami-artifactory.log] file = /var/log/messages log_group_name = /artifactory/instances/{instance_id} log_stream_name = /var/log/jfrog-ami-artifactory.log datetime_format = %b %d %H:%M:%S mode: "0400" config-ansible-art-ami: files: /root/.jfrog_ami/jfrog-ami-setup.yml: content: !Sub | # Base install for JFrogAMIInstance - import_playbook: artifactory-ami.yml vars: ami_creation: false artifactory_flavour: "pro" artifactory_ha_enabled: false artifactory_tar: "https://releases.jfrog.io/artifactory/artifactory-pro/org/artifactory/pro/jfrog-artifactory-pro/${ArtifactoryVersion}/jfrog-artifactory-pro-${ArtifactoryVersion}-linux.tar.gz" artifactory_version: ${ArtifactoryVersion} db_download_url: "https://jdbc.postgresql.org/download/postgresql-42.2.12.jar" db_type: "postgresql" db_driver: "org.postgresql.Driver" mode: "0400" config-artifactory-primary: files: /root/attach_volume.sh: content: !Sub | #!/usr/bin/env bash IS_PRIMARY="${ArtifactoryPrimary}" if [[ $IS_PRIMARY != "true" ]]; then echo 'Not primary node. Skipping EBS volume attachment.' lsblk # debug exit 0 fi echo "Using primary volume ID ${PrimaryVolume}" VOLUME_ID="${PrimaryVolume}" echo "VOLUME_ID: $VOLUME_ID" if [[ -z "$VOLUME_ID" ]]; then echo 'Invalid $VOLUME_ID' exit 1 fi # Get instance id from AWS INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) # Attach the volume created by another CFT # the device name should become /dev/nvme1n1 # See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html echo "Attaching volume $VOLUME_ID to instance $INSTANCE_ID" /var/awslogs/bin/aws ec2 attach-volume --volume-id $VOLUME_ID --instance-id $INSTANCE_ID --device /dev/xvdf --region ${AWS::Region} echo "Wait for volume $VOLUME_ID to attach" sleep 30 # Give volume time to attach lsblk # debug mode: "0770" /root/.jfrog_ami/artifactory.yml: content: !Sub - | # Base install for Artifactory - import_playbook: site-artifactory.yml vars: artifactory_product: ${product} artifactory_flavour: ${flavor} artifactory_ha_enabled: ${ha_enabled} artifactory_is_primary: ${ArtifactoryPrimary} artifactory_server_name: ${ArtifactoryServerName} server_name: ${ArtifactoryServerName}.fill_me_domain use_custom_data_directory: true custom_data_directory: "${UserDataDirectory}" s3_region: ${AWS::Region} s3_bucket: ${ArtifactoryS3Bucket} certificate: fill_me_certificate certificate_key: fill_me_key certificate_domain: fill_me_domain enable_ssl: ${EnableSSL} ssl_dir: /etc/pki/tls/certs db_type: ${DatabaseType} db_driver: ${DatabaseDriver} db_url: ${DatabaseUrl} db_user: ${DatabaseUser} db_password: ${DatabasePassword} master_key: ${MasterKey} join_key: ${MasterKey} extra_java_opts: ${ExtraJavaOptions} artifactory_version: ${ArtifactoryVersion} artifactory_keystore: path: /opt/jfrog/artifactory/app/third-party/java/lib/security/cacerts default_password: changeit new_keystore_pass: ${DatabasePassword} artifactory_java_db_drivers: - name: ${DatabasePlugin} url: ${DatabasePluginUrl} owner: artifactory group: artifactory product_id: 'CloudFormation_QS_EC2/1.0.0' - flavor: !FindInMap [ArtifactoryProductMap, !Ref ArtifactoryProduct, flavor] ha_enabled: !FindInMap [ArtifactoryProductMap, !Ref ArtifactoryProduct, haEabled] product: !FindInMap [ArtifactoryProductMap, !Ref ArtifactoryProduct, product] # Certificate: !If [SmCertNameExists, !Sub '{{resolve:secretsmanager:${SmCertName}:SecretString:Certificate}}', ''] # CertificateKey: !If [SmCertNameExists, !Sub '{{resolve:secretsmanager:${SmCertName}:SecretString:CertificateKey}}', ''] # CertificateDomain: !If [SmCertNameExists, !Sub '{{resolve:secretsmanager:${SmCertName}:SecretString:CertificateDomain}}', ''] EnableSSL: !If [SmCertNameExists, true, false] mode: "0400" /root/.vault_pass.txt: content: !Sub | ${DatabasePassword} mode: "0400" /root/.secureit.sh: content: ansible-vault encrypt /root/.jfrog_ami/artifactory.yml --vault-id /root/.vault_pass.txt mode: "0770" /root/.securecert.sh: content: !Sub - | sm=$(aws secretsmanager get-secret-value --secret-id ${SMCertName} --region ${AWS::Region} | jq -r '(.SecretString | fromjson)') domain=$(echo $sm | jq -r '(.CertificateDomain)') certificate=$(echo $sm | jq -r '(.Certificate)') key=$(echo $sm | jq -r '(.CertificateKey)') sed -i "s/fill_me_domain/$domain/g" /root/.jfrog_ami/artifactory.yml sed -i "s~fill_me_certificate~$certificate~g" /root/.jfrog_ami/artifactory.yml sed -i "s~fill_me_key~$key~g" /root/.jfrog_ami/artifactory.yml - SMCertName : !If [SmCertNameExists, !Ref SmCertName , ''] mode: "0770" secure-cert: commands: securing-certificates: command: '/root/.securecert.sh' ignoreErrors: 'true' secure-artifactory: commands: secure-ansible-playbook: command: '/root/.secureit.sh' ignoreErrors: 'false' Properties: KeyName: !Ref KeyPairName IamInstanceProfile: !Ref HostProfile ImageId: !FindInMap - AWSAMIRegionMap - !Ref AWS::Region - 'CentOS7HVM' SecurityGroups: - !Ref SecurityGroups BlockDeviceMappings: !If - IsSecondary - - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref VolumeSize VolumeType: gp2 DeleteOnTermination: true Encrypted: true - !Ref AWS::NoValue InstanceType: !Ref InstanceType UserData: Fn::Base64: !Sub | #!/bin/bash -x #CFN Functions function cfn_fail { cfn-signal -e 1 --stack ${AWS::StackName} --region ${AWS::Region} --resource ${LogicalId}ArtifactoryScalingGroup exit 1 } function cfn_success { cfn-signal -e 0 --stack ${AWS::StackName} --region ${AWS::Region} --resource ${LogicalId}ArtifactoryScalingGroup exit 0 } S3URI=${QsS3Uri} # Update OS yum update -y # Install git yum install -y epel-release git policycoreutils-python yum update --security -y 2>&1 | tee /var/log/userdata.yum_security_update.log yum install -y jq python3 libselinux-python3 echo $PATH PATH=/opt/aws/bin:$PATH echo $PATH # Create virtual env and activate python3 -m venv ~/venv --system-site-packages source ~/venv/bin/activate pip install --upgrade pip pip install wheel # Install Cloudformation helper scripts pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz 2>&1 | tee /var/log/userdata.aws_cfn_bootstrap_install.log pip install awscli 2>&1 | tee /var/log/userdata.awscli_install.log pip install ansible 2>&1 | tee /var/log/userdata.ansible_install.log mkdir ~/.jfrog_ami aws s3 --region ${AWS::Region} sync s3://${QsS3BucketName}/${QsS3KeyPrefix}cloudInstallerScripts/ ~/.jfrog_ami/ || cfn_fail setsebool httpd_can_network_connect 1 -P # CentOS cloned virtual machines do not create a new machine id # https://www.thegeekdiary.com/centos-rhel-7-how-to-change-the-machine-id/ rm -f /etc/machine-id systemd-machine-id-setup cfn-init -v --stack ${AWS::StackName} --resource ${LogicalId}ArtifactoryLaunchConfiguration --configsets jfrog_ami_setup --region ${AWS::Region} || cfn_fail # Setup CloudWatch Agent curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O chmod +x ./awslogs-agent-setup.py ./awslogs-agent-setup.py -n -r ${AWS::Region} -c /root/cloudwatch.conf 2>&1 | tee /var/log/userdata.cloudwatch_agent_install.log /root/attach_volume.sh || cfn_fail ansible-galaxy collection install community.general ansible.posix aws secretsmanager get-secret-value --secret-id ${ArtifactoryLicensesSecretName} --region ${AWS::Region} | jq -r '{"artifactory_licenses":(.SecretString | fromjson )}' > ~/.jfrog_ami/licenses.json || cfn_fail ansible-playbook /root/.jfrog_ami/jfrog-ami-setup.yml --vault-id /root/.vault_pass.txt 2>&1 | tee /var/log/jfrog-ami-setup.log || cfn_fail ansible-playbook /root/.jfrog_ami/artifactory.yml -e "@~/.jfrog_ami/licenses.json" --vault-id /root/.vault_pass.txt 2>&1 | tee /var/log/jfrog-ami-artifactory.log || cfn_fail rm -rf /root/.secureit.sh cfn_success &> /var/log/cfn_success.log cfn_success || cfn_fail