Commit d2b1d320 authored by Calvin Leung Huang's avatar Calvin Leung Huang
Browse files

update jwt to release/vault-1.5.x

parent b4d929f6
Showing with 13 additions and 28769 deletions
+13 -28769
......@@ -426,6 +426,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul-template v0.25.0/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0=
github.com/hashicorp/consul-template v0.25.1 h1:+D2s8eyRqWyX7GPNxeUi8tsyh8pRn3J6k8giEchPfKQ=
github.com/hashicorp/consul-template v0.25.1/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0=
github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY=
......@@ -547,6 +548,8 @@ github.com/hashicorp/vault-plugin-auth-gcp v0.6.2-0.20200428223335-82bd3a3ad5b3/
github.com/hashicorp/vault-plugin-auth-gcp v0.7.0 h1:38xERGtaK55lx5QOxBZP3i6aJZ/UvdfxVJlTai2FlE8=
github.com/hashicorp/vault-plugin-auth-gcp v0.7.0/go.mod h1:sHDguHmyGScoalGLEjuxvDCrMPVlw2c3f+ieeiHcv6w=
github.com/hashicorp/vault-plugin-auth-jwt v0.6.2/go.mod h1:SFadxIfoLGzugEjwUUmUaCGbsYEz2/jJymZDDQjEqYg=
github.com/hashicorp/vault-plugin-auth-jwt v0.7.1-0.20200817210115-8a9339e8d4cf h1:PAi65RHYHH0qFBC27YcgBuatvqKWHtOOKcB3ZTPc5Ns=
github.com/hashicorp/vault-plugin-auth-jwt v0.7.1-0.20200817210115-8a9339e8d4cf/go.mod h1:ZJJy4b0H3N7CSoJ6iPlWhV9EjHLSoB5NhP06CNm6ImU=
github.com/hashicorp/vault-plugin-auth-jwt v0.7.1 h1:6nuMtCs/c/rphMv05Z7Y4Nrt6Ae+AZjGb7yYdbJXIe8=
github.com/hashicorp/vault-plugin-auth-jwt v0.7.1/go.mod h1:pyR4z5f2Vuz9TXucuN0rivUJTtSdlOtDdZ16IqBjZVo=
github.com/hashicorp/vault-plugin-auth-kerberos v0.1.5 h1:knWedzZ51g8Aj6Hyi1ATlQ/7jEx6nJeqFoCoHSrbQFI=
......
......@@ -14,14 +14,15 @@ require (
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/mitchellh/mapstructure v1.1.2
github.com/mitchellh/pointerstructure v1.0.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/ryanuber/go-glob v1.0.0
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.3.0
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
google.golang.org/api v0.29.0
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/text v0.3.2 // indirect
google.golang.org/appengine v1.5.0 // indirect
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect
gopkg.in/square/go-jose.v2 v2.4.1
)
This diff is collapsed.
......@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/logical"
"golang.org/x/oauth2"
jose "gopkg.in/square/go-jose.v2"
)
const (
......@@ -258,7 +259,7 @@ func (b *jwtAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque
// default to e.g. "none".
for _, a := range config.JWTSupportedAlgs {
switch a {
case oidc.RS256, oidc.RS384, oidc.RS512, oidc.ES256, oidc.ES384, oidc.ES512, oidc.PS256, oidc.PS384, oidc.PS512:
case oidc.RS256, oidc.RS384, oidc.RS512, oidc.ES256, oidc.ES384, oidc.ES512, oidc.PS256, oidc.PS384, oidc.PS512, string(jose.EdDSA):
default:
return logical.ErrorResponse(fmt.Sprintf("Invalid supported algorithm: %s", a)), nil
}
......
......@@ -318,11 +318,6 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role *
return nil, nil, fmt.Errorf("claim %q could not be converted to string", role.UserClaim)
}
err := b.fetchUserInfo(allClaims, role)
if err != nil {
return nil, nil, err
}
metadata, err := extractMetadata(b.Logger(), allClaims, role.ClaimMappings)
if err != nil {
return nil, nil, err
......@@ -365,22 +360,6 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role *
return alias, groupAliases, nil
}
// Checks if there's a custom provider_config and calls FetchUserInfo() if implemented.
func (b *jwtAuthBackend) fetchUserInfo(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
if pConfig != nil {
if uif, ok := pConfig.(UserInfoFetcher); ok {
return uif.FetchUserInfo(b, allClaims, role)
}
}
return nil
}
// 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) {
pConfig, err := NewProviderConfig(b.cachedConfig, ProviderMap())
......
......@@ -11,8 +11,7 @@ import (
// ProviderMap returns a map of provider names to custom types
func ProviderMap() map[string]CustomProvider {
return map[string]CustomProvider{
"azure": &AzureProvider{},
"gsuite": &GSuiteProvider{},
"azure": &AzureProvider{},
}
}
......@@ -49,11 +48,6 @@ func NewProviderConfig(jc *jwtConfig, providerMap map[string]CustomProvider) (Cu
return newCustomProvider, nil
}
// UserInfoFetcher - Optional support for custom user info handling
type UserInfoFetcher interface {
FetchUserInfo(*jwtAuthBackend, map[string]interface{}, *jwtRole) error
}
// GroupsFetcher - Optional support for custom groups handling
type GroupsFetcher interface {
// FetchGroups queries for groups claims during login
......
package jwtauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
admin "google.golang.org/api/admin/directory/v1"
"google.golang.org/api/option"
)
// GSuiteProvider provides G Suite-specific configuration and behavior.
type GSuiteProvider struct {
config GSuiteProviderConfig // Configuration for the provider
jwtConfig *jwt.Config // Google JWT configuration
adminSvc *admin.Service // Google admin service
}
// GSuiteProviderConfig represents the configuration for a GSuiteProvider.
type GSuiteProviderConfig struct {
// Path to a Google service account key file. Required.
ServiceAccountFilePath string `mapstructure:"gsuite_service_account"`
// Email address of a G Suite admin to impersonate. Required.
AdminImpersonateEmail string `mapstructure:"gsuite_admin_impersonate"`
// If set to true, groups will be fetched from G Suite.
FetchGroups bool `mapstructure:"fetch_groups"`
// If set to true, user info will be fetched from G Suite using UserCustomSchemas.
FetchUserInfo bool `mapstructure:"fetch_user_info"`
// Group membership recursion max depth (0 = do not recurse).
GroupsRecurseMaxDepth int `mapstructure:"groups_recurse_max_depth"`
// Comma-separated list of G Suite custom schemas to fetch as claims.
UserCustomSchemas string `mapstructure:"user_custom_schemas"`
// JSON contents of a Google service account key file.
serviceAccountKeyJSON []byte
}
// Initialize initializes the GSuiteProvider by validating and creating configuration.
func (g *GSuiteProvider) Initialize(jc *jwtConfig) error {
// Decode the provider config
var config GSuiteProviderConfig
if err := mapstructure.Decode(jc.ProviderConfig, &config); err != nil {
return err
}
// Read the Google service account key file
keyJSON, err := ioutil.ReadFile(config.ServiceAccountFilePath)
if err != nil {
return err
}
config.serviceAccountKeyJSON = keyJSON
return g.initialize(config)
}
func (g *GSuiteProvider) initialize(config GSuiteProviderConfig) error {
var err error
// Validate configuration
if config.ServiceAccountFilePath == "" {
return errors.New("'gsuite_service_account' must be set to the file path for a " +
"service account key")
}
if config.AdminImpersonateEmail == "" {
return errors.New("'gsuite_admin_impersonate' must be set to an email address of a " +
"G Suite user with 'Read' permission to access the G Suite Admin User and Group APIs")
}
if config.GroupsRecurseMaxDepth < 0 {
return errors.New("'gsuite_recurse_max_depth' must be a positive integer")
}
// Create the google JWT config from the service account key file
if g.jwtConfig, err = google.JWTConfigFromJSON(config.serviceAccountKeyJSON,
admin.AdminDirectoryGroupReadonlyScope, admin.AdminDirectoryUserReadonlyScope); err != nil {
return err
}
// Set the subject to impersonate and config
g.jwtConfig.Subject = config.AdminImpersonateEmail
g.config = config
return nil
}
// SensitiveKeys returns keys that should be redacted when reading the config of this provider
func (g *GSuiteProvider) SensitiveKeys() []string {
return []string{}
}
// FetchGroups fetches and returns groups from G Suite.
func (g *GSuiteProvider) FetchGroups(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) {
if !g.config.FetchGroups {
return nil, nil
}
userName, err := g.getUserClaim(b, allClaims, role)
if err != nil {
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
userGroupsMap := make(map[string]bool)
if err := g.search(b.providerCtx, userGroupsMap, userName, g.config.GroupsRecurseMaxDepth); err != nil {
return nil, err
}
// Convert set of groups to list
var userGroups = make([]interface{}, 0, len(userGroupsMap))
for email := range userGroupsMap {
userGroups = append(userGroups, email)
}
b.Logger().Debug("fetched G Suite groups", "groups", userGroups)
return userGroups, nil
}
// search recursively searches for G Suite groups based on a configured depth for this provider.
func (g *GSuiteProvider) search(ctx context.Context, visited map[string]bool, userName string, depth int) error {
call := g.adminSvc.Groups.List().UserKey(userName).Fields("nextPageToken", "groups(email)")
if err := call.Pages(ctx, func(groups *admin.Groups) error {
var newGroups []string
for _, group := range groups.Groups {
if _, ok := visited[group.Email]; ok {
continue
}
visited[group.Email] = true
newGroups = append(newGroups, group.Email)
}
// Only recursively search for new groups that haven't been seen
if depth > 0 {
for _, email := range newGroups {
if err := g.search(ctx, visited, email, depth-1); err != nil {
return err
}
}
}
return nil
}); err != nil {
return err
}
return nil
}
// FetchUserInfo fetches additional user information from G Suite using custom schemas.
func (g *GSuiteProvider) FetchUserInfo(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) error {
if !g.config.FetchUserInfo || 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))
}
return nil
}
userName, err := g.getUserClaim(b, allClaims, role)
if err != nil {
return 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 err
}
return g.fillCustomSchemas(b.providerCtx, userName, allClaims)
}
// fillCustomSchemas fetches G Suite user information associated with the custom schemas
// configured for this provider. It inserts the schema -> value pairs into the passed
// allClaims so that the values can be used for claim mapping to token and identity metadata.
func (g *GSuiteProvider) fillCustomSchemas(ctx context.Context, userName string, allClaims map[string]interface{}) error {
userResponse, err := g.adminSvc.Users.Get(userName).Context(ctx).Projection("custom").
CustomFieldMask(g.config.UserCustomSchemas).Fields("customSchemas").Do()
if err != nil {
return err
}
for schema, rawValue := range userResponse.CustomSchemas {
// note: metadata extraction via claim_mappings only supports strings
// as values, but filtering happens later so we must use interface{}
var value map[string]interface{}
if err := json.Unmarshal(rawValue, &value); err != nil {
return err
}
allClaims[schema] = value
}
return nil
}
// getUserClaim returns the user claim value configured in the passed role.
// If the user claim is not found or is not a string, an error is returned.
func (g *GSuiteProvider) getUserClaim(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (string, error) {
userClaimRaw := getClaim(b.Logger(), allClaims, role.UserClaim)
if userClaimRaw == nil {
return "", fmt.Errorf("unable to locate %q in claims", role.UserClaim)
}
userClaim, ok := userClaimRaw.(string)
if !ok {
return "", fmt.Errorf("claim %q could not be converted to string", role.UserClaim)
}
return userClaim, nil
}
This diff is collapsed.
This diff is collapsed.
......@@ -461,7 +461,7 @@ github.com/hashicorp/vault-plugin-auth-cf/util
# github.com/hashicorp/vault-plugin-auth-gcp v0.7.0
github.com/hashicorp/vault-plugin-auth-gcp/plugin
github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache
# github.com/hashicorp/vault-plugin-auth-jwt v0.7.1
# github.com/hashicorp/vault-plugin-auth-jwt v0.7.1-0.20200817210115-8a9339e8d4cf
github.com/hashicorp/vault-plugin-auth-jwt
# github.com/hashicorp/vault-plugin-auth-kerberos v0.1.6
github.com/hashicorp/vault-plugin-auth-kerberos
......@@ -1006,7 +1006,6 @@ golang.org/x/tools/internal/packagesinternal
golang.org/x/xerrors
golang.org/x/xerrors/internal
# google.golang.org/api v0.29.0
google.golang.org/api/admin/directory/v1
google.golang.org/api/cloudresourcemanager/v1
google.golang.org/api/compute/v1
google.golang.org/api/googleapi
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment