{"record":{"id":"0bb038bb3cce6fea","repo":"t8y2/dbx","slug":"client-certificate-and-key-must-be-provided-togeth-0bb038","errorCode":null,"errorMessage":"Client certificate and key must be provided together","messagePattern":"Client certificate and key must be provided together","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/etcd-go/client.go","lineNumber":289,"sourceCode":"}\n\nfunc tlsConfigFor(connection connectionParams) (*tls.Config, error) {\n\ttlsConfig := &tls.Config{}\n\tif ca := strings.TrimSpace(connection.CACertPath); ca != \"\" {\n\t\tauthorityPEM, err := os.ReadFile(ca)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpool := x509.NewCertPool()\n\t\tif !pool.AppendCertsFromPEM(authorityPEM) {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse CA certificate at %s\", ca)\n\t\t}\n\t\ttlsConfig.RootCAs = pool\n\t}\n\tcertPath := firstNonBlank(connection.ClientCertPath, connection.CertPath)\n\tkeyPath := firstNonBlank(connection.ClientKeyPath, connection.KeyPath)\n\tif (certPath == \"\") != (keyPath == \"\") {\n\t\treturn nil, errors.New(\"Client certificate and key must be provided together\")\n\t}\n\tif certPath != \"\" {\n\t\tpair, err := tls.LoadX509KeyPair(certPath, keyPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(pair.Certificate) > 0 {\n\t\t\tpair.Leaf, err = x509.ParseCertificate(pair.Certificate[0])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{pair}\n\t}\n\treturn tlsConfig, nil\n}\n\nfunc clientCertificateUsername(config *tls.Config) string {","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd-go/client.go#L271-L307","documentation":"tlsConfigFor validates mTLS material before building the client. A client certificate and its private key are only meaningful as a pair, so supplying exactly one of cert/key is rejected up front with this error rather than failing later inside tls.LoadX509KeyPair. It protects users from half-configured TLS setups.","triggerScenarios":"Setting connection.ClientCertPath (or CertPath) without ClientKeyPath (or KeyPath), or vice versa, when building the etcd client via buildClient.","commonSituations":"Partial TLS config in env/files — e.g. only ETCD_CLIENT_CERT_PATH exported; copy-paste config where the key line was dropped; rotating certs and updating only one path; secret mounts where one file failed to mount.","solutions":["Set both the client cert path and the client key path in the connection config.","If only a CA is intended (no mTLS), remove the lone cert/key field and keep just the CA config.","Verify secret/file mounts so both PEM files actually exist at the configured paths.","Re-run the connection after fixing; buildClient will then load the pair via tls.LoadX509KeyPair."],"exampleFix":"// before\nconnection.ClientCertPath = \"/certs/client.pem\" // key missing\nclient, err := buildClient(connection) // error\n// after\nconnection.ClientCertPath = \"/certs/client.pem\"\nconnection.ClientKeyPath = \"/certs/client-key.pem\"\nclient, err := buildClient(connection)","handlingStrategy":"validation","validationCode":"cert := firstNonBlank(conn.ClientCertPath, conn.CertPath)\nkey := firstNonBlank(conn.ClientKeyPath, conn.KeyPath)\nif (cert == \"\") != (key == \"\") {\n\treturn errors.New(\"client cert and key must both be set (or both omitted)\")\n}\nif cert != \"\" {\n\tif _, err := os.Stat(cert); err != nil { return err }\n\tif _, err := os.Stat(key); err != nil { return err }\n}","typeGuard":"func hasPairedTLSMaterial(conn connectionParams) bool {\n\thasCert := firstNonBlank(conn.ClientCertPath, conn.CertPath) != \"\"\n\thasKey := firstNonBlank(conn.ClientKeyPath, conn.KeyPath) != \"\"\n\treturn hasCert == hasKey\n}","tryCatchPattern":"client, err := buildClient(conn)\nif err != nil {\n\tif strings.Contains(err.Error(), \"must be provided together\") {\n\t\treturn nil, fmt.Errorf(\"TLS config incomplete: set both client cert and key paths: %w\", err)\n\t}\n\treturn nil, err\n}","preventionTips":["Set cert and key paths together in config templates; never edit one alone.","Startup-check that both PEM files exist and are readable before connect.","Verify secret mounts contain both files (cert + key) in one unit.","Keep a single config field pair (clientCertPath/clientKeyPath) rather than legacy aliases."],"tags":["etcd","tls","mtls","configuration"],"backgroundTag":"incomplete-tls-configuration","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}