Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into DaWyz/events-targets-stepfunction-dlq
Browse files Browse the repository at this point in the history
mergify[bot] authored Mar 8, 2021
2 parents b3a31c2 + 6c5b1f4 commit 5dcf677
Showing 10 changed files with 260 additions and 77 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-docdb/lib/cluster.ts
Original file line number Diff line number Diff line change
@@ -238,7 +238,7 @@ export class DatabaseCluster extends DatabaseClusterBase {
public readonly clusterResourceIdentifier: string;

/**
* The connections object to implement IConectable
* The connections object to implement IConnectable
*/
public readonly connections: ec2.Connections;

Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ export class GlobalTableCoordinator extends cdk.Stack {
code: lambda.Code.fromAsset(path.resolve(__dirname, '../', 'lambda-packages', 'aws-global-table-coordinator', 'lib')),
description: 'Lambda to make DynamoDB a global table',
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_10_X,
runtime: lambda.Runtime.NODEJS_14_X,
timeout: cdk.Duration.minutes(5),
uuid: 'D38B65A6-6B54-4FB6-9BAD-9CD40A6DAC12',
});
Original file line number Diff line number Diff line change
@@ -203,7 +203,7 @@
"Arn"
]
},
"Runtime": "nodejs10.x",
"Runtime": "nodejs14.x",
"Description": "Lambda to make DynamoDB a global table",
"Timeout": 300
},
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-dynamodb/README.md
Original file line number Diff line number Diff line change
@@ -109,6 +109,17 @@ globalTable.autoScaleWriteCapacity({
}).scaleOnUtilization({ targetUtilizationPercent: 75 });
```

When adding a replica region for a large table, you might want to increase the
timeout for the replication operation:

```ts
const globalTable = new dynamodb.Table(this, 'Table', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
replicationRegions: ['us-east-1', 'us-east-2', 'us-west-2'],
replicationTimeout: Duration.hours(2), // defaults to Duration.minutes(30)
});
```

## Encryption

All user data stored in Amazon DynamoDB is fully encrypted at rest. When creating a new table, you can choose to encrypt using the following customer master keys (CMK) to encrypt your table:
19 changes: 16 additions & 3 deletions packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts
Original file line number Diff line number Diff line change
@@ -9,14 +9,26 @@ import { Construct } from 'constructs';
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* Properties for a ReplicaProvider
*/
export interface ReplicaProviderProps {
/**
* The timeout for the replication operation.
*
* @default Duration.minutes(30)
*/
readonly timeout?: Duration;
}

export class ReplicaProvider extends NestedStack {
/**
* Creates a stack-singleton resource provider nested stack.
*/
public static getOrCreate(scope: Construct) {
public static getOrCreate(scope: Construct, props: ReplicaProviderProps = {}) {
const stack = Stack.of(scope);
const uid = '@aws-cdk/aws-dynamodb.ReplicaProvider';
return stack.node.tryFindChild(uid) as ReplicaProvider || new ReplicaProvider(stack, uid);
return stack.node.tryFindChild(uid) as ReplicaProvider ?? new ReplicaProvider(stack, uid, props);
}

/**
@@ -34,7 +46,7 @@ export class ReplicaProvider extends NestedStack {
*/
public readonly isCompleteHandler: lambda.Function;

private constructor(scope: Construct, id: string) {
private constructor(scope: Construct, id: string, props: ReplicaProviderProps = {}) {
super(scope as CoreConstruct, id);

const code = lambda.Code.fromAsset(path.join(__dirname, 'replica-handler'));
@@ -80,6 +92,7 @@ export class ReplicaProvider extends NestedStack {
onEventHandler: this.onEventHandler,
isCompleteHandler: this.isCompleteHandler,
queryInterval: Duration.seconds(10),
totalTimeout: props.timeout,
});
}
}
17 changes: 12 additions & 5 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import {
Aws, CfnCondition, CfnCustomResource, CustomResource, Fn,
IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token,
Aws, CfnCondition, CfnCustomResource, CustomResource, Duration,
Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token,
} from '@aws-cdk/core';
import { Construct } from 'constructs';
import { DynamoDBMetrics } from './dynamodb-canned-metrics.generated';
@@ -218,6 +218,13 @@ export interface TableOptions {
* @experimental
*/
readonly replicationRegions?: string[];

/**
* The timeout for a table replication operation in a single region.
*
* @default Duration.minutes(30)
*/
readonly replicationTimeout?: Duration;
}

/**
@@ -1135,7 +1142,7 @@ export class Table extends TableBase {
}

if (props.replicationRegions && props.replicationRegions.length > 0) {
this.createReplicaTables(props.replicationRegions);
this.createReplicaTables(props.replicationRegions, props.replicationTimeout);
}
}

@@ -1451,14 +1458,14 @@ export class Table extends TableBase {
*
* @param regions regions where to create tables
*/
private createReplicaTables(regions: string[]) {
private createReplicaTables(regions: string[], timeout?: Duration) {
const stack = Stack.of(this);

if (!Token.isUnresolved(stack.region) && regions.includes(stack.region)) {
throw new Error('`replicationRegions` cannot include the region where this stack is deployed.');
}

const provider = ReplicaProvider.getOrCreate(this);
const provider = ReplicaProvider.getOrCreate(this, { timeout });

// Documentation at https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2gt_IAM.html
// is currently incorrect. AWS Support recommends `dynamodb:*` in both source and destination regions
35 changes: 23 additions & 12 deletions packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import { App, Aws, CfnDeletionPolicy, ConstructNode, Duration, PhysicalName, RemovalPolicy, Resource, Stack, Tags } from '@aws-cdk/core';
import * as cr from '@aws-cdk/custom-resources';
import { testLegacyBehavior } from 'cdk-build-tools/lib/feature-flag';
import { Construct } from 'constructs';
import {
@@ -20,6 +21,8 @@ import {
CfnTable,
} from '../lib';

jest.mock('@aws-cdk/custom-resources');

/* eslint-disable quote-props */

// CDK parameters
@@ -2295,12 +2298,6 @@ describe('global', () => {
// THEN
expect(stack).toHaveResource('Custom::DynamoDBReplica', {
Properties: {
ServiceToken: {
'Fn::GetAtt': [
'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D',
'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn',
],
},
TableName: {
Ref: 'TableCD117FA1',
},
@@ -2311,12 +2308,6 @@ describe('global', () => {

expect(stack).toHaveResource('Custom::DynamoDBReplica', {
Properties: {
ServiceToken: {
'Fn::GetAtt': [
'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D',
'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn',
],
},
TableName: {
Ref: 'TableCD117FA1',
},
@@ -2814,6 +2805,26 @@ describe('global', () => {
// THEN
expect(SynthUtils.toCloudFormation(stack).Conditions).toBeUndefined();
});

test('can configure timeout', () => {
// GIVEN
const stack = new Stack();

// WHEN
new Table(stack, 'Table', {
partitionKey: {
name: 'id',
type: AttributeType.STRING,
},
replicationRegions: ['eu-central-1'],
replicationTimeout: Duration.hours(1),
});

// THEN
expect(cr.Provider).toHaveBeenCalledWith(expect.anything(), expect.any(String), expect.objectContaining({
totalTimeout: Duration.hours(1),
}));
});
});

test('L1 inside L2 expects removalpolicy to have been set', () => {
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-neptune/README.md
Original file line number Diff line number Diff line change
@@ -58,6 +58,24 @@ attributes:
const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT"
```

