Skip to content

Commit

Permalink
feat: support referencing secret in any field of oidc config (argopro…
Browse files Browse the repository at this point in the history
…j#13475)

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
  • Loading branch information
alexmt authored and tesla59 committed Dec 16, 2023
1 parent d871d54 commit 5b59717
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 50 deletions.
52 changes: 8 additions & 44 deletions util/dex/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ import (
"github.com/argoproj/argo-cd/v2/util/settings"
)

func GenerateDexConfigYAML(settings *settings.ArgoCDSettings, disableTls bool) ([]byte, error) {
if !settings.IsDexConfigured() {
func GenerateDexConfigYAML(argocdSettings *settings.ArgoCDSettings, disableTls bool) ([]byte, error) {
if !argocdSettings.IsDexConfigured() {
return nil, nil
}
redirectURL, err := settings.RedirectURL()
redirectURL, err := argocdSettings.RedirectURL()
if err != nil {
return nil, fmt.Errorf("failed to infer redirect url from config: %v", err)
}
var dexCfg map[string]interface{}
err = yaml.Unmarshal([]byte(settings.DexConfig), &dexCfg)
err = yaml.Unmarshal([]byte(argocdSettings.DexConfig), &dexCfg)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal dex.config from configmap: %v", err)
}
dexCfg["issuer"] = settings.IssuerURL()
dexCfg["issuer"] = argocdSettings.IssuerURL()
dexCfg["storage"] = map[string]interface{}{
"type": "memory",
}
Expand Down Expand Up @@ -58,7 +58,7 @@ func GenerateDexConfigYAML(settings *settings.ArgoCDSettings, disableTls bool) (
argoCDStaticClient := map[string]interface{}{
"id": common.ArgoCDClientAppID,
"name": common.ArgoCDClientAppName,
"secret": settings.DexOAuth2ClientSecret(),
"secret": argocdSettings.DexOAuth2ClientSecret(),
"redirectURIs": []string{
redirectURL,
},
Expand All @@ -80,7 +80,7 @@ func GenerateDexConfigYAML(settings *settings.ArgoCDSettings, disableTls bool) (
dexCfg["staticClients"] = []interface{}{argoCDStaticClient, argoCDCLIStaticClient}
}

dexRedirectURL, err := settings.DexRedirectURL()
dexRedirectURL, err := argocdSettings.DexRedirectURL()
if err != nil {
return nil, err
}
Expand All @@ -106,46 +106,10 @@ func GenerateDexConfigYAML(settings *settings.ArgoCDSettings, disableTls bool) (
connectors[i] = connector
}
dexCfg["connectors"] = connectors
dexCfg = replaceMapSecrets(dexCfg, settings.Secrets)
dexCfg = settings.ReplaceMapSecrets(dexCfg, argocdSettings.Secrets)
return yaml.Marshal(dexCfg)
}

// replaceMapSecrets takes a json object and recursively looks for any secret key references in the
// object and replaces the value with the secret value
func replaceMapSecrets(obj map[string]interface{}, secretValues map[string]string) map[string]interface{} {
newObj := make(map[string]interface{})
for k, v := range obj {
switch val := v.(type) {
case map[string]interface{}:
newObj[k] = replaceMapSecrets(val, secretValues)
case []interface{}:
newObj[k] = replaceListSecrets(val, secretValues)
case string:
newObj[k] = settings.ReplaceStringSecret(val, secretValues)
default:
newObj[k] = val
}
}
return newObj
}

func replaceListSecrets(obj []interface{}, secretValues map[string]string) []interface{} {
newObj := make([]interface{}, len(obj))
for i, v := range obj {
switch val := v.(type) {
case map[string]interface{}:
newObj[i] = replaceMapSecrets(val, secretValues)
case []interface{}:
newObj[i] = replaceListSecrets(val, secretValues)
case string:
newObj[i] = settings.ReplaceStringSecret(val, secretValues)
default:
newObj[i] = val
}
}
return newObj
}

// needsRedirectURI returns whether or not the given connector type needs a redirectURI
// Update this list as necessary, as new connectors are added
// https://dexidp.io/docs/connectors/
Expand Down
59 changes: 54 additions & 5 deletions util/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -1680,13 +1680,26 @@ func (a *ArgoCDSettings) oidcConfig() *oidcConfig {
if a.OIDCConfigRAW == "" {
return nil
}
config, err := unmarshalOIDCConfig(a.OIDCConfigRAW)
configMap := map[string]interface{}{}
err := yaml.Unmarshal([]byte(a.OIDCConfigRAW), &configMap)
if err != nil {
log.Warnf("invalid oidc config: %v", err)
return nil
}
config.ClientSecret = ReplaceStringSecret(config.ClientSecret, a.Secrets)
config.ClientID = ReplaceStringSecret(config.ClientID, a.Secrets)

configMap = ReplaceMapSecrets(configMap, a.Secrets)
data, err := yaml.Marshal(configMap)
if err != nil {
log.Warnf("invalid oidc config: %v", err)
return nil
}

config, err := unmarshalOIDCConfig(string(data))
if err != nil {
log.Warnf("invalid oidc config: %v", err)
return nil
}

return &config
}

Expand Down Expand Up @@ -1977,8 +1990,44 @@ func (mgr *SettingsManager) InitializeSettings(insecureModeEnabled bool) (*ArgoC
return cdSettings, nil
}

// ReplaceStringSecret checks if given string is a secret key reference ( starts with $ ) and returns corresponding value from provided map
func ReplaceStringSecret(val string, secretValues map[string]string) string {
// ReplaceMapSecrets takes a json object and recursively looks for any secret key references in the
// object and replaces the value with the secret value
func ReplaceMapSecrets(obj map[string]interface{}, secretValues map[string]string) map[string]interface{} {
newObj := make(map[string]interface{})
for k, v := range obj {
switch val := v.(type) {
case map[string]interface{}:
newObj[k] = ReplaceMapSecrets(val, secretValues)
case []interface{}:
newObj[k] = replaceListSecrets(val, secretValues)
case string:
newObj[k] = replaceStringSecret(val, secretValues)
default:
newObj[k] = val
}
}
return newObj
}

func replaceListSecrets(obj []interface{}, secretValues map[string]string) []interface{} {
newObj := make([]interface{}, len(obj))
for i, v := range obj {
switch val := v.(type) {
case map[string]interface{}:
newObj[i] = ReplaceMapSecrets(val, secretValues)
case []interface{}:
newObj[i] = replaceListSecrets(val, secretValues)
case string:
newObj[i] = replaceStringSecret(val, secretValues)
default:
newObj[i] = val
}
}
return newObj
}

// replaceStringSecret checks if given string is a secret key reference ( starts with $ ) and returns corresponding value from provided map
func replaceStringSecret(val string, secretValues map[string]string) string {
if val == "" || !strings.HasPrefix(val, "$") {
return val
}
Expand Down
4 changes: 3 additions & 1 deletion util/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ func TestDownloadArgoCDBinaryUrls(t *testing.T) {
func TestSecretKeyRef(t *testing.T) {
data := map[string]string{
"oidc.config": `name: Okta
issuer: https://dev-123456.oktapreview.com
issuer: $acme:issuerSecret
clientID: aaaabbbbccccddddeee
clientSecret: $acme:clientSecret
# Optional set of OIDC scopes to request. If omitted, defaults to: ["openid", "profile", "email", "groups"]
Expand Down Expand Up @@ -1176,6 +1176,7 @@ requestedIDTokenClaims: {"groups": {"essential": true}}`,
},
},
Data: map[string][]byte{
"issuerSecret": []byte("https://dev-123456.oktapreview.com"),
"clientSecret": []byte("deadbeef"),
},
}
Expand All @@ -1186,6 +1187,7 @@ requestedIDTokenClaims: {"groups": {"essential": true}}`,
assert.NoError(t, err)

oidcConfig := settings.OIDCConfig()
assert.Equal(t, oidcConfig.Issuer, "https://dev-123456.oktapreview.com")
assert.Equal(t, oidcConfig.ClientSecret, "deadbeef")
}

Expand Down

0 comments on commit 5b59717

Please sign in to comment.