From 401e39e0f3661928bf24b466a935499990b118ef Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 25 Oct 2023 13:07:42 -0700 Subject: [PATCH] add respository-s3 plugin install and support to pass custom iam role (#71) Signed-off-by: Rishabh Singh --- README.md | 67 ++++++++++++++++++------------------ lib/infra/infra-stack.ts | 49 +++++++++++++------------- lib/os-cluster-entrypoint.ts | 3 ++ test/os-cluster.test.ts | 33 ++++++++++++++++++ 4 files changed, 95 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ebfea163fa5..f987e75f815 100644 --- a/README.md +++ b/README.md @@ -33,40 +33,39 @@ There are two stacks that get deployed: In order to deploy both the stacks the user needs to provide a set of required and optional parameters listed below: -| Name | Requirement | Type | Description | -|-----------------------------------|:--------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| distVersion | Required | string | The OpenSearch distribution version (released/un-released) the user wants to deploy | -| securityDisabled | Required | boolean | Enable or disable security plugin | -| minDistribution | Required | boolean | Is it the minimal OpenSearch distribution with no security and plugins | -| distributionUrl | Required | string | OpenSearch tar distribution url | -| cpuArch | Required | string | CPU platform for EC2, could be either `x64` or `arm64` | -| singleNodeCluster | Required | boolean | Set `true` for single-node cluster else `false` for multi-node | -| serverAccessType | Required | string | Restrict server access based on ip address (ipv4/ipv6), prefix list and/or security group. See [Restricting Server Access](#restricting-server-access) for more details. | -| restrictServerAccessTo | Required | string | The value for `serverAccessType`, e.g., 10.10.10.10/32, pl-12345, sg-12345. See [Restricting Server Access](#restricting-server-access) for more details. | -| dashboardsUrl | Optional | string | OpenSearch Dashboards tar distribution url | -| vpcId | Optional | string | Re-use existing vpc, provide vpc id | -| securityGroupId | Optional | boolean | Re-use existing security group, provide security group id | -| cidr | Optional | string | User provided CIDR block for new Vpc. Defaults to `10.0.0.0/16` | -| managerNodeCount | Optional | integer | Number of cluster manager nodes. Defaults to 3 | -| dataNodeCount | Optional | integer | Number of data nodes. Defaults to 2 | -| clientNodeCount | Optional | integer | Number of dedicated client nodes. Defaults to 0 | -| ingestNodeCount | Optional | integer | Number of dedicated ingest nodes. Defaults to 0 | -| mlNodeCount | Optional | integer | Number of dedicated machine learning nodes. Defaults to 0 | -| dataInstanceType | Optional | string | EC2 instance type for data node. Defaults to r5.xlarge. See options in `lib/opensearch-config/node-config.ts` for available options. E.g., `-c dataInstanceType=m5.xlarge` | -| mlInstanceType | Optional | string | EC2 instance type for ml node. Defaults to r5.xlarge. See options in `lib/opensearch-config/node-config.ts` for available options. E.g., `-c mlInstanceType=m5.xlarge` | -| jvmSysProps | Optional | string | A comma-separated list of key=value pairs that will be added to `jvm.options` as JVM system properties. | -| additionalConfig | Optional | string | Additional opensearch.yml config parameters passed as JSON. e.g., `--context additionalConfig='{"plugins.security.nodes_dn": ["CN=*.example.com, OU=SSL, O=Test, L=Test, C=DE", "CN=node.other.com, OU=SSL, O=Test, L=Test, C=DE"], "plugins.security.nodes_dn_dynamic_config_enabled": false}'` | -| suffix | Optional | string | An optional string identifier to be concatenated with infra stack name. | -| region | Optional | string | User provided aws region | -| account | Optional | string | User provided aws account | -| dataNodeStorage | Optional | string | User provided ebs block storage size. Defaults to 100Gb | -| mlNodeStorage | Optional | string | User provided ebs block storage size. Defaults to 100Gb | -| use50PercentHeap | Optional | boolean | Boolean flag to use 50% of physical memory as heap. Defaults to 1GB. e.g., `--context use50PercentHeap=true` | -| isInternal | Optional | boolean | Boolean flag to make network load balancer internal. Defaults to internet-facing e.g., `--context isInternal=true` | -| enableRemoteStore | Optional | boolean | Boolean flag to enable Remote Store feature e.g., `--context enableRemoteStore=true`. See [Enable Remote Store Feature](#enable-remote-store-feature) for more details. Defaults to false | -| storageVolumeType | Optional | string | EBS volume type for all the nodes (data, ml, cluster manager). Defaults to gp2. See `lib/opensearch-config/node-config.ts` for available options. E.g., `-c storageVolumeType=gp3`. For SSD based instance (i.e. i3 family), it is used for root volume configuration. | - - +| Name | Requirement | Type | Description | +|------------------------|:------------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| distVersion | Required | string | The OpenSearch distribution version (released/un-released) the user wants to deploy | +| securityDisabled | Required | boolean | Enable or disable security plugin | +| minDistribution | Required | boolean | Is it the minimal OpenSearch distribution with no security and plugins | +| distributionUrl | Required | string | OpenSearch tar distribution url | +| cpuArch | Required | string | CPU platform for EC2, could be either `x64` or `arm64` | +| singleNodeCluster | Required | boolean | Set `true` for single-node cluster else `false` for multi-node | +| serverAccessType | Required | string | Restrict server access based on ip address (ipv4/ipv6), prefix list and/or security group. See [Restricting Server Access](#restricting-server-access) for more details. | +| restrictServerAccessTo | Required | string | The value for `serverAccessType`, e.g., 10.10.10.10/32, pl-12345, sg-12345. See [Restricting Server Access](#restricting-server-access) for more details. | +| dashboardsUrl | Optional | string | OpenSearch Dashboards tar distribution url | +| vpcId | Optional | string | Re-use existing vpc, provide vpc id | +| securityGroupId | Optional | boolean | Re-use existing security group, provide security group id | +| cidr | Optional | string | User provided CIDR block for new Vpc. Defaults to `10.0.0.0/16` | +| managerNodeCount | Optional | integer | Number of cluster manager nodes. Defaults to 3 | +| dataNodeCount | Optional | integer | Number of data nodes. Defaults to 2 | +| clientNodeCount | Optional | integer | Number of dedicated client nodes. Defaults to 0 | +| ingestNodeCount | Optional | integer | Number of dedicated ingest nodes. Defaults to 0 | +| mlNodeCount | Optional | integer | Number of dedicated machine learning nodes. Defaults to 0 | +| dataInstanceType | Optional | string | EC2 instance type for data node. Defaults to r5.xlarge. See options in `lib/opensearch-config/node-config.ts` for available options. E.g., `-c dataInstanceType=m5.xlarge` | +| mlInstanceType | Optional | string | EC2 instance type for ml node. Defaults to r5.xlarge. See options in `lib/opensearch-config/node-config.ts` for available options. E.g., `-c mlInstanceType=m5.xlarge` | +| jvmSysProps | Optional | string | A comma-separated list of key=value pairs that will be added to `jvm.options` as JVM system properties. | +| additionalConfig | Optional | string | Additional opensearch.yml config parameters passed as JSON. e.g., `--context additionalConfig='{"plugins.security.nodes_dn": ["CN=*.example.com, OU=SSL, O=Test, L=Test, C=DE", "CN=node.other.com, OU=SSL, O=Test, L=Test, C=DE"], "plugins.security.nodes_dn_dynamic_config_enabled": false}'` | +| suffix | Optional | string | An optional string identifier to be concatenated with infra stack name. | +| region | Optional | string | User provided aws region | +| account | Optional | string | User provided aws account | +| dataNodeStorage | Optional | string | User provided ebs block storage size. Defaults to 100Gb | +| mlNodeStorage | Optional | string | User provided ebs block storage size. Defaults to 100Gb | +| use50PercentHeap | Optional | boolean | Boolean flag to use 50% of physical memory as heap. Defaults to 1GB. e.g., `--context use50PercentHeap=true` | +| isInternal | Optional | boolean | Boolean flag to make network load balancer internal. Defaults to internet-facing e.g., `--context isInternal=true` | +| enableRemoteStore | Optional | boolean | Boolean flag to enable Remote Store feature e.g., `--context enableRemoteStore=true`. See [Enable Remote Store Feature](#enable-remote-store-feature) for more details. Defaults to false | +| storageVolumeType | Optional | string | EBS volume type for all the nodes (data, ml, cluster manager). Defaults to gp2. See `lib/opensearch-config/node-config.ts` for available options. E.g., `-c storageVolumeType=gp3`. For SSD based instance (i.e. i3 family), it is used for root volume configuration. | +| customRoleArn | Optional | string | User provided IAM role arn to be used as ec2 instance profile. `-c customRoleArn=arn:aws:iam:::role/` | * Before starting this step, ensure that your AWS CLI is correctly configured with access credentials. * Also ensure that you're running these commands in the current directory diff --git a/lib/infra/infra-stack.ts b/lib/infra/infra-stack.ts index b33e112d710..0976e2f56b1 100644 --- a/lib/infra/infra-stack.ts +++ b/lib/infra/infra-stack.ts @@ -29,7 +29,9 @@ import { } from 'aws-cdk-lib/aws-ec2'; import { NetworkListener, NetworkLoadBalancer, Protocol } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import { InstanceTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; -import { ManagedPolicy, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; +import { + ManagedPolicy, Role, IRole, ServicePrincipal, +} from 'aws-cdk-lib/aws-iam'; import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; import { readFileSync } from 'fs'; import { dump, load } from 'js-yaml'; @@ -38,7 +40,7 @@ import { CloudwatchAgent } from '../cloudwatch/cloudwatch-agent'; import { nodeConfig } from '../opensearch-config/node-config'; import { RemoteStoreResources } from './remote-store-resources'; -export interface infraProps extends StackProps{ +export interface infraProps extends StackProps { readonly vpc: IVpc, readonly securityGroup: ISecurityGroup, readonly opensearchVersion: string, @@ -63,7 +65,8 @@ export interface infraProps extends StackProps{ readonly use50PercentHeap: boolean, readonly isInternal: boolean, readonly enableRemoteStore: boolean, - readonly storageVolumeType: EbsDeviceVolumeType + readonly storageVolumeType: EbsDeviceVolumeType, + readonly customRoleArn: string } export class InfraStack extends Stack { @@ -86,12 +89,16 @@ export class InfraStack extends Stack { removalPolicy: RemovalPolicy.DESTROY, }); - this.instanceRole = new Role(this, 'instanceRole', { - managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), - ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'), - ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')], - assumedBy: new ServicePrincipal('ec2.amazonaws.com'), - }); + if (props.customRoleArn === 'undefined') { + this.instanceRole = new Role(this, 'instanceRole', { + managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), + ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'), + ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')], + assumedBy: new ServicePrincipal('ec2.amazonaws.com'), + }); + } else { + this.instanceRole = Role.fromRoleArn(this, 'custom-role-arn', `${props.customRoleArn}`); + } if (props.enableRemoteStore) { // Remote Store needs an S3 bucket to be registered as snapshot repo @@ -475,6 +482,10 @@ export class InfraStack extends Stack { cwd: '/home/ec2-user', ignoreErrors: false, })); + cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install repository-s3 --batch', { + cwd: '/home/ec2-user', + ignoreErrors: false, + })); } else { cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install ' + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${props.opensearchVersion}/latest/linux/${props.cpuArch}` @@ -482,23 +493,15 @@ export class InfraStack extends Stack { cwd: '/home/ec2-user', ignoreErrors: false, })); + cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install ' + + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${props.opensearchVersion}/latest/linux/${props.cpuArch}` + + `/tar/builds/opensearch/core-plugins/repository-s3-${props.opensearchVersion}.zip --batch`, { + cwd: '/home/ec2-user', + ignoreErrors: false, + })); } if (props.enableRemoteStore) { - if (props.distributionUrl.includes('artifacts.opensearch.org') && !props.minDistribution) { - cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install repository-s3 --batch', { - cwd: '/home/ec2-user', - ignoreErrors: false, - })); - } else { - cfnInitConfig.push(InitCommand.shellCommand('set -ex;cd opensearch;sudo -u ec2-user bin/opensearch-plugin install ' - + `https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${props.opensearchVersion}/latest/linux/${props.cpuArch}` - + `/tar/builds/opensearch/core-plugins/repository-s3-${props.opensearchVersion}.zip --batch`, { - cwd: '/home/ec2-user', - ignoreErrors: false, - })); - } - // eslint-disable-next-line max-len cfnInitConfig.push(InitCommand.shellCommand(`set -ex;cd opensearch; echo "node.attr.remote_store.segment.repository: ${scope.stackName}-repo" >> config/opensearch.yml`, { cwd: '/home/ec2-user', diff --git a/lib/os-cluster-entrypoint.ts b/lib/os-cluster-entrypoint.ts index dc622cd37f3..752696b9bfa 100644 --- a/lib/os-cluster-entrypoint.ts +++ b/lib/os-cluster-entrypoint.ts @@ -199,6 +199,8 @@ export class OsClusterEntrypoint { const remoteStore = `${scope.node.tryGetContext('enableRemoteStore')}`; const enableRemoteStore = remoteStore === 'true'; + const customRoleArn = `${scope.node.tryGetContext('customRoleArn')}`; + const network = new NetworkStack(scope, 'opensearch-network-stack', { cidrBlock: cidrRange, maxAzs: 3, @@ -249,6 +251,7 @@ export class OsClusterEntrypoint { isInternal, enableRemoteStore, storageVolumeType: volumeType, + customRoleArn, ...props, }); diff --git a/test/os-cluster.test.ts b/test/os-cluster.test.ts index d1313bed2f1..bf07428a824 100644 --- a/test/os-cluster.test.ts +++ b/test/os-cluster.test.ts @@ -447,3 +447,36 @@ test('Throw error on unsupported ebs volume type', () => { expect(error.message).toEqual('Invalid volume type provided, please provide any one of the following: standard, gp2, gp3'); } }); + +test('Test multi-node cluster with custom IAM Role', () => { + const app = new App({ + context: { + securityDisabled: true, + minDistribution: false, + distributionUrl: 'www.example.com', + cpuArch: 'x64', + singleNodeCluster: false, + dashboardsUrl: 'www.example.com', + distVersion: '1.0.0', + serverAccessType: 'ipv4', + restrictServerAccessTo: 'all', + managerNodeCount: 0, + dataNodeCount: 3, + dataNodeStorage: 200, + customRoleArn: 'arn:aws:iam::12345678:role/customRoleName', + }, + }); + + // WHEN + const testStack = new OsClusterEntrypoint(app, { + env: { account: 'test-account', region: 'us-east-1' }, + }); + expect(testStack.stacks).toHaveLength(2); + + const infraStack = testStack.stacks.filter((s) => s.stackName === 'opensearch-infra-stack')[0]; + const infraTemplate = Template.fromStack(infraStack); + infraTemplate.resourceCountIs('AWS::IAM::Role', 0); + infraTemplate.hasResourceProperties('AWS::IAM::InstanceProfile', { + Roles: ['customRoleName'], + }); +});