crowdsecurity/crowdsec · error

client certificate OU %v doesn't match expected OU %v

Error message

client certificate OU %v doesn't match expected OU %v

What it means

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.

Source

Thrown at pkg/apiserver/middlewares/v1/tls_auth.go:98

			continue
		}

		uniqueOUs[ou] = struct{}{}

		ta.AllowedOUs = append(ta.AllowedOUs, ou)
	}

	return nil
}

func (ta *TLSAuth) checkAllowedOU(ous []string) error {
	for _, ou := range ous {
		if slices.Contains(ta.AllowedOUs, ou) {
			return nil
		}
	}

	return fmt.Errorf("client certificate OU %v doesn't match expected OU %v", ous, ta.AllowedOUs)
}

func (ta *TLSAuth) ValidateCert(c *gin.Context) (string, error) {
	// Checks cert validity, Returns true + CN if client cert matches requested OU
	var leaf *x509.Certificate

	if c.Request.TLS == nil || len(c.Request.TLS.PeerCertificates) == 0 {
		return "", errors.New("no certificate in request")
	}

	if len(c.Request.TLS.VerifiedChains) == 0 {
		return "", errors.New("no verified cert in request")
	}

	// although there can be multiple chains, the leaf certificate is the same
	// we take the first one
	leaf = c.Request.TLS.VerifiedChains[0][0]

View on GitHub (pinned to 909b515798)

Solutions

  1. Inspect the client cert: `openssl x509 -in cert.pem -noout -subject` and note the OU.
  2. Compare with the allowed OUs configured for the endpoint (api.server.tls.allowed_ous / LAPI ca config).
  3. Reissue the client certificate with the correct OU (e.g. `cscli bouncers`/CA workflow or the cert-generation script using the expected profile).
  4. Alternatively add the cert's actual OU to the allowed_ous list and restart crowdsec — only if the client's role is genuinely permitted.

Example fix

// before: bouncer cert issued with OU=crowdsec-bouncers, endpoint allows only crowdsec-agents
openssl req -new -subj "/CN=bouncer1/OU=crowdsec-agents" ...
// after: issue cert with the OU expected for its role, or extend config:
// api:
//   server:
//     tls:
//       allowed_ous: [crowdsec-agents, crowdsec-bouncers]
Defensive patterns

Strategy: validation

Validate before calling

subject, _ := exec.Command("openssl", "x509", "-in", certPath, "-noout", "-subject").Output()
// extract OU= from subject and compare against the allowed_ous list before deploying the cert

Try / catch

if _, err := tlsAuth.ValidateCert(c); err != nil {
    if strings.Contains(err.Error(), "OU") {
        // log cert subject vs allowed OUs; fail the request with 403
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/518f7c3e9396d541. Report an issue: GitHub.