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(neptune): Support IAM authentication #13462

Merged
merged 6 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-docdb/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-neptune/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice detail!

});
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
Expand Down
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';
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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],
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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.
*/
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
Loading