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(rds): grantConnect method enables iam auth to rds cluster #28118

Merged
merged 4 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 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
@@ -1,4 +1,5 @@
import { IClusterEngine } from './cluster-engine';
import * as iam from '../../aws-iam';
import { Endpoint } from './endpoint';
import { DatabaseProxy, DatabaseProxyOptions } from './proxy';
import * as ec2 from '../../aws-ec2';
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
Loading