in cdk/lib/amigo.ts [110:318]
constructor(scope: App, id: string, props: AmigoProps) {
super(scope, id, props);
const { domainName } = props;
this.packerInstanceProfile = new GuStringParameter(this, "PackerInstanceProfile", {
description:
"Instance profile given to instances created by Packer. Find this in the PackerUser-PackerRole in IAM",
});
this.dataBucket = new GuS3Bucket(this, "AmigoDataBucket", {
app: AmigoStack.app.app,
bucketName: `amigo-data-${this.stage.toLowerCase()}`,
});
this.overrideLogicalId(this.dataBucket, {
logicalId: "AmigoDataBucket",
reason: "To prevent orphaning of the YAML defined bucket",
});
const ssmPolicy = new Policy(this, "SSMPolicy", {
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
resources: ["*"],
actions: [
// Standard SSM permissions
"ec2messages:AcknowledgeMessage",
"ec2messages:DeleteMessage",
"ec2messages:FailMessage",
"ec2messages:GetEndpoint",
"ec2messages:GetMessages",
"ec2messages:SendReply",
"ssm:UpdateInstanceInformation",
"ssm:ListInstanceAssociations",
"ssm:DescribeInstanceProperties",
"ssm:DescribeDocumentParameters",
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel",
// required to allow Packer to run SSM commands
// see https://github.com/guardian/amigo/pull/526 and https://github.com/guardian/amigo/pull/538
"ssm:StartSession",
"ssm:TerminateSession",
],
}),
],
});
const policiesToAttachToRootRole: Policy[] = [
ssmPolicy,
GuLogShippingPolicy.getInstance(this),
new GuAllowPolicy(this, "PackerPolicy", {
policyName: "packer-required-permissions",
resources: ["*"],
actions: [
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CopyImage",
"ec2:CreateImage",
"ec2:CreateKeypair",
"ec2:CreateSecurityGroup",
"ec2:CreateSnapshot",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteKeypair",
"ec2:DeleteSecurityGroup",
"ec2:DeleteSnapshot",
"ec2:DeleteVolume",
"ec2:DeregisterImage",
"ec2:DescribeImageAttribute",
"ec2:DescribeImages",
"ec2:DescribeInstances",
"ec2:DescribeRegions",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSnapshots",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:GetPasswordData",
"ec2:ModifyImageAttribute",
"ec2:ModifyInstanceAttribute",
"ec2:ModifySnapshotAttribute",
"ec2:RegisterImage",
"ec2:RunInstances",
"ec2:StopInstances",
"ec2:TerminateInstances",
"iam:PassRole",
],
}),
GuDescribeEC2Policy.getInstance(this),
GuAnghammaradSenderPolicy.getInstance(this),
this.appPolicy,
];
const sg = new GuSecurityGroup(this, "PackerSecurityGroup", {
...AmigoStack.app,
vpc: GuVpc.fromIdParameter(this, "vpc"),
// The security group name is added to config.
// See `app/packer/PackerConfig.scala`.
securityGroupName: `amigo-packer-${this.stage}`,
description: "Security group for instances created by Packer",
// When true, `allowAllOutbound` will also allow all outbound UDP traffic
allowAllOutbound: false,
egresses: [
{
port: Port.tcpRange(0, 65535),
range: Peer.anyIpv4(),
description: "Allow all outbound TCP",
},
],
});
this.overrideLogicalId(sg, {
logicalId: "PackerSecurityGroup",
reason:
"Keeping the same resource for simplicity. We would otherwise have to update the stack when there are no ongoing bakes, i.e. when the security group isn't in use.",
});
const distBucket = GuDistributionBucketParameter.getInstance(this).valueAsString;
const artifactPath = [distBucket, this.stack, this.stage, AmigoStack.app.app, "amigo_1.0-latest_all.deb"].join("/");
const guPlayApp = new GuPlayApp(this, {
...AmigoStack.app,
instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.MEDIUM),
userData: UserData.custom([
"#!/bin/bash -ev",
`wget -P /tmp https://releases.hashicorp.com/packer/${packerVersion}/packer_${packerVersion}_linux_arm64.zip`,
"mkdir /opt/packer",
"unzip -d /opt/packer /tmp/packer_*_linux_arm64.zip",
"echo 'export PATH=${!PATH}:/opt/packer' > /etc/profile.d/packer.sh",
"wget -P /tmp https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_arm64/session-manager-plugin.deb",
"dpkg -i /tmp/session-manager-plugin.deb",
"mkdir /amigo",
`aws --region eu-west-1 s3 cp s3://${distBucket}/${this.stack}/${this.stage}/${AmigoStack.app.app}/conf/amigo-service-account-cert.json /amigo/`,
`aws --region eu-west-1 s3 cp s3://${artifactPath} /tmp/amigo.deb`,
"dpkg -i /tmp/amigo.deb",
].join("\n")),
access: {
scope: AccessScope.PUBLIC,
},
certificateProps: { domainName },
scaling: { minimumInstances: 1 },
monitoringConfiguration: {
noMonitoring: true,
},
roleConfiguration: {
additionalPolicies: policiesToAttachToRootRole,
},
applicationLogging: {
enabled: true,
},
});
// Ensure LB can egress to 443 (for Google endpoints) for OIDC flow.
const albEgressSg = new GuHttpsEgressSecurityGroup(this, "IdP-access", {
app: AmigoStack.app.app,
vpc: guPlayApp.vpc,
});
guPlayApp.loadBalancer.addSecurityGroup(albEgressSg);
// This parameter is used by https://github.com/guardian/waf
new StringParameter(this, "AlbSsmParam", {
parameterName: `/infosec/waf/services/${this.stage}/amigo-alb-arn`,
description: `The arn of the ALB for amigo-${this.stage}. N.B. this parameter is created via cdk`,
simpleName: false,
stringValue: guPlayApp.loadBalancer.loadBalancerArn,
tier: ParameterTier.STANDARD,
dataType: ParameterDataType.TEXT,
});
const clientId = new GuStringParameter(this, "ClientId", {
description: "Google OAuth client ID",
});
guPlayApp.listener.addAction("Google Auth", {
action: ListenerAction.authenticateOidc({
authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
issuer: "https://accounts.google.com",
scope: "openid",
authenticationRequestExtraParams: { hd: "guardian.co.uk" },
onUnauthenticatedRequest: UnauthenticatedAction.AUTHENTICATE,
tokenEndpoint: "https://oauth2.googleapis.com/token",
userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo",
clientId: clientId.valueAsString,
clientSecret: SecretValue.secretsManager(`/${this.stage}/deploy/amigo/clientSecret`),
next: ListenerAction.forward([guPlayApp.targetGroup]),
}),
});
new GuCname(this, "DnsRecord", {
app: AmigoStack.app.app,
domainName: domainName,
ttl: Duration.hours(1),
resourceRecord: guPlayApp.loadBalancer.loadBalancerDnsName,
});
}