mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-27 13:02:59 +02:00
Remove go routine to reloadSetting (#18318)
Config is cached in redis Fixes #18189 Signed-off-by: stonezdj <daojunz@vmware.com>
This commit is contained in:
parent
4efee54f07
commit
ab636fe3da
@ -58,7 +58,7 @@ func (oc *OIDCController) Prepare() {
|
|||||||
// RedirectLogin redirect user's browser to OIDC provider's login page
|
// RedirectLogin redirect user's browser to OIDC provider's login page
|
||||||
func (oc *OIDCController) RedirectLogin() {
|
func (oc *OIDCController) RedirectLogin() {
|
||||||
state := utils.GenerateRandomString()
|
state := utils.GenerateRandomString()
|
||||||
url, err := oidc.AuthCodeURL(state)
|
url, err := oidc.AuthCodeURL(oc.Context(), state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oc.SendInternalServerError(err)
|
oc.SendInternalServerError(err)
|
||||||
return
|
return
|
||||||
|
@ -17,7 +17,6 @@ package oidc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -50,14 +49,13 @@ type claimsProvider interface {
|
|||||||
type providerHelper struct {
|
type providerHelper struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
instance atomic.Value
|
instance atomic.Value
|
||||||
setting atomic.Value
|
|
||||||
creationTime time.Time
|
creationTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *providerHelper) get() (*gooidc.Provider, error) {
|
func (p *providerHelper) get(ctx context.Context) (*gooidc.Provider, error) {
|
||||||
if p.instance.Load() != nil {
|
if p.instance.Load() != nil {
|
||||||
if time.Since(p.creationTime) > 3*time.Second {
|
if time.Since(p.creationTime) > 3*time.Second {
|
||||||
if err := p.create(); err != nil {
|
if err := p.create(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,42 +63,23 @@ func (p *providerHelper) get() (*gooidc.Provider, error) {
|
|||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
if p.instance.Load() == nil {
|
if p.instance.Load() == nil {
|
||||||
if err := p.reloadSetting(); err != nil {
|
if err := p.create(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := p.create(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if err := p.reloadSetting(); err != nil {
|
|
||||||
log.Warningf("Failed to refresh configuration, error: %v", err)
|
|
||||||
}
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.instance.Load().(*gooidc.Provider), nil
|
return p.instance.Load().(*gooidc.Provider), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *providerHelper) reloadSetting() error {
|
func (p *providerHelper) create(ctx context.Context) error {
|
||||||
conf, err := config.OIDCSetting(orm.Context())
|
s, err := config.OIDCSetting(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load OIDC setting: %v", err)
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
p.setting.Store(*conf)
|
c := clientCtx(ctx, s.VerifyCert)
|
||||||
return nil
|
provider, err := gooidc.NewProvider(c, s.Endpoint)
|
||||||
}
|
|
||||||
|
|
||||||
func (p *providerHelper) create() error {
|
|
||||||
if p.setting.Load() == nil {
|
|
||||||
return errors.New("the configuration is not loaded")
|
|
||||||
}
|
|
||||||
s := p.setting.Load().(cfgModels.OIDCSetting)
|
|
||||||
ctx := clientCtx(context.Background(), s.VerifyCert)
|
|
||||||
provider, err := gooidc.NewProvider(ctx, s.Endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create OIDC provider, error: %v", err)
|
return fmt.Errorf("failed to create OIDC provider, error: %v", err)
|
||||||
}
|
}
|
||||||
@ -137,12 +116,16 @@ type UserInfo struct {
|
|||||||
hasGroupClaim bool
|
hasGroupClaim bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOauthConf() (*oauth2.Config, error) {
|
func getOauthConf(ctx context.Context) (*oauth2.Config, error) {
|
||||||
p, err := provider.get()
|
p, err := provider.get(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
setting := provider.setting.Load().(cfgModels.OIDCSetting)
|
setting, err := config.OIDCSetting(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
scopes := make([]string, 0)
|
scopes := make([]string, 0)
|
||||||
for _, sc := range setting.Scope {
|
for _, sc := range setting.Scope {
|
||||||
if strings.HasPrefix(p.Endpoint().AuthURL, googleEndpoint) && sc == gooidc.ScopeOfflineAccess {
|
if strings.HasPrefix(p.Endpoint().AuthURL, googleEndpoint) && sc == gooidc.ScopeOfflineAccess {
|
||||||
@ -162,14 +145,18 @@ func getOauthConf() (*oauth2.Config, error) {
|
|||||||
|
|
||||||
// AuthCodeURL returns the URL for OIDC provider's consent page. The state should be verified when user is redirected
|
// AuthCodeURL returns the URL for OIDC provider's consent page. The state should be verified when user is redirected
|
||||||
// back to Harbor.
|
// back to Harbor.
|
||||||
func AuthCodeURL(state string) (string, error) {
|
func AuthCodeURL(ctx context.Context, state string) (string, error) {
|
||||||
conf, err := getOauthConf()
|
conf, err := getOauthConf(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get OAuth configuration, error: %v", err)
|
log.Errorf("Failed to get OAuth configuration, error: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var options []oauth2.AuthCodeOption
|
var options []oauth2.AuthCodeOption
|
||||||
setting := provider.setting.Load().(cfgModels.OIDCSetting)
|
setting, err := config.OIDCSetting(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
for k, v := range setting.ExtraRedirectParms {
|
for k, v := range setting.ExtraRedirectParms {
|
||||||
options = append(options, oauth2.SetAuthURLParam(k, v))
|
options = append(options, oauth2.SetAuthURLParam(k, v))
|
||||||
}
|
}
|
||||||
@ -182,12 +169,16 @@ func AuthCodeURL(state string) (string, error) {
|
|||||||
|
|
||||||
// ExchangeToken get the token from token provider via the code
|
// ExchangeToken get the token from token provider via the code
|
||||||
func ExchangeToken(ctx context.Context, code string) (*Token, error) {
|
func ExchangeToken(ctx context.Context, code string) (*Token, error) {
|
||||||
oauth, err := getOauthConf()
|
oauth, err := getOauthConf(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get OAuth configuration, error: %v", err)
|
log.Errorf("Failed to get OAuth configuration, error: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
setting := provider.setting.Load().(cfgModels.OIDCSetting)
|
setting, err := config.OIDCSetting(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
ctx = clientCtx(ctx, setting.VerifyCert)
|
ctx = clientCtx(ctx, setting.VerifyCert)
|
||||||
oauthToken, err := oauth.Exchange(ctx, code)
|
oauthToken, err := oauth.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -208,11 +199,15 @@ func VerifyToken(ctx context.Context, rawIDToken string) (*gooidc.IDToken, error
|
|||||||
|
|
||||||
func verifyTokenWithConfig(ctx context.Context, rawIDToken string, conf *gooidc.Config) (*gooidc.IDToken, error) {
|
func verifyTokenWithConfig(ctx context.Context, rawIDToken string, conf *gooidc.Config) (*gooidc.IDToken, error) {
|
||||||
log.Debugf("Raw ID token for verification: %s", rawIDToken)
|
log.Debugf("Raw ID token for verification: %s", rawIDToken)
|
||||||
p, err := provider.get()
|
p, err := provider.get(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
settings := provider.setting.Load().(cfgModels.OIDCSetting)
|
settings, err := config.OIDCSetting(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
conf = &gooidc.Config{ClientID: settings.ClientID}
|
conf = &gooidc.Config{ClientID: settings.ClientID}
|
||||||
}
|
}
|
||||||
@ -236,11 +231,15 @@ func clientCtx(ctx context.Context, verifyCert bool) context.Context {
|
|||||||
// refreshToken tries to refresh the token if it's expired, if it doesn't the
|
// refreshToken tries to refresh the token if it's expired, if it doesn't the
|
||||||
// original one will be returned.
|
// original one will be returned.
|
||||||
func refreshToken(ctx context.Context, token *Token) (*Token, error) {
|
func refreshToken(ctx context.Context, token *Token) (*Token, error) {
|
||||||
oauthCfg, err := getOauthConf()
|
oauthCfg, err := getOauthConf(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
setting := provider.setting.Load().(cfgModels.OIDCSetting)
|
setting, err := config.OIDCSetting(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
cctx := clientCtx(ctx, setting.VerifyCert)
|
cctx := clientCtx(ctx, setting.VerifyCert)
|
||||||
ts := oauthCfg.TokenSource(cctx, &token.Token)
|
ts := oauthCfg.TokenSource(cctx, &token.Token)
|
||||||
nt, err := ts.Token()
|
nt, err := ts.Token()
|
||||||
@ -258,16 +257,20 @@ func refreshToken(ctx context.Context, token *Token) (*Token, error) {
|
|||||||
// to generate a UserInfo object, if the ID token is not in the input token struct, some attributes will be empty
|
// to generate a UserInfo object, if the ID token is not in the input token struct, some attributes will be empty
|
||||||
func UserInfoFromToken(ctx context.Context, token *Token) (*UserInfo, error) {
|
func UserInfoFromToken(ctx context.Context, token *Token) (*UserInfo, error) {
|
||||||
// #10913: preload the configuration, in case it was not previously loaded by the UI
|
// #10913: preload the configuration, in case it was not previously loaded by the UI
|
||||||
_, err := provider.get()
|
_, err := provider.get(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
setting := provider.setting.Load().(cfgModels.OIDCSetting)
|
setting, err := config.OIDCSetting(ctx)
|
||||||
local, err := UserInfoFromIDToken(ctx, token, setting)
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get OIDC configuration, error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
local, err := UserInfoFromIDToken(ctx, token, *setting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
remote, err := userInfoFromRemote(ctx, token, setting)
|
remote, err := userInfoFromRemote(ctx, token, *setting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to get userInfo by calling remote userinfo endpoint, error: %v ", err)
|
log.Warningf("Failed to get userInfo by calling remote userinfo endpoint, error: %v ", err)
|
||||||
}
|
}
|
||||||
@ -319,7 +322,7 @@ func mergeUserInfo(remote, local *UserInfo) *UserInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userInfoFromRemote(ctx context.Context, token *Token, setting cfgModels.OIDCSetting) (*UserInfo, error) {
|
func userInfoFromRemote(ctx context.Context, token *Token, setting cfgModels.OIDCSetting) (*UserInfo, error) {
|
||||||
p, err := provider.get()
|
p, err := provider.get(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -409,6 +412,7 @@ func populateGroupsDB(groupNames []string) ([]int, error) {
|
|||||||
ctx := orm.Context()
|
ctx := orm.Context()
|
||||||
cfg, err := config.OIDCSetting(ctx)
|
cfg, err := config.OIDCSetting(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("failed to get OIDC config, error: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("populateGroupsDB, group filter %v", cfg.GroupFilter)
|
log.Debugf("populateGroupsDB, group filter %v", cfg.GroupFilter)
|
||||||
|
@ -55,20 +55,11 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(result)
|
os.Exit(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestHelperLoadConf(t *testing.T) {
|
|
||||||
testP := &providerHelper{}
|
|
||||||
assert.Nil(t, testP.setting.Load())
|
|
||||||
err := testP.reloadSetting()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "test", testP.setting.Load().(cfgModels.OIDCSetting).Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHelperCreate(t *testing.T) {
|
func TestHelperCreate(t *testing.T) {
|
||||||
testP := &providerHelper{}
|
testP := &providerHelper{}
|
||||||
err := testP.reloadSetting()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, testP.instance.Load())
|
assert.Nil(t, testP.instance.Load())
|
||||||
err = testP.create()
|
err := testP.create(orm.Context())
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, testP.instance.Load())
|
assert.NotNil(t, testP.instance.Load())
|
||||||
assert.True(t, time.Now().Sub(testP.creationTime) < 2*time.Second)
|
assert.True(t, time.Now().Sub(testP.creationTime) < 2*time.Second)
|
||||||
@ -76,7 +67,8 @@ func TestHelperCreate(t *testing.T) {
|
|||||||
|
|
||||||
func TestHelperGet(t *testing.T) {
|
func TestHelperGet(t *testing.T) {
|
||||||
testP := &providerHelper{}
|
testP := &providerHelper{}
|
||||||
p, err := testP.get()
|
ctx := orm.Context()
|
||||||
|
p, err := testP.get(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "https://oauth2.googleapis.com/token", p.Endpoint().TokenURL)
|
assert.Equal(t, "https://oauth2.googleapis.com/token", p.Endpoint().TokenURL)
|
||||||
|
|
||||||
@ -89,12 +81,13 @@ func TestHelperGet(t *testing.T) {
|
|||||||
common.OIDCClientSecret: "new-secret",
|
common.OIDCClientSecret: "new-secret",
|
||||||
common.ExtEndpoint: "https://harbor.test",
|
common.ExtEndpoint: "https://harbor.test",
|
||||||
}
|
}
|
||||||
ctx := orm.Context()
|
|
||||||
config.GetCfgManager(ctx).UpdateConfig(ctx, update)
|
config.GetCfgManager(ctx).UpdateConfig(ctx, update)
|
||||||
|
|
||||||
t.Log("Sleep for 5 seconds")
|
t.Log("Sleep for 5 seconds")
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
assert.Equal(t, "new-secret", testP.setting.Load().(cfgModels.OIDCSetting).ClientSecret)
|
oidcSetting, err := config.OIDCSetting(ctx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "new-secret", oidcSetting.ClientSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthCodeURL(t *testing.T) {
|
func TestAuthCodeURL(t *testing.T) {
|
||||||
@ -110,7 +103,7 @@ func TestAuthCodeURL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx := orm.Context()
|
ctx := orm.Context()
|
||||||
config.GetCfgManager(ctx).UpdateConfig(ctx, conf)
|
config.GetCfgManager(ctx).UpdateConfig(ctx, conf)
|
||||||
res, err := AuthCodeURL("random")
|
res, err := AuthCodeURL(ctx, "random")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
u, err := url.ParseRequestURI(res)
|
u, err := url.ParseRequestURI(res)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user