diff --git a/upp-rosette-ras-provisioner/Dockerfile b/upp-rosette-ras-provisioner/Dockerfile new file mode 100644 index 0000000..92f387c --- /dev/null +++ b/upp-rosette-ras-provisioner/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:20.04 + +COPY cloudformation /cloudformation/ +COPY sh/* /usr/local/bin/ +COPY config/* /config/ + +RUN chmod +x /usr/local/bin/* + +RUN apt-get update && apt-get install -y \ + less \ + zip \ + curl \ + wget \ + jq \ + vim \ + && rm -rf /var/lib/apt/lists/* \ + && get-latest-awscli2 diff --git a/upp-rosette-ras-provisioner/Makefile b/upp-rosette-ras-provisioner/Makefile new file mode 100644 index 0000000..2de94e8 --- /dev/null +++ b/upp-rosette-ras-provisioner/Makefile @@ -0,0 +1,31 @@ +.PHONY: rosette-provisioner staging-rosette-ras destroy-staging-rosette-ras prod-rosette-ras destroy-prod-rosette-ras help + +help: ## Show this help. + @sed -ne '/@sed/!s/##//p' $(MAKEFILE_LIST) + +rosette-ras-provisioner: ## Build provisioner + @docker build -t rosette-ras-provisioner:local . + +staging-rosette-ras: ## Deploy staging rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-ras-provisioner:local provision.sh staging + +prod-rosette-ras: ## Deploy prod rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-ras-provisioner:local provision.sh prod + +destroy-staging-rosette-ras: ## Destroys staging rosette steack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-ras-provisioner:local destroy.sh staging + +destroy-prod-rosette-ras: ## Destroys prod rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-ras-provisioner:local destroy.sh prod diff --git a/upp-rosette-ras-provisioner/README.md b/upp-rosette-ras-provisioner/README.md new file mode 100644 index 0000000..6e119bb --- /dev/null +++ b/upp-rosette-ras-provisioner/README.md @@ -0,0 +1,57 @@ +# upp-rosette-ras-provisioner + +## Description + +This repository contains the files required to provision a Rosette RAS - PROD and STAGING environment + +## Prerequisites + +1. [Install docker](https://docs.docker.com/engine/installation/) locally + +## Building the Docker image + +``` +make rosette-ras-provisioner +``` + +## Provisioning a new instance + +Here are the steps for provisioning a new instance: + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette RAS. Get credentials for upp-rosette-provisioner user in PROD account and export them: +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will provision the stack in AWS: + +``` + +make staging-rosette-ras + +``` + +## Deleting the cluster + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette RAS. Get credentials for upp-rosette-provisioner user in PROD account and export them: + +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will decommission the stack in AWS: + +``` + +make destroy-staging-rosette-ras + +``` + +## Usage + diff --git a/upp-rosette-ras-provisioner/cloudformation/stack.yml b/upp-rosette-ras-provisioner/cloudformation/stack.yml new file mode 100644 index 0000000..8536308 --- /dev/null +++ b/upp-rosette-ras-provisioner/cloudformation/stack.yml @@ -0,0 +1,188 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: This template deploys Rosette RAS +Parameters: + VpcID: + Description: The ID of the VPC + Type: String + Default: vpc-ee57bf89 + SubnetIds: + Description: List of comma separated subnet IDs + Type: CommaDelimitedList + EnvironmentName: + Description: An environment name that will be prefixed to resource names + Type: String + Default: prod + EnvironmentType: + Description: Tag detail for the Environment + Type: String + Default: p + Ec2InstanceType: + Description: Size of ec2 Instance + Type: String + Default: m6a.2xlarge + ImageId: + Description: The Image ID of Amazon Linux 2 kto use + Type: String + Default: 'ami-0069d66985b09d219' + TagTeamDL: + Description: Tag of the TeamDL + Type: String + AllowedPattern: ^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$ + ConstraintDescription: There must be a valid email address for the TeamDL + Default: universal.publishing.platform@ft.com + TagSystemCode: + Description: The system code for the environment + Type: String + Default: upp + TagDescription: + Description: Tag detail for the describing the instance + Type: String + Default: UPP Rosette stack + +Resources: + RosetteRasSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: !Join ["" , ["Rosette-ras-", !Ref EnvironmentName]] + GroupDescription: "Security group for Rosette RAS Instance" + VpcId: !Ref VpcID + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette RAS Security Group ", !Ref EnvironmentName]] + - Key: environment + Value: !Ref EnvironmentName + - Key: teamDL + Value: !Ref TagTeamDL + - Key: systemCode + Value: !Ref TagSystemCode + - Key: description + Value: "Security group for UPP Rosette RAS Instance" + SecurityGroupIngress: + - Description: "AWS VPN range" + IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 10.165.0.0/24 + - Description: "Rosette RAS app port withing VPC" + IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 10.169.64.0/18 + - Description: "SSH port within VPC" + IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 10.165.0.0/24 + ElasticLoadBalancer: + Type: "AWS::ElasticLoadBalancing::LoadBalancer" + Properties: + LoadBalancerName: !Sub upp-rosette-elb-ras-${EnvironmentType} + CrossZone: false + Subnets: !Ref SubnetIds + SecurityGroups: [!Ref RosetteRasSecurityGroup] + Scheme: internal + Listeners: + - LoadBalancerPort: "80" + InstancePort: "80" + Protocol: "HTTP" + HealthCheck: + Target: HTTP:80/rest/v1/info + HealthyThreshold: '3' + UnhealthyThreshold: '5' + Interval: '30' + Timeout: '5' + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette RAS ", !Ref EnvironmentType]] + - Key: environment + Value: !Ref EnvironmentType + - Key: teamDL + Value: !Ref TagTeamDL + - Key: systemCode + Value: !Ref TagSystemCode + - Key: description + Value: !Ref TagDescription + AutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + AutoScalingGroupName: !Sub upp-rosette-ras-${EnvironmentName} + MinSize: '1' + MaxSize: '1' + DesiredCapacity: "1" + LoadBalancerNames: + [ Ref: ElasticLoadBalancer ] + LaunchConfigurationName: + Ref: LaunchConfigRosetteRAS + VPCZoneIdentifier: !Ref SubnetIds + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette RAS ", !Ref EnvironmentType]] + PropagateAtLaunch: true + - Key: environment + Value: !Ref EnvironmentType + PropagateAtLaunch: true + - Key: teamDL + Value: !Ref TagTeamDL + PropagateAtLaunch: true + - Key: systemCode + Value: !Ref TagSystemCode + PropagateAtLaunch: true + - Key: description + Value: !Ref TagDescription + PropagateAtLaunch: true + + LaunchConfigRosetteRAS: + Type: AWS::AutoScaling::LaunchConfiguration + Properties: + IamInstanceProfile: "FT-EC2-Role" + ImageId: !Ref ImageId + InstanceType: !Ref Ec2InstanceType + KeyName: "upp-k8s-provisioning-debug" + SecurityGroups: [!Ref RosetteRasSecurityGroup] + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeType: gp3 + VolumeSize: 100 + Encrypted: false + UserData: + Fn::Base64: !Sub | + #!/bin/bash -x + yum update -y + yum install git -y + cat > /etc/systemd/system/authorized_keys.service << EOF + [Unit] + Description=Update authorized_keys + [Service] + Type=oneshot + ExecStartPre=/bin/sh -c 'mkdir -p /home/ec2-user/.ssh && touch /home/ec2-user/.ssh/authorized_keys' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys.sha512 https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys.sha512' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys' + ExecStart=/bin/sh -c 'cd /tmp/ && sha512sum -c authorized_keys.sha512 && cp authorized_keys /home/ec2-user/.ssh/authorized_keys && chmod 700 /home/ec2-user/.ssh && chmod 600 /home/ec2-user/.ssh/authorized_keys && chown -R ec2-user:ec2-user /home/ec2-user/.ssh' + Restart=no + EOF + + systemctl start authorized_keys.service + systemctl enable authorized_keys.service + + cat > /etc/systemd/system/authorized_keys.timer << EOF + [Unit] + Description=Authorized keys timer + [Timer] + OnBootSec=1min + OnUnitActiveSec=1min + [Install] + WantedBy=timers.target + EOF + + systemctl start authorized_keys.timer + systemctl enable authorized_keys.timer + + yum update -y + yum install git -y + #update the command docker-compose -f /rosette/docker-compose.yaml up -d + +Outputs: + RosetteELBDNSname: + Description: Rosette ELB DNS record + Value: !GetAtt ElasticLoadBalancer.DNSName diff --git a/upp-rosette-ras-provisioner/config/rosette-prod.sh b/upp-rosette-ras-provisioner/config/rosette-prod.sh new file mode 100644 index 0000000..fbee1d3 --- /dev/null +++ b/upp-rosette-ras-provisioner/config/rosette-prod.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5,subnet-02526d7213a359f48,subnet-0f2c8a8f1e3db176a" +export EnvironmentType="p" +export EnvironmentName='prod' +export IMAGE_ID="ami-0069d66985b09d219" +export DNS_HOSTED_ZONE_NAME="upp.ft.com" +export DNS_HOSTED_ZONE_ID="ZE8P6HDQA4Y9N" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-prod-rosette-ras" +export DNS_STS_ASSUME_ROLE_ARN="arn:aws:iam::345152836601:role/route53-iam-dnsonlyroleuppprodE94AAA36-CAPB27QPX3K8" diff --git a/upp-rosette-ras-provisioner/config/rosette-staging.sh b/upp-rosette-ras-provisioner/config/rosette-staging.sh new file mode 100644 index 0000000..b7af709 --- /dev/null +++ b/upp-rosette-ras-provisioner/config/rosette-staging.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5,subnet-02526d7213a359f48,subnet-0f2c8a8f1e3db176a" +#export SECURITY_GROUPS="sg-39ef7b40,sg-f294008b" +export EnvironmentType="t" +export EnvironmentName='staging' +export IMAGE_ID="ami-0069d66985b09d219" +export DNS_HOSTED_ZONE_NAME="upp.ft.com" +export DNS_HOSTED_ZONE_ID="ZE8P6HDQA4Y9N" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-staging-rosette-ras" +export DNS_STS_ASSUME_ROLE_ARN="arn:aws:iam::345152836601:role/route53-iam-dnsonlyroleuppprodE94AAA36-CAPB27QPX3K8" diff --git a/upp-rosette-ras-provisioner/sh/destroy.sh b/upp-rosette-ras-provisioner/sh/destroy.sh new file mode 100644 index 0000000..7a12c89 --- /dev/null +++ b/upp-rosette-ras-provisioner/sh/destroy.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +manage-cname-rosette.sh "DELETE" + +aws cloudformation delete-stack \ + --stack-name "${CF_STACK_NAME}" diff --git a/upp-rosette-ras-provisioner/sh/get-latest-awscli2 b/upp-rosette-ras-provisioner/sh/get-latest-awscli2 new file mode 100644 index 0000000..c8755b1 --- /dev/null +++ b/upp-rosette-ras-provisioner/sh/get-latest-awscli2 @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +cd /tmp + +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +./aws/install --update + +rm awscliv2.zip +rm -rf ./aws/ diff --git a/upp-rosette-ras-provisioner/sh/manage-cname-rosette.sh b/upp-rosette-ras-provisioner/sh/manage-cname-rosette.sh new file mode 100644 index 0000000..104cdcc --- /dev/null +++ b/upp-rosette-ras-provisioner/sh/manage-cname-rosette.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + + +if [ $# -ne 1 ]; then + cat < +Actions: + UPSERT - Create or update a Route53 CNAME record + DELETE - Delete a Route53 CNAME record +EOF + + exit 1 +fi + +CNAME_ACTION=${1} + +ROSETTE_FQDN="cm-rosette-ras-${EnvironmentType}.upp.ft.com" +echo $ROSETTE_FQDN + +echo "Get the Rosette ELB DNS Name" +ROSETTE_ELB_DNS_NAME=$( + aws cloudformation describe-stacks \ + --output text \ + --region "${AWS_REGION}" \ + --stack-name "${CF_STACK_NAME}" \ + --query "Stacks[0].Outputs[?OutputKey=='RosetteELBDNSname'].OutputValue" +) + +echo "Assume the Route53 DNS prod role" +DNS_STS_ASSUME_ROLE=$( + aws sts assume-role \ + --role-arn "${DNS_STS_ASSUME_ROLE_ARN}" \ + --role-session-name "cm-rosette-provisioner-session" +) + +AWS_ACCESS_KEY_ID=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.AccessKeyId) +AWS_SECRET_ACCESS_KEY=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.SecretAccessKey) +AWS_SESSION_TOKEN=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.SessionToken) + +export AWS_ACCESS_KEY_ID +export AWS_SECRET_ACCESS_KEY +export AWS_SESSION_TOKEN + +aws sts get-caller-identity + +echo "Generate change set file..." +COMMENT="Auto change action $CNAME_ACTION @ $(date) from the cm-rosette-provisioner" +TMPFILE=$(mktemp /tmp/manage-cname-temporary-file.XXXXXXXX) +cat > "${TMPFILE}" << EOF + { + "Comment":"${COMMENT}", + "Changes":[ + { + "Action":"${CNAME_ACTION}", + "ResourceRecordSet":{ + "ResourceRecords":[ + { + "Value":"${ROSETTE_ELB_DNS_NAME}" + } + ], + "Name":"${ROSETTE_FQDN}", + "Type":"CNAME", + "TTL": 30 + } + } + ] + } +EOF + +cat "${TMPFILE}" + +echo "Change record set..." +aws route53 change-resource-record-sets \ + --hosted-zone-id "${DNS_HOSTED_ZONE_ID}" \ + --change-batch file://"${TMPFILE}" + +rm "${TMPFILE}" + +echo "Done. DNS action completed." diff --git a/upp-rosette-ras-provisioner/sh/provision.sh b/upp-rosette-ras-provisioner/sh/provision.sh new file mode 100644 index 0000000..2402f08 --- /dev/null +++ b/upp-rosette-ras-provisioner/sh/provision.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +CF_STACK_TEMPLATE="/cloudformation/stack.yml" +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +aws cloudformation deploy \ + --stack-name "${CF_STACK_NAME}" \ + --region "${AWS_REGION}" \ + --template-file "${CF_STACK_TEMPLATE}" \ + --parameter-overrides \ + SubnetIds="${SUBNET_IDS}" \ + EnvironmentName="${EnvironmentName}" \ + EnvironmentType="${EnvironmentType}" + +manage-cname-rosette.sh "UPSERT" diff --git a/upp-rosette-rts-provisioner/Dockerfile b/upp-rosette-rts-provisioner/Dockerfile new file mode 100644 index 0000000..92f387c --- /dev/null +++ b/upp-rosette-rts-provisioner/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:20.04 + +COPY cloudformation /cloudformation/ +COPY sh/* /usr/local/bin/ +COPY config/* /config/ + +RUN chmod +x /usr/local/bin/* + +RUN apt-get update && apt-get install -y \ + less \ + zip \ + curl \ + wget \ + jq \ + vim \ + && rm -rf /var/lib/apt/lists/* \ + && get-latest-awscli2 diff --git a/upp-rosette-rts-provisioner/Makefile b/upp-rosette-rts-provisioner/Makefile new file mode 100644 index 0000000..95ac1c0 --- /dev/null +++ b/upp-rosette-rts-provisioner/Makefile @@ -0,0 +1,31 @@ +.PHONY: rosette-provisioner staging-rosette-rts destroy-staging-rosette-rts prod-rosette-rts destroy-prod-rosette-rts help + +help: ## Show this help. + @sed -ne '/@sed/!s/##//p' $(MAKEFILE_LIST) + +rosette-rts-provisioner: ## Build provisioner + @docker build -t rosette-rts-provisioner:local . + +staging-rosette-rts: ## Deploy staging rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-rts-provisioner:local provision.sh staging + +prod-rosette-rts: ## Deploy prod rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-rts-provisioner:local provision.sh prod + +destroy-staging-rosette-rts: ## Destroys staging rosette steack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-rts-provisioner:local destroy.sh staging + +destroy-prod-rosette-rts: ## Destroys prod rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-rts-provisioner:local destroy.sh prod diff --git a/upp-rosette-rts-provisioner/README.md b/upp-rosette-rts-provisioner/README.md new file mode 100644 index 0000000..d4006f7 --- /dev/null +++ b/upp-rosette-rts-provisioner/README.md @@ -0,0 +1,57 @@ +# upp-rosette-rts-provisioner + +## Description + +This repository contains the files required to provision a Rosette RTS - PROD and STAGING environment + +## Prerequisites + +1. [Install docker](https://docs.docker.com/engine/installation/) locally + +## Building the Docker image + +``` +make rosette-rts-provisioner +``` + +## Provisioning a new instance + +Here are the steps for provisioning a new instance: + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette RTS. Get credentials for upp-rosette-provisioner user in PROD account and export them: +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will provision the stack in AWS: + +``` + +make staging-rosette-rts + +``` + +## Deleting the cluster + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette RTS. Get credentials for upp-rosette-provisioner user in PROD account and export them: + +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will decommission the stack in AWS: + +``` + +make destroy-staging-rosette-rts + +``` + +## Usage + diff --git a/upp-rosette-rts-provisioner/cloudformation/stack.yml b/upp-rosette-rts-provisioner/cloudformation/stack.yml new file mode 100644 index 0000000..7443ba8 --- /dev/null +++ b/upp-rosette-rts-provisioner/cloudformation/stack.yml @@ -0,0 +1,188 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: This template deploys Rosette RTS +Parameters: + VpcID: + Description: The ID of the VPC + Type: String + Default: vpc-ee57bf89 + SubnetIds: + Description: List of comma separated subnet IDs + Type: CommaDelimitedList + EnvironmentName: + Description: An environment name that will be prefixed to resource names + Type: String + Default: prod + EnvironmentType: + Description: Tag detail for the Environment + Type: String + Default: p + Ec2InstanceType: + Description: Size of ec2 Instance + Type: String + Default: m6a.2xlarge + ImageId: + Description: The Image ID of Amazon Linux 2 kto use + Type: String + Default: 'ami-0069d66985b09d219' + TagTeamDL: + Description: Tag of the TeamDL + Type: String + AllowedPattern: ^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$ + ConstraintDescription: There must be a valid email address for the TeamDL + Default: universal.publishing.platform@ft.com + TagSystemCode: + Description: The system code for the environment + Type: String + Default: upp + TagDescription: + Description: Tag detail for the describing the instance + Type: String + Default: UPP Rosette stack + +Resources: + RosetteRtsSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: !Join ["" , ["Rosette-rts-", !Ref EnvironmentName]] + GroupDescription: "Security group for Rosette RTS Instance" + VpcId: !Ref VpcID + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette RTS Security Group ", !Ref EnvironmentName]] + - Key: environment + Value: !Ref EnvironmentName + - Key: teamDL + Value: !Ref TagTeamDL + - Key: systemCode + Value: !Ref TagSystemCode + - Key: description + Value: "Security group for UPP Rosette RTS Instance" + SecurityGroupIngress: + - Description: "AWS VPN range" + IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 10.165.0.0/24 + - Description: "Rosette RTS app port withing VPC" + IpProtocol: tcp + FromPort: 9080 + ToPort: 9080 + CidrIp: 10.169.64.0/18 + - Description: "SSH port within VPC" + IpProtocol: tcp + FromPort: 9080 + ToPort: 9080 + CidrIp: 10.165.0.0/24 + ElasticLoadBalancer: + Type: "AWS::ElasticLoadBalancing::LoadBalancer" + Properties: + LoadBalancerName: !Sub upp-rosette-elb-rts-${EnvironmentType} + CrossZone: false + Subnets: !Ref SubnetIds + SecurityGroups: [!Ref RosetteRtsSecurityGroup] + Scheme: internal + Listeners: + - LoadBalancerPort: "9080" + InstancePort: "9080" + Protocol: "HTTP" + HealthCheck: + Target: HTTP:9080/rest/v1/info + HealthyThreshold: '3' + UnhealthyThreshold: '5' + Interval: '30' + Timeout: '5' + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette RTS ", !Ref EnvironmentType]] + - Key: environment + Value: !Ref EnvironmentType + - Key: teamDL + Value: !Ref TagTeamDL + - Key: systemCode + Value: !Ref TagSystemCode + - Key: description + Value: !Ref TagDescription + AutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + AutoScalingGroupName: !Sub upp-rosette-rts-${EnvironmentName} + MinSize: '1' + MaxSize: '1' + DesiredCapacity: "1" + LoadBalancerNames: + [ Ref: ElasticLoadBalancer ] + LaunchConfigurationName: + Ref: LaunchConfigRosetteRTS + VPCZoneIdentifier: !Ref SubnetIds + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette RTS ", !Ref EnvironmentType]] + PropagateAtLaunch: true + - Key: environment + Value: !Ref EnvironmentType + PropagateAtLaunch: true + - Key: teamDL + Value: !Ref TagTeamDL + PropagateAtLaunch: true + - Key: systemCode + Value: !Ref TagSystemCode + PropagateAtLaunch: true + - Key: description + Value: !Ref TagDescription + PropagateAtLaunch: true + + LaunchConfigRosetteRTS: + Type: AWS::AutoScaling::LaunchConfiguration + Properties: + IamInstanceProfile: "FT-EC2-Role" + ImageId: !Ref ImageId + InstanceType: !Ref Ec2InstanceType + KeyName: "upp-k8s-provisioning-debug" + SecurityGroups: [!Ref RosetteRtsSecurityGroup] + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeType: gp3 + VolumeSize: 100 + Encrypted: false + UserData: + Fn::Base64: !Sub | + #!/bin/bash -x + yum update -y + yum install git -y + cat > /etc/systemd/system/authorized_keys.service << EOF + [Unit] + Description=Update authorized_keys + [Service] + Type=oneshot + ExecStartPre=/bin/sh -c 'mkdir -p /home/ec2-user/.ssh && touch /home/ec2-user/.ssh/authorized_keys' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys.sha512 https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys.sha512' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys' + ExecStart=/bin/sh -c 'cd /tmp/ && sha512sum -c authorized_keys.sha512 && cp authorized_keys /home/ec2-user/.ssh/authorized_keys && chmod 700 /home/ec2-user/.ssh && chmod 600 /home/ec2-user/.ssh/authorized_keys && chown -R ec2-user:ec2-user /home/ec2-user/.ssh' + Restart=no + EOF + + systemctl start authorized_keys.service + systemctl enable authorized_keys.service + + cat > /etc/systemd/system/authorized_keys.timer << EOF + [Unit] + Description=Authorized keys timer + [Timer] + OnBootSec=1min + OnUnitActiveSec=1min + [Install] + WantedBy=timers.target + EOF + + systemctl start authorized_keys.timer + systemctl enable authorized_keys.timer + + yum update -y + yum install git -y + #update the command docker-compose -f /rosette/docker-compose.yaml up -d + +Outputs: + RosetteELBDNSname: + Description: Rosette ELB DNS record + Value: !GetAtt ElasticLoadBalancer.DNSName diff --git a/upp-rosette-rts-provisioner/config/rosette-prod.sh b/upp-rosette-rts-provisioner/config/rosette-prod.sh new file mode 100644 index 0000000..829365d --- /dev/null +++ b/upp-rosette-rts-provisioner/config/rosette-prod.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5,subnet-02526d7213a359f48,subnet-0f2c8a8f1e3db176a" +export EnvironmentType="p" +export EnvironmentName='prod' +export IMAGE_ID="ami-0069d66985b09d219" +export DNS_HOSTED_ZONE_NAME="upp.ft.com" +export DNS_HOSTED_ZONE_ID="ZE8P6HDQA4Y9N" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-prod-rosette-rts" +export DNS_STS_ASSUME_ROLE_ARN="arn:aws:iam::345152836601:role/route53-iam-dnsonlyroleuppprodE94AAA36-CAPB27QPX3K8" diff --git a/upp-rosette-rts-provisioner/config/rosette-staging.sh b/upp-rosette-rts-provisioner/config/rosette-staging.sh new file mode 100644 index 0000000..7d76eb4 --- /dev/null +++ b/upp-rosette-rts-provisioner/config/rosette-staging.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5,subnet-02526d7213a359f48,subnet-0f2c8a8f1e3db176a" +#export SECURITY_GROUPS="sg-39ef7b40,sg-f294008b" +export EnvironmentType="t" +export EnvironmentName='staging' +export IMAGE_ID="ami-0069d66985b09d219" +export DNS_HOSTED_ZONE_NAME="upp.ft.com" +export DNS_HOSTED_ZONE_ID="ZE8P6HDQA4Y9N" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-staging-rosette-rts" +export DNS_STS_ASSUME_ROLE_ARN="arn:aws:iam::345152836601:role/route53-iam-dnsonlyroleuppprodE94AAA36-CAPB27QPX3K8" diff --git a/upp-rosette-rts-provisioner/sh/destroy.sh b/upp-rosette-rts-provisioner/sh/destroy.sh new file mode 100644 index 0000000..7a12c89 --- /dev/null +++ b/upp-rosette-rts-provisioner/sh/destroy.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +manage-cname-rosette.sh "DELETE" + +aws cloudformation delete-stack \ + --stack-name "${CF_STACK_NAME}" diff --git a/upp-rosette-rts-provisioner/sh/get-latest-awscli2 b/upp-rosette-rts-provisioner/sh/get-latest-awscli2 new file mode 100644 index 0000000..c8755b1 --- /dev/null +++ b/upp-rosette-rts-provisioner/sh/get-latest-awscli2 @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +cd /tmp + +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +./aws/install --update + +rm awscliv2.zip +rm -rf ./aws/ diff --git a/upp-rosette-rts-provisioner/sh/manage-cname-rosette.sh b/upp-rosette-rts-provisioner/sh/manage-cname-rosette.sh new file mode 100644 index 0000000..613eb2f --- /dev/null +++ b/upp-rosette-rts-provisioner/sh/manage-cname-rosette.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + + +if [ $# -ne 1 ]; then + cat < +Actions: + UPSERT - Create or update a Route53 CNAME record + DELETE - Delete a Route53 CNAME record +EOF + + exit 1 +fi + +CNAME_ACTION=${1} + +ROSETTE_FQDN="cm-rosette-rts-${EnvironmentType}.upp.ft.com" +echo $ROSETTE_FQDN + +echo "Get the Rosette ELB DNS Name" +ROSETTE_ELB_DNS_NAME=$( + aws cloudformation describe-stacks \ + --output text \ + --region "${AWS_REGION}" \ + --stack-name "${CF_STACK_NAME}" \ + --query "Stacks[0].Outputs[?OutputKey=='RosetteELBDNSname'].OutputValue" +) + +echo "Assume the Route53 DNS prod role" +DNS_STS_ASSUME_ROLE=$( + aws sts assume-role \ + --role-arn "${DNS_STS_ASSUME_ROLE_ARN}" \ + --role-session-name "cm-rosette-provisioner-session" +) + +AWS_ACCESS_KEY_ID=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.AccessKeyId) +AWS_SECRET_ACCESS_KEY=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.SecretAccessKey) +AWS_SESSION_TOKEN=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.SessionToken) + +export AWS_ACCESS_KEY_ID +export AWS_SECRET_ACCESS_KEY +export AWS_SESSION_TOKEN + +aws sts get-caller-identity + +echo "Generate change set file..." +COMMENT="Auto change action $CNAME_ACTION @ $(date) from the cm-rosette-provisioner" +TMPFILE=$(mktemp /tmp/manage-cname-temporary-file.XXXXXXXX) +cat > "${TMPFILE}" << EOF + { + "Comment":"${COMMENT}", + "Changes":[ + { + "Action":"${CNAME_ACTION}", + "ResourceRecordSet":{ + "ResourceRecords":[ + { + "Value":"${ROSETTE_ELB_DNS_NAME}" + } + ], + "Name":"${ROSETTE_FQDN}", + "Type":"CNAME", + "TTL": 30 + } + } + ] + } +EOF + +cat "${TMPFILE}" + +echo "Change record set..." +aws route53 change-resource-record-sets \ + --hosted-zone-id "${DNS_HOSTED_ZONE_ID}" \ + --change-batch file://"${TMPFILE}" + +rm "${TMPFILE}" + +echo "Done. DNS action completed." diff --git a/upp-rosette-rts-provisioner/sh/provision.sh b/upp-rosette-rts-provisioner/sh/provision.sh new file mode 100644 index 0000000..2402f08 --- /dev/null +++ b/upp-rosette-rts-provisioner/sh/provision.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +CF_STACK_TEMPLATE="/cloudformation/stack.yml" +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +aws cloudformation deploy \ + --stack-name "${CF_STACK_NAME}" \ + --region "${AWS_REGION}" \ + --template-file "${CF_STACK_TEMPLATE}" \ + --parameter-overrides \ + SubnetIds="${SUBNET_IDS}" \ + EnvironmentName="${EnvironmentName}" \ + EnvironmentType="${EnvironmentType}" + +manage-cname-rosette.sh "UPSERT" diff --git a/upp-rosette-server-provisioner/Dockerfile b/upp-rosette-server-provisioner/Dockerfile new file mode 100644 index 0000000..92f387c --- /dev/null +++ b/upp-rosette-server-provisioner/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:20.04 + +COPY cloudformation /cloudformation/ +COPY sh/* /usr/local/bin/ +COPY config/* /config/ + +RUN chmod +x /usr/local/bin/* + +RUN apt-get update && apt-get install -y \ + less \ + zip \ + curl \ + wget \ + jq \ + vim \ + && rm -rf /var/lib/apt/lists/* \ + && get-latest-awscli2 diff --git a/upp-rosette-server-provisioner/Makefile b/upp-rosette-server-provisioner/Makefile new file mode 100644 index 0000000..ebd4813 --- /dev/null +++ b/upp-rosette-server-provisioner/Makefile @@ -0,0 +1,31 @@ +.PHONY: rosette-provisioner-provisioner staging-rosette-server destroy-staging-rosette-server prod-rosette-server destroy-prod-rosette-server help + +help: ## Show this help. + @sed -ne '/@sed/!s/##//p' $(MAKEFILE_LIST) + +rosette-server-provisioner: ## Build provisioner + @docker build -t rosette-server-provisioner:local . + +staging-rosette-server: ## Deploy staging rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-server-provisioner:local provision.sh staging + +prod-rosette-server: ## Deploy prod rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-server-provisioner:local provision.sh prod + +destroy-staging-rosette-server: ## Destroys staging rosette steack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-server-provisioner:local destroy.sh staging + +destroy-prod-rosette-server: ## Destroys prod rosette stack + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-server-provisioner:local destroy.sh prod diff --git a/upp-rosette-server-provisioner/README.md b/upp-rosette-server-provisioner/README.md new file mode 100644 index 0000000..4af0aaa --- /dev/null +++ b/upp-rosette-server-provisioner/README.md @@ -0,0 +1,86 @@ +# upp-rosette-server-provisioner + +## Description + +This repository contains the files required to provision a Rosette Server - PROD and STAGING environment + +## Prerequisites + +1. [Install docker](https://docs.docker.com/engine/installation/) locally + +## Building the Docker image + +``` +make rosette-server-provisioner +``` + +## Provisioning a new instance + +Here are the steps for provisioning a new cluster: + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette Server. Get credentials for upp-rosette-provisioner user in PROD account and export them: +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will provision the stack in AWS: + +``` + +make staging-rosette-server + +``` + +## Deleting the cluster + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette Server. Get credentials for upp-rosette-provisioner user in PROD account and export them: + +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will decommission the stack in AWS: + +``` + +make destroy-staging-rosette-server + +``` + +## Usage + +The server exposes an Endpoint at `http://cm-rosette-t.upp.ft.com` (for Staging) or `http://cm-rosette-p.upp.ft.com` (for Prod) on default port `8181`. You need to be in VPN to access it. +You can ping the endpoint with: + +``` + +curl http://cm-rosette-t.upp.ft.com:8181/rest/v1/ping | jq + +``` +Get current version: + +``` + +curl http://cm-rosette-t.upp.ft.com:8181/rest/v1/info | jq + +``` + +Test entities endpoint: + +``` + +curl --request POST \ + --url http://cm-rosette-t.upp.ft.com:8181/rest/v1/entities \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --data '{"content": "Bill Murray will appear in new Ghostbusters film: Dr. Venkman was spotted filming in Boston"}' + + ``` + +Login to the instance with AD credentials diff --git a/upp-rosette-server-provisioner/cloudformation/stack.yml b/upp-rosette-server-provisioner/cloudformation/stack.yml new file mode 100644 index 0000000..cd91c61 --- /dev/null +++ b/upp-rosette-server-provisioner/cloudformation/stack.yml @@ -0,0 +1,221 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: This template deploys Rosette stack +Parameters: + VpcID: + Description: The ID of the VPC + Type: String + Default: vpc-ee57bf89 + SubnetIds: + Description: List of comma separated subnet IDs + Type: CommaDelimitedList + EnvironmentName: + Description: An environment name that will be prefixed to resource names + Type: String + Default: prod + EnvironmentType: + Description: Tag detail for the Environment + Type: String + Default: p + Ec2InstanceType: + Description: Size of ec2 Instance + Type: String + Default: m6a.4xlarge + ImageId: + Description: The Image ID of Amazon Linux 2 kto use + Type: String + Default: 'ami-09552f658e9ff8bc0' + TagTeamDL: + Description: Tag of the TeamDL + Type: String + AllowedPattern: ^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$ + ConstraintDescription: There must be a valid email address for the TeamDL + Default: universal.publishing.platform@ft.com + TagSystemCode: + Description: The system code for the environment + Type: String + Default: upp + TagDescription: + Description: Tag detail for the describing the instance + Type: String + Default: UPP Rosette stack + +Resources: + RosetteSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: !Join ["" , ["Rosette-", !Ref EnvironmentName]] + GroupDescription: "Security group for Rosette Server Instance" + VpcId: !Ref VpcID + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette Server Security Group ", !Ref EnvironmentName]] + - Key: environment + Value: !Ref EnvironmentName + - Key: teamDL + Value: !Ref TagTeamDL + - Key: systemCode + Value: !Ref TagSystemCode + - Key: description + Value: "Security group for UPP Rosette Instance" + SecurityGroupIngress: + - Description: "AWS VPN range" + IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 10.165.0.0/24 + - Description: "Rosette app port withing VPC" + IpProtocol: tcp + FromPort: 8181 + ToPort: 8181 + CidrIp: 10.169.64.0/18 + - Description: "SSH port within VPC" + IpProtocol: tcp + FromPort: 8181 + ToPort: 8181 + CidrIp: 10.165.0.0/24 + - Description: "Rosette app port withing VPC" + IpProtocol: tcp + FromPort: 8080 + ToPort: 8080 + CidrIp: 10.169.64.0/18 + - Description: "SSH port within VPC" + IpProtocol: tcp + FromPort: 8080 + ToPort: 8080 + CidrIp: 10.165.0.0/24 + - Description: "Connection from Dev VPC" + IpProtocol: tcp + FromPort: 8181 + ToPort: 8181 + CidrIp: 10.172.32.0/21 + - Description: "Connection from Dev VPC" + IpProtocol: tcp + FromPort: 8080 + ToPort: 8080 + CidrIp: 10.172.32.0/21 + - Description: "Connection from Dev VPC" + IpProtocol: tcp + FromPort: 8080 + ToPort: 8080 + CidrIp: 10.169.0.0/18 + - Description: "Connection from Dev VPC" + IpProtocol: tcp + FromPort: 8181 + ToPort: 8181 + CidrIp: 10.169.0.0/18 + ElasticLoadBalancer: + Type: "AWS::ElasticLoadBalancing::LoadBalancer" + Properties: + LoadBalancerName: !Sub upp-rosette-elb-server-${EnvironmentType} + CrossZone: false + Subnets: !Ref SubnetIds + SecurityGroups: [!Ref RosetteSecurityGroup] + Scheme: internal + Listeners: + - LoadBalancerPort: "8181" + InstancePort: "8181" + Protocol: "HTTP" + - LoadBalancerPort: "8080" + InstancePort: "8080" + Protocol: "HTTP" + HealthCheck: + Target: HTTP:8181/rest/v1/info + HealthyThreshold: '3' + UnhealthyThreshold: '5' + Interval: '30' + Timeout: '5' + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette ", !Ref EnvironmentType]] + - Key: environment + Value: !Ref EnvironmentType + - Key: teamDL + Value: !Ref TagTeamDL + - Key: systemCode + Value: !Ref TagSystemCode + - Key: description + Value: !Ref TagDescription + AutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + AutoScalingGroupName: !Sub upp-rosette-server-${EnvironmentName} + MinSize: '1' + MaxSize: '1' + DesiredCapacity: "1" + LoadBalancerNames: + [ Ref: ElasticLoadBalancer ] + LaunchConfigurationName: + Ref: LaunchConfigRosette + VPCZoneIdentifier: !Ref SubnetIds + Tags: + - Key: Name + Value: !Join ["" , ["UPP Rosette ", !Ref EnvironmentType]] + PropagateAtLaunch: true + - Key: environment + Value: !Ref EnvironmentType + PropagateAtLaunch: true + - Key: teamDL + Value: !Ref TagTeamDL + PropagateAtLaunch: true + - Key: systemCode + Value: !Ref TagSystemCode + PropagateAtLaunch: true + - Key: description + Value: !Ref TagDescription + PropagateAtLaunch: true + + LaunchConfigRosette: + Type: AWS::AutoScaling::LaunchConfiguration + Properties: + IamInstanceProfile: "FT-EC2-Role" + ImageId: !Ref ImageId + InstanceType: !Ref Ec2InstanceType + KeyName: "upp-k8s-provisioning-debug" + SecurityGroups: [!Ref RosetteSecurityGroup] + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeType: gp3 + VolumeSize: 100 + Encrypted: false + UserData: + Fn::Base64: !Sub | + #!/bin/bash -x + yum update -y + yum install git -y + cat > /etc/systemd/system/authorized_keys.service << EOF + [Unit] + Description=Update authorized_keys + [Service] + Type=oneshot + ExecStartPre=/bin/sh -c 'mkdir -p /home/ec2-user/.ssh && touch /home/ec2-user/.ssh/authorized_keys' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys.sha512 https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys.sha512' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys' + ExecStart=/bin/sh -c 'cd /tmp/ && sha512sum -c authorized_keys.sha512 && cp authorized_keys /home/ec2-user/.ssh/authorized_keys && chmod 700 /home/ec2-user/.ssh && chmod 600 /home/ec2-user/.ssh/authorized_keys && chown -R ec2-user:ec2-user /home/ec2-user/.ssh' + Restart=no + EOF + + systemctl start authorized_keys.service + systemctl enable authorized_keys.service + + cat > /etc/systemd/system/authorized_keys.timer << EOF + [Unit] + Description=Authorized keys timer + [Timer] + OnBootSec=1min + OnUnitActiveSec=1min + [Install] + WantedBy=timers.target + EOF + + systemctl start authorized_keys.timer + systemctl enable authorized_keys.timer + + yum update -y + yum install git -y + docker-compose -f /rosette/docker-compose.yaml up -d + +Outputs: + RosetteELBDNSname: + Description: Rosette ELB DNS record + Value: !GetAtt ElasticLoadBalancer.DNSName diff --git a/upp-rosette-server-provisioner/config/rosette-prod.sh b/upp-rosette-server-provisioner/config/rosette-prod.sh new file mode 100644 index 0000000..049e788 --- /dev/null +++ b/upp-rosette-server-provisioner/config/rosette-prod.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5,subnet-02526d7213a359f48,subnet-0f2c8a8f1e3db176a" +export EnvironmentType="p" +export EnvironmentName='prod' +export IMAGE_ID="ami-07d9160fa81ccffb5" +export DNS_HOSTED_ZONE_NAME="upp.ft.com" +export DNS_HOSTED_ZONE_ID="ZE8P6HDQA4Y9N" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-prod-rosette-server" +export DNS_STS_ASSUME_ROLE_ARN="arn:aws:iam::345152836601:role/route53-iam-dnsonlyroleuppprodE94AAA36-CAPB27QPX3K8" diff --git a/upp-rosette-server-provisioner/config/rosette-staging.sh b/upp-rosette-server-provisioner/config/rosette-staging.sh new file mode 100644 index 0000000..3b3064c --- /dev/null +++ b/upp-rosette-server-provisioner/config/rosette-staging.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5,subnet-02526d7213a359f48,subnet-0f2c8a8f1e3db176a" +#export SECURITY_GROUPS="sg-39ef7b40,sg-f294008b" +export EnvironmentType="t" +export EnvironmentName='staging' +export IMAGE_ID="ami-07d9160fa81ccffb5" +export DNS_HOSTED_ZONE_NAME="upp.ft.com" +export DNS_HOSTED_ZONE_ID="ZE8P6HDQA4Y9N" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-staging-rosette-server" +export DNS_STS_ASSUME_ROLE_ARN="arn:aws:iam::345152836601:role/route53-iam-dnsonlyroleuppprodE94AAA36-CAPB27QPX3K8" diff --git a/upp-rosette-server-provisioner/sh/destroy.sh b/upp-rosette-server-provisioner/sh/destroy.sh new file mode 100644 index 0000000..7a12c89 --- /dev/null +++ b/upp-rosette-server-provisioner/sh/destroy.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +manage-cname-rosette.sh "DELETE" + +aws cloudformation delete-stack \ + --stack-name "${CF_STACK_NAME}" diff --git a/upp-rosette-server-provisioner/sh/get-latest-awscli2 b/upp-rosette-server-provisioner/sh/get-latest-awscli2 new file mode 100644 index 0000000..c8755b1 --- /dev/null +++ b/upp-rosette-server-provisioner/sh/get-latest-awscli2 @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +cd /tmp + +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +./aws/install --update + +rm awscliv2.zip +rm -rf ./aws/ diff --git a/upp-rosette-server-provisioner/sh/manage-cname-rosette.sh b/upp-rosette-server-provisioner/sh/manage-cname-rosette.sh new file mode 100644 index 0000000..3749e7f --- /dev/null +++ b/upp-rosette-server-provisioner/sh/manage-cname-rosette.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + + +if [ $# -ne 1 ]; then + cat < +Actions: + UPSERT - Create or update a Route53 CNAME record + DELETE - Delete a Route53 CNAME record +EOF + + exit 1 +fi + +CNAME_ACTION=${1} + +ROSETTE_FQDN="cm-rosette-${EnvironmentType}.upp.ft.com" +echo $ROSETTE_FQDN + +echo "Get the Rosette ELB DNS Name" +ROSETTE_ELB_DNS_NAME=$( + aws cloudformation describe-stacks \ + --output text \ + --region "${AWS_REGION}" \ + --stack-name "${CF_STACK_NAME}" \ + --query "Stacks[0].Outputs[?OutputKey=='RosetteELBDNSname'].OutputValue" +) + +echo "Assume the Route53 DNS prod role" +DNS_STS_ASSUME_ROLE=$( + aws sts assume-role \ + --role-arn "${DNS_STS_ASSUME_ROLE_ARN}" \ + --role-session-name "cm-rosette-provisioner-session" +) + +AWS_ACCESS_KEY_ID=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.AccessKeyId) +AWS_SECRET_ACCESS_KEY=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.SecretAccessKey) +AWS_SESSION_TOKEN=$(echo "${DNS_STS_ASSUME_ROLE}" | jq -r .Credentials.SessionToken) + +export AWS_ACCESS_KEY_ID +export AWS_SECRET_ACCESS_KEY +export AWS_SESSION_TOKEN + +aws sts get-caller-identity + +echo "Generate change set file..." +COMMENT="Auto change action $CNAME_ACTION @ $(date) from the cm-rosette-provisioner" +TMPFILE=$(mktemp /tmp/manage-cname-temporary-file.XXXXXXXX) +cat > "${TMPFILE}" << EOF + { + "Comment":"${COMMENT}", + "Changes":[ + { + "Action":"${CNAME_ACTION}", + "ResourceRecordSet":{ + "ResourceRecords":[ + { + "Value":"${ROSETTE_ELB_DNS_NAME}" + } + ], + "Name":"${ROSETTE_FQDN}", + "Type":"CNAME", + "TTL": 30 + } + } + ] + } +EOF + +cat "${TMPFILE}" + +echo "Change record set..." +aws route53 change-resource-record-sets \ + --hosted-zone-id "${DNS_HOSTED_ZONE_ID}" \ + --change-batch file://"${TMPFILE}" + +rm "${TMPFILE}" + +echo "Done. DNS action completed." diff --git a/upp-rosette-server-provisioner/sh/provision.sh b/upp-rosette-server-provisioner/sh/provision.sh new file mode 100644 index 0000000..2402f08 --- /dev/null +++ b/upp-rosette-server-provisioner/sh/provision.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +CF_STACK_TEMPLATE="/cloudformation/stack.yml" +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +aws cloudformation deploy \ + --stack-name "${CF_STACK_NAME}" \ + --region "${AWS_REGION}" \ + --template-file "${CF_STACK_TEMPLATE}" \ + --parameter-overrides \ + SubnetIds="${SUBNET_IDS}" \ + EnvironmentName="${EnvironmentName}" \ + EnvironmentType="${EnvironmentType}" + +manage-cname-rosette.sh "UPSERT" diff --git a/upp-rosette-tcat-provisioner/Dockerfile b/upp-rosette-tcat-provisioner/Dockerfile new file mode 100644 index 0000000..92f387c --- /dev/null +++ b/upp-rosette-tcat-provisioner/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:20.04 + +COPY cloudformation /cloudformation/ +COPY sh/* /usr/local/bin/ +COPY config/* /config/ + +RUN chmod +x /usr/local/bin/* + +RUN apt-get update && apt-get install -y \ + less \ + zip \ + curl \ + wget \ + jq \ + vim \ + && rm -rf /var/lib/apt/lists/* \ + && get-latest-awscli2 diff --git a/upp-rosette-tcat-provisioner/Makefile b/upp-rosette-tcat-provisioner/Makefile new file mode 100644 index 0000000..c39069c --- /dev/null +++ b/upp-rosette-tcat-provisioner/Makefile @@ -0,0 +1,20 @@ +.PHONY: rosette-tcat-provisioner rosette-tcat destroy-rosette-tcat help + +help: ## Show this help. + @sed -ne '/@sed/!s/##//p' $(MAKEFILE_LIST) + +rosette-tcat-provisioner: ## Build provisioner + @docker build -t rosette-tcat-provisioner:local . + +rosette-tcat-instance: ## Deploy staging rosette tcat instance + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-tcat-provisioner:local provision.sh staging + +destroy-rosette-tcat: ## Destroys staging rosette tcat instance + @docker run --rm -it \ + -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \ + -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \ + rosette-tcat-provisioner:local destroy.sh staging + diff --git a/upp-rosette-tcat-provisioner/README.md b/upp-rosette-tcat-provisioner/README.md new file mode 100644 index 0000000..01a0e76 --- /dev/null +++ b/upp-rosette-tcat-provisioner/README.md @@ -0,0 +1,63 @@ +# upp-rosette-tcat-provisioner + +## Description + +This repository contains the files required to provision a Rosette TCAT instance + +1. [Install docker](https://docs.docker.com/engine/installation/) locally + +## Building the Docker image + +``` +make rosette-tcat-provisioner +``` + +## Provisioning a new instance + +Here are the steps for provisioning a new cluster: + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette Server. Get credentials for upp-rosette-provisioner user in PROD account and export them: +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will provision the stack in AWS: + +``` + +make rosette-tcat-instance + +``` + +## Deleting the instance + +1. [Build your docker image locally](#building-the-docker-image) +1. Set the environment variables to provision a Rosette Server. Get credentials for upp-rosette-provisioner user in PROD account and export them: + +``` +export AWS_ACCESS_KEY= +export AWS_SECRET_ACCESS_KEY= +export AWS_REGION= +``` + +1. Run the following that will decommission the stack in AWS: + +``` + +make destroy-rosette-tcat + +``` + +## Usage + +1. Connect to the instance: +Find the instance in AWS console (name ending at `*rosette-classifier`), take the IP address and ssh with: + +``` +ssh ec2-user@ +``` + +The app is installed under `/tcat` folder. The folder is mounted on persistent EBS drive. diff --git a/upp-rosette-tcat-provisioner/cloudformation/stack.yml b/upp-rosette-tcat-provisioner/cloudformation/stack.yml new file mode 100644 index 0000000..79865b4 --- /dev/null +++ b/upp-rosette-tcat-provisioner/cloudformation/stack.yml @@ -0,0 +1,120 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: This template deploys Rosette stack +Parameters: + VpcID: + Description: The ID of the VPC + Type: String + Default: vpc-ee57bf89 + EnvironmentName: + Description: An environment name that will be prefixed to resource names + Type: String + Default: prod + EnvironmentType: + Description: Tag detail for the Environment + Type: String + Default: p + Ec2InstanceType: + Description: Size of ec2 Instance + Type: String + Default: c5.xlarge + NetworkIface: + Description: Network interface to be attached + Type: String + Default: 'eni-0997c5e38141e1f64' + ImageID: + Description: The Image ID of Amazon Linux 2 kto use + Type: String + Default: 'ami-0069d66985b09d219' + VolumeId: + Description: Volume ID where the app lives + Type: String + Default: vol-0aaadc2118b7a2362 + MountDevice: + Description: Where to mount external Ebs + Type: String + Default: '/dev/xvdb' + TagTeamDL: + Description: Tag of the TeamDL + Type: String + AllowedPattern: ^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$ + ConstraintDescription: There must be a valid email address for the TeamDL + Default: universal.publishing.platform@ft.com + TagSystemCode: + Description: The system code for the environment + Type: String + Default: upp + TagDescription: + Description: Tag detail for the describing the instance + Type: String + Default: UPP Rosette stack + TagName: + Description: Name of the instance + Type: String + Default: upp-rosette-tcat-instance + +Resources: + RosetteTCATInstance: + Type: AWS::EC2::Instance + Properties: + ImageId: !Ref ImageID + IamInstanceProfile: "FT-EC2-Role" + InstanceType: !Ref Ec2InstanceType + KeyName: "upp-k8s-provisioning-debug" + NetworkInterfaces: + - NetworkInterfaceId: !Ref NetworkIface + DeviceIndex: 0 + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeType: gp3 + VolumeSize: 80 + Encrypted: false + Volumes: + - Device: !Ref MountDevice + VolumeId: !Ref VolumeId + UserData: + Fn::Base64: !Sub | + #!/bin/bash -x + yum update -y + yum install git -y + cat > /etc/systemd/system/authorized_keys.service << EOF + [Unit] + Description=Update authorized_keys + [Service] + Type=oneshot + ExecStartPre=/bin/sh -c 'mkdir -p /home/ec2-user/.ssh && touch /home/ec2-user/.ssh/authorized_keys' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys.sha512 https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys.sha512' + ExecStart=/bin/sh -c 'curl -sSL --retry 5 --retry-delay 2 -o /tmp/authorized_keys https://raw.githubusercontent.com/Financial-Times/up-ssh-keys/master/authorized_keys' + ExecStart=/bin/sh -c 'cd /tmp/ && sha512sum -c authorized_keys.sha512 && cp authorized_keys /home/ec2-user/.ssh/authorized_keys && chmod 700 /home/ec2-user/.ssh && chmod 600 /home/ec2-user/.ssh/authorized_keys && chown -R ec2-user:ec2-user /home/ec2-user/.ssh' + Restart=no + EOF + + systemctl start authorized_keys.service + systemctl enable authorized_keys.service + + cat > /etc/systemd/system/authorized_keys.timer << EOF + [Unit] + Description=Authorized keys timer + [Timer] + OnBootSec=1min + OnUnitActiveSec=1min + [Install] + WantedBy=timers.target + EOF + + systemctl start authorized_keys.timer + systemctl enable authorized_keys.timer + + echo ${MountDevice} /tcat ext4 defaults,nofail 0 2 >> /etc/fstab + mkdir /tcat + mount ${MountDevice} /tcat + yum install -y java-1.8.0-openjdk + Tags: + - Key: "Name" + Value: !Ref TagName + - Key: "systemCode" + Value: !Ref TagSystemCode + - Key: "teamDL" + Value: !Ref TagTeamDL + - Key: "environment" + Value: !Ref EnvironmentType diff --git a/upp-rosette-tcat-provisioner/config/rosette-staging.sh b/upp-rosette-tcat-provisioner/config/rosette-staging.sh new file mode 100644 index 0000000..6cb5514 --- /dev/null +++ b/upp-rosette-tcat-provisioner/config/rosette-staging.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +export AWS_REGION="eu-west-1" +export SUBNET_IDS="subnet-024e2e32aefaa01c5" +export EnvironmentType="t" +export EnvironmentName='staging' +export IMAGE_ID="ami-0069d66985b09d219" +export VpcID="vpc-ee57bf89" +export CF_STACK_NAME="upp-staging-rosette-tcat" diff --git a/upp-rosette-tcat-provisioner/sh/destroy.sh b/upp-rosette-tcat-provisioner/sh/destroy.sh new file mode 100644 index 0000000..da1a2e7 --- /dev/null +++ b/upp-rosette-tcat-provisioner/sh/destroy.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + + +aws cloudformation delete-stack \ + --stack-name "${CF_STACK_NAME}" diff --git a/upp-rosette-tcat-provisioner/sh/get-latest-awscli2 b/upp-rosette-tcat-provisioner/sh/get-latest-awscli2 new file mode 100644 index 0000000..c8755b1 --- /dev/null +++ b/upp-rosette-tcat-provisioner/sh/get-latest-awscli2 @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +cd /tmp + +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +./aws/install --update + +rm awscliv2.zip +rm -rf ./aws/ diff --git a/upp-rosette-tcat-provisioner/sh/provision.sh b/upp-rosette-tcat-provisioner/sh/provision.sh new file mode 100644 index 0000000..60cec47 --- /dev/null +++ b/upp-rosette-tcat-provisioner/sh/provision.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +CF_STACK_TEMPLATE="/cloudformation/stack.yml" +ENVIRONMENT_NAME=$1 + +INSTANCE_CONFIG="/config/rosette-${ENVIRONMENT_NAME}.sh" + +if [ ! -f "${INSTANCE_CONFIG}" ]; then + echo "No configuration exist at ${INSTANCE_CONFIG}" + exit 1 +else + source "${INSTANCE_CONFIG}" +fi + + +aws cloudformation deploy \ + --stack-name "${CF_STACK_NAME}" \ + --region "${AWS_REGION}" \ + --template-file "${CF_STACK_TEMPLATE}" \ + --parameter-overrides \ + SubnetIds="${SUBNET_IDS}" \ + EnvironmentName="${EnvironmentName}" \ + EnvironmentType="${EnvironmentType}" +