Skip to content

Commit f8f256f

Browse files
committed
fix(dex): always request federated:id scope
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
1 parent 85c6d26 commit f8f256f

4 files changed

Lines changed: 112 additions & 4 deletions

File tree

common/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ const (
158158
ArgoCDCLIClientAppName = "Argo CD CLI"
159159
// ArgoCDCLIClientAppID is the Oauth client ID we will use when registering our CLI to dex
160160
ArgoCDCLIClientAppID = "argo-cd-cli"
161+
// DexFederatedScope allows to receive the federated_claims from Dex. https://dexidp.io/docs/configuration/custom-scopes-claims-clients/
162+
DexFederatedScope = "federated:id"
161163
)
162164

163165
// Resource metadata labels and annotations (keys and values) used by Argo CD components

pkg/apiclient/apiclient.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,10 @@ func (c *client) OIDCConfig(ctx context.Context, set *settingspkg.Settings) (*oa
327327
clientID = set.OIDCConfig.ClientID
328328
}
329329
issuerURL = set.OIDCConfig.Issuer
330-
scopes = set.OIDCConfig.Scopes
330+
scopes = oidcutil.GetScopesOrDefault(set.OIDCConfig.Scopes)
331331
case set.DexConfig != nil && len(set.DexConfig.Connectors) > 0:
332332
clientID = common.ArgoCDCLIClientAppID
333+
scopes = append(oidcutil.GetScopesOrDefault(scopes), common.DexFederatedScope)
333334
issuerURL = fmt.Sprintf("%s%s", set.URL, common.DexAPIEndpoint)
334335
default:
335336
return nil, nil, fmt.Errorf("%s is not configured with SSO", c.ServerAddr)
@@ -342,7 +343,6 @@ func (c *client) OIDCConfig(ctx context.Context, set *settingspkg.Settings) (*oa
342343
if err != nil {
343344
return nil, nil, fmt.Errorf("Failed to parse provider config: %w", err)
344345
}
345-
scopes = oidcutil.GetScopesOrDefault(scopes)
346346
if oidcutil.OfflineAccess(oidcConf.ScopesSupported) {
347347
scopes = append(scopes, oidc.ScopeOfflineAccess)
348348
}

util/oidc/oidc.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,13 @@ func (a *ClientApp) HandleLogin(w http.ResponseWriter, r *http.Request) {
314314
scopes := make([]string, 0)
315315
var opts []oauth2.AuthCodeOption
316316
if config := a.settings.OIDCConfig(); config != nil {
317-
scopes = config.RequestedScopes
317+
scopes = GetScopesOrDefault(config.RequestedScopes)
318318
opts = AppendClaimsAuthenticationRequestParameter(opts, config.RequestedIDTokenClaims)
319+
} else if a.settings.IsDexConfigured() {
320+
scopes = append(GetScopesOrDefault(scopes), common.DexFederatedScope)
319321
}
320-
oauth2Config, err := a.oauth2Config(r, GetScopesOrDefault(scopes))
322+
323+
oauth2Config, err := a.oauth2Config(r, scopes)
321324
if err != nil {
322325
http.Error(w, err.Error(), http.StatusInternalServerError)
323326
return

util/oidc/oidc_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/stretchr/testify/assert"
2222
"github.com/stretchr/testify/require"
2323
"golang.org/x/oauth2"
24+
"sigs.k8s.io/yaml"
2425

2526
"github.com/argoproj/argo-cd/v3/common"
2627
"github.com/argoproj/argo-cd/v3/server/settings/oidc"
@@ -204,6 +205,108 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
204205
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
205206
})
206207

208+
t.Run("OIDC auth", func(t *testing.T) {
209+
cdSettings := &settings.ArgoCDSettings{
210+
URL: "https://argocd.example.com",
211+
OIDCTLSInsecureSkipVerify: true,
212+
}
213+
oidcConfig := settings.OIDCConfig{
214+
Name: "Test",
215+
Issuer: oidcTestServer.URL,
216+
ClientID: "xxx",
217+
ClientSecret: "yyy",
218+
}
219+
oidcConfigRaw, err := yaml.Marshal(oidcConfig)
220+
require.NoError(t, err)
221+
cdSettings.OIDCConfigRAW = string(oidcConfigRaw)
222+
223+
app, err := NewClientApp(cdSettings, dexTestServer.URL, &dex.DexTLSConfig{StrictValidation: false}, "https://argocd.example.com", cache.NewInMemoryCache(24*time.Hour))
224+
require.NoError(t, err)
225+
226+
req := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/auth/login", nil)
227+
w := httptest.NewRecorder()
228+
app.HandleLogin(w, req)
229+
230+
assert.Equal(t, http.StatusSeeOther, w.Code)
231+
location, err := url.Parse(w.Header().Get("Location"))
232+
require.NoError(t, err)
233+
values, err := url.ParseQuery(location.RawQuery)
234+
require.NoError(t, err)
235+
assert.Equal(t, []string{"openid", "profile", "email", "groups"}, strings.Split(values.Get("scope"), " "))
236+
assert.Equal(t, "xxx", values.Get("client_id"))
237+
assert.Equal(t, "code", values.Get("response_type"))
238+
})
239+
240+
t.Run("OIDC auth with custom scopes", func(t *testing.T) {
241+
cdSettings := &settings.ArgoCDSettings{
242+
URL: "https://argocd.example.com",
243+
OIDCTLSInsecureSkipVerify: true,
244+
}
245+
oidcConfig := settings.OIDCConfig{
246+
Name: "Test",
247+
Issuer: oidcTestServer.URL,
248+
ClientID: "xxx",
249+
ClientSecret: "yyy",
250+
RequestedScopes: []string{"oidc"},
251+
}
252+
oidcConfigRaw, err := yaml.Marshal(oidcConfig)
253+
require.NoError(t, err)
254+
cdSettings.OIDCConfigRAW = string(oidcConfigRaw)
255+
256+
app, err := NewClientApp(cdSettings, dexTestServer.URL, &dex.DexTLSConfig{StrictValidation: false}, "https://argocd.example.com", cache.NewInMemoryCache(24*time.Hour))
257+
require.NoError(t, err)
258+
259+
req := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/auth/login", nil)
260+
w := httptest.NewRecorder()
261+
app.HandleLogin(w, req)
262+
263+
assert.Equal(t, http.StatusSeeOther, w.Code)
264+
location, err := url.Parse(w.Header().Get("Location"))
265+
require.NoError(t, err)
266+
values, err := url.ParseQuery(location.RawQuery)
267+
require.NoError(t, err)
268+
assert.Equal(t, []string{"oidc"}, strings.Split(values.Get("scope"), " "))
269+
assert.Equal(t, "xxx", values.Get("client_id"))
270+
assert.Equal(t, "code", values.Get("response_type"))
271+
})
272+
273+
t.Run("Dex auth", func(t *testing.T) {
274+
cdSettings := &settings.ArgoCDSettings{
275+
URL: dexTestServer.URL,
276+
}
277+
dexConfig := map[string]any{
278+
"connectors": []map[string]any{
279+
{
280+
"type": "github",
281+
"name": "GitHub",
282+
"config": map[string]any{
283+
"clientId": "aabbccddeeff00112233",
284+
"clientSecret": "aabbccddeeff00112233",
285+
},
286+
},
287+
},
288+
}
289+
dexConfigRaw, err := yaml.Marshal(dexConfig)
290+
require.NoError(t, err)
291+
cdSettings.DexConfig = string(dexConfigRaw)
292+
293+
app, err := NewClientApp(cdSettings, dexTestServer.URL, &dex.DexTLSConfig{StrictValidation: false}, "https://argocd.example.com", cache.NewInMemoryCache(24*time.Hour))
294+
require.NoError(t, err)
295+
296+
req := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/auth/login", nil)
297+
w := httptest.NewRecorder()
298+
app.HandleLogin(w, req)
299+
300+
assert.Equal(t, http.StatusSeeOther, w.Code)
301+
location, err := url.Parse(w.Header().Get("Location"))
302+
require.NoError(t, err)
303+
values, err := url.ParseQuery(location.RawQuery)
304+
require.NoError(t, err)
305+
assert.Equal(t, []string{"openid", "profile", "email", "groups", common.DexFederatedScope}, strings.Split(values.Get("scope"), " "))
306+
assert.Equal(t, common.ArgoCDClientAppID, values.Get("client_id"))
307+
assert.Equal(t, "code", values.Get("response_type"))
308+
})
309+
207310
t.Run("with additional base URL", func(t *testing.T) {
208311
cdSettings := &settings.ArgoCDSettings{
209312
URL: "https://argocd.example.com",

0 commit comments

Comments
 (0)