Skip to content

Commit

Permalink
Merge pull request kubernetes-sigs#6309 from zarvd/cp-release-1.30/fe…
Browse files Browse the repository at this point in the history
…at/cross-tenant/aux-token-provider

[release-1.30] Enable multi-tenant authentication with auxiliary token provider
  • Loading branch information
k8s-ci-robot committed May 28, 2024
2 parents ca54a70 + b6e7a36 commit 6792d47
Show file tree
Hide file tree
Showing 8 changed files with 680 additions and 547 deletions.
157 changes: 0 additions & 157 deletions go.sum

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions pkg/azureclients/armauth/multi_tenant_token_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package armauth

import (
"context"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/go-logr/logr"
)

// MultiTenantTokenProvider is the track1 multi-tenant token provider wrapper for track2 implementation.
type MultiTenantTokenProvider struct {
logger logr.Logger
primaryCredential azcore.TokenCredential
auxiliaryCredentials []azcore.TokenCredential
timeout time.Duration
scope string
}

func NewMultiTenantTokenProvider(
logger logr.Logger,
primaryCredential azcore.TokenCredential,
auxiliaryCredentials []azcore.TokenCredential,
scope string,
) (*MultiTenantTokenProvider, error) {
return &MultiTenantTokenProvider{
logger: logger,
primaryCredential: primaryCredential,
auxiliaryCredentials: auxiliaryCredentials,
timeout: 10 * time.Second,
scope: scope,
}, nil
}

func (p *MultiTenantTokenProvider) PrimaryOAuthToken() string {
p.logger.V(4).Info("Fetching primary oauth token")
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()

token, err := p.primaryCredential.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{p.scope},
})
if err != nil {
p.logger.Error(err, "Failed to fetch primary OAuth token")
return ""
}
return token.Token
}

func (p *MultiTenantTokenProvider) AuxiliaryOAuthTokens() []string {
p.logger.V(4).Info("Fetching auxiliary oauth token", "num-credentials", len(p.auxiliaryCredentials))
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()

var tokens []string
for _, cred := range p.auxiliaryCredentials {
token, err := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{p.scope},
})
if err != nil {
p.logger.Error(err, "Failed to fetch auxiliary OAuth token")
return nil
}

tokens = append(tokens, token.Token)
}

return tokens
}
63 changes: 63 additions & 0 deletions pkg/azureclients/armauth/token_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package armauth

import (
"context"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/go-logr/logr"
)

// TokenProvider is the track1 token provider wrapper for track2 implementation.
type TokenProvider struct {
logger logr.Logger
credential azcore.TokenCredential
timeout time.Duration
scope string
}

func NewTokenProvider(
logger logr.Logger,
credential azcore.TokenCredential,
scope string,
) (*TokenProvider, error) {
return &TokenProvider{
logger: logger,
credential: credential,
timeout: 10 * time.Second,
scope: scope,
}, nil
}

func (p *TokenProvider) OAuthToken() string {
p.logger.V(4).Info("Fetching OAuth token")
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()

token, err := p.credential.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{p.scope},
})
if err != nil {
p.logger.Error(err, "Failed to fetch OAuth token")
return ""
}
p.logger.V(4).Info("Fetched OAuth token successfully", "token", token.Token)
return token.Token
}
32 changes: 17 additions & 15 deletions pkg/provider/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,18 +693,19 @@ func (az *Cloud) InitializeCloudFromConfig(ctx context.Context, config *Config,
return nil
}

var authProvider *azclient.AuthProvider
authProvider, err = azclient.NewAuthProvider(&az.ARMClientConfig, &az.AzureAuthConfig.AzureAuthConfig)
if err != nil {
return err
}
// If uses network resources in different AAD Tenant, then prepare corresponding Service Principal Token for VM/VMSS client and network resources client
multiTenantServicePrincipalToken, networkResourceServicePrincipalToken, err := az.getAuthTokenInMultiTenantEnv(servicePrincipalToken)
multiTenantServicePrincipalToken, networkResourceServicePrincipalToken, err := az.getAuthTokenInMultiTenantEnv(servicePrincipalToken, authProvider)
if err != nil {
return err
}
az.configAzureClients(servicePrincipalToken, multiTenantServicePrincipalToken, networkResourceServicePrincipalToken)

