diff --git a/authorize/evaluator/evaluator.go b/authorize/evaluator/evaluator.go index 3bee9423e..07726ce20 100644 --- a/authorize/evaluator/evaluator.go +++ b/authorize/evaluator/evaluator.go @@ -44,6 +44,7 @@ type Evaluator struct { clientCA string authenticateHost string jwk interface{} + kid string } // New creates a new Evaluator. @@ -77,11 +78,16 @@ func New(options *config.Options) (*Evaluator, error) { if err != nil { return nil, fmt.Errorf("authorize: failed to decode certificate cert %v: %w", decodedCert, err) } - keyBytes, err := cryptutil.DecodePrivateKey((decodedCert)) + key, err := cryptutil.DecodePrivateKey(decodedCert) if err != nil { return nil, fmt.Errorf("authorize: couldn't generate signing key: %w", err) } - e.jwk = keyBytes + e.jwk = key + jwk, err := cryptutil.PublicJWKFromBytes(decodedCert, jose.ES256) + if err != nil { + return nil, fmt.Errorf("authorize: failed to convert jwk: %w", err) + } + e.kid = jwk.KeyID } authzPolicy, err := readPolicy("/authz.rego") @@ -163,10 +169,11 @@ func (e *Evaluator) ParseSignedJWT(signature string) ([]byte, error) { // SignedJWT returns the signature of given request. func (e *Evaluator) SignedJWT(req *Request) (string, error) { + signerOpt := &jose.SignerOptions{} signer, err := jose.NewSigner(jose.SigningKey{ Algorithm: jose.ES256, Key: e.jwk, - }, nil) + }, signerOpt.WithHeader("kid", e.kid)) if err != nil { return "", err } diff --git a/authorize/evaluator/evaluator_test.go b/authorize/evaluator/evaluator_test.go index 69cf26a1e..6fc7c9887 100644 --- a/authorize/evaluator/evaluator_test.go +++ b/authorize/evaluator/evaluator_test.go @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/square/go-jose.v2/jwt" "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/pkg/grpc/databroker" @@ -88,6 +89,28 @@ func TestEvaluator_SignedJWT(t *testing.T) { assert.NotEmpty(t, payload) } +func TestEvaluator_JWTWithKID(t *testing.T) { + opt := config.NewDefaultOptions() + opt.AuthenticateURL = mustParseURL("https://authenticate.example.com") + opt.SigningKey = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUpCMFZkbko1VjEvbVlpYUlIWHhnd2Q0Yzd5YWRTeXMxb3Y0bzA1b0F3ekdvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFVUc1eENQMEpUVDFINklvbDhqS3VUSVBWTE0wNENnVzlQbEV5cE5SbVdsb29LRVhSOUhUMwpPYnp6aktZaWN6YjArMUt3VjJmTVRFMTh1dy82MXJVQ0JBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=" + e, err := New(opt) + require.NoError(t, err) + req := &Request{ + HTTP: RequestHTTP{ + Method: http.MethodGet, + URL: "https://example.com", + }, + } + signedJWT, err := e.SignedJWT(req) + require.NoError(t, err) + assert.NotEmpty(t, signedJWT) + + tok, err := jwt.ParseSigned(signedJWT) + require.NoError(t, err) + require.Len(t, tok.Headers, 1) + assert.Equal(t, "5b419ade1895fec2d2def6cd33b1b9a018df60db231dc5ecb85cbed6d942813c", tok.Headers[0].KeyID) +} + func mustParseURL(str string) *url.URL { u, err := url.Parse(str) if err != nil { diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 1befa59ec..0193f2fa9 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -8,6 +8,7 @@ - config: change default log level to INFO @cuonglm [GH-902] - config: add pass_identity_headers @cuonglm [GH-903] - authenticate: allow hot reloaded admin users config @cuonglm [GH-984] +- authorize: include "kid" in JWT headers @cuonglm [GH-1046] ### Changes