{"record":{"id":"ad992893f1749add","repo":"argoproj/argo-workflows","slug":"client-certificate-authentication-requires-both-cl","errorCode":null,"errorMessage":"client certificate authentication requires both clientCert and clientKey","messagePattern":"client certificate authentication requires both clientCert and clientKey","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/tls/tls.go","lineNumber":164,"sourceCode":"\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t\tMinVersion:   tlsMinVersion,\n\t\tNextProtos:   []string{\"h2\"},\n\t}, nil\n}\n\n// GetClientTLSConfig creates a TLS 1.2 or newer configuration for client connections.\n// Client certificate authentication requires both clientCert and clientKey. If caCert is provided,\n// the certificate authority is used instead of the system roots to verify the server certificate.\n// The insecureSkipVerify parameter controls whether the server's certificate is verified.\nfunc GetClientTLSConfig(clientCert, clientKey, caCert string, insecureSkipVerify bool) (*tls.Config, error) {\n\ttlsConfig := &tls.Config{\n\t\tInsecureSkipVerify: insecureSkipVerify,\n\t\tMinVersion:         tls.VersionTLS12,\n\t}\n\tif (clientCert == \"\") != (clientKey == \"\") {\n\t\treturn nil, fmt.Errorf(\"client certificate authentication requires both clientCert and clientKey\")\n\t}\n\tif caCert != \"\" {\n\t\tcaPEM, err := os.ReadFile(caCert)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read certificate authority: %w\", err)\n\t\t}\n\t\tcertPool := x509.NewCertPool()\n\t\tif ok := certPool.AppendCertsFromPEM(caPEM); !ok {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse certificate authority %q\", caCert)\n\t\t}\n\t\ttlsConfig.RootCAs = certPool\n\t}\n\tif clientCert != \"\" && clientKey != \"\" {\n\t\tcert, err := tls.LoadX509KeyPair(clientCert, clientKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{cert}","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/util/tls/tls.go#L146-L182","documentation":"GetClientTLSConfig builds a client TLS config and enforces that mutual-TLS credentials are complete: clientCert and clientKey must both be set or both be empty. Exactly one was provided, so it refuses to build a half-configured mTLS client. This guard prevents silently connecting without client auth when the user thinks it is enabled.","triggerScenarios":"Calling tls.GetClientTLSConfig with a non-empty clientCert but empty clientKey (or vice versa) — e.g. the argo CLI/server flags --client-cert / --client-key or env-derived paths where only one path is configured.","commonSituations":"Setting ARGOPROJ/CLI TLS env vars or config for client auth and forgetting the key (or vice versa); mounting only one of two secret files in a pod; typo in one file path yielding an empty string after defaulting.","solutions":["Provide both the client certificate and the client key paths (or files) together","If mTLS is not intended, unset the single credential so both are empty","Check your Helm values / CLI flags / env vars to confirm both --client-cert and --client-key (or equivalents) are set","If using a k8s secret mount, verify both tls.crt and tls.key are mounted at the expected paths"],"exampleFix":"// before\nconfig, err := tls.GetClientTLSConfig(\"/certs/client.crt\", \"\", \"\", false)\n// after\nconfig, err := tls.GetClientTLSConfig(\"/certs/client.crt\", \"/certs/client.key\", \"\", false)","handlingStrategy":"validation","validationCode":"func validateClientTLSArgs(cert, key string) error {\n    if (cert == \"\") != (key == \"\") {\n        return fmt.Errorf(\"clientCert and clientKey must both be set or both empty (got cert=%q key=%q)\", cert != \"\", key != \"\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := validateClientTLSArgs(clientCert, clientKey); err != nil {\n    return fmt.Errorf(\"misconfigured mTLS: %w\", err)\n}\nconfig, err := tls.GetClientTLSConfig(clientCert, clientKey, caCert, insecure)","preventionTips":["Always set --client-cert and --client-key (or their env equivalents) as a pair","Use Helm/config values that template both fields together from one secret","Fail fast in startup code by validating flag pairs before calling the library","Mount both files of a k8s TLS secret into the pod"],"tags":["tls","mtls","client-certificate","config"],"backgroundTag":"incomplete-mtls-credentials","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}