Skip to content

feat: airgaps - #105

Draft
butler54 wants to merge 82 commits into
validatedpatterns:mainfrom
butler54:dev/airgap-testing
Draft

feat: airgaps#105
butler54 wants to merge 82 commits into
validatedpatterns:mainfrom
butler54:dev/airgap-testing

Conversation

@butler54

Copy link
Copy Markdown
Collaborator

No description provided.

butler54 and others added 30 commits July 21, 2026 09:35
oc-mirror v2 config targeting quay.apac-tech-lab.net/mirror:
- OCP 4.21.24 single release
- Full baremetal operator set (OSC 1.13, Trustee 1.2, ACM, ESO,
  GitOps, cert-manager, Kyverno, NFD, LVM, CNV, GPU, Intel)
- Application images (ubi9/httpd-24, vault)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iguration

- Add 7 VP Helm OCI charts as additionalImages (clustergroup, acm, vault, eso, trustee, sandbox, policies)
- Add Kyverno chart OCI artifact (ghcr.io/kyverno/charts/kyverno:3.7.2)
- Add VP utility + imperative container images
- Add oc-debug support-tools image (D-05)
- Add ose-cli image for wait-for-vault job
- Add Intel DCAP images (tag-based + digest-pinned from chart templates)
- Add ubi9 workload images
- Replace commented chart list with live additionalImages entries
- values-global: SSH git URL, helmRepoUrl, tokenSecret, CatalogSource sources
- values-global: clusterGroupName set to baremetal, patternsOperator + gitops sources
- values-baremetal: source overrides on all 10 subscriptions (8 redhat, 2 certified)
- values-baremetal: Kyverno repoURL changed to OCI quay mirror path
- Implements DPAT-01,03,04,06,14
- bootstrap_secrets v2.0 format for ArgoCD repo credential
- Creates private-repo Secret with SSH key (~/.ssh/id_rsa) + git URL
- URL matches values-global.yaml repoURL exactly for ArgoCD prefix matching
- Implements DPAT-02
The patterns-operator's getUserFromURL() only supports SCP-style URLs
(user@host:path), not ssh:// protocol URLs. Also use IPv4 address
since cluster DNS resolves hostname to unreachable IPv6.

- values-global.yaml: chbutler@172.25.36.135:/home/chbutler/coco-pattern
- values-secret.yaml.template: same URL in bootstrap_secrets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patterns-operator webhook rejects SSH URLs unless they start with git@,
and creating a git user requires sudo (unavailable on jump host).
Switch to dumb HTTP via Apache UserDir (~/public_html/git/).

- values-global.yaml: http://172.25.36.135/~chbutler/git/coco-pattern.git
- values-secret.yaml.template: same URL in bootstrap_secrets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patterns-operator is not in redhat-operator-index for OCP 4.21.
It's in the community-operator-index. Add it to the mirroring config
so oc-mirror handles it and the operator installs via OLM normally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- scripts/airgap-post-install.sh: idempotent bootstrap for disconnected
  clusters (disable default catalogs, mirror OCI Helm charts, add CA
  certs to ArgoCD, create bare git repos for HTTP serving, optional
  routingViaHost for test-lab networks)
- scripts/git-http-server.py: smart HTTP fallback server if Apache dumb
  HTTP doesn't work with go-git
- Makefile: airgap-post-install and airgap-sync-repos targets
- values-global.yaml: remove tokenSecret (HTTP repos need no auth)
- values-secret.yaml.template: remove bootstrap_secrets section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generic target wrapping oc-mirror with configurable MIRROR_REGISTRY,
AUTHFILE, and IMAGESET_CONFIG variables. Workflow:
  make airgap-mirror MIRROR_REGISTRY=quay.example.com:443/mirror
  make airgap-post-install
  make install

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oc-mirror reads auth from containers/auth.json or docker config.json
automatically. Remove the -a flag (not supported by oc-mirror).
Users should podman login or copy pull-secret.json to the right path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
skopeo is only needed for listing pattern-install tags. Make it
optional — the script warns and skips that step if skopeo is missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patterns-operator is in community-operator-index, not
redhat-operator-index. The previous value caused the subscription
to reference a non-existent CatalogSource on the disconnected cluster.

Added comments clarifying these are mirrored CatalogSource names
from labctl apply-mirror-resources.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major rewrite of the disconnected deployment bootstrap:

Layer B (general disconnected OpenShift):
- Create all 3 mirrored CatalogSources (redhat, certified, community)
- Create ITMS for tag-based image pulls (ubi-minimal, VP images)
- Fix manifest list failures from oc-mirror (skopeo fallback)
- Auto-extract CA from cluster trust bundle for ArgoCD

Layer C (validated patterns disconnected):
- Deploy Pattern CR directly via oc apply (bypass pattern.sh)
- Create patterns-operator-config ConfigMap (override gitops channel)
- Add gitops.channel: latest to values-global.yaml
- Add patternsOperator.channel: fast to values-global.yaml
- Smart HTTP git server setup (go-git can't handle Apache dumb HTTP)

imageset-config.yaml:
- Add imperative-container:v1 (VP uses :v1, not :latest)
- Add pattern-ui-catalog:stable-v1 (operator console plugin)
- Add ubi-minimal:latest explicitly

Makefile:
- airgap-deploy-pattern: deploy Pattern CR directly
- airgap-fix-manifests: fix oc-mirror manifest list failures

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add step 7b: configure_argocd_helm_auth
- Extracts registry credentials from cluster pull-secret
- Creates helm-registry-config secret in ArgoCD namespace
- Patches ArgoCD CR to mount HELM_REGISTRY_CONFIG
- Runs automatically during --deploy-pattern mode

This fixes the 401 errors when ArgoCD tries to pull OCI Helm charts
from private Quay repositories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ArgoCD's OCI registry credential system is broken (bugs #25513, #26311).
The ArgoCD operator reverts any custom env/volume patches.
Making Quay repos public requires admin access we don't have.

Solution: extract charts from OCI mirror, serve as traditional
Helm HTTP repo via Apache UserDir. No auth needed, no OCI issues.

- helmRepoUrl changed to http://jump-host/~user/helm-charts
- Charts extracted via helm pull from OCI, index.yaml generated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quay Helm chart repos made public — ArgoCD can pull OCI charts
without auth. Reverts the HTTP Helm repo workaround.

helmRepoUrl: quay.apac-tech-lab.net:443/mirror/validatedpatterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the full workflow: mirror → install OCP → bootstrap →
deploy pattern → load secrets. Documents all known issues,
workarounds, and Quay requirements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The clustergroup chart checks global.singleArgoCD, not
main.singleArgoCD. Move it to global: so the chart reads it
correctly and only creates one ArgoCD instance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The VP Helm chart uses registry.connect.redhat.com/hashicorp/vault
(Red Hat certified UBI build), not docker.io/hashicorp/vault.
Fix the imageset-config and add ITMS entry for registry.connect.redhat.com/hashicorp.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts ArgoCD admin credentials from the cluster and logs in
with the argocd CLI. Falls back to printing credentials + install
instructions if the CLI isn't available.

  make argocd-login

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts the argocd binary from the running ArgoCD server image
on the cluster — works in disconnected environments without
internet access to GitHub releases.

  make argocd-install          # installs to ~/.local/bin/
  make argocd-install ARGOCD_CLI_DIR=~/bin  # custom path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove 2>/dev/null so KUBECONFIG and API errors are visible.
Add explicit error messages for missing ArgoCD instance or deployment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenShift GitOps prefixes deployment names with the instance name
(e.g. vp-gitops-server), so app.kubernetes.io/name doesn't match
a static 'argocd-server'. Use component=server + part-of=argocd.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The reencrypt route handles TLS termination; let the CLI negotiate
the connection without forcing gRPC-web transport.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenShift reencrypt routes don't pass gRPC traffic. Use --core
mode which talks directly to the Kubernetes API instead of the
ArgoCD server, avoiding all route/TLS/gRPC issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kyverno chart was under mirror/kyverno/charts which has nested path
issues with Quay repo visibility. Re-mirrored under
mirror/validatedpatterns/kyverno (same public namespace as VP charts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
butler54 and others added 30 commits July 25, 2026 04:19
Confidential container workloads pull images inside the kata guest VM,
which has no access to IDMS mirror mappings. The image references must
point directly to the mirror registry.

Parameterized image refs in hello-openshift, kbs-access-curl, and
kbs-access-sealed charts via values.yaml. Baremetal topology overrides
set them to the Quay mirror path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire the full pipeline: values-secret -> Vault -> ESO -> Ansible -> initdata.
The init-data-gzipper reads a registry-ca Secret from the imperative namespace,
splits multi-cert PEM chains into individual certs, and renders them as
extra_root_certificates in cdh.toml. When no CA secret exists, behavior is
unchanged (backward compatible with connected clusters).

Also adds cache-registry-ca Makefile target and enables kbs.registryCa in
the baremetal topology.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the broken yq|jq pipeline with Python3+yaml that auto-detects
both old format (reference-values.json array) and new format
(reference_value dict with base64-encoded RVPS entries). Fixes
extraction for coco-tools 0.5.1 / Trustee 1.2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ACM's MCH hardcodes source: redhat-operators for the MCE child
subscription. In disconnected environments the default CatalogSource
is disabled and replaced with cs-redhat-operator-index-v4-21.
Without this override, MCH stays stuck in Installing forever
because it can never find the MCE operator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ACM creates an MCE subscription as a dependency. OLM needs the
multicluster-engine package in the mirrored catalog to resolve it.
Without this, MCH stays stuck in Installing with
"no operators found in package multicluster-engine".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Manual-approval subscriptions (sandbox, trustee, GPU) block the
parent app sync at wave 0, preventing vault init and SA creation.
Moving them to wave 10 lets vault initialize at wave 5 first.

Sandbox and trustee already had wave 10. GPU was missing it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VP operator images use hybrid OCI/Docker manifests (OCI index parent
with Docker v2 children) that Quay 3.17 rejects with MANIFEST_INVALID.

Workaround: extract amd64 single-arch manifest and create IDMS mapping
the original parent index digest to the amd64 child digest.

Upstream bug: validatedpatterns/patterns-operator#778

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
osc-pccs-rhel9:latest and osc-tdx-qgs-rhel9:latest do not exist on
registry.redhat.io. Only digest-pinned refs work. Removing the tag
entries eliminates 2 oc-mirror errors per run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quay has ANONYMOUS_ACCESS: true — Helm chart repos are public. Mounting
the pull-secret as HELM_REGISTRY_CONFIG causes ArgoCD to send Basic
auth that Quay rejects (expects Bearer tokens). Unauthenticated pulls
work correctly.

Also: mirror-registry step 4b not needed — all images go to Quay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Intel certified operator controller images (intel-deviceplugin-operator,
intel-tdx-dcap-operator) fail oc-mirror due to missing cosign signatures.
Add them to the fixup list alongside VP operator images.

Also consolidate the IDMS to cover all certified operator sources
(VP, Intel, NVIDIA, Hashicorp) in a single idms-certified-operators
resource.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NVIDIA bundle image fails oc-mirror (signature lookup). GPU operator
subscription at wave 10 blocks trustee (wave 20) and workloads (wave 30).

Disabled: gpu-operator subscription, nvidia-gpu app, gpu-workload app.
Re-enable when NVIDIA bundle is mirrored or upstream fix lands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oc-mirror generates idms-oc-mirror.yaml and itms-oc-mirror.yaml with
operator and release image mirror mappings, but they were never applied
to the cluster. Without them, tag-based operator image pulls (NFD, etc.)
can't resolve to the Quay mirror.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oc-mirror fails to mirror 10 additional images from certified operator
catalogs because cosign signature lookup fails. Add all of them to the
fixup mirror list so they're pushed to Quay by airgap-post-install.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Intel TDX DCAP operator creates QGS pods requiring privileged access
(hostPath, runAsUser 0) but its CSV doesn't declare SCC requirements.
Add ClusterRoleBinding granting privileged SCC to the default SA in
openshift-operators namespace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
QGS DaemonSet uses serviceAccount intel-tdx-dcap, not default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Uses pcsclient.py from the Intel PCS Client Tool repo
(intel/confidential-computing.tee.dcap) instead of pck-cert-tool
extracted from the QGS container (which no longer contains it).

Usage: make pck-register INTEL_PCS_API_KEY=<key>

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All operator subscriptions now at sync-wave 10 so wave 0 only contains
infrastructure (namespaces, SA, CRBs, CronJobs, AppProjects). This
lets the vault hook at wave 5 run after the imperative SA is created,
before any subscription health check can block the parent sync.

Testing whether this eliminates the vault init deadlock.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ESO and LVM are vault infrastructure dependencies:
- LVM provides storage for vault PVC
- ESO delivers secrets from vault to workload namespaces

Moving them to wave 10 blocked vault initialization (PVC unbound).
Only non-infrastructure subscriptions should be at wave 10.

Wave 0: ESO, LVM (vault dependencies)
Wave 10: ACM, sandbox, trustee, cert-manager, CNV, NFD, Intel, GPU

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lt PVC

CNV (kubevirt-hyperconverged) deploys the hostpath provisioner used
for vault storage on SNO. Must be at wave 0 with ESO and LVM.

Wave 0: ESO, LVM, CNV (vault infrastructure)
Wave 10: ACM, sandbox, trustee, cert-manager, NFD, Intel, GPU

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The vault hook must wait for the full chain:
- CNV operator install (~5 min) → hostpath provisioner
- StorageClass creation + PVC bind (~2 min)
- Vault pod startup (~2 min)
- unsealvault-cronjob init + unseal (up to 10 min)
- 120s post-unseal sleep in hook script

600s (10 min) was too short. 1800s (30 min) provides adequate margin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
values-baremetal.yaml set serviceAccountName: imperative-admin-sa,
which collides with the chart's adminServiceAccountName default
(also imperative-admin-sa). This caused ArgoCD RepeatedResourceWarning.

Remove the override — let the chart use its defaults:
- imperative-sa (serviceAccountCreate)
- imperative-admin-sa (adminServiceAccountCreate)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The imperative cronjob (init-data-gzipper, unsealvault) uses
imperative-sa which defaults to read-only (get/list/watch). These
jobs need to create/update ConfigMaps, secrets, and vault resources.

Override clusterRoleYaml to grant full admin access to imperative-sa,
matching what imperative-admin-sa already has. This avoids the
duplicate SA issue from overriding serviceAccountName to
imperative-admin-sa.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OLM pulls patterns-operator bundle by tag from
quay.io/community-operator-pipeline-prod. Without an ITMS entry,
the tag-based pull goes to the upstream (blackholed) registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d to Quay

Step 6b now has two separate image lists:
- VP hybrid manifest images → mirror-registry (--keep-manifest-list=true,
  preserves parent OCI index digest)
- Certified operator images → Quay (single-arch, signature failures only)

Also creates separate IDMS for each registry:
- idms-mirror-registry: VP operator → 172.25.36.135:8443
- idms-certified-operators: Intel/NVIDIA/Hashicorp → Quay

No manual steps 9-10 needed — all handled by the script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kata-cc emptyDir volume ownership doesn't apply fsGroup correctly,
causing the init container's shell redirect to fail with permission
denied when writing to /var/www/html/secret.txt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kata-cc with restricted-v2 SCC prevents direct writes to the emptyDir
mount. Write to /tmp first then copy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tibility

kata-cc with restricted-v2 SCC doesn't apply fsGroup to emptyDir
mounts, making /var/www/html unwritable. Mount at /tmp/shared instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ptyDir writes

kata-cc + restricted-v2 SCC prevents writes to emptyDir mounts.
Add a dedicated ServiceAccount with privileged SCC binding so the
pod runs as root and can write to the shared volume.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three-phase workflow separated by connectivity requirement:
- make snp-collect-vcek-urls  (cluster-connected, no internet)
- make snp-download-vcek      (internet-connected, no cluster)
- make snp-gen-overrides      (local, generates chart values + secret entries)

Also updates values-hw-amd-snp.yaml to enable SNP attestation and
disable intel-tdx-dcap subscription.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant