Skip to content

Commit

Permalink
Addresses PR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephan Hoermann committed Sep 20, 2020
1 parent fbaacf2 commit caa310d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 61 deletions.
33 changes: 27 additions & 6 deletions packages/@aws-cdk/aws-elasticsearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@
---
<!--END STABILITY BANNER-->

To create an Elasticsearch domain:
Create a development cluster by simply specifying the version:

```ts
import * as es from '@aws-cdk/aws-elasticsearch';

const devDomain = new es.Domain(this, 'Domain', {
version: es.ElasticsearchVersion.V7_1,
logging: {
slowSearchLogEnabled: true,
appLogEnabled: true
},
});
```

Create a production grade cluster by also specifying things like capacity and az distribution

```ts
const prodDomain = new es.Domain(this, 'Domain', {
version: es.ElasticsearchVersion.V7_1,
capacity: {
Expand All @@ -43,7 +43,7 @@ const prodDomain = new es.Domain(this, 'Domain', {
logging: {
slowSearchLogEnabled: true,
appLogEnabled: true,
slowIndexLogEnabled: true
slowIndexLogEnabled: true,
},
});
```
Expand Down Expand Up @@ -109,3 +109,24 @@ const masterSysMemoryUtilization = domain.metric('MasterSysMemoryUtilization');
```

This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.

### Fine grained access control

The domain can also be created with a master user configured. The password can
be supplied or dynamically created if not supplied.

```ts
const domain = new es.Domain(this, 'Domain', {
version: es.ElasticsearchVersion.V7_1,
enforceHttps: true,
nodeToNodeEncryption: true,
encryptionAtRest: {
enabled: true,
},
fineGrainedAccessControl: {
masterUserName: 'master-user',
},
});

