{"record":{"id":"2372a2a4e56d5bb7","repo":"k3s-io/k3s","slug":"new-ca-bundle-contains-only-a-single-certificate-b","errorCode":null,"errorMessage":"new CA bundle contains only a single certificate but should include root or intermediate CA certificates","messagePattern":"new CA bundle contains only a single certificate but should include root or intermediate CA certificates","errorType":"validation","errorClass":null,"httpStatus":500,"severity":"error","filePath":"pkg/server/handlers/cert.go","lineNumber":189,"sourceCode":"\nfunc validateCA(oldCAPath, newCAPath string) error {\n\t// Skip validation if old values are being reused\n\tif oldCAPath == newCAPath {\n\t\treturn nil\n\t}\n\n\toldCerts, err := certutil.CertsFromFile(oldCAPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnewCerts, err := certutil.CertsFromFile(newCAPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(newCerts) == 1 {\n\t\treturn errors.New(\"new CA bundle contains only a single certificate but should include root or intermediate CA certificates\")\n\t}\n\n\troots := x509.NewCertPool()\n\tintermediates := x509.NewCertPool()\n\n\t// Load all certs from the old bundle\n\tfor _, cert := range oldCerts {\n\t\tif len(cert.AuthorityKeyId) == 0 || bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) {\n\t\t\troots.AddCert(cert)\n\t\t} else {\n\t\t\tintermediates.AddCert(cert)\n\t\t}\n\t}\n\n\t// Include any intermediates from the new bundle, in case they're cross-signed by a cert in the old bundle\n\tfor i, cert := range newCerts {\n\t\tif i > 0 {\n\t\t\tif len(cert.AuthorityKeyId) > 0 {","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/k3s-io/k3s/blob/6ba341e396edc16b8dcae978a7c5e3ac7ee5606e/pkg/server/handlers/cert.go#L171-L207","documentation":"During CA rotation (caCertReplace in pkg/server/handlers/cert.go), k3s loads the submitted new CA bundle and rejects it if it contains exactly one certificate. The rotation logic rebuilds root and intermediate pools and verifies the serving/requesting certs against the combined old+new chains, so a single-cert bundle cannot express the root-plus-intermediate (or old-plus-new) chain it needs.","triggerScenarios":"'k3s certificate rotate-ca' (HTTP PUT /v1-k3s/cacert) with a --cacert file containing only a leaf-equivalent single CA cert - e.g. just the new root, or just an intermediate, without the rest of the chain.","commonSituations":"Bundles assembled from a single PEM block; copy/paste of only the intermediate from a provider; chain files truncated during transfer.","solutions":["Build a full chain bundle: concatenate the new root plus any intermediates (and keep the old root during transition) into one PEM file, e.g. 'cat old-root.crt new-intermediate.crt new-root.crt > bundle.crt'.","Verify the bundle before submitting: 'grep -c \"BEGIN CERTIFICATE\" bundle.crt' must be greater than 1, and 'openssl crl2pkcs7 -nocrl -certfile bundle.crt | openssl pkcs7 -print_certs' shows all chain members.","Re-run rotate-ca with the corrected --cacert file."],"exampleFix":"# before: single cert -> error\ncat new-root.crt > bundle.crt\nk3s certificate rotate-ca --cacert=bundle.crt ...\n\n# after: chain bundle (intermediate + root, old root kept during rotation)\ncat old-root.crt new-intermediate.crt new-root.crt > bundle.crt\nk3s certificate rotate-ca --cacert=bundle.crt --cakey=new-root.key","handlingStrategy":"validation","validationCode":"// Count certs in the bundle before submitting rotate-ca\nfunc bundleCertCount(pemPath string) (int, error) {\n    b, err := os.ReadFile(pemPath)\n    if err != nil {\n        return 0, err\n    }\n    return bytes.Count(b, []byte(\"BEGIN CERTIFICATE\")), nil\n}\n// require count > 1","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"single certificate\") {\n    // rebuild bundle as root+intermediates chain and resubmit\n}","preventionTips":["Assemble rotate-ca bundles as concatenated PEM chains (old root + intermediates + new root).","Validate with 'openssl crl2pkcs7 -nocrl -certfile bundle | openssl pkcs7 -print_certs' before rotating.","Never hand-rotate with a single PEM block."],"tags":["k3s","certificates","ca-rotation","pki","validation"],"backgroundTag":null,"analyzedSha":"6ba341e396edc16b8dcae978a7c5e3ac7ee5606e","analyzedAt":"2026-08-15T16:27:54.286Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}