## IAM Authentication

You can also authenticate to a database cluster using AWS Identity and Access Management (IAM) database authentication;
See <https://docs.aws.amazon.com/neptune/latest/userguide/iam-auth.html> for more information and a list of supported
versions and limitations.

The following example shows enabling IAM authentication for a database cluster and granting connection access to an IAM role.

```ts
const cluster = new rds.DatabaseCluster(stack, 'Cluster', {
vpc,
instanceType: neptune.InstanceType.R5_LARGE,
iamAuthentication: true, // Optional - will be automatically set if you call grantConnect().
});
const role = new Role(stack, 'DBRole', { assumedBy: new AccountPrincipal(stack.account) });
instance.grantConnect(role); // Grant the role connection access to the DB.
```

## Customizing parameters

Neptune allows configuring database behavior by supplying custom parameter groups. For more details, refer to the
91 changes: 72 additions & 19 deletions packages/@aws-cdk/aws-neptune/lib/cluster.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import { Duration, IResource, RemovalPolicy, Resource, Token } from '@aws-cdk/core';
import { Aws, Duration, IResource, Lazy, RemovalPolicy, Resource, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { Endpoint } from './endpoint';
import { InstanceType } from './instance';
@@ -119,6 +119,13 @@ export interface DatabaseClusterProps {
*/
readonly dbClusterName?: string;

/**
* Map AWS Identity and Access Management (IAM) accounts to database accounts
*
* @default - `false`
*/
readonly iamAuthentication?: boolean;

/**
* Base identifier for instances
*
@@ -233,6 +240,11 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable {
* @attribute ReadEndpoint
*/
readonly clusterReadEndpoint: Endpoint;

/**
* Grant the given identity connection access to the database.
*/
grantConnect(grantee: iam.IGrantable): iam.Grant;
}

/**
@@ -266,23 +278,15 @@ export interface DatabaseClusterAttributes {
}

/**
* Create a clustered database with a given number of instances.
*
* @resource AWS::Neptune::DBCluster
* A new or imported database cluster.
*/
export class DatabaseCluster extends Resource implements IDatabaseCluster {

/**
* The default number of instances in the Neptune cluster if none are
* specified
*/
public static readonly DEFAULT_NUM_INSTANCES = 1;
export abstract class DatabaseClusterBase extends Resource implements IDatabaseCluster {

/**
* Import an existing DatabaseCluster from properties
*/
public static fromDatabaseClusterAttributes(scope: Construct, id: string, attrs: DatabaseClusterAttributes): IDatabaseCluster {
class Import extends Resource implements IDatabaseCluster {
class Import extends DatabaseClusterBase implements IDatabaseCluster {
public readonly defaultPort = ec2.Port.tcp(attrs.port);
public readonly connections = new ec2.Connections({
securityGroups: [attrs.securityGroup],
@@ -291,6 +295,7 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
public readonly clusterIdentifier = attrs.clusterIdentifier;
public readonly clusterEndpoint = new Endpoint(attrs.clusterEndpointAddress, attrs.port);
public readonly clusterReadEndpoint = new Endpoint(attrs.readerEndpointAddress, attrs.port);
protected enableIamAuthentication = true;
}

return new Import(scope, id);
@@ -299,17 +304,65 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
/**
* Identifier of the cluster
*/
public readonly clusterIdentifier: string;
public abstract readonly clusterIdentifier: string;

/**
* The endpoint to use for read/write operations
*/
public readonly clusterEndpoint: Endpoint;
public abstract readonly clusterEndpoint: Endpoint;

/**
* Endpoint to use for load-balanced read-only operations.
*/
public abstract readonly clusterReadEndpoint: Endpoint;

/**
* The connections object to implement IConnectable
*/
public abstract readonly connections: ec2.Connections;

protected abstract enableIamAuthentication?: boolean;

public grantConnect(grantee: iam.IGrantable): iam.Grant {
if (this.enableIamAuthentication === false) {
throw new Error('Cannot grant connect when IAM authentication is disabled');
}

this.enableIamAuthentication = true;
return iam.Grant.addToPrincipal({
grantee,
actions: ['neptune-db:*'],
resourceArns: [
[
'arn',
Aws.PARTITION,
'neptune-db',
Aws.REGION,
Aws.ACCOUNT_ID,
`${this.clusterIdentifier}/*`,
].join(':'),
],
});
}
}

/**
* Create a clustered database with a given number of instances.
*
* @resource AWS::Neptune::DBCluster
*/
export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseCluster {

/**
* The default number of instances in the Neptune cluster if none are
* specified
*/
public static readonly DEFAULT_NUM_INSTANCES = 1;

public readonly clusterIdentifier: string;
public readonly clusterEndpoint: Endpoint;
public readonly clusterReadEndpoint: Endpoint;
public readonly connections: ec2.Connections;

/**
* The resource id for the cluster; for example: cluster-ABCD1234EFGH5678IJKL90MNOP. The cluster ID uniquely
@@ -318,11 +371,6 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
*/
public readonly clusterResourceIdentifier: string;

/**
* The connections object to implement IConectable
*/
public readonly connections: ec2.Connections;

/**
* The VPC where the DB subnet group is created.
*/
@@ -348,6 +396,8 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
*/
public readonly instanceEndpoints: Endpoint[] = [];

protected enableIamAuthentication?: boolean;

constructor(scope: Construct, id: string, props: DatabaseClusterProps) {
super(scope, id);

@@ -385,6 +435,8 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {

const deletionProtection = props.deletionProtection ?? (props.removalPolicy === RemovalPolicy.RETAIN ? true : undefined);

this.enableIamAuthentication = props.iamAuthentication;

// Create the Neptune cluster
const cluster = new CfnDBCluster(this, 'Resource', {
// Basic
@@ -396,6 +448,7 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
dbClusterParameterGroupName: props.clusterParameterGroup?.clusterParameterGroupName,
deletionProtection: deletionProtection,
associatedRoles: props.associatedRoles ? props.associatedRoles.map(role => ({ roleArn: role.roleArn })) : undefined,
iamAuthEnabled: Lazy.any({ produce: () => this.enableIamAuthentication }),
// Backup
backupRetentionPeriod: props.backupRetention?.toDays(),
preferredBackupWindow: props.preferredBackupWindow,
140 changes: 105 additions & 35 deletions packages/@aws-cdk/aws-neptune/test/cluster.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect as expectCDK, haveResource, ResourcePart } from '@aws-cdk/assert';
import '@aws-cdk/assert/jest';
import { ABSENT, ResourcePart } from '@aws-cdk/assert';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
@@ -20,28 +21,28 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
Properties: {
DBSubnetGroupName: { Ref: 'DatabaseSubnets3C9252C9' },
VpcSecurityGroupIds: [{ 'Fn::GetAtt': ['DatabaseSecurityGroup5C91FDCB', 'GroupId'] }],
StorageEncrypted: true,
},
DeletionPolicy: 'Retain',
UpdateReplacePolicy: 'Retain',
}, ResourcePart.CompleteDefinition));
}, ResourcePart.CompleteDefinition);

expectCDK(stack).to(haveResource('AWS::Neptune::DBInstance', {
expect(stack).toHaveResource('AWS::Neptune::DBInstance', {
DeletionPolicy: 'Retain',
UpdateReplacePolicy: 'Retain',
}, ResourcePart.CompleteDefinition));
}, ResourcePart.CompleteDefinition);

expectCDK(stack).to(haveResource('AWS::Neptune::DBSubnetGroup', {
expect(stack).toHaveResource('AWS::Neptune::DBSubnetGroup', {
SubnetIds: [
{ Ref: 'VPCPrivateSubnet1Subnet8BCA10E0' },
{ Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A' },
{ Ref: 'VPCPrivateSubnet3Subnet3EDCD457' },
],
}));
});
});

test('can create a cluster with a single instance', () => {
@@ -57,10 +58,10 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
DBSubnetGroupName: { Ref: 'DatabaseSubnets3C9252C9' },
VpcSecurityGroupIds: [{ 'Fn::GetAtt': ['DatabaseSecurityGroup5C91FDCB', 'GroupId'] }],
}));
});
});

