Skip to content

How VPC Atlas is built and released

Everything that builds, tests, scans and publishes VPC Atlas runs in public GitHub Actions workflows, in the repository. The runs are on the Actions page. This page lists what each workflow guards, the choices that keep the supply chain small, how to verify every artifact you download, and how to run the same checks on your own machine. For the contributor side (setup, pull requests, releasing) see Contributing.

The workflows

There are five workflows. Each one has a narrow job, the least permissions it needs, and a timeout.

ci

Runs on every pull request, on every push to main and by hand. A newer push to the same branch cancels the older run. These jobs are the required checks for merging.

Job What it guards When
Lint ruff check, ruff format --check, actionlint and zizmor over the workflow files. Every run
Type check mypy over src/vpcatlas. A type error fails the run. Every run
Tests The full suite on Linux (Python 3.12 and 3.13), macOS and Windows. The Linux 3.12 leg also enforces 88% coverage and writes the least covered files to the job summary. Every run
Docs mkdocs build --strict: a broken link or a page missing from the nav fails. Every run
Python package The sdist and wheel build from the hash-pinned build lock, pass twine check and carry the dashboard. Every run
Images The dashboard and Lambda images build; the dashboard answers /healthz; the Lambda image answers an HTTP API event through the Runtime Interface Emulator; Trivy fails on a fixable critical CVE and reports the rest to the Security tab. Every run
Helm chart helm lint --strict and helm template with the default values and with every option on; then the chart is installed in a kind cluster with the image built from the commit, passes helm test and answers /healthz and an authenticated page through a port-forward, and is upgraded with every option on. Every run
Installer install.sh (Linux, macOS) and install.ps1 (Windows) install a freshly built archive, run vpcatlas --version, install into another directory, and refuse a wrong checksum without leaving files behind. Every run
Dependency review A change that adds a dependency with a known high-severity advisory is blocked, with a comment on the pull request. Pull requests

release

Runs when a tag vX.Y.Z is pushed. Started by hand it only tests and builds, as a rehearsal: nothing is published.

Job What it guards or does When
tests Lint and test again on the tagged commit, and check that the tag, pyproject.toml, src/vpcatlas/__init__.py and deploy/helm/vpcatlas/Chart.yaml agree on the version. Tag
binaries Builds the program for Linux, macOS (x86_64 and arm64) and Windows, smoke-tests each one and measures its startup. The Linux x86_64 leg also writes the SBOM. Tag
package Builds the sdist and the wheel. Tag
release Writes SHA256SUMS, attests every archive, the checksum file and the SBOM, attests the SBOM against the Linux archive, and creates the GitHub Release with a verify section in its notes. Tag
docker Builds the dashboard image for amd64 and arm64, smoke-tests it, fails on a fixable critical CVE before anything is pushed, then pushes, signs with cosign and attests. Tag
lambda The same for the Lambda image, one per architecture. Tag
chart Packages the Helm chart, pushes it to ghcr.io/karina-pp/charts/vpcatlas, signs it with cosign and attests it, pulls it back to verify the signature and the attestation, and attaches the .tgz to the GitHub Release. Tag
pypi Publishes the sdist and wheel through trusted publishing. Off until the project is registered on PyPI. Tag
verify Installs the published release the way a user does (the live install.sh and install.ps1, with the version pinned) on Linux, macOS and Windows, then checks the image attestation and signature. Tag

codeql

CodeQL analysis of the Python source with the security-extended queries. Runs on pull requests, on main and every Monday. Results are in the Security tab.

scorecard

OpenSSF Scorecard grades the repository on pinned dependencies, token permissions, branch protection, signed releases and similar practices. Runs on main and every Monday, and publishes the result, which feeds the badge in the README.

nightly

Checks that can fail without a code change. Runs every day at 05:00 UTC and by hand.

Job What it watches
Dependency audit pip-audit over the installed environment: a new advisory for a dependency fails the job.
Links lychee over the README, the docs and the changelog: a dead link fails the job.
Layout benchmark scripts/bench_layout.py on 77 and 5 APIs, full and simple detail. A report in the job summary and an artifact; it does not fail.
Published image Trivy over vpcatlas/vpcatlas:latest from Docker Hub. Fixable high and critical findings go to the Security tab; it does not fail.
Open or update the issue If anything above failed, opens one issue titled "Nightly checks failed" (label nightly), or comments on the open one.

Dependabot also opens weekly pull requests: one for all GitHub Actions, one for the Python minor and patch updates, and one per Docker base image.

