From c94323528859302d84febb581952a26950264a1d Mon Sep 17 00:00:00 2001 From: Theron Voran Date: Mon, 29 Jun 2020 10:23:32 -0700 Subject: [PATCH] Update auth-jwt to v0.7.0 (#9320) Adds support for distributed groups claims on Azure, necessary when a user is a member of more than 200 groups. --- go.mod | 2 +- go.sum | 6 +- .../hashicorp/vault-plugin-auth-jwt/README.md | 23 ++- .../hashicorp/vault-plugin-auth-jwt/go.mod | 12 +- .../hashicorp/vault-plugin-auth-jwt/go.sum | 18 +- .../vault-plugin-auth-jwt/path_config.go | 47 +++-- .../vault-plugin-auth-jwt/path_login.go | 29 ++- .../vault-plugin-auth-jwt/path_oidc.go | 3 +- .../vault-plugin-auth-jwt/provider_azure.go | 188 ++++++++++++++++++ .../vault-plugin-auth-jwt/provider_config.go | 55 +++++ vendor/modules.txt | 4 +- 11 files changed, 351 insertions(+), 36 deletions(-) create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_azure.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_config.go diff --git a/go.mod b/go.mod index 1088a9269..134e89706 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( github.com/hashicorp/vault-plugin-auth-centrify v0.5.5 github.com/hashicorp/vault-plugin-auth-cf v0.5.4 github.com/hashicorp/vault-plugin-auth-gcp v0.6.2-0.20200428223335-82bd3a3ad5b3 - github.com/hashicorp/vault-plugin-auth-jwt v0.6.2 + github.com/hashicorp/vault-plugin-auth-jwt v0.7.0 github.com/hashicorp/vault-plugin-auth-kerberos v0.1.6 github.com/hashicorp/vault-plugin-auth-kubernetes v0.6.2 github.com/hashicorp/vault-plugin-auth-oci v0.5.5 diff --git a/go.sum b/go.sum index bbcfed359..4872d587b 100644 --- a/go.sum +++ b/go.sum @@ -531,8 +531,9 @@ github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazV github.com/hashicorp/vault-plugin-auth-gcp v0.6.1 h1:WXTuja3WC2BdZekYCnzuZGoVvZTAGH8kSDUHzOK2PQY= github.com/hashicorp/vault-plugin-auth-gcp v0.6.1/go.mod h1:8eBRzg+JIhAaDBfDndDAQKIhDrQ3WW8OPklxAYftNFs= github.com/hashicorp/vault-plugin-auth-gcp v0.6.2-0.20200428223335-82bd3a3ad5b3/go.mod h1:U0fkAlxWTEyQ74lx8wlGdD493lP1DD/qpMjXgOEbwj0= -github.com/hashicorp/vault-plugin-auth-jwt v0.6.2 h1:fp6Rk89iPjDS8dyEK7lEauYE/UhkgkHbmwRZKuQA01U= github.com/hashicorp/vault-plugin-auth-jwt v0.6.2/go.mod h1:SFadxIfoLGzugEjwUUmUaCGbsYEz2/jJymZDDQjEqYg= +github.com/hashicorp/vault-plugin-auth-jwt v0.7.0 h1:lHg02BB7IpUQbJStAPmGyS3KnZJC7PSEvc5LOZNPjHM= +github.com/hashicorp/vault-plugin-auth-jwt v0.7.0/go.mod h1:ZJJy4b0H3N7CSoJ6iPlWhV9EjHLSoB5NhP06CNm6ImU= github.com/hashicorp/vault-plugin-auth-kerberos v0.1.5 h1:knWedzZ51g8Aj6Hyi1ATlQ/7jEx6nJeqFoCoHSrbQFI= github.com/hashicorp/vault-plugin-auth-kerberos v0.1.5/go.mod h1:r4UqWITHYKmBeAMKPWqLo4V8bl/wNqoSIaQcMpeK9ss= github.com/hashicorp/vault-plugin-auth-kerberos v0.1.6 h1:l5wu8J7aiQBLsTtkKhf1QQjGoeVjcfcput+uJ/pu2MM= @@ -697,8 +698,9 @@ github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 h1:1CO5wil3HuiVLrUQ2ovSTO+6AfNOA5EMkHHVyHE9IwA= github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= +github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= +github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/README.md b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/README.md index bd786a259..efc00e4de 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/README.md +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/README.md @@ -104,7 +104,28 @@ $ vault auth enable -plugin-name='jwt' plugin Successfully enabled 'plugin' at 'jwt'! ``` -#### Tests +### Provider-specific handling + +Provider-specific handling can be added by writing an object that conforms to +one or more interfaces in [provider_config.go](provider_config.go). Some +interfaces will be required, like [CustomProvider](provider_config.go), and +others will be invoked if present during the login process (e.g. GroupsFetcher). +The interfaces themselves will be small (usually a single method) as it is +expected that the parts of the login that need specialization will be different +per provider. This pattern allows us to start with a minimal set and add +interfaces as necessary. + +If a custom provider is configured on the backend object and satisfies a given +interface, the interface will be used during the relevant part of the login +flow. e.g. after an ID token has been received, the custom provider's +UserInfoFetcher interface will be used, if present, to fetch and merge +additional identity data. + +The custom handlers will be standalone objects defined in their own file (one +per provider). They'll be part of the main jwtauth package to avoid potential +circular import issues. + +### Tests If you are developing this plugin and want to verify it is still functioning (and you haven't broken anything else), we recommend diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.mod b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.mod index 05e45fdde..ca956323a 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.mod +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/vault-plugin-auth-jwt -go 1.13 +go 1.14 require ( github.com/coreos/go-oidc v2.1.0+incompatible @@ -10,14 +10,16 @@ require ( github.com/hashicorp/go-hclog v0.12.0 github.com/hashicorp/go-sockaddr v1.0.2 github.com/hashicorp/go-uuid v1.0.2 - github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02 - github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02 + github.com/hashicorp/go-version v1.2.0 // indirect + 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/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 + 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 - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + github.com/stretchr/testify v1.3.0 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 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 diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.sum b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.sum index e8d304c7c..562126f7a 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.sum +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/go.sum @@ -62,17 +62,19 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02 h1:OGEV0U0+lb8SP5aZA1m456Sr3MYxFel2awVr55QRri0= -github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o= +github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820 h1:biZidYDDEWnuOI9mXnJre8lwHKhb5ym85aSXk3oz/dc= +github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o= github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500 h1:tiMX2ewq4ble+e2zENzBvaH2dMoFHe80NbnrF5Ir9Kk= github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= -github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02 h1:vVrOAVfunVvkTkE9iF3Fe1+PGPLwGIp3nP4qgHGrHFs= -github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820 h1:TmDZ1sS6gU0hFeFlFuyJVUwRPEzifZIHCBeS2WF2uSc= +github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -95,8 +97,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 h1:1CO5wil3HuiVLrUQ2ovSTO+6AfNOA5EMkHHVyHE9IwA= -github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= +github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= +github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -140,8 +142,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_config.go b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_config.go index f8b5d90d2..46fa669f3 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_config.go +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_config.go @@ -81,6 +81,18 @@ func pathConfig(b *jwtAuthBackend) *framework.Path { Type: framework.TypeString, Description: "The value against which to match the 'iss' claim in a JWT. Optional.", }, + "provider_config": { + Type: framework.TypeMap, + Description: "Provider-specific configuration. Optional.", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Provider Config", + Value: map[string]interface{}{ + "provider": "gsuite", + "fetch_groups": true, + "gsuite_service_account": "ey4921...", + }, + }, + }, }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -157,6 +169,7 @@ func (b *jwtAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reques "jwks_url": config.JWKSURL, "jwks_ca_pem": config.JWKSCAPEM, "bound_issuer": config.BoundIssuer, + "provider_config": config.ProviderConfig, }, } @@ -177,6 +190,7 @@ func (b *jwtAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque JWTValidationPubKeys: d.Get("jwt_validation_pubkeys").([]string), JWTSupportedAlgs: d.Get("jwt_supported_algs").([]string), BoundIssuer: d.Get("bound_issuer").(string), + ProviderConfig: d.Get("provider_config").(map[string]interface{}), } // Run checks on values @@ -239,6 +253,9 @@ func (b *jwtAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque return nil, errors.New("unknown condition") } + // NOTE: the OIDC lib states that if nothing is passed into its config, it + // defaults to "RS256". So in the case of a zero value here it won't + // 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: @@ -263,6 +280,11 @@ func (b *jwtAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Reque return logical.ErrorResponse("invalid response_mode: %q", config.OIDCResponseMode), nil } + // Validate provider_config + if _, err := NewProviderConfig(config, ProviderMap()); err != nil { + return logical.ErrorResponse("invalid provider_config: %s", err), nil + } + entry, err := logical.StorageEntryJSON(configPath, config) if err != nil { return nil, err @@ -318,18 +340,19 @@ func (b *jwtAuthBackend) createCAContext(ctx context.Context, caPEM string) (con } type jwtConfig struct { - OIDCDiscoveryURL string `json:"oidc_discovery_url"` - OIDCDiscoveryCAPEM string `json:"oidc_discovery_ca_pem"` - OIDCClientID string `json:"oidc_client_id"` - OIDCClientSecret string `json:"oidc_client_secret"` - OIDCResponseMode string `json:"oidc_response_mode"` - OIDCResponseTypes []string `json:"oidc_response_types"` - JWKSURL string `json:"jwks_url"` - JWKSCAPEM string `json:"jwks_ca_pem"` - JWTValidationPubKeys []string `json:"jwt_validation_pubkeys"` - JWTSupportedAlgs []string `json:"jwt_supported_algs"` - BoundIssuer string `json:"bound_issuer"` - DefaultRole string `json:"default_role"` + OIDCDiscoveryURL string `json:"oidc_discovery_url"` + OIDCDiscoveryCAPEM string `json:"oidc_discovery_ca_pem"` + OIDCClientID string `json:"oidc_client_id"` + OIDCClientSecret string `json:"oidc_client_secret"` + OIDCResponseMode string `json:"oidc_response_mode"` + OIDCResponseTypes []string `json:"oidc_response_types"` + JWKSURL string `json:"jwks_url"` + JWKSCAPEM string `json:"jwks_ca_pem"` + JWTValidationPubKeys []string `json:"jwt_validation_pubkeys"` + JWTSupportedAlgs []string `json:"jwt_supported_algs"` + BoundIssuer string `json:"bound_issuer"` + DefaultRole string `json:"default_role"` + ProviderConfig map[string]interface{} `json:"provider_config"` ParsedJWTPubKeys []interface{} `json:"-"` } diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_login.go b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_login.go index dd0810f86..3d1007256 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_login.go +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_login.go @@ -334,10 +334,9 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role * return alias, groupAliases, nil } - groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim) - - if groupsClaimRaw == nil { - return nil, nil, fmt.Errorf("%q claim not found in token", role.GroupsClaim) + groupsClaimRaw, err := b.fetchGroups(allClaims, role) + if err != nil { + return nil, nil, fmt.Errorf("failed to fetch groups: %s", err) } groups, ok := normalizeList(groupsClaimRaw) @@ -361,6 +360,28 @@ func (b *jwtAuthBackend) createIdentity(allClaims map[string]interface{}, role * return alias, groupAliases, 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()) + if err != nil { + return nil, fmt.Errorf("failed to load custom provider config: %s", err) + } + // If the custom provider implements interface GroupsFetcher, call it, + // otherwise fall through to the default method + if pConfig != nil { + if gf, ok := pConfig.(GroupsFetcher); ok { + return gf.FetchGroups(b, allClaims, role) + } + } + groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim) + + if groupsClaimRaw == nil { + return nil, fmt.Errorf("%q claim not found in token", role.GroupsClaim) + } + + return groupsClaimRaw, nil +} + const ( pathLoginHelpSyn = ` Authenticates to Vault using a JWT (or OIDC) token. diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_oidc.go b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_oidc.go index 6c5a9ee26..363456008 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_oidc.go +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/path_oidc.go @@ -28,6 +28,7 @@ const ( errLoginFailed = "Vault login failed." errNoResponse = "No response from provider." errTokenVerification = "Token verification failed." + errNotOIDCFlow = "OIDC login is not configured for this mount" noCode = "no_code" ) @@ -344,7 +345,7 @@ func (b *jwtAuthBackend) authURL(ctx context.Context, req *logical.Request, d *f } if config.authType() != OIDCFlow { - return logical.ErrorResponse("OIDC login is not configured for this mount"), nil + return logical.ErrorResponse(errNotOIDCFlow), nil } roleName := d.Get("role").(string) diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_azure.go b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_azure.go new file mode 100644 index 000000000..f4fe00dfa --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_azure.go @@ -0,0 +1,188 @@ +package jwtauth + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/coreos/go-oidc" + log "github.com/hashicorp/go-hclog" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" +) + +const ( + // The old MS graph API requires setting an api-version query parameter + windowsGraphHost = "graph.windows.net" + windowsAPIVersion = "1.6" + + // Distributed claim fields + claimNamesField = "_claim_names" + claimSourcesField = "_claim_sources" +) + +// AzureProvider is used for Azure-specific configuration +type AzureProvider struct { + // Context for azure calls + ctx context.Context + + // OIDC provider + provider *oidc.Provider +} + +// Initialize anything in the AzureProvider struct - satisfying the CustomProvider interface +func (a *AzureProvider) Initialize(jc *jwtConfig) error { + return nil +} + +// SensitiveKeys - satisfying the CustomProvider interface +func (a *AzureProvider) SensitiveKeys() []string { + return []string{} +} + +// FetchGroups - custom groups fetching for azure - satisfying GroupsFetcher interface +func (a *AzureProvider) FetchGroups(b *jwtAuthBackend, allClaims map[string]interface{}, role *jwtRole) (interface{}, error) { + groupsClaimRaw := getClaim(b.Logger(), allClaims, role.GroupsClaim) + + if groupsClaimRaw == nil { + // If the "groups" claim is missing, it might be because the user is a + // member of more than 200 groups, which means the token contains + // distributed claim information. Attempt to look that up here. + azureClaimSourcesURL, err := a.getClaimSource(b.Logger(), allClaims, role) + if err != nil { + return nil, fmt.Errorf("unable to get claim sources: %s", err) + } + + // Get provider because we'll need to get a new token for microsoft's + // graph API, specifically the old graph API + provider, err := b.getProvider(b.cachedConfig) + if err != nil { + return nil, fmt.Errorf("unable to get provider: %s", err) + } + a.provider = provider + + a.ctx, err = b.createCAContext(b.providerCtx, b.cachedConfig.OIDCDiscoveryCAPEM) + if err != nil { + return nil, fmt.Errorf("unable to create CA Context: %s", err) + } + + azureGroups, err := a.getAzureGroups(azureClaimSourcesURL, b.cachedConfig) + if err != nil { + return nil, fmt.Errorf("%q claim not found in token: %v", role.GroupsClaim, err) + } + groupsClaimRaw = azureGroups + } + b.Logger().Debug(fmt.Sprintf("groups claim raw is %v", groupsClaimRaw)) + return groupsClaimRaw, nil +} + +// In Azure, if you are indirectly member of more than 200 groups, they will +// send _claim_names and _claim_sources instead of the groups, per OIDC Core +// 1.0, section 5.6.2: +// https://openid.net/specs/openid-connect-core-1_0.html#AggregatedDistributedClaims +// In the future this could be used with other providers as well. Example: +// +// { +// "_claim_names": { +// "groups": "src1" +// }, +// "_claim_sources": { +// "src1": { +// "endpoint": "https://graph.windows.net...." +// } +// } +// } +// +// For this to work, "profile" should be set in "oidc_scopes" in the vault oidc role. +// +func (a *AzureProvider) getClaimSource(logger log.Logger, allClaims map[string]interface{}, role *jwtRole) (string, error) { + // Get the source key for the groups claim + name := fmt.Sprintf("/%s/%s", claimNamesField, role.GroupsClaim) + groupsClaimSource := getClaim(logger, allClaims, name) + if groupsClaimSource == nil { + return "", fmt.Errorf("unable to locate groups claim %q in %s", role.GroupsClaim, claimNamesField) + } + // Get the endpoint source for the groups claim + endpoint := fmt.Sprintf("/%s/%s/endpoint", claimSourcesField, groupsClaimSource.(string)) + val := getClaim(logger, allClaims, endpoint) + if val == nil { + return "", fmt.Errorf("unable to locate %s in claims", endpoint) + } + logger.Debug(fmt.Sprintf("found Azure Graph API endpoint for group membership: %v", val)) + return fmt.Sprintf("%v", val), nil +} + +// Fetch user groups from the Azure AD Graph API +func (a *AzureProvider) getAzureGroups(groupsURL string, c *jwtConfig) (interface{}, error) { + urlParsed, err := url.Parse(groupsURL) + if err != nil { + return nil, fmt.Errorf("failed to parse distributed groups source url %s: %s", groupsURL, err) + } + token, err := a.getAzureToken(c, urlParsed.Host) + if err != nil { + return nil, fmt.Errorf("unable to get token: %s", err) + } + payload := strings.NewReader("{\"securityEnabledOnly\": false}") + req, err := http.NewRequest("POST", groupsURL, payload) + if err != nil { + return nil, fmt.Errorf("error constructing groups endpoint request: %s", err) + } + req.Header.Add("content-type", "application/json") + req.Header.Add("authorization", fmt.Sprintf("Bearer %s", token)) + + // If endpoint is the old windows graph api, add api-version + if urlParsed.Host == windowsGraphHost { + query := req.URL.Query() + query.Add("api-version", windowsAPIVersion) + req.URL.RawQuery = query.Encode() + } + client := http.DefaultClient + if c, ok := a.ctx.Value(oauth2.HTTPClient).(*http.Client); ok { + client = c + } + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("unable to call Azure AD Graph API: %s", err) + } + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("failed to read Azure AD Graph API response: %s", err) + } + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get groups: %s", string(body)) + } + + var target azureGroups + if err := json.Unmarshal(body, &target); err != nil { + return nil, fmt.Errorf("unabled to decode response: %s", err) + } + return target.Value, nil +} + +// Login to Azure, using client id and secret. +func (a *AzureProvider) getAzureToken(c *jwtConfig, host string) (string, error) { + config := &clientcredentials.Config{ + ClientID: c.OIDCClientID, + ClientSecret: c.OIDCClientSecret, + TokenURL: a.provider.Endpoint().TokenURL, + Scopes: []string{ + "openid", + "profile", + "https://" + host + "/.default", + }, + } + token, err := config.Token(a.ctx) + if err != nil { + return "", fmt.Errorf("failed to fetch Azure token: %s", err) + } + return token.AccessToken, nil +} + +type azureGroups struct { + Value []interface{} `json:"value"` +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_config.go b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_config.go new file mode 100644 index 000000000..97c36aa8c --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-jwt/provider_config.go @@ -0,0 +1,55 @@ +package jwtauth + +import ( + "fmt" +) + +// Provider-specific configuration interfaces +// All providers must implement the CustomProvider interface, and may implement +// others as needed. + +// ProviderMap returns a map of provider names to custom types +func ProviderMap() map[string]CustomProvider { + return map[string]CustomProvider{ + "azure": &AzureProvider{}, + } +} + +// CustomProvider - Any custom provider must implement this interface +type CustomProvider interface { + // Initialize should validate jwtConfig.ProviderConfig, set internal values + // and run any initialization necessary for subsequent calls to interface + // functions the provider implements + Initialize(*jwtConfig) error + + // SensitiveKeys returns any fields in a provider's jwtConfig.ProviderConfig + // that should be masked or omitted when output + SensitiveKeys() []string +} + +// NewProviderConfig - returns appropriate provider struct if provider_config is +// specified in jwtConfig. The provider map is provider name -to- instance of a +// CustomProvider. +func NewProviderConfig(jc *jwtConfig, providerMap map[string]CustomProvider) (CustomProvider, error) { + if len(jc.ProviderConfig) == 0 { + return nil, nil + } + provider, ok := jc.ProviderConfig["provider"].(string) + if !ok { + return nil, fmt.Errorf("'provider' field not found in provider_config") + } + newCustomProvider, ok := providerMap[provider] + if !ok { + return nil, fmt.Errorf("provider %q not found in custom providers", provider) + } + if err := newCustomProvider.Initialize(jc); err != nil { + return nil, fmt.Errorf("error initializing %q provider_config: %s", provider, err) + } + return newCustomProvider, nil +} + +// GroupsFetcher - Optional support for custom groups handling +type GroupsFetcher interface { + // FetchGroups queries for groups claims during login + FetchGroups(*jwtAuthBackend, map[string]interface{}, *jwtRole) (interface{}, error) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e4c171997..fe4ac1487 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -461,7 +461,7 @@ github.com/hashicorp/vault-plugin-auth-cf/util # github.com/hashicorp/vault-plugin-auth-gcp v0.6.1 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.6.2 +# github.com/hashicorp/vault-plugin-auth-jwt v0.7.0 github.com/hashicorp/vault-plugin-auth-jwt # github.com/hashicorp/vault-plugin-auth-kerberos v0.1.6 github.com/hashicorp/vault-plugin-auth-kerberos @@ -678,7 +678,7 @@ github.com/mitchellh/hashstructure github.com/mitchellh/iochan # github.com/mitchellh/mapstructure v1.3.2 github.com/mitchellh/mapstructure -# github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 +# github.com/mitchellh/pointerstructure v1.0.0 github.com/mitchellh/pointerstructure # github.com/mitchellh/reflectwalk v1.0.1 github.com/mitchellh/reflectwalk