{"record":{"id":"acdbb7ac20a65d17","repo":"crowdsecurity/crowdsec","slug":"no-certificate-in-request","errorCode":null,"errorMessage":"no certificate in request","messagePattern":"no certificate in request","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"pkg/apiserver/middlewares/v1/tls_auth.go","lineNumber":106,"sourceCode":"\treturn nil\n}\n\nfunc (ta *TLSAuth) checkAllowedOU(ous []string) error {\n\tfor _, ou := range ous {\n\t\tif slices.Contains(ta.AllowedOUs, ou) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"client certificate OU %v doesn't match expected OU %v\", ous, ta.AllowedOUs)\n}\n\nfunc (ta *TLSAuth) ValidateCert(c *gin.Context) (string, error) {\n\t// Checks cert validity, Returns true + CN if client cert matches requested OU\n\tvar leaf *x509.Certificate\n\n\tif c.Request.TLS == nil || len(c.Request.TLS.PeerCertificates) == 0 {\n\t\treturn \"\", errors.New(\"no certificate in request\")\n\t}\n\n\tif len(c.Request.TLS.VerifiedChains) == 0 {\n\t\treturn \"\", errors.New(\"no verified cert in request\")\n\t}\n\n\t// although there can be multiple chains, the leaf certificate is the same\n\t// we take the first one\n\tleaf = c.Request.TLS.VerifiedChains[0][0]\n\n\tif err := ta.checkAllowedOU(leaf.Subject.OrganizationalUnit); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif ta.isExpired(leaf) {\n\t\treturn \"\", errors.New(\"client certificate is expired\")\n\t}\n","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/middlewares/v1/tls_auth.go#L88-L124","documentation":"ValidateCert extracts the client certificate from the gin request's TLS state. If the request carries no TLS session data or no peer certificates were presented, there is nothing to validate, so it fails with this error and the mTLS auth is refused.","triggerScenarios":"authTLS -> ValidateCert on a request where c.Request.TLS is nil or c.Request.TLS.PeerCertificates is empty, i.e. the client connected without presenting a client certificate.","commonSituations":"Client did not configure its client cert/key; server TLS listener configured with client auth mode that doesn't require certs (e.g. NoClientCert/VerifyClientCertIfGiven); a plain HTTP request hit the TLS-configured endpoint; misconfigured proxy terminating TLS.","solutions":["Configure the client to present its client certificate and key (e.g. curl --cert/--key or equivalent client config)","Ensure the server requests/requires client certificates in its TLS listener settings","Confirm the request goes directly to the TLS endpoint, not through a proxy that strips TLS client context"],"exampleFix":"// before\nclient: no certificate configured\n// after (curl)\ncurl --cert client.pem --key client-key.pem --cacert ca.pem https://lapi:8080/...","handlingStrategy":"type-guard","validationCode":"// caller-side guard before trusting mTLS identity\ntlsState := c.Request.TLS\nif tlsState == nil || len(tlsState.PeerCertificates) == 0 {\n    c.AbortWithStatus(401)\n}","typeGuard":"func hasClientCert(req *http.Request) bool {\n    return req.TLS != nil && len(req.TLS.PeerCertificates) > 0\n}","tryCatchPattern":"cn, err := ta.ValidateCert(c)\nif err != nil {\n    c.AbortWithStatus(http.StatusUnauthorized)\n    return\n}","preventionTips":["Always configure client cert and key on LAPI/bouncer clients","Set server ClientAuth to RequireAndVerifyClientCert so missing certs fail at handshake","Beware TLS-terminating proxies that strip client certificates"],"tags":["tls","mtls","certificate","apiserver"],"backgroundTag":"missing-client-certificate","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}