From 2b73aa4b28956e86f546b53109e9c039b0ab06e3 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Wed, 11 Feb 2026 11:07:54 -0800 Subject: [PATCH] Add plans for OIDC provider, zot hardening, and network segmentation Transcribes backlog tasks into plan documents: - adopt-oidc-provider: Dex-based OIDC IdP for SSO across services - harden-zot-registry: OIDC + API key auth and tag immutability for zot - Expands add-unifi-pulumi-stack with NFS security motivation, services subnet, appliance segregation, and firewall rules Co-Authored-By: Claude Opus 4.6 --- docs/changelog.d/plan-backlog-to-plans.doc.md | 1 + docs/how-to/how-to.md | 2 + docs/how-to/plans/add-unifi-pulumi-stack.md | 45 +++- docs/how-to/plans/adopt-oidc-provider.md | 207 +++++++++++++++++ docs/how-to/plans/harden-zot-registry.md | 210 ++++++++++++++++++ docs/how-to/plans/plans.md | 2 + 6 files changed, 458 insertions(+), 9 deletions(-) create mode 100644 docs/changelog.d/plan-backlog-to-plans.doc.md create mode 100644 docs/how-to/plans/adopt-oidc-provider.md create mode 100644 docs/how-to/plans/harden-zot-registry.md diff --git a/docs/changelog.d/plan-backlog-to-plans.doc.md b/docs/changelog.d/plan-backlog-to-plans.doc.md new file mode 100644 index 0000000..d1cda08 --- /dev/null +++ b/docs/changelog.d/plan-backlog-to-plans.doc.md @@ -0,0 +1 @@ +Add plan documents for OIDC provider adoption, zot registry hardening, and expanded network segmentation details. diff --git a/docs/how-to/how-to.md b/docs/how-to/how-to.md index a81e205..862863c 100644 --- a/docs/how-to/how-to.md +++ b/docs/how-to/how-to.md @@ -56,3 +56,5 @@ Migration and transition plans for upcoming infrastructure changes. | [[add-unifi-pulumi-stack]] | Add Pulumi IaC for UniFi Express 7 | | [[adopt-dagger-ci]] | Adopt Dagger as CI/CD build engine | | [[upstream-fork-strategy]] | Stacked-branch forking strategy for upstream projects | +| [[adopt-oidc-provider]] | Deploy OIDC identity provider for SSO across services | +| [[harden-zot-registry]] | Add authentication and tag immutability to zot registry | diff --git a/docs/how-to/plans/add-unifi-pulumi-stack.md b/docs/how-to/plans/add-unifi-pulumi-stack.md index 75746af..d7f4730 100644 --- a/docs/how-to/plans/add-unifi-pulumi-stack.md +++ b/docs/how-to/plans/add-unifi-pulumi-stack.md @@ -75,17 +75,44 @@ The `pulumiverse_unifi` PyPI package bridges from `paultyng/terraform-provider-u Once the stack is operational, we plan to configure these network zones: -| Network | VLAN | Subnet | Purpose | -|---------|------|--------|---------| -| Default LAN | 1 | `192.168.1.0/24` | Main network (indri, gilbert, ringtail, sifaka) | -| Guest | TBD | `192.168.2.0/24` | Guest WiFi, internet-only | -| IoT | TBD | `192.168.3.0/24` | Smart devices, isolated from LAN | +| Network | VLAN | Subnet | Purpose | Devices | +|---------|------|--------|---------|---------| +| BlumeOps Services | TBD | `192.168.10.0/24` | Infrastructure and services | indri, sifaka, k8s pods | +| User Devices | 1 | `192.168.1.0/24` | Trusted personal devices | gilbert, ringtail | +| Guest | TBD | `192.168.2.0/24` | Guest WiFi, internet-only | Visitors | +| IoT / Appliances | TBD | `192.168.3.0/24` | Smart devices, isolated | Frame TV, dishwasher, etc. | -Zone-based firewall rules will enforce: +### Motivation: NFS Share Exposure -- Guest → Internet only (no LAN, no IoT) -- IoT → Internet + limited LAN access (e.g., mDNS) -- LAN → full access +The immediate security driver for segmentation is NFS. Currently, sifaka's NFS exports (`/volume1/torrents`, `/volume1/music`, `/volume1/photos`) whitelist `192.168.1.0/24` and `100.64.0.0/10` (Docker NAT). This means **any device on the WiFi** — including IoT appliances, guest devices, or a compromised smart TV — can mount and write to these shares. + +After segmentation, NFS exports will be restricted to the BlumeOps Services subnet (`192.168.10.0/24`) and the Docker NAT range (`100.64.0.0/10`). Only indri, sifaka, and k8s pods will have NFS access. + +### Zone-Based Firewall Rules + +| Source | Destination | Policy | +|--------|-------------|--------| +| BlumeOps Services | Internet | Allow | +| BlumeOps Services | User Devices | Allow (for management, e.g., SSH from ringtail) | +| User Devices | BlumeOps Services | Allow (trusted users need access to services) | +| User Devices | Internet | Allow | +| Guest | Internet | Allow | +| Guest | All other zones | **Block** | +| IoT / Appliances | Internet | Allow | +| IoT / Appliances | User Devices | **Block** (except mDNS for AirPlay/casting) | +| IoT / Appliances | BlumeOps Services | **Allow specific ports** (Jellyfin, Navidrome for streaming) | + +### NFS Export Changes + +After the network migration, update sifaka's NFS export rules: + +| Share | Before | After | +|-------|--------|-------| +| `/volume1/torrents` | `192.168.1.0/24`, `100.64.0.0/10` | `192.168.10.0/24`, `100.64.0.0/10` | +| `/volume1/music` | `192.168.1.0/24`, `100.64.0.0/10` | `192.168.10.0/24`, `100.64.0.0/10` | +| `/volume1/photos` | `192.168.1.0/24`, `100.64.0.0/10` | `192.168.10.0/24`, `100.64.0.0/10` | + +This is a manual change in the Synology DSM NFS settings (not managed by Pulumi — sifaka's NFS config is outside the UniFi provider's scope). The k8s PersistentVolume definitions (`argocd/manifests/*/pv-nfs.yaml`) resolve sifaka by hostname and don't need subnet changes. These will be declared after the initial import is stable. diff --git a/docs/how-to/plans/adopt-oidc-provider.md b/docs/how-to/plans/adopt-oidc-provider.md new file mode 100644 index 0000000..e49dfba --- /dev/null +++ b/docs/how-to/plans/adopt-oidc-provider.md @@ -0,0 +1,207 @@ +--- +title: "Plan: Adopt OIDC Identity Provider" +tags: + - how-to + - plans + - security + - oidc +--- + +# Plan: Adopt OIDC Identity Provider + +> **Status:** Planning (design sketch — not yet ready to execute) + +## Background + +BlumeOps services currently handle authentication independently — ArgoCD has its own admin password, Grafana has its own login, Forgejo has local accounts, and zot has no auth at all. There is no single sign-on, no centralized user management, and no way to issue scoped API keys or service tokens from a shared identity. + +Adding an OpenID Connect (OIDC) identity provider gives BlumeOps a central authentication layer. Services delegate login to the IdP, and the IdP issues tokens that carry identity and group claims. This unlocks: + +- **SSO across services** — one login for Grafana, ArgoCD, Forgejo, zot, and future services +- **API keys derived from identity** — zot's API key feature requires OIDC; CI service accounts get scoped, expirable tokens tied to a real identity +- **Group-based authorization** — services can make access decisions based on IdP group claims rather than per-service user lists +- **Audit trail** — authentication events flow through one system + +### Goals + +- Deploy a lightweight OIDC provider on the BlumeOps infrastructure +- Configure at least one service (zot) as a relying party to validate the setup +- Establish patterns for adding future OIDC clients (Grafana, ArgoCD, Forgejo) +- Keep complexity appropriate for a single-user homelab + +## Provider Comparison + +| Provider | Language | Resources | UI | OIDC Maturity | Zot Integration | Notes | +|----------|----------|-----------|-----|---------------|-----------------|-------| +| **Dex** | Go | ~20-50MB RAM | None (config-driven) | Mature, purpose-built | Explicitly documented in zot examples | CNCF Sandbox; `staticPasswords` connector for single-user | +| **Authentik** | Python | ~200-300MB RAM, needs PostgreSQL + Redis | Full web UI, visual flow builder | Mature | [Proven community guide](https://integrations.goauthentik.io/infrastructure/zot/) | Best for small teams; heavier than needed for one user | +| **Authelia** | Go | ~30MB RAM | None (YAML config) | Maturing (OIDC provider still on roadmap) | [Unresolved integration issues](https://github.com/authelia/authelia/discussions/7615) | Primarily a forward-auth proxy; OIDC is secondary | +| **Keycloak** | Java | ~500MB+ RAM | Enterprise admin console | Battle-tested | Works via generic OIDC | Massive overkill for homelab | + +### Recommendation: Investigate Dex First + +Dex is the strongest candidate for BlumeOps: + +- **Lightest footprint** — single Go binary, no database dependencies (in-memory or SQLite storage) +- **Designed for exactly this** — Dex is an OIDC provider that federates identity; it's not a full IAM suite bolted onto other things +- **Zot uses Dex in its own examples** — lowest integration risk +- **`staticPasswords` connector** — define the single `eblume` user directly in YAML config, no external user store needed +- **Future flexibility** — if SSO via GitHub or Google is ever wanted, add a connector without changing the architecture +- **CNCF project** — actively maintained, well-documented + +The main trade-off is no web UI for user management — but for a single-user setup, that's a non-issue. Config changes go through the normal PR workflow. + +If Dex proves insufficient during execution (e.g., missing features for a specific service integration), Authentik is the fallback — heavier but more capable. + +## Architecture + +``` + Caddy (TLS termination) + | + +--------------+--------------+ + | | | + Browser SSO CLI / CI k8s services + | | | + v v v + Dex (OIDC IdP) API Keys OIDC tokens + issuer: (generated (validated by + dex.ops.eblu.me after OIDC each service) + | login) + v + staticPasswords + connector (eblume) +``` + +### Deployment Options + +Dex can run as: + +1. **k8s pod** (via ArgoCD) — follows the pattern of other BlumeOps services, gets automatic restarts, lives alongside its consumers +2. **Native on indri** (via Ansible/LaunchAgent) — follows the zot/Forgejo pattern, simpler networking + +The k8s option is preferred since most OIDC consumers (Grafana, ArgoCD) are already in k8s. Evaluate during execution. + +### Endpoints + +| Endpoint | URL | Purpose | +|----------|-----|---------| +| Issuer | `https://dex.ops.eblu.me` | OIDC discovery (`/.well-known/openid-configuration`) | +| Auth | `https://dex.ops.eblu.me/auth` | Browser login redirect | +| Token | `https://dex.ops.eblu.me/token` | Token exchange | +| Callback | Per-client (e.g., `https://registry.ops.eblu.me/zot/auth/callback/oidc`) | OAuth2 redirect URI | + +## Dex Configuration Sketch + +```yaml +issuer: https://dex.ops.eblu.me + +storage: + type: sqlite3 + config: + file: /var/dex/dex.db + +web: + http: 0.0.0.0:5556 + +connectors: + - type: local + id: local + name: Local + +staticPasswords: + - email: eblume@eblume.net + hash: "" # generated at deploy time + username: eblume + userID: "" + +staticClients: + - id: zot-registry + name: Zot Registry + secret: "" + redirectURIs: + - https://registry.ops.eblu.me/zot/auth/callback/oidc + + # Future clients: + # - id: grafana + # ... + # - id: argocd + # ... + # - id: forgejo + # ... +``` + +Secrets (static password hash, client secrets) are stored in 1Password and injected at deploy time — never committed to the repo. + +## Planned OIDC Clients + +Initial rollout targets zot only. Future services to integrate: + +| Service | OIDC Support | Priority | Notes | +|---------|-------------|----------|-------| +| **Zot** | Native (`openid.providers.oidc`) | First (validates IdP) | See [[harden-zot-registry]] | +| **Grafana** | Native (`auth.generic_oauth`) | High | Currently uses default admin password | +| **ArgoCD** | Native (`oidc.config` in `argocd-cm`) | High | Currently uses local admin password | +| **Forgejo** | Native (OAuth2 provider in admin settings) | Medium | Currently uses local accounts | + +## Execution Steps + +1. **Choose deployment method** (k8s vs native) and set up the service + - If k8s: create `argocd/manifests/dex/` with Deployment, Service, ConfigMap + - If native: create `ansible/roles/dex/` following the zot pattern + - Add Caddy reverse proxy entry for `dex.ops.eblu.me` + +2. **Configure Dex** + - Generate static password hash and client secrets + - Store all secrets in 1Password + - Deploy initial config with `staticPasswords` connector and zot as the first client + +3. **Verify OIDC discovery** + - `curl https://dex.ops.eblu.me/.well-known/openid-configuration` returns valid JSON + - Issuer URL matches config + +4. **Integrate first client (zot)** + - This is covered by [[harden-zot-registry]] — configure zot's `openid.providers.oidc` to point at Dex + - Test browser login → API key generation → CLI push flow + +5. **Documentation** + - Create `docs/reference/services/dex.md` reference card + - Update service indexes + - Add changelog fragment + +## Verification Checklist + +- [ ] Dex is running and healthy +- [ ] OIDC discovery endpoint returns valid configuration +- [ ] Browser login flow works (redirect → Dex login → redirect back) +- [ ] At least one client (zot) successfully authenticates via Dex +- [ ] Caddy proxies `dex.ops.eblu.me` correctly +- [ ] `mise run services-check` passes (if health check is added) + +## Open Questions + +- **Service dependency and recovery:** If Dex runs in k8s and k8s goes down, services that depend on Dex for authentication may become inaccessible — potentially including tools needed to bring k8s back up. This circular dependency **must be resolved** before execution. Options include: running Dex natively on indri (outside k8s), ensuring all critical recovery paths have break-glass credentials that bypass OIDC, or designing the system so that OIDC is additive (services fall back to local auth when the IdP is unreachable). This needs its own design pass during implementation planning. +- **Dex vs Authentik:** Dex is the starting recommendation, but evaluate during execution. If multiple services need dynamic user management or a web UI for client registration, Authentik may be worth the extra weight. +- **Storage backend:** SQLite is simplest for single-node. If Dex runs in k8s, it needs a PersistentVolume or could use the k8s CRD storage backend instead. +- **Tailscale ACL interaction:** Should the Dex endpoint be tailnet-only, or accessible from the public internet (for potential external SSO)? Start with tailnet-only. +- **Token lifetime and refresh:** Dex defaults are reasonable, but may need tuning for long-running CI jobs. + +## Future Considerations + +- **Additional connectors** — add GitHub or Google as upstream identity sources for SSO convenience +- **Group claims** — define groups in Dex config (e.g., `admin`, `ci`) and use them for authorization across services +- **Mutual TLS** — Dex supports mTLS for service-to-service token exchange, which could harden the CI credential path + +## Reference Pattern Files + +| File | Purpose | +|------|---------| +| `argocd/manifests/grafana-config/` | Example k8s service with ConfigMap-based config | +| `ansible/roles/zot/` | Example native service deployment pattern | +| `pulumi/tailscale/` | Example of secrets injection from 1Password | + +## Related + +- [[harden-zot-registry]] — first OIDC client (execute after this plan) +- [[zot]] — container registry reference +- [[cluster]] — k8s cluster (potential Dex host) +- [[indri]] — native service host (alternative Dex host) diff --git a/docs/how-to/plans/harden-zot-registry.md b/docs/how-to/plans/harden-zot-registry.md new file mode 100644 index 0000000..a8f9618 --- /dev/null +++ b/docs/how-to/plans/harden-zot-registry.md @@ -0,0 +1,210 @@ +--- +title: "Plan: Harden Zot Registry" +tags: + - how-to + - plans + - zot + - registry + - security +--- + +# Plan: Harden Zot Registry + +> **Status:** Planned (not yet executed) +> **Sequence:** Execute after [[adopt-dagger-ci]] and [[adopt-oidc-provider]] — the Dagger migration will change how images are built and pushed, and the OIDC provider supplies the identity layer that zot's auth and API key features depend on. + +## Background + +Zot is the BlumeOps OCI container registry, running natively on [[indri]]. It serves two roles: a pull-through cache for upstream registries (Docker Hub, GHCR, Quay) and the private image store for `blumeops/*` images. + +Currently, zot has **no authentication** — the security boundary is the Tailscale ACL. This was an acceptable starting point, but has two gaps: + +1. **Any tailnet client can push images** — there's no distinction between pull (which k8s pods need) and push (which only CI should do). A compromised service or misconfigured pod could overwrite production images. +2. **Tags are mutable** — pushing the same tag twice silently overwrites the previous image. There's no protection against accidental or malicious tag clobbering. + +### Goals + +- **Authenticated push** — only CI (Forgejo Actions / Dagger) can push images; all other clients are pull-only +- **Tag immutability** — once a version tag is pushed, it cannot be overwritten +- **No disruption to pulls** — k8s pods and pull-through caching continue to work without authentication +- **Minimal complexity** — use zot's built-in OIDC and API key features with the BlumeOps identity provider + +## Current State + +### Push Mechanism + +Images are currently pushed via the composite action at `.forgejo/actions/build-push-image/action.yaml`: + +1. `docker buildx build` creates the image +2. `docker save` exports to a tarball +3. `skopeo copy` pushes to `registry.ops.eblu.me` (no credentials needed) + +The action pushes two tags per build: a version tag (e.g., `v1.2.0`) and the git commit SHA. + +### Zot Configuration + +The config template (`ansible/roles/zot/templates/config.json.j2`) has no `accessControl` or `http.auth` section. The HTTP listener binds to `0.0.0.0:5050` with no TLS (Caddy terminates TLS at `registry.ops.eblu.me`). + +## Plan + +### 1. Add Authentication for Push (OIDC + API Keys) + +Zot supports native OIDC authentication with a built-in API key feature designed for exactly this use case. The approach: + +1. **OIDC for browser login** — zot delegates authentication to the BlumeOps OIDC provider (see [[adopt-oidc-provider]]). Human users log in via browser redirect. +2. **API keys for CI** — after logging in via OIDC, generate a scoped API key for Forgejo CI / Dagger. API keys are zot-native tokens (`zak_...`) that work with `docker login`, `skopeo`, and Dagger's `with_registry_auth()`. They can be scoped to specific repositories and given expiration dates. +3. **Access control** — `anonymousPolicy` allows unauthenticated pull; push requires authentication. + +```json +{ + "http": { + "auth": { + "openid": { + "providers": { + "oidc": { + "name": "BlumeOps", + "credentialsFile": "/Users/erichblume/.config/zot/oidc-credentials.json", + "issuer": "https://dex.ops.eblu.me", + "scopes": ["openid", "profile", "email"] + } + } + }, + "apikey": true + }, + "accessControl": { + "repositories": { + "**": { + "anonymousPolicy": ["read"], + "defaultPolicy": ["read", "create", "update"], + "policies": [ + { + "users": ["eblume"], + "actions": ["read", "create", "update", "delete"] + } + ] + } + }, + "adminPolicy": { + "users": ["eblume"], + "actions": ["read", "create", "update", "delete"] + } + } + } +} +``` + +The OIDC credentials file (client ID and secret) is deployed by Ansible from 1Password — never committed to the repo. + +**CI push flow after setup:** +1. Log in to zot UI via browser (OIDC redirect to Dex) +2. Generate an API key: `POST /zot/auth/apikey` with label `forgejo-ci`, scoped to `blumeops/**` +3. Store the key in 1Password (`op://blumeops/zot-ci-apikey/credential`) +4. CI uses the key: `docker login -u eblume -p zak_... registry.ops.eblu.me` + +This ensures: +- k8s pods, minikube containerd, and pull-through caching all continue to work anonymously (read-only) +- Push requires a valid API key tied to an OIDC identity +- No standalone password files (htpasswd) to manage — identity flows from the central IdP + +### 2. Enforce Tag Immutability + +Zot does not have a built-in tag immutability feature at the registry level. Options to consider during execution: + +- **Registry-side:** Check if newer zot versions (post-2.1) have added immutability policies. If so, configure in `config.json`. +- **Push-side enforcement:** The simpler approach — check whether a tag already exists before pushing. The current build-push-image action (and its eventual Dagger replacement) should query the registry API (`GET /v2//tags/list`) and **fail the build** if the version tag already exists. Commit SHA tags are inherently unique and don't need this check. + +The push-side approach is pragmatic: it prevents accidental overwrites in the normal CI flow. Combined with authenticated push, a tag can only be overwritten by someone with CI credentials who deliberately bypasses the check. + +> **See:** `.forgejo/actions/build-push-image/action.yaml` — this is where the pre-push tag check would be added in the current workflow. After [[adopt-dagger-ci]], the equivalent check goes in the Dagger `Container.publish()` wrapper. + +### 3. Update Ansible Role + +The `ansible/roles/zot/` role needs: + +- **New template:** `oidc-credentials.json.j2` (client ID and secret for the Dex OIDC client) +- **Updated config template:** `config.json.j2` gains `http.auth` (openid + apikey) and `accessControl` sections +- **Updated config template:** `config.json.j2` gains `externalUrl` set to `https://registry.ops.eblu.me` (required for OIDC callback redirects behind Caddy) +- **New variables:** `zot_oidc_client_id` and `zot_oidc_client_secret` sourced from 1Password in the playbook's `pre_tasks` +- **Handler:** restart zot LaunchAgent after config changes (already exists) + +### 4. Update CI Push Credentials + +After [[adopt-dagger-ci]], the Dagger module will use the zot API key for registry auth: + +```python +api_key = dag.set_secret("registry-api-key", + os.environ["ZOT_CI_API_KEY"]) +container.with_registry_auth("registry.ops.eblu.me", "eblume", api_key) +container.publish("registry.ops.eblu.me/blumeops/image:tag") +``` + +### 5. Update Minikube Containerd Config + +The minikube containerd config (`ansible/roles/minikube/tasks/main.yml`) currently talks to zot without credentials. Since anonymous pull remains allowed, **no changes are needed** for containerd. + +## Execution Steps + +1. **Prerequisite: OIDC provider is running** (see [[adopt-oidc-provider]]) + - Dex (or chosen provider) is deployed and serving `https://dex.ops.eblu.me` + - A zot OIDC client is registered with the provider + +2. **Update Ansible role** + - Add OIDC credentials template + - Update `config.json.j2` with auth (openid + apikey) and access control + - Store OIDC client credentials in 1Password + - Test with `mise run provision-indri -- --tags zot --check --diff` + +3. **Deploy and verify pulls still work** + - `mise run provision-indri -- --tags zot` + - Verify anonymous pull: `curl -sf https://registry.ops.eblu.me/v2/_catalog` + - Verify unauthenticated push fails: `skopeo copy ... docker://registry.ops.eblu.me/blumeops/test:fail` (should get 401) + +4. **Set up OIDC login and generate CI API key** + - Log in to zot UI via browser (OIDC flow through Dex) + - Generate an API key for CI use, store in 1Password + - Verify authenticated push works: `docker login -u eblume -p zak_... registry.ops.eblu.me` + +5. **Add tag immutability check to push workflow** + - Add pre-push tag existence check to Dagger module (or build-push-image action) + - Test by attempting to push an existing tag + +6. **Update documentation** + - Update `docs/reference/services/zot.md` security model section + - Add changelog fragment + +## Verification Checklist + +- [ ] Anonymous pull works (k8s pods, containerd, curl) +- [ ] Pull-through caching still works (pull an uncached image from docker.io) +- [ ] Unauthenticated push is rejected (401) +- [ ] OIDC browser login works (redirect to Dex and back) +- [ ] API key generation works from zot UI +- [ ] Authenticated push with API key succeeds +- [ ] Pushing a duplicate version tag fails (immutability check) +- [ ] Pushing a new commit SHA tag succeeds +- [ ] Grafana dashboard still shows zot metrics +- [ ] `mise run services-check` passes + +## Open Questions + +- **Immutability granularity:** Should immutability apply only to semver tags (`v*`) or also to commit SHA tags? SHA tags are unique by nature, so immutability is only meaningful for version tags. +- **API key rotation:** API keys can have expiration dates. Decide on a rotation policy — e.g., annual expiry with a reminder, or no expiry with manual rotation. + +## Reference Pattern Files + +| File | Purpose | +|------|---------| +| `ansible/roles/zot/templates/config.json.j2` | Current zot config (no auth) | +| `ansible/roles/zot/tasks/main.yml` | Zot deployment tasks | +| `ansible/roles/zot/defaults/main.yml` | Zot default variables | +| `.forgejo/actions/build-push-image/action.yaml` | Current image push workflow (skopeo) | +| `ansible/roles/minikube/tasks/main.yml` | Containerd registry mirror config | +| `docs/reference/services/zot.md` | Zot reference documentation | + +## Related + +- [[adopt-oidc-provider]] — OIDC identity provider (execute first) +- [[adopt-dagger-ci]] — CI/CD engine migration (execute first) +- [[zot]] — Zot reference card +- [[forgejo]] — CI platform that pushes images +- [[cluster]] — Registry consumer diff --git a/docs/how-to/plans/plans.md b/docs/how-to/plans/plans.md index cff2c38..213682a 100644 --- a/docs/how-to/plans/plans.md +++ b/docs/how-to/plans/plans.md @@ -17,3 +17,5 @@ Plans differ from regular how-to guides in that they describe work that has been | [[add-unifi-pulumi-stack]] | Planned | Add Pulumi IaC for UniFi Express 7 home network | | [[adopt-dagger-ci]] | Planned | Adopt Dagger as CI/CD build engine, migrate docs artifacts to Forgejo packages | | [[upstream-fork-strategy]] | Planned | Stacked-branch forking strategy for tracking upstream projects | +| [[adopt-oidc-provider]] | Planning | Deploy OIDC identity provider for SSO across services | +| [[harden-zot-registry]] | Planned | Add authentication and tag immutability to zot registry |