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/direct-csi v1.3.5-0.20210601185811-f7776f7961bf
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/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/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/mitchellh/go-homedir v1.1.0
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/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.17 h1:VMEn4nMKf0X3uNH0u+fZcn17KSwVkQGwyER/igG556E=
github.com/minio/madmin-go v1.0.17/go.mod h1:4nl9hvLWFnwCjkLfZSsZXEHgDODa2XSG6xGlIZyQ2oA=
github.com/minio/madmin-go v1.1.5 h1:xfzHwQ/KeKDQZKLqllNSyexwOPM/tvc13UdCeVMzADY=
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/go.mod h1:tuaonkPjVApCXkbtKENHBtsqUf7YTV33qmFrC+Pgp5g=
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/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.13-0.20210715203016-9e713532886e h1:aVnxKPpUI1gVeEf9vC+QEt8OxMXiiNMeUWcrBM62oDU=
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 h1:T7cw8P586gVwEEd0y21kTYtloD576XZgP62N8pE130s=
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/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/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.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.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/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
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
redirectURL := ""
if oauth2.IsIdpEnabled() {
if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client
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"))
}
func GetIdpURL() string {
return env.Get(ConsoleIdpURL, "")
func GetIDPURL() string {
return env.Get(ConsoleIDPURL, "")
}
func GetIdpClientID() string {
return env.Get(ConsoleIdpClientID, "")
func GetIDPClientID() string {
return env.Get(ConsoleIDPClientID, "")
}
func GetIdpSecret() string {
return env.Get(ConsoleIdpSecret, "")
func GetIDPUserInfo() bool {
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
func GetIdpCallbackURL() string {
return env.Get(ConsoleIdpCallbackURL, "")
func GetIDPCallbackURL() string {
return env.Get(ConsoleIDPCallbackURL, "")
}
func IsIdpEnabled() bool {
return GetIdpURL() != "" &&
GetIdpClientID() != "" &&
GetIdpCallbackURL() != ""
func IsIDPEnabled() bool {
return GetIDPURL() != "" &&
GetIDPClientID() != "" &&
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
func getPassphraseForIdpHmac() string {
return env.Get(ConsoleIdpHmacPassphrase, defaultPassphraseForIdpHmac)
// GetPassphraseForIDPHmac returns passphrase for the pbkdf2 function used to sign the oauth2 state parameter
func getPassphraseForIDPHmac() string {
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
func getSaltForIdpHmac() string {
return env.Get(ConsoleIdpHmacSalt, defaultSaltForIdpHmac)
// GetSaltForIDPHmac returns salt for the pbkdf2 function used to sign the oauth2 state parameter
func getSaltForIDPHmac() string {
return env.Get(ConsoleIDPHmacSalt, defaultSaltForIDPHmac)
}
// getIdpScopes return default scopes during the IDP login request
func getIdpScopes() string {
// getIDPScopes return default scopes during the IDP login request
func getIDPScopes() string {
return env.Get(ConsoleIDPScopes, "openid,profile,email")
}
// getIdpTokenExpiration return default token expiration for access token (in seconds)
func getIdpTokenExpiration() string {
// getIDPTokenExpiration return default token expiration for access token (in seconds)
func getIDPTokenExpiration() string {
return env.Get(ConsoleIDPTokenExpiration, "3600")
}

View File

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

View File

@@ -88,7 +88,9 @@ type Provider struct {
// often available via site-specific packages, such as
// google.Endpoint or github.Endpoint.
// - Scopes specifies optional requested permissions.
ClientID string
ClientID string
// if enabled means that we need extrace access_token as well
UserInfo bool
oauth2Config Configuration
oidcProvider *oidc.Provider
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
// its derived using pbkdf on CONSOLE_IDP_HMAC_PASSPHRASE with CONSOLE_IDP_HMAC_SALT
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
@@ -105,34 +107,30 @@ var derivedKey = func() []byte {
// oauth2 authentication flow
func NewOauth2ProviderClient(ctx context.Context, scopes []string, httpClient *http.Client) (*Provider, error) {
customCtx := oidc.ClientContext(ctx, httpClient)
provider, err := oidc.NewProvider(customCtx, GetIdpURL())
provider, err := oidc.NewProvider(customCtx, GetIDPURL())
if err != nil {
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 len(scopes) == 0 {
scopes = strings.Split(getIdpScopes(), ",")
scopes = strings.Split(getIDPScopes(), ",")
}
// add "openid" scope always.
scopes = append(scopes, oidc.ScopeOpenID)
client := new(Provider)
client.oauth2Config = &xoauth2.Config{
ClientID: GetIdpClientID(),
ClientSecret: GetIdpSecret(),
RedirectURL: GetIdpCallbackURL(),
ClientID: GetIDPClientID(),
ClientSecret: GetIDPSecret(),
RedirectURL: GetIDPCallbackURL(),
Endpoint: provider.Endpoint(),
Scopes: scopes,
}
client.oidcProvider = provider
client.ClientID = GetIdpClientID()
client.ClientID = GetIDPClientID()
client.UserInfo = GetIDPUserInfo()
client.provHTTPClient = httpClient
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
// and override the incoming expiration
userConfiguredExpiration := getIdpTokenExpiration()
userConfiguredExpiration := getIDPTokenExpiration()
if userConfiguredExpiration != "" {
expiration, _ = strconv.Atoi(userConfiguredExpiration)
}
idToken := oauth2Token.Extra("id_token")
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),
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()
sts := credentials.New(&credentials.STSWebIdentity{

View File

@@ -183,7 +183,7 @@ func getLoginDetailsResponse() (*models.LoginDetails, *models.Error) {
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := ""
if oauth2.IsIdpEnabled() {
if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client
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) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
if oauth2.IsIdpEnabled() {
if oauth2.IsIDPEnabled() {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil, GetConsoleHTTPClient())
if err != nil {