Skip to content

Commit ab39462

Browse files
committed
feat: RestApiGateway construct
1 parent bec1d82 commit ab39462

5 files changed

Lines changed: 601 additions & 7 deletions

File tree

package-lock.json

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import {Fn, RemovalPolicy} from "aws-cdk-lib"
2+
import {
3+
CfnStage,
4+
EndpointType,
5+
LogGroupLogDestination,
6+
MethodLoggingLevel,
7+
MTLSConfig,
8+
RestApi,
9+
SecurityPolicy
10+
} from "aws-cdk-lib/aws-apigateway"
11+
import {
12+
IManagedPolicy,
13+
IRole,
14+
ManagedPolicy,
15+
PolicyStatement,
16+
Role,
17+
ServicePrincipal
18+
} from "aws-cdk-lib/aws-iam"
19+
import {Stream} from "aws-cdk-lib/aws-kinesis"
20+
import {Key} from "aws-cdk-lib/aws-kms"
21+
import {CfnSubscriptionFilter, LogGroup} from "aws-cdk-lib/aws-logs"
22+
import {Construct} from "constructs"
23+
import {accessLogFormat} from "./RestApiGateway/accessLogFormat.js"
24+
import {Certificate, CertificateValidation} from "aws-cdk-lib/aws-certificatemanager"
25+
import {Bucket} from "aws-cdk-lib/aws-s3"
26+
import {BucketDeployment, Source} from "aws-cdk-lib/aws-s3-deployment"
27+
import {ARecord, HostedZone, RecordTarget} from "aws-cdk-lib/aws-route53"
28+
import {ApiGateway as ApiGatewayTarget} from "aws-cdk-lib/aws-route53-targets"
29+
import {NagSuppressions} from "cdk-nag"
30+
31+
export interface RestApiGatewayProps {
32+
readonly stackName: string
33+
readonly logRetentionInDays: number
34+
readonly mutualTlsTrustStoreKey: string | undefined
35+
readonly forwardCsocLogs: boolean
36+
readonly csocApiGatewayDestination: string
37+
readonly executionPolicies: Array<IManagedPolicy>
38+
}
39+
40+
export class RestApiGateway extends Construct {
41+
public readonly api: RestApi
42+
public readonly role: IRole
43+
44+
public constructor(scope: Construct, id: string, props: RestApiGatewayProps) {
45+
super(scope, id)
46+
47+
// Imports
48+
const cloudWatchLogsKmsKey = Key.fromKeyArn(
49+
this, "cloudWatchLogsKmsKey", Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn"))
50+
51+
const splunkDeliveryStream = Stream.fromStreamArn(
52+
this, "SplunkDeliveryStream", Fn.importValue("lambda-resources:SplunkDeliveryStream"))
53+
54+
const splunkSubscriptionFilterRole = Role.fromRoleArn(
55+
this, "splunkSubscriptionFilterRole", Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole"))
56+
57+
const trustStoreBucket = Bucket.fromBucketArn(
58+
this, "TrustStoreBucket", Fn.importValue("account-resources:TrustStoreBucket"))
59+
60+
const trustStoreDeploymentBucket = Bucket.fromBucketArn(
61+
this, "TrustStoreDeploymentBucket", Fn.importValue("account-resources:TrustStoreDeploymentBucket"))
62+
63+
const trustStoreBucketKmsKey = Key.fromKeyArn(
64+
this, "TrustStoreBucketKmsKey", Fn.importValue("account-resources:TrustStoreBucketKMSKey"))
65+
66+
const epsDomainName: string = Fn.importValue("eps-route53-resources:EPS-domain")
67+
const hostedZone = HostedZone.fromHostedZoneAttributes(this, "HostedZone", {
68+
hostedZoneId: Fn.importValue("eps-route53-resources:EPS-ZoneID"),
69+
zoneName: epsDomainName
70+
})
71+
const serviceDomainName = `${props.stackName}.${epsDomainName}`
72+
73+
// Resources
74+
const logGroup = new LogGroup(this, "ApiGatewayAccessLogGroup", {
75+
encryptionKey: cloudWatchLogsKmsKey,
76+
logGroupName: `/aws/apigateway/${props.stackName}-apigw`,
77+
retention: props.logRetentionInDays,
78+
removalPolicy: RemovalPolicy.DESTROY
79+
})
80+
81+
new CfnSubscriptionFilter(this, "ApiGatewayAccessLogsSplunkSubscriptionFilter", {
82+
destinationArn: splunkDeliveryStream.streamArn,
83+
filterPattern: "",
84+
logGroupName: logGroup.logGroupName,
85+
roleArn: splunkSubscriptionFilterRole.roleArn
86+
})
87+
88+
if (props.forwardCsocLogs) {
89+
new CfnSubscriptionFilter(this, "ApiGatewayAccessLogsCSOCSubscriptionFilter", {
90+
destinationArn: props.csocApiGatewayDestination,
91+
filterPattern: "",
92+
logGroupName: logGroup.logGroupName,
93+
roleArn: splunkSubscriptionFilterRole.roleArn
94+
})
95+
}
96+
97+
const certificate = new Certificate(this, "Certificate", {
98+
domainName: serviceDomainName,
99+
validation: CertificateValidation.fromDns(hostedZone)
100+
})
101+
102+
let mtlsConfig: MTLSConfig | undefined
103+
104+
if (props.mutualTlsTrustStoreKey) {
105+
const trustStoreKeyPrefix = `cpt-api/${props.stackName}-truststore`
106+
const logGroup = new LogGroup(scope, "LambdaLogGroup", {
107+
encryptionKey: cloudWatchLogsKmsKey,
108+
logGroupName: `/aws/lambda/${props.stackName}-truststore-deployment`,
109+
retention: props.logRetentionInDays,
110+
removalPolicy: RemovalPolicy.DESTROY
111+
})
112+
const trustStoreDeploymentPolicy = new ManagedPolicy(this, "TrustStoreDeploymentPolicy", {
113+
statements: [
114+
new PolicyStatement({
115+
actions: [
116+
"s3:ListBucket"
117+
],
118+
resources: [
119+
trustStoreBucket.bucketArn,
120+
trustStoreDeploymentBucket.bucketArn
121+
]
122+
}),
123+
new PolicyStatement({
124+
actions: [
125+
"s3:GetObject"
126+
],
127+
resources: [trustStoreBucket.arnForObjects(props.mutualTlsTrustStoreKey)]
128+
}),
129+
new PolicyStatement({
130+
actions: [
131+
"s3:DeleteObject",
132+
"s3:PutObject"
133+
],
134+
resources: [
135+
trustStoreDeploymentBucket.arnForObjects(trustStoreKeyPrefix + "/" + props.mutualTlsTrustStoreKey)
136+
]
137+
}),
138+
new PolicyStatement({
139+
actions: [
140+
"kms:Decrypt",
141+
"kms:Encrypt",
142+
"kms:GenerateDataKey"
143+
],
144+
resources: [trustStoreBucketKmsKey.keyArn]
145+
}),
146+
new PolicyStatement({
147+
actions: [
148+
"logs:CreateLogStream",
149+
"logs:PutLogEvents"
150+
],
151+
resources: [
152+
logGroup.logGroupArn,
153+
`${logGroup.logGroupArn}:log-stream:*`
154+
]
155+
})
156+
]
157+
})
158+
NagSuppressions.addResourceSuppressions(trustStoreDeploymentPolicy, [
159+
{
160+
id: "AwsSolutions-IAM5",
161+
// eslint-disable-next-line max-len
162+
reason: "Suppress error for not having wildcards in permissions. This is a fine as we need to have permissions on all log streams under path"
163+
}
164+
])
165+
const trustStoreDeploymentRole = new Role(this, "TrustStoreDeploymentRole", {
166+
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
167+
managedPolicies: [trustStoreDeploymentPolicy]
168+
}).withoutPolicyUpdates()
169+
const deployment = new BucketDeployment(this, "TrustStoreDeployment", {
170+
sources: [Source.bucket(trustStoreBucket, props.mutualTlsTrustStoreKey)],
171+
destinationBucket: trustStoreDeploymentBucket,
172+
destinationKeyPrefix: trustStoreKeyPrefix,
173+
extract: false,
174+
retainOnDelete: false,
175+
role: trustStoreDeploymentRole,
176+
logGroup: logGroup
177+
})
178+
mtlsConfig = {
179+
bucket: deployment.deployedBucket,
180+
key: trustStoreKeyPrefix + "/" + props.mutualTlsTrustStoreKey
181+
}
182+
}
183+
184+
const apiGateway = new RestApi(this, "ApiGateway", {
185+
restApiName: `${props.stackName}-apigw`,
186+
domainName: {
187+
domainName: serviceDomainName,
188+
certificate: certificate,
189+
securityPolicy: SecurityPolicy.TLS_1_2,
190+
endpointType: EndpointType.REGIONAL,
191+
mtls: mtlsConfig
192+
},
193+
disableExecuteApiEndpoint: mtlsConfig ? true : false,
194+
endpointConfiguration: {
195+
types: [EndpointType.REGIONAL]
196+
},
197+
deploy: true,
198+
deployOptions: {
199+
accessLogDestination: new LogGroupLogDestination(logGroup),
200+
accessLogFormat: accessLogFormat(),
201+
loggingLevel: MethodLoggingLevel.INFO,
202+
metricsEnabled: true
203+
}
204+
})
205+
206+
const role = new Role(this, "ApiGatewayRole", {
207+
assumedBy: new ServicePrincipal("apigateway.amazonaws.com"),
208+
managedPolicies: props.executionPolicies
209+
}).withoutPolicyUpdates()
210+
211+
new ARecord(this, "ARecord", {
212+
recordName: props.stackName,
213+
target: RecordTarget.fromAlias(new ApiGatewayTarget(apiGateway)),
214+
zone: hostedZone
215+
})
216+
217+
const cfnStage = apiGateway.deploymentStage.node.defaultChild as CfnStage
218+
cfnStage.cfnOptions.metadata = {
219+
guard: {
220+
SuppressedRules: [
221+
"API_GW_CACHE_ENABLED_AND_ENCRYPTED"
222+
]
223+
}
224+
}
225+
226+
// Outputs
227+
this.api = apiGateway
228+
this.role = role
229+
}
230+
}

0 commit comments

Comments
 (0)