Skip to content

Commit

Permalink
feat(rds): grantConnect method enables iam auth to rds cluster (#28118)
Browse files Browse the repository at this point in the history
This adds a `grantConnect` method to the `DatabaseCluster` which gives an IAM entity access to connect to the cluster using the specified database user.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
corymhall authored Dec 5, 2023
1 parent d19640b commit 766ff8b
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 3 deletions.
26 changes: 24 additions & 2 deletions packages/aws-cdk-lib/aws-rds/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,10 +744,10 @@ You can also authenticate to a database instance using AWS Identity and Access M
See <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html> for more information
and a list of supported versions and limitations.

**Note**: `grantConnect()` does not currently work - see [this GitHub issue](https://github.com/aws/aws-cdk/issues/11851).

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

### Instance

```ts
declare const vpc: ec2.Vpc;
const instance = new rds.DatabaseInstance(this, 'Instance', {
Expand All @@ -759,6 +759,8 @@ const role = new iam.Role(this, 'DBRole', { assumedBy: new iam.AccountPrincipal(
instance.grantConnect(role); // Grant the role connection access to the DB.
```

### Proxy

The following example shows granting connection access for RDS Proxy to an IAM role.

```ts
Expand All @@ -784,6 +786,26 @@ proxy.grantConnect(role, 'admin'); // Grant the role connection access to the DB
**Note**: In addition to the setup above, a database user will need to be created to support IAM auth.
See <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html> for setup instructions.

### Cluster

The following example shows granting connection access for an IAM role to an Aurora Cluster.

```ts
declare const vpc: ec2.Vpc;
const cluster = new rds.DatabaseCluster(this, 'Database', {
engine: rds.DatabaseClusterEngine.auroraMysql({
version: rds.AuroraMysqlEngineVersion.VER_3_03_0,
}),
writer: rds.ClusterInstance.provisioned('writer'),
vpc,
});
const role = new iam.Role(this, 'AppRole', { assumedBy: new iam.ServicePrincipal('someservice.amazonaws.com') });
cluster.grantConnect(role, 'somedbuser');
```

**Note**: In addition to the setup above, a database user will need to be created to support IAM auth.
See <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html> for setup instructions.

## Kerberos Authentication

You can also authenticate using Kerberos to a database instance using AWS Managed Microsoft AD for authentication;
Expand Down
10 changes: 10 additions & 0 deletions packages/aws-cdk-lib/aws-rds/lib/cluster-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IClusterEngine } from './cluster-engine';
import { Endpoint } from './endpoint';
import { DatabaseProxy, DatabaseProxyOptions } from './proxy';
import * as ec2 from '../../aws-ec2';
import * as iam from '../../aws-iam';
import * as secretsmanager from '../../aws-secretsmanager';
import { IResource } from '../../core';

Expand Down Expand Up @@ -53,6 +54,15 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable, secretsma
* Add a new db proxy to this cluster.
*/
addProxy(id: string, options: DatabaseProxyOptions): DatabaseProxy;

/**
* Grant the given identity connection access to the Cluster.
*
* @param grantee the Principal to grant the permissions to
* @param dbUser the name of the database user to allow connecting
*
*/
grantConnect(grantee: iam.IGrantable, dbUser: string): iam.Grant;
}

/**
Expand Down
16 changes: 15 additions & 1 deletion packages/aws-cdk-lib/aws-rds/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { CfnDBCluster, CfnDBClusterProps, CfnDBInstance } from './rds.generated'
import { ISubnetGroup, SubnetGroup } from './subnet-group';
import * as cloudwatch from '../../aws-cloudwatch';
import * as ec2 from '../../aws-ec2';
import * as iam from '../../aws-iam';
import { IRole, ManagedPolicy, Role, ServicePrincipal } from '../../aws-iam';
import * as kms from '../../aws-kms';
import * as logs from '../../aws-logs';
import * as s3 from '../../aws-s3';
import * as secretsmanager from '../../aws-secretsmanager';
import { Annotations, Duration, FeatureFlags, Lazy, RemovalPolicy, Resource, Token } from '../../core';
import { Annotations, ArnFormat, Duration, FeatureFlags, Lazy, RemovalPolicy, Resource, Stack, Token } from '../../core';
import * as cxapi from '../../cx-api';

/**
Expand Down Expand Up @@ -457,6 +458,19 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC
targetType: secretsmanager.AttachmentTargetType.RDS_DB_CLUSTER,
};
}

public grantConnect(grantee: iam.IGrantable, dbUser: string): iam.Grant {
return iam.Grant.addToPrincipal({
actions: ['rds-db:connect'],
grantee,
resourceArns: [Stack.of(this).formatArn({
service: 'rds-db',
resource: 'dbuser',
resourceName: `${this.clusterResourceIdentifier}/${dbUser}`,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
})],
});
}
}

/**
Expand Down
46 changes: 46 additions & 0 deletions packages/aws-cdk-lib/aws-rds/test/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3855,6 +3855,52 @@ describe('cluster', () => {
EngineVersion: Match.absent(),
});
});

test('grantConnect', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');
const role = new Role(stack, 'Role', {
assumedBy: new ServicePrincipal('service.amazonaws.com'),
});

// WHEN
const cluster = new DatabaseCluster(stack, 'Database', {
engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_14_3 }),
instanceProps: { vpc },
});
cluster.grantConnect(role, 'someUser');

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
Roles: [{ Ref: 'Role1ABCC5F0' }],
PolicyDocument: {
Statement: [{
Action: 'rds-db:connect',
Effect: 'Allow',
Resource: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':rds-db:us-test-1:12345:dbuser:',
{
'Fn::GetAtt': [
'DatabaseB269D8BB',
'DBClusterResourceId',
],
},
'/someUser',
],
],
},
}],
},
});
});
});

test.each([
Expand Down

0 comments on commit 766ff8b

Please sign in to comment.