From 0f4584e649e842a08e5eadf991d2d367af75caf6 Mon Sep 17 00:00:00 2001 From: Ananth Date: Tue, 14 Jul 2026 16:06:22 +0000 Subject: [PATCH] fix(certificatee): quiet unmanaged vault certificates --- cmd/certificatee/main.go | 35 +++++++++++++++++- cmd/certificatee/main_test.go | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/cmd/certificatee/main.go b/cmd/certificatee/main.go index 8cccf44..5c21f92 100644 --- a/cmd/certificatee/main.go +++ b/cmd/certificatee/main.go @@ -20,6 +20,8 @@ import ( var ( version = "dev" // GoReleaser will inject the Git tag here + + errNoUsableVaultCertificate = errors.New("no usable Vault certificate") ) type certificateStore interface { @@ -32,6 +34,20 @@ type certificateSyncResult struct { storageSynced bool runtimeUpdated bool reason string + skipReason string +} + +type noUsableVaultCertificateError struct { + domains []string + cause error +} + +func (e *noUsableVaultCertificateError) Error() string { + return fmt.Sprintf("no usable Vault certificate found for candidates %s: %v", strings.Join(e.domains, ", "), e.cause) +} + +func (e *noUsableVaultCertificateError) Unwrap() []error { + return []error{errNoUsableVaultCertificate, e.cause} } func main() { @@ -137,6 +153,7 @@ func processHAProxyEndpoint(logger *logrus.Logger, cfg config.Config, vaultClien var errs []error var expiringCount int + var skippedVaultCount int for _, ref := range certRefs { displayName := ref.DisplayName @@ -180,6 +197,12 @@ func processHAProxyEndpoint(logger *logrus.Logger, cfg config.Config, vaultClien continue } + if syncResult.skipReason != "" { + skippedVaultCount++ + logger.Debugf("[%s] Skipping certificate %s: %s", endpoint, displayName, syncResult.skipReason) + continue + } + if syncResult.storageSynced { logger.Infof("[%s] Certificate %s persisted to storage", endpoint, displayName) } @@ -195,6 +218,9 @@ func processHAProxyEndpoint(logger *logrus.Logger, cfg config.Config, vaultClien // Record expiring certificates count certmetrics.CertificatesExpiring.WithLabelValues(endpoint).Set(float64(expiringCount)) + if skippedVaultCount > 0 { + logger.Infof("[%s] Skipped %d certificate(s) without usable Vault replacement material", endpoint, skippedVaultCount) + } return errors.Join(errs...) } @@ -403,7 +429,10 @@ func readValidVaultCertificateBundle(domains []string, vaultClient certificateSt return "", nil, nil, fmt.Errorf("no Vault certificate candidates found") } - return "", nil, nil, fmt.Errorf("no usable Vault certificate found for candidates %s: %w", strings.Join(domains, ", "), errors.Join(errs...)) + return "", nil, nil, &noUsableVaultCertificateError{ + domains: domains, + cause: errors.Join(errs...), + } } func firstDomain(domains []string) string { @@ -425,6 +454,10 @@ func syncCertificate(certPath string, domains []string, vaultClient certificateS domain, certificateSecrets, vaultCert, err := readValidVaultCertificateBundle(domains, vaultClient, haproxyCert) if err != nil { + if !isExpiring && errors.Is(err, errNoUsableVaultCertificate) { + result.skipReason = err.Error() + return result, nil + } return result, err } result.domain = domain diff --git a/cmd/certificatee/main_test.go b/cmd/certificatee/main_test.go index 4274da1..9a9dec2 100644 --- a/cmd/certificatee/main_test.go +++ b/cmd/certificatee/main_test.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "errors" "fmt" "io" "math/big" @@ -317,6 +318,72 @@ func TestProcessHAProxyEndpointMarksV3ReadyEndpoint(t *testing.T) { } } +func TestSyncCertificateSkipsNonExpiringUnusableVaultCandidate(t *testing.T) { + logger := logrus.New() + logger.SetLevel(logrus.PanicLevel) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected HAProxy request for skipped certificate: %s %s", r.Method, r.URL.String()) + })) + defer server.Close() + + haproxyClient, err := haproxy.NewClient(haproxy.ClientConfig{BaseURL: server.URL}, logger) + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + + result, err := syncCertificate( + "certs/_.mainnet.rpcpool.com.pem", + []string{"*.mainnet.rpcpool.com"}, + fakeCertificateStore{}, + haproxyClient, + &haproxy.CertificateDetail{NotAfter: time.Now().AddDate(0, 0, 60)}, + 30, + ) + if err != nil { + t.Fatalf("syncCertificate() error = %v, want nil", err) + } + if result.skipReason == "" { + t.Fatal("skipReason is empty, want non-empty") + } + if result.storageSynced || result.runtimeUpdated { + t.Fatalf("storageSynced=%v runtimeUpdated=%v, want both false", result.storageSynced, result.runtimeUpdated) + } +} + +func TestSyncCertificateErrorsWhenExpiringWithoutUsableVaultCandidate(t *testing.T) { + logger := logrus.New() + logger.SetLevel(logrus.PanicLevel) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected HAProxy request for failed certificate: %s %s", r.Method, r.URL.String()) + })) + defer server.Close() + + haproxyClient, err := haproxy.NewClient(haproxy.ClientConfig{BaseURL: server.URL}, logger) + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + + result, err := syncCertificate( + "certs/_.mainnet.rpcpool.com.pem", + []string{"*.mainnet.rpcpool.com"}, + fakeCertificateStore{}, + haproxyClient, + &haproxy.CertificateDetail{NotAfter: time.Now().AddDate(0, 0, 10)}, + 30, + ) + if err == nil { + t.Fatal("syncCertificate() error = nil, want error") + } + if !errors.Is(err, errNoUsableVaultCertificate) { + t.Fatalf("syncCertificate() error = %v, want no usable Vault certificate", err) + } + if !result.isExpiring { + t.Fatal("isExpiring = false, want true") + } +} + func TestSyncCertificatePersistsStorageWhenRuntimeCurrent(t *testing.T) { logger := logrus.New() logger.SetLevel(logrus.PanicLevel)