diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7ee5e39 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.ts] +indent_size = 2 diff --git a/.githooks/pre-commit b/.githooks/pre-commit index dfb1ecd..23efff5 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -42,6 +42,11 @@ if command_exists terraform-docs; then done fi +if command_exists npm; then + npm run codegen + files="$files $script_dir/../iam-policies/terraform/policies" +fi + if command_exists ./node_modules/.bin/prettier; then echo "$files" | xargs ./node_modules/.bin/prettier --ignore-unknown --write fi diff --git a/.github/actions/collect-meta/action.yaml b/.github/actions/collect-meta/action.yaml new file mode 100644 index 0000000..b35178a --- /dev/null +++ b/.github/actions/collect-meta/action.yaml @@ -0,0 +1,29 @@ +name: Collect metadata about the repository +description: > + Looks for modules and examples in the repository and outputs their paths. + +outputs: + tf-modules: + description: Paths to the Terraform modules found in the repository + value: ${{ steps.find-modules.outputs.tf-modules }} + + tf-examples: + description: Paths to the Terraform examples found in the repository + value: ${{ steps.find-examples.outputs.tf-examples }} + +runs: + using: composite + steps: + - name: Find modules + id: find-modules + run: | + tf_modules=$(./.github/scripts/collect-modules.sh | jq -cnR '[inputs]') + echo "tf-modules=$tf_modules" > "$GITHUB_OUTPUT" + shell: bash + + - name: Find examples + id: find-examples + run: | + tf_examples=$(./.github/scripts/collect-examples.sh | jq -cnR '[inputs]') + echo "tf-examples=$tf_examples" > "$GITHUB_OUTPUT" + shell: bash diff --git a/.github/actions/collect-modules/action.yaml b/.github/actions/collect-modules/action.yaml deleted file mode 100644 index f82d2dd..0000000 --- a/.github/actions/collect-modules/action.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Find modules in the repository -description: > - Looks for modules in the repository and outputs their paths. - -outputs: - tf-modules: - description: Paths to the Terraform modules found in the repository - value: ${{ steps.find-modules.outputs.tf-modules }} - -runs: - using: composite - steps: - - name: Find modules - id: find-modules - run: | - tf_modules=$(./.github/scripts/collect-modules.sh) - echo tf-modules=$(printf '%s\n' "${tf_modules[@]}" | jq -cnR '[inputs]') > $GITHUB_OUTPUT - shell: bash diff --git a/.github/scripts/collect-examples.sh b/.github/scripts/collect-examples.sh new file mode 100755 index 0000000..df25815 --- /dev/null +++ b/.github/scripts/collect-examples.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +git ls-files --cached --others --exclude-standard \ + | grep '/examples/' \ + | xargs -I{} dirname {} \ + | sort -u diff --git a/.github/scripts/collect-modules.sh b/.github/scripts/collect-modules.sh index c8c52bb..97efc01 100755 --- a/.github/scripts/collect-modules.sh +++ b/.github/scripts/collect-modules.sh @@ -10,7 +10,6 @@ while IFS= read -r -d '' module_cfg; do exit 1 ;; terraform) - echo "Found Terraform module in $module_cfg" >&2 tf_modules+=("$(dirname "$module_cfg")") ;; esac diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38dcce5..0a3417d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,16 +6,16 @@ on: pull_request: jobs: - collect-modules: + meta: runs-on: ubuntu-latest outputs: - tf-modules: ${{ steps.collect-modules.outputs.tf-modules }} + tf-modules: ${{ steps.meta.outputs.tf-modules }} + tf-examples: ${{ steps.meta.outputs.tf-examples }} steps: - uses: actions/checkout@v4 - - - uses: ./.github/actions/collect-modules - id: collect-modules + - uses: ./.github/actions/collect-meta + id: meta typos: runs-on: ubuntu-latest @@ -37,13 +37,11 @@ jobs: terraform-validate: runs-on: ubuntu-latest + needs: [meta] strategy: matrix: - project: - - asset-account/terraform/stack-set/examples/self-managed - - asset-account/terraform/stack-set/examples/service-managed - - connector/terraform/examples/basic + terraform_example: ${{fromJson(needs.meta.outputs.tf-modules)}} steps: - uses: actions/checkout@v4 @@ -54,19 +52,18 @@ jobs: terraform_wrapper: false - run: terraform init -input=false - working-directory: ${{ matrix.project }} + working-directory: ${{ matrix.terraform_example }} - run: terraform validate - working-directory: ${{ matrix.project }} + working-directory: ${{ matrix.terraform_example }} terraform-docs: runs-on: ubuntu-latest - needs: - - collect-modules + needs: [meta] strategy: matrix: - terraform_module: ${{fromJson(needs.collect-modules.outputs.tf-modules)}} + terraform_module: ${{fromJson(needs.meta.outputs.tf-modules)}} steps: - uses: actions/checkout@v4 @@ -129,3 +126,21 @@ jobs: cache: "npm" - run: npm ci --ignore-scripts - run: npx prettier --check . + + codegen-freshness: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - run: npm ci --ignore-scripts + - run: cd codegen && npm ci --ignore-scripts + - run: npm run codegen + + - run: >- + git diff --exit-code --color=always || ( echo "Generated code is + out-of-date. See the diff above." && exit 1 ) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 953282a..1be5ec7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,26 +4,26 @@ on: workflow_dispatch: jobs: - collect-modules: + meta: runs-on: ubuntu-latest outputs: - tf-modules: ${{ steps.collect-modules.outputs.tf-modules }} + tf-modules: ${{ steps.meta.outputs.tf-modules }} + tf-examples: ${{ steps.meta.outputs.tf-examples }} steps: - uses: actions/checkout@v4 - - - uses: ./.github/actions/collect-modules - id: collect-modules + - uses: ./.github/actions/collect-meta + id: meta release-module: runs-on: ubuntu-latest - needs: ["collect-modules"] + needs: [meta] permissions: contents: write actions: write strategy: matrix: - module: ${{fromJson(needs.collect-modules.outputs.tf-modules)}} + module: ${{fromJson(needs.meta.outputs.tf-modules)}} fail-fast: false steps: diff --git a/asset-account/terraform/stack-set/examples/self-managed/admin.tf b/asset-account/terraform/stack-set/examples/self-managed/admin.tf index fde1c27..cca3aa2 100644 --- a/asset-account/terraform/stack-set/examples/self-managed/admin.tf +++ b/asset-account/terraform/stack-set/examples/self-managed/admin.tf @@ -7,7 +7,7 @@ module "elastio_asset_account" { depends_on = [ # Needs to wait for the execution role in the asset account to be fully created - aws_iam_role_policy.execution_deployment, + aws_iam_role_policy_attachment.execution_deployment, # Needs to wait for the admin role in the admin account to be fully created aws_iam_role_policy.admin_execution, diff --git a/asset-account/terraform/stack-set/examples/self-managed/asset.tf b/asset-account/terraform/stack-set/examples/self-managed/asset.tf index baff615..960ee5e 100644 --- a/asset-account/terraform/stack-set/examples/self-managed/asset.tf +++ b/asset-account/terraform/stack-set/examples/self-managed/asset.tf @@ -18,18 +18,16 @@ data "aws_iam_policy_document" "execution_trust" { } # Specifies the set of permissions required for the deployment of the Cloudfomation stack -data "aws_iam_policy_document" "execution_deployment" { - statement { - actions = ["*"] - effect = "Allow" - resources = ["*"] - } +module "elastio_policies" { + # Use this module from the Cloudsmith registry via the URL in real code: + # source = "terraform.cloudsmith.io/public/elastio-iam-policies/aws" + source = "../../../../../iam-policies/terraform" + policies = ["ElastioAssetAccountDeployer"] } -resource "aws_iam_role_policy" "execution_deployment" { +resource "aws_iam_role_policy_attachment" "execution_deployment" { provider = aws.asset - name = "Deployment" - policy = data.aws_iam_policy_document.execution_deployment.json - role = aws_iam_role.execution.name + policy_arn = module.elastio_policies.policies.ElastioAssetAccountDeployer.arn + role = aws_iam_role.execution.name } diff --git a/codegen/package-lock.json b/codegen/package-lock.json new file mode 100644 index 0000000..53f9a46 --- /dev/null +++ b/codegen/package-lock.json @@ -0,0 +1,551 @@ +{ + "name": "codegen", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "aws-iam-policy-types": "^1.0.2" + }, + "devDependencies": { + "@types/node": "^22.13.13", + "tsx": "^4.19.3", + "typescript": "^5.8.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.13.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", + "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/aws-iam-policy-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/aws-iam-policy-types/-/aws-iam-policy-types-1.0.2.tgz", + "integrity": "sha512-vdkTEqWn9WLINbb/jLsi7Ra1MvVFThuP5qanfKF+rJ79J8GPnAXnuoNKusXm62NZ84VyczQ0GkiPVD8YlvghvQ==", + "bin": { + "aws-iam-policy-types": "dist/cjs/cli/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + } + } +} diff --git a/codegen/package.json b/codegen/package.json new file mode 100644 index 0000000..dd6500f --- /dev/null +++ b/codegen/package.json @@ -0,0 +1,14 @@ +{ + "type": "module", + "scripts": { + "start": "tsc --noEmit && tsx ./src/main.ts" + }, + "devDependencies": { + "@types/node": "^22.13.13", + "tsx": "^4.19.3", + "typescript": "^5.8.2" + }, + "dependencies": { + "aws-iam-policy-types": "^1.0.2" + } +} diff --git a/codegen/src/iam.ts b/codegen/src/iam.ts new file mode 100644 index 0000000..6f882ad --- /dev/null +++ b/codegen/src/iam.ts @@ -0,0 +1,84 @@ +import type * as iam from "aws-iam-policy-types"; + +/** + * The name of the policy is the PascalCased version of the policy file name + * with the `Elastio` prefix. + */ +export interface Policy { + description: string; + statements: PolicyStatement[]; +} + +interface PolicyStatement { + /** + * If not specified then `Allow` is assumed. + */ + Effect?: "Deny"; + Action: Action | Action[]; + Principal?: Principal; + Resource: string | string[]; + Condition?: Record; + + /** + * Statement ID usually used as a description of the statement. + */ + Sid?: string; +} + +type Principal = + | "*" + | { + AWS: string | string[]; + } + | { + Federated: string | string[]; + } + | { + Service: string | string[]; + }; + +type Action = + | "cloudformation:*" + | `${iam.AwsCloudformationActions}` + | "logs:*" + | `${iam.AwsLogsActions}` + | "iam:*" + | `${iam.AwsIamActions}` + | "lambda:*" + | `${iam.AwsLambdaActions}` + | "s3:*" + | `${iam.AwsS3Actions}` + | "ssm:*" + | `${iam.AwsSsmActions}`; + +type KnownTag = + // A simple tag that customers can add to their resource for Elastio to + // get access to it. It's first use case at the time of this writing is + // autorizing Elastio access to KMS keys customers use to encrypt their data. + // + // This tag can currently be set to a value like an empty string or `true`. + // However, we may reserve the right to endow special values for this tag + // with specific behavior. Think of `elastio:authorize=read` or + // `elastio:authorize=write` giving different level of access to customer's + // resources. Although today we don't need this because we only read customer's + // data and don't modify it. + | "elastio:authorize" + + // Set on every resource deployed by Elastio + | "elastio:resource"; + +export function hasResourceTag(tag: KnownTag) { + return hasTags("aws:ResourceTag", tag); +} + +export function hasRequestTag(tag: KnownTag) { + return hasTags("aws:RequestTag", tag); +} + +function hasTags(kind: string, tag: KnownTag) { + return { + StringLike: { + [`${kind}/${tag}`]: "*", + }, + }; +} diff --git a/codegen/src/main.ts b/codegen/src/main.ts new file mode 100644 index 0000000..01161a4 --- /dev/null +++ b/codegen/src/main.ts @@ -0,0 +1,89 @@ +/** + * This script generates IAM policies from TypeScript files in the `policies` directory. + * We use TypeScript for this because it's easier to maintain shared code for policies + * this way and we also get nice compile-time checks from TypeScript for the IAM actions + * used in the policies. This codegen logic may also be used to support other kinds of + * orchestration tools like CloudFormation, CDK, etc. + */ + +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Policy } from "./iam"; + +const scriptPath = fileURLToPath(import.meta.url); +const scriptDir = path.dirname(scriptPath); +const iamPoliciesTfModulePath = path.join( + path.join(scriptDir, "../../iam-policies/terraform"), +); + +async function writePolicy(policyName: string, policy: Policy) { + const policyDocument = { + Version: "2012-10-17", + Statement: policy.statements.map((statement) => ({ + ...statement, + Effect: statement.Effect ?? "Allow", + })), + }; + + const policyDefinition = { + Description: policy.description, + PolicyDocument: policyDocument, + }; + + const policyDocumentJson = JSON.stringify(policyDefinition, null, 2); + + const policyOutputPath = path.join( + iamPoliciesTfModulePath, + "policies", + `${policyName}.json`, + ); + + await fs.writeFile(policyOutputPath, policyDocumentJson); +} + +async function main() { + const policiesDir = path.join(scriptDir, "policies"); + const policyFiles = await fs.readdir(policiesDir); + const policyNames = policyFiles.map((file) => path.basename(file, ".ts")); + + const policies = await Promise.all( + policyNames.map(async (policyName) => { + const policyPath = path.join(policiesDir, `${policyName}.ts`); + const module: { default: Policy } = await import(policyPath); + const policy = module.default; + + await writePolicy(policyName, policy); + + return [policyName, policy] as const; + }), + ); + + const policiesMdTable = policies + .map(([policyName, policy]) => { + const name = `[\`${policyName}\`][${policyName}]`; + return `| ${name} | ${policy.description} |`; + }) + .join("\n"); + + const links = policies + .map( + ([policyName]) => + `[${policyName}]: ../../codegen/src/policies/${policyName}.ts`, + ) + .join("\n"); + + const readmePath = path.join(iamPoliciesTfModulePath, "README.md"); + const readme = await fs.readFile(readmePath, "utf-8"); + + const docs = `| Policy | Description |\n| --- | --- |\n${policiesMdTable}\n\n${links}`; + + const policiesDocs = readme.replace( + /(.*)/s, + `\n${docs}\n`, + ); + + await fs.writeFile(readmePath, policiesDocs); +} + +main(); diff --git a/codegen/src/policies/ElastioAssetAccountDeployer.ts b/codegen/src/policies/ElastioAssetAccountDeployer.ts new file mode 100644 index 0000000..08db12e --- /dev/null +++ b/codegen/src/policies/ElastioAssetAccountDeployer.ts @@ -0,0 +1,105 @@ +import * as iam from "../iam"; + +/** + * Use the following command to discover what resource types are deployed by + * Elastio Asset Account stack: + * + * ```bash + * aws cloudformation list-stack-resources --stack-name {stack_name} \ + * | jq '.StackResourceSummaries | map(.ResourceType) | unique' + * ``` + * + * This policy is designed to be used for the StackSet execution role: + * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html + */ +export default { + description: "Permissions required to deploy the Elastio Asset Account stack", + statements: [ + { + Action: ["lambda:*", "cloudformation:*", "logs:*", "ssm:*"], + Resource: "*", + }, + { + Sid: "ElastioIamRead", + Action: [ + "iam:GetRole", + "iam:GetRolePolicy", + + "iam:ListAttachedRolePolicies", + "iam:ListRolePolicies", + "iam:ListRoles", + "iam:ListPolicyVersions", + + "iam:ListRoleTags", + "iam:ListPolicyTags", + ], + Resource: "*", + }, + { + Sid: "ElastioIamCreate", + Action: ["iam:CreateRole", "iam:CreatePolicy"], + Resource: "*", + Condition: iam.hasRequestTag("elastio:resource"), + }, + { + Sid: "ElastioIamUpdate", + Action: [ + // Roles + "iam:UpdateRole", + "iam:UpdateAssumeRolePolicy", + "iam:UpdateRoleDescription", + + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + + "iam:PutRolePermissionsBoundary", + "iam:DeleteRolePermissionsBoundary", + + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + + "iam:TagRole", + "iam:UntagRole", + + // Managed Policies + "iam:CreatePolicyVersion", + "iam:DeletePolicyVersion", + + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:SetDefaultPolicyVersion", + + "iam:TagPolicy", + "iam:UntagPolicy", + ], + Resource: "*", + Condition: iam.hasResourceTag("elastio:resource"), + }, + { + Sid: "ElastioIamDelete", + Action: ["iam:DeleteRole", "iam:DeletePolicy"], + + // A name wildcard is required here because if Cloudformation tries to delete + // a non-existing resource with a Condition based on `elastio:resource` tag, + // then it'll get a 403 AccessDenied error which it doesn't handle properly. + // It stops the stack deletion process in a DELETE_FAILED state: + // + // ``` + // "User: arn:aws:sts::{account}:assumed-role/AWSCloudFormationStackSetExecutionRole/{session} + // is not authorized to perform: iam:DeleteRole on resource: + // role ElastioAssetAccountCfnDeploymentNotifier because no identity-based + // policy allows the iam:DeleteRole action (Service: Iam, Status Code: 403... + // ``` + Resource: [ + "arn:*:iam::*:role/*Elastio*", + "arn:*:iam::*:policy/*Elastio*", + ], + }, + { + Sid: "ElastioIamPassRole", + // PassRole doesn't support tag-based conditions + Action: "iam:PassRole", + Resource: ["arn:*:iam::*:role/*Elastio*"], + }, + ], +} satisfies iam.Policy; diff --git a/codegen/tsconfig.json b/codegen/tsconfig.json new file mode 100644 index 0000000..c793b3d --- /dev/null +++ b/codegen/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es2023", + "module": "Preserve", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/iam-policies/terraform/.module.toml b/iam-policies/terraform/.module.toml new file mode 100644 index 0000000..74d735b --- /dev/null +++ b/iam-policies/terraform/.module.toml @@ -0,0 +1,5 @@ +[module] +name = "aws-elastio-iam-policies" +description = "A collection of AWS IAM policies for use with Elastio" +type = "terraform" +version = "0.33.0" diff --git a/iam-policies/terraform/README.md b/iam-policies/terraform/README.md new file mode 100644 index 0000000..262b463 --- /dev/null +++ b/iam-policies/terraform/README.md @@ -0,0 +1,78 @@ +# `elastio-iam-policies` module + +This Terraform module deploys additional Elastio IAM managed policies that you can use for managing Elastio stacks. + +## Installation + +[Configure](../../README.md#configuring-the-terraform-modules-registry) the Elastio terraform module registry, and add this to your project: + +```tf +module "elastio_policies" { + source = "terraform.cloudsmith.io/public/elastio-iam-policies/aws" + version = "0.33.1" + + // Provide input parameters +} +``` + +## Usage + +Specify the set of names of policies from the list of [available policies](#available-policies) that you want to deploy as a `policies` input to the module. + +The policies are generated using TypeScript. Their final JSON output is stored as `policies/{PolicyName}.json` documents in this module's directory. You can see the original policy source code with comments about the reasoning for some IAM permissions if you click on the policy names in the table below. + +See also the basic [usage example](./examples/basic/). + +## Available Policies + + + +| Policy | Description | +| ------------------------------------------------------------ | -------------------------------------------------------------- | +| [`ElastioAssetAccountDeployer`][ElastioAssetAccountDeployer] | Permissions required to deploy the Elastio Asset Account stack | + +[ElastioAssetAccountDeployer]: ../../codegen/src/policies/ElastioAssetAccountDeployer.ts + + + + + +## Requirements + +| Name | Version | +| ------------------------------------------------------------------------ | ------- | +| [terraform](#requirement_terraform) | ~> 1.0 | +| [aws](#requirement_aws) | ~> 5.0 | + +## Providers + +| Name | Version | +| ------------------------------------------------ | ------- | +| [aws](#provider_aws) | ~> 5.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +| ------------------------------------------------------------------------------------------------------------- | -------- | +| [aws_iam_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------- | ------- | :------: | +| [name_prefix](#input_name_prefix) | A prefix to apply to all resources created by this stack | `string` | `""` | no | +| [name_suffix](#input_name_suffix) | A suffix to apply to all resources created by this stack | `string` | `""` | no | +| [policies](#input_policies) | A set of names of Elastio IAM policies to create. See the available policies
in the README of the module. | `set(string)` | n/a | yes | +| [tags](#input_tags) | Additional tags to apply to all resources created by this stack. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +| ----------------------------------------------------------- | -------------------------------------------------------------- | +| [policies](#output_policies) | A map of the created Elastio IAM policies keyed by their names | + + diff --git a/iam-policies/terraform/examples/basic/.terraform.lock.hcl b/iam-policies/terraform/examples/basic/.terraform.lock.hcl new file mode 100644 index 0000000..7573cdb --- /dev/null +++ b/iam-policies/terraform/examples/basic/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.92.0" + constraints = "~> 5.0" + hashes = [ + "h1:ZnpTxMfg5PThZc5WZCsZELinsR0gPhdTpNmXjVcf7aE=", + "zh:1d3a0b40831360e8e988aee74a9ff3d69d95cb541c2eae5cb843c64303a091ba", + "zh:3d29cbced6c708be2041a708d25c7c0fc22d09e4d0b174360ed113bfae786137", + "zh:4341a203cf5820a0ca18bb514ae10a6c113bc6a728fb432acbf817d232e8eff4", + "zh:4a49e2d91e4d92b6b93ccbcbdcfa2d67935ce62e33b939656766bb81b3fd9a2c", + "zh:54c7189358b37fd895dedbabf84e509c1980a8c404a1ee5b29b06e40497b8655", + "zh:5d8bb1ff089c37cb65c83b4647f1981fded993e87d8132915d92d79f29e2fcd8", + "zh:618f2eb87cd65b245aefba03991ad714a51ff3b841016ef68e2da2b85d0b2325", + "zh:7bce07bc542d0588ca42bac5098dd4f8af715417cd30166b4fb97cedd44ab109", + "zh:81419eab2d8810beb114b1ff5cbb592d21edc21b809dc12bb066e4b88fdd184a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9dea39d4748eeeebe2e76ca59bca4ccd161c2687050878c47289a98407a23372", + "zh:d692fc33b67ac89e916c8f9233d39eacab8c438fe10172990ee9d94fba5ca372", + "zh:d9075c7da48947c029ba47d5985e1e8e3bf92367bfee8ca1ff0e747765e779a1", + "zh:e81c62db317f3b640b2e04eba0ada8aa606bcbae0152c09f6242e86b86ef5889", + "zh:f68562e073722c378d2f3529eb80ad463f12c44aa5523d558ae3b69f4de5ca1f", + ] +} diff --git a/iam-policies/terraform/examples/basic/main.tf b/iam-policies/terraform/examples/basic/main.tf new file mode 100644 index 0000000..504c3b8 --- /dev/null +++ b/iam-policies/terraform/examples/basic/main.tf @@ -0,0 +1,6 @@ + +module "elastio_policies" { + source = "../../" + + policies = ["ElastioAssetAccountDeployer"] +} diff --git a/iam-policies/terraform/examples/basic/versions.tf b/iam-policies/terraform/examples/basic/versions.tf new file mode 100644 index 0000000..ab789a3 --- /dev/null +++ b/iam-policies/terraform/examples/basic/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = "~> 1.0" +} diff --git a/iam-policies/terraform/main.tf b/iam-policies/terraform/main.tf new file mode 100644 index 0000000..73db8d8 --- /dev/null +++ b/iam-policies/terraform/main.tf @@ -0,0 +1,16 @@ +locals { + policies = { + for policy in var.policies : + policy => jsondecode(file("${path.module}/policies/${policy}.json")) + } +} + +resource "aws_iam_policy" "this" { + for_each = local.policies + + name = "${var.name_prefix}${each.key}${var.name_suffix}" + description = each.value.Description + policy = jsonencode(each.value.PolicyDocument) + + tags = merge(var.tags, { "elastio:resource" = true }) +} diff --git a/iam-policies/terraform/outputs.tf b/iam-policies/terraform/outputs.tf new file mode 100644 index 0000000..e2eee68 --- /dev/null +++ b/iam-policies/terraform/outputs.tf @@ -0,0 +1,5 @@ +output "policies" { + description = "A map of the created Elastio IAM policies keyed by their names" + + value = aws_iam_policy.this +} diff --git a/iam-policies/terraform/policies/ElastioAssetAccountDeployer.json b/iam-policies/terraform/policies/ElastioAssetAccountDeployer.json new file mode 100644 index 0000000..fcfb778 --- /dev/null +++ b/iam-policies/terraform/policies/ElastioAssetAccountDeployer.json @@ -0,0 +1,84 @@ +{ + "Description": "Permissions required to deploy the Elastio Asset Account stack", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["lambda:*", "cloudformation:*", "logs:*", "ssm:*"], + "Resource": "*", + "Effect": "Allow" + }, + { + "Sid": "ElastioIamRead", + "Action": [ + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListAttachedRolePolicies", + "iam:ListRolePolicies", + "iam:ListRoles", + "iam:ListPolicyVersions", + "iam:ListRoleTags", + "iam:ListPolicyTags" + ], + "Resource": "*", + "Effect": "Allow" + }, + { + "Sid": "ElastioIamCreate", + "Action": ["iam:CreateRole", "iam:CreatePolicy"], + "Resource": "*", + "Condition": { + "StringLike": { + "aws:RequestTag/elastio:resource": "*" + } + }, + "Effect": "Allow" + }, + { + "Sid": "ElastioIamUpdate", + "Action": [ + "iam:UpdateRole", + "iam:UpdateAssumeRolePolicy", + "iam:UpdateRoleDescription", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:PutRolePermissionsBoundary", + "iam:DeleteRolePermissionsBoundary", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:TagRole", + "iam:UntagRole", + "iam:CreatePolicyVersion", + "iam:DeletePolicyVersion", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:SetDefaultPolicyVersion", + "iam:TagPolicy", + "iam:UntagPolicy" + ], + "Resource": "*", + "Condition": { + "StringLike": { + "aws:ResourceTag/elastio:resource": "*" + } + }, + "Effect": "Allow" + }, + { + "Sid": "ElastioIamDelete", + "Action": ["iam:DeleteRole", "iam:DeletePolicy"], + "Resource": [ + "arn:*:iam::*:role/*Elastio*", + "arn:*:iam::*:policy/*Elastio*" + ], + "Effect": "Allow" + }, + { + "Sid": "ElastioIamPassRole", + "Action": "iam:PassRole", + "Resource": ["arn:*:iam::*:role/*Elastio*"], + "Effect": "Allow" + } + ] + } +} diff --git a/iam-policies/terraform/variables.tf b/iam-policies/terraform/variables.tf new file mode 100644 index 0000000..d4aa08c --- /dev/null +++ b/iam-policies/terraform/variables.tf @@ -0,0 +1,62 @@ +######################### +## Required parameters ## +######################### + +variable "policies" { + description = <<-DESCR + A set of names of Elastio IAM policies to create. See the available policies + in the README of the module. + DESCR + + type = set(string) + nullable = false + + validation { + condition = length(var.policies) > 0 + error_message = "At least one policy must be specified." + } + + validation { + condition = length(setsubtract(var.policies, local.available_policies)) == 0 + error_message = <<-ERR + The following policy names are invalid: + ${join(", ", setsubtract(var.policies, local.available_policies))} + ERR + } +} + +locals { + available_policies = [ + for policy in fileset("${path.module}/policies", "*.json") : + substr(policy, 0, length(policy) - length(".json")) + ] +} + +######################### +## Optional parameters ## +######################### + +variable "tags" { + description = <<-DESCR + Additional tags to apply to all resources created by this stack. + DESCR + + type = map(string) + default = {} +} + +variable "name_prefix" { + description = "A prefix to apply to all resources created by this stack" + + type = string + nullable = false + default = "" +} + +variable "name_suffix" { + description = "A suffix to apply to all resources created by this stack" + + type = string + nullable = false + default = "" +} diff --git a/iam-policies/terraform/versions.tf b/iam-policies/terraform/versions.tf new file mode 100644 index 0000000..d50a6bd --- /dev/null +++ b/iam-policies/terraform/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = "~> 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} diff --git a/package.json b/package.json index 76e7bb9..cccd6d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "scripts": { - "fmt": "prettier --write ." + "fmt": "prettier --write .", + "codegen": "cd codegen && npm start && cd .. && npm run format-codegen", + "format-codegen": "prettier --write \"./iam-policies/terraform/{README.md,policies/*.json}\"" }, "devDependencies": { "prettier": "^3.5.3"