-
Notifications
You must be signed in to change notification settings - Fork 25
/
ci-stack.ts
225 lines (199 loc) · 10 KB
/
ci-stack.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
import {
CfnOutput, CfnParameter, Fn, Stack, StackProps,
} from 'aws-cdk-lib';
import {
FlowLogDestination, FlowLogTrafficType, IPeer, Peer, Vpc,
} from 'aws-cdk-lib/aws-ec2';
import { ListenerCertificate } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { Construct } from 'constructs';
import { CiAuditLogging } from './auditing/ci-audit-logging';
import { CIConfigStack } from './ci-config-stack';
import { AgentNodeProps } from './compute/agent-node-config';
import { AgentNodes } from './compute/agent-nodes';
import { JenkinsMainNode } from './compute/jenkins-main-node';
import { RunAdditionalCommands } from './compute/run-additional-commands';
import { JenkinsMonitoring } from './monitoring/ci-alarms';
import { JenkinsExternalLoadBalancer } from './network/ci-external-load-balancer';
import { JenkinsSecurityGroups } from './security/ci-security-groups';
import { JenkinsWAF } from './security/waf';
export interface CIStackProps extends StackProps {
/** Should the Jenkins use https */
readonly useSsl?: boolean;
/** Type of login mechanism to adopt */
readonly authType?: string;
/** Restrict jenkins access to */
readonly restrictServerAccessTo?: IPeer;
/** Additional verification during deployment and resource startup. */
readonly ignoreResourcesFailures?: boolean;
/** Users with admin access during initial deployment */
readonly adminUsers?: string[];
/** Additional logic that needs to be run on Master Node. The value has to be path to a file */
readonly additionalCommands?: string;
/** Do you want to retain jenkins jobs and build history */
readonly dataRetention?: boolean;
/** IAM role ARN to be assumed by jenkins agent nodes eg: cross-account */
readonly agentAssumeRole?: string[];
/** File path containing global environment variables to be added to jenkins enviornment */
readonly envVarsFilePath?: string;
/** Add Mac agent to jenkins */
readonly macAgent?: boolean;
/** Enable views on jenkins UI */
readonly enableViews?: boolean;
/** Use Production Agents */
readonly useProdAgents?: boolean;
}
function getServerAccess(serverAccessType: string, restrictServerAccessTo: string): IPeer {
if (typeof restrictServerAccessTo === 'undefined') {
throw new Error('restrictServerAccessTo should be specified');
}
switch (serverAccessType) {
case 'ipv4':
return restrictServerAccessTo === 'all' ? Peer.anyIpv4() : Peer.ipv4(restrictServerAccessTo);
case 'ipv6':
return restrictServerAccessTo === 'all' ? Peer.anyIpv6() : Peer.ipv6(restrictServerAccessTo);
case 'prefixList':
return Peer.prefixList(restrictServerAccessTo);
case 'securityGroupId':
return Peer.securityGroupId(restrictServerAccessTo);
default:
throw new Error('serverAccessType should be one of the below values: ipv4, ipv6, prefixList or securityGroupId');
}
}
export class CIStack extends Stack {
public readonly monitoring: JenkinsMonitoring;
public readonly agentNodes: AgentNodeProps[];
public readonly securityGroups: JenkinsSecurityGroups;
constructor(scope: Construct, id: string, props: CIStackProps) {
super(scope, id, props);
const auditloggingS3Bucket = new CiAuditLogging(this);
const vpc = new Vpc(this, 'JenkinsVPC', {
flowLogs: {
s3: {
destination: FlowLogDestination.toS3(auditloggingS3Bucket.bucket, 'vpcFlowLogs'),
trafficType: FlowLogTrafficType.ALL,
},
},
});
const macAgentParameter = `${props?.macAgent ?? this.node.tryGetContext('macAgent')}`;
const useSslParameter = `${props?.useSsl ?? this.node.tryGetContext('useSsl')}`;
if (useSslParameter !== 'true' && useSslParameter !== 'false') {
throw new Error('useSsl parameter is required to be set as - true or false');
}
const useSsl = useSslParameter === 'true';
let authType = `${props?.authType ?? this.node.tryGetContext('authType')}`;
if (authType.toString() === 'undefined') {
authType = 'default';
}
if (authType !== 'default' && authType !== 'github' && authType !== 'oidc') {
throw new Error('authType parameter is required to be set as - default, github or oidc');
}
let useProdAgents = `${props?.useProdAgents ?? this.node.tryGetContext('useProdAgents')}`;
if (useProdAgents.toString() === 'undefined') {
useProdAgents = 'false';
}
const serverAccessType = this.node.tryGetContext('serverAccessType');
const restrictServerAccessTo = this.node.tryGetContext('restrictServerAccessTo');
const serverAcess = props?.restrictServerAccessTo ?? getServerAccess(serverAccessType, restrictServerAccessTo);
if (!serverAcess) {
throw new Error('serverAccessType and restrictServerAccessTo parameters are required - eg: serverAccessType=ipv4 restrictServerAccessTo=10.10.10.10/32');
}
const additionalCommandsContext = `${props?.additionalCommands ?? this.node.tryGetContext('additionalCommands')}`;
// Setting CfnParameters to record the value in cloudFormation
new CfnParameter(this, 'authType', {
description: 'Auth type for jenkins login',
default: authType,
});
// Setting CfnParameters to record the value in cloudFormation
new CfnParameter(this, 'useSsl', {
description: 'If the jenkins instance should be access via SSL',
default: useSsl,
});
this.securityGroups = new JenkinsSecurityGroups(this, vpc, useSsl, serverAcess);
const importedContentsSecretBucketValue = Fn.importValue(`${CIConfigStack.CERTIFICATE_CONTENTS_SECRET_EXPORT_VALUE}`);
const importedContentsChainBucketValue = Fn.importValue(`${CIConfigStack.CERTIFICATE_CHAIN_SECRET_EXPORT_VALUE}`);
const importedCertSecretBucketValue = Fn.importValue(`${CIConfigStack.PRIVATE_KEY_SECRET_EXPORT_VALUE}`);
const importedArnSecretBucketValue = Fn.importValue(`${CIConfigStack.CERTIFICATE_ARN_SECRET_EXPORT_VALUE}`);
const importedRedirectUrlSecretBucketValue = Fn.importValue(`${CIConfigStack.REDIRECT_URL_SECRET_EXPORT_VALUE}`);
const importedAuthConfigValuesSecretBucketValue = Fn.importValue(`${CIConfigStack.AUTH_CONFIGURATION_VALUE_SECRET_EXPORT_VALUE}`);
const certificateArn = Secret.fromSecretCompleteArn(this, 'certificateArn', importedArnSecretBucketValue.toString());
const importedReloadPasswordSecretsArn = Fn.importValue(`${CIConfigStack.CASC_RELOAD_TOKEN_SECRET_EXPORT_VALUE}`);
const listenerCertificate = ListenerCertificate.fromArn(certificateArn.secretValue.toString());
const agentNode = new AgentNodes(this);
if (useProdAgents.toString() === 'true') {
// eslint-disable-next-line no-console
console.warn('Please note that if you have decided to use the provided production jenkins agents then '
+ 'please make sure that you are deploying the stack in US-EAST-1 region as the AMIs used are only publicly '
+ 'available in US-EAST-1 region. '
+ 'If you want to deploy the stack in another region then please make sure you copy the public AMIs used '
+ 'from us-east-1 region to your region of choice and update the ami-id in agent-nodes.ts file accordingly. '
+ 'If you do not copy the AMI in required region and update the code then the jenkins agents will not spin up.');
this.agentNodes = [
agentNode.AL2023_X64,
agentNode.AL2_X64_DOCKER_HOST,
agentNode.AL2023_X64_DOCKER_HOST,
agentNode.AL2023_ARM64,
agentNode.AL2_ARM64_DOCKER_HOST,
agentNode.AL2023_ARM64_DOCKER_HOST,
agentNode.AL2023_X64_BENCHMARK_TEST,
agentNode.UBUNTU2004_X64_GRADLE_CHECK,
agentNode.UBUNTU2004_X64_DOCKER_BUILDER,
agentNode.MACOS13_X64_MULTI_HOST,
agentNode.MACOS13_ARM64_MULTI_HOST,
agentNode.WINDOWS2019_X64_DOCKER_HOST,
agentNode.WINDOWS2019_X64_DOCKER_BUILDER,
agentNode.WINDOWS2019_X64_GRADLE_CHECK,
];
} else {
this.agentNodes = [agentNode.AL2_X64_DEFAULT_AGENT, agentNode.AL2_ARM64_DEFAULT_AGENT];
}
const mainJenkinsNode = new JenkinsMainNode(this, {
vpc,
sg: this.securityGroups.mainNodeSG,
efsSG: this.securityGroups.efsSG,
dataRetention: props.dataRetention ?? false,
envVarsFilePath: props.envVarsFilePath ?? '',
enableViews: props.enableViews ?? false,
reloadPasswordSecretsArn: importedReloadPasswordSecretsArn.toString(),
sslCertContentsArn: importedContentsSecretBucketValue.toString(),
sslCertChainArn: importedContentsChainBucketValue.toString(),
sslCertPrivateKeyContentsArn: importedCertSecretBucketValue.toString(),
redirectUrlArn: importedRedirectUrlSecretBucketValue.toString(),
authCredsSecretsArn: importedAuthConfigValuesSecretBucketValue.toString(),
useSsl,
authType,
failOnCloudInitError: props?.ignoreResourcesFailures,
adminUsers: props?.adminUsers,
agentNodeSecurityGroup: this.securityGroups.agentNodeSG.securityGroupId,
subnetId: vpc.privateSubnets[0].subnetId,
}, this.agentNodes, macAgentParameter.toString(), props?.agentAssumeRole);
const externalLoadBalancer = new JenkinsExternalLoadBalancer(this, {
vpc,
sg: this.securityGroups.externalAccessSG,
targetInstance: mainJenkinsNode.mainNodeAsg,
listenerCertificate,
useSsl,
accessLogBucket: auditloggingS3Bucket.bucket,
});
const waf = new JenkinsWAF(this, {
loadBalancer: externalLoadBalancer.loadBalancer,
});
const artifactBucket = new Bucket(this, 'BuildBucket');
this.monitoring = new JenkinsMonitoring(this, externalLoadBalancer, mainJenkinsNode);
if (additionalCommandsContext.toString() !== 'undefined') {
new RunAdditionalCommands(this, additionalCommandsContext.toString());
}
new CfnOutput(this, 'Artifact Bucket Arn', {
value: artifactBucket.bucketArn.toString(),
exportName: 'buildBucketArn',
});
}
}