1 Commits

Author SHA1 Message Date
roof fa746a3b30 fix: restore cosign pubkey on setup so clean reinstall keeps image verification
Unit Tests / test (push) Successful in 9m50s
`make reinstall`/`uninstall` run `rm -rf config/`, which deletes the git-tracked
config/cosign/cosign.pub. Nothing recreated it, so after any clean reinstall the
bind-mounted key was missing and cosign verification failed for EVERY store
service under the default enforce mode ("loading public key: open
/app/config/cosign/cosign.pub: no such file or directory") — store installs were
completely broken on a fresh install. Found during clean-build pic1 verification.

setup_cell.ensure_cosign_pubkey() now restores the key from git HEAD on every
setup (best-effort; warns rather than fails outside a git checkout). Also fixes
the stale service_composer comment that claimed a Dockerfile COPY that never
existed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 11:32:19 -04:00
3 changed files with 104 additions and 3 deletions
+4 -3
View File
@@ -35,9 +35,10 @@ _SAFE_ID_RE = re.compile(r'^[a-z0-9][a-z0-9_-]{0,63}$')
_DIGEST_RE = re.compile(r'@sha256:[0-9a-f]{64}$') _DIGEST_RE = re.compile(r'@sha256:[0-9a-f]{64}$')
# Bundled cosign public key — shipped in the repo (config/cosign/cosign.pub) so # Bundled cosign public key — shipped in the repo (config/cosign/cosign.pub) so
# every cell can verify store-service image signatures offline. install.sh keeps # every cell can verify store-service image signatures offline. It is bind-mounted
# it at /opt/pic/config/cosign/cosign.pub; in the cell-api container it is # into cell-api at /app/config/cosign/cosign.pub (see docker-compose.yml). Because
# COPYed to /app/config/cosign/cosign.pub. # `make reinstall`/`uninstall` run `rm -rf config/`, setup_cell.ensure_cosign_pubkey()
# restores it from git on every setup so the mount is never empty.
_COSIGN_PUBKEY_PATH = os.environ.get( _COSIGN_PUBKEY_PATH = os.environ.get(
'PIC_COSIGN_PUBKEY', '/app/config/cosign/cosign.pub' 'PIC_COSIGN_PUBKEY', '/app/config/cosign/cosign.pub'
) )
+33
View File
@@ -62,6 +62,38 @@ def ensure_file(rel):
print(f'[EXISTS] {rel}') print(f'[EXISTS] {rel}')
def ensure_cosign_pubkey():
"""Restore the tracked cosign public key if a config wipe removed it.
`config/cosign/cosign.pub` is a git-tracked asset bind-mounted into cell-api
and used to verify store-service image signatures. `make reinstall`/
`uninstall` run `rm -rf config/`, which deletes it from the working tree, and
nothing else recreates it leaving every store install broken under the
default enforce mode. Restore it from HEAD here (setup runs on every
install/reinstall). Best-effort: if this is not a git checkout, warn rather
than fail install.sh surfaces the same warning.
"""
rel = os.path.join('config', 'cosign', 'cosign.pub')
path = os.path.join(ROOT, rel)
if os.path.exists(path) and os.path.getsize(path) > 0:
print(f'[EXISTS] {rel}')
return
os.makedirs(os.path.dirname(path), exist_ok=True)
try:
blob = subprocess.run(
['git', '-C', ROOT, 'show', 'HEAD:config/cosign/cosign.pub'],
capture_output=True, check=True).stdout
if blob:
with open(path, 'wb') as f:
f.write(blob)
print(f'[RESTORED] {rel} (from git HEAD)')
return
except Exception as e:
print(f'[WARN] could not restore {rel} from git: {e}')
print(f'[WARN] {rel} is missing — store-service image signature '
'verification will fail under enforce mode until it is provided')
def ensure_caddy_ca_cert(): def ensure_caddy_ca_cert():
cert_dir = os.path.join(ROOT, 'config', 'caddy', 'certs') cert_dir = os.path.join(ROOT, 'config', 'caddy', 'certs')
ca_key = os.path.join(cert_dir, 'ca.key') ca_key = os.path.join(cert_dir, 'ca.key')
@@ -402,6 +434,7 @@ def main():
for f in REQUIRED_FILES: for f in REQUIRED_FILES:
ensure_file(f) ensure_file(f)
ensure_cosign_pubkey()
ensure_caddy_ca_cert() ensure_caddy_ca_cert()
priv, _pub = generate_wg_keys() priv, _pub = generate_wg_keys()
write_wg0_conf(priv, vpn_address, wg_port) write_wg0_conf(priv, vpn_address, wg_port)
+67
View File
@@ -0,0 +1,67 @@
"""Tests for scripts/setup_cell.py setup helpers."""
import os
import sys
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts'))
import setup_cell # noqa: E402
class TestEnsureCosignPubkey(unittest.TestCase):
"""ensure_cosign_pubkey restores the tracked key after a `rm -rf config/`.
Regression: `make reinstall`/`uninstall` wipe config/, deleting the tracked
config/cosign/cosign.pub; without restore, enforce-mode store installs break.
"""
KEY_REL = os.path.join('config', 'cosign', 'cosign.pub')
KEY_BODY = '-----BEGIN PUBLIC KEY-----\nTESTKEYDATA\n-----END PUBLIC KEY-----\n'
def setUp(self):
self.tmp = tempfile.mkdtemp()
env = {**os.environ, 'GIT_CONFIG_GLOBAL': '/dev/null', 'GIT_CONFIG_SYSTEM': '/dev/null'}
subprocess.run(['git', 'init', '-q', self.tmp], check=True, env=env)
subprocess.run(['git', '-C', self.tmp, 'config', 'user.email', 't@t'], check=True)
subprocess.run(['git', '-C', self.tmp, 'config', 'user.name', 't'], check=True)
self.key = os.path.join(self.tmp, self.KEY_REL)
os.makedirs(os.path.dirname(self.key))
with open(self.key, 'w') as f:
f.write(self.KEY_BODY)
subprocess.run(['git', '-C', self.tmp, 'add', '-A'], check=True)
subprocess.run(['git', '-C', self.tmp, 'commit', '-qm', 'init'], check=True, env=env)
self._root = setup_cell.ROOT
setup_cell.ROOT = self.tmp
def tearDown(self):
setup_cell.ROOT = self._root
shutil.rmtree(self.tmp, ignore_errors=True)
def test_restores_key_when_wiped(self):
os.remove(self.key)
shutil.rmtree(os.path.dirname(self.key)) # mimic `rm -rf config/`
self.assertFalse(os.path.exists(self.key))
setup_cell.ensure_cosign_pubkey()
self.assertTrue(os.path.exists(self.key))
self.assertEqual(open(self.key).read(), self.KEY_BODY)
def test_noop_when_key_present(self):
setup_cell.ensure_cosign_pubkey()
self.assertEqual(open(self.key).read(), self.KEY_BODY)
def test_warns_not_raises_outside_git(self):
# Not a git checkout and key missing → must warn, never raise.
non_git = tempfile.mkdtemp()
setup_cell.ROOT = non_git
try:
setup_cell.ensure_cosign_pubkey() # should not raise
self.assertFalse(os.path.exists(os.path.join(non_git, self.KEY_REL)))
finally:
shutil.rmtree(non_git, ignore_errors=True)
if __name__ == '__main__':
unittest.main()