Supply chain decisions

  • Pinned by content. Every action is pinned to a full commit SHA with its version in a comment, and the base images and Python requirements are pinned by digest or hash (requirements.lock is installed with --require-hashes). A tag can be moved after the fact; a SHA cannot. Dependabot proposes the bumps.
  • Least privilege. Every workflow starts with permissions: contents: read; a job asks for more only for what it does (security-events: write to upload findings, id-token: write to sign, attestations: write to attest). Checkouts do not keep the token in the working copy.
  • No long-lived tokens where an identity will do. Signing and attestations use the short-lived OIDC identity of the workflow run, and PyPI uses trusted publishing. The one stored secret is the Docker Hub token, because Docker Hub does not accept OIDC from GitHub.
  • No caches in publishing jobs. The jobs that build what you download do not restore build caches, so a poisoned cache cannot reach a release.
  • Workflows are linted. actionlint and zizmor run on every change; a finding fails the build, and the zizmor findings also go to the Security tab.
  • Provenance for everything published. Archives, SHA256SUMS, the SBOM and both image families carry a signed SLSA build provenance attestation, and the images are also signed with cosign.
  • An SBOM per release. A CycloneDX file lists what is inside the Linux x86_64 build, and is attested against that archive.
  • Scanned before and after. Trivy gates the image before it is pushed; CodeQL, Scorecard, pip-audit and the nightly scan of the published image watch what is already out.
  • The release is tested as a user meets it. After publishing, the verify job installs from the live installers and checks the signatures.

Verify what you install

Images and SBOMs are signed and attested from 0.4.1 on, the first release made by this pipeline; earlier releases carry only the attestations of the archives and SHA256SUMS. Verifying the image signature needs cosign 3 or newer.

You need the GitHub CLI for the attestations and cosign for the image signatures. Replace X.Y.Z with the version.

An archive (use .zip on Windows, and your own OS and architecture):

gh attestation verify vpcatlas-X.Y.Z-linux-x86_64.tar.gz --owner karina-pp

The checksum file, and the archive against it:

gh attestation verify SHA256SUMS --owner karina-pp
sha256sum --check --ignore-missing SHA256SUMS       # macOS: shasum -a 256 --check --ignore-missing SHA256SUMS

The dashboard image, by attestation and by signature:

gh attestation verify oci://index.docker.io/vpcatlas/vpcatlas:X.Y.Z --owner karina-pp
cosign verify index.docker.io/vpcatlas/vpcatlas:X.Y.Z \
  --certificate-identity-regexp '^https://github.com/karina-pp/vpcatlas/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

The Lambda image is verified the same way, with the name vpcatlas/vpcatlas-lambda and the tag X.Y.Z-<arch>.

The SBOM, which is vpcatlas-X.Y.Z-sbom.cdx.json in the release assets: its own provenance, and the SBOM attestation of the Linux x86_64 archive.

gh attestation verify vpcatlas-X.Y.Z-sbom.cdx.json --owner karina-pp
gh attestation verify vpcatlas-X.Y.Z-linux-x86_64.tar.gz --owner karina-pp --predicate-type https://cyclonedx.org/bom

The installers run the archive checks for you; VPCATLAS_SKIP_ATTESTATION=1 turns the attestation check off. See Install.

Reproduce the checks locally

python -m pip install -e ".[dev,docs]"
python -m ruff check . && python -m ruff format --check .       # Lint
python -m mypy                                                   # Type check
python -m pytest --cov=vpcatlas --cov-fail-under=88              # Tests with the coverage gate
python -m mkdocs build --strict                                  # Docs
python -m pip install --require-hashes -r requirements-release.lock  # build and twine, pinned as in CI
python -m build && python -m twine check dist/*                  # Python package
docker build -f deploy/Dockerfile -t vpcatlas:ci .               # Images
docker build -f deploy/Dockerfile.lambda -t vpcatlas-lambda:ci .
bash scripts/build-binary.sh                                     # Binary and archive
python scripts/bench_layout.py --apis 77 --detail simple         # Layout benchmark

The workflow linters run from their own tools: actionlint and zizmor .github/workflows (install them with your package manager or pip install zizmor). The Lambda smoke test with the emulator is written out in Contributing.

Demo assets

The recordings and the diagram images in the README and on the home page (docs/assets/demo.gif, demo-inventory.gif, demo-diagram.gif, region-eu-west-1.png, vpc-prod.png) are produced by scripts/demo.sh. It starts a simulated AWS account with moto, behind a small shim in demo/, and runs the real CLI against it, so nothing touches a real account. Then:

  • VHS plays every tape in demo/ and writes one GIF per tape;
  • draw.io exports the region and VPC pages to PNG.

The dashboard image (dashboard.png) is not made by the script. A maintainer captures it by hand, in a browser, from vpcatlas serve running against the same simulated account.

Regenerate them when the output of vpcatlas discover or the dashboard changes, and commit the new files with that change. The ids in the images are examples from the simulated account.