Skip to content

Commit b38ed04

Browse files
committed
Support cluster using network resources (VNet, LB, IP, etc.) across AAD Tenants.
1 parent fc573f9 commit b38ed04

File tree

21 files changed

+324
-63
lines changed

21 files changed

+324
-63
lines changed

staging/src/k8s.io/legacy-cloud-providers/azure/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ go_library(
8383
"//vendor/github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage:go_default_library",
8484
"//vendor/github.com/Azure/azure-sdk-for-go/storage:go_default_library",
8585
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
86+
"//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library",
8687
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
8788
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
8889
"//vendor/github.com/rubiojr/go-vhd/vhd:go_default_library",

staging/src/k8s.io/legacy-cloud-providers/azure/auth/azure_auth.go

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,20 @@ type AzureAuthConfig struct {
6868
// ResourceManagerEndpoint is the cloud's resource manager endpoint. If set, cloud provider queries this endpoint
6969
// in order to generate an autorest.Environment instance instead of using one of the pre-defined Environments.
7070
ResourceManagerEndpoint string `json:"resourceManagerEndpoint,omitempty" yaml:"resourceManagerEndpoint,omitempty"`
71+
// The AAD Tenant ID for the Subscription that the network resources are deployed in
72+
NetworkResourceTenantID string `json:"networkResourceTenantID,omitempty" yaml:"networkResourceTenantID,omitempty"`
73+
// The ID of the Azure Subscription that the network resources are deployed in
74+
NetworkResourceSubscriptionID string `json:"networkResourceSubscriptionID,omitempty" yaml:"networkResourceSubscriptionID,omitempty"`
7175
}
7276

73-
// GetServicePrincipalToken creates a new service principal token based on the configuration
77+
// GetServicePrincipalToken creates a new service principal token based on the configuration.
78+
//
79+
// By default, the cluster and its network resources are deployed in the same AAD Tenant and Subscription,
80+
// and all azure clients use this method to fetch Service Principal Token.
81+
//
82+
// If NetworkResourceTenantID and NetworkResourceSubscriptionID are specified to have different values than TenantID and SubscriptionID, network resources are deployed in different AAD Tenant and Subscription than those for the cluster,
83+
// than only azure clients except VM/VMSS and network resource ones use this method to fetch Token.
84+
// For tokens for VM/VMSS and network resource ones, please check GetMultiTenantServicePrincipalToken and GetNetworkResourceServicePrincipalToken.
7485
func GetServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (*adal.ServicePrincipalToken, error) {
7586
var tenantID string
7687
if strings.EqualFold(config.IdentitySystem, ADFSIdentitySystem) {
@@ -132,6 +143,75 @@ func GetServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (
132143
return nil, ErrorNoAuth
133144
}
134145

146+
// GetMultiTenantServicePrincipalToken is used when (and only when) NetworkResourceTenantID and NetworkResourceSubscriptionID are specified to have different values than TenantID and SubscriptionID.
147+
//
148+
// In that scenario, network resources are deployed in different AAD Tenant and Subscription than those for the cluster,
149+
// and this method creates a new multi-tenant service principal token based on the configuration.
150+
//
151+
// PrimaryToken of the returned multi-tenant token is for the AAD Tenant specified by TenantID, and AuxiliaryToken of the returned multi-tenant token is for the AAD Tenant specified by NetworkResourceTenantID.
152+
//
153+
// Azure VM/VMSS clients use this multi-tenant token, in order to operate those VM/VMSS in AAD Tenant specified by TenantID, and meanwhile in their payload they are referencing network resources (e.g. Load Balancer, Network Security Group, etc.) in AAD Tenant specified by NetworkResourceTenantID.
154+
func GetMultiTenantServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (*adal.MultiTenantServicePrincipalToken, error) {
155+
err := config.checkConfigWhenNetworkResourceInDifferentTenant()
156+
if err != nil {
157+
return nil, fmt.Errorf("got error(%v) in getting multi-tenant service principal token", err)
158+
}
159+
160+
multiTenantOAuthConfig, err := adal.NewMultiTenantOAuthConfig(
161+
env.ActiveDirectoryEndpoint, config.TenantID, []string{config.NetworkResourceTenantID}, adal.OAuthOptions{})
162+
if err != nil {
163+
return nil, fmt.Errorf("creating the multi-tenant OAuth config: %v", err)
164+
}
165+
166+
if len(config.AADClientSecret) > 0 {
167+
klog.V(2).Infoln("azure: using client_id+client_secret to retrieve multi-tenant access token")
168+
return adal.NewMultiTenantServicePrincipalToken(
169+
multiTenantOAuthConfig,
170+
config.AADClientID,
171+
config.AADClientSecret,
172+
env.ServiceManagementEndpoint)
173+
}
174+
175+
if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
176+
return nil, fmt.Errorf("AAD Application client certificate authentication is not supported in getting multi-tenant service principal token")
177+
}
178+
179+
return nil, ErrorNoAuth
180+
}
181+
182+
// GetNetworkResourceServicePrincipalToken is used when (and only when) NetworkResourceTenantID and NetworkResourceSubscriptionID are specified to have different values than TenantID and SubscriptionID.
183+
//
184+
// In that scenario, network resources are deployed in different AAD Tenant and Subscription than those for the cluster,
185+
// and this method creates a new service principal token for network resources tenant based on the configuration.
186+
//
187+
// Azure network resource (Load Balancer, Public IP, Route Table, Network Security Group and their sub level resources) clients use this multi-tenant token, in order to operate resources in AAD Tenant specified by NetworkResourceTenantID.
188+
func GetNetworkResourceServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (*adal.ServicePrincipalToken, error) {
189+
err := config.checkConfigWhenNetworkResourceInDifferentTenant()
190+
if err != nil {
191+
return nil, fmt.Errorf("got error(%v) in getting network resources service principal token", err)
192+
}
193+
194+
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(env.ActiveDirectoryEndpoint, config.NetworkResourceTenantID, nil)
195+
if err != nil {
196+
return nil, fmt.Errorf("creating the OAuth config for network resources tenant: %v", err)
197+
}
198+
199+
if len(config.AADClientSecret) > 0 {
200+
klog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token for network resources tenant")
201+
return adal.NewServicePrincipalToken(
202+
*oauthConfig,
203+
config.AADClientID,
204+
config.AADClientSecret,
205+
env.ServiceManagementEndpoint)
206+
}
207+
208+
if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
209+
return nil, fmt.Errorf("AAD Application client certificate authentication is not supported in getting network resources service principal token")
210+
}
211+
212+
return nil, ErrorNoAuth
213+
}
214+
135215
// ParseAzureEnvironment returns the azure environment.
136216
// If 'resourceManagerEndpoint' is set, the environment is computed by quering the cloud's resource manager endpoint.
137217
// Otherwise, a pre-defined Environment is looked up by name.
@@ -155,6 +235,16 @@ func ParseAzureEnvironment(cloudName, resourceManagerEndpoint, identitySystem st
155235
return &env, err
156236
}
157237

238+
// UsesNetworkResourceInDifferentTenant determines whether the AzureAuthConfig indicates to use network resources in different AAD Tenant and Subscription than those for the cluster
239+
// Return true only when both NetworkResourceTenantID and NetworkResourceSubscriptionID are specified
240+
// and they are not equals to TenantID and SubscriptionID
241+
func (config *AzureAuthConfig) UsesNetworkResourceInDifferentTenant() bool {
242+
return len(config.NetworkResourceTenantID) > 0 &&
243+
len(config.NetworkResourceSubscriptionID) > 0 &&
244+
!strings.EqualFold(config.NetworkResourceTenantID, config.TenantID) &&
245+
!strings.EqualFold(config.NetworkResourceSubscriptionID, config.SubscriptionID)
246+
}
247+
158248
// decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and
159249
// the private RSA key
160250
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
@@ -181,3 +271,20 @@ func azureStackOverrides(env *azure.Environment, resourceManagerEndpoint, identi
181271
env.ActiveDirectoryEndpoint = strings.TrimSuffix(env.ActiveDirectoryEndpoint, "adfs")
182272
}
183273
}
274+
275+
// checkConfigWhenNetworkResourceInDifferentTenant checks configuration for the scenario of using network resource in different tenant
276+
func (config *AzureAuthConfig) checkConfigWhenNetworkResourceInDifferentTenant() error {
277+
if !config.UsesNetworkResourceInDifferentTenant() {
278+
return fmt.Errorf("NetworkResourceTenantID and NetworkResourceSubscriptionID must be configured")
279+
}
280+
281+
if strings.EqualFold(config.IdentitySystem, ADFSIdentitySystem) {
282+
return fmt.Errorf("ADFS identity system is not supported")
283+
}
284+
285+
if config.UseManagedIdentityExtension {
286+
return fmt.Errorf("managed identity is not supported")
287+
}
288+
289+
return nil
290+
}

