Skip to content

Conversation

@skuenzli
Copy link
Contributor

@skuenzli skuenzli commented Dec 10, 2024

Add support for generating k9-style least privilege resource policies for DynamoDB resources.

const ddbResourcePolicyProps: k9.dynamodb.K9DynamoDBResourcePolicyProps = {
    k9DesiredAccess: new Array<k9.k9policy.IAccessSpec>(
        {
        ... desired access capabilities ...
        note: you should explicitly-allow your AWS Access Analyzer service-linked role to read-config
              so that you don't get an Error finding in AWS AA because it can't read the table metadata & policy
        });
const ddbResourcePolicy = k9.dynamodb.grantAccessViaResourcePolicy(ddbResourcePolicyProps);

const table = new dynamodb.TableV2(stack, 'k9-cdk-v2-int-test', {
  partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  resourcePolicy: ddbResourcePolicy
});

@skuenzli skuenzli changed the title (feat) Generate ddb resource policies feat: Generate ddb resource policies Dec 10, 2024
@skuenzli skuenzli changed the title feat: Generate ddb resource policies feat: Generate DynamoDB resource policies Dec 10, 2024
…ourcePolicyResult like S3

* Return AddToResourcePolicyResult[] instead of a (parallel, disconnected) PolicyDocument
* Expand assertions for typical use of DynamoDB generator
…cy and providing on construction

This is what actually works.

It's not clear to me:
1. why the TableV2 resourcePolicy property is not populated from constructor within a unit test
2. why adding resource policy statements works in unit tests but not at runtime
Better communicates that the function does not have side effects.

Aligns to name for side-effect free KMS policy generator.
"DynamoDB": {
"administer-resource": [
"dynamodb:CreateBackup",
"dynamodb:CreateGlobalTable",

Choose a reason for hiding this comment

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

These permissions for global tables very likely need to stay. The admin user should be allowed to make replicas.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These permissions changes were the result of:

  1. updating to the full current list of DDB permissions
  2. removing permissions not supported by DDB resource policy

Here are the administer-resource permissions from the full set of DDB perms that the DDB resource policy API rejected:

(administer-resource) One or more parameter values were invalid: Invalid policy document: The following action names are invalid: "dynamodb:UpdateGlobalTable", "dynamodb:UpdateGlobalTableSettings", "dynamodb:CreateTable", "dynamodb:UpdateGlobalTableVersion", "dynamodb:CreateGlobalTable", "dynamodb:RestoreTableFromBackup", "dynamodb:RestoreTableFromAwsBackup", "dynamodb:CreateTableReplica", "dynamodb:PurchaseReservedCapacityOfferings", "dynamodb:ImportTable"

(I have similar output for read-config, read-data, and write-data if you are interested)

I can see why you logically you want an admin of a table to create a replica. However, I think that probably has to be granted from the Identity side. That aligns more with how Create[Resource] is generally not supported in resource policy. And in this specific case, I would think the principal CFn uses to create the table resources would be the one that needs permissions.

wdyt?

* @param props specifying desired access
* @return a PolicyDocument that can be attached to DynamoDB resources
*/
export function makeResourcePolicy(props: K9DynamoDBResourcePolicyProps): PolicyDocument {

Choose a reason for hiding this comment

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

This works for our needs, but I was wondering if you still wanted to wrap this in a grantAccessViaResourcePolicy method to be consistent with s3.

}

if (!canPrincipalsManageResources(accessSpecsByCapability)) {
throw Error('At least one principal must be able to administer and read-config for DynamoDB resources' +

Choose a reason for hiding this comment

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

Good check to have.

}


export function toPascalCase(input: string): string {

Choose a reason for hiding this comment

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

Did you hit a different naming restriction here that forced the casing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. DDB doesn't allow spaces nor hyphens.


});

describe('DynamoDBResourcePolicy', () => {

Choose a reason for hiding this comment

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

nit: break the tests into files to match src.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, might do that.

src/k9policy.ts Outdated
* @return true when at least one principal that can administer and read configuration exists
*/
export function canPrincipalsManageResources(accessSpecsByCapability: Map<AccessCapability, IAccessSpec>) {
console.log(`canPrincipalsManageResources eval'ing ${accessSpecsByCapability}`);

Choose a reason for hiding this comment

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

Console logging in a CDK library is a bit unusual. I'd advise debug here.

I forgot to mention that a while back. I swallow your console.log's from the KMS key policy generation to hide them. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, that's not supposed to be there. will remove.

&& (readConfigSpec?.allowPrincipalArns && readConfigSpec.allowPrincipalArns.length > 0)) {
const adminPrincipals = new Set<string>(adminSpec.allowPrincipalArns);
const readConfigPrincipals = new Set<string>(readConfigSpec.allowPrincipalArns);
const intersection = new Set(

Choose a reason for hiding this comment

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

Set has a native intersection method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, I don't see an intersection method available on TypeScript 5.4.5 & Node 20.14.0 (what I dev on).

Looks like an upgrade might help here. However, it's tough when CDK supports all the way back to Node 14.15.0 and I know we have users on 16.x

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`DynamoDBResourcePolicy Typical usage 1`] = `
Object {

Choose a reason for hiding this comment

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

Jest 28 or 29 finally improved snapshot syntax and removed the type literals everywhere. Much easier on the eyes.

@skuenzli
Copy link
Contributor Author

For future reference...

The DynamoDB permissions changes were the result of:

  1. updating to the full current list of DDB permissions c.f. the k9 access capabilities API
  2. removing permissions not supported by DDB resource policy

Here are from full set of DDB perms that the DDB resource policy API rejected:

One or more parameter values were invalid: Invalid policy document: The following action names are invalid:

(administer-resource): "dynamodb:UpdateGlobalTable", "dynamodb:UpdateGlobalTableSettings", "dynamodb:CreateTable", "dynamodb:UpdateGlobalTableVersion", "dynamodb:CreateGlobalTable", "dynamodb:RestoreTableFromBackup", "dynamodb:RestoreTableFromAwsBackup", "dynamodb:CreateTableReplica", "dynamodb:PurchaseReservedCapacityOfferings", "dynamodb:ImportTable"

(read-config): "dynamodb:ListContributorInsights", "dynamodb:ListGlobalTables", "dynamodb:DescribeGlobalTableSettings", "dynamodb:DescribeReservedCapacityOfferings", "dynamodb:ListBackups", "dynamodb:DescribeLimits", "dynamodb:ListExports", "dynamodb:DescribeEndpoints", "dynamodb:DescribeReservedCapacity", "dynamodb:ListImports", "dynamodb:ListTables", "dynamodb:DescribeStream", "dynamodb:DescribeImport", "dynamodb:DescribeGlobalTable", "dynamodb:DescribeBackup", "dynamodb:ListStreams"

(read-data): "dynamodb:GetRecords", "dynamodb:GetShardIterator"

(write-data): "dynamodb:RestoreTableFromAwsBackup", "dynamodb:StartAwsBackupJob", "dynamodb:CreateTableReplica", "dynamodb:ImportTable"

(delete-data): "dynamodb:DeleteBackup"

@skuenzli skuenzli merged commit e356b7f into v2-main Dec 12, 2024
3 of 4 checks passed
@skuenzli skuenzli deleted the feat-generate-ddb-resource-policies branch December 30, 2024 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants