fix(server): Ensure OIDC config is refreshed at server restart (#26913)

Signed-off-by: OpenGuidou <guillaume.doussin@gmail.com>
This commit is contained in:
OpenGuidou 2026-04-02 02:54:22 +02:00 committed by GitHub
parent 32f23a446f
commit 4259f467b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 102 additions and 3 deletions

View file

@ -334,8 +334,6 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts, appsetOpts Applicatio
appsetLister := appFactory.Argoproj().V1alpha1().ApplicationSets().Lister()
userStateStorage := util_session.NewUserStateStorage(opts.RedisClient)
ssoClientApp, err := oidc.NewClientApp(settings, opts.DexServerAddr, opts.DexTLSConfig, opts.BaseHRef, cacheutil.NewRedisCache(opts.RedisClient, settings.UserInfoCacheExpiration(), cacheutil.RedisCompressionNone))
errorsutil.CheckError(err)
sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.DexTLSConfig, userStateStorage)
enf := rbac.NewEnforcer(opts.KubeClientset, opts.Namespace, common.ArgoCDRBACConfigMapName, nil)
enf.EnableEnforce(!opts.DisableAuth)
@ -383,7 +381,6 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts, appsetOpts Applicatio
a := &ArgoCDServer{
ArgoCDServerOpts: opts,
ApplicationSetOpts: appsetOpts,
ssoClientApp: ssoClientApp,
log: logger,
settings: settings,
sessionMgr: sessionMgr,
@ -586,6 +583,10 @@ func (server *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
if server.RedisClient != nil {
cacheutil.CollectMetrics(server.RedisClient, metricsServ, server.userStateStorage.GetLockObject())
}
// OIDC config needs to be refreshed at each server restart
ssoClientApp, err := oidc.NewClientApp(server.settings, server.DexServerAddr, server.DexTLSConfig, server.BaseHRef, cacheutil.NewRedisCache(server.RedisClient, server.settings.UserInfoCacheExpiration(), cacheutil.RedisCompressionNone))
errorsutil.CheckError(err)
server.ssoClientApp = ssoClientApp
// Don't init storage until after CollectMetrics. CollectMetrics adds hooks to the Redis client, and Init
// reads those hooks. If this is called first, there may be a data race.

View file

@ -488,6 +488,100 @@ func TestGracefulShutdown(t *testing.T) {
assert.True(t, shutdown)
}
func TestOIDCRefresh(t *testing.T) {
port, err := test.GetFreePort()
require.NoError(t, err)
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
cm := test.NewFakeConfigMap()
cm.Data["oidc.config"] = `
name: Test OIDC
issuer: $oidc.myoidc.issuer
clientID: $oidc.myoidc.clientId
clientSecret: $oidc.myoidc.clientSecret
`
secret := test.NewFakeSecret()
issuerURL := "http://oidc.127.0.0.1.nip.io"
updatedIssuerURL := "http://newoidc.127.0.0.1.nip.io"
secret.Data["oidc.myoidc.issuer"] = []byte(issuerURL)
secret.Data["oidc.myoidc.clientId"] = []byte("myClientId")
secret.Data["oidc.myoidc.clientSecret"] = []byte("myClientSecret")
kubeclientset := fake.NewSimpleClientset(cm, secret)
redis, redisCloser := test.NewInMemoryRedis()
defer redisCloser()
s := NewServer(
t.Context(),
ArgoCDServerOpts{
ListenPort: port,
Namespace: test.FakeArgoCDNamespace,
KubeClientset: kubeclientset,
AppClientset: apps.NewSimpleClientset(),
RepoClientset: mockRepoClient,
RedisClient: redis,
},
ApplicationSetOpts{},
)
projInformerCancel := test.StartInformer(s.projInformer)
defer projInformerCancel()
appInformerCancel := test.StartInformer(s.appInformer)
defer appInformerCancel()
appsetInformerCancel := test.StartInformer(s.appsetInformer)
defer appsetInformerCancel()
clusterInformerCancel := test.StartInformer(s.clusterInformer)
defer clusterInformerCancel()
shutdown := false
lns, err := s.Listen()
require.NoError(t, err)
runCtx := t.Context()
var wg gosync.WaitGroup
wg.Add(1)
go func(shutdown *bool) {
defer wg.Done()
s.Run(runCtx, lns)
*shutdown = true
}(&shutdown)
for !s.available.Load() {
time.Sleep(10 * time.Millisecond)
}
assert.True(t, s.available.Load())
assert.Equal(t, issuerURL, s.ssoClientApp.IssuerURL())
// Update oidc config
secret.Data["oidc.myoidc.issuer"] = []byte(updatedIssuerURL)
secret.ResourceVersion = "12345"
_, err = kubeclientset.CoreV1().Secrets(test.FakeArgoCDNamespace).Update(runCtx, secret, metav1.UpdateOptions{})
require.NoError(t, err)
// Wait for graceful shutdown
wg.Wait()
for s.available.Load() {
time.Sleep(10 * time.Millisecond)
}
assert.False(t, s.available.Load())
shutdown = false
wg.Add(1)
go func(shutdown *bool) {
defer wg.Done()
s.Run(runCtx, lns)
*shutdown = true
}(&shutdown)
for !s.available.Load() {
time.Sleep(10 * time.Millisecond)
}
assert.True(t, s.available.Load())
assert.Equal(t, updatedIssuerURL, s.ssoClientApp.IssuerURL())
s.stopCh <- syscall.SIGINT
wg.Wait()
}
func TestAuthenticate(t *testing.T) {
type testData struct {
test string

View file

@ -1069,3 +1069,7 @@ func FormatAccessTokenCacheKey(sub string) string {
func formatOidcTokenCacheKey(sub string, sid string) string {
return fmt.Sprintf("%s_%s_%s", OidcTokenCachePrefix, sub, sid)
}
func (a *ClientApp) IssuerURL() string {
return a.issuerURL
}