{"record":{"id":"518f7c3e9396d541","repo":"crowdsecurity/crowdsec","slug":"client-certificate-ou-v-doesn-t-match-expected-ou","errorCode":null,"errorMessage":"client certificate OU %v doesn't match expected OU %v","messagePattern":"client certificate OU (.+?) doesn't match expected OU (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/apiserver/middlewares/v1/tls_auth.go","lineNumber":98,"sourceCode":"\t\t\tcontinue\n\t\t}\n\n\t\tuniqueOUs[ou] = struct{}{}\n\n\t\tta.AllowedOUs = append(ta.AllowedOUs, ou)\n\t}\n\n\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","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/middlewares/v1/tls_auth.go#L80-L116","documentation":"The TLS client-auth middleware validates the presented client certificate's Organizational Unit (OU) subject field against the configured allow-list (AllowedOUs, e.g. crowdsec_lapi cert OUs like 'crowdsec-agents' or 'crowdsec-bouncers'). If none of the OUs in the client cert's subject appears in the allow-list, the certificate is refused even though it chains to a trusted CA. This enforces role separation between agents, bouncers and admins at the TLS layer.","triggerScenarios":"ValidateCert extracts the leaf cert (VerifiedChains[0][0]) and calls checkAllowedOU with leaf.Subject.OrganizationalUnit; the connection is rejected when the cert was issued with an OU not listed in the endpoint's allowed_ous/LAPI TLS config — e.g. a bouncer presenting a 'crowdsec-agents' cert, or a cert issued from a different CA profile.","commonSituations":"Certificate generated with the wrong CA profile or for the wrong component role; allowed_ous edited in config.yaml without reissuing certs; older certs minted before an OU naming convention change; copying a client cert between hosts with different roles.","solutions":["Inspect the client cert: `openssl x509 -in cert.pem -noout -subject` and note the OU.","Compare with the allowed OUs configured for the endpoint (api.server.tls.allowed_ous / LAPI ca config).","Reissue the client certificate with the correct OU (e.g. `cscli bouncers`/CA workflow or the cert-generation script using the expected profile).","Alternatively add the cert's actual OU to the allowed_ous list and restart crowdsec — only if the client's role is genuinely permitted."],"exampleFix":"// before: bouncer cert issued with OU=crowdsec-bouncers, endpoint allows only crowdsec-agents\nopenssl req -new -subj \"/CN=bouncer1/OU=crowdsec-agents\" ...\n// after: issue cert with the OU expected for its role, or extend config:\n// api:\n//   server:\n//     tls:\n//       allowed_ous: [crowdsec-agents, crowdsec-bouncers]","handlingStrategy":"validation","validationCode":"subject, _ := exec.Command(\"openssl\", \"x509\", \"-in\", certPath, \"-noout\", \"-subject\").Output()\n// extract OU= from subject and compare against the allowed_ous list before deploying the cert","typeGuard":null,"tryCatchPattern":"if _, err := tlsAuth.ValidateCert(c); err != nil {\n    if strings.Contains(err.Error(), \"OU\") {\n        // log cert subject vs allowed OUs; fail the request with 403\n    }\n}","preventionTips":["Issue client certs through the same CA profile script that sets the role OU (crowdsec-agents / crowdsec-bouncers).","Before rollout, verify each cert: `openssl x509 -noout -subject -in cert.pem`.","Keep allowed_ous in config in sync with your cert-generation profiles; change both together.","Separate CA/profiles per component role to prevent cert reuse across roles."],"tags":["tls","authentication","certificates","config"],"backgroundTag":"permission-denied","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"}