test('errors when less than one instance is specified', () => {
@@ -111,11 +112,11 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
EngineVersion: '1.0.4.1',
DBSubnetGroupName: { Ref: 'DatabaseSubnets3C9252C9' },
VpcSecurityGroupIds: [{ 'Fn::GetAtt': ['DatabaseSecurityGroup5C91FDCB', 'GroupId'] }],
}));
});
});

test('can create a cluster with imported vpc and security group', () => {
@@ -135,10 +136,10 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
DBSubnetGroupName: { Ref: 'DatabaseSubnets3C9252C9' },
VpcSecurityGroupIds: ['SecurityGroupId12345'],
}));
});
});

test('cluster with parameter group', () => {
@@ -160,9 +161,9 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
DBClusterParameterGroupName: { Ref: 'ParamsA8366201' },
}));
});
});

test('cluster with associated role', () => {
@@ -183,7 +184,7 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
AssociatedRoles: [
{
RoleArn: {
@@ -194,7 +195,7 @@ describe('DatabaseCluster', () => {
},
},
],
}));
});
});

test('cluster with imported parameter group', () => {
@@ -212,9 +213,9 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
DBClusterParameterGroupName: 'ParamGroupName',
}));
});
});

test('create an encrypted cluster with custom KMS key', () => {
@@ -230,15 +231,15 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
KmsKeyId: {
'Fn::GetAtt': [
'Key961B73FD',
'Arn',
],
},
StorageEncrypted: true,
}));
});
});

