{"record":{"id":"abd07ace42e5637f","repo":"kubernetes/kops","slug":"intermediate-certificate-fetch-recently-failed-for","errorCode":null,"errorMessage":"intermediate certificate fetch recently failed for signer issuer %q (cached)","messagePattern":"intermediate certificate fetch recently failed for signer issuer %q \\(cached\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/cloudup/azure/attest.go","lineNumber":349,"sourceCode":"\n// intermediateCertPoolWithCaches performs a cached lookup against the supplied positive and\n// negative TTL caches, invoking fetch on a miss. Tests inject their own stores and fetchers.\nfunc intermediateCertPoolWithCaches(signer *x509.Certificate, fetch func(*x509.Certificate) (*x509.CertPool, error), positive, negative expirationcache.Store) (*x509.CertPool, error) {\n\tif signer == nil {\n\t\treturn nil, fmt.Errorf(\"signer certificate is required\")\n\t}\n\n\tkeyStr := intermediateCacheKeyForSigner(signer)\n\n\t// Positive cache wins over negative: a successful later fetch overwrites any stale negative entry,\n\t// which expires on its own shorter TTL.\n\tif obj, ok, _ := positive.GetByKey(keyStr); ok {\n\t\tklog.V(4).Infof(\"Intermediate certificate cache hit (positive) for signer issuer %q\", signer.Issuer)\n\t\treturn obj.(*intermediateCertCacheEntry).pool, nil\n\t}\n\tif _, ok, _ := negative.GetByKey(keyStr); ok {\n\t\tklog.V(4).Infof(\"Intermediate certificate cache hit (negative) for signer issuer %q\", signer.Issuer)\n\t\treturn nil, fmt.Errorf(\"intermediate certificate fetch recently failed for signer issuer %q (cached)\", signer.Issuer)\n\t}\n\n\tklog.V(2).Infof(\"Intermediate certificate cache miss for signer issuer %q\", signer.Issuer)\n\tpool, fetchErr := fetch(signer)\n\tentry := &intermediateCertCacheEntry{key: keyStr, pool: pool}\n\tif fetchErr != nil {\n\t\t// List() walks every entry and lazily deletes expired ones; ListKeys() would not trigger\n\t\t// expiration. Doing this before each write bounds cache memory to ~(write_rate × TTL) without a\n\t\t// background goroutine, which matters most for the negative cache since an attacker rotating\n\t\t// issuer keys can drive writes to it at the fetch rate. Cost is O(N) per write, so in attack\n\t\t// conditions writes become slower as the cache grows, which also acts as a natural rate limit.\n\t\t// For legitimate traffic (a handful of entries), this is effectively free.\n\t\t_ = negative.List()\n\t\t_ = negative.Add(entry)\n\t\treturn nil, fetchErr\n\t}\n\t// Evict expired entries before writing (same rationale as negative cache above).\n\t_ = positive.List()","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/cloudup/azure/attest.go#L331-L367","documentation":"intermediateCertPoolWithCaches maintains a negative TTL cache of failed intermediate-certificate fetches keyed by signer issuer. If a previous fetch for this issuer recently failed, the negative entry short-circuits the lookup and this error is returned instead of hammering the AIA endpoint again. The negative entry expires after its own (shorter) TTL, after which a new fetch is attempted.","triggerScenarios":"Calling intermediateCertPoolForSigner for a signer whose issuer previously failed an AIA fetch within the negative-cache TTL, and no positive cache entry exists (positive entries always win over stale negatives).","commonSituations":"Transient network outage to the CA's AIA URL; the CA endpoint returning non-200; DNS failures; repeated retries during the negative-cache window all fail fast with this cached error.","solutions":["Wait for the negative cache TTL to expire and retry","Force a cache invalidation / restart the component to clear the negative entry","Fix the underlying fetch failure (network reachability, DNS, CA endpoint health) so the next fetch succeeds and populates the positive cache","Check whether the signer's AIA URL is correct and reachable from the cluster"],"exampleFix":"// before\n// retrying immediately returns the cached negative error\npool, err := intermediateCertPoolForSigner(signer)\n// after\nif err != nil && strings.Contains(err.Error(), \"(cached)\") {\n    time.Sleep(negativeCacheTTL) // or invalidate cache entry, then retry\n    pool, err = intermediateCertPoolForSigner(signer)\n}","handlingStrategy":"retry","validationCode":"// Optionally peek at whether a negative entry exists before calling, if the caches are shared\nif _, ok, _ := negativeCache.GetByKey(intermediateCacheKeyForSigner(signer)); ok {\n    time.Sleep(negativeCacheTTL) // or skip/requeue the operation\n}","typeGuard":null,"tryCatchPattern":"pool, err := intermediateCertPoolForSigner(signer)\nif err != nil && strings.Contains(err.Error(), \"(cached)\") {\n    // back off until the negative TTL lapses, then retry once\n    time.Sleep(negativeCacheTTL)\n    pool, err = intermediateCertPoolForSigner(signer)\n}\nif err != nil { return nil, err }","preventionTips":["Fix the root fetch failure instead of retrying inside the negative-cache window","Provide an escape hatch to invalidate negative cache entries after remediation","Keep negative TTL short so transient outages self-heal"],"tags":["azure","tls","cache","negative-cache","aia-fetch"],"backgroundTag":"negative-cache-hit","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}