{"record":{"id":"9dfb1759a4c19f2f","repo":"slackhq/nebula","slug":"v2-certificate-already-found-in-pki-cert","errorCode":null,"errorMessage":"v2 certificate already found in pki.cert","messagePattern":"v2 certificate already found in pki\\.cert","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pki.go","lineNumber":352,"sourceCode":"\t\t// Load the certificate\n\t\tcrt, rawCert, err = loadCertificate(rawCert)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif fips140.Enforced() && crt.Curve() != cert.Curve_P256 {\n\t\t\treturn nil, fmt.Errorf(\"pki: use of %s is not allowed in FIPS 140-only mode\", crt.Curve())\n\t\t}\n\n\t\tswitch crt.Version() {\n\t\tcase cert.Version1:\n\t\t\tif v1 != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"v1 certificate already found in pki.cert\")\n\t\t\t}\n\t\t\tv1 = crt\n\t\tcase cert.Version2:\n\t\t\tif v2 != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"v2 certificate already found in pki.cert\")\n\t\t\t}\n\t\t\tv2 = crt\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown certificate version %v\", crt.Version())\n\t\t}\n\n\t\tif len(rawCert) == 0 || strings.TrimSpace(string(rawCert)) == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif v1 == nil && v2 == nil {\n\t\treturn nil, errors.New(\"no certificates found in pki.cert\")\n\t}\n\n\tuseInitiatingVersion := uint32(1)\n\tif v1 == nil {\n\t\t// The only condition that requires v2 as the default is if only a v2 certificate is present","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/pki.go#L334-L370","documentation":"newCertStateFromConfig parses the pki.cert file/PEM which may contain multiple certificates (a v1 and a v2 certificate). It keeps one slot per version; if it encounters a second certificate of the same version it aborts, because nebula requires exactly zero or one certificate per version in pki.cert.","triggerScenarios":"newCertStateFromConfig (via reloadCerts) reads pki.cert and finds two cert.Version2 certificates concatenated in the PEM bundle.","commonSituations":"Appending a renewed v2 certificate to the existing pki.cert instead of replacing it; concatenating multiple hosts' certs; copying the same file twice during automation.","solutions":["Remove the duplicate v2 certificate so pki.cert contains at most one v2 certificate","If renewing, replace the old certificate entirely instead of appending","Verify with: openssl crl2pkcs7 -nocrl -certfile pki.cert | openssl pkcs7 -print_certs -noout"],"exampleFix":"// before (pki.cert: old v2 cert + new v2 cert concatenated)\n-----BEGIN NEBULA CERTIFICATE-----\n(old v2)\n-----END NEBULA CERTIFICATE-----\n-----BEGIN NEBULA CERTIFICATE-----\n(new v2)\n-----END NEBULA CERTIFICATE-----\n// after (only the new v2 cert)\n-----BEGIN NEBULA CERTIFICATE-----\n(new v2)\n-----END NEBULA CERTIFICATE-----","handlingStrategy":"validation","validationCode":"// before reload: ensure at most one cert per version in pki.cert\nimport \"github.com/slushie/...\" // nebula cert pkg\nfunc validateSingleCertsPerVersion(pkiCertPEM []byte) error {\n    seen := map[string]bool{}\n    rest := pkiCertPEM\n    for {\n        crt, r, err := cert.UnmarshalCertificate(rest)\n        if err != nil { break }\n        rest = r\n        v := crt.Version().String()\n        if seen[v] { return fmt.Errorf(\"duplicate %s certificate in pki.cert\", v) }\n        seen[v] = true\n        if len(rest) == 0 { break }\n    }\n    return nil\n}","typeGuard":"func hasDupVersion(crts []cert.Certificate) bool {\n    seen := map[cert.Version]bool{}\n    for _, c := range crts {\n        v := c.Version()\n        if seen[v] { return true }\n        seen[v] = true\n    }\n    return false\n}","tryCatchPattern":null,"preventionTips":["When renewing certificates, replace pki.cert atomically instead of appending","Audit config-management templates that concatenate PEM bundles","Run nebula-cert print -path pki.cert in CI to count certificates"],"tags":["pki","certificate","config"],"backgroundTag":"duplicate-certificate-in-bundle","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}