Updates the OIDC/JWT auth plugin (#10546)

This commit is contained in:
Austin Gebauer 2020-12-14 10:07:07 -08:00 committed by GitHub
parent acd0cd037c
commit 747d49150b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 58 additions and 56 deletions

3
changelog/10546.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
auth/jwt: Fixes `bound_claims` validation for provider-specific group and user info fetching.
```

2
go.mod
View File

@ -78,7 +78,7 @@ require (
github.com/hashicorp/vault-plugin-auth-centrify v0.7.0 github.com/hashicorp/vault-plugin-auth-centrify v0.7.0
github.com/hashicorp/vault-plugin-auth-cf v0.7.0 github.com/hashicorp/vault-plugin-auth-cf v0.7.0
github.com/hashicorp/vault-plugin-auth-gcp v0.8.0 github.com/hashicorp/vault-plugin-auth-gcp v0.8.0
github.com/hashicorp/vault-plugin-auth-jwt v0.8.0 github.com/hashicorp/vault-plugin-auth-jwt v0.7.2-0.20201203001230-e35700fcc0d5
github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0 github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0
github.com/hashicorp/vault-plugin-auth-kubernetes v0.8.0 github.com/hashicorp/vault-plugin-auth-kubernetes v0.8.0
github.com/hashicorp/vault-plugin-auth-oci v0.6.0 github.com/hashicorp/vault-plugin-auth-oci v0.6.0

4
go.sum
View File

@ -634,8 +634,8 @@ github.com/hashicorp/vault-plugin-auth-cf v0.7.0/go.mod h1:exPUMj8yNohKM7yRiHa7O
github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazVLF2sVbSAJ3LRHLRhF/pUmlI= github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazVLF2sVbSAJ3LRHLRhF/pUmlI=
github.com/hashicorp/vault-plugin-auth-gcp v0.8.0 h1:E9EHvC9jCDNix/pB9NKYYLMUkpfv65TSDk2rVvtkdzU= github.com/hashicorp/vault-plugin-auth-gcp v0.8.0 h1:E9EHvC9jCDNix/pB9NKYYLMUkpfv65TSDk2rVvtkdzU=
github.com/hashicorp/vault-plugin-auth-gcp v0.8.0/go.mod h1:sHDguHmyGScoalGLEjuxvDCrMPVlw2c3f+ieeiHcv6w= github.com/hashicorp/vault-plugin-auth-gcp v0.8.0/go.mod h1:sHDguHmyGScoalGLEjuxvDCrMPVlw2c3f+ieeiHcv6w=
github.com/hashicorp/vault-plugin-auth-jwt v0.8.0 h1:DvwV2RPdZ4q6eTXJ1APIGsEdqzkkZXls1d46e45QSrk= github.com/hashicorp/vault-plugin-auth-jwt v0.7.2-0.20201203001230-e35700fcc0d5 h1:BEsc9LNqgCNMhRVVOzS2v1Czioqod5Lln+Zol7zFmak=
github.com/hashicorp/vault-plugin-auth-jwt v0.8.0/go.mod h1:pyR4z5f2Vuz9TXucuN0rivUJTtSdlOtDdZ16IqBjZVo= github.com/hashicorp/vault-plugin-auth-jwt v0.7.2-0.20201203001230-e35700fcc0d5/go.mod h1:pyR4z5f2Vuz9TXucuN0rivUJTtSdlOtDdZ16IqBjZVo=
github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0 h1:7ct50ngVFTeO7EJ3N9PvPHeHc+2cANTHi2+9RwIUIHM= github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0 h1:7ct50ngVFTeO7EJ3N9PvPHeHc+2cANTHi2+9RwIUIHM=
github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0/go.mod h1:IM/n7LY1rIM4MVzOfSH6cRmY/C2rGkrjGrEr0B/yO9c= github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0/go.mod h1:IM/n7LY1rIM4MVzOfSH6cRmY/C2rGkrjGrEr0B/yO9c=
github.com/hashicorp/vault-plugin-auth-kubernetes v0.8.0 h1:v1jOqR70chxRxONey7g/v0/57MneP05z2dfw6qmlE+8= github.com/hashicorp/vault-plugin-auth-kubernetes v0.8.0 h1:v1jOqR70chxRxONey7g/v0/57MneP05z2dfw6qmlE+8=

View File

@ -303,7 +303,7 @@ func (b *jwtAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque
} }
// Validate provider_config // Validate provider_config
if _, err := NewProviderConfig(config, ProviderMap()); err != nil { if _, err := NewProviderConfig(ctx, config, ProviderMap()); err != nil {
return logical.ErrorResponse("invalid provider_config: %s", err), nil return logical.ErrorResponse("invalid provider_config: %s", err), nil
} }

View File

@ -213,15 +213,15 @@ func (b *jwtAuthBackend) pathLogin(ctx context.Context, req *logical.Request, d
return nil, errors.New("unhandled case during login") return nil, errors.New("unhandled case during login")
} }
if err := validateBoundClaims(b.Logger(), role.BoundClaimsType, role.BoundClaims, allClaims); err != nil { alias, groupAliases, err := b.createIdentity(ctx, allClaims, role)
return logical.ErrorResponse("error validating claims: %s", err.Error()), nil
}
alias, groupAliases, err := b.createIdentity(allClaims, role)
if err != nil { if err != nil {
return logical.ErrorResponse(err.Error()), nil return logical.ErrorResponse(err.Error()), nil
} }
if err := validateBoundClaims(b.Logger(), role.BoundClaimsType, role.BoundClaims, allClaims); err != nil {
return logical.ErrorResponse("error validating claims: %s", err.Error()), nil
}
tokenMetadata := map[string]string{"role": roleName} tokenMetadata := map[string]string{"role": roleName}
for k, v := range alias.Metadata { for k, v := range alias.Metadata {
tokenMetadata[k] = v tokenMetadata[k] = v
@ -308,7 +308,7 @@ func (b *jwtAuthBackend) verifyOIDCToken(ctx context.Context, config *jwtConfig,
// createIdentity creates an alias and set of groups aliases based on the role // createIdentity creates an alias and set of groups aliases based on the role
// definition and received claims. // definition and received claims.
func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role *jwtRole) (*logical.Alias, []*logical.Alias, error) { func (b *jwtAuthBackend) createIdentity(ctx context.Context, allClaims map[string]interface{}, role *jwtRole) (*logical.Alias, []*logical.Alias, error) {
userClaimRaw, ok := allClaims[role.UserClaim] userClaimRaw, ok := allClaims[role.UserClaim]
if !ok { if !ok {
return nil, nil, fmt.Errorf("claim %q not found in token", role.UserClaim) return nil, nil, fmt.Errorf("claim %q not found in token", role.UserClaim)
@ -318,8 +318,12 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role *
return nil, nil, fmt.Errorf("claim %q could not be converted to string", role.UserClaim) return nil, nil, fmt.Errorf("claim %q could not be converted to string", role.UserClaim)
} }
err := b.fetchUserInfo(allClaims, role) pConfig, err := NewProviderConfig(ctx, b.cachedConfig, ProviderMap())
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to load custom provider config: %s", err)
}
if err := b.fetchUserInfo(ctx, pConfig, allClaims, role); err != nil {
return nil, nil, err return nil, nil, err
} }
@ -339,7 +343,7 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role *
return alias, groupAliases, nil return alias, groupAliases, nil
} }
groupsClaimRaw, err := b.fetchGroups(allClaims, role) groupsClaimRaw, err := b.fetchGroups(ctx, pConfig, allClaims, role)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to fetch groups: %s", err) return nil, nil, fmt.Errorf("failed to fetch groups: %s", err)
} }
@ -366,15 +370,11 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role *
} }
// Checks if there's a custom provider_config and calls FetchUserInfo() if implemented. // Checks if there's a custom provider_config and calls FetchUserInfo() if implemented.
func (b *jwtAuthBackend) fetchUserInfo(allClaims map[string]interface{}, role *jwtRole) error { func (b *jwtAuthBackend) fetchUserInfo(ctx context.Context, pConfig CustomProvider, allClaims map[string]interface{}, role *jwtRole) error {
pConfig, err := NewProviderConfig(b.cachedConfig, ProviderMap())
if err != nil {
return fmt.Errorf("failed to load custom provider config: %s", err)
}
// Fetch user info from custom provider if it's implemented // Fetch user info from custom provider if it's implemented
if pConfig != nil { if pConfig != nil {
if uif, ok := pConfig.(UserInfoFetcher); ok { if uif, ok := pConfig.(UserInfoFetcher); ok {
return uif.FetchUserInfo(b, allClaims, role) return uif.FetchUserInfo(ctx, b, allClaims, role)
} }
} }
@ -382,16 +382,19 @@ func (b *jwtAuthBackend) fetchUserInfo(allClaims map[string]interface{}, role *j
} }
// Checks if there's a custom provider_config and calls FetchGroups() if implemented // Checks if there's a custom provider_config and calls FetchGroups() if implemented
func (b *jwtAuthBackend) fetchGroups(allClaims map[string]interface{}, role *jwtRole) (interface{}, error) { func (b *jwtAuthBackend) fetchGroups(ctx context.Context, pConfig CustomProvider, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) {
pConfig, err := NewProviderConfig(b.cachedConfig, ProviderMap())
if err != nil {
return nil, fmt.Errorf("failed to load custom provider config: %s", err)
}
// If the custom provider implements interface GroupsFetcher, call it, // If the custom provider implements interface GroupsFetcher, call it,
// otherwise fall through to the default method // otherwise fall through to the default method
if pConfig != nil { if pConfig != nil {
if gf, ok := pConfig.(GroupsFetcher); ok { if gf, ok := pConfig.(GroupsFetcher); ok {
return gf.FetchGroups(b, allClaims, role) groupsRaw, err := gf.FetchGroups(ctx, b, allClaims, role)
if err != nil {
return nil, err
}
// Add groups obtained by provider-specific fetching to the claims
// so that they can be used for bound_claims validation on the role.
allClaims["groups"] = groupsRaw
} }
} }
groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim) groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim)

View File

@ -280,15 +280,15 @@ func (b *jwtAuthBackend) pathCallback(ctx context.Context, req *logical.Request,
} }
} }
if err := validateBoundClaims(b.Logger(), role.BoundClaimsType, role.BoundClaims, allClaims); err != nil { alias, groupAliases, err := b.createIdentity(ctx, allClaims, role)
return logical.ErrorResponse("error validating claims: %s", err.Error()), nil
}
alias, groupAliases, err := b.createIdentity(allClaims, role)
if err != nil { if err != nil {
return logical.ErrorResponse(err.Error()), nil return logical.ErrorResponse(err.Error()), nil
} }
if err := validateBoundClaims(b.Logger(), role.BoundClaimsType, role.BoundClaims, allClaims); err != nil {
return logical.ErrorResponse("error validating claims: %s", err.Error()), nil
}
tokenMetadata := map[string]string{"role": roleName} tokenMetadata := map[string]string{"role": roleName}
for k, v := range alias.Metadata { for k, v := range alias.Metadata {
tokenMetadata[k] = v tokenMetadata[k] = v

View File

@ -35,7 +35,7 @@ type AzureProvider struct {
} }
// Initialize anything in the AzureProvider struct - satisfying the CustomProvider interface // Initialize anything in the AzureProvider struct - satisfying the CustomProvider interface
func (a *AzureProvider) Initialize(jc *jwtConfig) error { func (a *AzureProvider) Initialize(_ context.Context, _ *jwtConfig) error {
return nil return nil
} }
@ -45,7 +45,7 @@ func (a *AzureProvider) SensitiveKeys() []string {
} }
// FetchGroups - custom groups fetching for azure - satisfying GroupsFetcher interface // FetchGroups - custom groups fetching for azure - satisfying GroupsFetcher interface
func (a *AzureProvider) FetchGroups(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) { func (a *AzureProvider) FetchGroups(_ context.Context, b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) {
groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim) groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim)
if groupsClaimRaw == nil { if groupsClaimRaw == nil {

View File

@ -1,6 +1,7 @@
package jwtauth package jwtauth
import ( import (
"context"
"fmt" "fmt"
) )
@ -21,7 +22,7 @@ type CustomProvider interface {
// Initialize should validate jwtConfig.ProviderConfig, set internal values // Initialize should validate jwtConfig.ProviderConfig, set internal values
// and run any initialization necessary for subsequent calls to interface // and run any initialization necessary for subsequent calls to interface
// functions the provider implements // functions the provider implements
Initialize(*jwtConfig) error Initialize(context.Context, *jwtConfig) error
// SensitiveKeys returns any fields in a provider's jwtConfig.ProviderConfig // SensitiveKeys returns any fields in a provider's jwtConfig.ProviderConfig
// that should be masked or omitted when output // that should be masked or omitted when output
@ -31,7 +32,7 @@ type CustomProvider interface {
// NewProviderConfig - returns appropriate provider struct if provider_config is // NewProviderConfig - returns appropriate provider struct if provider_config is
// specified in jwtConfig. The provider map is provider name -to- instance of a // specified in jwtConfig. The provider map is provider name -to- instance of a
// CustomProvider. // CustomProvider.
func NewProviderConfig(jc *jwtConfig, providerMap map[string]CustomProvider) (CustomProvider, error) { func NewProviderConfig(ctx context.Context, jc *jwtConfig, providerMap map[string]CustomProvider) (CustomProvider, error) {
if len(jc.ProviderConfig) == 0 { if len(jc.ProviderConfig) == 0 {
return nil, nil return nil, nil
} }
@ -43,7 +44,7 @@ func NewProviderConfig(jc *jwtConfig, providerMap map[string]CustomProvider) (Cu
if !ok { if !ok {
return nil, fmt.Errorf("provider %q not found in custom providers", provider) return nil, fmt.Errorf("provider %q not found in custom providers", provider)
} }
if err := newCustomProvider.Initialize(jc); err != nil { if err := newCustomProvider.Initialize(ctx, jc); err != nil {
return nil, fmt.Errorf("error initializing %q provider_config: %s", provider, err) return nil, fmt.Errorf("error initializing %q provider_config: %s", provider, err)
} }
return newCustomProvider, nil return newCustomProvider, nil
@ -51,11 +52,11 @@ func NewProviderConfig(jc *jwtConfig, providerMap map[string]CustomProvider) (Cu
// UserInfoFetcher - Optional support for custom user info handling // UserInfoFetcher - Optional support for custom user info handling
type UserInfoFetcher interface { type UserInfoFetcher interface {
FetchUserInfo(*jwtAuthBackend, map[string]interface{}, *jwtRole) error FetchUserInfo(context.Context, *jwtAuthBackend, map[string]interface{}, *jwtRole) error
} }
// GroupsFetcher - Optional support for custom groups handling // GroupsFetcher - Optional support for custom groups handling
type GroupsFetcher interface { type GroupsFetcher interface {
// FetchGroups queries for groups claims during login // FetchGroups queries for groups claims during login
FetchGroups(*jwtAuthBackend, map[string]interface{}, *jwtRole) (interface{}, error) FetchGroups(context.Context, *jwtAuthBackend, map[string]interface{}, *jwtRole) (interface{}, error)
} }

View File

@ -46,7 +46,7 @@ type GSuiteProviderConfig struct {
} }
// Initialize initializes the GSuiteProvider by validating and creating configuration. // Initialize initializes the GSuiteProvider by validating and creating configuration.
func (g *GSuiteProvider) Initialize(jc *jwtConfig) error { func (g *GSuiteProvider) Initialize(ctx context.Context, jc *jwtConfig) error {
// Decode the provider config // Decode the provider config
var config GSuiteProviderConfig var config GSuiteProviderConfig
if err := mapstructure.Decode(jc.ProviderConfig, &config); err != nil { if err := mapstructure.Decode(jc.ProviderConfig, &config); err != nil {
@ -60,10 +60,10 @@ func (g *GSuiteProvider) Initialize(jc *jwtConfig) error {
} }
config.serviceAccountKeyJSON = keyJSON config.serviceAccountKeyJSON = keyJSON
return g.initialize(config) return g.initialize(ctx, config)
} }
func (g *GSuiteProvider) initialize(config GSuiteProviderConfig) error { func (g *GSuiteProvider) initialize(ctx context.Context, config GSuiteProviderConfig) error {
var err error var err error
// Validate configuration // Validate configuration
@ -88,6 +88,13 @@ func (g *GSuiteProvider) initialize(config GSuiteProviderConfig) error {
// Set the subject to impersonate and config // Set the subject to impersonate and config
g.jwtConfig.Subject = config.AdminImpersonateEmail g.jwtConfig.Subject = config.AdminImpersonateEmail
g.config = config g.config = config
// Create a new admin service for requests to Google admin APIs
g.adminSvc, err = admin.NewService(ctx, option.WithHTTPClient(g.jwtConfig.Client(ctx)))
if err != nil {
return err
}
return nil return nil
} }
@ -97,7 +104,7 @@ func (g *GSuiteProvider) SensitiveKeys() []string {
} }
// FetchGroups fetches and returns groups from G Suite. // FetchGroups fetches and returns groups from G Suite.
func (g *GSuiteProvider) FetchGroups(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) { func (g *GSuiteProvider) FetchGroups(ctx context.Context, b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) {
if !g.config.FetchGroups { if !g.config.FetchGroups {
return nil, nil return nil, nil
} }
@ -107,15 +114,9 @@ func (g *GSuiteProvider) FetchGroups(b *jwtAuthBackend, allClaims map[string]int
return nil, err return nil, err
} }
// Set context and create a new admin service for requests to Google admin APIs
g.adminSvc, err = admin.NewService(b.providerCtx, option.WithHTTPClient(g.jwtConfig.Client(b.providerCtx)))
if err != nil {
return nil, err
}
// Get the G Suite groups // Get the G Suite groups
userGroupsMap := make(map[string]bool) userGroupsMap := make(map[string]bool)
if err := g.search(b.providerCtx, userGroupsMap, userName, g.config.GroupsRecurseMaxDepth); err != nil { if err := g.search(ctx, userGroupsMap, userName, g.config.GroupsRecurseMaxDepth); err != nil {
return nil, err return nil, err
} }
@ -157,7 +158,7 @@ func (g *GSuiteProvider) search(ctx context.Context, visited map[string]bool, us
} }
// FetchUserInfo fetches additional user information from G Suite using custom schemas. // FetchUserInfo fetches additional user information from G Suite using custom schemas.
func (g *GSuiteProvider) FetchUserInfo(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) error { func (g *GSuiteProvider) FetchUserInfo(ctx context.Context, b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) error {
if !g.config.FetchUserInfo || g.config.UserCustomSchemas == "" { if !g.config.FetchUserInfo || g.config.UserCustomSchemas == "" {
if g.config.UserCustomSchemas != "" { if g.config.UserCustomSchemas != "" {
b.Logger().Warn(fmt.Sprintf("must set 'fetch_user_info=true' to fetch 'user_custom_schemas': %s", g.config.UserCustomSchemas)) b.Logger().Warn(fmt.Sprintf("must set 'fetch_user_info=true' to fetch 'user_custom_schemas': %s", g.config.UserCustomSchemas))
@ -171,13 +172,7 @@ func (g *GSuiteProvider) FetchUserInfo(b *jwtAuthBackend, allClaims map[string]i
return err return err
} }
// Set context and create a new admin service for requests to Google admin APIs return g.fillCustomSchemas(ctx, userName, allClaims)
g.adminSvc, err = admin.NewService(b.providerCtx, option.WithHTTPClient(g.jwtConfig.Client(b.providerCtx)))
if err != nil {
return err
}
return g.fillCustomSchemas(b.providerCtx, userName, allClaims)
} }
// fillCustomSchemas fetches G Suite user information associated with the custom schemas // fillCustomSchemas fetches G Suite user information associated with the custom schemas

2
vendor/modules.txt vendored
View File

@ -515,7 +515,7 @@ github.com/hashicorp/vault-plugin-auth-cf/util
# github.com/hashicorp/vault-plugin-auth-gcp v0.8.0 # github.com/hashicorp/vault-plugin-auth-gcp v0.8.0
github.com/hashicorp/vault-plugin-auth-gcp/plugin github.com/hashicorp/vault-plugin-auth-gcp/plugin
github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache
# github.com/hashicorp/vault-plugin-auth-jwt v0.8.0 # github.com/hashicorp/vault-plugin-auth-jwt v0.7.2-0.20201203001230-e35700fcc0d5
github.com/hashicorp/vault-plugin-auth-jwt github.com/hashicorp/vault-plugin-auth-jwt
# github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0 # github.com/hashicorp/vault-plugin-auth-kerberos v0.2.0
github.com/hashicorp/vault-plugin-auth-kerberos github.com/hashicorp/vault-plugin-auth-kerberos