Skip to content

Commit 131de5a

Browse files
authored
add ubuntu agent and cdn with lambda@edge resource for public access (#134)
Signed-off-by: Rishabh Singh <sngri@amazon.com>
1 parent 6421528 commit 131de5a

17 files changed

+791
-12
lines changed

.github/workflows/cdk.yml

+3
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ jobs:
1515
- name: Run CDK Build and Test
1616
run: |
1717
npm install
18+
cd resources/cf-url-rewriter
19+
npm install
20+
cd -
1821
npm run build

bin/ci-stack.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { App } from '@aws-cdk/core';
1010
import { CIStack } from '../lib/ci-stack';
1111
import { CIConfigStack } from '../lib/ci-config-stack';
12+
import { CiCdnStack } from '../lib/ci-cdn-stack';
1213

1314
const app = new App();
1415

@@ -17,3 +18,6 @@ const defaultEnv: string = 'Dev';
1718
const ciConfigStack = new CIConfigStack(app, `OpenSearch-CI-Config-${defaultEnv}`, {});
1819

1920
const ciStack = new CIStack(app, `OpenSearch-CI-${defaultEnv}`, {});
21+
22+
const ciCdnStack = new CiCdnStack(app, `OpenSearch-CI-Cdn-${defaultEnv}`, {});
23+
ciCdnStack.addDependency(ciStack);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
import { Bucket } from '@aws-cdk/aws-s3';
10+
import {
11+
CloudFrontAllowedMethods, CloudFrontWebDistribution, LambdaEdgeEventType, OriginAccessIdentity,
12+
} from '@aws-cdk/aws-cloudfront';
13+
import { CanonicalUserPrincipal, PolicyStatement } from '@aws-cdk/aws-iam';
14+
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
15+
import { Architecture, Runtime } from '@aws-cdk/aws-lambda';
16+
import { CfnOutput, Duration } from '@aws-cdk/core';
17+
import { CiCdnStack } from '../ci-cdn-stack';
18+
19+
export class ArtifactsPublicAccess {
20+
constructor(stack: CiCdnStack, buildBucketArn: string) {
21+
const buildBucket = Bucket.fromBucketArn(stack, 'artifactBuildBucket', `${buildBucketArn.toString()}`);
22+
23+
const originAccessIdentity = new OriginAccessIdentity(stack, 'cloudfront-OAI', {
24+
comment: `OAI for ${buildBucket.bucketName}`,
25+
});
26+
27+
buildBucket.addToResourcePolicy(new PolicyStatement({
28+
actions: ['s3:GetObject'],
29+
resources: [buildBucket.arnForObjects('*')],
30+
principals: [new CanonicalUserPrincipal(originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId)],
31+
}));
32+
33+
const urlRewriter = new NodejsFunction(stack, 'CfUrlRewriter', {
34+
runtime: Runtime.NODEJS_14_X,
35+
entry: `${__dirname}/../../resources/cf-url-rewriter/cf-url-rewriter.ts`,
36+
handler: 'handler',
37+
memorySize: 128,
38+
architecture: Architecture.X86_64,
39+
});
40+
41+
const distro = new CloudFrontWebDistribution(stack, 'CloudFrontBuildBucket', {
42+
originConfigs: [
43+
{
44+
s3OriginSource: {
45+
s3BucketSource: buildBucket,
46+
originAccessIdentity,
47+
},
48+
behaviors: [
49+
{
50+
isDefaultBehavior: true,
51+
compress: true,
52+
allowedMethods: CloudFrontAllowedMethods.GET_HEAD,
53+
lambdaFunctionAssociations: [{
54+
eventType: LambdaEdgeEventType.VIEWER_REQUEST,
55+
lambdaFunction: urlRewriter.currentVersion,
56+
}],
57+
defaultTtl: Duration.seconds(300),
58+
},
59+
],
60+
},
61+
],
62+
});
63+
64+
new CfnOutput(stack, 'BuildDistributionDomainName', {
65+
value: distro.distributionDomainName,
66+
description: 'The domain name where the build artifacts will be available',
67+
});
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
import { Stack } from '@aws-cdk/core';
10+
import {
11+
ArnPrincipal, Effect, Policy, PolicyStatement, Role,
12+
} from '@aws-cdk/aws-iam';
13+
import { CiCdnStack } from '../ci-cdn-stack';
14+
15+
export interface buildArtifactProps {
16+
mainNodeArn: string,
17+
agentNodeArn: string,
18+
buildBucketArn: string
19+
}
20+
21+
export class BuildArtifactsPermissions {
22+
private static readonly BUNDLE_ROLE_NAME = 'opensearch-bundle';
23+
24+
constructor(stack: CiCdnStack, props: buildArtifactProps) {
25+
const opensearchBundleRole = new Role(stack, BuildArtifactsPermissions.BUNDLE_ROLE_NAME, {
26+
assumedBy: Role.fromRoleArn(stack, 'MainNodeRole', `${props.mainNodeArn}`),
27+
roleName: 'opensearch-bundle',
28+
});
29+
30+
opensearchBundleRole.assumeRolePolicy?.addStatements(new PolicyStatement({
31+
actions: ['sts:AssumeRole'],
32+
principals: [new ArnPrincipal(`${props.agentNodeArn}`)],
33+
}));
34+
35+
const opensearchBundlePolicies = BuildArtifactsPermissions.getOpensearchBundlePolicies(stack, props.buildBucketArn);
36+
opensearchBundlePolicies.forEach((policy) => {
37+
policy.attachToRole(opensearchBundleRole);
38+
});
39+
}
40+
41+
private static getOpensearchBundlePolicies(scope: Stack, s3BucketArn: string): Policy[] {
42+
const policies: Policy[] = [];
43+
const signingPolicy = new Policy(scope, 'signing-policy', {
44+
statements: [
45+
new PolicyStatement(
46+
{
47+
actions: ['sts:AssumeRole'],
48+
resources: ['arn:aws:iam::447201093745:role/OpenSearchSignerPGPSigning-ArtifactAccessRole'],
49+
effect: Effect.ALLOW,
50+
},
51+
),
52+
],
53+
});
54+
55+
const openSearchBundlePolicy = new Policy(scope, 'opensearch-bundle-policy', {
56+
statements: [
57+
new PolicyStatement({
58+
actions: [
59+
's3:GetObject*',
60+
's3:ListBucket',
61+
],
62+
resources: [`${s3BucketArn}`],
63+
effect: Effect.ALLOW,
64+
}),
65+
new PolicyStatement(
66+
{
67+
actions: [
68+
's3:GetObject*',
69+
's3:ListBucket',
70+
's3:PutObject',
71+
's3:PutObjectLegalHold',
72+
's3:PutObjectRetention',
73+
's3:PutObjectTagging',
74+
's3:PutObjectVersionTagging',
75+
's3:Abort*',
76+
],
77+
resources: [
78+
`${s3BucketArn}/*/builds/*`,
79+
`${s3BucketArn}/*/shas/*`,
80+
`${s3BucketArn}/*/dist/*`,
81+
`${s3BucketArn}/*/index.json`,
82+
],
83+
effect: Effect.ALLOW,
84+
},
85+
),
86+
],
87+
});
88+
policies.push(signingPolicy, openSearchBundlePolicy);
89+
return policies;
90+
}
91+
}

lib/ci-cdn-stack.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
import {
10+
Construct, Fn, Stack, StackProps,
11+
} from '@aws-cdk/core';
12+
import { BuildArtifactsPermissions } from './buildArtifacts/build-artifacts-permissions';
13+
import { ArtifactsPublicAccess } from './buildArtifacts/artifacts-public-access';
14+
15+
export class CiCdnStack extends Stack {
16+
constructor(scope: Construct, id: string, props: StackProps) {
17+
super(scope, id, props);
18+
const mainNodeRoleArn = Fn.importValue('mainNodeRoleArn');
19+
const agentNodeRoleArn = Fn.importValue('agentNodeRoleArn');
20+
const buildBucketArn = Fn.importValue('buildBucketArn');
21+
22+
const buildArtifacts = new BuildArtifactsPermissions(this, {
23+
mainNodeArn: mainNodeRoleArn,
24+
agentNodeArn: agentNodeRoleArn,
25+
buildBucketArn,
26+
});
27+
28+
const artifactPublicAccess = new ArtifactsPublicAccess(this, buildBucketArn);
29+
}
30+
}

lib/ci-stack.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
import { FlowLogDestination, FlowLogTrafficType, Vpc } from '@aws-cdk/aws-ec2';
1010
import { Secret } from '@aws-cdk/aws-secretsmanager';
1111
import {
12-
CfnParameter, Construct, Fn, Stack, StackProps,
12+
CfnOutput,
13+
CfnParameter, Construct, Fn, RemovalPolicy, Stack, StackProps,
1314
} from '@aws-cdk/core';
1415
import { ListenerCertificate } from '@aws-cdk/aws-elasticloadbalancingv2';
16+
import { FileSystem } from '@aws-cdk/aws-efs';
17+
import { Bucket } from '@aws-cdk/aws-s3';
1518
import { CIConfigStack } from './ci-config-stack';
1619
import { JenkinsMainNode } from './compute/jenkins-main-node';
1720
import { JenkinsMonitoring } from './monitoring/ci-alarms';
@@ -97,7 +100,8 @@ export class CIStack extends Stack {
97100
const certificateArn = Secret.fromSecretCompleteArn(this, 'certificateArn', importedArnSecretBucketValue.toString());
98101
const listenerCertificate = ListenerCertificate.fromArn(certificateArn.secretValue.toString());
99102
const agentNode = new AgentNodes(this);
100-
const agentNodes: AgentNodeProps[] = [agentNode.AL2_X64, agentNode.AL2_X64_DOCKER_1, agentNode.AL2_ARM64, agentNode.AL2_ARM64_DOCKER_1];
103+
const agentNodes: AgentNodeProps[] = [agentNode.AL2_X64, agentNode.AL2_X64_DOCKER_1, agentNode.AL2_ARM64, agentNode.AL2_ARM64_DOCKER_1,
104+
agentNode.UBUNTU_X64_DOCKER];
101105

102106
const mainJenkinsNode = new JenkinsMainNode(this, {
103107
vpc,
@@ -126,10 +130,17 @@ export class CIStack extends Stack {
126130
useSsl,
127131
});
128132

133+
const artifactBucket = new Bucket(this, 'BuildBucket');
134+
129135
this.monitoring = new JenkinsMonitoring(this, externalLoadBalancer, mainJenkinsNode);
130136

131137
if (additionalCommandsContext.toString() !== 'undefined') {
132138
new RunAdditionalCommands(this, additionalCommandsContext.toString());
133139
}
140+
141+
new CfnOutput(this, 'Artifact Bucket Arn', {
142+
value: artifactBucket.bucketArn.toString(),
143+
exportName: 'buildBucketArn',
144+
});
134145
}
135146
}

lib/compute/agent-node-config.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ export class AgentNodeConfig {
123123
});
124124
}
125125

126-
public addAgentConfigToJenkinsYaml(templates: AgentNodeProps[], props: AgentNodeNetworkProps): any {
126+
public addAgentConfigToJenkinsYaml(stack: Stack, templates: AgentNodeProps[], props: AgentNodeNetworkProps): any {
127127
const jenkinsYaml: any = load(readFileSync(JenkinsMainNode.BASE_JENKINS_YAML_PATH, 'utf-8'));
128128
const configTemplates: any = [];
129129

130130
templates.forEach((element) => {
131-
configTemplates.push(this.getTemplate(element, props));
131+
configTemplates.push(this.getTemplate(stack, element, props));
132132
});
133133

134134
const agentNodeYamlConfig = [{
@@ -144,7 +144,7 @@ export class AgentNodeConfig {
144144
return jenkinsYaml;
145145
}
146146

147-
private getTemplate(config: AgentNodeProps, props: AgentNodeNetworkProps): { [x: string]: any; } {
147+
private getTemplate(stack: Stack, config: AgentNodeProps, props: AgentNodeNetworkProps): { [x: string]: any; } {
148148
return {
149149
ami: config.amiId,
150150
amiType:
@@ -177,7 +177,7 @@ export class AgentNodeConfig {
177177
t2Unlimited: false,
178178
tags: [{
179179
name: 'Name',
180-
value: 'OpenSearch-CI-Prod/AgentNode',
180+
value: `${stack.stackName}/AgentNode`,
181181
},
182182
{
183183
name: 'type',

lib/compute/agent-nodes.ts

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export class AgentNodes {
2222

2323
readonly AL2_ARM64_DOCKER_1: AgentNodeProps;
2424

25+
readonly UBUNTU_X64_DOCKER: AgentNodeProps;
26+
2527
constructor(stack: Stack) {
2628
this.AL2_X64 = {
2729
workerLabelString: 'AL2-X64',
@@ -61,5 +63,12 @@ export class AgentNodes {
6163
amiId: 'ami-020c52efb1a60f1ae',
6264
initScript: 'sudo yum update -y || sudo yum update -y',
6365
};
66+
this.UBUNTU_X64_DOCKER = {
67+
workerLabelString: 'Jenkins-Agent-Ubuntu2004-X64-m52xlarge-Docker-Builder',
68+
instanceType: 'M52xlarge',
69+
remoteUser: 'ubuntu',
70+
amiId: 'ami-0f6ceb3b3687a3fba',
71+
initScript: 'sudo apt-mark hold docker docker.io openssh-server && docker ps',
72+
};
6473
}
6574
}

lib/compute/jenkins-main-node.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export class JenkinsMainNode {
107107
};
108108

109109
const agentNodeConfig = new AgentNodeConfig(stack, assumeRole);
110-
const jenkinsyaml = JenkinsMainNode.addConfigtoJenkinsYaml(props, props, agentNodeConfig, props, agentNode);
110+
const jenkinsyaml = JenkinsMainNode.addConfigtoJenkinsYaml(stack, props, props, agentNodeConfig, props, agentNode);
111111
if (props.dataRetention) {
112112
const efs = new FileSystem(stack, 'EFSfilesystem', {
113113
vpc: props.vpc,
@@ -395,10 +395,9 @@ export class JenkinsMainNode {
395395
];
396396
}
397397

398-
public static addConfigtoJenkinsYaml(jenkinsMainNodeProps:JenkinsMainNodeProps, oidcProps: OidcFederateProps, agentNodeObject: AgentNodeConfig,
398+
public static addConfigtoJenkinsYaml(stack: Stack, jenkinsMainNodeProps:JenkinsMainNodeProps, oidcProps: OidcFederateProps, agentNodeObject: AgentNodeConfig,
399399
props: AgentNodeNetworkProps, agentNode: AgentNodeProps[]): string {
400-
let updatedConfig = agentNodeObject.addAgentConfigToJenkinsYaml(agentNode, props);
401-
400+
let updatedConfig = agentNodeObject.addAgentConfigToJenkinsYaml(stack, agentNode, props);
402401
if (oidcProps.runWithOidc) {
403402
updatedConfig = OidcConfig.addOidcConfigToJenkinsYaml(updatedConfig, oidcProps.adminUsers);
404403
}

0 commit comments

Comments
 (0)