staging/src/k8s.io/legacy-cloud-providers/azure/auth/azure_auth_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@ import (
2424
"github.com/stretchr/testify/assert"
2525
)
2626

27+
var (
28+
CrossTenantNetworkResourceNegativeConfig = []*AzureAuthConfig{
29+
{
30+
TenantID: "TenantID",
31+
AADClientID: "AADClientID",
32+
AADClientSecret: "AADClientSecret",
33+
},
34+
{
35+
TenantID: "TenantID",
36+
AADClientID: "AADClientID",
37+
AADClientSecret: "AADClientSecret",
38+
NetworkResourceTenantID: "NetworkResourceTenantID",
39+
NetworkResourceSubscriptionID: "NetworkResourceSubscriptionID",
40+
IdentitySystem: ADFSIdentitySystem,
41+
},
42+
{
43+
TenantID: "TenantID",
44+
AADClientID: "AADClientID",
45+
AADClientSecret: "AADClientSecret",
46+
NetworkResourceTenantID: "NetworkResourceTenantID",
47+
NetworkResourceSubscriptionID: "NetworkResourceSubscriptionID",
48+
UseManagedIdentityExtension: true,
49+
},
50+
}
51+
)
52+
2753
func TestGetServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) {
2854
configs := []*AzureAuthConfig{
2955
{
@@ -106,6 +132,66 @@ func TestGetServicePrincipalToken(t *testing.T) {
106132
assert.Equal(t, token, spt)
107133
}
108134

135+
func TestGetMultiTenantServicePrincipalToken(t *testing.T) {
136+
config := &AzureAuthConfig{
137+
TenantID: "TenantID",
138+
AADClientID: "AADClientID",
139+
AADClientSecret: "AADClientSecret",
140+
NetworkResourceTenantID: "NetworkResourceTenantID",
141+
NetworkResourceSubscriptionID: "NetworkResourceSubscriptionID",
142+
}
143+
env := &azure.PublicCloud
144+
145+
multiTenantToken, err := GetMultiTenantServicePrincipalToken(config, env)
146+
assert.NoError(t, err)
147+
148+
multiTenantOAuthConfig, err := adal.NewMultiTenantOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID, []string{config.NetworkResourceTenantID}, adal.OAuthOptions{})
149+
assert.NoError(t, err)
150+
151+
spt, err := adal.NewMultiTenantServicePrincipalToken(multiTenantOAuthConfig, config.AADClientID, config.AADClientSecret, env.ServiceManagementEndpoint)
152+
assert.NoError(t, err)
153+
154+
assert.Equal(t, multiTenantToken, spt)
155+
}
156+
157+
func TestGetMultiTenantServicePrincipalTokenNegative(t *testing.T) {
158+
env := &azure.PublicCloud
159+
for _, config := range CrossTenantNetworkResourceNegativeConfig {
160+
_, err := GetMultiTenantServicePrincipalToken(config, env)
161+
assert.Error(t, err)
162+
}
163+
}
164+
165+
func TestGetNetworkResourceServicePrincipalToken(t *testing.T) {
166+
config := &AzureAuthConfig{
167+
TenantID: "TenantID",
168+
AADClientID: "AADClientID",
169+
AADClientSecret: "AADClientSecret",
170+
NetworkResourceTenantID: "NetworkResourceTenantID",
171+
NetworkResourceSubscriptionID: "NetworkResourceSubscriptionID",
172+
}
173+
env := &azure.PublicCloud
174+
175+
token, err := GetNetworkResourceServicePrincipalToken(config, env)
176+
assert.NoError(t, err)
177+
178+
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(env.ActiveDirectoryEndpoint, config.NetworkResourceTenantID, nil)
179+
assert.NoError(t, err)
180+
181+
spt, err := adal.NewServicePrincipalToken(*oauthConfig, config.AADClientID, config.AADClientSecret, env.ServiceManagementEndpoint)
182+
assert.NoError(t, err)
183+
184+
assert.Equal(t, token, spt)
185+
}
186+
187+
func TestGetNetworkResourceServicePrincipalTokenNegative(t *testing.T) {
188+
env := &azure.PublicCloud
189+
for _, config := range CrossTenantNetworkResourceNegativeConfig {
190+
_, err := GetNetworkResourceServicePrincipalToken(config, env)
191+
assert.Error(t, err)
192+
}
193+
}
194+
109195
func TestParseAzureEngironment(t *testing.T) {
110196
cases := []struct {
111197
cloudName string

0 commit comments

Comments
 (0)