Skip to content

Commit

Permalink
Pass the assertions as method to the OIDC server
Browse files Browse the repository at this point in the history
Signed-off-by: Jagpreet Singh Tamber <[email protected]>
  • Loading branch information
jagpreetstamber committed Jan 17, 2025
1 parent 9fc41b0 commit 3bf9530
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 32 deletions.
11 changes: 6 additions & 5 deletions docs/operator-manual/user-management/microsoft.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@

#### Add credentials a new Entra ID App registration
##### Using Workload Identity Federation (Recommended)
1. **Label the Pods:** Add the azure.workload.identity/use: "true" label to the argocd-server pods.
2. **Add Annotation to Service Account:** Add "azure.workload.identity/client-id": "$CLIENT_ID" annotation to the argocd-server service account using the details from application created in previous step.
3. From the `Certificates & secrets` menu, Navigate to `Federated credentials` choose `+ Add credential`
1. **Label the Pods:** Add the `azure.workload.identity/use: "true"` label to the `argocd-server` pods.
2. **Add Annotation to Service Account:** Add `azure.workload.identity/client-id: "$CLIENT_ID"` annotation to the `argocd-server` service account using the details from application created in previous step.
3. From the `Certificates & secrets` menu, navigate to `Federated credentials`, then choose `+ Add credential`
4. Choose `Federated credential scenario` as `Kubernetes Accessing Azure resources`
- Enter Cluster Issuer URL, refer [Retreive the OIDC issuer URL](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster#retrieve-the-oidc-issuer-url)
- Enter Cluster Issuer URL, refer to [retrieve the OIDC issuer URL](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster#retrieve-the-oidc-issuer-url) documentation
- Enter namespace as the namespace where the argocd is deployed
- Enter service account name as `argocd-server`
- Enter a unique name
Expand Down Expand Up @@ -75,7 +75,8 @@
issuer: https://login.microsoftonline.com/{directory_tenant_id}/v2.0
clientID: {azure_ad_application_client_id}
clientSecret: $oidc.azure.clientSecret // if using client secret for authentication
useAzureWorkloadIdentity: true // if using azure workload identity for authentication
azure:
useWorkloadIdentity: true // if using azure workload identity for authentication
requestedIDTokenClaims:
groups:
essential: true
Expand Down
2 changes: 1 addition & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool, useDex
})
oidcServer := ts
if !useDexForSSO {
oidcServer = testutil.GetOIDCTestServer(t, false)
oidcServer = testutil.GetOIDCTestServer(t)
}
if withFakeSSO {
cm.Data["url"] = ts.URL
Expand Down
22 changes: 17 additions & 5 deletions util/oidc/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestHandleCallback(t *testing.T) {
}

func TestClientApp_HandleLogin(t *testing.T) {
oidcTestServer := test.GetOIDCTestServer(t, false)
oidcTestServer := test.GetOIDCTestServer(t)
t.Cleanup(oidcTestServer.Close)

dexTestServer := test.GetDexTestServer(t)
Expand Down Expand Up @@ -293,7 +293,7 @@ func Test_Login_Flow(t *testing.T) {
// Show that SSO login works when no redirect URL is provided, and we fall back to the configured base href for the
// Argo CD instance.

oidcTestServer := test.GetOIDCTestServer(t, false)
oidcTestServer := test.GetOIDCTestServer(t)
t.Cleanup(oidcTestServer.Close)

cdSettings := &settings.ArgoCDSettings{
Expand Down Expand Up @@ -335,7 +335,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
}

func TestClientApp_HandleCallback(t *testing.T) {
oidcTestServer := test.GetOIDCTestServer(t, false)
oidcTestServer := test.GetOIDCTestServer(t)
t.Cleanup(oidcTestServer.Close)

dexTestServer := test.GetDexTestServer(t)
Expand Down Expand Up @@ -416,7 +416,18 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
}

func TestClientAppWithAzureWorkloadIdentity_HandleCallback(t *testing.T) {
oidcTestServer := test.GetOIDCTestServer(t, true)
tokenRequestAssertions := func(r *http.Request) {
err := r.ParseForm()
require.NoError(t, err)

formData := r.Form
clientAssertion := formData.Get("client_assertion")
clientAssertionType := formData.Get("client_assertion_type")
assert.Equal(t, "serviceAccountToken", clientAssertion)
assert.Equal(t, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", clientAssertionType)
}

oidcTestServer := test.GetAzureOIDCTestServer(t, tokenRequestAssertions)
t.Cleanup(oidcTestServer.Close)

dexTestServer := test.GetDexTestServer(t)
Expand All @@ -437,7 +448,8 @@ func TestClientAppWithAzureWorkloadIdentity_HandleCallback(t *testing.T) {
name: Test
issuer: %s
clientID: xxx
useAzureWorkloadIdentity: true
azure:
useWorkloadIdentity: true
skipAudienceCheckWhenTokenHasNoAudience: true
requestedScopes: ["oidc"]`, oidcTestServer.URL),
}
Expand Down
2 changes: 1 addition & 1 deletion util/session/sessionmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ func getKubeClientWithConfig(config map[string]string, secretConfig map[string][
}

func TestSessionManager_VerifyToken(t *testing.T) {
oidcTestServer := utiltest.GetOIDCTestServer(t, false)
oidcTestServer := utiltest.GetOIDCTestServer(t)
t.Cleanup(oidcTestServer.Close)

dexTestServer := utiltest.GetDexTestServer(t)
Expand Down
12 changes: 8 additions & 4 deletions util/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (o *oidcConfig) toExported() *OIDCConfig {
Issuer: o.Issuer,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
UseAzureWorkloadIdentity: o.UseAzureWorkloadIdentity,
Azure: o.Azure,
CLIClientID: o.CLIClientID,
UserInfoPath: o.UserInfoPath,
EnableUserInfoGroups: o.EnableUserInfoGroups,
Expand All @@ -188,7 +188,6 @@ type OIDCConfig struct {
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"clientID,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty"`
CLIClientID string `json:"cliClientID,omitempty"`
EnableUserInfoGroups bool `json:"enableUserInfoGroups,omitempty"`
UserInfoPath string `json:"userInfoPath,omitempty"`
Expand All @@ -199,6 +198,11 @@ type OIDCConfig struct {
RootCA string `json:"rootCA,omitempty"`
EnablePKCEAuthentication bool `json:"enablePKCEAuthentication,omitempty"`
DomainHint string `json:"domainHint,omitempty"`
Azure *AzureOIDCConfig `json:"azure,omitempty"`
}

type AzureOIDCConfig struct {
UseWorkloadIdentity bool `json:"useWorkloadIdentity,omitempty"`
}

// DEPRECATED. Helm repository credentials are now managed using RepoCredentials
Expand Down Expand Up @@ -2012,8 +2016,8 @@ func (a *ArgoCDSettings) OAuth2ClientSecret() string {
}

func (a *ArgoCDSettings) UseAzureWorkloadIdentity() bool {
if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
return oidcConfig.UseAzureWorkloadIdentity
if oidcConfig := a.OIDCConfig(); oidcConfig != nil && oidcConfig.Azure != nil {
return oidcConfig.Azure.UseWorkloadIdentity
}
return false
}
Expand Down
11 changes: 9 additions & 2 deletions util/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1822,14 +1822,21 @@ func TestUseAzureWorkloadIdentity(t *testing.T) {
{
Name: "UseAzureWorkloadIdentity defined and set to true",
Settings: &ArgoCDSettings{
OIDCConfigRAW: "{ \"useAzureWorkloadIdentity\": true }",
OIDCConfigRAW: "{ \"azure\": {\"useWorkloadIdentity\": true }}",
},
ExpectedResult: true,
},
{
Name: "UseAzureWorkloadIdentity defined and set to false",
Settings: &ArgoCDSettings{
OIDCConfigRAW: "{ \"useAzureWorkloadIdentity\": false }",
OIDCConfigRAW: "{ \"azure\": {\"useWorkloadIdentity\": false }}",
},
ExpectedResult: false,
},
{
Name: "UseAzureWorkloadIdentity not defined, with azure key present",
Settings: &ArgoCDSettings{
OIDCConfigRAW: "{ \"azure\": {}}",
},
ExpectedResult: false,
},
Expand Down
30 changes: 16 additions & 14 deletions util/test/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/go-jose/go-jose/v3"
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -150,7 +149,7 @@ func GetDexTestServer(t *testing.T) *httptest.Server {
return ts
}

func oidcMockHandler(t *testing.T, url string, assertAzureWorkloadIdentityAssertions bool) func(http.ResponseWriter, *http.Request) {
func oidcMockHandler(t *testing.T, url string, tokenRequestPreHandler func(r *http.Request)) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -199,17 +198,9 @@ func oidcMockHandler(t *testing.T, url string, assertAzureWorkloadIdentityAssert
_, err = w.Write(out)
require.NoError(t, err)
case "/token":
if assertAzureWorkloadIdentityAssertions {
err := r.ParseForm()
require.NoError(t, err)

formData := r.Form
clientAssertion := formData.Get("client_assertion")
clientAssertionType := formData.Get("client_assertion_type")
assert.Equal(t, "serviceAccountToken", clientAssertion)
assert.Equal(t, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", clientAssertionType)
if tokenRequestPreHandler != nil {
tokenRequestPreHandler(r)
}

response, err := mockTokenEndpointResponse(url)
require.NoError(t, err)
out, err := json.Marshal(response)
Expand All @@ -222,13 +213,24 @@ func oidcMockHandler(t *testing.T, url string, assertAzureWorkloadIdentityAssert
}
}

func GetOIDCTestServer(t *testing.T, assertAzureWorkloadIdentityAssertions bool) *httptest.Server {
func GetOIDCTestServer(t *testing.T) *httptest.Server {
t.Helper()
ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
// Start with a placeholder. We need the server URL before setting up the real handler.
}))
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
oidcMockHandler(t, ts.URL, nil)(w, r)
})
return ts
}

func GetAzureOIDCTestServer(t *testing.T, tokenRequestPreHandler func(r *http.Request)) *httptest.Server {
t.Helper()
ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
// Start with a placeholder. We need the server URL before setting up the real handler.
}))
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
oidcMockHandler(t, ts.URL, assertAzureWorkloadIdentityAssertions)(w, r)
oidcMockHandler(t, ts.URL, tokenRequestPreHandler)(w, r)
})
return ts
}
Expand Down

0 comments on commit 3bf9530

Please sign in to comment.