fix: add support for userInfo endpoint (#1048)

With support for GitLab OpenID service,
we need to make sure to support userInfo
endpoint for this MinIO server requires that
access_token is additionally sent along with
id_token as before to make sure we can fetch
additional claims from /userinfo endpoint.

This PR brings support on console for this
feature.

Refer https://github.com/minio/minio/pull/12469
This commit is contained in:
Harshavardhana
2021-09-14 16:16:29 -07:00
committed by GitHub
parent 77eff6ce91
commit b6f818bdd3
7 changed files with 76 additions and 64 deletions

6
go.mod
View File

@@ -19,12 +19,12 @@ require (
github.com/minio/cli v1.22.0 github.com/minio/cli v1.22.0
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf
github.com/minio/kes v0.11.0 github.com/minio/kes v0.11.0
github.com/minio/madmin-go v1.0.17 github.com/minio/madmin-go v1.1.5
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f github.com/minio/mc v0.0.0-20210626002108-cebf3318546f
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e github.com/minio/minio-go/v7 v7.0.14
github.com/minio/operator v0.0.0-20210812082324-26350f153661 github.com/minio/operator v0.0.0-20210812082324-26350f153661
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661 github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661
github.com/minio/pkg v1.0.8 github.com/minio/pkg v1.1.3
github.com/minio/selfupdate v0.3.1 github.com/minio/selfupdate v0.3.1
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect

11
go.sum
View File

@@ -877,8 +877,8 @@ github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEX
github.com/minio/kes v0.11.0 h1:8ma6OCVSxKT50b1uYXLJro3m7PmZtCLxBaTddQexI5k= github.com/minio/kes v0.11.0 h1:8ma6OCVSxKT50b1uYXLJro3m7PmZtCLxBaTddQexI5k=
github.com/minio/kes v0.11.0/go.mod h1:mTF1Bv8YVEtQqF/B7Felp4tLee44Pp+dgI0rhCvgNg8= github.com/minio/kes v0.11.0/go.mod h1:mTF1Bv8YVEtQqF/B7Felp4tLee44Pp+dgI0rhCvgNg8=
github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs= github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
github.com/minio/madmin-go v1.0.17 h1:VMEn4nMKf0X3uNH0u+fZcn17KSwVkQGwyER/igG556E= github.com/minio/madmin-go v1.1.5 h1:xfzHwQ/KeKDQZKLqllNSyexwOPM/tvc13UdCeVMzADY=
github.com/minio/madmin-go v1.0.17/go.mod h1:4nl9hvLWFnwCjkLfZSsZXEHgDODa2XSG6xGlIZyQ2oA= github.com/minio/madmin-go v1.1.5/go.mod h1:xIPJHUbyYhNDgeD9Wov5Fz5/p7DIW0u+q6Rs/+Xu2TM=
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f h1:hyFvo5hSFw2K417YvDr/vAKlgCG69uTuhZW/5LNdL0U= github.com/minio/mc v0.0.0-20210626002108-cebf3318546f h1:hyFvo5hSFw2K417YvDr/vAKlgCG69uTuhZW/5LNdL0U=
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f/go.mod h1:tuaonkPjVApCXkbtKENHBtsqUf7YTV33qmFrC+Pgp5g= github.com/minio/mc v0.0.0-20210626002108-cebf3318546f/go.mod h1:tuaonkPjVApCXkbtKENHBtsqUf7YTV33qmFrC+Pgp5g=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
@@ -886,16 +886,17 @@ github.com/minio/md5-simd v1.1.1 h1:9ojcLbuZ4gXbB2sX53MKn8JUZ0sB/2wfwsEcRw+I08U=
github.com/minio/md5-simd v1.1.1/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/md5-simd v1.1.1/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw= github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
github.com/minio/minio-go/v7 v7.0.11-0.20210607181445-e162fdb8e584/go.mod h1:WoyW+ySKAKjY98B9+7ZbI8z8S3jaxaisdcvj9TGlazA= github.com/minio/minio-go/v7 v7.0.11-0.20210607181445-e162fdb8e584/go.mod h1:WoyW+ySKAKjY98B9+7ZbI8z8S3jaxaisdcvj9TGlazA=
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e h1:aVnxKPpUI1gVeEf9vC+QEt8OxMXiiNMeUWcrBM62oDU= github.com/minio/minio-go/v7 v7.0.14 h1:T7cw8P586gVwEEd0y21kTYtloD576XZgP62N8pE130s=
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs= github.com/minio/minio-go/v7 v7.0.14/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
github.com/minio/operator v0.0.0-20210812082324-26350f153661 h1:dGAJHpfmhNukFg0M0wDqH+G1OB2YPgZCcT6uv4n9YQk= github.com/minio/operator v0.0.0-20210812082324-26350f153661 h1:dGAJHpfmhNukFg0M0wDqH+G1OB2YPgZCcT6uv4n9YQk=
github.com/minio/operator v0.0.0-20210812082324-26350f153661/go.mod h1:zQqn6VGT46xlSpVXh1I/VZRv+eSgHtVu6URdg71YKX8= github.com/minio/operator v0.0.0-20210812082324-26350f153661/go.mod h1:zQqn6VGT46xlSpVXh1I/VZRv+eSgHtVu6URdg71YKX8=
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661 h1:tJw15hS3b1dVTf5PwA4roXZ/oRNnHyZ/8Y+yNTmQ5rA= github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661 h1:tJw15hS3b1dVTf5PwA4roXZ/oRNnHyZ/8Y+yNTmQ5rA=
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661/go.mod h1:R+38Pf3wfm+JMiyLPb/r8OMrBm0vK2hZgUT4y4aYoSY= github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661/go.mod h1:R+38Pf3wfm+JMiyLPb/r8OMrBm0vK2hZgUT4y4aYoSY=
github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8= github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8= github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
github.com/minio/pkg v1.0.8 h1:lWQwHSeYlvnRoPpO+wS0I4mL6c00ABxBgbGjSmjwOi4=
github.com/minio/pkg v1.0.8/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14= github.com/minio/pkg v1.0.8/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
github.com/minio/pkg v1.1.3 h1:J4vGnlNSxc/o9gDOQMZ3k0L3koA7ZgBQ7GRMrUpt/OY=
github.com/minio/pkg v1.1.3/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs= github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM= github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=

View File

@@ -185,7 +185,7 @@ func getLoginDetailsResponse() (*models.LoginDetails, *models.Error) {
loginStrategy := models.LoginDetailsLoginStrategyServiceDashAccount loginStrategy := models.LoginDetailsLoginStrategyServiceDashAccount
redirectURL := "" redirectURL := ""
if oauth2.IsIdpEnabled() { if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client // initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, restapi.GetConsoleHTTPClient()) oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, restapi.GetConsoleHTTPClient())

View File

@@ -29,49 +29,53 @@ func GetSTSEndpoint() string {
return strings.TrimSpace(env.Get(ConsoleMinIOServer, "http://localhost:9000")) return strings.TrimSpace(env.Get(ConsoleMinIOServer, "http://localhost:9000"))
} }
func GetIdpURL() string { func GetIDPURL() string {
return env.Get(ConsoleIdpURL, "") return env.Get(ConsoleIDPURL, "")
} }
func GetIdpClientID() string { func GetIDPClientID() string {
return env.Get(ConsoleIdpClientID, "") return env.Get(ConsoleIDPClientID, "")
} }
func GetIdpSecret() string { func GetIDPUserInfo() bool {
return env.Get(ConsoleIdpSecret, "") return env.Get(ConsoleIDPUserInfo, "") == "on"
}
func GetIDPSecret() string {
return env.Get(ConsoleIDPSecret, "")
} }
// Public endpoint used by the identity oidcProvider when redirecting the user after identity verification // Public endpoint used by the identity oidcProvider when redirecting the user after identity verification
func GetIdpCallbackURL() string { func GetIDPCallbackURL() string {
return env.Get(ConsoleIdpCallbackURL, "") return env.Get(ConsoleIDPCallbackURL, "")
} }
func IsIdpEnabled() bool { func IsIDPEnabled() bool {
return GetIdpURL() != "" && return GetIDPURL() != "" &&
GetIdpClientID() != "" && GetIDPClientID() != "" &&
GetIdpCallbackURL() != "" GetIDPCallbackURL() != ""
} }
var defaultPassphraseForIdpHmac = utils.RandomCharString(64) var defaultPassphraseForIDPHmac = utils.RandomCharString(64)
// GetPassphraseForIdpHmac returns passphrase for the pbkdf2 function used to sign the oauth2 state parameter // GetPassphraseForIDPHmac returns passphrase for the pbkdf2 function used to sign the oauth2 state parameter
func getPassphraseForIdpHmac() string { func getPassphraseForIDPHmac() string {
return env.Get(ConsoleIdpHmacPassphrase, defaultPassphraseForIdpHmac) return env.Get(ConsoleIDPHmacPassphrase, defaultPassphraseForIDPHmac)
} }
var defaultSaltForIdpHmac = utils.RandomCharString(64) var defaultSaltForIDPHmac = utils.RandomCharString(64)
// GetSaltForIdpHmac returns salt for the pbkdf2 function used to sign the oauth2 state parameter // GetSaltForIDPHmac returns salt for the pbkdf2 function used to sign the oauth2 state parameter
func getSaltForIdpHmac() string { func getSaltForIDPHmac() string {
return env.Get(ConsoleIdpHmacSalt, defaultSaltForIdpHmac) return env.Get(ConsoleIDPHmacSalt, defaultSaltForIDPHmac)
} }
// getIdpScopes return default scopes during the IDP login request // getIDPScopes return default scopes during the IDP login request
func getIdpScopes() string { func getIDPScopes() string {
return env.Get(ConsoleIDPScopes, "openid,profile,email") return env.Get(ConsoleIDPScopes, "openid,profile,email")
} }
// getIdpTokenExpiration return default token expiration for access token (in seconds) // getIDPTokenExpiration return default token expiration for access token (in seconds)
func getIdpTokenExpiration() string { func getIDPTokenExpiration() string {
return env.Get(ConsoleIDPTokenExpiration, "3600") return env.Get(ConsoleIDPTokenExpiration, "3600")
} }

View File

@@ -16,15 +16,16 @@
package oauth2 package oauth2
// Environment constants for console IDP/SSO configuration
const ( const (
// const for idp configuration
ConsoleMinIOServer = "CONSOLE_MINIO_SERVER" ConsoleMinIOServer = "CONSOLE_MINIO_SERVER"
ConsoleIdpURL = "CONSOLE_IDP_URL" ConsoleIDPURL = "CONSOLE_IDP_URL"
ConsoleIdpClientID = "CONSOLE_IDP_CLIENT_ID" ConsoleIDPClientID = "CONSOLE_IDP_CLIENT_ID"
ConsoleIdpSecret = "CONSOLE_IDP_SECRET" ConsoleIDPSecret = "CONSOLE_IDP_SECRET"
ConsoleIdpCallbackURL = "CONSOLE_IDP_CALLBACK" ConsoleIDPCallbackURL = "CONSOLE_IDP_CALLBACK"
ConsoleIdpHmacPassphrase = "CONSOLE_IDP_HMAC_PASSPHRASE" ConsoleIDPHmacPassphrase = "CONSOLE_IDP_HMAC_PASSPHRASE"
ConsoleIdpHmacSalt = "CONSOLE_IDP_HMAC_SALT" ConsoleIDPHmacSalt = "CONSOLE_IDP_HMAC_SALT"
ConsoleIDPScopes = "CONSOLE_IDP_SCOPES" ConsoleIDPScopes = "CONSOLE_IDP_SCOPES"
ConsoleIDPUserInfo = "CONSOLE_IDP_USERINFO"
ConsoleIDPTokenExpiration = "CONSOLE_IDP_TOKEN_EXPIRATION" ConsoleIDPTokenExpiration = "CONSOLE_IDP_TOKEN_EXPIRATION"
) )

View File

@@ -88,7 +88,9 @@ type Provider struct {
// often available via site-specific packages, such as // often available via site-specific packages, such as
// google.Endpoint or github.Endpoint. // google.Endpoint or github.Endpoint.
// - Scopes specifies optional requested permissions. // - Scopes specifies optional requested permissions.
ClientID string ClientID string
// if enabled means that we need extrace access_token as well
UserInfo bool
oauth2Config Configuration oauth2Config Configuration
oidcProvider *oidc.Provider oidcProvider *oidc.Provider
provHTTPClient *http.Client provHTTPClient *http.Client
@@ -97,7 +99,7 @@ type Provider struct {
// derivedKey is the key used to compute the HMAC for signing the oauth state parameter // derivedKey is the key used to compute the HMAC for signing the oauth state parameter
// its derived using pbkdf on CONSOLE_IDP_HMAC_PASSPHRASE with CONSOLE_IDP_HMAC_SALT // its derived using pbkdf on CONSOLE_IDP_HMAC_PASSPHRASE with CONSOLE_IDP_HMAC_SALT
var derivedKey = func() []byte { var derivedKey = func() []byte {
return pbkdf2.Key([]byte(getPassphraseForIdpHmac()), []byte(getSaltForIdpHmac()), 4096, 32, sha1.New) return pbkdf2.Key([]byte(getPassphraseForIDPHmac()), []byte(getSaltForIDPHmac()), 4096, 32, sha1.New)
} }
// NewOauth2ProviderClient instantiates a new oauth2 client using the configured credentials // NewOauth2ProviderClient instantiates a new oauth2 client using the configured credentials
@@ -105,34 +107,30 @@ var derivedKey = func() []byte {
// oauth2 authentication flow // oauth2 authentication flow
func NewOauth2ProviderClient(ctx context.Context, scopes []string, httpClient *http.Client) (*Provider, error) { func NewOauth2ProviderClient(ctx context.Context, scopes []string, httpClient *http.Client) (*Provider, error) {
customCtx := oidc.ClientContext(ctx, httpClient) customCtx := oidc.ClientContext(ctx, httpClient)
provider, err := oidc.NewProvider(customCtx, GetIdpURL()) provider, err := oidc.NewProvider(customCtx, GetIDPURL())
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if google, change scopes
u, err := url.Parse(GetIdpURL())
if err != nil {
return nil, err
}
// below verification should not be necessary if the user configure exactly the
// scopes he need, will be removed on a future release
if u.Host == "google.com" {
scopes = []string{oidc.ScopeOpenID}
}
// If provided scopes are empty we use a default list or the user configured list // If provided scopes are empty we use a default list or the user configured list
if len(scopes) == 0 { if len(scopes) == 0 {
scopes = strings.Split(getIdpScopes(), ",") scopes = strings.Split(getIDPScopes(), ",")
} }
// add "openid" scope always.
scopes = append(scopes, oidc.ScopeOpenID)
client := new(Provider) client := new(Provider)
client.oauth2Config = &xoauth2.Config{ client.oauth2Config = &xoauth2.Config{
ClientID: GetIdpClientID(), ClientID: GetIDPClientID(),
ClientSecret: GetIdpSecret(), ClientSecret: GetIDPSecret(),
RedirectURL: GetIdpCallbackURL(), RedirectURL: GetIDPCallbackURL(),
Endpoint: provider.Endpoint(), Endpoint: provider.Endpoint(),
Scopes: scopes, Scopes: scopes,
} }
client.oidcProvider = provider client.oidcProvider = provider
client.ClientID = GetIdpClientID() client.ClientID = GetIDPClientID()
client.UserInfo = GetIDPUserInfo()
client.provHTTPClient = httpClient client.provHTTPClient = httpClient
return client, nil return client, nil
@@ -184,18 +182,26 @@ func (client *Provider) VerifyIdentity(ctx context.Context, code, state string)
// check if user configured a hardcoded expiration for console via env variables // check if user configured a hardcoded expiration for console via env variables
// and override the incoming expiration // and override the incoming expiration
userConfiguredExpiration := getIdpTokenExpiration() userConfiguredExpiration := getIDPTokenExpiration()
if userConfiguredExpiration != "" { if userConfiguredExpiration != "" {
expiration, _ = strconv.Atoi(userConfiguredExpiration) expiration, _ = strconv.Atoi(userConfiguredExpiration)
} }
idToken := oauth2Token.Extra("id_token") idToken := oauth2Token.Extra("id_token")
if idToken == nil { if idToken == nil {
return nil, errors.New("returned token is missing id_token claim") return nil, errors.New("missing id_token")
} }
return &credentials.WebIdentityToken{ token := &credentials.WebIdentityToken{
Token: idToken.(string), Token: idToken.(string),
Expiry: expiration, Expiry: expiration,
}, nil }
if client.UserInfo { // look for access_token only if userinfo is requested.
accessToken := oauth2Token.Extra("access_token")
if accessToken == nil {
return nil, errors.New("missing access_token")
}
token.AccessToken = accessToken.(string)
}
return token, nil
} }
stsEndpoint := GetSTSEndpoint() stsEndpoint := GetSTSEndpoint()
sts := credentials.New(&credentials.STSWebIdentity{ sts := credentials.New(&credentials.STSWebIdentity{

View File

@@ -183,7 +183,7 @@ func getLoginDetailsResponse() (*models.LoginDetails, *models.Error) {
loginStrategy := models.LoginDetailsLoginStrategyForm loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := "" redirectURL := ""
if oauth2.IsIdpEnabled() { if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client // initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, GetConsoleHTTPClient()) oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, GetConsoleHTTPClient())
@@ -215,7 +215,7 @@ func verifyUserAgainstIDP(ctx context.Context, provider auth.IdentityProviderI,
func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, *models.Error) { func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel() defer cancel()
if oauth2.IsIdpEnabled() { if oauth2.IsIDPEnabled() {
// initialize new oauth2 client // initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, GetConsoleHTTPClient()) oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, GetConsoleHTTPClient())
if err != nil { if err != nil {