test('creating a cluster defaults to using encryption', () => {
@@ -253,9 +254,9 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
StorageEncrypted: true,
}));
});
});

test('supplying a KMS key with storageEncryption false throws an error', () => {
@@ -306,9 +307,9 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBInstance', {
expect(stack).toHaveResource('AWS::Neptune::DBInstance', {
DBInstanceIdentifier: `${instanceIdentifierBase}1`,
}));
});
});

test('cluster identifier used', () => {
@@ -325,9 +326,9 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBInstance', {
expect(stack).toHaveResource('AWS::Neptune::DBInstance', {
DBInstanceIdentifier: `${clusterIdentifier}instance1`,
}));
});
});

test('imported cluster has supplied attributes', () => {
@@ -370,9 +371,9 @@ describe('DatabaseCluster', () => {
cluster.connections.allowToAnyIpv4(ec2.Port.tcp(443));

// THEN
expectCDK(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', {
expect(stack).toHaveResource('AWS::EC2::SecurityGroupEgress', {
GroupId: 'sg-123456789',
}));
});
});

test('backup retention period respected', () => {
@@ -388,9 +389,9 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
BackupRetentionPeriod: 20,
}));
});
});

test('backup maintenance window respected', () => {
@@ -407,10 +408,10 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
BackupRetentionPeriod: 20,
PreferredBackupWindow: '07:34-08:04',
}));
});
});

