Pass through custom groups claim and username claim

Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
Margo Crawford 2020-12-15 16:11:53 -08:00 committed by Ryan Richard
parent 05ab8f375e
commit a10d219049
2 changed files with 95 additions and 11 deletions

View File

@ -169,12 +169,20 @@ func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthentica
caFile = temp.Name() caFile = temp.Name()
} }
usernameClaim := spec.UsernameClaim
if usernameClaim == "" {
usernameClaim = defaultUsernameClaim
}
groupsClaim := spec.GroupsClaim
if groupsClaim == "" {
groupsClaim = defaultGroupsClaim
}
authenticator, err := oidc.New(oidc.Options{ authenticator, err := oidc.New(oidc.Options{
IssuerURL: spec.Issuer, IssuerURL: spec.Issuer,
ClientID: spec.Audience, ClientID: spec.Audience,
UsernameClaim: defaultUsernameClaim, UsernameClaim: usernameClaim,
GroupsClaim: defaultGroupsClaim, GroupsClaim: groupsClaim,
SupportedSigningAlgs: defaultSupportedSigningAlgos(), SupportedSigningAlgs: defaultSupportedSigningAlgos(),
CAFile: caFile, CAFile: caFile,
}) })

View File

@ -89,6 +89,18 @@ func TestController(t *testing.T) {
Audience: goodAudience, Audience: goodAudience,
TLS: tlsSpecFromTLSConfig(server.TLS), TLS: tlsSpecFromTLSConfig(server.TLS),
} }
someJWTAuthenticatorSpecWithUsernameClaim := &auth1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: tlsSpecFromTLSConfig(server.TLS),
UsernameClaim: "my-custom-username-claim",
}
someJWTAuthenticatorSpecWithGroupsClaim := &auth1alpha1.JWTAuthenticatorSpec{
Issuer: goodIssuer,
Audience: goodAudience,
TLS: tlsSpecFromTLSConfig(server.TLS),
GroupsClaim: "my-custom-groups-claim",
}
otherJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{ otherJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
Issuer: "https://some-other-issuer.com", Issuer: "https://some-other-issuer.com",
Audience: goodAudience, Audience: goodAudience,
@ -113,6 +125,8 @@ func TestController(t *testing.T) {
wantErr string wantErr string
wantLogs []string wantLogs []string
wantCacheEntries int wantCacheEntries int
wantUsernameClaim string
wantGroupsClaim string
runTestsOnResultingAuthenticator bool runTestsOnResultingAuthenticator bool
}{ }{
{ {
@ -140,6 +154,44 @@ func TestController(t *testing.T) {
wantCacheEntries: 1, wantCacheEntries: 1,
runTestsOnResultingAuthenticator: true, runTestsOnResultingAuthenticator: true,
}, },
{
name: "valid jwt authenticator with custom username claim",
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
jwtAuthenticators: []runtime.Object{
&auth1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-namespace",
Name: "test-name",
},
Spec: *someJWTAuthenticatorSpecWithUsernameClaim,
},
},
wantLogs: []string{
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
},
wantCacheEntries: 1,
wantUsernameClaim: someJWTAuthenticatorSpecWithUsernameClaim.UsernameClaim,
runTestsOnResultingAuthenticator: true,
},
{
name: "valid jwt authenticator with custom groups claim",
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
jwtAuthenticators: []runtime.Object{
&auth1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-namespace",
Name: "test-name",
},
Spec: *someJWTAuthenticatorSpecWithGroupsClaim,
},
},
wantLogs: []string{
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
},
wantCacheEntries: 1,
wantGroupsClaim: someJWTAuthenticatorSpecWithGroupsClaim.GroupsClaim,
runTestsOnResultingAuthenticator: true,
},
{ {
name: "updating jwt authenticator with new fields closes previous instance", name: "updating jwt authenticator with new fields closes previous instance",
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
@ -329,6 +381,14 @@ func TestController(t *testing.T) {
goodUsername = "pinny123" goodUsername = "pinny123"
) )
if tt.wantUsernameClaim == "" {
tt.wantUsernameClaim = "username"
}
if tt.wantGroupsClaim == "" {
tt.wantGroupsClaim = "groups"
}
for _, test := range testTableForAuthenticateTokenTests( for _, test := range testTableForAuthenticateTokenTests(
t, t,
goodRSASigningKey, goodRSASigningKey,
@ -337,6 +397,8 @@ func TestController(t *testing.T) {
group0, group0,
group1, group1,
goodUsername, goodUsername,
tt.wantUsernameClaim,
tt.wantGroupsClaim,
) { ) {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
@ -363,7 +425,17 @@ func TestController(t *testing.T) {
test.jwtSignature(&signingKey, &signingAlgo, &signingKID) test.jwtSignature(&signingKey, &signingAlgo, &signingKID)
} }
jwt := createJWT(t, signingKey, signingAlgo, signingKID, &wellKnownClaims, groups, username) jwt := createJWT(
t,
signingKey,
signingAlgo,
signingKID,
&wellKnownClaims,
tt.wantGroupsClaim,
groups,
tt.wantUsernameClaim,
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)
@ -387,6 +459,8 @@ func testTableForAuthenticateTokenTests(
group0 string, group0 string,
group1 string, group1 string,
goodUsername string, goodUsername string,
expectedUsernameClaim string,
expectedGroupsClaim string,
) []struct { ) []struct {
name string name string
jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}, username *string) jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}, username *string)
@ -469,7 +543,7 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(_ *jwt.Claims, groups *interface{}, username *string) { 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 \"" + expectedGroupsClaim + "\": json: cannot unmarshal object into Go value of type string",
}, },
{ {
name: "bad token with wrong issuer", name: "bad token with wrong issuer",
@ -519,7 +593,7 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) { jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
*username = "" *username = ""
}, },
wantErrorRegexp: `oidc: parse username claims "username": claim not present`, wantErrorRegexp: `oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`,
}, },
{ {
name: "signing key is wrong", name: "signing key is wrong",
@ -567,8 +641,10 @@ func createJWT(
signingAlgo jose.SignatureAlgorithm, signingAlgo jose.SignatureAlgorithm,
kid string, kid string,
claims *jwt.Claims, claims *jwt.Claims,
groups interface{}, groupsClaim string,
username string, groupsValue interface{},
usernameClaim string,
usernameValue string,
) string { ) string {
t.Helper() t.Helper()
@ -579,11 +655,11 @@ func createJWT(
require.NoError(t, err) require.NoError(t, err)
builder := jwt.Signed(sig).Claims(claims) builder := jwt.Signed(sig).Claims(claims)
if groups != nil { if groupsValue != nil {
builder = builder.Claims(map[string]interface{}{"groups": groups}) builder = builder.Claims(map[string]interface{}{groupsClaim: groupsValue})
} }
if username != "" { if usernameValue != "" {
builder = builder.Claims(map[string]interface{}{"username": username}) builder = builder.Claims(map[string]interface{}{usernameClaim: usernameValue})
} }
jwt, err := builder.CompactSerialize() jwt, err := builder.CompactSerialize()
require.NoError(t, err) require.NoError(t, err)