diff --git a/doc/opa.md b/doc/opa.md index 79152db..4fcdd72 100644 --- a/doc/opa.md +++ b/doc/opa.md @@ -20,6 +20,8 @@ relic will provide the following inputs to the policy: - token - The Bearer token provided in the `Authorization` header, if provided - fingerprint - Digest of the client's leaf certificate, if provided +- client_cert - The client's provided certificate chain, in PEM format, + with the leaf certificate last - path - Path from the URL being accessed - query - Query parameters from the URL being accessed diff --git a/internal/authmodel/opa.go b/internal/authmodel/opa.go index 92b55c4..e7a6ce3 100644 --- a/internal/authmodel/opa.go +++ b/internal/authmodel/opa.go @@ -3,7 +3,9 @@ package authmodel import ( "bytes" "context" + "crypto/x509" "encoding/json" + "encoding/pem" "fmt" "io" "net/http" @@ -44,6 +46,7 @@ func (a *PolicyAuth) Authenticate(req *http.Request) (UserInfo, error) { return nil, err } else if len(peerCerts) != 0 { input.Fingerprint = fingerprint(peerCerts[0]) + input.ClientCert = formatCerts(peerCerts) } if input.Token == "" && input.Fingerprint == "" { return nil, httperror.ErrTokenRequired @@ -164,6 +167,7 @@ type policyInput struct { Query url.Values `json:"query"` Token string `json:"token"` Fingerprint string `json:"fingerprint"` + ClientCert string `json:"client_cert"` } type policyResponse struct { @@ -205,6 +209,19 @@ func bearerToken(req *http.Request) string { return auth[len(prefix):] } +func formatCerts(peerCerts []*x509.Certificate) string { + var certs strings.Builder + for i := range peerCerts { + // OPA wants leaf cert last, which is the opposite of how they arrive + cert := peerCerts[len(peerCerts)-i-1] + _ = pem.Encode(&certs, &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }) + } + return certs.String() +} + var should401 = map[string]bool{ "token is missing or not well-formed": true, "token issuer is not in known_issuers": true,