Harden zot registry, pt 1 (#231)
## Summary - Enable OIDC + API key authentication on zot with anonymous pull preserved - Enforce tag immutability for version tags - Adopt commit-SHA-based container image tagging Details in the [[harden-zot-registry]] Mikado chain (`mise run docs-mikado harden-zot-registry`). ## Test plan - [ ] Anonymous pull still works - [ ] Unauthenticated push fails (401) - [ ] CI container builds pass with new auth and tagging - [ ] `mise run services-check` passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/231
This commit is contained in:
parent
6d7071e5ec
commit
0e2c10176d
28 changed files with 743 additions and 30 deletions
|
|
@ -1 +1 @@
|
|||
Create C2 Mikado cards for harden-zot-registry: root goal and three prerequisite cards (register-zot-oidc-client, wire-ci-registry-auth, enforce-tag-immutability).
|
||||
Expand harden-zot-registry Mikado chain: add prereqs for container version sync check, pin container versions, and Dagger nix build function.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Discovered while attempting [[deploy-authentik]]: the deployment references `reg
|
|||
|
||||
## What to Do
|
||||
|
||||
1. Verify `containers/authentik/default.nix` builds on ringtail (the Nix builder runs there)
|
||||
1. Verify `containers/authentik/default.nix` builds — locally via Dagger (`dagger call build-nix --src=. --container-name=authentik`) or on ringtail (the CI nix builder runs there)
|
||||
2. The `ak` entrypoint needs bash (included via `bashInteractive`) and orchestrates both `server` and `worker` subcommands
|
||||
3. Tag and release: `mise run container-tag-and-release authentik v1.0.0`
|
||||
4. Verify the `-nix` tagged image appears in the registry
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Build Container Image
|
||||
modified: 2026-02-19
|
||||
modified: 2026-02-20
|
||||
last-reviewed: 2026-02-15
|
||||
tags:
|
||||
- how-to
|
||||
|
|
@ -38,7 +38,13 @@ A container can have one or both build files. The directory name becomes the ima
|
|||
dagger call build --src=. --container-name=<name>
|
||||
```
|
||||
|
||||
**Nix** — test with nix-build (requires nix, e.g. on [[ringtail]]):
|
||||
**Nix** — test with Dagger (no local nix required):
|
||||
|
||||
```bash
|
||||
dagger call build-nix --src=. --container-name=<name> export --path=./<name>.tar.gz
|
||||
```
|
||||
|
||||
Or with nix-build directly (requires nix, e.g. on [[ringtail]]):
|
||||
|
||||
```bash
|
||||
nix-build containers/<name>/default.nix -o result
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ Mikado chain for hardening the zot registry. Track progress with `mise run docs-
|
|||
- [[wire-ci-registry-auth]]
|
||||
- [[enforce-tag-immutability]]
|
||||
- [[adopt-commit-based-container-tags]]
|
||||
- [[add-container-version-sync-check]]
|
||||
- [[pin-container-versions]]
|
||||
- [[add-dagger-nix-build]]
|
||||
- [[fix-ntfy-nix-version]]
|
||||
|
||||
## Authentik
|
||||
|
||||
|
|
|
|||
82
docs/how-to/zot/add-container-version-sync-check.md
Normal file
82
docs/how-to/zot/add-container-version-sync-check.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: Add Container Version Sync Check
|
||||
modified: 2026-02-20
|
||||
requires:
|
||||
- pin-container-versions
|
||||
- add-dagger-nix-build
|
||||
- fix-ntfy-nix-version
|
||||
tags:
|
||||
- how-to
|
||||
- containers
|
||||
- ci
|
||||
- zot
|
||||
---
|
||||
|
||||
# Add Container Version Sync Check
|
||||
|
||||
Add a pre-commit check that validates version consistency across the three places container versions are declared: Dockerfile ARGs, `service-versions.yaml`, and nix derivations. No VERSION files needed — the existing sources are the source of truth, and the check enforces they agree.
|
||||
|
||||
## Context
|
||||
|
||||
Discovered during analysis of [[adopt-commit-based-container-tags]]: the new commit-SHA-based image tags need a reliable version source (`vX.Y.Z-<sha>`). Versions are currently scattered across Dockerfile ARGs (varying naming conventions), `service-versions.yaml` entries (many still `null`), and nix derivations (implicit from nixpkgs). A sync check ensures these stay consistent without adding a redundant fourth source.
|
||||
|
||||
## What Was Done
|
||||
|
||||
### 1. Created `mise run container-version-check` task
|
||||
|
||||
A typer-based uv-script that iterates over `containers/*/` and validates five rules per container:
|
||||
|
||||
1. Any Dockerfile must declare `ARG CONTAINER_APP_VERSION=<value>`
|
||||
2. Any `default.nix` must produce a version via `dagger call nix-version`
|
||||
3. At least one build file must exist (Dockerfile or default.nix)
|
||||
4. A matching `service-versions.yaml` entry must exist with non-null `current-version`
|
||||
5. All resolved versions from (1), (2), and (4) must agree (v-prefix stripped for comparison)
|
||||
|
||||
Scoping: by default only checks containers changed vs main. `--all-files` checks everything. If `service-versions.yaml` itself changed, all containers are checked.
|
||||
|
||||
Blacklisted containers (utility images, not tracked services): `kubectl`, `nettest`.
|
||||
|
||||
Container-to-service name mapping: `quartz` → `docs`, `kiwix-serve` → `kiwix`.
|
||||
|
||||
### 2. Added pre-commit hook
|
||||
|
||||
```yaml
|
||||
- id: container-version-check
|
||||
name: container-version-check
|
||||
entry: mise run container-version-check
|
||||
language: system
|
||||
files: ^(containers/|service-versions\.yaml)
|
||||
pass_filenames: false
|
||||
```
|
||||
|
||||
### 3. Populated `service-versions.yaml`
|
||||
|
||||
Filled in `current-version` for all hybrid services: navidrome (v0.60.3), miniflux (2.2.17), teslamate (v2.2.0), transmission (4.0.6-r4), kiwix (3.8.1), forgejo-runner (0.19.11). Added authentik (2025.10.1) as a new hybrid entry.
|
||||
|
||||
### ntfy nix version skew (resolved)
|
||||
|
||||
The check discovered that ntfy's Dockerfile pins v2.17.0 but nixpkgs has ntfy-sh 2.15.0. This was resolved in [[fix-ntfy-nix-version]] by building a custom nix derivation from the forge mirror. The version check now extracts the version from local nix files via regex, falling back to Dagger for unmodified nixpkgs packages.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `mise-tasks/container-version-check` | New: typer CLI sync validation script |
|
||||
| `.pre-commit-config.yaml` | Add `container-version-check` hook |
|
||||
| `service-versions.yaml` | Populate `current-version` for all hybrid services + authentik |
|
||||
|
||||
## Verification
|
||||
|
||||
- [x] `mise run container-version-check --all-files` passes with no errors
|
||||
- [x] Intentionally changing a Dockerfile ARG without updating `service-versions.yaml` fails the check
|
||||
- [x] `service-versions.yaml` has `current-version` populated for all hybrid services
|
||||
- [x] Nix-only container versions (authentik) checked via Dagger
|
||||
- [x] ntfy nix version resolved via [[fix-ntfy-nix-version]]
|
||||
|
||||
## Related
|
||||
|
||||
- [[pin-container-versions]] — Prereq: containers need parseable version ARGs first
|
||||
- [[add-dagger-nix-build]] — Prereq: nix version extraction
|
||||
- [[fix-ntfy-nix-version]] — Prereq: ntfy nix derivation version skew
|
||||
- [[adopt-commit-based-container-tags]] — Parent: CI uses the same version extraction at build time
|
||||
- [[harden-zot-registry]] — Root goal
|
||||
97
docs/how-to/zot/add-dagger-nix-build.md
Normal file
97
docs/how-to/zot/add-dagger-nix-build.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
title: Add Dagger Nix Build Function
|
||||
modified: 2026-02-20
|
||||
status:
|
||||
tags:
|
||||
- how-to
|
||||
- containers
|
||||
- ci
|
||||
- dagger
|
||||
- zot
|
||||
---
|
||||
|
||||
# Add Dagger Nix Build Function
|
||||
|
||||
Add Dagger functions for building nix container images and extracting version info from nix derivations. This enables local nix container evaluation and provides the version extraction mechanism needed by [[add-container-version-sync-check]].
|
||||
|
||||
## Context
|
||||
|
||||
Discovered during analysis of [[adopt-commit-based-container-tags]]: nix containers (authentik, ntfy, nettest) derive their bundled app version from the nixpkgs pin, not from an explicit declaration. To validate that a VERSION file matches the actual nix-built version, we need a way to query the version from nix.
|
||||
|
||||
Currently, nix containers can only be built on ringtail (the `nix-container-builder` runner). There is no local build path for developers — the only option is to push and wait for CI. Adding a Dagger-based nix build gives both local evaluation and version extraction.
|
||||
|
||||
## What to Do
|
||||
|
||||
### 1. Add `build_nix` Dagger function
|
||||
|
||||
A new function in `.dagger/src/blumeops_ci/main.py` that builds a nix container inside a `nixos/nix` container:
|
||||
|
||||
```python
|
||||
@function
|
||||
async def build_nix(
|
||||
self, src: dagger.Directory, container_name: str
|
||||
) -> dagger.File:
|
||||
"""Build a nix container from containers/<name>/default.nix. Returns the image tarball."""
|
||||
# Uses NIX_IMAGE (nixos/nix:2.33.3) — already defined in the module
|
||||
# Runs nix-build inside the container
|
||||
# Returns the docker-archive tarball
|
||||
```
|
||||
|
||||
This mirrors the existing `build` function (Dockerfile) but for nix. The result is a docker-archive tarball that can be loaded with `docker load` or pushed with `skopeo`.
|
||||
|
||||
### 2. Add `nix_version` Dagger function
|
||||
|
||||
A function that extracts the version of a specific nix package from the nixpkgs pin:
|
||||
|
||||
```python
|
||||
@function
|
||||
async def nix_version(
|
||||
self, src: dagger.Directory, package: str
|
||||
) -> str:
|
||||
"""Extract the version of a nixpkgs package. Returns version string."""
|
||||
# nix eval --raw nixpkgs#<package>.version
|
||||
```
|
||||
|
||||
This lets the version sync check run `dagger call nix-version --src=. --package=authentik` to get the actual version that would be built.
|
||||
|
||||
### 3. Add `publish_nix` Dagger function (optional)
|
||||
|
||||
If useful, a combined build-and-push that mirrors `publish` but for nix images:
|
||||
|
||||
```python
|
||||
@function
|
||||
async def publish_nix(
|
||||
self, src: dagger.Directory, container_name: str, version: str,
|
||||
registry: str = "registry.ops.eblu.me",
|
||||
) -> str:
|
||||
"""Build nix container and push to registry via skopeo."""
|
||||
```
|
||||
|
||||
This would give a `dagger call publish-nix` path parallel to the existing `dagger call publish`.
|
||||
|
||||
## Nix in Dagger
|
||||
|
||||
The `flake_lock` function already demonstrates running nix inside Dagger using `nixos/nix:2.33.3`. The nix build function follows the same pattern but needs:
|
||||
|
||||
- `NIX_PATH` set to resolved nixpkgs (same as the CI workflow does)
|
||||
- `--extra-experimental-features "nix-command flakes"` for `nix eval`
|
||||
- The full repo source mounted (nix files may reference other files like `test-connectivity.sh`)
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `.dagger/src/blumeops_ci/main.py` | Add `build_nix`, `nix_version`, optionally `publish_nix` |
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `dagger call build-nix --src=. --container-name=nettest` produces a valid docker-archive tarball
|
||||
- [ ] `dagger call nix-version --src=. --package=ntfy-sh` returns the correct version string
|
||||
- [ ] `dagger call nix-version --src=. --package=authentik` returns the Authentik version
|
||||
- [ ] Tarball from `build-nix` can be loaded with `docker load` and run locally
|
||||
|
||||
## Related
|
||||
|
||||
- [[add-container-version-sync-check]] — Parent: needs nix version extraction for sync check
|
||||
- [[adopt-commit-based-container-tags]] — Grandparent goal
|
||||
- [[dagger]] — Dagger reference
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
title: Adopt Commit-Based Container Tags
|
||||
modified: 2026-02-20
|
||||
status: active
|
||||
requires:
|
||||
- add-container-version-sync-check
|
||||
tags:
|
||||
- how-to
|
||||
- containers
|
||||
|
|
@ -35,7 +37,12 @@ Both the Dockerfile and Nix workflows fire for each trigger, each bailing out if
|
|||
|
||||
### Version Source
|
||||
|
||||
Each container declares the version of its primary bundled app. The mechanism for declaring this (e.g., a `VERSION` file, parsing a Dockerfile `ARG`, or a convention per container) should be determined during implementation.
|
||||
Each container's version is extracted at build time from existing declarations — no separate VERSION file:
|
||||
|
||||
- **Dockerfile builds**: parsed from `ARG CONTAINER_APP_VERSION=<value>` in the Dockerfile
|
||||
- **Nix builds**: extracted via `dagger call nix-version` or `nix eval`
|
||||
|
||||
The [[add-container-version-sync-check]] pre-commit check ensures these declarations stay in sync with `service-versions.yaml`. See [[pin-container-versions]] for the work to ensure every container has a parseable version.
|
||||
|
||||
### Image Tag Format
|
||||
|
||||
|
|
|
|||
41
docs/how-to/zot/fix-ntfy-nix-version.md
Normal file
41
docs/how-to/zot/fix-ntfy-nix-version.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: Fix ntfy Nix Version
|
||||
modified: 2026-02-20
|
||||
tags:
|
||||
- how-to
|
||||
- containers
|
||||
- nix
|
||||
- zot
|
||||
---
|
||||
|
||||
# Fix ntfy Nix Version
|
||||
|
||||
Override the nixpkgs ntfy-sh derivation to build v2.17.0 from the forge mirror, aligning the nix-built container with the Dockerfile version.
|
||||
|
||||
## Context
|
||||
|
||||
Discovered during [[add-container-version-sync-check]]: the ntfy container has both a Dockerfile and a `default.nix`. The Dockerfile builds v2.17.0 from `forge.ops.eblu.me/eblume/ntfy.git`, but the nix derivation uses `pkgs.ntfy-sh` from nixpkgs which is pinned at 2.15.0. The version sync check currently excludes ntfy from nix version validation as a workaround.
|
||||
|
||||
## What Was Done
|
||||
|
||||
Replaced the nixpkgs `pkgs.ntfy-sh` reference in `containers/ntfy/default.nix` with a custom derivation that builds v2.17.0 from the forge mirror using `fetchgit`, `buildNpmPackage` (web UI), and `buildGoModule` (server). Docs are skipped (placeholder for `go:embed`, matching the Dockerfile approach).
|
||||
|
||||
The `container-version-check` script was updated to extract versions from local nix files via regex (`version = "X.Y.Z"`) before falling back to the Dagger `nix-version` function for unmodified nixpkgs packages. This avoids the issue where `nix eval nixpkgs#ntfy-sh.version` returns the upstream 2.15.0 instead of our overridden 2.17.0.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `containers/ntfy/default.nix` | Custom derivation building v2.17.0 from forge |
|
||||
| `mise-tasks/container-version-check` | Regex-based local nix version extraction |
|
||||
|
||||
## Verification
|
||||
|
||||
- [x] `dagger call build-nix --src=. --container-name=ntfy` produces a working image
|
||||
- [x] Version extractable from local `default.nix` via regex (2.17.0)
|
||||
- [x] `mise run container-version-check --all-files` passes with ntfy included
|
||||
|
||||
## Related
|
||||
|
||||
- [[add-container-version-sync-check]] — Parent: needs ntfy in NIX_PACKAGE_MAP
|
||||
- [[harden-zot-registry]] — Root goal
|
||||
53
docs/how-to/zot/pin-container-versions.md
Normal file
53
docs/how-to/zot/pin-container-versions.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: Pin Container Versions
|
||||
modified: 2026-02-20
|
||||
tags:
|
||||
- how-to
|
||||
- containers
|
||||
- ci
|
||||
- zot
|
||||
---
|
||||
|
||||
# Pin Container Versions
|
||||
|
||||
Ensure every container has an explicit, parseable version declaration so that [[add-container-version-sync-check]] has something to validate against.
|
||||
|
||||
## Context
|
||||
|
||||
Discovered during analysis of [[adopt-commit-based-container-tags]]: containers needed a uniform, parseable version declaration for the sync check. Most containers already had version ARGs (miniflux, navidrome, ntfy, etc.), but with inconsistent naming (`NAVIDROME_VERSION`, `MINIFLUX_VERSION`, etc.), and several containers (devpi, cv, quartz, nettest) had none.
|
||||
|
||||
## What Was Done
|
||||
|
||||
Every container Dockerfile now declares `ARG CONTAINER_APP_VERSION=X.Y.Z` as its first ARG, providing a uniform parsing target. Containers that use the version in build commands chain it to a semantic ARG:
|
||||
|
||||
```dockerfile
|
||||
ARG CONTAINER_APP_VERSION=v0.60.3
|
||||
ARG NAVIDROME_VERSION=${CONTAINER_APP_VERSION}
|
||||
```
|
||||
|
||||
Specific changes:
|
||||
- **devpi**: Pinned devpi-server==6.19.1 and devpi-web==5.0.1
|
||||
- **cv**: `CONTAINER_APP_VERSION=1.0.3` (matches latest Forgejo package release)
|
||||
- **quartz**: `CONTAINER_APP_VERSION=1.28.2` (pinned nginx:1.28.2-alpine base)
|
||||
- **nettest**: `CONTAINER_APP_VERSION=0.1.0` (internal, no upstream)
|
||||
- **All others**: Existing versions carried forward with new uniform ARG pattern
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `containers/*/Dockerfile` | Add `ARG CONTAINER_APP_VERSION` to all 13 containers |
|
||||
| `service-versions.yaml` | Populate `current-version` for devpi, cv, docs |
|
||||
|
||||
## Verification
|
||||
|
||||
- [x] Every container Dockerfile has `ARG CONTAINER_APP_VERSION=X.Y.Z`
|
||||
- [x] ARG chaining tested with Docker build (nginx:1.28.2-alpine)
|
||||
- [x] devpi container pins pip package versions
|
||||
- [x] cv version matches Forgejo package release (1.0.3)
|
||||
- [x] quartz pins nginx base image to stable (1.28.2)
|
||||
|
||||
## Related
|
||||
|
||||
- [[add-container-version-sync-check]] — Parent: needs parseable versions for sync check
|
||||
- [[adopt-commit-based-container-tags]] — Grandparent goal
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Dagger
|
||||
modified: 2026-02-12
|
||||
modified: 2026-02-20
|
||||
tags:
|
||||
- reference
|
||||
- ci-cd
|
||||
|
|
@ -27,7 +27,10 @@ Build engine for BlumeOps CI/CD pipelines. Replaces shell-based build scripts wi
|
|||
|----------|-----------|-------------|
|
||||
| `build` | `(src, container_name) → Container` | Build a container from `containers/<name>/Dockerfile` |
|
||||
| `publish` | `(src, container_name, version, registry?) → str` | Build and push to registry (default: `registry.ops.eblu.me`) |
|
||||
| `build_nix` | `(src, container_name) → File` | Build a nix container from `containers/<name>/default.nix`, return docker-archive tarball |
|
||||
| `nix_version` | `(package) → str` | Extract the version of a nixpkgs package |
|
||||
| `build_docs` | `(src, version) → File` | Build Quartz docs site, return docs tarball |
|
||||
| `flake_lock` | `(src, flake_path?) → File` | Resolve flake inputs, return updated `flake.lock` |
|
||||
|
||||
## CLI Examples
|
||||
|
||||
|
|
@ -44,6 +47,12 @@ dagger call --interactive build --src=. --container-name=devpi
|
|||
# Publish a container to zot
|
||||
dagger call publish --src=. --container-name=devpi --version=v1.1.0
|
||||
|
||||
# Build a nix container (no local nix required)
|
||||
dagger call build-nix --src=. --container-name=nettest export --path=./nettest.tar.gz
|
||||
|
||||
# Check a nixpkgs package version
|
||||
dagger call nix-version --package=authentik
|
||||
|
||||
# Build docs tarball locally
|
||||
dagger call build-docs --src=. --version=dev export --path=./docs-dev.tar.gz
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue