Skip to content

Commit

Permalink
fixed IAM roles and code cleanup
Browse files Browse the repository at this point in the history
Signed-off-by: pgodithi <pgodithi@amazon.com>
  • Loading branch information
prudhvigodithi committed May 5, 2022
1 parent 83a6200 commit 4c38aa5
Show file tree
Hide file tree
Showing 10 changed files with 14,559 additions and 547 deletions.
17 changes: 0 additions & 17 deletions bin/ci-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import { App, RemovalPolicy } from '@aws-cdk/core';
import { CIStack } from '../lib/ci-stack';
import { CIConfigStack } from '../lib/ci-config-stack';
import { DeployAwsAssets } from '../lib/deploy-aws-assets';

const app = new App();

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

const ciStack = new CIStack(app, `OpenSearch-CI-${defaultEnv}`, {});

new DeployAwsAssets(app, `OpenSearch-CI-Deploy-Assets-${defaultEnv}`, {
/* This will delete the ECR repository once the stack is destroyed.
* Default removal policy (if not specified) is RemovalPolicy.DESTROY */
removalPolicy: RemovalPolicy.DESTROY,
mainNodeAccountNumber: ciStack.account,
envName: defaultEnv,
env: {
// public ECR repositories can only be created in us-east-1
// https://github.com/aws/aws-cli/issues/5917#issuecomment-775564831
region: 'us-east-1',
},
repositories: [
'opensearch',
],
});
73 changes: 0 additions & 73 deletions lib/ci-ecr-stack.ts

This file was deleted.

