From 55b70cc4e0180c5d66fb78247aaf35dc894a24c2 Mon Sep 17 00:00:00 2001 From: Nicolas Long Date: Mon, 14 Nov 2022 10:23:57 +0000 Subject: [PATCH 1/2] feat: add Google Auth to EC2 patterns --- src/patterns/ec2-app/base.ts | 61 ++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/patterns/ec2-app/base.ts b/src/patterns/ec2-app/base.ts index 7999eb9f25..b80447c759 100644 --- a/src/patterns/ec2-app/base.ts +++ b/src/patterns/ec2-app/base.ts @@ -1,10 +1,11 @@ -import { Duration, Tags } from "aws-cdk-lib"; +import { Duration, SecretValue, Tags } from "aws-cdk-lib"; import type { BlockDevice } from "aws-cdk-lib/aws-autoscaling"; import { HealthCheck } from "aws-cdk-lib/aws-autoscaling"; import type { InstanceType, IPeer, ISubnet, IVpc } from "aws-cdk-lib/aws-ec2"; import { Port } from "aws-cdk-lib/aws-ec2"; -import { ApplicationProtocol } from "aws-cdk-lib/aws-elasticloadbalancingv2"; +import { ApplicationProtocol, ListenerAction } from "aws-cdk-lib/aws-elasticloadbalancingv2"; import { Bucket } from "aws-cdk-lib/aws-s3"; +import { StringParameter } from "aws-cdk-lib/aws-ssm"; import { Construct } from "constructs"; import { AccessScope, MetadataKeys, NAMED_SSM_PARAMETER_PATHS } from "../../constants"; import { GuCertificate } from "../../constructs/acm"; @@ -62,6 +63,30 @@ export interface Alarms { noMonitoring?: false; } +/** + * Use this to enable Google Auth for your service. + * + * As part of using this, you'll need to do two things: + * + * 1. Create a Google app and generate OAuth credentials for it. Store the + * `clientSecret` in AWS Secrets Manager. See the `clientSecretPath` prop for + * the path to use here (and override the default if required). + * 2. Update your app to validate the JWT token, which gets passed via the + * `x-amzn-oidc-data` header. You MUST check that the token is a valid JWT + * token - specifically, that it was encoded using the `ES256` algorithm and + * that the contained `email` claim is a @guardian.co.uk email address. + * + * For more information, see: + * https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html. + */ +export interface GoogleAuthProps { + clientId: string; + + // The Secrets Manager path containing your Google Client Secret. Defaults to + // `/:STAGE/:stack/:app/googleClientSecret`. + clientSecretPath?: string; +} + /** * Configuration options for the [[`GuEc2App`]] pattern. * @@ -146,6 +171,7 @@ export interface GuEc2AppProps extends AppIdentity { vpc?: IVpc; privateSubnets?: ISubnet[]; publicSubnets?: ISubnet[]; + googleAuth?: GoogleAuthProps; } function restrictedCidrRanges(ranges: IPeer[]) { @@ -476,6 +502,37 @@ export class GuEc2App extends Construct { open: access.scope === AccessScope.PUBLIC, }); + if (props.googleAuth) { + const configPrefix = `${scope.stage}/${scope.stack}/${app}`; + const clientId = StringParameter.fromStringParameterAttributes(this, "clientID", { + parameterName: `/${configPrefix}/googleClientID`, + }).stringValue; + + const secretPath = props.googleAuth.clientSecretPath ?? `${configPrefix}/clientSecret`; + const clientSecret = SecretValue.secretsManager(secretPath); + + const authAction = ListenerAction.authenticateOidc({ + next: ListenerAction.forward([targetGroup]), + clientId: clientId, + clientSecret: clientSecret, + scope: "openid email", + + // See the `hd` section of + // https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters. + // Note, this is NOT sufficient to ensure access is limited to Guardian + // emails. Users should also validate the token and check the domain in + // their app. + authenticationRequestExtraParams: { hd: "guardian.co.uk" }, + + authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", + issuer: "https://accounts.google.com", + tokenEndpoint: "https://oauth2.googleapis.com/token", + userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo", + }); + + listener.addAction("auth", { action: authAction }); + } + // Since AWS won't create a security group automatically when open=false, we need to add our own if (access.scope !== AccessScope.PUBLIC) { loadBalancer.addSecurityGroup( From c23945289bd6b7994ff889d058c66d81aae4912c Mon Sep 17 00:00:00 2001 From: Nicolas Long Date: Mon, 14 Nov 2022 17:18:33 +0000 Subject: [PATCH 2/2] Tag use of EC2 Google auth for tracking purposes --- src/constants/metadata-keys.ts | 1 + src/patterns/ec2-app/base.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/constants/metadata-keys.ts b/src/constants/metadata-keys.ts index 33e4de7172..824ed0b13e 100644 --- a/src/constants/metadata-keys.ts +++ b/src/constants/metadata-keys.ts @@ -5,4 +5,5 @@ export const MetadataKeys = { PATTERN_NAME: "gu:cdk:pattern-name", LOG_KINESIS_STREAM_NAME: "LogKinesisStreamName", SYSTEMD_UNIT: "SystemdUnit", + CDK_FEATURE: "gu:cdk:feature", }; diff --git a/src/patterns/ec2-app/base.ts b/src/patterns/ec2-app/base.ts index b80447c759..7343ed5c45 100644 --- a/src/patterns/ec2-app/base.ts +++ b/src/patterns/ec2-app/base.ts @@ -531,6 +531,8 @@ export class GuEc2App extends Construct { }); listener.addAction("auth", { action: authAction }); + + Tags.of(loadBalancer).add(MetadataKeys.CDK_FEATURE, "google-auth"); } // Since AWS won't create a security group automatically when open=false, we need to add our own