{"record":{"id":"e498072de634ddbc","repo":"docker/cli","slug":"unable-to-read-ca-cert-for-external-ca-w","errorCode":null,"errorMessage":"unable to read CA cert for external CA: %w","messagePattern":"unable to read CA cert for external CA: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/swarm/opts.go","lineNumber":198,"sourceCode":"\t\t\treturn nil, fmt.Errorf(\"invalid field '%s' must be a key=value pair\", field)\n\t\t}\n\n\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","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/command/swarm/opts.go#L180-L216","documentation":"Wraps an os.ReadFile failure when reading the file referenced by cacert= in an --external-ca spec. The CLI reads the PEM file at parse time; any I/O error (missing, no perms) is surfaced here.","triggerScenarios":"Passing 'cacert=/path/that/does/not/exist' or a path the CLI process can't read. Triggered in the parseExternalCA 'cacert' case at opts.go:196-198.","commonSituations":"Relative path that resolves differently than expected; missing file; permissions; SELinux/AppArmor denying read; running CLI in a different mount namespace than where the cert lives.","solutions":["Confirm the path exists and is readable: 'ls -l <path>' and 'cat <path>' from the CLI's working directory.","Use an absolute path to the PEM file.","Verify the file is valid PEM (the next check pem.Decodes it)."],"exampleFix":"# before\ndocker swarm init --external-ca protocol=cfssl,url=https://ca,cacert=./ca.crt\n\n# after\ndocker swarm init --external-ca protocol=cfssl,url=https://ca,cacert=/etc/docker/external-ca.crt","handlingStrategy":"validation","validationCode":"// Validate the cacert path is readable\nif _, err := os.Stat(path); err != nil {\n    return fmt.Errorf(\"external CA cacert unreadable: %w\", err)\n}","typeGuard":"func fileReadable(path string) bool {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_ = f.Close()\n\treturn true\n}","tryCatchPattern":null,"preventionTips":["Use absolute paths for cacert files.","Verify readability (ls -l) from the CLI's working directory.","Confirm the file is PEM before passing."],"tags":["swarm","external-ca","filesystem","tls"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}