3 changes: 0 additions & 3 deletions lib/ci-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export interface CIStackProps extends StackProps {
readonly runWithOidc?: boolean;
/** Additional verification during deployment and resource startup. */
readonly ignoreResourcesFailures?: boolean;
/** Account to deploy your ECR Assets on */
readonly ecrAccountId?: string;
/** 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 */
Expand Down Expand Up @@ -109,7 +107,6 @@ export class CIStack extends Stack {
useSsl,
runWithOidc,
failOnCloudInitError: props?.ignoreResourcesFailures,
ecrAccountId: props.ecrAccountId ?? Stack.of(this).account,
adminUsers: props?.adminUsers,
agentNodeSecurityGroup: securityGroups.agentNodeSG.securityGroupId,
subnetId: vpc.publicSubnets[0].subnetId,
Expand Down
269 changes: 165 additions & 104 deletions lib/compute/agent-node-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,108 +6,169 @@
* compatible open source license.
*/

import { CfnInstanceProfile, ServicePrincipal, Role } from '@aws-cdk/aws-iam';
import { Fn, Stack, Tags } from '@aws-cdk/core';
import { KeyPair } from 'cdk-ec2-key-pair';
import { readFileSync } from 'fs';
import { load } from 'js-yaml';
import { JenkinsMainNode } from './jenkins-main-node';

export interface AgentNodeProps {
amiId: string;
instanceType: string;
workerLabelString: string;
remoteUser: string;
initScript: string
}

export interface AgentNodeNetworkProps {
readonly agentNodeSecurityGroup: string;
readonly subnetId: string;
}

export class AgentNodeConfig {
public readonly AgentNodeInstanceProfileArn: string;

public readonly STACKREGION: string;

public readonly SSHEC2KeySecretId: string;

constructor(stack: Stack) {
this.STACKREGION = stack.region;
const key = new KeyPair(stack, 'AgentNode-KeyPair', {
name: 'AgentNodeKeyPair',
description: 'KeyPair used by Jenkins Main Node to SSH into Agent Nodes',
});
Tags.of(key)
.add('jenkins:credentials:type', 'sshUserPrivateKey');
const AgentNodeRole = new Role(stack, 'JenkinsAgentNodeRole', {
assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
});
const AgentNodeInstanceProfile = new CfnInstanceProfile(stack, 'JenkinsAgentNodeInstanceProfile', { roles: [AgentNodeRole.roleName] });
this.AgentNodeInstanceProfileArn = AgentNodeInstanceProfile.attrArn.toString();
this.SSHEC2KeySecretId = Fn.join('/', ['ec2-ssh-key', key.keyPairName.toString(), 'private']);
}

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

templates.forEach((element) => {
configTemplates.push(this.getTemplate(element, props));
});

const agentNodeYamlConfig = [{
amazonEC2: {
cloudName: 'Amazon_ec2_cloud',
region: this.STACKREGION,
sshKeysCredentialsId: this.SSHEC2KeySecretId,
templates: configTemplates,
useInstanceProfileForCredentials: true,
},
}];
jenkinsYaml.jenkins.clouds = agentNodeYamlConfig;
return jenkinsYaml;
}

private getTemplate(config: AgentNodeProps, props: AgentNodeNetworkProps): { [x: string]: any; } {
return {
ami: config.amiId,
amiType:
{ unixData: { sshPort: '22' } },
associatePublicIp: false,
connectBySSHProcess: false,
connectionStrategy: 'PRIVATE_IP',
customDeviceMapping: '/dev/xvda=:100:true:::encrypted',
deleteRootOnTermination: true,
description: `jenkinsAgentNode-${config.workerLabelString}`,
ebsEncryptRootVolume: 'ENCRYPTED',
ebsOptimized: false,
hostKeyVerificationStrategy: 'OFF',
iamInstanceProfile: this.AgentNodeInstanceProfileArn,
idleTerminationMinutes: '30',
initScript: config.initScript,
labelString: config.workerLabelString,
launchTimeoutStr: '300',
maxTotalUses: -1,
minimumNumberOfInstances: 0,
minimumNumberOfSpareInstances: 1,
mode: 'EXCLUSIVE',
monitoring: true,
numExecutors: 1,
remoteAdmin: config.remoteUser,
remoteFS: '/var/jenkins',
securityGroups: props.agentNodeSecurityGroup,
stopOnTerminate: false,
subnetId: props.subnetId,
t2Unlimited: false,
tags: [{
name: 'Name',
value: `jenkinsAgentNode-${config.workerLabelString}`,
}],
tenancy: 'Default',
type: config.instanceType,
useEphemeralDevices: false,
};
}
}
import * as iam from '@aws-cdk/aws-iam';
import { Fn, Stack, Tags } from '@aws-cdk/core';
import { KeyPair } from 'cdk-ec2-key-pair';
import { readFileSync } from 'fs';
import { load } from 'js-yaml';
import { JenkinsMainNode } from './jenkins-main-node';


export interface AgentNodeProps {
amiId: string;
instanceType: string;
workerLabelString: string;
remoteUser: string;
initScript: string
}

export interface AgentNodeNetworkProps {
readonly agentNodeSecurityGroup: string;
readonly subnetId: string;
}

export class AgentNodeConfig {
public readonly AgentNodeInstanceProfileArn: string;

public readonly STACKREGION: string;

private readonly ACCOUNT: string;

public readonly SSHEC2KeySecretId: string;

constructor(stack: Stack) {
this.STACKREGION = stack.region;
this.ACCOUNT = stack.account;
const key = new KeyPair(stack, 'AgentNode-KeyPair', {
name: 'AgentNodeKeyPair',
description: 'KeyPair used by Jenkins Main Node to SSH into Agent Nodes',
});
Tags.of(key)
.add('jenkins:credentials:type', 'sshUserPrivateKey');
const AgentNodeRole = new iam.Role(stack, 'OpenSearch-CI-AgentNodeRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
//assumedBy: new iam.AccountPrincipal(this.ACCOUNT),
description: 'Jenkins agents Node Role',
roleName: 'OpenSearch-CI-AgentNodeRole',
});

const ecrManagedPolicy = new iam.ManagedPolicy(stack, 'OpenSearch-CI-AgentNodePolicy', {
description: 'Jenkins agents Node Policy',
managedPolicyName: 'OpenSearch-CI-AgentNodePolicy',
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"ecr-public:BatchCheckLayerAvailability",
"ecr-public:GetRepositoryPolicy",
"ecr-public:DescribeRepositories",
"ecr-public:DescribeRegistries",
"ecr-public:DescribeImages",
"ecr-public:DescribeImageTags",
"ecr-public:GetRepositoryCatalogData",
"ecr-public:GetRegistryCatalogData",
"ecr-public:InitiateLayerUpload",
"ecr-public:UploadLayerPart",
"ecr-public:CompleteLayerUpload",
"ecr-public:PutImage",
],
resources: ['arn:aws:ecr-public::'+this.ACCOUNT+':repository/*'],
conditions: {
'StringEquals': {
'aws:RequestedRegion': this.STACKREGION,
'aws:PrincipalAccount': [this.ACCOUNT],
},
},
}),
],
roles: [AgentNodeRole],
});
ecrManagedPolicy.addStatements(
new iam.PolicyStatement({
actions: [
'ecr-public:GetAuthorizationToken',
'sts:GetServiceBearerToken',
],
resources: ['*'],
conditions: {
'StringEquals': {
'aws:RequestedRegion': this.STACKREGION,
'aws:PrincipalAccount': [this.ACCOUNT],
},
},
}),
);
const AgentNodeInstanceProfile = new iam.CfnInstanceProfile(stack, 'JenkinsAgentNodeInstanceProfile', { roles: [AgentNodeRole.roleName] });
this.AgentNodeInstanceProfileArn = AgentNodeInstanceProfile.attrArn.toString();
this.SSHEC2KeySecretId = Fn.join('/', ['ec2-ssh-key', key.keyPairName.toString(), 'private']);
}

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

