diff --git a/.github/workflows/apiserver.yml b/.github/workflows/apiserver.yml deleted file mode 100644 index aaa1a57..0000000 --- a/.github/workflows/apiserver.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: API Server CI/CD - -on: - workflow_dispatch: - inputs: - deploy: - description: "Deploy? Set to true" - required: true - push: - paths: - - .github/workflows/apiserver.yml - - apiserver/** - -jobs: - build: - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "24" - - - name: Install eclint - run: npm install -g eclint - - name: Check EditorConfig compliance - run: eclint check $(git ls-files) apiserver - - - name: Check that we're using system Ruby - run: test "$(which ruby)" = /usr/bin/ruby - - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: apiserver/vendor/bundle - key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} - - - name: Install matching Bundler version - run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document - working-directory: apiserver - - - name: Install gem bundle - run: bundle install - working-directory: apiserver - env: - BUNDLE_DEPLOYMENT: "true" - BUNDLE_PATH: vendor/bundle - BUNDLE_JOBS: 4 - BUNDLE_RETRY: 3 - BUNDLE_WITH: ci - BUNDLE_CLEAN: "true" - - - name: Check code formatting with Rufo - run: bundle exec rufo --check . - working-directory: apiserver - env: - BUNDLE_PATH: vendor/bundle - BUNDLE_WITH: ci - - - name: Detect library dependencies - run: bundle exec debendencies . -o dpkg-dependencies.txt --tee - working-directory: apiserver - env: - BUNDLE_PATH: vendor/bundle - BUNDLE_WITH: ci - - - name: Create tarball - run: > - tar - -c - --use-compress-program 'zstd -T0' - --sort name - --owner root:0 - --group root:0 - --mtime '2024-01-01 00:00Z' - --preserve-permissions - --pax-option exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime - -f apiserver-"$GITHUB_RUN_NUMBER"-$(lsb_release --id --short | tr '[:upper:]' '[:lower:]')-$(lsb_release --release --short)-$(dpkg --print-architecture).tar.zst - -C apiserver - . - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: apiserver - path: "*.tar.zst" - compression-level: 0 - - deploy: - runs-on: ubuntu-24.04 - needs: build - if: github.ref == 'refs/heads/main' || github.event.inputs.deploy == 'true' - environment: deploy - permissions: - contents: write - id-token: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: apiserver - - - name: Create tag - run: git tag -f apiserver-"$GITHUB_RUN_NUMBER" - - - name: Push tag - run: git push origin apiserver-"$GITHUB_RUN_NUMBER" - - - name: Create release - run: > - gh release create - apiserver-"$GITHUB_RUN_NUMBER" - apiserver-*.tar.zst - --title "apiserver v$GITHUB_RUN_NUMBER" - --notes-from-tag - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - id: get-id-token - with: - script: | - const fs = require('fs'); - const token = await core.getIDToken('backend.fullstaqruby.org'); - fs.writeFileSync( - process.env.GITHUB_OUTPUT, - `id_token< - curl -fL --no-progress-meter -X POST -H "Authorization: Bearer $TOKEN" - https://apt.fullstaqruby.org/admin/upgrade_apiserver - env: - TOKEN: ${{ steps.get-id-token.outputs.id_token }} diff --git a/.github/workflows/code-reviews.yml b/.github/workflows/code-reviews.yml deleted file mode 100644 index 7ba1852..0000000 --- a/.github/workflows/code-reviews.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This workflow must be usable with third-party pull requests, -# and thus may not make use of any secrets. - -name: Code reviews - -on: - workflow_dispatch: {} - push: - paths-ignore: - - "**.md" - - .github/workflows/apiserver.yml - - apiserver/** - -jobs: - check: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "24" - - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 - with: - terraform_version: "1.5.7" - - - name: Install eclint - run: npm install -g eclint - - name: Check EditorConfig compliance - run: eclint check $(git ls-files) - - - name: Initialize terraform/ - run: terraform init -backend=false - working-directory: terraform - - name: Validate terraform/ - run: terraform validate - working-directory: terraform - - name: Check formatting of terraform/ - run: terraform fmt -check -diff -recursive - working-directory: terraform - - - name: Initialize terraform-hisec/ - run: terraform init -backend=false - working-directory: terraform-hisec - - name: Validate terraform-hisec/ - run: terraform validate - working-directory: terraform-hisec - - name: Check formatting of terraform-hisec/ - run: terraform fmt -check -diff -recursive - working-directory: terraform-hisec diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..250ae5a --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,203 @@ +# Security checklists: +# https://aquilax.ai/blog/github-actions-security-hardening +# https://www.wiz.io/blog/github-actions-security-guide + +name: Deploy + +on: + workflow_dispatch: + inputs: + run_id: + description: "Test and build workflow run ID to deploy" + required: true + # zizmor: ignore[dangerous-triggers] Only triggered via default branch + workflow_run: + workflows: + - Test and build + branches: + - main + types: + - completed + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + +jobs: + validate: + runs-on: ubuntu-24.04 + timeout-minutes: 2 + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + outputs: + build_run_id: ${{ steps.resolve-run-id.outputs.run_id }} + build_skipped: ${{ steps.validate-run-id.outputs.build_skipped }} + build_head_sha: ${{ steps.validate-run-id.outputs.build_head_sha }} + build_run_number: ${{ steps.validate-run-id.outputs.build_run_number }} + build_url: ${{ steps.validate-run-id.outputs.build_url }} + steps: + - name: Resolve run ID + id: resolve-run-id + run: echo "run_id=$RESULT" >> "$GITHUB_OUTPUT" + env: + RESULT: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || github.event.inputs.run_id }} + + - name: Validate run ID + id: validate-run-id + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_ID: ${{ steps.resolve-run-id.outputs.run_id }} + EXPECTED_WORKFLOW_NAME: Test and build + EXPECTED_BRANCH: main + EXPECTED_JOB_NAME: apiserver-build + with: + script: | + const runId = Number(process.env.RUN_ID); + if (!Number.isInteger(runId) || runId <= 0) { + core.setFailed(`Invalid run_id: ${process.env.RUN_ID}`); + return; + } + + const { owner, repo } = context.repo; + const run = ( + await github.rest.actions.getWorkflowRun({ + owner, + repo, + run_id: runId + }) + ).data; + + if (run.name !== process.env.EXPECTED_WORKFLOW_NAME) { + core.setFailed( + `run_id ${runId} is for workflow '${run.name}', expected '${process.env.EXPECTED_WORKFLOW_NAME}'` + ); + return; + } + + if (run.head_branch !== process.env.EXPECTED_BRANCH) { + core.setFailed( + `run_id ${runId} is on branch '${run.head_branch}', expected '${process.env.EXPECTED_BRANCH}'` + ); + return; + } + + if (run.conclusion !== 'success') { + core.setFailed( + `run_id ${runId} has conclusion '${run.conclusion}', expected 'success'` + ); + return; + } + + if (run.head_repository?.full_name !== `${owner}/${repo}`) { + core.setFailed( + `run_id ${runId} belongs to '${run.head_repository?.full_name}', expected '${owner}/${repo}'` + ); + return; + } + + const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, { + owner, + repo, + run_id: runId, + per_page: 100, + }); + + const buildJob = jobs.find((job) => job.name === process.env.EXPECTED_JOB_NAME); + if (!buildJob) { + core.setFailed( + `run_id ${runId} does not contain expected job '${process.env.EXPECTED_JOB_NAME}'` + ); + return; + } + + if (buildJob.conclusion === 'skipped') { + core.setOutput('build_skipped', 'true'); + core.info(`job '${process.env.EXPECTED_JOB_NAME}' in run_id ${runId} was skipped; nothing to deploy`); + return; + } + + if (buildJob.conclusion !== 'success') { + core.setFailed( + `job '${process.env.EXPECTED_JOB_NAME}' in run_id ${runId} has conclusion '${buildJob.conclusion}', expected 'success'` + ); + return; + } + + core.setOutput('build_skipped', 'false'); + core.setOutput('build_head_sha', run.head_sha); + core.setOutput('build_run_number', String(run.run_number)); + core.setOutput('build_url', run.html_url); + + deploy-apiserver: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + needs: validate + environment: deploy + if: needs.validate.outputs.build_skipped == 'false' + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: true + ref: ${{ needs.validate.outputs.build_head_sha }} + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: apiserver + run-id: ${{ needs.validate.outputs.build_run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create tag + run: | + { + git show -s --format=%B "$BUILD_HEAD_SHA" + echo + echo "Deploy run: $GITHUB_RUN_ID" + echo "Build run: $BUILD_RUN_ID" + echo "Build number: $BUILD_RUN_NUMBER" + echo "Build URL: $BUILD_URL" + echo "Build SHA: $BUILD_HEAD_SHA" + } > tag-message.txt + + git tag -a apiserver-"$GITHUB_RUN_NUMBER" -F tag-message.txt "$BUILD_HEAD_SHA" + env: + BUILD_RUN_ID: ${{ needs.validate.outputs.build_run_id }} + BUILD_RUN_NUMBER: ${{ needs.validate.outputs.build_run_number }} + BUILD_URL: ${{ needs.validate.outputs.build_url }} + BUILD_HEAD_SHA: ${{ needs.validate.outputs.build_head_sha }} + + - name: Push tag + run: git push origin apiserver-"$GITHUB_RUN_NUMBER" + + - name: Create release + run: > + gh release create + apiserver-"$GITHUB_RUN_NUMBER" + apiserver-*.tar.zst + --title "apiserver v$GITHUB_RUN_NUMBER" + --notes-from-tag + --verify-tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + id: get-id-token + with: + script: | + const fs = require('fs'); + const token = await core.getIDToken('backend.fullstaqruby.org'); + fs.writeFileSync( + process.env.GITHUB_OUTPUT, + `id_token< + curl -fL --no-progress-meter -X POST -H "Authorization: Bearer $TOKEN" + https://apt.fullstaqruby.org/admin/upgrade_apiserver + env: + TOKEN: ${{ steps.get-id-token.outputs.id_token }} diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml new file mode 100644 index 0000000..53d59d0 --- /dev/null +++ b/.github/workflows/test-and-build.yml @@ -0,0 +1,279 @@ +# This workflow must be usable with third-party pull requests, +# and thus may not make use of any secrets. +# +# Security checklists: +# https://aquilax.ai/blog/github-actions-security-hardening +# https://www.wiz.io/blog/github-actions-security-guide + +name: Test and build + +on: + workflow_dispatch: {} + push: + paths-ignore: + - "**.md" + pull_request: + types: + - opened + - synchronize + paths-ignore: + - "**.md" + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} + +jobs: + test-terraform: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.5.7" + + - name: Install eclint + run: npm install -g eclint + - name: Check EditorConfig compliance + run: eclint check $(git ls-files) + + - name: Initialize terraform/ + run: terraform init -backend=false + working-directory: terraform + - name: Validate terraform/ + run: terraform validate + working-directory: terraform + - name: Check formatting of terraform/ + run: terraform fmt -check -diff -recursive + working-directory: terraform + + - name: Initialize terraform-hisec/ + run: terraform init -backend=false + working-directory: terraform-hisec + - name: Validate terraform-hisec/ + run: terraform validate + working-directory: terraform-hisec + - name: Check formatting of terraform-hisec/ + run: terraform fmt -check -diff -recursive + working-directory: terraform-hisec + + test-ansible: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Install Ansible + run: sudo apt update && sudo apt install -y ansible + - name: Check Ansible playbook syntax + run: ansible-playbook -i hosts.ini --syntax-check main.yml + working-directory: ansible + + audit-workflows: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + permissions: + security-events: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + + test-apiserver: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + BUNDLE_PATH: vendor/bundle + BUNDLE_WITH: ci + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 + with: + ruby-version: "3.2" + bundler-cache: true + env: + BUNDLE_GEMFILE: apiserver/Gemfile + BUNDLE_PATH: apiserver/vendor/bundle + - name: Check code formatting with Rufo + run: bundle exec rufo --check . + working-directory: apiserver + + detect-apiserver-changes: + runs-on: ubuntu-24.04 + timeout-minutes: 2 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + outputs: + changed: ${{ steps.detect.outputs.changed }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + - id: detect + name: Detect apiserver/ changes + shell: bash + run: | + set -euo pipefail + + if [[ "$GITHUB_EVENT_NAME" == "pull_request" || "$GITHUB_EVENT_NAME" == "pull_request_target" ]]; then + base_sha="$GITHUB_PR_BASE_SHA" + head_sha="$GITHUB_PR_HEAD_SHA" + changed_files="$(git diff --name-only "$base_sha" "$head_sha")" + elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then + before_sha="$GITHUB_EVENT_BEFORE" + head_sha="$GITHUB_SHA" + default_branch="$GITHUB_DEFAULT_BRANCH" + ref_name="$GITHUB_REF_NAME" + + if [[ "$before_sha" =~ ^0+$ ]] || ! git cat-file -e "$before_sha^{commit}" 2>/dev/null; then + if [[ "$ref_name" == "$default_branch" ]]; then + echo "::warning::Push to default branch with invalid before-SHA; failing open." + echo 'changed=true' >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Compare from merge-base with default branch to avoid missing changes on force-push. + if merge_base="$(git merge-base "$head_sha" "origin/$default_branch" 2>/dev/null)"; then + changed_files="$(git diff --name-only "$merge_base" "$head_sha")" + else + echo "::warning::Could not determine merge-base with default branch; failing open." + echo 'changed=true' >> "$GITHUB_OUTPUT" + exit 0 + fi + else + changed_files="$(git diff --name-only "$before_sha" "$head_sha")" + fi + else + if git rev-parse HEAD^ >/dev/null 2>&1; then + changed_files="$(git diff --name-only HEAD^ HEAD)" + else + changed_files="$(git diff-tree --no-commit-id --name-only -r HEAD)" + fi + fi + + if printf '%s\n' "$changed_files" | grep -q '^apiserver/'; then + echo 'changed=true' >> "$GITHUB_OUTPUT" + else + echo 'changed=false' >> "$GITHUB_OUTPUT" + fi + env: + GITHUB_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + GITHUB_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_EVENT_BEFORE: ${{ github.event.before }} + GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + + apiserver-build: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + needs: + - detect-apiserver-changes + - test-terraform + - test-ansible + - audit-workflows + - test-apiserver + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + needs.detect-apiserver-changes.outputs.changed == 'true' && + ( + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + ) + env: + BUNDLE_PATH: vendor/bundle + BUNDLE_WITH: ci + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + # We don't use ruby/setup-ruby in this workflow because it produces gem binaries + # that aren't compatible with the server, which install Ruby from distro repos. + - name: Check that we're using system Ruby + run: test "$(which ruby)" = /usr/bin/ruby + + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: apiserver/vendor/bundle + key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} + restore-keys: ${{ runner.os }}-24.04-${{ runner.arch }}-gems- + + - name: Install matching Bundler version + run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document + working-directory: apiserver + + - name: Install gem bundle + run: bundle install + working-directory: apiserver + env: + BUNDLE_DEPLOYMENT: "true" + BUNDLE_JOBS: 4 + BUNDLE_RETRY: 3 + BUNDLE_CLEAN: "true" + + - name: Detect library dependencies + run: bundle exec debendencies . -o dpkg-dependencies.txt --tee + working-directory: apiserver + + - name: Create tarball + run: > + tar + -c + --use-compress-program 'zstd -T0' + --sort name + --owner root:0 + --group root:0 + --mtime '2024-01-01 00:00Z' + --preserve-permissions + --pax-option exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime + -f apiserver-"$GITHUB_RUN_NUMBER"-$(lsb_release --id --short | tr '[:upper:]' '[:lower:]')-$(lsb_release --release --short)-$(dpkg --print-architecture).tar.zst + -C apiserver + . + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: apiserver + path: "*.tar.zst" + compression-level: 0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7f66d72 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +This is the infra-as-code repo for Fullstaq Ruby. Consult README.md and `docs/` to learn more about the codebase, especially [Infrastructure overview](docs/infrastructure-overview.md). + +# Directory structure + +- `terraform-hisec/`: tier-0 infrastructure that only a limited subteam (Infra Owners) can touch. +- `terraform/`: normal infrastructure that all team members can touch. +- `ansible/`: playbook for the server backend.fullstaqruby.org which hosts the domains {apt,yum}.fullstaqruby.org. +- `apiserver/`: a Ruby HTTP API app, deployed to backend.fullstaqruby.org, with and endpoint allowing the APT/YUM publishing jobs to notify the server that new packages have been uploaded. + +# Github Workflows review instructions (ALWAYS FOLLOW) + +Review not only for correctness, but also security (VERY IMPORTANT): + +- Consult security checklists: + https://aquilax.ai/blog/github-actions-security-hardening + https://www.wiz.io/blog/github-actions-security-guide +- Learn insights from the Tanstack postmortem: https://snyk.io/blog/tanstack-npm-packages-compromised/ +- Beware of cache poisoning opportunities. +- Review transitive Github Actions dependencies too. diff --git a/apiserver/README.md b/apiserver/README.md index db03d29..6cf9ca5 100644 --- a/apiserver/README.md +++ b/apiserver/README.md @@ -14,4 +14,4 @@ curl -v -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://a ## Continuous deployment -New API server code changes, when pushed to master, are automatically deployed by the Infrastructure project's CI. +New API server code changes, when pushed to main, are automatically deployed by the Infrastructure project's CI. diff --git a/docs/members.md b/docs/members.md index 58e8c79..9e3354a 100644 --- a/docs/members.md +++ b/docs/members.md @@ -1,8 +1,8 @@ # Members -| Name | Roles | -| ------------------------------------------------------------------------------------------ | ----------------------------- | -| [Hongli Lai](mailto:hongli@hongli.nl) ([FooBarWidget](https://github.com/FooBarWidget)) | Infra Owner, Infra Maintainer | -| [Max Erkin](mailto:russs.max@gmail.com) ([rus-max](https://github.com/rus-max)) | Infra Maintainer | -| [Britt Treece](mailto:britt.treece@gmail.com) ([abtreece](https://github.com/abtreece)) | Infra Maintainer | -| [Noah Iniguez](mailto:niniguezg@gmail.com) ([ncispreedly](https://github.com/ncispreedly)) | Infra Maintainer | +| Name | Roles | +| ------------------------------------------------------------------------------------------------ | ----------------------------- | +| [Hongli Lai](mailto:hongli@hongli.nl) ([FooBarWidget](https://github.com/FooBarWidget)) | Infra Owner, Infra Maintainer | +| [Max Erkin](mailto:russs.max@gmail.com) ([rus-max](https://github.com/rus-max)) | Infra Maintainer | +| [Britt Treece](mailto:britt.treece@gmail.com) ([abtreece](https://github.com/abtreece)) | Infra Maintainer | +| [Noah Iniguez](mailto:niniguezg@gmail.com) ([noahssarcastic](https://github.com/noahssarcastic)) | Infra Maintainer | diff --git a/docs/offboarding.md b/docs/offboarding.md index 559e329..ed62695 100644 --- a/docs/offboarding.md +++ b/docs/offboarding.md @@ -2,13 +2,14 @@ Offboarding is to be done by someone with the [Infra Owner role](roles.md). -- [ ] Remove member from the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). -- [ ] Remove member from the `fsruby-server-edition2` Google Cloud project. -- [ ] Remove member from `terraform-hisec/variables.tf`, both `infra_owners_azure_group_members` and `infra_maintainers_azure_group_members`. Apply Terraform. +- [ ] Remove from the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). +- [ ] Remove from the [Infra Owners team](https://github.com/orgs/fullstaq-ruby/teams/infra-owners). +- [ ] Remove from the `fsruby-server-edition2` Google Cloud project. +- [ ] Remove from `terraform-hisec/variables.tf`, both `infra_owners_azure_group_members` and `infra_maintainers_azure_group_members`. Apply Terraform. - [ ] Rotate the shared access keys for the `fsruby2seredci1` Azure storage account. 1. In the Azure portal, go to the `fsruby2seredci1` storage account -> Access keys. 2. Click "Rotate key" for key1. 3. Refresh Terraform state: run `pushd terraform && terraform refresh; popd` 4. Install the new connection string as a Github Actions secret. See [Infrastructure bootstrapping](infrastructure-bootstrapping.md) -> "Populate Github Actions secrets and variables". -- [ ] Remove member from Entra ID. +- [ ] Remove from Entra ID. - [ ] In the [Members](members.md) document, move member to the "Alumni" section. diff --git a/docs/onboarding.md b/docs/onboarding.md index e441756..cd00aab 100644 --- a/docs/onboarding.md +++ b/docs/onboarding.md @@ -2,14 +2,13 @@ Onboarding is to be done by someone with the [Infra Owner role](roles.md). -- [ ] Add member to the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). - - If member is an Infra Maintainer: assign "Maintain" role. - - If member is an Infra Owner: assign "Admin" role. -- [ ] Add member to the `fsruby-server-edition2` Google Cloud project. - - If member is an Infra Maintainer: assign roles "Editor", "Storage Admin". - - If member is an Infra Owner: assign roles "Owner", "Storage Admin". -- Add member to Entra ID. +- [ ] Give access. + - If Infra Maintainer: add to the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). + - If Infra Owner: add to the [Infra Owners team](https://github.com/orgs/fullstaq-ruby/teams/infra-owners). +- [ ] Add to the `fsruby-server-edition2` Google Cloud project. + - If Infra Maintainer: assign roles "Editor", "Storage Admin". + - If Infra Owner: assign roles "Owner", "Storage Admin". +- [ ] Add to Entra ID. - [ ] Create a (regular) user. - [ ] Add object ID to `terraform-hisec/variables.tf` -> `infra_owners_azure_group_members` or `infra_maintainers_azure_group_members`. Apply Terraform. -- [ ] Add member to the [Members](members.md) document. -- [ ] Add member to the [Infra Owners team](https://github.com/orgs/fullstaq-ruby/teams/infra-owners) if applicable. +- [ ] Add to the [Members](members.md) document.