feat(build): opt-in PKG_CACHE build arg for the internal package cache (HTTPS, GPG-verified) #31

Merged
jknapp merged 3 commits from feat/pkg-cache-build-arg into trunk 2026-10-21 23:46:40 +00:00
Owner

Opt-in PKG_CACHE build arg pointing dnf/apt at the internal package cache, CT130 pkg-cache, https://192.168.1.40. Without the arg, builds use upstream repositories as before. The one change in that path is that the release RPMs are now GPG-verified. Update 2026-10-21 (Josh approved):

  • The runner firewall exception is applied on pve and pve3 (443/5000 to 192.168.1.40 only) and verified from the runner bridges.
  • The workflow now enables PKG_CACHE through a TLS probe with measured fall-back: <1 s when the cache is refused, 10 s on a silent drop.
  • Added scripts/host/pkgcache-host-repos.sh for WHP hosts, tested on .148 including cache-down fall-back.

See docs/pkg-cache.md.

What changes

  • scripts/pkg-cache-repos.sh, with four modes:
    • rpm: rewrites AlmaLinux/EPEL/Remi baseurls to <cache> <origin> (dnf fails over to the origin), adds a scoped sslcacert, gpgcheck=1, and repo_gpgcheck=1 for Remi and AlmaLinux.
    • apt: rewrites Ubuntu and LiteSpeed URIs and sets a per-host CaInfo.
    • release-install: HTTPS download, package-name check, keys from keys/pkg-cache/, rpm -K must say "signatures OK", then localpkg_gpgcheck=1.
    • restore: puts back rewritten files, deletes created files and any new file referencing the cache, and fails if a reference remains.
  • The script is wired into Dockerfile, Dockerfile.fpm, Dockerfile.shared-httpd and Dockerfile.shared-ols. Dockerfile.litespeed and Dockerfile.lsphp are not wired yet (multi-stage plus the madison check; see docs).
  • keys/pkg-cache/:
    • the name-constrained pkg-cache CA (IP 192.168.1.40 / pkg-cache.* only);
    • the EPEL 10 key, with its fingerprint checked against fedoraproject.org/security;
    • the Remi 2024 key, checked only against Remi's own servers: fingerprint CF1D F005 7CE8 5DFF 5B2F 2A37 C2FD 3B2C 2A09 48E4 confirmed by Josh.
  • docs/pkg-cache.md: cache rules, trust model, the CI plan (firewall exception, probe step, BuildKit mirror with ca=), the WHP host plan, and operations.

Tested on 192.168.1.193 (measured, 2026-10-21)

Build Result
Dockerfile PHPVER=85, PKG_CACHE via http (first version), cold cache rc=0, 181 s; repo+update step 39.8 s; PHP 8.5.12, passed EXPECT_PHP_MIN=8.5.12
same, warm, --no-cache rc=0, 72 s
same, no PKG_CACHE (upstream) the repo+update step alone took 1031.5 s (BaseOS 84 kB/s, remi-release 2.0 kB/s); then I killed the run by removing its builder, so a full completion was not observed
Dockerfile PHPVER=85, https PKG_CACHE (final code), TLS registry mirror rc=0, 89 s; release RPMs "digests signatures OK"; 3× "no reference to 192.168.1.40 left"
Dockerfile.shared-ols 1.8.4/85, https PKG_CACHE rc=0, 26 s (http version: 41 s cold, 15 s warm)
final images 0 cache references in /etc/yum.repos.d, /etc/dnf, /etc/apt; no state dir; no apt drop-in
release-install without PKG_CACHE both RPMs verified from the HTTPS origins; restore is a no-op

Script checks in a throwaway container:

  • http:// PKG_CACHE is refused.
  • A wrong signing key gives SIGNATURES NOT OK and a hard fail.
  • repo_gpgcheck=1 dnf installs php-cli 8.5.12.
  • A new cache-referencing repo file is removed by restore.
  • A leftover reference in a pre-existing file makes restore FATAL.

🤖 Generated with Claude Code

Opt-in `PKG_CACHE` build arg pointing dnf/apt at the internal package cache, **CT130 `pkg-cache`, https://192.168.1.40**. Without the arg, builds use upstream repositories as before. The one change in that path is that the release RPMs are now GPG-verified. **Update 2026-10-21 (Josh approved):** - The runner firewall exception is applied on pve and pve3 (443/5000 to 192.168.1.40 only) and verified from the runner bridges. - The workflow now enables `PKG_CACHE` through a TLS probe with measured fall-back: <1 s when the cache is refused, 10 s on a silent drop. - Added `scripts/host/pkgcache-host-repos.sh` for WHP hosts, tested on .148 including cache-down fall-back. See `docs/pkg-cache.md`. ## What changes - `scripts/pkg-cache-repos.sh`, with four modes: - `rpm`: rewrites AlmaLinux/EPEL/Remi `baseurl`s to `<cache> <origin>` (dnf fails over to the origin), adds a scoped `sslcacert`, `gpgcheck=1`, and `repo_gpgcheck=1` for Remi and AlmaLinux. - `apt`: rewrites Ubuntu and LiteSpeed URIs and sets a per-host CaInfo. - `release-install`: HTTPS download, package-name check, keys from `keys/pkg-cache/`, `rpm -K` must say "signatures OK", then `localpkg_gpgcheck=1`. - `restore`: puts back rewritten files, deletes created files and any new file referencing the cache, and **fails** if a reference remains. - The script is wired into `Dockerfile`, `Dockerfile.fpm`, `Dockerfile.shared-httpd` and `Dockerfile.shared-ols`. `Dockerfile.litespeed` and `Dockerfile.lsphp` are not wired yet (multi-stage plus the madison check; see docs). - `keys/pkg-cache/`: - the name-constrained pkg-cache CA (IP 192.168.1.40 / pkg-cache.* only); - the EPEL 10 key, with its fingerprint checked against fedoraproject.org/security; - the Remi 2024 key, checked only against Remi's own servers: fingerprint `CF1D F005 7CE8 5DFF 5B2F 2A37 C2FD 3B2C 2A09 48E4` **confirmed by Josh**. - `docs/pkg-cache.md`: cache rules, trust model, the CI plan (firewall exception, probe step, BuildKit mirror with `ca=`), the WHP host plan, and operations. ## Tested on 192.168.1.193 (measured, 2026-10-21) | Build | Result | |---|---| | `Dockerfile` PHPVER=85, PKG_CACHE via http (first version), cold cache | rc=0, 181 s; repo+update step 39.8 s; PHP 8.5.12, passed EXPECT_PHP_MIN=8.5.12 | | same, warm, `--no-cache` | rc=0, 72 s | | same, **no PKG_CACHE** (upstream) | the repo+update step alone took **1031.5 s** (BaseOS 84 kB/s, remi-release 2.0 kB/s); then I killed the run by removing its builder, so a full completion was not observed | | `Dockerfile` PHPVER=85, **https** PKG_CACHE (final code), TLS registry mirror | rc=0, 89 s; release RPMs "digests signatures OK"; 3× "no reference to 192.168.1.40 left" | | `Dockerfile.shared-ols` 1.8.4/85, https PKG_CACHE | rc=0, 26 s (http version: 41 s cold, 15 s warm) | | final images | 0 cache references in /etc/yum.repos.d, /etc/dnf, /etc/apt; no state dir; no apt drop-in | | release-install **without** PKG_CACHE | both RPMs verified from the HTTPS origins; restore is a no-op | Script checks in a throwaway container: - `http://` PKG_CACHE is refused. - A wrong signing key gives `SIGNATURES NOT OK` and a hard fail. - `repo_gpgcheck=1` dnf installs php-cli 8.5.12. - A new cache-referencing repo file is removed by restore. - A leftover reference in a pre-existing file makes restore FATAL. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 2 commits 2026-10-21 19:53:48 +00:00
Adds scripts/pkg-cache-repos.sh and wires it into Dockerfile,
Dockerfile.fpm, Dockerfile.shared-httpd and Dockerfile.shared-ols. With
--build-arg PKG_CACHE=http://192.168.1.40 (CT130 pkg-cache), dnf/apt go
through the LAN cache. That cache fetches from the Remi/EPEL/AlmaLinux/
Ubuntu/LiteSpeed origins over HTTPS, with fallback tiers and
stale-on-error. Without the arg the script is a no-op and builds are
unchanged.

- rpm: each baseurl becomes "<cache>/<prefix>/... <origin>/...", so dnf
  fails over to the origin if the cache dies mid-build
- every RUN restores the original repo files before it ends, so no
  shipped layer carries the LAN address
- EPEL/Remi release RPMs come from the cache when it is set
- the php.net version gates are untouched

Dockerfile.litespeed/.lsphp are not wired yet (see docs/pkg-cache.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Security review follow-up for the opt-in PKG_CACHE path:

- PKG_CACHE must be https:// (http is refused). The cache's certificate
  comes from a name-constrained internal CA (keys/pkg-cache/ca.crt, valid
  only for 192.168.1.40 / pkg-cache.*). The CA is trusted only by the
  rewritten repos (dnf sslcacert bundle, apt per-host CaInfo) and only
  for the duration of the RUN.
- epel-release / remi-release are no longer installed straight from a
  URL. release-install downloads them over HTTPS, checks the package name,
  imports the signing keys committed in keys/pkg-cache/ and requires
  `rpm -K` "signatures OK", then installs with localpkg_gpgcheck=1. This
  applies with and without the cache.
- Rewritten repos get gpgcheck=1. Remi and AlmaLinux also get
  repo_gpgcheck=1, since both sign repomd.xml. EPEL does not publish a
  repomd signature, so it relies on end-to-end HTTPS.
- restore also deletes files it created and any new repo/apt file that
  still references the cache, then fails the build if a reference remains
  under /etc/yum.repos.d, /etc/dnf or /etc/apt.
- docs: trust model, replay risk, proposed openssl/httpd/ImageMagick
  minimum-version gates, TLS registry mirror (BuildKit ca= / dockerd
  certs.d, no insecure-registries).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp added 1 commit 2026-10-21 23:46:04 +00:00
- build-push.yaml: Build-and-Push, Build-FPM-Images, Build-Shared-httpd
  and Build-Shared-OLS probe https://192.168.1.40 and :5000 with the
  pinned pkg-cache CA (5 s timeouts). When the cache answers they pass
  PKG_CACHE and configure the BuildKit Docker Hub mirror (ca=). When it
  doesn't, they build from upstream exactly as before and emit a warning.
  Fallback measured from the runner bridge: refused <1 s, silent drop 10 s.
- scripts/host/pkgcache-host-repos.sh: apply/revert/status for WHP hosts.
  Each rewritten repo uses baseurl=<cache> <origin> as the real fallback;
  mirrorlist/metalink stay commented because measured dnf ignores baseurl
  when they are present. Keeps gpgcheck=1 / repo_gpgcheck=1 and backs up
  every file. Tested on 192.168.1.148, including cache-down and
  silent-drop fallback.
- docs: runner firewall rule (applied), measured fallback results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp merged commit cb0cecb557 into trunk 2026-10-21 23:46:40 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cloud-hosting-platform/cloud-apache-container#31