github.com/argoproj/argo-cd/v3@v3.2.1/util/dex/dex_test.go (about) 1 package dex 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "net/url" 9 "strings" 10 "testing" 11 12 log "github.com/sirupsen/logrus" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "sigs.k8s.io/yaml" 16 17 "github.com/argoproj/argo-cd/v3/common" 18 utillog "github.com/argoproj/argo-cd/v3/util/log" 19 "github.com/argoproj/argo-cd/v3/util/settings" 20 ) 21 22 const invalidURL = ":://localhost/foo/bar" 23 24 var malformedDexConfig = ` 25 valid: 26 yaml: valid 27 yaml: 28 valid 29 ` 30 31 var goodDexConfig = ` 32 connectors: 33 # GitHub example 34 - type: github 35 id: github 36 name: GitHub 37 config: 38 clientID: aabbccddeeff00112233 39 clientSecret: $dex.github.clientSecret 40 orgs: 41 - name: your-github-org 42 43 # GitHub enterprise example 44 - type: github 45 id: acme-github 46 name: Acme GitHub 47 config: 48 hostName: github.acme.example.com 49 clientID: abcdefghijklmnopqrst 50 clientSecret: $dex.acme.clientSecret 51 orgs: 52 - name: your-github-org 53 ` 54 55 var customStaticClientDexConfig = ` 56 connectors: 57 # GitHub example 58 - type: github 59 id: github 60 name: GitHub 61 config: 62 clientID: aabbccddeeff00112233 63 clientSecret: abcdefghijklmnopqrst\n\r 64 orgs: 65 - name: your-github-org 66 staticClients: 67 - id: argo-workflow 68 name: Argo Workflow 69 redirectURIs: 70 - https://argo/oauth2/callback 71 secret: $dex.acme.clientSecret 72 ` 73 74 var badDexConfig = ` 75 connectors: 76 # GitHub example 77 - type: github 78 id: github 79 name: GitHub 80 config: foo 81 82 # GitHub enterprise example 83 - type: github 84 id: acme-github 85 name: Acme GitHub 86 config: 87 hostName: github.acme.example.com 88 clientID: abcdefghijklmnopqrst 89 clientSecret: $dex.acme.clientSecret 90 orgs: 91 - name: your-github-org 92 ` 93 94 var goodDexConfigWithOauthOverrides = ` 95 oauth2: 96 passwordConnector: ldap 97 connectors: 98 - type: ldap 99 name: OpenLDAP 100 id: ldap 101 config: 102 host: localhost:389 103 insecureNoSSL: true 104 bindDN: cn=admin,dc=example,dc=org 105 bindPW: admin 106 usernamePrompt: Email Address 107 userSearch: 108 baseDN: ou=People,dc=example,dc=org 109 filter: "(objectClass=person)" 110 username: mail 111 idAttr: DN 112 emailAttr: mail 113 nameAttr: cn 114 groupSearch: 115 baseDN: ou=Groups,dc=example,dc=org 116 filter: "(objectClass=groupOfNames)" 117 nameAttr: cn 118 ` 119 120 var goodDexConfigWithEnabledApprovalScreen = ` 121 oauth2: 122 passwordConnector: ldap 123 skipApprovalScreen: false 124 connectors: 125 - type: ldap 126 name: OpenLDAP 127 id: ldap 128 config: 129 host: localhost:389 130 insecureNoSSL: true 131 bindDN: cn=admin,dc=example,dc=org 132 bindPW: admin 133 usernamePrompt: Email Address 134 userSearch: 135 baseDN: ou=People,dc=example,dc=org 136 filter: "(objectClass=person)" 137 username: mail 138 idAttr: DN 139 emailAttr: mail 140 nameAttr: cn 141 groupSearch: 142 baseDN: ou=Groups,dc=example,dc=org 143 filter: "(objectClass=groupOfNames)" 144 nameAttr: cn 145 ` 146 147 var goodDexConfigWithLogger = ` 148 logger: 149 level: debug 150 other: value 151 connectors: 152 # GitHub example 153 - type: github 154 id: github 155 name: GitHub 156 config: 157 clientID: aabbccddeeff00112233 158 clientSecret: $dex.github.clientSecret 159 orgs: 160 - name: your-github-org 161 162 # GitHub enterprise example 163 - type: github 164 id: acme-github 165 name: Acme GitHub 166 config: 167 hostName: github.acme.example.com 168 clientID: abcdefghijklmnopqrst 169 clientSecret: $dex.acme.clientSecret 170 orgs: 171 - name: your-github-org 172 ` 173 174 var goodSecrets = map[string]string{ 175 "dex.github.clientSecret": "foobar", 176 "dex.acme.clientSecret": "barfoo", 177 } 178 179 var goodSecretswithCRLF = map[string]string{ 180 "dex.github.clientSecret": "foobar\n\r", 181 "dex.acme.clientSecret": "barfoo\n\r", 182 } 183 184 func Test_GenerateDexConfig(t *testing.T) { 185 t.Run("Empty settings", func(t *testing.T) { 186 s := settings.ArgoCDSettings{} 187 config, err := GenerateDexConfigYAML(&s, false) 188 require.NoError(t, err) 189 assert.Nil(t, config) 190 }) 191 192 t.Run("Invalid URL", func(t *testing.T) { 193 s := settings.ArgoCDSettings{ 194 URL: invalidURL, 195 DexConfig: goodDexConfig, 196 } 197 config, err := GenerateDexConfigYAML(&s, false) 198 require.Error(t, err) 199 assert.Nil(t, config) 200 }) 201 202 t.Run("No URL set", func(t *testing.T) { 203 s := settings.ArgoCDSettings{ 204 URL: "", 205 DexConfig: "invalidyaml", 206 } 207 config, err := GenerateDexConfigYAML(&s, false) 208 require.NoError(t, err) 209 assert.Nil(t, config) 210 }) 211 212 t.Run("Invalid YAML", func(t *testing.T) { 213 s := settings.ArgoCDSettings{ 214 URL: "http://localhost", 215 DexConfig: "invalidyaml", 216 } 217 config, err := GenerateDexConfigYAML(&s, false) 218 require.NoError(t, err) 219 assert.Nil(t, config) 220 }) 221 222 t.Run("Valid YAML but incorrect Dex config", func(t *testing.T) { 223 s := settings.ArgoCDSettings{ 224 URL: "http://localhost", 225 DexConfig: malformedDexConfig, 226 } 227 config, err := GenerateDexConfigYAML(&s, false) 228 require.Error(t, err) 229 assert.Nil(t, config) 230 }) 231 232 t.Run("Valid YAML but incorrect Dex config", func(t *testing.T) { 233 s := settings.ArgoCDSettings{ 234 URL: "http://localhost", 235 DexConfig: badDexConfig, 236 } 237 config, err := GenerateDexConfigYAML(&s, false) 238 require.Error(t, err) 239 assert.Nil(t, config) 240 }) 241 242 t.Run("Valid YAML and correct Dex config", func(t *testing.T) { 243 s := settings.ArgoCDSettings{ 244 URL: "http://localhost", 245 DexConfig: goodDexConfig, 246 } 247 config, err := GenerateDexConfigYAML(&s, false) 248 require.NoError(t, err) 249 assert.NotNil(t, config) 250 }) 251 252 t.Run("Secret dereference", func(t *testing.T) { 253 s := settings.ArgoCDSettings{ 254 URL: "http://localhost", 255 DexConfig: goodDexConfig, 256 Secrets: goodSecrets, 257 } 258 config, err := GenerateDexConfigYAML(&s, false) 259 require.NoError(t, err) 260 assert.NotNil(t, config) 261 var dexCfg map[string]any 262 err = yaml.Unmarshal(config, &dexCfg) 263 if err != nil { 264 panic(err.Error()) 265 } 266 connectors, ok := dexCfg["connectors"].([]any) 267 assert.True(t, ok) 268 for i, connectorsIf := range connectors { 269 config := connectorsIf.(map[string]any)["config"].(map[string]any) 270 switch i { 271 case 0: 272 assert.Equal(t, "foobar", config["clientSecret"]) 273 case 1: 274 assert.Equal(t, "barfoo", config["clientSecret"]) 275 } 276 } 277 }) 278 279 t.Run("Secret dereference with extra white space", func(t *testing.T) { 280 s := settings.ArgoCDSettings{ 281 URL: "http://localhost", 282 DexConfig: goodDexConfig, 283 Secrets: goodSecretswithCRLF, 284 } 285 config, err := GenerateDexConfigYAML(&s, false) 286 require.NoError(t, err) 287 assert.NotNil(t, config) 288 var dexCfg map[string]any 289 err = yaml.Unmarshal(config, &dexCfg) 290 if err != nil { 291 panic(err.Error()) 292 } 293 connectors, ok := dexCfg["connectors"].([]any) 294 assert.True(t, ok) 295 for i, connectorsIf := range connectors { 296 config := connectorsIf.(map[string]any)["config"].(map[string]any) 297 switch i { 298 case 0: 299 assert.Equal(t, "foobar", config["clientSecret"]) 300 case 1: 301 assert.Equal(t, "barfoo", config["clientSecret"]) 302 } 303 } 304 }) 305 306 t.Run("Logging level", func(t *testing.T) { 307 s := settings.ArgoCDSettings{ 308 URL: "http://localhost", 309 DexConfig: goodDexConfig, 310 } 311 t.Setenv(common.EnvLogLevel, log.WarnLevel.String()) 312 t.Setenv(common.EnvLogFormat, utillog.JsonFormat) 313 314 config, err := GenerateDexConfigYAML(&s, false) 315 require.NoError(t, err) 316 assert.NotNil(t, config) 317 var dexCfg map[string]any 318 err = yaml.Unmarshal(config, &dexCfg) 319 if err != nil { 320 panic(err.Error()) 321 } 322 loggerCfg, ok := dexCfg["logger"].(map[string]any) 323 assert.True(t, ok) 324 325 level, ok := loggerCfg["level"].(string) 326 assert.True(t, ok) 327 assert.Equal(t, "WARN", level) 328 329 format, ok := loggerCfg["format"].(string) 330 assert.True(t, ok) 331 assert.Equal(t, "json", format) 332 }) 333 334 t.Run("Logging level with config", func(t *testing.T) { 335 s := settings.ArgoCDSettings{ 336 URL: "http://localhost", 337 DexConfig: goodDexConfigWithLogger, 338 } 339 t.Setenv(common.EnvLogLevel, log.WarnLevel.String()) 340 t.Setenv(common.EnvLogFormat, utillog.JsonFormat) 341 342 config, err := GenerateDexConfigYAML(&s, false) 343 require.NoError(t, err) 344 assert.NotNil(t, config) 345 var dexCfg map[string]any 346 err = yaml.Unmarshal(config, &dexCfg) 347 if err != nil { 348 panic(err.Error()) 349 } 350 loggerCfg, ok := dexCfg["logger"].(map[string]any) 351 assert.True(t, ok) 352 353 level, ok := loggerCfg["level"].(string) 354 assert.True(t, ok) 355 assert.Equal(t, "debug", level) 356 357 format, ok := loggerCfg["format"].(string) 358 assert.True(t, ok) 359 assert.Equal(t, "json", format) 360 361 _, ok = loggerCfg["other"].(string) 362 assert.True(t, ok) 363 }) 364 365 t.Run("Redirect config", func(t *testing.T) { 366 types := []string{"oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud", "openshift", "gitea", "google", "oauth"} 367 for _, c := range types { 368 assert.True(t, needsRedirectURI(c)) 369 } 370 assert.False(t, needsRedirectURI("invalid")) 371 }) 372 373 t.Run("Custom static clients", func(t *testing.T) { 374 s := settings.ArgoCDSettings{ 375 URL: "http://localhost", 376 DexConfig: customStaticClientDexConfig, 377 Secrets: goodSecretswithCRLF, 378 } 379 config, err := GenerateDexConfigYAML(&s, false) 380 require.NoError(t, err) 381 assert.NotNil(t, config) 382 var dexCfg map[string]any 383 err = yaml.Unmarshal(config, &dexCfg) 384 if err != nil { 385 panic(err.Error()) 386 } 387 clients, ok := dexCfg["staticClients"].([]any) 388 assert.True(t, ok) 389 assert.Len(t, clients, 4) 390 391 customClient := clients[3].(map[string]any) 392 assert.Equal(t, "argo-workflow", customClient["id"].(string)) 393 assert.Len(t, customClient["redirectURIs"].([]any), 1) 394 }) 395 t.Run("Custom static clients secret dereference with trailing CRLF", func(t *testing.T) { 396 s := settings.ArgoCDSettings{ 397 URL: "http://localhost", 398 DexConfig: customStaticClientDexConfig, 399 Secrets: goodSecretswithCRLF, 400 } 401 config, err := GenerateDexConfigYAML(&s, false) 402 require.NoError(t, err) 403 assert.NotNil(t, config) 404 var dexCfg map[string]any 405 err = yaml.Unmarshal(config, &dexCfg) 406 if err != nil { 407 panic(err.Error()) 408 } 409 clients, ok := dexCfg["staticClients"].([]any) 410 assert.True(t, ok) 411 assert.Len(t, clients, 4) 412 413 customClient := clients[3].(map[string]any) 414 assert.Equal(t, "barfoo", customClient["secret"]) 415 }) 416 t.Run("Override dex oauth2 configuration", func(t *testing.T) { 417 s := settings.ArgoCDSettings{ 418 URL: "http://localhost", 419 DexConfig: goodDexConfigWithOauthOverrides, 420 } 421 config, err := GenerateDexConfigYAML(&s, false) 422 require.NoError(t, err) 423 assert.NotNil(t, config) 424 var dexCfg map[string]any 425 err = yaml.Unmarshal(config, &dexCfg) 426 if err != nil { 427 panic(err.Error()) 428 } 429 oauth2Config, ok := dexCfg["oauth2"].(map[string]any) 430 assert.True(t, ok) 431 pwConn, ok := oauth2Config["passwordConnector"].(string) 432 assert.True(t, ok) 433 assert.Equal(t, "ldap", pwConn) 434 435 skipApprScr, ok := oauth2Config["skipApprovalScreen"].(bool) 436 assert.True(t, ok) 437 assert.True(t, skipApprScr) 438 }) 439 t.Run("Override dex oauth2 with enabled ApprovalScreen", func(t *testing.T) { 440 s := settings.ArgoCDSettings{ 441 URL: "http://localhost", 442 DexConfig: goodDexConfigWithEnabledApprovalScreen, 443 } 444 config, err := GenerateDexConfigYAML(&s, false) 445 require.NoError(t, err) 446 assert.NotNil(t, config) 447 var dexCfg map[string]any 448 err = yaml.Unmarshal(config, &dexCfg) 449 if err != nil { 450 panic(err.Error()) 451 } 452 oauth2Config, ok := dexCfg["oauth2"].(map[string]any) 453 assert.True(t, ok) 454 pwConn, ok := oauth2Config["passwordConnector"].(string) 455 assert.True(t, ok) 456 assert.Equal(t, "ldap", pwConn) 457 458 skipApprScr, ok := oauth2Config["skipApprovalScreen"].(bool) 459 assert.True(t, ok) 460 assert.False(t, skipApprScr) 461 }) 462 } 463 464 func Test_DexReverseProxy(t *testing.T) { 465 t.Run("Good case", func(t *testing.T) { 466 var host string 467 fakeDex := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 468 host = req.Host 469 rw.WriteHeader(http.StatusOK) 470 })) 471 defer fakeDex.Close() 472 fmt.Printf("Fake Dex listening on %s\n", fakeDex.URL) 473 server := httptest.NewServer(http.HandlerFunc(NewDexHTTPReverseProxy(fakeDex.URL, "/", nil))) 474 fmt.Printf("Fake API Server listening on %s\n", server.URL) 475 defer server.Close() 476 target, _ := url.Parse(fakeDex.URL) 477 resp, err := http.Get(server.URL) 478 assert.NotNil(t, resp) 479 require.NoError(t, err) 480 assert.Equal(t, http.StatusOK, resp.StatusCode) 481 assert.Equal(t, host, target.Host) 482 fmt.Printf("%s\n", resp.Status) 483 }) 484 485 t.Run("Bad case", func(t *testing.T) { 486 fakeDex := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { 487 rw.WriteHeader(http.StatusInternalServerError) 488 })) 489 defer fakeDex.Close() 490 fmt.Printf("Fake Dex listening on %s\n", fakeDex.URL) 491 server := httptest.NewServer(http.HandlerFunc(NewDexHTTPReverseProxy(fakeDex.URL, "/", nil))) 492 fmt.Printf("Fake API Server listening on %s\n", server.URL) 493 defer server.Close() 494 client := &http.Client{ 495 CheckRedirect: func(_ *http.Request, _ []*http.Request) error { 496 return http.ErrUseLastResponse 497 }, 498 } 499 resp, err := client.Get(server.URL) 500 assert.NotNil(t, resp) 501 require.NoError(t, err) 502 assert.Equal(t, http.StatusSeeOther, resp.StatusCode) 503 location, _ := resp.Location() 504 fmt.Printf("%s %s\n", resp.Status, location.RequestURI()) 505 assert.True(t, strings.HasPrefix(location.RequestURI(), "/login?has_sso_error=true")) 506 }) 507 508 t.Run("Invalid URL for Dex reverse proxy", func(t *testing.T) { 509 // Can't test for now, since it would call exit 510 t.Skip() 511 f := NewDexHTTPReverseProxy(invalidURL, "/", nil) 512 assert.Nil(t, f) 513 }) 514 515 t.Run("Round Tripper", func(t *testing.T) { 516 server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { 517 assert.Equal(t, "/", req.URL.String()) 518 })) 519 defer server.Close() 520 rt := NewDexRewriteURLRoundTripper(server.URL, http.DefaultTransport) 521 assert.NotNil(t, rt) 522 req, err := http.NewRequest(http.MethodGet, "/", bytes.NewBuffer([]byte(""))) 523 require.NoError(t, err) 524 _, err = rt.RoundTrip(req) 525 require.NoError(t, err) 526 target, _ := url.Parse(server.URL) 527 assert.Equal(t, req.Host, target.Host) 528 }) 529 }