Default to "username" claim in jwtcachefiller

Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
Ryan Richard 2020-12-15 14:37:38 -08:00 committed by Margo Crawford
parent 720bc7ae42
commit 05ab8f375e
2 changed files with 39 additions and 27 deletions

View File

@ -29,7 +29,7 @@ import (
// These default values come from the way that the Supervisor issues and signs tokens. We make these // These default values come from the way that the Supervisor issues and signs tokens. We make these
// the defaults for a JWTAuthenticator so that they can easily integrate with the Supervisor. // the defaults for a JWTAuthenticator so that they can easily integrate with the Supervisor.
const ( const (
defaultUsernameClaim = "sub" defaultUsernameClaim = "username"
defaultGroupsClaim = "groups" defaultGroupsClaim = "groups"
) )

View File

@ -19,11 +19,10 @@ import (
"testing" "testing"
"time" "time"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
@ -327,16 +326,17 @@ func TestController(t *testing.T) {
goodSubject = "some-subject" goodSubject = "some-subject"
group0 = "some-group-0" group0 = "some-group-0"
group1 = "some-group-1" group1 = "some-group-1"
goodUsername = "pinny123"
) )
for _, test := range testTableForAuthenticateTokenTests( for _, test := range testTableForAuthenticateTokenTests(
t, t,
goodSubject,
goodRSASigningKey, goodRSASigningKey,
goodRSASigningAlgo, goodRSASigningAlgo,
goodRSASigningKeyID, goodRSASigningKeyID,
group0, group0,
group1, group1,
goodUsername,
) { ) {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
@ -351,8 +351,9 @@ func TestController(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
} }
var groups interface{} var groups interface{}
username := goodUsername
if test.jwtClaims != nil { if test.jwtClaims != nil {
test.jwtClaims(&wellKnownClaims, &groups) test.jwtClaims(&wellKnownClaims, &groups, &username)
} }
var signingKey interface{} = goodECSigningKey var signingKey interface{} = goodECSigningKey
@ -362,7 +363,7 @@ func TestController(t *testing.T) {
test.jwtSignature(&signingKey, &signingAlgo, &signingKID) test.jwtSignature(&signingKey, &signingAlgo, &signingKID)
} }
jwt := createJWT(t, signingKey, signingAlgo, signingKID, &wellKnownClaims, groups) jwt := createJWT(t, signingKey, signingAlgo, signingKID, &wellKnownClaims, groups, username)
rsp, authenticated, err := cachedAuthenticator.AuthenticateToken(context.Background(), jwt) rsp, authenticated, err := cachedAuthenticator.AuthenticateToken(context.Background(), jwt)
if test.wantErrorRegexp != "" { if test.wantErrorRegexp != "" {
require.Error(t, err) require.Error(t, err)
@ -380,15 +381,15 @@ func TestController(t *testing.T) {
func testTableForAuthenticateTokenTests( func testTableForAuthenticateTokenTests(
t *testing.T, t *testing.T,
goodSubject string,
goodRSASigningKey *rsa.PrivateKey, goodRSASigningKey *rsa.PrivateKey,
goodRSASigningAlgo jose.SignatureAlgorithm, goodRSASigningAlgo jose.SignatureAlgorithm,
goodRSASigningKeyID string, goodRSASigningKeyID string,
group0 string, group0 string,
group1 string, group1 string,
goodUsername string,
) []struct { ) []struct {
name string name string
jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}) jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}, username *string)
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
wantResponse *authenticator.Response wantResponse *authenticator.Response
wantAuthenticated bool wantAuthenticated bool
@ -396,7 +397,7 @@ func testTableForAuthenticateTokenTests(
} { } {
tests := []struct { tests := []struct {
name string name string
jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}) jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}, username *string)
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
wantResponse *authenticator.Response wantResponse *authenticator.Response
wantAuthenticated bool wantAuthenticated bool
@ -406,7 +407,7 @@ func testTableForAuthenticateTokenTests(
name: "good token without groups and with EC signature", name: "good token without groups and with EC signature",
wantResponse: &authenticator.Response{ wantResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: goodSubject, Name: goodUsername,
}, },
}, },
wantAuthenticated: true, wantAuthenticated: true,
@ -420,19 +421,19 @@ func testTableForAuthenticateTokenTests(
}, },
wantResponse: &authenticator.Response{ wantResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: goodSubject, Name: goodUsername,
}, },
}, },
wantAuthenticated: true, wantAuthenticated: true,
}, },
{ {
name: "good token with groups as array", name: "good token with groups as array",
jwtClaims: func(_ *jwt.Claims, groups *interface{}) { jwtClaims: func(_ *jwt.Claims, groups *interface{}, username *string) {
*groups = []string{group0, group1} *groups = []string{group0, group1}
}, },
wantResponse: &authenticator.Response{ wantResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: goodSubject, Name: goodUsername,
Groups: []string{group0, group1}, Groups: []string{group0, group1},
}, },
}, },
@ -440,12 +441,12 @@ func testTableForAuthenticateTokenTests(
}, },
{ {
name: "good token with groups as string", name: "good token with groups as string",
jwtClaims: func(_ *jwt.Claims, groups *interface{}) { jwtClaims: func(_ *jwt.Claims, groups *interface{}, username *string) {
*groups = group0 *groups = group0
}, },
wantResponse: &authenticator.Response{ wantResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: goodSubject, Name: goodUsername,
Groups: []string{group0}, Groups: []string{group0},
}, },
}, },
@ -453,26 +454,26 @@ func testTableForAuthenticateTokenTests(
}, },
{ {
name: "good token with nbf unset", name: "good token with nbf unset",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.NotBefore = nil claims.NotBefore = nil
}, },
wantResponse: &authenticator.Response{ wantResponse: &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: goodSubject, Name: goodUsername,
}, },
}, },
wantAuthenticated: true, wantAuthenticated: true,
}, },
{ {
name: "bad token with groups as map", name: "bad token with groups as map",
jwtClaims: func(_ *jwt.Claims, groups *interface{}) { jwtClaims: func(_ *jwt.Claims, groups *interface{}, username *string) {
*groups = map[string]string{"not an array": "or a string"} *groups = map[string]string{"not an array": "or a string"}
}, },
wantErrorRegexp: "oidc: parse groups claim \"groups\": json: cannot unmarshal object into Go value of type string", wantErrorRegexp: "oidc: parse groups claim \"groups\": json: cannot unmarshal object into Go value of type string",
}, },
{ {
name: "bad token with wrong issuer", name: "bad token with wrong issuer",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Issuer = "wrong-issuer" claims.Issuer = "wrong-issuer"
}, },
wantResponse: nil, wantResponse: nil,
@ -480,39 +481,46 @@ func testTableForAuthenticateTokenTests(
}, },
{ {
name: "bad token with no audience", name: "bad token with no audience",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Audience = nil claims.Audience = nil
}, },
wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \[\]`, wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \[\]`,
}, },
{ {
name: "bad token with wrong audience", name: "bad token with wrong audience",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Audience = []string{"wrong-audience"} claims.Audience = []string{"wrong-audience"}
}, },
wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`, wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`,
}, },
{ {
name: "bad token with nbf in the future", name: "bad token with nbf in the future",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.NotBefore = jwt.NewNumericDate(time.Date(3020, 2, 3, 4, 5, 6, 7, time.UTC)) claims.NotBefore = jwt.NewNumericDate(time.Date(3020, 2, 3, 4, 5, 6, 7, time.UTC))
}, },
wantErrorRegexp: `oidc: verify token: oidc: current time .* before the nbf \(not before\) time: 3020-.*`, wantErrorRegexp: `oidc: verify token: oidc: current time .* before the nbf \(not before\) time: 3020-.*`,
}, },
{ {
name: "bad token with exp in past", name: "bad token with exp in past",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Expiry = jwt.NewNumericDate(time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)) claims.Expiry = jwt.NewNumericDate(time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC))
}, },
wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-02-02 20:12:08 -0752 LMT\)`, wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-02-02 20:12:08 -0752 LMT\)`,
}, },
{ {
name: "bad token without exp", name: "bad token without exp",
jwtClaims: func(claims *jwt.Claims, _ *interface{}) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Expiry = nil claims.Expiry = nil
}, },
wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-01-01 00:00:00 \+0000 UTC\)`, wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-01-01 00:00:00 \+0000 UTC\)`,
}, },
{
name: "token does not have username claim",
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
*username = ""
},
wantErrorRegexp: `oidc: parse username claims "username": claim not present`,
},
{ {
name: "signing key is wrong", name: "signing key is wrong",
jwtSignature: func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) { jwtSignature: func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) {
@ -560,6 +568,7 @@ func createJWT(
kid string, kid string,
claims *jwt.Claims, claims *jwt.Claims,
groups interface{}, groups interface{},
username string,
) string { ) string {
t.Helper() t.Helper()
@ -573,6 +582,9 @@ func createJWT(
if groups != nil { if groups != nil {
builder = builder.Claims(map[string]interface{}{"groups": groups}) builder = builder.Claims(map[string]interface{}{"groups": groups})
} }
if username != "" {
builder = builder.Claims(map[string]interface{}{"username": username})
}
jwt, err := builder.CompactSerialize() jwt, err := builder.CompactSerialize()
require.NoError(t, err) require.NoError(t, err)