Skip to content

Commit 1069827

Browse files
christophgysincornerwings
authored andcommitted
feat(neptune): Support IAM authentication (aws#13462)
fixes aws#13461 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 987a2a4 commit 1069827

File tree

4 files changed

+196
-55
lines changed

4 files changed

+196
-55
lines changed

packages/@aws-cdk/aws-docdb/lib/cluster.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export class DatabaseCluster extends DatabaseClusterBase {
238238
public readonly clusterResourceIdentifier: string;
239239

240240
/**
241-
* The connections object to implement IConectable
241+
* The connections object to implement IConnectable
242242
*/
243243
public readonly connections: ec2.Connections;
244244

packages/@aws-cdk/aws-neptune/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ attributes:
5858
const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT"
5959
```
6060

61+
## IAM Authentication
62+
63+
You can also authenticate to a database cluster using AWS Identity and Access Management (IAM) database authentication;
64+
See <https://docs.aws.amazon.com/neptune/latest/userguide/iam-auth.html> for more information and a list of supported
65+
versions and limitations.
66+
67+
The following example shows enabling IAM authentication for a database cluster and granting connection access to an IAM role.
68+
69+
```ts
70+
const cluster = new rds.DatabaseCluster(stack, 'Cluster', {
71+
vpc,
72+
instanceType: neptune.InstanceType.R5_LARGE,
73+
iamAuthentication: true, // Optional - will be automatically set if you call grantConnect().
74+
});
75+
const role = new Role(stack, 'DBRole', { assumedBy: new AccountPrincipal(stack.account) });
76+
instance.grantConnect(role); // Grant the role connection access to the DB.
77+
```
78+
6179
## Customizing parameters
6280

6381
Neptune allows configuring database behavior by supplying custom parameter groups. For more details, refer to the

packages/@aws-cdk/aws-neptune/lib/cluster.ts

+72-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ec2 from '@aws-cdk/aws-ec2';
22
import * as iam from '@aws-cdk/aws-iam';
33
import * as kms from '@aws-cdk/aws-kms';
4-
import { Duration, IResource, RemovalPolicy, Resource, Token } from '@aws-cdk/core';
4+
import { Aws, Duration, IResource, Lazy, RemovalPolicy, Resource, Token } from '@aws-cdk/core';
55
import { Construct } from 'constructs';
66
import { Endpoint } from './endpoint';
77
import { InstanceType } from './instance';
@@ -119,6 +119,13 @@ export interface DatabaseClusterProps {
119119
*/
120120
readonly dbClusterName?: string;
121121

122+
/**
123+
* Map AWS Identity and Access Management (IAM) accounts to database accounts
124+
*
125+
* @default - `false`
126+
*/
127+
readonly iamAuthentication?: boolean;
128+
122129
/**
123130
* Base identifier for instances
124131
*
@@ -233,6 +240,11 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable {
233240
* @attribute ReadEndpoint
234241
*/
235242
readonly clusterReadEndpoint: Endpoint;
243+
244+
/**
245+
* Grant the given identity connection access to the database.
246+
*/
247+
grantConnect(grantee: iam.IGrantable): iam.Grant;
236248
}
237249

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

268280
/**
269-
* Create a clustered database with a given number of instances.
270-
*
271-
* @resource AWS::Neptune::DBCluster
281+
* A new or imported database cluster.
272282
*/
273-
export class DatabaseCluster extends Resource implements IDatabaseCluster {
274-
275-
/**
276-
* The default number of instances in the Neptune cluster if none are
277-
* specified
278-
*/
279-
public static readonly DEFAULT_NUM_INSTANCES = 1;
283+
export abstract class DatabaseClusterBase extends Resource implements IDatabaseCluster {
280284

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

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

304309
/**
305310
* The endpoint to use for read/write operations
306311
*/
307-
public readonly clusterEndpoint: Endpoint;
312+
public abstract readonly clusterEndpoint: Endpoint;
308313

309314
/**
310315
* Endpoint to use for load-balanced read-only operations.
311316
*/
317+
public abstract readonly clusterReadEndpoint: Endpoint;
318+
319+
/**
320+
* The connections object to implement IConnectable
321+
*/
322+
public abstract readonly connections: ec2.Connections;
323+
324+
protected abstract enableIamAuthentication?: boolean;
325+
326+
public grantConnect(grantee: iam.IGrantable): iam.Grant {
327+
if (this.enableIamAuthentication === false) {
328+
throw new Error('Cannot grant connect when IAM authentication is disabled');
329+
}
330+
331+
this.enableIamAuthentication = true;
332+
return iam.Grant.addToPrincipal({
333+
grantee,
334+
actions: ['neptune-db:*'],
335+
resourceArns: [
336+
[
337+
'arn',
338+
Aws.PARTITION,
339+
'neptune-db',
340+
Aws.REGION,
341+
Aws.ACCOUNT_ID,
342+
`${this.clusterIdentifier}/*`,
343+
].join(':'),
344+
],
345+
});
346+
}
347+
}
348+
349+
/**
350+
* Create a clustered database with a given number of instances.
351+
*
352+
* @resource AWS::Neptune::DBCluster
353+
*/
354+
export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseCluster {
355+
356+
/**
357+
* The default number of instances in the Neptune cluster if none are
358+
* specified
359+
*/
360+
public static readonly DEFAULT_NUM_INSTANCES = 1;
361+
362+
public readonly clusterIdentifier: string;
363+
public readonly clusterEndpoint: Endpoint;
312364
public readonly clusterReadEndpoint: Endpoint;
365+
public readonly connections: ec2.Connections;
313366

314367
/**
315368
* 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 {
318371
*/
319372
public readonly clusterResourceIdentifier: string;
320373

321-
/**
322-
* The connections object to implement IConectable
323-
*/
324-
public readonly connections: ec2.Connections;
325-
326374
/**
327375
* The VPC where the DB subnet group is created.
328376
*/
@@ -348,6 +396,8 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
348396
*/
349397
public readonly instanceEndpoints: Endpoint[] = [];
350398

399+
protected enableIamAuthentication?: boolean;
400+
351401
constructor(scope: Construct, id: string, props: DatabaseClusterProps) {
352402
super(scope, id);
353403

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

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

438+
this.enableIamAuthentication = props.iamAuthentication;
439+
388440
// Create the Neptune cluster
389441
const cluster = new CfnDBCluster(this, 'Resource', {
390442
// Basic
@@ -396,6 +448,7 @@ export class DatabaseCluster extends Resource implements IDatabaseCluster {
396448
dbClusterParameterGroupName: props.clusterParameterGroup?.clusterParameterGroupName,
397449
deletionProtection: deletionProtection,
398450
associatedRoles: props.associatedRoles ? props.associatedRoles.map(role => ({ roleArn: role.roleArn })) : undefined,
451+
iamAuthEnabled: Lazy.any({ produce: () => this.enableIamAuthentication }),
399452
// Backup
400453
backupRetentionPeriod: props.backupRetention?.toDays(),
401454
preferredBackupWindow: props.preferredBackupWindow,

0 commit comments

Comments
 (0)