plugin/pkg/auth/authenticator/token/oidc: get groups from custom claim
This commit is contained in:
@@ -42,13 +42,14 @@ type OIDCAuthenticator struct {
|
||||
clientConfig oidc.ClientConfig
|
||||
client *oidc.Client
|
||||
usernameClaim string
|
||||
groupsClaim string
|
||||
stopSyncProvider chan struct{}
|
||||
}
|
||||
|
||||
// New creates a new OpenID Connect client with the given issuerURL and clientID.
|
||||
// NOTE(yifan): For now we assume the server provides the "jwks_uri" so we don't
|
||||
// need to manager the key sets by ourselves.
|
||||
func New(issuerURL, clientID, caFile, usernameClaim string) (*OIDCAuthenticator, error) {
|
||||
func New(issuerURL, clientID, caFile, usernameClaim, groupsClaim string) (*OIDCAuthenticator, error) {
|
||||
var cfg oidc.ProviderConfig
|
||||
var err error
|
||||
var roots *x509.CertPool
|
||||
@@ -117,7 +118,7 @@ func New(issuerURL, clientID, caFile, usernameClaim string) (*OIDCAuthenticator,
|
||||
// and maximum threshold.
|
||||
stop := client.SyncProviderConfig(issuerURL)
|
||||
|
||||
return &OIDCAuthenticator{ccfg, client, usernameClaim, stop}, nil
|
||||
return &OIDCAuthenticator{ccfg, client, usernameClaim, groupsClaim, stop}, nil
|
||||
}
|
||||
|
||||
// AuthenticateToken decodes and verifies a JWT using the OIDC client, if the verification succeeds,
|
||||
@@ -155,8 +156,20 @@ func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, er
|
||||
username = fmt.Sprintf("%s#%s", a.clientConfig.ProviderConfig.Issuer, claim)
|
||||
}
|
||||
|
||||
// TODO(yifan): Add UID and Group, also populate the issuer to upper layer.
|
||||
return &user.DefaultInfo{Name: username}, true, nil
|
||||
// TODO(yifan): Add UID, also populate the issuer to upper layer.
|
||||
info := &user.DefaultInfo{Name: username}
|
||||
|
||||
if a.groupsClaim != "" {
|
||||
groups, found, err := claims.StringsClaim(a.groupsClaim)
|
||||
if err != nil {
|
||||
// Custom claim is present, but isn't an array of strings.
|
||||
return nil, false, fmt.Errorf("custom group claim contains invalid object: %v", err)
|
||||
}
|
||||
if found {
|
||||
info.Groups = groups
|
||||
}
|
||||
}
|
||||
return info, true, nil
|
||||
}
|
||||
|
||||
// Close closes the OIDC authenticator, this will close the provider sync goroutine.
|
||||
|
@@ -99,10 +99,13 @@ func (op *oidcProvider) handleKeys(w http.ResponseWriter, req *http.Request) {
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, usernameClaim, value string, iat, exp time.Time) string {
|
||||
func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string, iat, exp time.Time) string {
|
||||
signer := op.privKey.Signer()
|
||||
claims := oidc.NewClaims(iss, sub, aud, iat, exp)
|
||||
claims.Add(usernameClaim, value)
|
||||
if groups != nil && groupsClaim != "" {
|
||||
claims.Add(groupsClaim, groups)
|
||||
}
|
||||
|
||||
jwt, err := jose.NewSignedJWT(claims, signer)
|
||||
if err != nil {
|
||||
@@ -112,16 +115,16 @@ func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, userna
|
||||
return jwt.Encode()
|
||||
}
|
||||
|
||||
func (op *oidcProvider) generateGoodToken(t *testing.T, iss, sub, aud string, usernameClaim, value string) string {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, time.Now(), time.Now().Add(time.Hour))
|
||||
func (op *oidcProvider) generateGoodToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour))
|
||||
}
|
||||
|
||||
func (op *oidcProvider) generateMalformedToken(t *testing.T, iss, sub, aud string, usernameClaim, value string) string {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, time.Now(), time.Now().Add(time.Hour)) + "randombits"
|
||||
func (op *oidcProvider) generateMalformedToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + "randombits"
|
||||
}
|
||||
|
||||
func (op *oidcProvider) generateExpiredToken(t *testing.T, iss, sub, aud string, usernameClaim, value string) string {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour))
|
||||
func (op *oidcProvider) generateExpiredToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour))
|
||||
}
|
||||
|
||||
// generateSelfSignedCert generates a self-signed cert/key pairs and writes to the certPath/keyPath.
|
||||
@@ -192,7 +195,7 @@ func TestOIDCDiscoveryTimeout(t *testing.T) {
|
||||
retryBackoff = time.Second
|
||||
expectErr := fmt.Errorf("failed to fetch provider config after 3 retries")
|
||||
|
||||
_, err := New("https://foo/bar", "client-foo", "", "sub")
|
||||
_, err := New("https://foo/bar", "client-foo", "", "sub", "")
|
||||
if !reflect.DeepEqual(err, expectErr) {
|
||||
t.Errorf("Expecting %v, but got %v", expectErr, err)
|
||||
}
|
||||
@@ -224,7 +227,7 @@ func TestOIDCDiscoveryNoKeyEndpoint(t *testing.T) {
|
||||
Issuer: srv.URL,
|
||||
}
|
||||
|
||||
_, err = New(srv.URL, "client-foo", cert, "sub")
|
||||
_, err = New(srv.URL, "client-foo", cert, "sub", "")
|
||||
if !reflect.DeepEqual(err, expectErr) {
|
||||
t.Errorf("Expecting %v, but got %v", expectErr, err)
|
||||
}
|
||||
@@ -247,7 +250,7 @@ func TestOIDCDiscoverySecureConnection(t *testing.T) {
|
||||
|
||||
expectErr := fmt.Errorf("'oidc-issuer-url' (%q) has invalid scheme (%q), require 'https'", srv.URL, "http")
|
||||
|
||||
_, err := New(srv.URL, "client-foo", "", "sub")
|
||||
_, err := New(srv.URL, "client-foo", "", "sub", "")
|
||||
if !reflect.DeepEqual(err, expectErr) {
|
||||
t.Errorf("Expecting %v, but got %v", expectErr, err)
|
||||
}
|
||||
@@ -282,7 +285,7 @@ func TestOIDCDiscoverySecureConnection(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a client using cert2, should fail.
|
||||
_, err = New(tlsSrv.URL, "client-foo", cert2, "sub")
|
||||
_, err = New(tlsSrv.URL, "client-foo", cert2, "sub", "")
|
||||
if err == nil {
|
||||
t.Fatalf("Expecting error, but got nothing")
|
||||
}
|
||||
@@ -317,15 +320,17 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
userClaim string
|
||||
token string
|
||||
userInfo user.Info
|
||||
verified bool
|
||||
err string
|
||||
userClaim string
|
||||
groupsClaim string
|
||||
token string
|
||||
userInfo user.Info
|
||||
verified bool
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"sub",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo"),
|
||||
"",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
&user.DefaultInfo{Name: fmt.Sprintf("%s#%s", srv.URL, "user-foo")},
|
||||
true,
|
||||
"",
|
||||
@@ -333,14 +338,34 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
{
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com"),
|
||||
"",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "", nil),
|
||||
&user.DefaultInfo{Name: "foo@example.com"},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
"",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
|
||||
&user.DefaultInfo{Name: "foo@example.com"},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
"groups",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
|
||||
&user.DefaultInfo{Name: "foo@example.com", Groups: []string{"group1", "group2"}},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"sub",
|
||||
op.generateMalformedToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo"),
|
||||
"",
|
||||
op.generateMalformedToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"malformed JWS, unable to decode signature",
|
||||
@@ -348,7 +373,8 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
{
|
||||
// Invalid 'aud'.
|
||||
"sub",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-bar", "sub", "user-foo"),
|
||||
"",
|
||||
op.generateGoodToken(t, srv.URL, "client-foo", "client-bar", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
|
||||
@@ -356,14 +382,16 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
{
|
||||
// Invalid issuer.
|
||||
"sub",
|
||||
op.generateGoodToken(t, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo"),
|
||||
"",
|
||||
op.generateGoodToken(t, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: JWT claims invalid: invalid claim value: 'iss'.",
|
||||
},
|
||||
{
|
||||
"sub",
|
||||
op.generateExpiredToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo"),
|
||||
"",
|
||||
op.generateExpiredToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: JWT claims invalid: token is expired",
|
||||
@@ -371,7 +399,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
client, err := New(srv.URL, "client-foo", cert, tt.userClaim)
|
||||
client, err := New(srv.URL, "client-foo", cert, tt.userClaim, tt.groupsClaim)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user