mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Add key_id config param to auth.jwt (#72711)
* Specify keyID for public key provided in PEM format for JWT Auth * Update docs * Update sample.ini
This commit is contained in:
@@ -46,7 +46,7 @@ func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) {
|
||||
scenario(t, "verifies a token", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, key, jwt.Claims{
|
||||
Subject: subject,
|
||||
})
|
||||
}, nil)
|
||||
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, verifiedClaims["sub"], subject)
|
||||
@@ -55,10 +55,23 @@ func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) {
|
||||
scenario(t, "rejects a token signed by unknown key", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, unknownKey, jwt.Claims{
|
||||
Subject: subject,
|
||||
})
|
||||
}, nil)
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.Error(t, err)
|
||||
}, configurePKIXPublicKeyFile)
|
||||
|
||||
publicKeyID := "some-key-id"
|
||||
scenario(t, "verifies a token with a specified kid", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, key, jwt.Claims{
|
||||
Subject: subject,
|
||||
}, (&jose.SignerOptions{}).WithHeader("kid", publicKeyID))
|
||||
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, verifiedClaims["sub"], subject)
|
||||
}, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) {
|
||||
t.Helper()
|
||||
cfg.JWTAuthKeyID = publicKeyID
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyUsingJWKSetFile(t *testing.T) {
|
||||
@@ -80,21 +93,21 @@ func TestVerifyUsingJWKSetFile(t *testing.T) {
|
||||
}
|
||||
|
||||
scenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject})
|
||||
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
|
||||
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, verifiedClaims["sub"], subject)
|
||||
}, configure)
|
||||
|
||||
scenario(t, "verifies a token signed with another key from the set", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject})
|
||||
token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}, nil)
|
||||
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, verifiedClaims["sub"], subject)
|
||||
}, configure)
|
||||
|
||||
scenario(t, "rejects a token signed with a key not from the set", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, jwKeys[2], jwt.Claims{Subject: subject})
|
||||
token := sign(t, jwKeys[2], jwt.Claims{Subject: subject}, nil)
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.Error(t, err)
|
||||
}, configure)
|
||||
@@ -126,21 +139,21 @@ func TestVerifyUsingJWKSetURL(t *testing.T) {
|
||||
})
|
||||
|
||||
jwkHTTPScenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject})
|
||||
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
|
||||
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, verifiedClaims["sub"], subject)
|
||||
})
|
||||
|
||||
jwkHTTPScenario(t, "verifies a token signed with another key from the set", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject})
|
||||
token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}, nil)
|
||||
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, verifiedClaims["sub"], subject)
|
||||
})
|
||||
|
||||
jwkHTTPScenario(t, "rejects a token signed with a key not from the set", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, jwKeys[2], jwt.Claims{Subject: subject})
|
||||
token := sign(t, jwKeys[2], jwt.Claims{Subject: subject}, nil)
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.Error(t, err)
|
||||
})
|
||||
@@ -149,7 +162,7 @@ func TestVerifyUsingJWKSetURL(t *testing.T) {
|
||||
func TestCachingJWKHTTPResponse(t *testing.T) {
|
||||
jwkCachingScenario(t, "caches the jwk response", func(t *testing.T, sc cachingScenarioContext) {
|
||||
for i := 0; i < 5; i++ {
|
||||
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject})
|
||||
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, token)
|
||||
require.NoError(t, err, "verify call %d", i+1)
|
||||
}
|
||||
@@ -160,8 +173,8 @@ func TestCachingJWKHTTPResponse(t *testing.T) {
|
||||
jwkCachingScenario(t, "respects TTL setting (while cached)", func(t *testing.T, sc cachingScenarioContext) {
|
||||
var err error
|
||||
|
||||
token0 := sign(t, &jwKeys[0], jwt.Claims{Subject: subject})
|
||||
token1 := sign(t, &jwKeys[1], jwt.Claims{Subject: subject})
|
||||
token0 := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
|
||||
token1 := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}, nil)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, token0)
|
||||
require.NoError(t, err)
|
||||
@@ -176,7 +189,7 @@ func TestCachingJWKHTTPResponse(t *testing.T) {
|
||||
|
||||
jwkCachingScenario(t, "does not cache the response when TTL is zero", func(t *testing.T, sc cachingScenarioContext) {
|
||||
for i := 0; i < 2; i++ {
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, sign(t, &jwKeys[i], jwt.Claims{Subject: subject}))
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, sign(t, &jwKeys[i], jwt.Claims{Subject: subject}, nil))
|
||||
require.NoError(t, err, "verify call %d", i+1)
|
||||
}
|
||||
|
||||
@@ -198,8 +211,8 @@ func TestClaimValidation(t *testing.T) {
|
||||
key := rsaKeys[0]
|
||||
|
||||
scenario(t, "validates iss field for equality", func(t *testing.T, sc scenarioContext) {
|
||||
tokenValid := sign(t, key, jwt.Claims{Issuer: "http://foo"})
|
||||
tokenInvalid := sign(t, key, jwt.Claims{Issuer: "http://bar"})
|
||||
tokenValid := sign(t, key, jwt.Claims{Issuer: "http://foo"}, nil)
|
||||
tokenInvalid := sign(t, key, jwt.Claims{Issuer: "http://bar"}, nil)
|
||||
|
||||
_, err := sc.authJWTSvc.Verify(sc.ctx, tokenValid)
|
||||
require.NoError(t, err)
|
||||
@@ -213,8 +226,8 @@ func TestClaimValidation(t *testing.T) {
|
||||
scenario(t, "validates sub field for equality", func(t *testing.T, sc scenarioContext) {
|
||||
var err error
|
||||
|
||||
tokenValid := sign(t, key, jwt.Claims{Subject: "foo"})
|
||||
tokenInvalid := sign(t, key, jwt.Claims{Subject: "bar"})
|
||||
tokenValid := sign(t, key, jwt.Claims{Subject: "foo"}, nil)
|
||||
tokenInvalid := sign(t, key, jwt.Claims{Subject: "bar"}, nil)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, tokenValid)
|
||||
require.NoError(t, err)
|
||||
@@ -228,19 +241,19 @@ func TestClaimValidation(t *testing.T) {
|
||||
scenario(t, "validates aud field for inclusion", func(t *testing.T, sc scenarioContext) {
|
||||
var err error
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "foo"}}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "foo"}}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo", "bar", "baz"}}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo", "bar", "baz"}}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo"}}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo"}}, nil))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "baz"}}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "baz"}}, nil))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"baz"}}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"baz"}}, nil))
|
||||
require.Error(t, err)
|
||||
}, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) {
|
||||
cfg.JWTAuthExpectClaims = `{"aud": ["foo", "bar"]}`
|
||||
@@ -249,19 +262,19 @@ func TestClaimValidation(t *testing.T) {
|
||||
scenario(t, "validates non-registered (custom) claims for equality", func(t *testing.T, sc scenarioContext) {
|
||||
var err error
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 123}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 123}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "bar", "my-number": 123}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "bar", "my-number": 123}, nil))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 100}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 100}, nil))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo"}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo"}, nil))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-number": 123}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-number": 123}, nil))
|
||||
require.Error(t, err)
|
||||
}, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) {
|
||||
cfg.JWTAuthExpectClaims = `{"my-str": "foo", "my-number": 123}`
|
||||
@@ -270,30 +283,30 @@ func TestClaimValidation(t *testing.T) {
|
||||
scenario(t, "validates exp claim of the token", func(t *testing.T, sc scenarioContext) {
|
||||
var err error
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour))}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour))}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(-time.Hour))}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(-time.Hour))}, nil))
|
||||
require.Error(t, err)
|
||||
}, configurePKIXPublicKeyFile)
|
||||
|
||||
scenario(t, "validates nbf claim of the token", func(t *testing.T, sc scenarioContext) {
|
||||
var err error
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour))}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour))}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Hour))}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Hour))}, nil))
|
||||
require.Error(t, err)
|
||||
}, configurePKIXPublicKeyFile)
|
||||
|
||||
scenario(t, "validates iat claim of the token", func(t *testing.T, sc scenarioContext) {
|
||||
var err error
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour))}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour))}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(time.Hour))}))
|
||||
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(time.Hour))}, nil))
|
||||
require.Error(t, err)
|
||||
}, configurePKIXPublicKeyFile)
|
||||
}
|
||||
@@ -360,7 +373,7 @@ func TestBase64Paddings(t *testing.T) {
|
||||
scenario(t, "verifies a token with base64 padding (non compliant rfc7515#section-2 but accepted)", func(t *testing.T, sc scenarioContext) {
|
||||
token := sign(t, key, jwt.Claims{
|
||||
Subject: subject,
|
||||
})
|
||||
}, nil)
|
||||
var tokenParts []string
|
||||
for i, part := range strings.Split(token, ".") {
|
||||
// Create parts with different padding numbers to test multiple cases.
|
||||
|
||||
@@ -125,7 +125,7 @@ func (s *AuthService) initKeySet() error {
|
||||
|
||||
s.keySet = &keySetJWKS{
|
||||
jose.JSONWebKeySet{
|
||||
Keys: []jose.JSONWebKey{{Key: key}},
|
||||
Keys: []jose.JSONWebKey{{Key: key, KeyID: s.Cfg.JWTAuthKeyID}},
|
||||
},
|
||||
}
|
||||
} else if keyFilePath := s.Cfg.JWTAuthJWKSetFile; keyFilePath != "" {
|
||||
|
||||
@@ -10,10 +10,13 @@ import (
|
||||
|
||||
type noneSigner struct{}
|
||||
|
||||
func sign(t *testing.T, key interface{}, claims interface{}) string {
|
||||
func sign(t *testing.T, key interface{}, claims interface{}, opts *jose.SignerOptions) string {
|
||||
t.Helper()
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.PS512, Key: key}, (&jose.SignerOptions{}).WithType("JWT"))
|
||||
if opts == nil {
|
||||
opts = &jose.SignerOptions{}
|
||||
}
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.PS512, Key: key}, (opts).WithType("JWT"))
|
||||
require.NoError(t, err)
|
||||
token, err := jwt.Signed(sig).Claims(claims).CompactSerialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user