const masterUserPassword = domain.masterUserPassword;
```
90 changes: 46 additions & 44 deletions packages/@aws-cdk/aws-elasticsearch/lib/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ export interface AdvancedSecurityOptions {
*
* @default - A Secrets Manager generated password
*/
readonly masterUserPasswordSecret?: cdk.SecretValue;
readonly masterUserPassword?: cdk.SecretValue;
}

/**
Expand Down Expand Up @@ -388,7 +388,10 @@ export interface DomainProps {

/**
* The configurations of Amazon Elastic Block Store (Amazon EBS) volumes that
* are attached to data nodes in the Amazon ES domain.
* are attached to data nodes in the Amazon ES domain. For more information, see
* [Configuring EBS-based Storage]
* (https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createupdatedomains.html#es-createdomain-configure-ebs)
* in the Amazon Elasticsearch Service Developer Guide.
*
* @default - 10 GiB General Purpose (SSD) volumes per node.
*/
Expand All @@ -410,11 +413,6 @@ export interface DomainProps {

/**
* The Elasticsearch version that your domain will leverage.
*
* Per https://aws.amazon.com/elasticsearch-service/faqs/, Amazon Elasticsearch Service
* currently supports Elasticsearch versions 7.7, 7.4, 7.1, 6.8, 6.7, 6.5, 6.4, 6.3, 6.2,
* 6.0, 5.6, 5.5, 5.3, 5.1, 2.3, and 1.5.
*
*/
readonly version: ElasticsearchVersion;

Expand All @@ -425,15 +423,13 @@ export interface DomainProps {
*/
readonly encryptionAtRest?: EncryptionAtRestOptions;


/**
* Configuration log publishing configuration options.
*
* @default - No logs are published
*/
readonly logging?: LoggingOptions;


/**
* Specify true to enable node to node encryption.
* Requires Elasticsearch version 6.0 or later.
Expand Down Expand Up @@ -511,27 +507,6 @@ export interface IDomain extends cdk.IResource {
*/
readonly domainEndpoint: string;

/**
* Log group that slow searches are logged to.
*
* @attribute
*/
readonly slowSearchLogGroup?: logs.ILogGroup;

/**
* Log group that slow indices are logged to.
*
* @attribute
*/
readonly slowIndexLogGroup?: logs.ILogGroup;

/**
* Log group that application logs are logged to.
*
* @attribute
*/
readonly appLogGroup?: logs.ILogGroup;

/**
* Grant read permissions for this domain and its contents to an IAM
* principal (Role/Group/User).
Expand Down Expand Up @@ -1149,10 +1124,33 @@ export class Domain extends DomainBase implements IDomain {
public readonly domainArn: string;
public readonly domainName: string;
public readonly domainEndpoint: string;

/**
* Log group that slow searches are logged to.
*
* @attribute
*/
public readonly slowSearchLogGroup?: logs.ILogGroup;

/**
* Log group that slow indices are logged to.
*
* @attribute
*/
public readonly slowIndexLogGroup?: logs.ILogGroup;

/**
* Log group that application logs are logged to.
*
* @attribute
*/
public readonly appLogGroup?: logs.ILogGroup;

/**
* Master user password if fine grained access control is configured.
*/
public readonly masterUserPassword?: cdk.SecretValue;


private readonly domain: CfnDomain;

Expand All @@ -1161,11 +1159,6 @@ export class Domain extends DomainBase implements IDomain {
physicalName: props.domainName,
});

// If VPC options are supplied ensure that the number of subnets matches the number AZ
if (props.vpcOptions?.subnets.map((subnet) => subnet.availabilityZone).length != props?.zoneAwareness?.availabilityZoneCount) {
throw new Error('When providing vpc options you need to provide a subnet for each AZ you are using');
};

const defaultInstanceType = 'r5.large.elasticsearch';

const dedicatedMasterType =
Expand All @@ -1190,6 +1183,12 @@ export class Domain extends DomainBase implements IDomain {
props.zoneAwareness?.enabled ??
props.zoneAwareness?.availabilityZoneCount != null;

// If VPC options are supplied ensure that the number of subnets matches the number AZ
if (props.vpcOptions != null && zoneAwarenessEnabled &&
new Set(props.vpcOptions?.subnets.map((subnet) => subnet.availabilityZone)).size < availabilityZoneCount) {
throw new Error('When providing vpc options you need to provide a subnet for each AZ you are using');
};

if ([dedicatedMasterType, instanceType].some(t => !t.endsWith('.elasticsearch'))) {
throw new Error('Master and data node instance types must end with ".elasticsearch".');
}
Expand Down Expand Up @@ -1231,24 +1230,27 @@ export class Domain extends DomainBase implements IDomain {
const masterUserArn = props.fineGrainedAccessControl?.masterUserArn;
const masterUserName = props.fineGrainedAccessControl?.masterUserName;

if (masterUserArn != null && masterUserName != null) {
throw new Error('Invalid fine grained access control settings. Only provide one of master user ARN or master user name. Not both.');
}

const advancedSecurityEnabled = (masterUserArn ?? masterUserName) != null;
const internalUserDatabaseEnabled = masterUserName != null;
const masterUserPasswordString = props.fineGrainedAccessControl?.masterUserPasswordSecret?.toString();
const createMasterUserPasswordSecret = (): string => {
return new secretsmanager.Secret(this, id, {
const masterUserPasswordProp = props.fineGrainedAccessControl?.masterUserPassword;
const createMasterUserPassword = (): cdk.SecretValue => {
return new secretsmanager.Secret(this, `${id}Password`, {
generateSecretString: {
secretStringTemplate: JSON.stringify({
username: masterUserName,
}),
generateStringKey: 'password',
},
})
.secretValueFromJson('password')
.toString();
.secretValueFromJson('password');
};
const masterUserPassword =
masterUserPasswordString ??
internalUserDatabaseEnabled ? createMasterUserPasswordSecret() : undefined;
this.masterUserPassword = internalUserDatabaseEnabled ?
(masterUserPasswordProp ?? createMasterUserPassword())
: undefined;

const encryptionAtRestEnabled =
props.encryptionAtRest?.enabled ?? props.encryptionAtRest?.kmsKey != null;
Expand Down Expand Up @@ -1460,7 +1462,7 @@ export class Domain extends DomainBase implements IDomain {
masterUserOptions: {
masterUserArn: masterUserArn,
masterUserName: masterUserName,
masterUserPassword: masterUserPassword,
masterUserPassword: this.masterUserPassword?.toString(),
},
}
: undefined,
Expand Down
72 changes: 61 additions & 11 deletions packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,14 +422,30 @@ describe('import', () => {
expect(stack).not.toHaveResource('AWS::Elasticsearch::Domain');
});

test('static fromDomainAttributes(attributes) allows importing an external/existing domain', () => {
const domainName = 'test-domain-2w2x2u3tifly';
const domainArn = `es:testregion:1234:domain/${domainName}`;
const domainEndpoint = `https://${domainName}-jcjotrt6f7otem4sqcwbch3c4u.testregion.es.amazonaws.com`;
const imported = Domain.fromDomainAttributes(stack, 'Domain', {
domainArn,
domainEndpoint,
});

