diff --git a/internal/aggregated/servingcert/servingcert.go b/internal/aggregated/servingcert/servingcert.go index d2ba3df94..a7f733c82 100644 --- a/internal/aggregated/servingcert/servingcert.go +++ b/internal/aggregated/servingcert/servingcert.go @@ -116,6 +116,9 @@ func Generate(namespace string, now time.Time) (*Bundle, error) { caCert, err := certutil.NewSelfSignedCACert(certutil.Config{ CommonName: fmt.Sprintf("%s-ca@%d", ServiceName, now.Unix()), Organization: []string{"coder-k8s"}, + // certutil otherwise uses its own time.Now() with no backdating, so a clock that is behind + // the issuer's (another replica, kube-apiserver, or an injected now) sees a future CA. + NotBefore: now.Add(-clockSkewAllowance), }, caKey) if err != nil { return nil, fmt.Errorf("generate CA certificate: %w", err) diff --git a/internal/aggregated/servingcert/servingcert_test.go b/internal/aggregated/servingcert/servingcert_test.go index 93d3948b9..bba28568d 100644 --- a/internal/aggregated/servingcert/servingcert_test.go +++ b/internal/aggregated/servingcert/servingcert_test.go @@ -78,6 +78,29 @@ func TestGenerateProducesVerifiableServingCert(t *testing.T) { } } +// The CA must take its validity from the injected clock and be backdated like the serving +// certificate. Otherwise a replica or kube-apiserver whose clock is behind the issuer's rejects a +// fresh CA as "not valid until", and Parse(Generate(now), now) fails across a second boundary. +func TestGenerateDatesCAFromInjectedClock(t *testing.T) { + now := time.Now().Add(-30 * time.Minute) + b, err := Generate(testNS, now) + if err != nil { + t.Fatal(err) + } + // x509 encodes validity with one-second precision. + if want := now.Add(-clockSkewAllowance).Truncate(time.Second); !b.CACert.NotBefore.Equal(want) { + t.Fatalf("CA NotBefore = %s, want %s", b.CACert.NotBefore, want) + } + roots := x509.NewCertPool() + roots.AddCert(b.CACert) + if _, err := b.Cert.Verify(x509.VerifyOptions{DNSName: DNSNames(testNS)[2], Roots: roots, CurrentTime: now}); err != nil { + t.Fatalf("serving certificate does not verify at the injected time: %v", err) + } + if _, err := Parse(secretFor(b), testNS, now); err != nil { + t.Fatalf("generated bundle does not parse at the injected time: %v", err) + } +} + func TestEnsureCreatesMarkedSecretAndServesIt(t *testing.T) { client := fake.NewClientset() m := newTestManager(t, client, time.Now())