Skip to content

Commit

Permalink
infrastructure wiring for graphiql. Note new param required before th…
Browse files Browse the repository at this point in the history
…is will deploy.
  • Loading branch information
fredex42 committed Mar 17, 2024
1 parent 4fe4cc4 commit 630a5b8
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 0 deletions.
119 changes: 119 additions & 0 deletions cdk/lib/__snapshots__/concierge-graphql.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,20 @@ exports[`The ConciergeGraphql stack matches the snapshot 1`] = `
"GuSecurityGroup",
"GuSecurityGroup",
"GuSecurityGroup",
"GuStringParameter",
"GuStringParameter",
],
"gu:cdk:version": "TEST",
},
"Outputs": {
"ExplorerDistroUrlOut91AD6AD6": {
"Value": {
"Fn::GetAtt": [
"GraphiQLDistroDDB6F81C",
"DomainName",
],
},
},
"LoadBalancerConciergegraphqlDnsName": {
"Description": "DNS entry for LoadBalancerConciergegraphql",
"Value": {
Expand All @@ -51,6 +61,11 @@ exports[`The ConciergeGraphql stack matches the snapshot 1`] = `
"Description": "SSM parameter containing the S3 bucket name holding distribution artifacts",
"Type": "AWS::SSM::Parameter::Value<String>",
},
"ExplorerCertArn": {
"Default": "/TEST/content-api/graphiql-explorer/GlobalCertArn",
"Description": "Cert to use for graphiql TEST. This must reside in us-east-1",
"Type": "AWS::SSM::Parameter::Value<String>",
},
"LoggingStreamName": {
"Default": "/account/services/logging.stream.name",
"Description": "SSM parameter containing the Name (not ARN) on the kinesis stream",
Expand All @@ -60,6 +75,11 @@ exports[`The ConciergeGraphql stack matches the snapshot 1`] = `
"Default": "/account/services/capi.gutools/TEST/hostedzoneid",
"Type": "AWS::SSM::Parameter::Value<String>",
},
"StaticBucketName": {
"Default": "/account/services/static.serving.bucket",
"Description": "SSM parameter giving the name of a bucket which is to be used for static hosting",
"Type": "AWS::SSM::Parameter::Value<String>",
},
"subnets": {
"Default": "/account/vpc/PROD-live/subnets",
"Description": "Subnets to deploy into",
Expand Down Expand Up @@ -415,6 +435,105 @@ exports[`The ConciergeGraphql stack matches the snapshot 1`] = `
},
"Type": "AWS::IAM::Policy",
},
"GraphiQLDistroDDB6F81C": {
"Properties": {
"DistributionConfig": {
"Aliases": [
"graphiql.capi.test.dev-gutools.co.uk",
],
"CustomErrorResponses": [
{
"ErrorCachingMinTTL": 5,
"ErrorCode": 403,
"ResponseCode": 200,
"ResponsePagePath": "/index.html",
},
],
"DefaultCacheBehavior": {
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true,
"TargetOriginId": "ConciergeGraphqlGraphiQLDistroOrigin1368BF828",
"ViewerProtocolPolicy": "redirect-to-https",
},
"DefaultRootObject": "index.html",
"Enabled": true,
"HttpVersion": "http2",
"IPV6Enabled": true,
"Origins": [
{
"DomainName": {
"Fn::Join": [
"",
[
{
"Ref": "StaticBucketName",
},
".s3.",
{
"Ref": "AWS::Region",
},
".",
{
"Ref": "AWS::URLSuffix",
},
],
],
},
"Id": "ConciergeGraphqlGraphiQLDistroOrigin1368BF828",
"OriginPath": "/TEST/graphiql-explorer",
"S3OriginConfig": {
"OriginAccessIdentity": {
"Fn::Join": [
"",
[
"origin-access-identity/cloudfront/",
{
"Ref": "GraphiQLDistroOrigin1S3OriginE0C3987C",
},
],
],
},
},
},
],
"PriceClass": "PriceClass_100",
"ViewerCertificate": {
"AcmCertificateArn": {
"Ref": "ExplorerCertArn",
},
"MinimumProtocolVersion": "TLSv1.2_2021",
"SslSupportMethod": "sni-only",
},
},
"Tags": [
{
"Key": "gu:cdk:version",
"Value": "TEST",
},
{
"Key": "gu:repo",
"Value": "guardian/concierge-graphql",
},
{
"Key": "Stack",
"Value": "content-api",
},
{
"Key": "Stage",
"Value": "TEST",
},
],
},
"Type": "AWS::CloudFront::Distribution",
},
"GraphiQLDistroOrigin1S3OriginE0C3987C": {
"Properties": {
"CloudFrontOriginAccessIdentityConfig": {
"Comment": "Identity for ConciergeGraphqlGraphiQLDistroOrigin1368BF828",
},
},
"Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity",
},
"GuHttpsEgressSecurityGroupConciergegraphql1855BF47": {
"Properties": {
"GroupDescription": "Allow all outbound HTTPS traffic",
Expand Down
4 changes: 4 additions & 0 deletions cdk/lib/concierge-graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {AttributeType, BillingMode, Table} from "aws-cdk-lib/aws-dynamodb";
import {GuPolicy} from "@guardian/cdk/lib/constructs/iam";
import {Effect, PolicyStatement} from "aws-cdk-lib/aws-iam";
import {StringParameter} from "aws-cdk-lib/aws-ssm";
import {GraphiqlExplorer} from "./graphiql-explorer";

export class ConciergeGraphql extends GuStack {
constructor(scope: App, id: string, props: GuStackProps) {
Expand Down Expand Up @@ -127,6 +128,9 @@ export class ConciergeGraphql extends GuStack {

autoScalingGroup.connections.allowTo(Peer.ipv4("10.0.0.0/8"), Port.tcp(9200), "Allow outgoing connections to Elasticsearch");

new GraphiqlExplorer(this, "Explorer", {
appName: "graphiql-explorer" //needs to match the value in riff-raff.yaml
})
//OK - so this is a good idea and should really be in here. But it's damn fiddly so leaving it out for now.
//The idea is we need a connection to the relevant Elasticsearch instance. So, we define a "connection" (which basically
//to an egress rule) on our SG which allows egress to the remote ES SG. You still manually need to add a rule on the relevant
Expand Down
5 changes: 5 additions & 0 deletions cdk/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const hostingDomain:Record<string,string> = {
"PROD-AARDVARK": "graphiql.capi.gutools.co.uk",
"CODE-AARDVARK": "graphiql.capi.code.dev-gutools.co.uk",
"TEST": "graphiql.capi.test.dev-gutools.co.uk"
};
85 changes: 85 additions & 0 deletions cdk/lib/graphiql-explorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {Construct} from "constructs";
import type { GuStack } from "@guardian/cdk/lib/constructs/core";
import { GuStringParameter } from "@guardian/cdk/lib/constructs/core";
import { CfnOutput, Duration } from "aws-cdk-lib";
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
import {
Distribution,
PriceClass,
ViewerProtocolPolicy
} from "aws-cdk-lib/aws-cloudfront";
import { RestApiOrigin, S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
import { Effect, PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { Bucket } from "aws-cdk-lib/aws-s3";
import {hostingDomain} from "./constants";

interface GraphiqlExplorerProps {
appName: string;

}

export class GraphiqlExplorer extends Construct {
constructor(scope: GuStack, id: string, props: GraphiqlExplorerProps) {
super(scope, id);

//We can't create the cert here, because it must live in us-east-1 for Cloudfront to use it.
const hostingCertArn = new GuStringParameter(scope, "ExplorerCertArn", {
fromSSM: true,
default: `/${scope.stage}/${scope.stack}/${props.appName}/GlobalCertArn`,
description: `Cert to use for graphiql ${scope.stage}. This must reside in us-east-1`,
});
const certificate = Certificate.fromCertificateArn(this, "CapiExplorerCert", hostingCertArn.valueAsString);

const staticBucketNameParam = new GuStringParameter(scope, "StaticBucketName", {
fromSSM: true,
default: `/account/services/static.serving.bucket`,
description: "SSM parameter giving the name of a bucket which is to be used for static hosting"
});

const hostingBucket = Bucket.fromBucketName(this, "StaticBucket", staticBucketNameParam.valueAsString);

const distro = new Distribution(scope, "GraphiQLDistro", {
defaultRootObject: "index.html",
certificate,
domainNames: [hostingDomain[scope.stage]],
defaultBehavior: {
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
origin: new S3Origin(hostingBucket, {
originPath: `${scope.stage}/${props.appName}`
}),
},
enableIpv6: true,
enabled: true,
priceClass: PriceClass.PRICE_CLASS_100, //US & EU only
/*
we must tell Cloudfront to redirect 403 (forbidden/not present) exceptions from S3 into a 200 response from /index in order for react-router to work.
*/
errorResponses: [
{
httpStatus: 403,
responseHttpStatus: 200,
responsePagePath: "/index.html",
ttl: Duration.seconds(5),
}
]
});

hostingBucket.addToResourcePolicy(new PolicyStatement({
effect: Effect.ALLOW,
principals: [
new ServicePrincipal("cloudfront.amazonaws.com"),
],
actions: ["s3:GetObject"],
resources: [`arn:aws:s3:::${hostingBucket.bucketName}/${scope.stage}/${props.appName}`],
conditions: {
"StringEquals": {
"AWS:SourceArn": `arn:aws:cloudfront::${scope.account}:distribution/${distro.distributionId}`
}
}
}));

new CfnOutput(this, "DistroUrlOut", {
value: distro.distributionDomainName,
});
}
}

0 comments on commit 630a5b8

Please sign in to comment.