M2 §5: pin auth attestation into InstalledMetadata and inject AUTH_PINNED_FINGERPRINT

Fetch the auth service's key attestation over mTLS at install time and
persist the fingerprint (plus kid/issued-at) in the install record. At
start, inject AUTH_PINNED_FINGERPRINT into every non-oneoff service so
the shared authclient refuses a substituted key instead of silently
trusting it (TOFU/D3). Best-effort: installs that cannot reach the auth
service backfill the pin on the next start. Skip pinning for the auth
service itself (it is the attester).
This commit is contained in:
2026-08-15 23:43:02 +01:00
parent 950b6e5827
commit 77bd21aea2
3 changed files with 96 additions and 2 deletions
+35
View File
@@ -30,6 +30,7 @@ import (
"syscall"
"time"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/authmgmt"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/docker"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/logger"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/types"
@@ -70,6 +71,20 @@ func InstallContainer(ctx context.Context, item types.RegistryContainer) error {
RegistryContainer: item,
InstalledAt: time.Now().UTC().Format(time.RFC3339),
}
// 2.6. Pin the auth service's signing key (security theme M2 §5): fetch its
// attestation over mTLS and store the fingerprint in the install record so
// the container gets AUTH_PINNED_FINGERPRINT at start. Best-effort — when
// the auth service is not reachable yet the pin stays empty and is retried
// on start.
if att, err := fetchAuthAttestation(ctx); err == nil {
metadata.AuthFingerprint = att.Fingerprint
metadata.AuthKid = att.Kid
metadata.AuthAttestedAt = att.IssuedAt
} else {
logger.Warn("Install %s: auth attestation not pinned (will retry at start): %v", item.Name, err)
}
metadataPath := filepath.Join(targetDir, "metadata.json")
if err := writeMetadata(metadataPath, metadata); err != nil {
return fmt.Errorf("failed to write metadata: %w", err)
@@ -453,3 +468,23 @@ func writeMetadata(path string, metadata types.InstalledMetadata) error {
}
return os.WriteFile(path, data, 0644)
}
// fetchAuthAttestation fetches the auth service's public key statement over
// mTLS (Maestro's own identity certificate). It backs the M2 §5 TOFU pin: the
// returned fingerprint is what consumer containers are pinned to.
func fetchAuthAttestation(ctx context.Context) (*authmgmt.Attestation, error) {
client, err := authmgmt.New()
if err != nil {
return nil, fmt.Errorf("auth management client: %w", err)
}
fetchCtx, cancel := context.WithTimeout(ctx, 8*time.Second)
defer cancel()
att, err := client.Attestation(fetchCtx)
if err != nil {
return nil, err
}
if att.Fingerprint == "" {
return nil, fmt.Errorf("attestation carried an empty fingerprint")
}
return att, nil
}