Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(elasticsearch): support audit logs #12106

Merged
merged 6 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
```

iliapolo marked this conversation as resolved.
Show resolved Hide resolved

iliapolo marked this conversation as resolved.
Show resolved Hide resolved

## 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