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:
6
go.mod
6
go.mod
@@ -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
11
go.sum
@@ -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=
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user