From c9c7ac9a1e150fb634e00961d791ec7ea00a11fc Mon Sep 17 00:00:00 2001 From: Svenja Raether Date: Fri, 22 Mar 2024 11:29:41 +0100 Subject: [PATCH] feat: adjust usage of Statement for schema --- README.md | 162 +++++++++++++++++++++++------------ src/statement.ts | 6 +- test/policy-store.test.ts | 29 +++++++ test/policy-template.test.ts | 26 ++++++ test/schema.json | 16 ++++ test/statement.test.ts | 4 +- 6 files changed, 182 insertions(+), 61 deletions(-) create mode 100644 test/schema.json diff --git a/README.md b/README.md index 63d3c49..2ac2041 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,34 @@ # Amazon Verified Permissions L2 CDK Construct + This repo contains the implementation of an L2 CDK Construct for Amazon Verified Permissions # Project Stability + This construct is still versioned with alpha/v0 major version and we could introduce breaking changes even without a major version bump. Our goal is to keep the API stable & backwards compatible as much as possible but we currently cannot guarantee that. Once we'll publish v1.0.0 the breaking changes will be introduced via major version bumps. # Getting Started ## Policy Store -Define a Policy Store with defaults (No schema & Validation Settings Mode set to OFF) + +Define a Policy Store with defaults (No schema & Validation Settings Mode set to OFF): + ```ts -const test = new PolicyStore(scope, 'PolicyStore') +const test = new PolicyStore(scope, "PolicyStore"); ``` -Define a Policy Store without Schema definition (Validation Settings Mode must be set to OFF) +Define a Policy Store without Schema definition (Validation Settings Mode must be set to OFF): + ```ts const validationSettingsOff = { mode: ValidationSettingsMode.OFF, }; -const test = new PolicyStore(scope, 'PolicyStore', { +const test = new PolicyStore(scope, "PolicyStore", { validationSettings: validationSettingsOff, -}) +}); ``` Define a Policy Store with Schema definition (a STRICT Validation Settings Mode is strongly suggested for Policy Stores with schemas): + ```ts const validationSettingsStrict = { mode: ValidationSettingsMode.STRICT, @@ -36,8 +42,8 @@ const cedarJsonSchema = { actions: { viewPhoto: { appliesTo: { - principalTypes: ['User'], - resourceTypes: ['Photo'], + principalTypes: ["User"], + resourceTypes: ["Photo"], }, }, }, @@ -46,26 +52,44 @@ const cedarJsonSchema = { const cedarSchema = { cedarJson: JSON.stringify(cedarJsonSchema), }; -const policyStore = new PolicyStore(scope, 'PolicyStore', { +const policyStore = new PolicyStore(scope, "PolicyStore", { + schema: cedarSchema, + validationSettings: validationSettingsStrict, +}); +``` + +Define a Policy Store with Schema definition from file: + +```ts +const validationSettingsStrict = { + mode: ValidationSettingsMode.STRICT, +}; +const cedarSchema = { + cedarJson: Statement.fromFile("assets/policy-store-schema.json"), +}; +const policyStore = new PolicyStore(scope, "PolicyStore", { schema: cedarSchema, validationSettings: validationSettingsStrict, }); ``` ## Identity Source -Define Identity Source with required properties + +Define Identity Source with required properties: + ```ts -const userPool = new UserPool(scope, 'UserPool'); // Creating a new Cognito UserPool -new IdentitySource(scope, 'IdentitySource', { - configuration: { - cognitoUserPoolConfiguration: { - userPool: userPool, - }, - } +const userPool = new UserPool(scope, "UserPool"); // Creating a new Cognito UserPool +new IdentitySource(scope, "IdentitySource", { + configuration: { + cognitoUserPoolConfiguration: { + userPool: userPool, + }, + }, }); ``` -Define Identity Source with all the properties +Define Identity Source with all the properties: + ```ts const validationSettingsStrict = { mode: ValidationSettingsMode.STRICT, @@ -79,8 +103,8 @@ const cedarJsonSchema = { actions: { viewPhoto: { appliesTo: { - principalTypes: ['User'], - resourceTypes: ['Photo'], + principalTypes: ["User"], + resourceTypes: ["Photo"], }, }, }, @@ -89,27 +113,27 @@ const cedarJsonSchema = { const cedarSchema = { cedarJson: JSON.stringify(cedarJsonSchema), }; -const policyStore = new PolicyStore(scope, 'PolicyStore', { +const policyStore = new PolicyStore(scope, "PolicyStore", { schema: cedarSchema, validationSettings: validationSettingsStrict, }); -const userPool = new UserPool(scope, 'UserPool'); // Creating a new Cognito UserPool -new IdentitySource(scope, 'IdentitySource', { - configuration: { - cognitoUserPoolConfiguration: { - clientIds: [ - '&ExampleCogClientId;', - ], - userPool: userPool, - }, +const userPool = new UserPool(scope, "UserPool"); // Creating a new Cognito UserPool +new IdentitySource(scope, "IdentitySource", { + configuration: { + cognitoUserPoolConfiguration: { + clientIds: ["&ExampleCogClientId;"], + userPool: userPool, }, - policyStore: policyStore, - principalEntityType: 'PETEXAMPLEabcdefg111111', + }, + policyStore: policyStore, + principalEntityType: "PETEXAMPLEabcdefg111111", }); ``` ## Policy -Define a Policy and add it to a specific Policy Store + +Define a Policy and add it to a specific Policy Store: + ```ts const statement = `permit( principal, @@ -119,32 +143,33 @@ const statement = `permit( true };`; -const description = 'Test policy assigned to the test store'; +const description = "Test policy assigned to the test store"; const validationSettingsOff = { mode: ValidationSettingsMode.OFF, }; -const policyStore = new PolicyStore(scope, 'PolicyStore', { - validationSettings: validationSettingsOff, +const policyStore = new PolicyStore(scope, "PolicyStore", { + validationSettings: validationSettingsOff, }); // Create a policy and add it to the policy store -const policy = new Policy(scope, 'MyTestPolicy', { - definition: { +const policy = new Policy(scope, "MyTestPolicy", { + definition: { static: { - statement, - description, + statement, + description, }, - }, - policyStore: policyStore, + }, + policyStore: policyStore, }); ``` -Define a policy with a template linked definition +Define a policy with a template linked definition: + ```ts const validationSettingsOff = { mode: ValidationSettingsMode.OFF, }; -const policyStore = new PolicyStore(scope, 'PolicyStore', { +const policyStore = new PolicyStore(scope, "PolicyStore", { validationSettings: validationSettingsOff, }); const policyTemplateStatement = ` @@ -153,47 +178,72 @@ permit ( action in [TinyTodo::Action::"ReadList", TinyTodo::Action::"ListTasks"], resource == ?resource );`; -const template = new PolicyTemplate(scope, 'PolicyTemplate', { +const template = new PolicyTemplate(scope, "PolicyTemplate", { statement: policyTemplateStatement, policyStore: policyStore, }); -const policy = new Policy(scope, 'MyTestPolicy', { +const policy = new Policy(scope, "MyTestPolicy", { definition: { templateLinked: { policyTemplate: template, principal: { - entityId: 'exampleId', - entityType: 'exampleType', + entityId: "exampleId", + entityType: "exampleType", }, resource: { - entityId: 'exampleId', - entityType: 'exampleType', + entityId: "exampleId", + entityType: "exampleType", }, }, }, policyStore: policyStore, }); +``` +Define a Policy with a statement from file: + +```ts +const description = "Test policy assigned to the test store"; +const validationSettingsOff = { + mode: ValidationSettingsMode.OFF, +}; +const policyStore = new PolicyStore(scope, "PolicyStore", { + validationSettings: validationSettingsOff, +}); + +// Create a policy and add it to the policy store +const policy = new Policy(scope, "MyTestPolicy", { + definition: { + static: { + statement: Statement.fromFile("assets/policy-statement.cedar"), + description, + }, + }, + policyStore: policyStore, +}); ``` ## Policy Template -Define a Policy Template referring to a Cedar Statement in local file + +Define a Policy Template referring to a Cedar Statement in local file: + ```ts const validationSettingsOff = { mode: ValidationSettingsMode.OFF, }; -const policyStore = new PolicyStore(scope, 'PolicyStore', { +const policyStore = new PolicyStore(scope, "PolicyStore", { validationSettings: validationSettingsOff, }); -new PolicyTemplate(scope, 'PolicyTemplate', { - description: 'Allows sharing photos in full access mode', +new PolicyTemplate(scope, "PolicyTemplate", { + description: "Allows sharing photos in full access mode", policyStore: policyStore, - statement: Statement.fromFile('assets/template-statement.cedar'), + statement: Statement.fromFile("assets/template-statement.cedar"), }); ``` # Notes -* This project is following the AWS CDK Official Design Guidelines (see https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) and the AWS CDK New Constructs Creation Guide (see here https://github.com/aws/aws-cdk/blob/main/docs/NEW_CONSTRUCTS_GUIDE.md). -* Feedback is a gift: if you find something wrong or you've ideas to improve please open an issue or a pull request +- This project is following the AWS CDK Official Design Guidelines (see https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) and the AWS CDK New Constructs Creation Guide (see here https://github.com/aws/aws-cdk/blob/main/docs/NEW_CONSTRUCTS_GUIDE.md). + +- Feedback is a gift: if you find something wrong or you've ideas to improve please open an issue or a pull request diff --git a/src/statement.ts b/src/statement.ts index ce1eca5..11587a2 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'fs'; /* - * Represents the Policy Statement. + * Represents a Cedar Statement (Schema or Policy). */ export class Statement { /** @@ -10,7 +10,7 @@ export class Statement { */ public static fromInline(statement: string): string { if (statement.length === 0) { - throw new Error('Policies inline statement cannot be empty'); + throw new Error('Statement cannot be empty'); } return statement; } @@ -22,7 +22,7 @@ export class Statement { */ public static fromFile(path: string): string { if (path.length === 0) { - throw new Error('Policy path cannot be empty'); + throw new Error('Path cannot be empty'); } return readFileSync(path, 'utf-8'); } diff --git a/test/policy-store.test.ts b/test/policy-store.test.ts index 276dfdc..6827b9f 100644 --- a/test/policy-store.test.ts +++ b/test/policy-store.test.ts @@ -1,3 +1,4 @@ +import { readFileSync } from 'fs'; import { ArnFormat, Aws, Stack } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import * as iam from 'aws-cdk-lib/aws-iam'; @@ -102,6 +103,34 @@ describe('Policy Store creation', () => { }, ); }); + + test('Creating Policy Store with validation settings and schema (mode = STRICT) from file', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.STRICT, + }, + schema: { + cedarJson: Statement.fromFile('test/schema.json'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties( + 'AWS::VerifiedPermissions::PolicyStore', + { + ValidationSettings: { + Mode: ValidationSettingsMode.STRICT, + }, + Schema: { + CedarJson: readFileSync('test/schema.json', 'utf-8'), + }, + }, + ); + }); }); describe('Policy Store grant to IGrantable', () => { diff --git a/test/policy-template.test.ts b/test/policy-template.test.ts index 0f0d5ac..fc79986 100644 --- a/test/policy-template.test.ts +++ b/test/policy-template.test.ts @@ -1,3 +1,4 @@ +import { readFileSync } from 'fs'; import { Stack } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import { CfnPolicyStore } from 'aws-cdk-lib/aws-verifiedpermissions'; @@ -87,4 +88,29 @@ describe('Policy template reference existing policy template', () => { // THEN expect(policyTemplate.policyTemplateId).toBe(policyTemplateId); }); + + test('Policy Template with Statement from file', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + + // WHEN + new PolicyTemplate(stack, 'PolicyTemplate', { + statement: Statement.fromFile('test/statement.cedar'), + policyStore, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties( + 'AWS::VerifiedPermissions::PolicyTemplate', + { + Statement: readFileSync('test/statement.cedar', 'utf-8'), + }, + ); + }); }); diff --git a/test/schema.json b/test/schema.json new file mode 100644 index 0000000..5a20577 --- /dev/null +++ b/test/schema.json @@ -0,0 +1,16 @@ +{ + "PhotoApp": { + "entityTypes": { + "User": {}, + "Photo": {} + }, + "actions": { + "viewPhoto": { + "appliesTo": { + "principalTypes": ["User"], + "resourceTypes": ["Photo"] + } + } + } + } + } \ No newline at end of file diff --git a/test/statement.test.ts b/test/statement.test.ts index c9ff572..c71d90b 100644 --- a/test/statement.test.ts +++ b/test/statement.test.ts @@ -30,12 +30,12 @@ describe('Statement creation errors', () => { // THEN expect(() => { Statement.fromInline(''); - }).toThrow('Policies inline statement cannot be empty'); + }).toThrow('Statement cannot be empty'); }); test('Creating a statement from and empty file path', () => { expect(() => { Statement.fromFile(''); - }).toThrow('Policy path cannot be empty'); + }).toThrow('Path cannot be empty'); }); });