argoproj/argo-workflows · error
client certificate authentication requires both clientCert a
Error message
client certificate authentication requires both clientCert and clientKey
What it means
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.
Source
Thrown at util/tls/tls.go:164
return &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tlsMinVersion,
NextProtos: []string{"h2"},
}, nil
}
// GetClientTLSConfig creates a TLS 1.2 or newer configuration for client connections.
// Client certificate authentication requires both clientCert and clientKey. If caCert is provided,
// the certificate authority is used instead of the system roots to verify the server certificate.
// The insecureSkipVerify parameter controls whether the server's certificate is verified.
func GetClientTLSConfig(clientCert, clientKey, caCert string, insecureSkipVerify bool) (*tls.Config, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: insecureSkipVerify,
MinVersion: tls.VersionTLS12,
}
if (clientCert == "") != (clientKey == "") {
return nil, fmt.Errorf("client certificate authentication requires both clientCert and clientKey")
}
if caCert != "" {
caPEM, err := os.ReadFile(caCert)
if err != nil {
return nil, fmt.Errorf("failed to read certificate authority: %w", err)
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(caPEM); !ok {
return nil, fmt.Errorf("failed to parse certificate authority %q", caCert)
}
tlsConfig.RootCAs = certPool
}
if clientCert != "" && clientKey != "" {
cert, err := tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}View on GitHub (pinned to 35bff19146)
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
Example fix
// before
config, err := tls.GetClientTLSConfig("/certs/client.crt", "", "", false)
// after
config, err := tls.GetClientTLSConfig("/certs/client.crt", "/certs/client.key", "", false) Defensive patterns
Strategy: validation
Validate before calling
func validateClientTLSArgs(cert, key string) error {
if (cert == "") != (key == "") {
return fmt.Errorf("clientCert and clientKey must both be set or both empty (got cert=%q key=%q)", cert != "", key != "")
}
return nil
} Try / catch
if err := validateClientTLSArgs(clientCert, clientKey); err != nil {
return fmt.Errorf("misconfigured mTLS: %w", err)
}
config, err := tls.GetClientTLSConfig(clientCert, clientKey, caCert, insecure) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- failed to create HTTP client: %w
- --client-certificate and --client-key must be provided toget
- invalid TTL
- single branch mode without a branch specified
- memoization configmap doesn't have %s label, refusing to use
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/ad992893f1749add.
Report an issue: GitHub.