diff --git a/packages/@aws-cdk/aws-neptune/README.md b/packages/@aws-cdk/aws-neptune/README.md index 0f2957bc72254..bb1c45dbcfd55 100644 --- a/packages/@aws-cdk/aws-neptune/README.md +++ b/packages/@aws-cdk/aws-neptune/README.md @@ -70,10 +70,12 @@ The following example shows enabling IAM authentication for a database cluster a const cluster = new neptune.DatabaseCluster(this, 'Cluster', { vpc, instanceType: neptune.InstanceType.R5_LARGE, - iamAuthentication: true, // Optional - will be automatically set if you call grantConnect(). + iamAuthentication: true, // Optional - will be automatically set if you call grantConnect() or grant(). }); const role = new iam.Role(this, 'DBRole', { assumedBy: new iam.AccountPrincipal(this.account) }); -cluster.grantConnect(role); // Grant the role neptune-db:* access to the DB. +// Use one of the following statements to grant the role the necessary permissions +cluster.grantConnect(role); // Grant the role neptune-db:* access to the DB +cluster.grant(role, 'neptune-db:ReadDataViaQuery', 'neptune-db:WriteDataViaQuery'); // Grant the role the specified actions to the DB ``` ## Customizing parameters diff --git a/packages/@aws-cdk/aws-neptune/lib/cluster.ts b/packages/@aws-cdk/aws-neptune/lib/cluster.ts index 2d0ca1332f892..f30bf46851f0f 100644 --- a/packages/@aws-cdk/aws-neptune/lib/cluster.ts +++ b/packages/@aws-cdk/aws-neptune/lib/cluster.ts @@ -271,6 +271,15 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable { */ readonly clusterReadEndpoint: Endpoint; + /** + * Grant the given identity the specified actions + * @param grantee the identity to be granted the actions + * @param actions the data-access actions + * + * @see https://docs.aws.amazon.com/neptune/latest/userguide/iam-dp-actions.html + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + /** * Grant the given identity connection access to the database. */ @@ -364,15 +373,15 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC protected abstract enableIamAuthentication?: boolean; - public grantConnect(grantee: iam.IGrantable): iam.Grant { + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { if (this.enableIamAuthentication === false) { - throw new Error('Cannot grant connect when IAM authentication is disabled'); + throw new Error('Cannot grant permissions when IAM authentication is disabled'); } this.enableIamAuthentication = true; return iam.Grant.addToPrincipal({ grantee, - actions: ['neptune-db:*'], + actions, resourceArns: [ [ 'arn', @@ -385,6 +394,10 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC ], }); } + + public grantConnect(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, 'neptune-db:*'); + } } /** diff --git a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.assets.json b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.assets.json index 3ca21b0b70510..f4fc567828962 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.assets.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "06bc77521a70e494cf9fb7d601f5111e19745b0ecde4b6ac42b311f1a19f8328": { + "86dda049435a7e62de07d7e302f55c3c286433c9f4736de7c9bee4336473b1c7": { "source": { "path": "aws-cdk-neptune-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "06bc77521a70e494cf9fb7d601f5111e19745b0ecde4b6ac42b311f1a19f8328.json", + "objectKey": "86dda049435a7e62de07d7e302f55c3c286433c9f4736de7c9bee4336473b1c7.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.template.json b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.template.json index 43aa778486b0b..4bbd5e768c6ba 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.template.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/aws-cdk-neptune-integ.template.json @@ -426,6 +426,74 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "Role1ABCC5F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "sagemaker.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Description": "AWS Sagemaker notebooks role example for interacting with Neptune Database Cluster" + } + }, + "RoleDefaultPolicy5FFB7DAB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "neptune-db:GetEngineStatus", + "neptune-db:ReadDataViaQuery" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":neptune-db:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "ClusterResourceId" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "RoleDefaultPolicy5FFB7DAB", + "Roles": [ + { + "Ref": "Role1ABCC5F0" + } + ] + } + }, "ParamsA8366201": { "Type": "AWS::Neptune::DBClusterParameterGroup", "Properties": { @@ -503,6 +571,7 @@ "Ref": "DatabaseSubnets3C9252C9" }, "EngineVersion": "1.2.0.0", + "IamAuthEnabled": true, "KmsKeyId": { "Fn::GetAtt": [ "DbSecurity381C2C15", diff --git a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/manifest.json index 5f4e106168dcc..e06029652094d 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/manifest.json @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/06bc77521a70e494cf9fb7d601f5111e19745b0ecde4b6ac42b311f1a19f8328.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/86dda049435a7e62de07d7e302f55c3c286433c9f4736de7c9bee4336473b1c7.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -183,6 +183,18 @@ "data": "DbSecurity381C2C15" } ], + "/aws-cdk-neptune-integ/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Role1ABCC5F0" + } + ], + "/aws-cdk-neptune-integ/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RoleDefaultPolicy5FFB7DAB" + } + ], "/aws-cdk-neptune-integ/Params/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/tree.json b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/tree.json index 6e3578029b3f3..9fe2fa8dea40d 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-neptune/test/cluster-ev12.integ.snapshot/tree.json @@ -710,6 +710,110 @@ "version": "0.0.0" } }, + "Role": { + "id": "Role", + "path": "aws-cdk-neptune-integ/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-neptune-integ/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "sagemaker.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "description": "AWS Sagemaker notebooks role example for interacting with Neptune Database Cluster" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "aws-cdk-neptune-integ/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-neptune-integ/Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "neptune-db:GetEngineStatus", + "neptune-db:ReadDataViaQuery" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":neptune-db:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "ClusterResourceId" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "RoleDefaultPolicy5FFB7DAB", + "roles": [ + { + "Ref": "Role1ABCC5F0" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, "Params": { "id": "Params", "path": "aws-cdk-neptune-integ/Params", @@ -856,6 +960,7 @@ "Ref": "DatabaseSubnets3C9252C9" }, "engineVersion": "1.2.0.0", + "iamAuthEnabled": true, "kmsKeyId": { "Fn::GetAtt": [ "DbSecurity381C2C15", diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts index 723a4b2fdfe41..e92386cfefcb0 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts @@ -473,7 +473,7 @@ describe('DatabaseCluster', () => { }); }); - test('createGrant - enables IAM auth and grants neptune-db:* to the specified grantee', () => { + test('grantConnect - enables IAM auth and grants neptune-db:* to the grantee', () => { // GIVEN const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -528,7 +528,7 @@ describe('DatabaseCluster', () => { }); }); - test('createGrant - throws if IAM auth disabled', () => { + test('grantConnect - throws if IAM auth disabled', () => { // GIVEN const stack = testStack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -544,7 +544,81 @@ describe('DatabaseCluster', () => { }); // THEN - expect(() => { cluster.grantConnect(role); }).toThrow(/Cannot grant connect when IAM authentication is disabled/); + expect(() => { cluster.grantConnect(role); }).toThrow(/Cannot grant permissions when IAM authentication is disabled/); + }); + + test('grant - enables IAM auth and grants specified actions to the grantee', () => { + // 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.grant(role, 'neptune-db:ReadDataViaQuery', 'neptune-db:WriteDataViaQuery'); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Neptune::DBCluster', { + IamAuthEnabled: true, + }); + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [{ + Effect: 'Allow', + Action: ['neptune-db:ReadDataViaQuery', 'neptune-db:WriteDataViaQuery'], + Resource: { + 'Fn::Join': [ + '', [ + 'arn:', { + Ref: 'AWS::Partition', + }, + ':neptune-db:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':', + { + 'Fn::GetAtt': [ + 'ClusterEB0386A7', + 'ClusterResourceId', + ], + }, + '/*', + ], + ], + }, + }], + Version: '2012-10-17', + }, + }); + }); + + test('grant - 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.grant(role, 'neptune-db:ReadDataViaQuery', 'neptune-db:WriteDataViaQuery'); }).toThrow(/Cannot grant permissions when IAM authentication is disabled/); }); test('autoMinorVersionUpgrade is enabled when configured', () => { diff --git a/packages/@aws-cdk/aws-neptune/test/integ.cluster-ev12.ts b/packages/@aws-cdk/aws-neptune/test/integ.cluster-ev12.ts index 4f55f06267fc5..f23606920a7f6 100644 --- a/packages/@aws-cdk/aws-neptune/test/integ.cluster-ev12.ts +++ b/packages/@aws-cdk/aws-neptune/test/integ.cluster-ev12.ts @@ -1,4 +1,5 @@ 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 * as cdk from '@aws-cdk/core'; import * as integ from '@aws-cdk/integ-tests'; @@ -22,6 +23,11 @@ const kmsKey = new kms.Key(stack, 'DbSecurity', { removalPolicy: cdk.RemovalPolicy.DESTROY, }); +const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'), + description: 'AWS Sagemaker notebooks role example for interacting with Neptune Database Cluster', +}); + const clusterParameterGroup = new ClusterParameterGroup(stack, 'Params', { description: 'A nice parameter group', family: ParameterGroupFamily.NEPTUNE_1_2, @@ -44,6 +50,8 @@ const cluster = new DatabaseCluster(stack, 'Database', { cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); +cluster.grant(role, 'neptune-db:ReadDataViaQuery', 'neptune-db:GetEngineStatus'); + new integ.IntegTest(app, 'ClusterTest', { testCases: [stack], });