diff --git a/auth/providers/azure/azure.go b/auth/providers/azure/azure.go index 386709990..42b781c17 100644 --- a/auth/providers/azure/azure.go +++ b/auth/providers/azure/azure.go @@ -166,7 +166,7 @@ func New(ctx context.Context, opts Options) (auth.Interface, error) { switch opts.AuthMode { case ClientCredentialAuthMode: - c.graphClient, err = graph.New(c.ClientID, c.ClientSecret, c.TenantID, c.UseGroupUID, cachedAuthInfo.AADEndpoint, cachedAuthInfo.MSGraphHost) + c.graphClient, err = graph.New(c.ClientID, c.ClientSecret, c.ClientAssertion, c.TenantID, c.UseGroupUID, cachedAuthInfo.AADEndpoint, cachedAuthInfo.MSGraphHost) case ARCAuthMode: c.graphClient, err = graph.NewWithARC(c.ClientID, c.ResourceId, c.TenantID, c.AzureRegion) case OBOAuthMode: diff --git a/auth/providers/azure/azure_test.go b/auth/providers/azure/azure_test.go index e56d78679..bb925ca11 100644 --- a/auth/providers/azure/azure_test.go +++ b/auth/providers/azure/azure_test.go @@ -130,7 +130,7 @@ func clientSetup(clientID, clientSecret, tenantID, serverUrl string, useGroupUID ClientID: clientID, }) - c.graphClient, err = graph.TestUserInfo(clientID, clientSecret, serverUrl+"/login", serverUrl+"/api", useGroupUID) + c.graphClient, err = graph.TestUserInfo(clientID, clientSecret, "", serverUrl+"/login", serverUrl+"/api", useGroupUID) if err != nil { return nil, err } diff --git a/auth/providers/azure/graph/clientcredential_tokenprovider.go b/auth/providers/azure/graph/clientcredential_tokenprovider.go index 709734088..051002bf8 100644 --- a/auth/providers/azure/graph/clientcredential_tokenprovider.go +++ b/auth/providers/azure/graph/clientcredential_tokenprovider.go @@ -31,24 +31,26 @@ import ( ) type clientCredentialTokenProvider struct { - name string - client *http.Client - clientID string - clientSecret string - scope string - loginURL string + name string + client *http.Client + clientID string + clientSecret string + clientAssertion string + scope string + loginURL string } // NewClientCredentialTokenProvider returns a TokenProvider that implements OAuth client credential flow on Azure Active Directory // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#get-a-token -func NewClientCredentialTokenProvider(clientID, clientSecret, loginURL, scope string) TokenProvider { +func NewClientCredentialTokenProvider(clientID, clientSecret, clientAssertion, loginURL, scope string) TokenProvider { return &clientCredentialTokenProvider{ - name: "ClientCredentialTokenProvider", - client: httpclient.DefaultHTTPClient, - clientID: clientID, - clientSecret: clientSecret, - scope: scope, - loginURL: loginURL, + name: "ClientCredentialTokenProvider", + client: httpclient.DefaultHTTPClient, + clientID: clientID, + clientAssertion: clientAssertion, + clientSecret: clientSecret, + scope: scope, + loginURL: loginURL, } } @@ -58,9 +60,14 @@ func (u *clientCredentialTokenProvider) Acquire(ctx context.Context, token strin authResp := AuthResponse{} form := url.Values{} form.Set("client_id", u.clientID) - form.Set("client_secret", u.clientSecret) - form.Set("scope", u.scope) + if u.clientAssertion != "" { + form.Set("client_assertion", u.clientAssertion) + form.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") + } else { + form.Set("client_secret", u.clientSecret) + } form.Set("grant_type", "client_credentials") + form.Set("scope", u.scope) req, err := http.NewRequest(http.MethodPost, u.loginURL, strings.NewReader(form.Encode())) if err != nil { diff --git a/auth/providers/azure/graph/graph.go b/auth/providers/azure/graph/graph.go index e20df458d..c10771210 100644 --- a/auth/providers/azure/graph/graph.go +++ b/auth/providers/azure/graph/graph.go @@ -407,11 +407,11 @@ func newUserInfo(tokenProvider TokenProvider, graphURL *url.URL, useGroupUID boo } // New returns a new UserInfo object -func New(clientID, clientSecret, tenantID string, useGroupUID bool, aadEndpoint, msgraphHost string) (*UserInfo, error) { +func New(clientID, clientSecret, clientAssertion, tenantID string, useGroupUID bool, aadEndpoint, msgraphHost string) (*UserInfo, error) { graphEndpoint := "https://" + msgraphHost + "/" graphURL, _ := url.Parse(graphEndpoint + "v1.0") - tokenProvider := NewClientCredentialTokenProvider(clientID, clientSecret, + tokenProvider := NewClientCredentialTokenProvider(clientID, clientSecret, clientAssertion, fmt.Sprintf("%s%s/oauth2/v2.0/token", aadEndpoint, tenantID), fmt.Sprintf("https://%s/.default", msgraphHost)) @@ -456,7 +456,7 @@ func NewWithARC(msiAudience, resourceId, tenantId, region string) (*UserInfo, er return userInfo, nil } -func TestUserInfo(clientID, clientSecret, loginUrl, apiUrl string, useGroupUID bool) (*UserInfo, error) { +func TestUserInfo(clientID, clientSecret, clientAssertion, loginUrl, apiUrl string, useGroupUID bool) (*UserInfo, error) { parsedApi, err := url.Parse(apiUrl) if err != nil { return nil, err @@ -470,7 +470,7 @@ func TestUserInfo(clientID, clientSecret, loginUrl, apiUrl string, useGroupUID b groupsPerCall: expandedGroupsPerCall, useGroupUID: useGroupUID, } - u.tokenProvider = NewClientCredentialTokenProvider(clientID, clientSecret, loginUrl, "") + u.tokenProvider = NewClientCredentialTokenProvider(clientID, clientSecret, clientAssertion, loginUrl, "") if err != nil { return nil, err } diff --git a/auth/providers/azure/graph/graph_test.go b/auth/providers/azure/graph/graph_test.go index d36df156c..b8ad58309 100644 --- a/auth/providers/azure/graph/graph_test.go +++ b/auth/providers/azure/graph/graph_test.go @@ -94,7 +94,7 @@ func getAuthServerAndUserInfo(returnCode int, body, clientID, clientSecret strin headers: http.Header{}, groupsPerCall: expandedGroupsPerCall, } - u.tokenProvider = NewClientCredentialTokenProvider(clientID, clientSecret, ts.URL, "") + u.tokenProvider = NewClientCredentialTokenProvider(clientID, clientSecret, "", ts.URL, "") return ts, u } @@ -140,7 +140,7 @@ func TestLogin(t *testing.T) { headers: http.Header{}, groupsPerCall: expandedGroupsPerCall, } - u.tokenProvider = NewClientCredentialTokenProvider("CIA", "outcome", badURL, "") + u.tokenProvider = NewClientCredentialTokenProvider("CIA", "outcome", "", badURL, "") err := u.RefreshToken(ctx, "") if err == nil { diff --git a/auth/providers/azure/options.go b/auth/providers/azure/options.go index 5e70c0bb9..2d30f74b7 100644 --- a/auth/providers/azure/options.go +++ b/auth/providers/azure/options.go @@ -43,6 +43,7 @@ type Options struct { Environment string ClientID string ClientSecret string + ClientAssertion string TenantID string UseGroupUID bool AuthMode string @@ -60,8 +61,9 @@ type Options struct { func NewOptions() Options { return Options{ - ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), - UseGroupUID: true, + ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), + ClientAssertion: os.Getenv("AZURE_CLIENT_ASSERTION"), + UseGroupUID: true, } } @@ -69,6 +71,7 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.Environment, "azure.environment", o.Environment, "Azure cloud environment") fs.StringVar(&o.ClientID, "azure.client-id", o.ClientID, "MS Graph application client ID to use") fs.StringVar(&o.ClientSecret, "azure.client-secret", o.ClientSecret, "MS Graph application client secret to use") + fs.StringVar(&o.ClientAssertion, "azure.client-assertion", o.ClientAssertion, "MS Graph application client assertion (JWT) to use") fs.StringVar(&o.TenantID, "azure.tenant-id", o.TenantID, "MS Graph application tenant id to use") fs.BoolVar(&o.UseGroupUID, "azure.use-group-uid", o.UseGroupUID, "Use group UID for authentication instead of group display name") fs.StringVar(&o.AuthMode, "azure.auth-mode", "client-credential", "auth mode to call graph api, valid value is either aks, arc, obo, client-credential or passthrough") @@ -99,8 +102,8 @@ func (o *Options) Validate() []error { } if o.AuthMode != AKSAuthMode && o.AuthMode != PassthroughAuthMode && o.AuthMode != ARCAuthMode { - if o.ClientSecret == "" { - errs = append(errs, errors.New("azure.client-secret must be non-empty")) + if o.ClientSecret == "" && o.ClientAssertion == "" { + errs = append(errs, errors.New("azure.client-secret or azure.client-assertion must be non-empty")) } } if o.AuthMode == AKSAuthMode && o.AKSTokenURL == "" { @@ -156,7 +159,8 @@ func (o Options) Apply(d *apps.Deployment) (extraObjs []runtime.Object, err erro Labels: d.Labels, }, Data: map[string][]byte{ - "client-secret": []byte(o.ClientSecret), + "client-secret": []byte(o.ClientSecret), + "client-assertion": []byte(o.ClientAssertion), }, } extraObjs = append(extraObjs, authSecret) @@ -191,6 +195,17 @@ func (o Options) Apply(d *apps.Deployment) (extraObjs []runtime.Object, err erro }, }, }) + container.Env = append(container.Env, core.EnvVar{ + Name: "AZURE_CLIENT_ASSERTION", + ValueFrom: &core.EnvVarSource{ + SecretKeyRef: &core.SecretKeySelector{ + LocalObjectReference: core.LocalObjectReference{ + Name: authSecret.Name, + }, + Key: "client-assertion", + }, + }, + }) args := container.Args if o.Environment != "" { diff --git a/authz/providers/azure/rbac/rbac.go b/authz/providers/azure/rbac/rbac.go index db310f37d..40500fb6f 100644 --- a/authz/providers/azure/rbac/rbac.go +++ b/authz/providers/azure/rbac/rbac.go @@ -197,7 +197,7 @@ func New(opts authzOpts.Options, authopts auth.Options, authzInfo *AuthzInfo) (* case authzOpts.ARCAuthzMode: // if client secret is there check use client credential provider if authopts.ClientSecret != "" { - tokenProvider = graph.NewClientCredentialTokenProvider(authopts.ClientID, authopts.ClientSecret, + tokenProvider = graph.NewClientCredentialTokenProvider(authopts.ClientID, authopts.ClientSecret, authopts.ClientAssertion, fmt.Sprintf("%s%s/oauth2/v2.0/token", authzInfo.AADEndpoint, authopts.TenantID), fmt.Sprintf("%s.default", authzInfo.ARMEndPoint)) } else { diff --git a/server/server.go b/server/server.go index 7ee72a52f..c35713993 100644 --- a/server/server.go +++ b/server/server.go @@ -231,7 +231,7 @@ func (s Server) ListenAndServe() { klog.Fatalf("Authzmode %s is not supported for fetching list of resources", s.AuthzRecommendedOptions.Azure.AuthzMode) } - err := azureutils.SetDiscoverResourcesSettings(clusterType, s.AuthRecommendedOptions.Azure.Environment, s.AuthzRecommendedOptions.Azure.AKSAuthzTokenURL, s.AuthzRecommendedOptions.Azure.KubeConfigFile, s.AuthRecommendedOptions.Azure.TenantID, s.AuthRecommendedOptions.Azure.ClientID, s.AuthRecommendedOptions.Azure.ClientSecret) + err := azureutils.SetDiscoverResourcesSettings(clusterType, s.AuthRecommendedOptions.Azure.Environment, s.AuthzRecommendedOptions.Azure.AKSAuthzTokenURL, s.AuthzRecommendedOptions.Azure.KubeConfigFile, s.AuthRecommendedOptions.Azure.TenantID, s.AuthRecommendedOptions.Azure.ClientID, s.AuthRecommendedOptions.Azure.ClientSecret, s.AuthRecommendedOptions.Azure.ClientAssertion) if err != nil { klog.Fatalf("Failed to create settings for discovering resources. Error:%s", err) } diff --git a/util/azure/utils.go b/util/azure/utils.go index f0de92042..c81c9384d 100644 --- a/util/azure/utils.go +++ b/util/azure/utils.go @@ -120,6 +120,7 @@ type DiscoverResourcesSettings struct { tenantID string clientID string clientSecret string + clientAssertion string } type Display struct { @@ -204,7 +205,7 @@ func ConvertIntToString(number int) string { return strconv.Itoa(number) } -func SetDiscoverResourcesSettings(clusterType string, environment string, loginURL string, kubeconfigFilePath string, tenantID string, clientID string, clientSecret string) error { +func SetDiscoverResourcesSettings(clusterType string, environment string, loginURL string, kubeconfigFilePath string, tenantID string, clientID string, clientSecret string, clientAssertion string) error { if settings == nil { settings = &DiscoverResourcesSettings{ clusterType: clusterType, @@ -213,6 +214,7 @@ func SetDiscoverResourcesSettings(clusterType string, environment string, loginU tenantID: tenantID, clientID: clientID, clientSecret: clientSecret, + clientAssertion: clientAssertion, } env := azure.PublicCloud @@ -437,7 +439,7 @@ func fetchDataActionsList(ctx context.Context) ([]Operation, error) { var token string if settings.clusterType == ConnectedClusters { - tokenProvider := graph.NewClientCredentialTokenProvider(settings.clientID, settings.clientSecret, + tokenProvider := graph.NewClientCredentialTokenProvider(settings.clientID, settings.clientSecret, settings.clientAssertion, fmt.Sprintf("%s%s/oauth2/v2.0/token", settings.environment.ActiveDirectoryEndpoint, settings.tenantID), fmt.Sprintf("%s/.default", settings.environment.ResourceManagerEndpoint))