expect(imported.domainName).toEqual(domainName);
expect(imported.domainArn).toEqual(domainArn);

expect(stack).not.toHaveResource('AWS::Elasticsearch::Domain');
});

});

describe('advanced security options', () => {
const masterUserArn = 'arn:aws:iam::123456789012:user/JohnDoe';
const masterUserName = 'JohnDoe';
const masterUserPasswordSecret = SecretValue.plainText('password');
const password = 'password';
const masterUserPassword = SecretValue.plainText(password);

test('enable fine-grained access logging with a master user ARN', () => {
test('enable fine-grained access control with a master user ARN', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_1,
fineGrainedAccessControl: {
Expand Down Expand Up @@ -462,12 +478,46 @@ describe('advanced security options', () => {
});
});

test('enable fine-grained access logging with a master user name and password', () => {
test('enable fine-grained access control with a master user name and password', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_1,
fineGrainedAccessControl: {
masterUserName,
masterUserPassword,
},
encryptionAtRest: {
enabled: true,
},
nodeToNodeEncryption: true,
enforceHttps: true,
});

expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', {
AdvancedSecurityOptions: {
Enabled: true,
InternalUserDatabaseEnabled: true,
MasterUserOptions: {
MasterUserName: masterUserName,
MasterUserPassword: password,
},
},
EncryptionAtRestOptions: {
Enabled: true,
},
NodeToNodeEncryptionOptions: {
Enabled: true,
},
DomainEndpointOptions: {
EnforceHTTPS: true,
},
});
});

test('enable fine-grained access control with a master user name and dynamically generated password', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_1,
fineGrainedAccessControl: {
masterUserName,
masterUserPasswordSecret,
},
encryptionAtRest: {
enabled: true,
Expand All @@ -488,7 +538,7 @@ describe('advanced security options', () => {
[
'{{resolve:secretsmanager:',
{
Ref: 'Domain15DC21D9',
Ref: 'DomainDomainPassword65DAD325',
},
':SecretString:password::}}',
],
Expand All @@ -514,7 +564,7 @@ describe('advanced security options', () => {
});
});

test('enabling fine-grained access logging throws with Elasticsearch < 6.7', () => {
test('enabling fine-grained access control throws with Elasticsearch < 6.7', () => {
expect(() => new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V6_5,
fineGrainedAccessControl: {
Expand All @@ -525,10 +575,10 @@ describe('advanced security options', () => {
},
nodeToNodeEncryption: true,
enforceHttps: true,
})).toThrow(/Fine-grained access logging requires Elasticsearch version 6\.7 or later/);
})).toThrow(/Fine-grained access control requires Elasticsearch version 6\.7 or later/);
});

test('enabling fine-grained access logging throws without node-to-node encryption enabled', () => {
test('enabling fine-grained access control throws without node-to-node encryption enabled', () => {
expect(() => new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_7,
fineGrainedAccessControl: {
Expand All @@ -542,7 +592,7 @@ describe('advanced security options', () => {
})).toThrow(/Node-to-node encryption is required when fine-grained access control is enabled/);
});

test('enabling fine-grained access logging throws without encryption-at-rest enabled', () => {
test('enabling fine-grained access control throws without encryption-at-rest enabled', () => {
expect(() => new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_7,
fineGrainedAccessControl: {
Expand All @@ -556,7 +606,7 @@ describe('advanced security options', () => {
})).toThrow(/Encryption-at-rest is required when fine-grained access control is enabled/);
});

test('enabling fine-grained access logging throws without enforceHttps enabled', () => {
test('enabling fine-grained access control throws without enforceHttps enabled', () => {
expect(() => new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_7,
fineGrainedAccessControl: {
Expand Down Expand Up @@ -757,7 +807,7 @@ describe('custom error responses', () => {
capacity: {
masterNodeInstanceType: 'm5.large.elasticsearch',
},
})).toThrow(/EBS volumes are required for all instance types except R3 and I3/);
})).toThrow(/EBS volumes are required when using instance types other than r3 or i3/);
});

test('error when availabilityZoneCount is not 2 or 3', () => {
Expand Down

0 comments on commit caa310d

Please sign in to comment.