diff --git a/components/egress/main.go b/components/egress/main.go index 347e87869..d74c98c23 100644 --- a/components/egress/main.go +++ b/components/egress/main.go @@ -50,6 +50,11 @@ func main() { ctx = withLogger(ctx) defer log.Logger.Sync() + // Erase any stale mitmproxy CA left on the shared volume by a previous + // egress generation so the agent's bootstrap wait-loop blocks for this + // generation's export. See PurgeStaleExportedCA / upstream issue #1370. + mitmproxy.PurgeStaleExportedCA() + otelShutdown, err := telemetry.Init(ctx) if err != nil { log.Warnf("OpenTelemetry metrics disabled (continuing without OTLP): %v", err) diff --git a/components/egress/pkg/mitmproxy/cacert_export.go b/components/egress/pkg/mitmproxy/cacert_export.go index 258767aa8..3fc235dd4 100644 --- a/components/egress/pkg/mitmproxy/cacert_export.go +++ b/components/egress/pkg/mitmproxy/cacert_export.go @@ -62,6 +62,40 @@ func waitMitmCACertPath(confDirEnv, home string) (string, error) { return "", fmt.Errorf("mitmproxy CA not found after %v (tried: %v)", waitCACert, cands) } +// PurgeStaleExportedCA removes any mitmproxy-ca-cert.pem left on the shared +// volume by a previous generation of this egress container. mitmproxy's CA +// lives in the egress container's ephemeral confdir, so an egress container +// restart rotates the CA while the previous public cert is still on the +// (pod-scoped) shared volume; without this purge, an agent container starting +// alongside us would pass its bootstrap readiness check on the stale file +// and install a CA that no longer matches what mitmproxy will sign with. +// See upstream issue #1370. Must be called as early as possible in main(). +func PurgeStaleExportedCA() { + if !constants.IsTruthy(os.Getenv(constants.EnvMitmproxyTransparent)) { + return + } + purgeStaleExportedCAFrom(constants.OpenSandboxRootDir) +} + +// purgeStaleExportedCAFrom is the testable core of PurgeStaleExportedCA. +func purgeStaleExportedCAFrom(rootDir string) { + path := filepath.Join(rootDir, mitmCACertName) + err := os.Remove(path) + switch { + case err == nil: + // warn: on first pod startup this file should not exist; its presence + // means the egress container (or process) has restarted, which is + // useful signal when diagnosing HTTPS-from-agent problems. + log.Warnf("[mitmproxy] removed stale exported CA at %s (previous egress generation left it behind; see upstream issue #1370)", path) + case os.IsNotExist(err): + // Common on first pod startup. + default: + // Non-fatal: SyncRootCA will overwrite the file. But on the race + // path the agent may still grab the old contents before we do. + log.Warnf("[mitmproxy] failed to remove stale exported CA at %s: %v", path, err) + } +} + func SyncRootCA(confDirEnv, home string) error { src, err := waitMitmCACertPath(confDirEnv, home) if err != nil { diff --git a/components/egress/pkg/mitmproxy/cacert_export_test.go b/components/egress/pkg/mitmproxy/cacert_export_test.go index 13731c759..157d07936 100644 --- a/components/egress/pkg/mitmproxy/cacert_export_test.go +++ b/components/egress/pkg/mitmproxy/cacert_export_test.go @@ -15,6 +15,7 @@ package mitmproxy import ( + "os" "path/filepath" "testing" @@ -33,3 +34,41 @@ func TestCandidateCACertPaths(t *testing.T) { filepath.Join(h, ".mitmproxy", mitmCACertName), }, got) } + +func TestPurgeStaleExportedCAFrom_RemovesExistingFile(t *testing.T) { + root := t.TempDir() + path := filepath.Join(root, mitmCACertName) + require.NoError(t, os.WriteFile(path, []byte("stale-ca"), 0o644)) + + purgeStaleExportedCAFrom(root) + + _, err := os.Stat(path) + require.True(t, os.IsNotExist(err), "expected file to be removed, got err=%v", err) +} + +func TestPurgeStaleExportedCAFrom_NoFileIsNoOp(t *testing.T) { + root := t.TempDir() + // No file present; must not panic and must not create anything. + purgeStaleExportedCAFrom(root) + + entries, err := os.ReadDir(root) + require.NoError(t, err) + require.Empty(t, entries) +} + +func TestPurgeStaleExportedCAFrom_LeavesUnrelatedFiles(t *testing.T) { + root := t.TempDir() + stale := filepath.Join(root, mitmCACertName) + require.NoError(t, os.WriteFile(stale, []byte("stale-ca"), 0o644)) + + // A sibling file that must survive (e.g. the agent's merged bundle). + other := filepath.Join(root, "merged-ca-certificates.pem") + require.NoError(t, os.WriteFile(other, []byte("merged"), 0o644)) + + purgeStaleExportedCAFrom(root) + + _, err := os.Stat(stale) + require.True(t, os.IsNotExist(err)) + _, err = os.Stat(other) + require.NoError(t, err) +}