Skip to content

Commit

Permalink
feat(elasticsearch): support audit logs (aws#12106)
Browse files Browse the repository at this point in the history
Implemented support for audit logs like other logs (application, slow search and slow index) are implemented.
Audit logs can only be enabled when fine grained access control is enabled and ES version >= 6.7 [see here](https://aws.amazon.com/about-aws/whats-new/2020/09/elasticsearch-audit-logs-now-available-on-amazon-elasticsearch-service/).

The check for fine grained access control enabled is implemented. Version check is implicitly implemented since fine grained access control cannot be enabled < 6.7.

Closes aws#12105

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Ruben-E authored and flochaz committed Jan 5, 2021
1 parent 99f40d8 commit c9d8382
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 1 deletion.
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-elasticsearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,29 @@ const domain = new es.Domain(this, 'Domain', {

const masterUserPassword = domain.masterUserPassword;
```



## Audit logs

Audit logs can be enabled for a domain, but only when fine grained access control is enabled.

```ts
const domain = new es.Domain(this, 'Domain', {
version: es.ElasticsearchVersion.V7_1,
enforceHttps: true,
nodeToNodeEncryption: true,
encryptionAtRest: {
enabled: true,
},
fineGrainedAccessControl: {
masterUserName: 'master-user',
},
logging: {
auditLogEnabled: true,
slowSearchLogEnabled: true,
appLogEnabled: true,
slowIndexLogEnabled: true,
},
});
```
41 changes: 41 additions & 0 deletions packages/@aws-cdk/aws-elasticsearch/lib/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,21 @@ export interface LoggingOptions {
* @default - a new log group is created if app logging is enabled
*/
readonly appLogGroup?: logs.ILogGroup;

/**
* Specify if Elasticsearch audit logging should be set up.
* Requires Elasticsearch version 6.7 or later and fine grained access control to be enabled.
*
* @default - false
*/
readonly auditLogEnabled?: boolean;

/**
* Log Elasticsearch audit logs to this log group.
*
* @default - a new log group is created if audit logging is enabled
*/
readonly auditLogGroup?: logs.ILogGroup;
}

/**
Expand Down Expand Up @@ -1162,6 +1177,13 @@ export class Domain extends DomainBase implements IDomain {
*/
public readonly appLogGroup?: logs.ILogGroup;

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

/**
* Master user password if fine grained access control is configured.
*/
Expand Down Expand Up @@ -1365,6 +1387,12 @@ export class Domain extends DomainBase implements IDomain {
}
}

// Validate fine grained access control enabled for audit logs, per
// https://aws.amazon.com/about-aws/whats-new/2020/09/elasticsearch-audit-logs-now-available-on-amazon-elasticsearch-service/
if (props.logging?.auditLogEnabled && !advancedSecurityEnabled) {
throw new Error('Fine-grained access control is required when audit logs publishing is enabled.');
}

let cfnVpcOptions: CfnDomain.VPCOptionsProperty | undefined;
if (props.vpcOptions) {
cfnVpcOptions = {
Expand Down Expand Up @@ -1403,6 +1431,15 @@ export class Domain extends DomainBase implements IDomain {
logGroups.push(this.appLogGroup);
};

if (props.logging?.auditLogEnabled) {
this.auditLogGroup = props.logging.auditLogGroup ??
new logs.LogGroup(this, 'AuditLogs', {
retention: logs.RetentionDays.ONE_MONTH,
});

logGroups.push(this.auditLogGroup);
};

let logGroupResourcePolicy: LogGroupResourcePolicy | null = null;
if (logGroups.length > 0) {
const logPolicyStatement = new iam.PolicyStatement({
Expand Down Expand Up @@ -1454,6 +1491,10 @@ export class Domain extends DomainBase implements IDomain {
},
nodeToNodeEncryptionOptions: { enabled: nodeToNodeEncryptionEnabled },
logPublishingOptions: {
AUDIT_LOGS: {
enabled: this.auditLogGroup != null,
cloudWatchLogsLogGroupArn: this.auditLogGroup?.logGroupArn,
},
ES_APPLICATION_LOGS: {
enabled: this.appLogGroup != null,
cloudWatchLogsLogGroupArn: this.appLogGroup?.logGroupArn,
Expand Down
201 changes: 201 additions & 0 deletions packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@aws-cdk/assert/jest';
import { Metric, Statistic } from '@aws-cdk/aws-cloudwatch';
import { Subnet, Vpc, EbsDeviceVolumeType } from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as logs from '@aws-cdk/aws-logs';
import { App, Stack, Duration, SecretValue } from '@aws-cdk/core';
import { Domain, ElasticsearchVersion } from '../lib';

Expand Down Expand Up @@ -47,6 +48,9 @@ test('minimal example renders correctly', () => {
Enabled: false,
},
LogPublishingOptions: {
AUDIT_LOGS: {
Enabled: false,
},
ES_APPLICATION_LOGS: {
Enabled: false,
},
Expand Down Expand Up @@ -152,6 +156,46 @@ describe('log groups', () => {
});
});

test('auditLogEnabled should create a custom log group', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_4,
logging: {
auditLogEnabled: true,
},
fineGrainedAccessControl: {
masterUserName: 'username',
},
nodeToNodeEncryption: true,
encryptionAtRest: {
enabled: true,
},
enforceHttps: true,
});

expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', {
LogPublishingOptions: {
AUDIT_LOGS: {
CloudWatchLogsLogGroupArn: {
'Fn::GetAtt': [
'DomainAuditLogs608E0FA6',
'Arn',
],
},
Enabled: true,
},
ES_APPLICATION_LOGS: {
Enabled: false,
},
SEARCH_SLOW_LOGS: {
Enabled: false,
},
INDEX_SLOW_LOGS: {
Enabled: false,
},
},
});
});

test('two domains with logging enabled can be created in same stack', () => {
new Domain(stack, 'Domain1', {
version: ElasticsearchVersion.V7_7,
Expand Down Expand Up @@ -265,6 +309,163 @@ describe('log groups', () => {
});
});

test('enabling audit logs throws without fine grained access control enabled', () => {
expect(() => new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V6_7,
logging: {
auditLogEnabled: true,
},
})).toThrow(/Fine-grained access control is required when audit logs publishing is enabled\./);
});

test('slowSearchLogGroup should use a custom log group', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_4,
logging: {
slowSearchLogEnabled: true,
slowSearchLogGroup: new logs.LogGroup(stack, 'SlowSearchLogs', {
retention: logs.RetentionDays.THREE_MONTHS,
}),
},
});

expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', {
LogPublishingOptions: {
AUDIT_LOGS: {
Enabled: false,
},
ES_APPLICATION_LOGS: {
Enabled: false,
},
SEARCH_SLOW_LOGS: {
CloudWatchLogsLogGroupArn: {
'Fn::GetAtt': [
'SlowSearchLogsE00DC2E7',
'Arn',
],
},
Enabled: true,
},
INDEX_SLOW_LOGS: {
Enabled: false,
},
},
});
});

test('slowIndexLogEnabled should use a custom log group', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_4,
logging: {
slowIndexLogEnabled: true,
slowIndexLogGroup: new logs.LogGroup(stack, 'SlowIndexLogs', {
retention: logs.RetentionDays.THREE_MONTHS,
}),
},
});

expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', {
LogPublishingOptions: {
AUDIT_LOGS: {
Enabled: false,
},
ES_APPLICATION_LOGS: {
Enabled: false,
},
SEARCH_SLOW_LOGS: {
Enabled: false,
},
INDEX_SLOW_LOGS: {
CloudWatchLogsLogGroupArn: {
'Fn::GetAtt': [
'SlowIndexLogsAD49DED0',
'Arn',
],
},
Enabled: true,
},
},
});
});

test('appLogGroup should use a custom log group', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_4,
logging: {
appLogEnabled: true,
appLogGroup: new logs.LogGroup(stack, 'AppLogs', {
retention: logs.RetentionDays.THREE_MONTHS,
}),
},
});

expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', {
LogPublishingOptions: {
AUDIT_LOGS: {
Enabled: false,
},
ES_APPLICATION_LOGS: {
CloudWatchLogsLogGroupArn: {
'Fn::GetAtt': [
'AppLogsC5DF83A6',
'Arn',
],
},
Enabled: true,
},
SEARCH_SLOW_LOGS: {
Enabled: false,
},
INDEX_SLOW_LOGS: {
Enabled: false,
},
},
});
});

test('auditLOgGroup should use a custom log group', () => {
new Domain(stack, 'Domain', {
version: ElasticsearchVersion.V7_4,
fineGrainedAccessControl: {
masterUserName: 'username',
},
nodeToNodeEncryption: true,
encryptionAtRest: {
enabled: true,
},
enforceHttps: true,
logging: {
auditLogEnabled: true,
auditLogGroup: new logs.LogGroup(stack, 'AuditLogs', {
retention: logs.RetentionDays.THREE_MONTHS,
}),
},
});

expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', {
LogPublishingOptions: {
AUDIT_LOGS: {
CloudWatchLogsLogGroupArn: {
'Fn::GetAtt': [
'AuditLogsB945E340',
'Arn',
],
},
Enabled: true,
},
ES_APPLICATION_LOGS: {
Enabled: false,
},
SEARCH_SLOW_LOGS: {
Enabled: false,
},
INDEX_SLOW_LOGS: {
Enabled: false,
},
},
});
});

});

describe('grants', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
"Enabled": true
},
"LogPublishingOptions": {
"AUDIT_LOGS": {
"Enabled": false
},
"ES_APPLICATION_LOGS": {
"Enabled": false
},
Expand All @@ -57,4 +60,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@
"Enabled": true
},
"LogPublishingOptions": {
"AUDIT_LOGS": {
"Enabled": false
},
"ES_APPLICATION_LOGS": {
"CloudWatchLogsLogGroupArn": {
"Fn::GetAtt": [
Expand Down Expand Up @@ -431,6 +434,9 @@
"Enabled": true
},
"LogPublishingOptions": {
"AUDIT_LOGS": {
"Enabled": false
},
"ES_APPLICATION_LOGS": {
"CloudWatchLogsLogGroupArn": {
"Fn::GetAtt": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
"Enabled": true
},
"LogPublishingOptions": {
"AUDIT_LOGS": {
"Enabled": false
},
"ES_APPLICATION_LOGS": {
"Enabled": false
},
Expand Down

0 comments on commit c9d8382

Please sign in to comment.