if az.ComputeClientFactory == nil {
authProvider, err := azclient.NewAuthProvider(&az.ARMClientConfig, &az.AzureAuthConfig.AzureAuthConfig)
if err != nil {
return err
}
var cred azcore.TokenCredential
if authProvider.IsMultiTenantModeEnabled() {
multiTenantCred := authProvider.GetMultiTenantIdentity()
Expand Down Expand Up @@ -888,21 +889,21 @@ func (az *Cloud) setLBDefaults(config *Config) error {
return nil
}

func (az *Cloud) getAuthTokenInMultiTenantEnv(_ *adal.ServicePrincipalToken) (*adal.MultiTenantServicePrincipalToken, *adal.ServicePrincipalToken, error) {
func (az *Cloud) getAuthTokenInMultiTenantEnv(_ *adal.ServicePrincipalToken, authProvider *azclient.AuthProvider) (adal.MultitenantOAuthTokenProvider, adal.OAuthTokenProvider, error) {
var err error
var multiTenantServicePrincipalToken *adal.MultiTenantServicePrincipalToken
var networkResourceServicePrincipalToken *adal.ServicePrincipalToken
var multiTenantOAuthToken adal.MultitenantOAuthTokenProvider
var networkResourceServicePrincipalToken adal.OAuthTokenProvider
if az.Config.UsesNetworkResourceInDifferentTenant() {
multiTenantServicePrincipalToken, err = ratelimitconfig.GetMultiTenantServicePrincipalToken(&az.Config.AzureAuthConfig, &az.Environment)
multiTenantOAuthToken, err = ratelimitconfig.GetMultiTenantServicePrincipalToken(&az.Config.AzureAuthConfig, &az.Environment, authProvider)
if err != nil {
return nil, nil, err
}
networkResourceServicePrincipalToken, err = ratelimitconfig.GetNetworkResourceServicePrincipalToken(&az.Config.AzureAuthConfig, &az.Environment)
networkResourceServicePrincipalToken, err = ratelimitconfig.GetNetworkResourceServicePrincipalToken(&az.Config.AzureAuthConfig, &az.Environment, authProvider)
if err != nil {
return nil, nil, err
}
}
return multiTenantServicePrincipalToken, networkResourceServicePrincipalToken, nil
return multiTenantOAuthToken, networkResourceServicePrincipalToken, nil
}

func (az *Cloud) setCloudProviderBackoffDefaults(config *Config) wait.Backoff {
Expand Down Expand Up @@ -947,8 +948,8 @@ func (az *Cloud) setCloudProviderBackoffDefaults(config *Config) wait.Backoff {

func (az *Cloud) configAzureClients(
servicePrincipalToken *adal.ServicePrincipalToken,
multiTenantServicePrincipalToken *adal.MultiTenantServicePrincipalToken,
networkResourceServicePrincipalToken *adal.ServicePrincipalToken) {
multiTenantOAuthTokenProvider adal.MultitenantOAuthTokenProvider,
networkResourceServicePrincipalToken adal.OAuthTokenProvider) {
azClientConfig := az.getAzureClientConfig(servicePrincipalToken)

// Prepare AzureClientConfig for all azure clients
Expand Down Expand Up @@ -981,8 +982,9 @@ func (az *Cloud) configAzureClients(
zoneClientConfig := azClientConfig.WithRateLimiter(nil)

// If uses network resources in different AAD Tenant, update Authorizer for VM/VMSS/VMAS client config
if multiTenantServicePrincipalToken != nil {
multiTenantServicePrincipalTokenAuthorizer := autorest.NewMultiTenantServicePrincipalTokenAuthorizer(multiTenantServicePrincipalToken)
if multiTenantOAuthTokenProvider != nil {
multiTenantServicePrincipalTokenAuthorizer := autorest.NewMultiTenantServicePrincipalTokenAuthorizer(multiTenantOAuthTokenProvider)

vmClientConfig.Authorizer = multiTenantServicePrincipalTokenAuthorizer
vmssClientConfig.Authorizer = multiTenantServicePrincipalTokenAuthorizer
vmssVMClientConfig.Authorizer = multiTenantServicePrincipalTokenAuthorizer
Expand Down
8 changes: 8 additions & 0 deletions pkg/provider/azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,7 @@ func TestNewCloudFromJSON(t *testing.T) {
// Test Backoff and Rate Limit defaults (json)
func TestCloudDefaultConfigFromJSON(t *testing.T) {
config := `{
"tenantId": "--tenant-id--",
"aadClientId": "--aad-client-id--",
"aadClientSecret": "--aad-client-secret--"
}`
Expand All @@ -2099,6 +2100,7 @@ func TestCloudDefaultConfigFromJSON(t *testing.T) {
// Test Backoff and Rate Limit defaults (yaml)
func TestCloudDefaultConfigFromYAML(t *testing.T) {
config := `
tenantId: --tenant-id--
aadClientId: --aad-client-id--
aadClientSecret: --aad-client-secret--
`
Expand Down Expand Up @@ -2294,9 +2296,15 @@ func getCloudFromConfig(t *testing.T, config string) *Cloud {
mockZoneClient := az.ZoneClient.(*mockzoneclient.MockInterface)
mockZoneClient.EXPECT().GetZones(gomock.Any(), gomock.Any()).Return(map[string][]string{"eastus": {"1", "2", "3"}}, nil)

// Skip AAD client cert path validation since it will read the file from the path
aadCertPath := c.AADClientCertPath
c.AADClientCertPath = ""

err = az.InitializeCloudFromConfig(context.Background(), c, false, true)
assert.NoError(t, err)

az.AADClientCertPath = aadCertPath

return az
}

Expand Down
Loading

0 comments on commit 6792d47

Please sign in to comment.