{"record":{"id":"d81209c0b388d33d","repo":"thanos-io/thanos","slug":"client-credentials","errorCode":null,"errorMessage":"client credentials","messagePattern":"client credentials","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/tls/options.go","lineNumber":222,"sourceCode":"}\n\nfunc (m *clientTLSManager) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {\n\tm.mtx.Lock()\n\tdefer m.mtx.Unlock()\n\n\tstatCert, err := os.Stat(m.certPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstatKey, err := os.Stat(m.keyPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif m.cert == nil || !statCert.ModTime().Equal(m.certModTime) || !statKey.ModTime().Equal(m.keyModTime) {\n\t\tcert, err := tls.LoadX509KeyPair(m.certPath, m.keyPath)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"client credentials\")\n\t\t}\n\t\tm.certModTime = statCert.ModTime()\n\t\tm.keyModTime = statKey.ModTime()\n\t\tm.cert = &cert\n\t}\n\n\treturn m.cert, nil\n}\n\ntype validOption struct {\n\ttlsOption map[string]uint16\n}\n\nfunc (validOption validOption) joinString() string {\n\tvar keys []string\n\n\tfor key := range validOption.tlsOption {\n\t\tkeys = append(keys, key)","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/tls/options.go#L204-L240","documentation":"The client TLS certificate manager loads the mTLS key pair via tls.LoadX509KeyPair on demand and on mtime change. Failure (missing/unreadable/mismatched/malformed files) is wrapped as \"client credentials\".","triggerScenarios":"getClientCertificate is called during a TLS handshake (initial or after cert/key mtime change) and LoadX509KeyPair fails on certPath/keyPath.","commonSituations":"Client cert rotated and key regenerated so the pair no longer matches; key file has a passphrase or wrong permissions; only cert was mounted; paths from env vars pointing to wrong files; cert/key written non-atomically during rotation.","solutions":["Confirm cert and key match: compare modulus/sha of openssl x509 -noout -modulus -in cert and openssl pkey -in key -pubout.","Fix file permissions so the process can read the key (chmod 0600, correct owner).","Rotate both files atomically (write to temp then rename) to avoid mid-rotation mismatches.","Verify the cert/key paths in config point at the intended files."],"exampleFix":"// before\nclient, err := tls.StoreClientTLSCredentials(logger, cert, key, ca, ...) // key path stale after rotation\n// after\n// re-issue cert+key as a matched pair and update both paths\nopenssl x509 -noout -modulus -in client.crt | openssl md5\nopenssl rsa -noout -modulus -in client.key | openssl md5  # must match","handlingStrategy":"try-catch","validationCode":"func validateClientPair(certPath, keyPath string) error {\n    if _, err := tls.LoadX509KeyPair(certPath, keyPath); err != nil {\n        return fmt.Errorf(\"client cert/key invalid: %w\", err)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"cert, err := mgr.getClientCertificate(cri)\nif err != nil {\n    level.Error(logger).Log(\"msg\", \"client cert reload failed; keeping previous cert\", \"err\", err)\n    return m.lastGoodCert, nil\n}","preventionTips":["Issue cert and key as a matched pair from the same CSR.","Rotate files atomically (temp + rename).","Check key permissions (0600, correct owner).","Validate pair with openssl before replacing on disk."],"tags":["tls","mtls","certificate","file"],"backgroundTag":"file-read-failed","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}