├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── acm-pca │ └── index.ts ├── apigateway │ └── index.ts ├── backup │ └── index.ts ├── cloudformation │ └── index.ts ├── codeartifact │ └── index.ts ├── codebuild │ └── index.ts ├── core │ └── index.ts ├── ec2 │ └── index.ts ├── ecr │ └── index.ts ├── efs │ └── index.ts ├── events │ └── index.ts ├── glacier │ └── index.ts ├── glue │ └── index.ts ├── iam │ └── index.ts ├── index.ts ├── iot │ └── index.ts ├── kms │ └── index.ts ├── lambda │ └── index.ts ├── logs │ └── index.ts ├── mediastore │ └── index.ts ├── opensearch │ └── index.ts ├── ram │ └── index.ts ├── s3 │ └── index.ts ├── schemas │ └── index.ts ├── secretsmanager │ └── index.ts ├── serverless │ └── index.ts ├── sns │ └── index.ts └── sqs │ └── index.ts ├── tsconfig.json └── yarn.lock /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | env: 12 | CI: 'true' 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: '14.x' 19 | - name: Install 20 | run: yarn install 21 | - name: Build 22 | run: yarn build 23 | - name: Set git identity 24 | run: |- 25 | git config user.name "github-actions" 26 | git config user.email "github-actions@github.com" 27 | - name: Create release 28 | run: yarn release 29 | - name: Push release 30 | run: git push --follow-tags 31 | - uses: actions/upload-artifact@v3 32 | with: 33 | name: build-artifact 34 | path: | 35 | * 36 | !node_modules 37 | release_npm: 38 | needs: release 39 | runs-on: ubuntu-latest 40 | env: 41 | CI: 'true' 42 | steps: 43 | - uses: actions/download-artifact@v3 44 | with: 45 | name: build-artifact 46 | - run: ls -la 47 | - uses: actions/setup-node@v3 48 | with: 49 | node-version: '14.x' 50 | - run: yarn publish 51 | env: 52 | NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "trailingComma": "all", 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 1.4.3 (2022-09-17) 6 | 7 | ### 1.4.2 (2022-09-02) 8 | 9 | ### 1.4.1 (2022-08-30) 10 | 11 | ## 1.4.0 (2022-08-30) 12 | 13 | 14 | ### Features 15 | 16 | * added logs and now sleeping some API calls ([ca75d14](https://github.com/willdady/aws-resource-based-policy-collector/commit/ca75d14f62f0ab552c9682255edd91e84fc40230)) 17 | 18 | ### 1.3.3 (2022-08-29) 19 | 20 | ### 1.3.2 (2022-08-29) 21 | 22 | ### 1.3.1 (2022-08-29) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * ignore deleted stacks ([e17c022](https://github.com/willdady/aws-resource-based-policy-collector/commit/e17c022c9dc7da2fba2fc0c9e150220abd1cffc9)) 28 | 29 | ## 1.3.0 (2022-08-26) 30 | 31 | 32 | ### Features 33 | 34 | * added AcmPcaPolicyCollector ([bffa79e](https://github.com/willdady/aws-resource-based-policy-collector/commit/bffa79ea6dc78ad30c8de994e4f35d88a44c8331)) 35 | 36 | ### 1.2.1 (2022-08-26) 37 | 38 | ## 1.2.0 (2022-08-25) 39 | 40 | 41 | ### Features 42 | 43 | * added CloudformationPolicyCollector ([6e0c016](https://github.com/willdady/aws-resource-based-policy-collector/commit/6e0c0161b3d5510379519b2323777421421bd664)) 44 | 45 | ## 1.1.0 (2022-08-25) 46 | 47 | 48 | ### Features 49 | 50 | * added RamPolicyCollector ([e1da0fd](https://github.com/willdady/aws-resource-based-policy-collector/commit/e1da0fdd4022c876ab2a052916f57c93f97d6f18)) 51 | 52 | ### 1.0.16 (2022-08-25) 53 | 54 | ### 1.0.15 (2022-08-25) 55 | 56 | ### 1.0.14 (2022-08-23) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * handle PolicyNotFound in EFS service ([be682f4](https://github.com/willdady/aws-resource-based-policy-collector/commit/be682f486273d32e8c17444446febef31d5a9ec3)) 62 | 63 | ### 1.0.13 (2022-08-22) 64 | 65 | ### 1.0.12 (2022-08-17) 66 | 67 | ### 1.0.11 (2022-07-15) 68 | 69 | ### 1.0.10 (2022-07-14) 70 | 71 | ### 1.0.9 (2022-07-14) 72 | 73 | ### 1.0.8 (2022-07-13) 74 | 75 | ### 1.0.7 (2022-04-11) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * handle not-found policies for EventBridge schema registries ([7120a65](https://github.com/willdady/aws-resource-based-policy-collector/commit/7120a6529230bdea9c3f9fc052ace1486ff72ca1)) 81 | 82 | ### 1.0.6 (2022-04-11) 83 | 84 | ### 1.0.5 (2022-04-11) 85 | 86 | 87 | ### Bug Fixes 88 | 89 | * handling ResourceNotFoundException when getting AWS Backup vault access policy ([c2737f4](https://github.com/willdady/aws-resource-based-policy-collector/commit/c2737f4ea0f293da53f5b959c2beb1a81f910d96)) 90 | 91 | ### 1.0.4 (2022-04-08) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * use region-specific client when fetching bucket policies ([f36466d](https://github.com/willdady/aws-resource-based-policy-collector/commit/f36466d8b10f1d6d67614e9b55cf5610aab99f4b)) 97 | 98 | ### 1.0.3 (2022-04-08) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * exporting collect function ([60f3dd7](https://github.com/willdady/aws-resource-based-policy-collector/commit/60f3dd772a25f0bbc1651f86fc1344b31afe2eb8)) 104 | 105 | ### 1.0.2 (2022-04-08) 106 | 107 | ### 1.0.1 (2022-04-08) 108 | 109 | 110 | ### Bug Fixes 111 | 112 | * reorder workflow steps ([eae904c](https://github.com/willdady/aws-resource-based-policy-collector/commit/eae904c8372dd607cdc1f495c4a35907302a52af)) 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Will Dady 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS resource-based policy collector 2 | 3 | This library aims to collect resource-based policies from an AWS account. 4 | 5 | ## Install 6 | 7 | ```bash 8 | yarn add aws-resource-based-policy-collector 9 | ``` 10 | or 11 | ```bash 12 | npm install aws-resource-based-policy-collector 13 | ``` 14 | 15 | ## Motivation 16 | 17 | When removing an account from an AWS organisation special attention must be paid to resource-based policies. Specifically, the presence of the [aws:PrincipalOrgID](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalorgid) condition key will cause access issues once the account leaves it's parent organisation. 18 | 19 | This library simply collects resources and their associated policies in an unopinionated manner. The actual analysis of the output is left to the consumers of this library. 20 | 21 | ## Usage 22 | 23 | Your environment must be configured with valid AWS credentials. See [Setting credentials in Node.js][credentials]. Your credentials must be authorised to perform read-only actions within your account. This can be achieved simply by creating a role in your account with the AWS managed `ReadOnlyAccess` policy. Naturally, your account must also not have read actions restricted by any service control policies in your organisation hierarchy. 24 | 25 | ```typescript 26 | 27 | import { collect } from 'aws-resource-based-policy-collector'; 28 | 29 | const main = async () => { 30 | const result = await collect(); 31 | // ... Do something with result 32 | }; 33 | 34 | main(); 35 | ``` 36 | 37 | The AWS region defaults to that of your credentials however you may optionally set this explicitly. 38 | 39 | ```typescript 40 | const result = await collect({ region: 'us-east-1' }); 41 | ``` 42 | 43 | The `collect` function returns an array of objects per-service where each service object contains an array of `resource` objects. The service object may also contain an optional `error` field if there was an issue listing resources. This typically ocurrs if your credentials do not have the required permissions to read the resources (or is blocked by an SCP). 44 | 45 | Each resource object contains a `type` and `id` to uniquly identify the resource as well as a JSON encoded `policy`. The resource may also contain an optional `error` field if there was an issue querying the resource or it's policy. 46 | 47 | ```typescript 48 | [ 49 | { 50 | serviceName: 's3', 51 | resources: [ 52 | { 53 | type: 'AWS::S3::Bucket', 54 | id: 'my-bucket', 55 | policy: '', // Policy document 56 | error: '', // Only present if an error ocurred 57 | } 58 | ], 59 | error: '', // Only present if an error ocurred 60 | }, 61 | ... 62 | ] 63 | ``` 64 | 65 | Only resources with policies or errors are included. 66 | 67 | ## Supported services 68 | 69 | This library currently collects resource-based policies for AWS services listed below. 70 | 71 | This list of services is taken from the tables found at [AWS services that work with IAM][services], specifically those services with a **Yes** or **Partial** in the **Resource-based policies** column. 72 | 73 | - [x] Lambda 74 | - [x] Serverless Application Repository 75 | - [x] ECR 76 | - [x] AWS Backup 77 | - [x] EFS 78 | - [x] S3 Glacier 79 | - [x] S3 80 | - [ ] S3 on AWS Outposts 81 | - [ ] Cloud9 82 | - [x] CodeArtifact 83 | - [x] CodeBuild 84 | - [x] IAM 85 | - [x] SecretsManager 86 | - [x] ACM Private Certificate Authority 87 | - [x] KMS 88 | - [ ] Lex v2 89 | - [x] CloudWatch Logs 90 | - [ ] Systems Manager Incident Manager 91 | - [ ] Systems Manager Incident Manager Contacts 92 | - [x] API Gateway 93 | - [x] VPC (endpoints) 94 | - [x] Elemental MediaStore 95 | - [x] OpenSearch 96 | - [x] Glue 97 | - [x] EventBridge 98 | - [x] EventBridge Schemas 99 | - [x] SNS 100 | - [x] SQS 101 | - [x] IoT 102 | - [ ] SES v2 103 | 104 | ## Other services 105 | 106 | ### AWS RAM 107 | 108 | AWS RAM does not support resource-based policies however it is included as it is likely of interest as resources may be shared with the parent organisation. 109 | 110 | Note the `policy` field for this resource type is NOT a JSON policy rather it is an `arn` of the principal the resource is shared with. 111 | 112 | ## Troubleshooting 113 | 114 | ### Access denied on S3 buckets 115 | 116 | If you are getting `AccessDenied` errors on S3 bucket resources your bucket likely has a bucket policy preventing access. Remove the bucket policy or modify it to grant read access to your role. 117 | 118 | [services]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html 119 | [credentials]: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-resource-based-policy-collector", 3 | "description": "Utility for collecting resource-based policies from an AWS account", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/willdady/aws-resource-based-policy-collector.git" 7 | }, 8 | "version": "1.4.3", 9 | "main": "dist/index.js", 10 | "license": "MIT", 11 | "author": { 12 | "name": "Will Dady", 13 | "email": "willdady@gmail.com", 14 | "organization": false 15 | }, 16 | "keywords": [ 17 | "aws", 18 | "policies", 19 | "policy" 20 | ], 21 | "scripts": { 22 | "build": "tsc", 23 | "release": "standard-version" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^17.0.23", 27 | "standard-version": "^9.3.2", 28 | "typescript": "^4.6.3" 29 | }, 30 | "dependencies": { 31 | "@aws-sdk/client-acm-pca": "^3.58.0", 32 | "@aws-sdk/client-api-gateway": "^3.58.0", 33 | "@aws-sdk/client-backup": "^3.58.0", 34 | "@aws-sdk/client-cloudformation": "^3.58.0", 35 | "@aws-sdk/client-cloudwatch-logs": "^3.58.0", 36 | "@aws-sdk/client-codeartifact": "^3.58.0", 37 | "@aws-sdk/client-codebuild": "^3.58.0", 38 | "@aws-sdk/client-ec2": "^3.60.0", 39 | "@aws-sdk/client-ecr": "^3.58.0", 40 | "@aws-sdk/client-efs": "^3.58.0", 41 | "@aws-sdk/client-eventbridge": "^3.58.0", 42 | "@aws-sdk/client-glacier": "^3.58.0", 43 | "@aws-sdk/client-glue": "^3.58.0", 44 | "@aws-sdk/client-iam": "^3.58.0", 45 | "@aws-sdk/client-iot": "^3.58.0", 46 | "@aws-sdk/client-kms": "^3.58.0", 47 | "@aws-sdk/client-lambda": "^3.58.0", 48 | "@aws-sdk/client-lex-models-v2": "^3.58.0", 49 | "@aws-sdk/client-mediastore": "^3.58.0", 50 | "@aws-sdk/client-opensearch": "^3.58.0", 51 | "@aws-sdk/client-ram": "^3.58.0", 52 | "@aws-sdk/client-s3": "^3.58.0", 53 | "@aws-sdk/client-schemas": "^3.58.0", 54 | "@aws-sdk/client-secrets-manager": "^3.58.0", 55 | "@aws-sdk/client-serverlessapplicationrepository": "^3.58.0", 56 | "@aws-sdk/client-sns": "^3.58.0", 57 | "@aws-sdk/client-sqs": "^3.58.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/acm-pca/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ACMPCAClient, 3 | ACMPCAClientConfig, 4 | GetPolicyCommand, 5 | ResourceNotFoundException, 6 | paginateListCertificateAuthorities, 7 | } from '@aws-sdk/client-acm-pca'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class AcmPcaPolicyCollector extends BasePolicyCollector { 11 | private client: ACMPCAClient; 12 | 13 | constructor(clientConfig?: ACMPCAClientConfig) { 14 | super({ serviceName: 'acm-pca' }); 15 | this.client = new ACMPCAClient(clientConfig || {}); 16 | } 17 | 18 | private async listCertificateAuthorities() { 19 | const paginator = paginateListCertificateAuthorities( 20 | { client: this.client }, 21 | {}, 22 | ); 23 | const certificateAuthorities = []; 24 | for await (const page of paginator) { 25 | certificateAuthorities.push(...(page.CertificateAuthorities || [])); 26 | } 27 | return certificateAuthorities; 28 | } 29 | 30 | private async getPolicy(resourceArn: string) { 31 | return this.client.send( 32 | new GetPolicyCommand({ 33 | ResourceArn: resourceArn, 34 | }), 35 | ); 36 | } 37 | 38 | public async run(): Promise { 39 | const result: ServicePoliciesResult = { 40 | serviceName: this.serviceName, 41 | resources: [], 42 | }; 43 | try { 44 | const certificateAuthorities = await this.listCertificateAuthorities(); 45 | for (const ca of certificateAuthorities) { 46 | let policy; 47 | try { 48 | policy = await this.getPolicy(ca.Arn!); 49 | } catch (err) { 50 | if (err instanceof ResourceNotFoundException) continue; 51 | throw err; 52 | } 53 | result.resources.push({ 54 | type: 'AWS::ACMPCA::CertificateAuthority', 55 | id: ca.Arn!, 56 | policy: policy.Policy!, 57 | }); 58 | } 59 | } catch (err) { 60 | result.error = JSON.stringify(err); 61 | } 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/apigateway/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | APIGatewayClient, 3 | APIGatewayClientConfig, 4 | paginateGetRestApis, 5 | RestApi, 6 | } from '@aws-sdk/client-api-gateway'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class ApiGatewayPolicyCollector extends BasePolicyCollector { 10 | private client: APIGatewayClient; 11 | 12 | constructor(clientConfig?: APIGatewayClientConfig) { 13 | super({ serviceName: 'apigateway' }); 14 | this.client = new APIGatewayClient(clientConfig || {}); 15 | } 16 | 17 | private async getRestApis(): Promise { 18 | const paginator = paginateGetRestApis({ client: this.client }, {}); 19 | const restApis = []; 20 | for await (const page of paginator) { 21 | restApis.push(...(page.items || [])); 22 | } 23 | return restApis; 24 | } 25 | 26 | public async run(): Promise { 27 | const result: ServicePoliciesResult = { 28 | serviceName: this.serviceName, 29 | resources: [], 30 | }; 31 | try { 32 | const restApis = await this.getRestApis(); 33 | for (const r of restApis) { 34 | if (!r.policy) continue; 35 | result.resources.push({ 36 | type: 'AWS::ApiGateway::RestApi', 37 | id: r.id!, 38 | policy: r.policy, 39 | }); 40 | } 41 | } catch (err) { 42 | result.error = JSON.stringify(err); 43 | } 44 | return result; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/backup/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BackupClient, 3 | BackupClientConfig, 4 | BackupVaultListMember, 5 | GetBackupVaultAccessPolicyCommand, 6 | paginateListBackupVaults, 7 | ResourceNotFoundException, 8 | } from '@aws-sdk/client-backup'; 9 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 10 | 11 | export class BackupPolicyCollector extends BasePolicyCollector { 12 | private client: BackupClient; 13 | 14 | constructor(clientConfig?: BackupClientConfig) { 15 | super({ serviceName: 'backup' }); 16 | this.client = new BackupClient(clientConfig || {}); 17 | } 18 | 19 | private async listBackupVaults(): Promise { 20 | const paginator = paginateListBackupVaults({ client: this.client }, {}); 21 | const vaults = []; 22 | for await (const page of paginator) { 23 | vaults.push(...(page.BackupVaultList || [])); 24 | } 25 | return vaults; 26 | } 27 | 28 | private async getVaultAccessPolicy(vaultName: string) { 29 | return this.client.send( 30 | new GetBackupVaultAccessPolicyCommand({ BackupVaultName: vaultName }), 31 | ); 32 | } 33 | 34 | public async run(): Promise { 35 | const result: ServicePoliciesResult = { 36 | serviceName: this.serviceName, 37 | resources: [], 38 | }; 39 | try { 40 | const vaults = await this.listBackupVaults(); 41 | for (const v of vaults) { 42 | let response; 43 | try { 44 | response = await this.getVaultAccessPolicy(v.BackupVaultName!); 45 | } catch (err) { 46 | if (err instanceof ResourceNotFoundException) { 47 | continue; 48 | } 49 | throw err; 50 | } 51 | if (!response.Policy) continue; 52 | result.resources.push({ 53 | type: 'AWS::Backup::BackupVault', 54 | id: v.BackupVaultName!, 55 | policy: response.Policy, 56 | }); 57 | } 58 | } catch (err) { 59 | result.error = JSON.stringify(err); 60 | } 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/cloudformation/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CloudFormationClient, 3 | CloudFormationClientConfig, 4 | GetStackPolicyCommand, 5 | paginateListStacks, 6 | } from '@aws-sdk/client-cloudformation'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class CloudformationPolicyCollector extends BasePolicyCollector { 10 | private client: CloudFormationClient; 11 | 12 | constructor(clientConfig?: CloudFormationClientConfig) { 13 | super({ serviceName: 'cloudformation' }); 14 | this.client = new CloudFormationClient(clientConfig || {}); 15 | } 16 | 17 | private async listStacks() { 18 | const paginator = paginateListStacks({ client: this.client }, {}); 19 | const stacks = []; 20 | for await (const page of paginator) { 21 | stacks.push(...(page.StackSummaries || [])); 22 | } 23 | return stacks; 24 | } 25 | 26 | private async getStackPolicy(stackName: string) { 27 | const response = await this.client.send( 28 | new GetStackPolicyCommand({ 29 | StackName: stackName, 30 | }), 31 | ); 32 | return response.StackPolicyBody; 33 | } 34 | 35 | public async run(): Promise { 36 | const result: ServicePoliciesResult = { 37 | serviceName: this.serviceName, 38 | resources: [], 39 | }; 40 | try { 41 | this.log('Listing stacks'); 42 | const stacks = await this.listStacks(); 43 | this.log(`- Got ${stacks.length} stacks`); 44 | for (const s of stacks) { 45 | // Ignore deleted stacks 46 | if (s.DeletionTime) continue; 47 | await this.sleep(500); 48 | const stackPolicy = await this.getStackPolicy(s.StackName!); 49 | if (!stackPolicy) continue; 50 | result.resources.push({ 51 | type: 'Cloudformation::StackPolicy', // Note there is no-such CF resource type 52 | id: s.StackName!, 53 | policy: stackPolicy, 54 | }); 55 | } 56 | } catch (err) { 57 | result.error = JSON.stringify(err); 58 | } 59 | return result; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/codeartifact/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CodeartifactClient, 3 | CodeartifactClientConfig, 4 | DomainSummary, 5 | GetDomainPermissionsPolicyCommand, 6 | paginateListDomains, 7 | } from '@aws-sdk/client-codeartifact'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class CodeArtifactPolicyCollector extends BasePolicyCollector { 11 | private client: CodeartifactClient; 12 | 13 | constructor(clientConfig?: CodeartifactClientConfig) { 14 | super({ serviceName: 'codeartifact' }); 15 | this.client = new CodeartifactClient(clientConfig || {}); 16 | } 17 | 18 | private async listDomains(): Promise { 19 | const paginator = paginateListDomains({ client: this.client }, {}); 20 | const domains = []; 21 | for await (const page of paginator) { 22 | domains.push(...(page.domains || [])); 23 | } 24 | return domains; 25 | } 26 | 27 | private getDomainPermissionsPolicy(domain: string) { 28 | return this.client.send(new GetDomainPermissionsPolicyCommand({ domain })); 29 | } 30 | 31 | public async run(): Promise { 32 | const result: ServicePoliciesResult = { 33 | serviceName: this.serviceName, 34 | resources: [], 35 | }; 36 | try { 37 | const domains = await this.listDomains(); 38 | for (const d of domains) { 39 | const response = await this.getDomainPermissionsPolicy(d.name!); 40 | if (!response.policy?.document) continue; 41 | result.resources.push({ 42 | type: 'AWS::CodeArtifact::Domain', 43 | id: d.name!, 44 | policy: response.policy.document, 45 | }); 46 | } 47 | } catch (err) { 48 | result.error = JSON.stringify(err); 49 | } 50 | return result; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/codebuild/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CodeBuildClient, 3 | CodeBuildClientConfig, 4 | GetResourcePolicyCommand, 5 | paginateListSharedProjects, 6 | } from '@aws-sdk/client-codebuild'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class CodeBuildPolicyCollector extends BasePolicyCollector { 10 | private client: CodeBuildClient; 11 | 12 | constructor(clientConfig?: CodeBuildClientConfig) { 13 | super({ serviceName: 'codebuild' }); 14 | this.client = new CodeBuildClient({}); 15 | } 16 | 17 | private async listSharedProjects(): Promise { 18 | const paginator = paginateListSharedProjects({ client: this.client }, {}); 19 | const domains = []; 20 | for await (const page of paginator) { 21 | domains.push(...(page.projects || [])); 22 | } 23 | return domains; 24 | } 25 | 26 | private getResourcePolicy(projectArn: string) { 27 | return this.client.send( 28 | new GetResourcePolicyCommand({ resourceArn: projectArn }), 29 | ); 30 | } 31 | 32 | public async run(): Promise { 33 | const result: ServicePoliciesResult = { 34 | serviceName: this.serviceName, 35 | resources: [], 36 | }; 37 | try { 38 | const projects = await this.listSharedProjects(); 39 | for (const arn of projects) { 40 | const response = await this.getResourcePolicy(arn); 41 | if (!response.policy) continue; 42 | result.resources.push({ 43 | type: 'AWS::CodeBuild::Project', 44 | id: arn, 45 | policy: response.policy, 46 | }); 47 | } 48 | } catch (err) { 49 | result.error = JSON.stringify(err); 50 | } 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | interface BasePolicyCollectorProps { 2 | serviceName: string; 3 | } 4 | 5 | export interface ServiceResource { 6 | id: string; 7 | type: string; 8 | policy: string; 9 | error?: string; 10 | } 11 | 12 | export interface ServicePoliciesResult { 13 | serviceName: string; 14 | resources: ServiceResource[]; 15 | error?: string; 16 | } 17 | 18 | export abstract class BasePolicyCollector { 19 | serviceName: string; 20 | 21 | constructor({ serviceName }: BasePolicyCollectorProps) { 22 | this.serviceName = serviceName; 23 | } 24 | 25 | protected log(message?: any) { 26 | console.log(this.serviceName, ': ', message); 27 | } 28 | 29 | protected sleep(ms: number) { 30 | return new Promise((resolve) => setTimeout(resolve, ms)); 31 | } 32 | 33 | public abstract run(): Promise; 34 | } 35 | -------------------------------------------------------------------------------- /src/ec2/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EC2Client, 3 | EC2ClientConfig, 4 | paginateDescribeVpcEndpoints, 5 | VpcEndpoint, 6 | } from '@aws-sdk/client-ec2'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class Ec2PolicyCollector extends BasePolicyCollector { 10 | private client: EC2Client; 11 | 12 | constructor(clientConfig?: EC2ClientConfig) { 13 | super({ serviceName: 'ec2' }); 14 | this.client = new EC2Client(clientConfig || {}); 15 | } 16 | 17 | private async describeVpcEndpoints(): Promise { 18 | const paginator = paginateDescribeVpcEndpoints({ client: this.client }, {}); 19 | const vpcEndpoints = []; 20 | for await (const page of paginator) { 21 | vpcEndpoints.push(...(page.VpcEndpoints || [])); 22 | } 23 | return vpcEndpoints; 24 | } 25 | 26 | public async run(): Promise { 27 | const result: ServicePoliciesResult = { 28 | serviceName: this.serviceName, 29 | resources: [], 30 | }; 31 | try { 32 | const vpcEndpoints = await this.describeVpcEndpoints(); 33 | for (const vpce of vpcEndpoints) { 34 | if (!vpce.PolicyDocument) continue; 35 | result.resources.push({ 36 | type: 'AWS::EC2::VPCEndpoint', 37 | id: vpce.VpcEndpointId!, 38 | policy: vpce.PolicyDocument, 39 | }); 40 | } 41 | } catch (err) { 42 | result.error = JSON.stringify(err); 43 | } 44 | return result; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ecr/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ECRClient, 3 | ECRClientConfig, 4 | GetRepositoryPolicyCommand, 5 | paginateDescribeRepositories, 6 | Repository, 7 | RepositoryPolicyNotFoundException, 8 | } from '@aws-sdk/client-ecr'; 9 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 10 | 11 | export class EcrPolicyCollector extends BasePolicyCollector { 12 | private client: ECRClient; 13 | 14 | constructor(clientConfig?: ECRClientConfig) { 15 | super({ serviceName: 'ecr' }); 16 | this.client = new ECRClient(clientConfig || {}); 17 | } 18 | 19 | private async describeRepositories(): Promise { 20 | const paginator = paginateDescribeRepositories({ client: this.client }, {}); 21 | const repositories = []; 22 | for await (const page of paginator) { 23 | repositories.push(...(page.repositories || [])); 24 | } 25 | return repositories; 26 | } 27 | 28 | private async getRepositoryPolicy(repositoryName: string) { 29 | return this.client.send( 30 | new GetRepositoryPolicyCommand({ 31 | repositoryName, 32 | }), 33 | ); 34 | } 35 | 36 | public async run(): Promise { 37 | const result: ServicePoliciesResult = { 38 | serviceName: this.serviceName, 39 | resources: [], 40 | }; 41 | try { 42 | const repositories = await this.describeRepositories(); 43 | for (const r of repositories) { 44 | try { 45 | const response = await this.getRepositoryPolicy(r.repositoryName!); 46 | if (!response.policyText) continue; 47 | result.resources.push({ 48 | type: 'AWS::ECR::Repository', 49 | id: r.repositoryName!, 50 | policy: response.policyText, 51 | }); 52 | } catch (err) { 53 | if (err instanceof RepositoryPolicyNotFoundException) { 54 | continue; 55 | } 56 | throw err; 57 | } 58 | } 59 | } catch (err) { 60 | result.error = JSON.stringify(err); 61 | } 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/efs/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DescribeFileSystemPolicyCommand, 3 | EFSClient, 4 | EFSClientConfig, 5 | FileSystemDescription, 6 | paginateDescribeFileSystems, 7 | PolicyNotFound 8 | } from '@aws-sdk/client-efs'; 9 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 10 | 11 | export class EfsPolicyCollector extends BasePolicyCollector { 12 | private client: EFSClient; 13 | 14 | constructor(clientConfig?: EFSClientConfig) { 15 | super({ serviceName: 'efs' }); 16 | this.client = new EFSClient(clientConfig || {}); 17 | } 18 | 19 | private async describeFileSystems(): Promise { 20 | const paginator = paginateDescribeFileSystems({ client: this.client }, {}); 21 | const fileSystems = []; 22 | for await (const page of paginator) { 23 | fileSystems.push(...(page.FileSystems || [])); 24 | } 25 | return fileSystems; 26 | } 27 | 28 | private async describeFileSystemPolicy(fileSystemId: string) { 29 | return this.client.send( 30 | new DescribeFileSystemPolicyCommand({ FileSystemId: fileSystemId }), 31 | ); 32 | } 33 | 34 | public async run(): Promise { 35 | const result: ServicePoliciesResult = { 36 | serviceName: this.serviceName, 37 | resources: [], 38 | }; 39 | try { 40 | const fileSysterms = await this.describeFileSystems(); 41 | for (const fs of fileSysterms) { 42 | try { 43 | const response = await this.describeFileSystemPolicy(fs.FileSystemId!); 44 | if (!response.Policy) continue; 45 | result.resources.push({ 46 | type: 'AWS::EFS::FileSystem', 47 | id: fs.FileSystemId!, 48 | policy: response.Policy, 49 | }); 50 | } catch (err) { 51 | if (err instanceof PolicyNotFound) { 52 | continue; 53 | } 54 | throw err; 55 | } 56 | } 57 | } catch (err) { 58 | result.error = JSON.stringify(err); 59 | } 60 | return result; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/events/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EventBridgeClient, 3 | ListEventBusesCommand, 4 | EventBus, 5 | EventBridgeClientConfig, 6 | } from '@aws-sdk/client-eventbridge'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class EventBridgePolicyCollector extends BasePolicyCollector { 10 | private client: EventBridgeClient; 11 | 12 | constructor(clientConfig?: EventBridgeClientConfig) { 13 | super({ serviceName: 'events' }); 14 | this.client = new EventBridgeClient(clientConfig || {}); 15 | } 16 | 17 | private async listEventBuses(): Promise { 18 | const f = async ( 19 | eventBuses: EventBus[] = [], 20 | nextToken?: string, 21 | ): Promise => { 22 | const response = await this.client.send( 23 | new ListEventBusesCommand({ NextToken: nextToken }), 24 | ); 25 | if (response.NextToken) { 26 | return await f( 27 | [...eventBuses, ...(response.EventBuses || [])], 28 | response.NextToken, 29 | ); 30 | } 31 | return [...eventBuses, ...(response.EventBuses || [])]; 32 | }; 33 | return f(); 34 | } 35 | 36 | public async run(): Promise { 37 | const result: ServicePoliciesResult = { 38 | serviceName: this.serviceName, 39 | resources: [], 40 | }; 41 | try { 42 | const eventBuses = await this.listEventBuses(); 43 | for (const b of eventBuses) { 44 | if (!b.Policy) continue; 45 | result.resources.push({ 46 | type: 'AWS::Events::EventBus', 47 | id: b.Name!, 48 | policy: b.Policy, 49 | }); 50 | } 51 | } catch (err) { 52 | result.error = JSON.stringify(err); 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/glacier/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DescribeVaultOutput, 3 | GetVaultAccessPolicyCommand, 4 | GlacierClient, 5 | GlacierClientConfig, 6 | paginateListVaults, 7 | } from '@aws-sdk/client-glacier'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class GlacierPolicyCollector extends BasePolicyCollector { 11 | private client: GlacierClient; 12 | 13 | constructor(clientConfig?: GlacierClientConfig) { 14 | super({ serviceName: 'glacier' }); 15 | this.client = new GlacierClient(clientConfig || {}); 16 | } 17 | 18 | private async listVaults(): Promise { 19 | const paginator = paginateListVaults( 20 | { client: this.client }, 21 | { accountId: '-' }, 22 | ); 23 | const vaults = []; 24 | for await (const page of paginator) { 25 | vaults.push(...(page.VaultList || [])); 26 | } 27 | return vaults; 28 | } 29 | 30 | private async getVaultAccessPolicy(vaultName: string) { 31 | return this.client.send( 32 | new GetVaultAccessPolicyCommand({ accountId: '-', vaultName }), 33 | ); 34 | } 35 | 36 | public async run(): Promise { 37 | const result: ServicePoliciesResult = { 38 | serviceName: this.serviceName, 39 | resources: [], 40 | }; 41 | try { 42 | const vaults = await this.listVaults(); 43 | for (const v of vaults) { 44 | const response = await this.getVaultAccessPolicy(v.VaultName!); 45 | if (!response.policy?.Policy) continue; 46 | result.resources.push({ 47 | type: 'AWS::Glacier::Vault', 48 | id: v.VaultName!, 49 | policy: response.policy.Policy, 50 | }); 51 | } 52 | } catch (err) { 53 | result.error = JSON.stringify(err); 54 | } 55 | return result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/glue/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GlueClient, 3 | GlueClientConfig, 4 | paginateGetResourcePolicies, 5 | } from '@aws-sdk/client-glue'; 6 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 7 | 8 | export class GluePolicyCollector extends BasePolicyCollector { 9 | private client: GlueClient; 10 | 11 | constructor(clientConfig?: GlueClientConfig) { 12 | super({ serviceName: 'glue' }); 13 | this.client = new GlueClient(clientConfig || {}); 14 | } 15 | 16 | private async getResourcePolicies() { 17 | const paginator = paginateGetResourcePolicies({ client: this.client }, {}); 18 | const policies = []; 19 | for await (const page of paginator) { 20 | policies.push(...(page.GetResourcePoliciesResponseList || [])); 21 | } 22 | return policies; 23 | } 24 | 25 | public async run(): Promise { 26 | const result: ServicePoliciesResult = { 27 | serviceName: this.serviceName, 28 | resources: [], 29 | }; 30 | try { 31 | const policies = await this.getResourcePolicies(); 32 | for (const p of policies) { 33 | if (!p.PolicyHash) 34 | throw new Error( 35 | `Unable to identify glue policy. Missing 'PolicyHash' attribute.`, 36 | ); 37 | if (!p.PolicyInJson) 38 | throw new Error( 39 | `Unexpected glue policy result. Policy with PolicyHash '${p.PolicyHash}' is missing 'PolicyInJson' attribute.`, 40 | ); 41 | result.resources.push({ 42 | type: 'Glue::Policy', // Note there is no-such CF resource type 43 | id: p.PolicyHash, 44 | policy: p.PolicyInJson, 45 | }); 46 | } 47 | } catch (err) { 48 | result.error = JSON.stringify(err); 49 | } 50 | return result; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/iam/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetRoleCommand, 3 | IAMClient, 4 | IAMClientConfig, 5 | paginateListRoles, 6 | paginateListPolicies, 7 | Role, 8 | GetPolicyVersionCommand, 9 | } from '@aws-sdk/client-iam'; 10 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 11 | 12 | export class IamPolicyCollector extends BasePolicyCollector { 13 | private client: IAMClient; 14 | 15 | constructor(clientConfig?: IAMClientConfig) { 16 | super({ serviceName: 'iam' }); 17 | this.client = new IAMClient(clientConfig || {}); 18 | } 19 | 20 | private async listRoles(): Promise { 21 | const paginator = paginateListRoles({ client: this.client }, {}); 22 | const roles = []; 23 | for await (const page of paginator) { 24 | roles.push(...(page.Roles || [])); 25 | } 26 | return roles; 27 | } 28 | 29 | private async getRole(roleName: string) { 30 | return this.client.send(new GetRoleCommand({ RoleName: roleName })); 31 | } 32 | 33 | private async listPolicies() { 34 | const paginator = paginateListPolicies( 35 | { client: this.client }, 36 | { Scope: 'Local' }, 37 | ); 38 | const policies = []; 39 | for await (const page of paginator) { 40 | policies.push(...(page.Policies || [])); 41 | } 42 | return policies; 43 | } 44 | 45 | private async getPolicyVersion(policyArn: string, versionId: string) { 46 | const response = await this.client.send( 47 | new GetPolicyVersionCommand({ 48 | PolicyArn: policyArn, 49 | VersionId: versionId, 50 | }), 51 | ); 52 | return response.PolicyVersion?.Document; 53 | } 54 | 55 | public async run(): Promise { 56 | const result: ServicePoliciesResult = { 57 | serviceName: this.serviceName, 58 | resources: [], 59 | }; 60 | try { 61 | const roles = await this.listRoles(); 62 | for (const r of roles) { 63 | const response = await this.getRole(r.RoleName!); 64 | if (!response.Role?.AssumeRolePolicyDocument) continue; 65 | result.resources.push({ 66 | type: 'AWS::IAM::Role', 67 | id: r.RoleName!, 68 | policy: decodeURIComponent(response.Role.AssumeRolePolicyDocument), 69 | }); 70 | } 71 | } catch (err) { 72 | result.error = JSON.stringify(err); 73 | } 74 | 75 | try { 76 | const policies = await this.listPolicies(); 77 | for (const p of policies) { 78 | if (!p.Arn || !p.DefaultVersionId) continue; 79 | const policyDocument = await this.getPolicyVersion( 80 | p.Arn, 81 | p.DefaultVersionId, 82 | ); 83 | if (!policyDocument) continue; 84 | result.resources.push({ 85 | type: 'AWS::IAM::Policy', 86 | id: p.PolicyName!, 87 | policy: policyDocument, 88 | }); 89 | } 90 | } catch (err) { 91 | result.error = JSON.stringify(err); 92 | } 93 | 94 | return result; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { AcmPcaPolicyCollector } from './acm-pca'; 2 | import { ApiGatewayPolicyCollector } from './apigateway'; 3 | import { BackupPolicyCollector } from './backup'; 4 | import { CloudformationPolicyCollector } from './cloudformation'; 5 | import { CloudWatchLogsPolicyCollector } from './logs'; 6 | import { CodeArtifactPolicyCollector } from './codeartifact'; 7 | import { CodeBuildPolicyCollector } from './codebuild'; 8 | import { Ec2PolicyCollector } from './ec2'; 9 | import { EcrPolicyCollector } from './ecr'; 10 | import { EfsPolicyCollector } from './efs'; 11 | import { EventBridgePolicyCollector } from './events'; 12 | import { EventBridgeSchemasPolicyCollector } from './schemas'; 13 | import { GlacierPolicyCollector } from './glacier'; 14 | import { GluePolicyCollector } from './glue'; 15 | import { IamPolicyCollector } from './iam'; 16 | import { IotPolicyCollector } from './iot'; 17 | import { KmsPolicyCollector } from './kms'; 18 | import { LambdaPolicyCollector } from './lambda'; 19 | import { MediaStorePolicyCollector } from './mediastore'; 20 | import { OpenSearchClientPolicyCollector } from './opensearch'; 21 | import { RamPolicyCollector } from './ram'; 22 | import { S3PolicyCollector } from './s3'; 23 | import { SecretsManagerPolicyCollector } from './secretsmanager'; 24 | import { ServerlessApplicationRepositoryPolicyCollector } from './serverless'; 25 | import { SnsPolicyCollector } from './sns'; 26 | import { SqsPolicyCollector } from './sqs'; 27 | 28 | export { ServicePoliciesResult, ServiceResource } from './core'; 29 | 30 | export const collect = async (config?: { region?: string }) => { 31 | const collectors = [ 32 | new AcmPcaPolicyCollector(config), 33 | new ApiGatewayPolicyCollector(config), 34 | new BackupPolicyCollector(config), 35 | new CloudformationPolicyCollector(config), 36 | new CloudWatchLogsPolicyCollector(config), 37 | new CodeArtifactPolicyCollector(config), 38 | new CodeBuildPolicyCollector(config), 39 | new Ec2PolicyCollector(config), 40 | new EcrPolicyCollector(config), 41 | new EfsPolicyCollector(config), 42 | new EventBridgePolicyCollector(config), 43 | new EventBridgeSchemasPolicyCollector(config), 44 | new GlacierPolicyCollector(config), 45 | new GluePolicyCollector(config), 46 | new IamPolicyCollector(config), 47 | new IotPolicyCollector(config), 48 | new KmsPolicyCollector(config), 49 | new LambdaPolicyCollector(config), 50 | new MediaStorePolicyCollector(config), 51 | new OpenSearchClientPolicyCollector(config), 52 | new RamPolicyCollector(config), 53 | new S3PolicyCollector(config), 54 | new SecretsManagerPolicyCollector(config), 55 | new ServerlessApplicationRepositoryPolicyCollector(config), 56 | new SnsPolicyCollector(config), 57 | new SqsPolicyCollector(config), 58 | ]; 59 | return await Promise.all(collectors.map((collector) => collector.run())); 60 | }; 61 | -------------------------------------------------------------------------------- /src/iot/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetPolicyCommand, 3 | IoTClient, 4 | IoTClientConfig, 5 | paginateListPolicies, 6 | } from '@aws-sdk/client-iot'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class IotPolicyCollector extends BasePolicyCollector { 10 | private client: IoTClient; 11 | 12 | constructor(clientConfig?: IoTClientConfig) { 13 | super({ serviceName: 'iot' }); 14 | this.client = new IoTClient(clientConfig || {}); 15 | } 16 | 17 | private async getPolicy(policyName: string) { 18 | return this.client.send(new GetPolicyCommand({ policyName })); 19 | } 20 | 21 | private async listPolicies() { 22 | const paginator = paginateListPolicies({ client: this.client }, {}); 23 | const policies = []; 24 | for await (const page of paginator) { 25 | policies.push(...(page.policies || [])); 26 | } 27 | return policies; 28 | } 29 | 30 | public async run(): Promise { 31 | const result: ServicePoliciesResult = { 32 | serviceName: this.serviceName, 33 | resources: [], 34 | }; 35 | try { 36 | const policies = await this.listPolicies(); 37 | for (const p of policies) { 38 | const policy = await this.getPolicy(p.policyName!); 39 | result.resources.push({ 40 | type: 'AWS::IoT::Policy', 41 | id: p.policyName!, 42 | policy: policy.policyDocument!, 43 | }); 44 | } 45 | } catch (err) { 46 | result.error = JSON.stringify(err); 47 | } 48 | 49 | return result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/kms/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetKeyPolicyCommand, 3 | KeyListEntry, 4 | KMSClient, 5 | KMSClientConfig, 6 | paginateListKeys, 7 | } from '@aws-sdk/client-kms'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class KmsPolicyCollector extends BasePolicyCollector { 11 | private client: KMSClient; 12 | 13 | constructor(clientConfig?: KMSClientConfig) { 14 | super({ serviceName: 'kms' }); 15 | this.client = new KMSClient(clientConfig || {}); 16 | } 17 | 18 | private async listKeys(): Promise { 19 | const paginator = paginateListKeys({ client: this.client }, {}); 20 | const keys = []; 21 | for await (const page of paginator) { 22 | keys.push(...(page.Keys || [])); 23 | } 24 | return keys; 25 | } 26 | 27 | private async getKeyPolicy(keyId: string) { 28 | return await this.client.send( 29 | new GetKeyPolicyCommand({ KeyId: keyId, PolicyName: 'default' }), 30 | ); 31 | } 32 | 33 | public async run(): Promise { 34 | const result: ServicePoliciesResult = { 35 | serviceName: this.serviceName, 36 | resources: [], 37 | }; 38 | try { 39 | const keys = await this.listKeys(); 40 | for (const k of keys) { 41 | const response = await this.getKeyPolicy(k.KeyId!); 42 | if (!response.Policy) continue; 43 | result.resources.push({ 44 | type: 'AWS::KMS::Key', 45 | id: k.KeyId!, 46 | policy: response.Policy, 47 | }); 48 | } 49 | } catch (err) { 50 | result.error = JSON.stringify(err); 51 | } 52 | return result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lambda/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LambdaClient, 3 | GetPolicyCommand, 4 | ResourceNotFoundException, 5 | paginateListFunctions, 6 | paginateListAliases, 7 | paginateListVersionsByFunction, 8 | LambdaClientConfig, 9 | } from '@aws-sdk/client-lambda'; 10 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 11 | 12 | export class LambdaPolicyCollector extends BasePolicyCollector { 13 | private client: LambdaClient; 14 | 15 | constructor(clientConfig?: LambdaClientConfig) { 16 | super({ serviceName: 'lambda' }); 17 | this.client = new LambdaClient(clientConfig || {}); 18 | } 19 | 20 | private async listFunctions() { 21 | const paginator = paginateListFunctions({ client: this.client }, {}); 22 | const fns = []; 23 | for await (const page of paginator) { 24 | fns.push(...(page.Functions || [])); 25 | } 26 | return fns; 27 | } 28 | 29 | private async listAliases(functionName: string) { 30 | const paginator = paginateListAliases( 31 | { client: this.client }, 32 | { FunctionName: functionName }, 33 | ); 34 | const aliases = []; 35 | for await (const page of paginator) { 36 | aliases.push(...(page.Aliases || [])); 37 | } 38 | return aliases; 39 | } 40 | 41 | private async listVersions(functionName: string) { 42 | const paginator = paginateListVersionsByFunction( 43 | { client: this.client }, 44 | { FunctionName: functionName }, 45 | ); 46 | const versions = []; 47 | for await (const page of paginator) { 48 | versions.push(...(page.Versions || [])); 49 | } 50 | return versions; 51 | } 52 | 53 | private async getPolicy(functionName: string, qualifier?: string) { 54 | return this.client.send( 55 | new GetPolicyCommand({ 56 | FunctionName: functionName, 57 | Qualifier: qualifier, 58 | }), 59 | ); 60 | } 61 | 62 | public async run(): Promise { 63 | const result: ServicePoliciesResult = { 64 | serviceName: this.serviceName, 65 | resources: [], 66 | }; 67 | try { 68 | this.log('Listing functions'); 69 | const functions = await this.listFunctions(); 70 | this.log(`- got ${functions.length} functions`); 71 | for (const f of functions) { 72 | const aliases = await this.listAliases(f.FunctionName!); 73 | for (const a of aliases) { 74 | await this.sleep(500); 75 | try { 76 | const response = await this.getPolicy(f.FunctionName!, a.Name!); 77 | if (!response.Policy) continue; 78 | result.resources.push({ 79 | type: 'AWS::Lambda::Alias', 80 | id: [f.FunctionArn, ':', a.Name].join(''), 81 | policy: response.Policy, 82 | }); 83 | } catch (err) { 84 | if (err instanceof ResourceNotFoundException) { 85 | continue; 86 | } 87 | result.resources.push({ 88 | type: 'AWS::Lambda::Alias', 89 | id: [f.FunctionArn, ':', a.Name].join(''), 90 | policy: '', 91 | error: JSON.stringify(err), 92 | }); 93 | continue; 94 | } 95 | } 96 | 97 | this.log('Listing versions'); 98 | const versions = await this.listVersions(f.FunctionName!); 99 | this.log(`- got ${versions.length} versions`); 100 | for (const v of versions) { 101 | await this.sleep(500); 102 | try { 103 | const response = await this.getPolicy(f.FunctionName!, v.Version!); 104 | if (!response.Policy) continue; 105 | result.resources.push({ 106 | type: 'AWS::Lambda::Version', 107 | id: [f.FunctionArn, ':', v.Version].join(''), 108 | policy: response.Policy, 109 | }); 110 | } catch (err) { 111 | if (err instanceof ResourceNotFoundException) { 112 | continue; 113 | } 114 | result.resources.push({ 115 | type: 'AWS::Lambda::Version', 116 | id: [f.FunctionArn, ':', v.Version].join(''), 117 | policy: '', 118 | error: JSON.stringify(err), 119 | }); 120 | continue; 121 | } 122 | } 123 | 124 | try { 125 | const response = await this.getPolicy(f.FunctionName!); 126 | if (!response.Policy) continue; 127 | result.resources.push({ 128 | type: 'AWS::Lambda::Function', 129 | id: f.FunctionName!, 130 | policy: response.Policy, 131 | }); 132 | } catch (err) { 133 | if (err instanceof ResourceNotFoundException) { 134 | continue; 135 | } 136 | result.resources.push({ 137 | type: 'AWS::Lambda::Function', 138 | id: f.FunctionName!, 139 | policy: '', 140 | error: JSON.stringify(err), 141 | }); 142 | } 143 | } 144 | } catch (err) { 145 | result.error = JSON.stringify(err); 146 | } 147 | return result; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/logs/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CloudWatchLogsClient, 3 | CloudWatchLogsClientConfig, 4 | paginateDescribeDestinations, 5 | } from '@aws-sdk/client-cloudwatch-logs'; 6 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 7 | 8 | export class CloudWatchLogsPolicyCollector extends BasePolicyCollector { 9 | private client: CloudWatchLogsClient; 10 | 11 | constructor(clientConfig?: CloudWatchLogsClientConfig) { 12 | super({ serviceName: 'logs' }); 13 | this.client = new CloudWatchLogsClient(clientConfig || {}); 14 | } 15 | 16 | private async describeDestinations() { 17 | const paginator = paginateDescribeDestinations({ client: this.client }, {}); 18 | const destinations = []; 19 | for await (const page of paginator) { 20 | destinations.push(...(page.destinations || [])); 21 | } 22 | return destinations; 23 | } 24 | 25 | public async run(): Promise { 26 | const result: ServicePoliciesResult = { 27 | serviceName: this.serviceName, 28 | resources: [], 29 | }; 30 | try { 31 | const destinations = await this.describeDestinations(); 32 | for (const d of destinations) { 33 | if (!d.accessPolicy) continue; 34 | result.resources.push({ 35 | type: 'AWS::Logs::Destination', 36 | id: d.destinationName!, 37 | policy: d.accessPolicy, 38 | }); 39 | } 40 | } catch (err) { 41 | result.error = JSON.stringify(err); 42 | } 43 | return result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mediastore/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetContainerPolicyCommand, 3 | MediaStoreClient, 4 | MediaStoreClientConfig, 5 | paginateListContainers, 6 | PolicyNotFoundException, 7 | } from '@aws-sdk/client-mediastore'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class MediaStorePolicyCollector extends BasePolicyCollector { 11 | private client: MediaStoreClient; 12 | 13 | constructor(clientConfig?: MediaStoreClientConfig) { 14 | super({ serviceName: 'mediastore' }); 15 | this.client = new MediaStoreClient(clientConfig || {}); 16 | } 17 | 18 | private async listContainers() { 19 | const paginator = paginateListContainers({ client: this.client }, {}); 20 | const containers = []; 21 | for await (const page of paginator) { 22 | containers.push(...(page.Containers || [])); 23 | } 24 | return containers; 25 | } 26 | 27 | private async getContainerPolicy(containerName: string) { 28 | return this.client.send( 29 | new GetContainerPolicyCommand({ ContainerName: containerName }), 30 | ); 31 | } 32 | 33 | public async run(): Promise { 34 | const result: ServicePoliciesResult = { 35 | serviceName: this.serviceName, 36 | resources: [], 37 | }; 38 | try { 39 | const containers = await this.listContainers(); 40 | for (const c of containers) { 41 | try { 42 | const response = await this.getContainerPolicy(c.Name!); 43 | if (!response.Policy) continue; 44 | result.resources.push({ 45 | type: 'AWS::MediaStore::Container', 46 | id: c.Name!, 47 | policy: response.Policy, 48 | }); 49 | } catch (err) { 50 | if (err instanceof PolicyNotFoundException) { 51 | continue; 52 | } 53 | throw err; 54 | } 55 | } 56 | } catch (err) { 57 | result.error = JSON.stringify(err); 58 | } 59 | return result; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/opensearch/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DescribeDomainCommand, 3 | ListDomainNamesCommand, 4 | OpenSearchClient, 5 | OpenSearchClientConfig, 6 | } from '@aws-sdk/client-opensearch'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class OpenSearchClientPolicyCollector extends BasePolicyCollector { 10 | private client: OpenSearchClient; 11 | 12 | constructor(clientConfig?: OpenSearchClientConfig) { 13 | super({ serviceName: 'opensearch' }); 14 | this.client = new OpenSearchClient(clientConfig || {}); 15 | } 16 | 17 | private async listDomainNames() { 18 | return this.client.send(new ListDomainNamesCommand({})); 19 | } 20 | 21 | private async describeDomain(domainName: string) { 22 | return this.client.send( 23 | new DescribeDomainCommand({ DomainName: domainName }), 24 | ); 25 | } 26 | 27 | public async run(): Promise { 28 | const result: ServicePoliciesResult = { 29 | serviceName: this.serviceName, 30 | resources: [], 31 | }; 32 | try { 33 | const domainNames = await this.listDomainNames(); 34 | for (const d of domainNames.DomainNames || []) { 35 | const response = await this.describeDomain(d.DomainName!); 36 | if (!response.DomainStatus?.AccessPolicies) continue; 37 | result.resources.push({ 38 | type: 'AWS::OpenSearchService::Domain', 39 | id: d.DomainName!, 40 | policy: response.DomainStatus.AccessPolicies, 41 | }); 42 | } 43 | } catch (err) { 44 | result.error = JSON.stringify(err); 45 | } 46 | return result; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ram/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RAMClient, 3 | RAMClientConfig, 4 | paginateGetResourceShareAssociations, 5 | } from '@aws-sdk/client-ram'; 6 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 7 | 8 | export class RamPolicyCollector extends BasePolicyCollector { 9 | private client: RAMClient; 10 | 11 | constructor(clientConfig?: RAMClientConfig) { 12 | super({ serviceName: 'ram' }); 13 | this.client = new RAMClient(clientConfig || {}); 14 | } 15 | 16 | private async getResouceShareAssociations() { 17 | const resourceShareAssociations = []; 18 | const paginator = paginateGetResourceShareAssociations( 19 | { client: this.client }, 20 | { 21 | associationType: 'PRINCIPAL', 22 | }, 23 | ); 24 | for await (const page of paginator) { 25 | resourceShareAssociations.push(...(page.resourceShareAssociations || [])); 26 | } 27 | return resourceShareAssociations; 28 | } 29 | 30 | public async run(): Promise { 31 | const result: ServicePoliciesResult = { 32 | serviceName: this.serviceName, 33 | resources: [], 34 | }; 35 | try { 36 | const resourceShareAssociations = 37 | await this.getResouceShareAssociations(); 38 | for (const r of resourceShareAssociations) { 39 | if (!r.associatedEntity) continue; 40 | result.resources.push({ 41 | type: 'RAM::ResourceShareAssociation', // Note there is no-such CF resource type 42 | id: r.resourceShareName!, 43 | policy: r.associatedEntity, // Note this is an arn, NOT a JSON policy 44 | }); 45 | } 46 | } catch (err) { 47 | result.error = JSON.stringify(err); 48 | } 49 | return result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/s3/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | S3Client, 3 | ListBucketsCommand, 4 | GetBucketPolicyCommand, 5 | GetBucketLocationCommand, 6 | S3ClientConfig, 7 | } from '@aws-sdk/client-s3'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class S3PolicyCollector extends BasePolicyCollector { 11 | private client: S3Client; 12 | 13 | constructor(clientConfig?: S3ClientConfig) { 14 | super({ serviceName: 's3' }); 15 | this.client = new S3Client(clientConfig || {}); 16 | } 17 | 18 | private async listBuckets() { 19 | return this.client.send(new ListBucketsCommand({})); 20 | } 21 | 22 | private async getBucketLocation(bucketName: string) { 23 | return this.client.send( 24 | new GetBucketLocationCommand({ 25 | Bucket: bucketName, 26 | }), 27 | ); 28 | } 29 | 30 | private async getBucketPolicy(bucketName: string, region: string) { 31 | // Region-specific client 32 | const client = new S3Client({ region }); 33 | return client.send( 34 | new GetBucketPolicyCommand({ 35 | Bucket: bucketName, 36 | }), 37 | ); 38 | } 39 | 40 | public async run(): Promise { 41 | const result: ServicePoliciesResult = { 42 | serviceName: this.serviceName, 43 | resources: [], 44 | }; 45 | try { 46 | this.log('Listing buckets'); 47 | const buckets = await this.listBuckets(); 48 | this.log(`- got ${(buckets.Buckets || []).length} buckets`); 49 | for (const b of buckets.Buckets || []) { 50 | if (!b.Name) continue; 51 | let getBucketPolicyResponse; 52 | try { 53 | this.log(`Getting bucket location for bucket '${b.Name}'`); 54 | const bucketLocationResponse = await this.getBucketLocation(b.Name); 55 | this.log(`Getting bucket policy for bucket '${b.Name}'`); 56 | getBucketPolicyResponse = await this.getBucketPolicy( 57 | b.Name, 58 | bucketLocationResponse.LocationConstraint || 'us-east-1', 59 | ); 60 | } catch (err) { 61 | if ((err as Error).name === 'NoSuchBucketPolicy') continue; 62 | const errStringified = JSON.stringify(err); 63 | this.log(errStringified); 64 | result.resources.push({ 65 | type: 'AWS::S3::Bucket', 66 | id: b.Name, 67 | policy: '', 68 | error: errStringified, 69 | }); 70 | continue; 71 | } 72 | if (!getBucketPolicyResponse.Policy) continue; 73 | result.resources.push({ 74 | type: 'AWS::S3::Bucket', 75 | id: b.Name, 76 | policy: getBucketPolicyResponse.Policy, 77 | }); 78 | } 79 | } catch (err) { 80 | const errStringified = JSON.stringify(err); 81 | this.log(errStringified); 82 | result.error = errStringified; 83 | } 84 | return result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetResourcePolicyCommand, 3 | NotFoundException, 4 | paginateListRegistries, 5 | RegistrySummary, 6 | SchemasClient, 7 | SchemasClientConfig, 8 | } from '@aws-sdk/client-schemas'; 9 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 10 | 11 | export class EventBridgeSchemasPolicyCollector extends BasePolicyCollector { 12 | private client: SchemasClient; 13 | 14 | constructor(clientConfig?: SchemasClientConfig) { 15 | super({ serviceName: 'schemas' }); 16 | this.client = new SchemasClient(clientConfig || {}); 17 | } 18 | 19 | private async listRegistries(): Promise { 20 | const paginator = paginateListRegistries({ client: this.client }, {}); 21 | const registries = []; 22 | for await (const page of paginator) { 23 | registries.push(...(page.Registries || [])); 24 | } 25 | return registries; 26 | } 27 | 28 | private async getResourcePolicy(registryName: string) { 29 | return this.client.send( 30 | new GetResourcePolicyCommand({ RegistryName: registryName }), 31 | ); 32 | } 33 | 34 | public async run(): Promise { 35 | const result: ServicePoliciesResult = { 36 | serviceName: this.serviceName, 37 | resources: [], 38 | }; 39 | try { 40 | const registries = await this.listRegistries(); 41 | for (const r of registries) { 42 | // NOTE: Can not read policies of AWS managed registries 43 | if (r.RegistryName!.startsWith('aws.')) continue; 44 | let response; 45 | try { 46 | response = await this.getResourcePolicy(r.RegistryName!); 47 | } catch (err) { 48 | if (err instanceof NotFoundException) { 49 | continue; 50 | } 51 | throw err; 52 | } 53 | if (!response.Policy) continue; 54 | result.resources.push({ 55 | type: 'AWS::EventSchemas::Registry', 56 | id: r.RegistryName!, 57 | policy: response.Policy as string, 58 | }); 59 | } 60 | } catch (err) { 61 | result.error = JSON.stringify(err); 62 | } 63 | return result; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/secretsmanager/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SecretsManagerClient, 3 | GetResourcePolicyCommand, 4 | SecretListEntry, 5 | paginateListSecrets, 6 | SecretsManagerClientConfig, 7 | } from '@aws-sdk/client-secrets-manager'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class SecretsManagerPolicyCollector extends BasePolicyCollector { 11 | private client: SecretsManagerClient; 12 | 13 | constructor(clientConfig?: SecretsManagerClientConfig) { 14 | super({ serviceName: 'secretsmanager' }); 15 | this.client = new SecretsManagerClient(clientConfig || {}); 16 | } 17 | 18 | private async listSecrets(): Promise { 19 | const paginator = paginateListSecrets({ client: this.client }, {}); 20 | const secrets = []; 21 | for await (const page of paginator) { 22 | secrets.push(...(page.SecretList || [])); 23 | } 24 | return secrets; 25 | } 26 | 27 | private async getResourcePolicy(secretName: string) { 28 | return this.client.send( 29 | new GetResourcePolicyCommand({ 30 | SecretId: secretName, 31 | }), 32 | ); 33 | } 34 | 35 | public async run(): Promise { 36 | const result: ServicePoliciesResult = { 37 | serviceName: this.serviceName, 38 | resources: [], 39 | }; 40 | try { 41 | const secrets = await this.listSecrets(); 42 | for (const s of secrets) { 43 | const response = await this.getResourcePolicy(s.Name!); 44 | if (!response.ResourcePolicy) continue; 45 | result.resources.push({ 46 | type: 'AWS::SecretsManager::Secret', 47 | id: s.Name!, 48 | policy: response.ResourcePolicy, 49 | }); 50 | } 51 | } catch (err) { 52 | result.error = JSON.stringify(err); 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/serverless/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApplicationSummary, 3 | GetApplicationPolicyCommand, 4 | paginateListApplications, 5 | ServerlessApplicationRepositoryClient, 6 | ServerlessApplicationRepositoryClientConfig, 7 | } from '@aws-sdk/client-serverlessapplicationrepository'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class ServerlessApplicationRepositoryPolicyCollector extends BasePolicyCollector { 11 | private client: ServerlessApplicationRepositoryClient; 12 | 13 | constructor(clientConfig?: ServerlessApplicationRepositoryClientConfig) { 14 | super({ serviceName: 'serverless' }); 15 | this.client = new ServerlessApplicationRepositoryClient(clientConfig || {}); 16 | } 17 | 18 | private async listApplications(): Promise { 19 | const paginator = paginateListApplications({ client: this.client }, {}); 20 | const applications = []; 21 | for await (const page of paginator) { 22 | applications.push(...(page.Applications || [])); 23 | } 24 | return applications; 25 | } 26 | 27 | private async getApplicationPolicy(applicationId: string) { 28 | return this.client.send( 29 | new GetApplicationPolicyCommand({ 30 | ApplicationId: applicationId, 31 | }), 32 | ); 33 | } 34 | 35 | public async run(): Promise { 36 | const result: ServicePoliciesResult = { 37 | serviceName: this.serviceName, 38 | resources: [], 39 | }; 40 | try { 41 | const applications = await this.listApplications(); 42 | for (const a of applications) { 43 | const response = await this.getApplicationPolicy(a.ApplicationId!); 44 | if (!response.Statements) continue; 45 | result.resources.push({ 46 | type: 'AWS::Serverless::Application', 47 | id: a.ApplicationId!, 48 | policy: JSON.stringify(response.Statements), 49 | }); 50 | } 51 | } catch (err) { 52 | result.error = JSON.stringify(err); 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/sns/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetTopicAttributesCommand, 3 | paginateListTopics, 4 | SNSClient, 5 | SNSClientConfig, 6 | Topic, 7 | } from '@aws-sdk/client-sns'; 8 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 9 | 10 | export class SnsPolicyCollector extends BasePolicyCollector { 11 | private client: SNSClient; 12 | 13 | constructor(clientConfig?: SNSClientConfig) { 14 | super({ serviceName: 'sns' }); 15 | this.client = new SNSClient(clientConfig || {}); 16 | } 17 | 18 | private async listTopics(): Promise { 19 | const paginator = paginateListTopics({ client: this.client }, {}); 20 | const topics = []; 21 | for await (const page of paginator) { 22 | topics.push(...(page.Topics || [])); 23 | } 24 | return topics; 25 | } 26 | 27 | private async getTopicAttributes(topicArn: string) { 28 | return this.client.send( 29 | new GetTopicAttributesCommand({ 30 | TopicArn: topicArn, 31 | }), 32 | ); 33 | } 34 | 35 | public async run(): Promise { 36 | const result: ServicePoliciesResult = { 37 | serviceName: this.serviceName, 38 | resources: [], 39 | }; 40 | try { 41 | const topics = await this.listTopics(); 42 | for (const topic of topics) { 43 | const response = await this.getTopicAttributes(topic.TopicArn!); 44 | if (!response.Attributes?.Policy) continue; 45 | result.resources.push({ 46 | type: 'AWS::SNS::Topic', 47 | id: topic.TopicArn!, 48 | policy: response.Attributes.Policy, 49 | }); 50 | } 51 | } catch (err) { 52 | result.error = JSON.stringify(err); 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/sqs/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SQSClient, 3 | GetQueueAttributesCommand, 4 | paginateListQueues, 5 | SQSClientConfig, 6 | } from '@aws-sdk/client-sqs'; 7 | import { BasePolicyCollector, ServicePoliciesResult } from '../core'; 8 | 9 | export class SqsPolicyCollector extends BasePolicyCollector { 10 | private client: SQSClient; 11 | 12 | constructor(clientConfig?: SQSClientConfig) { 13 | super({ serviceName: 'sqs' }); 14 | this.client = new SQSClient(clientConfig || {}); 15 | } 16 | 17 | private async listQueues(): Promise { 18 | const paginator = paginateListQueues({ client: this.client }, {}); 19 | const queuesUrls = []; 20 | for await (const page of paginator) { 21 | queuesUrls.push(...(page.QueueUrls || [])); 22 | } 23 | return queuesUrls; 24 | } 25 | 26 | private async getQueueAttributes(queueUrl: string) { 27 | return this.client.send( 28 | new GetQueueAttributesCommand({ 29 | QueueUrl: queueUrl, 30 | AttributeNames: ['Policy'], 31 | }), 32 | ); 33 | } 34 | 35 | public async run(): Promise { 36 | const result: ServicePoliciesResult = { 37 | serviceName: this.serviceName, 38 | resources: [], 39 | }; 40 | try { 41 | const queueUrls = await this.listQueues(); 42 | for (const queueUrl of queueUrls) { 43 | const response = await this.getQueueAttributes(queueUrl); 44 | if (!response.Attributes?.Policy) continue; 45 | result.resources.push({ 46 | type: 'AWS::SQS::Queue', 47 | id: queueUrl, 48 | policy: response.Attributes.Policy, 49 | }); 50 | } 51 | } catch (err) { 52 | result.error = JSON.stringify(err); 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "declaration": true, 10 | "strict": true 11 | }, 12 | "lib": ["es2018", "esnext.asynciterable"] 13 | } --------------------------------------------------------------------------------