test('regular maintenance window respected', () => {
@@ -426,9 +427,78 @@ describe('DatabaseCluster', () => {
});

// THEN
expectCDK(stack).to(haveResource('AWS::Neptune::DBCluster', {
expect(stack).toHaveResource('AWS::Neptune::DBCluster', {
PreferredMaintenanceWindow: '07:34-08:04',
}));
});
});

test('iam authentication - off by default', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');

// WHEN
new DatabaseCluster(stack, 'Cluster', {
vpc,
instanceType: InstanceType.R5_LARGE,
});

// THEN
expect(stack).toHaveResourceLike('AWS::Neptune::DBCluster', {
IamAuthEnabled: ABSENT,
});
});

test('createGrant - creates IAM policy and enables IAM auth', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');

// WHEN
const cluster = new DatabaseCluster(stack, 'Cluster', {
vpc,
instanceType: InstanceType.R5_LARGE,
});
const role = new iam.Role(stack, 'DBRole', {
assumedBy: new iam.AccountPrincipal(stack.account),
});
cluster.grantConnect(role);

// THEN
expect(stack).toHaveResourceLike('AWS::Neptune::DBCluster', {
IamAuthEnabled: true,
});
expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [{
Effect: 'Allow',
Action: 'neptune-db:*',
Resource: {
'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':neptune-db:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, ':', { Ref: 'ClusterEB0386A7' }, '/*']],
},
}],
Version: '2012-10-17',
},
});
});

test('createGrant - throws if IAM auth disabled', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');

// WHEN
const cluster = new DatabaseCluster(stack, 'Cluster', {
vpc,
instanceType: InstanceType.R5_LARGE,
iamAuthentication: false,
});
const role = new iam.Role(stack, 'DBRole', {
assumedBy: new iam.AccountPrincipal(stack.account),
});

// THEN
expect(() => { cluster.grantConnect(role); }).toThrow(/Cannot grant connect when IAM authentication is disabled/);
});
});

0 comments on commit 5dcf677

Please sign in to comment.