{"record":{"id":"5330930cf15e12c2","repo":"docker/cli","slug":"ca-cert-for-external-ca-must-be-in-pem-format","errorCode":null,"errorMessage":"CA cert for external CA must be in PEM format","messagePattern":"CA cert for external CA must be in PEM format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/swarm/opts.go","lineNumber":201,"sourceCode":"\t\t// TODO(thaJeztah): these options should not be case-insensitive.\n\t\tswitch strings.ToLower(key) {\n\t\tcase \"protocol\":\n\t\t\thasProtocol = true\n\t\t\tif strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {\n\t\t\t\texternalCA.Protocol = swarm.ExternalCAProtocolCFSSL\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"unrecognized external CA protocol %s\", value)\n\t\t\t}\n\t\tcase \"url\":\n\t\t\thasURL = true\n\t\t\texternalCA.URL = value\n\t\tcase \"cacert\":\n\t\t\tcacontents, err := os.ReadFile(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"unable to read CA cert for external CA: %w\", err)\n\t\t\t}\n\t\t\tif pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {\n\t\t\t\treturn nil, errors.New(\"CA cert for external CA must be in PEM format\")\n\t\t\t}\n\t\t\texternalCA.CACert = string(cacontents)\n\t\tdefault:\n\t\t\texternalCA.Options[key] = value\n\t\t}\n\t}\n\n\tif !hasProtocol {\n\t\treturn nil, errors.New(\"the external-ca option needs a protocol= parameter\")\n\t}\n\tif !hasURL {\n\t\treturn nil, errors.New(\"the external-ca option needs a url= parameter\")\n\t}\n\n\treturn &externalCA, nil\n}\n\nfunc addSwarmCAFlags(flags *pflag.FlagSet, options *swarmCAOptions) {","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/command/swarm/opts.go#L183-L219","documentation":"Thrown by parseExternalCA (cli/command/swarm/opts.go:201) when an `--external-ca` spec's `cacert=` sub-field points at a file whose contents fail pem.Decode. This validates the CA certificate pinned to an external CA endpoint, distinct from the root-CA `--ca-cert` path.","triggerScenarios":"Passing `--external-ca protocol=cfssl,url=https://ca,cacert=/path/to/non-pem` where the cacert file is not PEM-formatted.","commonSituations":"Pointing cacert at a DER file or wrong artifact; copy-paste error in the path; cert exported from a CA tool in non-PEM form.","solutions":["Provide a PEM-formatted CA certificate path in the `cacert=` field.","Convert if needed: `openssl x509 -inform der -in ca.der -out ca.pem`.","Validate the file with `openssl x509 -in ca.pem -noout` before using it."],"exampleFix":"// before\ndocker swarm update --external-ca protocol=cfssl,url=https://ca:12381,cacert=/etc/ca.der\n\n// after\nopenssl x509 -inform der -in /etc/ca.der -out /etc/ca.pem\ndocker swarm update --external-ca protocol=cfssl,url=https://ca:12381,cacert=/etc/ca.pem","handlingStrategy":"validation","validationCode":"// Validate the cacert= file in an external-ca spec.\nif caPath != \"\" {\n\tb, err := os.ReadFile(caPath)\n\tif err != nil { return err }\n\tif block, _ := pem.Decode(b); block == nil {\n\t\treturn errors.New(\"external-ca cacert must be PEM\")\n\t}\n}","typeGuard":"func isPEM(b []byte) bool {\n\tblock, _ := pem.Decode(b)\n\treturn block != nil\n}","tryCatchPattern":null,"preventionTips":["Export CA certs from your PKI tool in PEM form.","Validate the cacert file with openssl before rotate/update.","Centralize external-CA spec building in one tested helper."],"tags":["docker","swarm","external-ca","pem","certificates","validation"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}