From ea8cb403755f75eb249c714f56384807ba7fd603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Manuel=20M=C3=A9ndez=20Araguacaima?= Date: Wed, 7 Aug 2024 16:15:24 -0400 Subject: [PATCH] feat: Adding support for cloudfront user principal --- .gitignore | 3 ++ package-lock.json | 4 +-- src/index.ts | 1 + src/principals/cloudfront.ts | 23 +++++++++++++ src/principals/deserialiser.ts | 11 ++++-- tests/principals/deserialiser.spec.ts | 48 ++++++++++++++++++++++++--- 6 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/principals/cloudfront.ts diff --git a/.gitignore b/.gitignore index 0f40d34..1422c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ #Visual Studio Code settings /.vscode/ + +#Jetbrains settings +.idea diff --git a/package-lock.json b/package-lock.json index 3c9c926..3149af1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@thinkinglabs/aws-iam-policy", - "version": "2.6.0", + "version": "2.6.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@thinkinglabs/aws-iam-policy", - "version": "2.6.0", + "version": "2.6.1", "license": "MIT", "devDependencies": { "@types/chai": "^4.2.15", diff --git a/src/index.ts b/src/index.ts index 66628ef..5cf13da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,4 +8,5 @@ export * from './principals/account'; export * from './principals/root-account'; export * from './principals/federated'; export * from './principals/wildcard'; +export * from './principals/cloudfront'; export * from './condition/condition'; diff --git a/src/principals/cloudfront.ts b/src/principals/cloudfront.ts new file mode 100644 index 0000000..74d2380 --- /dev/null +++ b/src/principals/cloudfront.ts @@ -0,0 +1,23 @@ +import {AbstractBasePrincipal} from './base'; + +class CloudFrontPrincipal extends AbstractBasePrincipal { + private arn: string; + + constructor(arn: string) { + super(); + this.arn = arn; + } + + static validate(input: string): string | undefined { + const regex = new RegExp( + '^arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]{6,14}$'); + const result = regex.exec(input) as RegExpExecArray; + return result ? result[0] : undefined; + } + + toJSON() { + return {AWS: this.arn}; + } +} + +export {CloudFrontPrincipal}; diff --git a/src/principals/deserialiser.ts b/src/principals/deserialiser.ts index 8708625..ce296b7 100644 --- a/src/principals/deserialiser.ts +++ b/src/principals/deserialiser.ts @@ -6,9 +6,10 @@ import {AccountPrincipal} from './account'; import {ServicePrincipal} from './service'; import {FederatedPrincipal} from './federated'; import {parseArray} from '../arrays'; +import {CloudFrontPrincipal} from './cloudfront'; class PrincipalJSONDeserialiser { - static fromJSON(input: {[key:string]: PrincipalValues} | AnonymousValue | undefined): Principal[] { + static fromJSON(input: { [key: string]: PrincipalValues } | AnonymousValue | undefined): Principal[] { if (input === undefined) { return []; } @@ -19,7 +20,7 @@ class PrincipalJSONDeserialiser { const result: Principal[] = []; - const principals = input as {[key:string]: string[] | string}; + const principals = input as { [key: string]: string[] | string }; const principalTypes = Object.keys(principals); principalTypes.forEach((principalType) => { const principalValues = parseArray(principals[principalType]); @@ -52,11 +53,17 @@ class PrincipalJSONDeserialiser { if (validation) { return new AnonymousUserPrincipal(); } + validation = CloudFrontPrincipal.validate(value); + if (validation) { + return new CloudFrontPrincipal(value); + } throw new Error(`Unsupported AWS principal value "${value}"`); } + function parseService(value: string) { return new ServicePrincipal(value); } + function parseFederated(value: string) { return new FederatedPrincipal(value); } diff --git a/tests/principals/deserialiser.spec.ts b/tests/principals/deserialiser.spec.ts index 271815a..04f13c9 100644 --- a/tests/principals/deserialiser.spec.ts +++ b/tests/principals/deserialiser.spec.ts @@ -1,13 +1,14 @@ import {expect} from 'chai'; import {PrincipalJSONDeserialiser} from '../../src/principals/deserialiser'; import { - ArnPrincipal, - ServicePrincipal, - RootAccountPrincipal, AccountPrincipal, AnonymousUserPrincipal, - WildcardPrincipal, + ArnPrincipal, + CloudFrontPrincipal, FederatedPrincipal, + RootAccountPrincipal, + ServicePrincipal, + WildcardPrincipal, } from '../../src'; describe('#PrincipalJSONDeserialise', function() { @@ -76,6 +77,45 @@ describe('#PrincipalJSONDeserialise', function() { }); }); + describe('with an CloudFront user', function() { + it('should return a CloudFrontPrincipal', function() { + const validCloudFrontIds = [ + 'E1ABCDEFGHIJ', + 'E12345ABCDE', + 'EABCD1234567', + 'E1A2B3C4D5E6F', + 'E12345678ABCDE', + ]; + for (const validCloudFrontId of validCloudFrontIds) { + const arn = `arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${validCloudFrontId}`; + const input = {AWS: arn}; + expect(PrincipalJSONDeserialiser.fromJSON(input)).to.deep.equal([new CloudFrontPrincipal(arn)]); + } + }); + it('should fail with invalid cloud front ids', function() { + const invalidCloudFrontIds = [ + 'EABCDEFGHIJKL', + 'E123456789', + 'EABCDEFGHIJKLMNO', + '1EABC1234567', + 'EFGHJKLMNOP', + ]; + let arn: string | undefined; + for (const invalidCloudFrontId of invalidCloudFrontIds) { + arn = `arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${invalidCloudFrontId}`; + const input = {AWS: arn}; + try { + PrincipalJSONDeserialiser.fromJSON(input); + } catch (error) { + const error_ = error as Error; + if (error_.message !== `Unsupported AWS principal value "${arn}"`) { + throw error; + } + } + } + }); + }); + describe('with an unsupported ARN', function() { it('should throw an error', function() { expect(() => PrincipalJSONDeserialiser.fromJSON({AWS: ['anArn']})).to.throw(Error)