templates.forEach((element) => {
configTemplates.push(this.getTemplate(element, props));
});

const agentNodeYamlConfig = [{
amazonEC2: {
cloudName: 'Amazon_ec2_cloud',
region: this.STACKREGION,
sshKeysCredentialsId: this.SSHEC2KeySecretId,
templates: configTemplates,
useInstanceProfileForCredentials: true,
},
}];
jenkinsYaml.jenkins.clouds = agentNodeYamlConfig;
return jenkinsYaml;
}

private getTemplate(config: AgentNodeProps, props: AgentNodeNetworkProps): { [x: string]: any; } {
return {
ami: config.amiId,
amiType:
{ unixData: { sshPort: '22' } },
associatePublicIp: false,
connectBySSHProcess: false,
connectionStrategy: 'PRIVATE_IP',
customDeviceMapping: '/dev/xvda=:100:true:::encrypted',
deleteRootOnTermination: true,
description: `jenkinsAgentNode-${config.workerLabelString}`,
ebsEncryptRootVolume: 'ENCRYPTED',
ebsOptimized: false,
hostKeyVerificationStrategy: 'OFF',
iamInstanceProfile: this.AgentNodeInstanceProfileArn,
idleTerminationMinutes: '30',
initScript: config.initScript,
labelString: config.workerLabelString,
launchTimeoutStr: '300',
maxTotalUses: -1,
minimumNumberOfInstances: 0,
minimumNumberOfSpareInstances: 1,
mode: 'EXCLUSIVE',
monitoring: true,
numExecutors: 1,
remoteAdmin: config.remoteUser,
remoteFS: '/var/jenkins',
securityGroups: props.agentNodeSecurityGroup,
stopOnTerminate: false,
subnetId: props.subnetId,
t2Unlimited: false,
tags: [{
name: 'Name',
value: 'OpenSearch-CI-Prod/AgentNode',
},
{
name: 'type',
value: `jenkinsAgentNode-${config.workerLabelString}`,
},
],
tenancy: 'Default',
type: config.instanceType,
useEphemeralDevices: false,
};
}
}

Loading

0 comments on commit 4c38aa5

Please sign in to comment.