mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 23:19:39 -05:00
bootstrapper: make Azure auth method configurable on cluster init (#1346)
* bootstrapper: make Azure auth method configurable on cluster init * azure: convert uami resource ID to clientID Co-authored-by: 3u13r <lc@edgeless.systems>
This commit is contained in:
parent
5cb1899c27
commit
d15968bed7
@ -13,7 +13,6 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//bootstrapper/internal/kubernetes/k8sapi",
|
"//bootstrapper/internal/kubernetes/k8sapi",
|
||||||
"//bootstrapper/internal/kubernetes/kubewaiter",
|
"//bootstrapper/internal/kubernetes/kubewaiter",
|
||||||
"//internal/cloud/azureshared",
|
|
||||||
"//internal/cloud/cloudprovider",
|
"//internal/cloud/cloudprovider",
|
||||||
"//internal/cloud/gcpshared",
|
"//internal/cloud/gcpshared",
|
||||||
"//internal/cloud/metadata",
|
"//internal/cloud/metadata",
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/k8sapi"
|
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/k8sapi"
|
||||||
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/kubewaiter"
|
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/kubewaiter"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
|
||||||
@ -477,24 +476,6 @@ func (k *KubeWrapper) setupExtraVals(ctx context.Context, serviceConfig constell
|
|||||||
"subnetworkPodCIDR": serviceConfig.subnetworkPodCIDR,
|
"subnetworkPodCIDR": serviceConfig.subnetworkPodCIDR,
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
creds, err := azureshared.ApplicationCredentialsFromURI(serviceConfig.cloudServiceAccountURI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extraVals["autoscaler"] = map[string]any{
|
|
||||||
"Azure": map[string]any{
|
|
||||||
"clientID": creds.AppClientID,
|
|
||||||
"clientSecret": creds.ClientSecretValue,
|
|
||||||
"resourceGroup": resourceGroup,
|
|
||||||
"subscriptionID": subscriptionID,
|
|
||||||
"tenantID": creds.TenantID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case cloudprovider.OpenStack:
|
case cloudprovider.OpenStack:
|
||||||
creds, err := openstack.AccountKeyFromURI(serviceConfig.cloudServiceAccountURI)
|
creds, err := openstack.AccountKeyFromURI(serviceConfig.cloudServiceAccountURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -451,6 +451,9 @@ func (i *initCmd) getMarshaledServiceAccountURI(provider cloudprovider.Provider,
|
|||||||
AppClientID: config.Provider.Azure.AppClientID,
|
AppClientID: config.Provider.Azure.AppClientID,
|
||||||
ClientSecretValue: config.Provider.Azure.ClientSecretValue,
|
ClientSecretValue: config.Provider.Azure.ClientSecretValue,
|
||||||
Location: config.Provider.Azure.Location,
|
Location: config.Provider.Azure.Location,
|
||||||
|
// TODO(malt3): Switch preferred auth method to uami as planned by AB#2961
|
||||||
|
PreferredAuthMethod: azureshared.AuthMethodServicePrincipal,
|
||||||
|
UamiResourceID: config.Provider.Azure.UserAssignedIdentity,
|
||||||
}
|
}
|
||||||
return creds.ToCloudServiceAccountURI(), nil
|
return creds.ToCloudServiceAccountURI(), nil
|
||||||
|
|
||||||
|
@ -181,7 +181,6 @@ go_library(
|
|||||||
"charts/edgeless/constellation-services/charts/autoscaler/Chart.yaml",
|
"charts/edgeless/constellation-services/charts/autoscaler/Chart.yaml",
|
||||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/aws-deployment.yaml",
|
"charts/edgeless/constellation-services/charts/autoscaler/templates/aws-deployment.yaml",
|
||||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/azure-deployment.yaml",
|
"charts/edgeless/constellation-services/charts/autoscaler/templates/azure-deployment.yaml",
|
||||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/azure-secret.yaml",
|
|
||||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/clusterrole.yaml",
|
"charts/edgeless/constellation-services/charts/autoscaler/templates/clusterrole.yaml",
|
||||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/clusterrolebinding.yaml",
|
"charts/edgeless/constellation-services/charts/autoscaler/templates/clusterrolebinding.yaml",
|
||||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/gcp-deployment.yaml",
|
"charts/edgeless/constellation-services/charts/autoscaler/templates/gcp-deployment.yaml",
|
||||||
|
@ -24,43 +24,25 @@ spec:
|
|||||||
- name: cluster-autoscaler
|
- name: cluster-autoscaler
|
||||||
image: {{ .Values.image | quote }}
|
image: {{ .Values.image | quote }}
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- ./cluster-autoscaler
|
||||||
|
args:
|
||||||
|
- --cloud-provider=azure
|
||||||
|
- --cloud-config=/etc/azure/azure.json
|
||||||
|
- --logtostderr=true
|
||||||
|
- --namespace=kube-system
|
||||||
|
- --stderrthreshold=info
|
||||||
|
- --v=2
|
||||||
|
volumeMounts:
|
||||||
|
- name: azureconfig
|
||||||
|
mountPath: /etc/azure
|
||||||
|
readOnly: true
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health-check
|
path: /health-check
|
||||||
port: 8085
|
port: 8085
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8085
|
- containerPort: 8085
|
||||||
env:
|
|
||||||
- name: ARM_SUBSCRIPTION_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: SubscriptionID
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_RESOURCE_GROUP
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: ResourceGroup
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_TENANT_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: TenantID
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_CLIENT_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: ClientID
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_CLIENT_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: ClientSecret
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_VM_TYPE
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: VMType
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
resources: {}
|
resources: {}
|
||||||
dnsPolicy: ClusterFirst
|
dnsPolicy: ClusterFirst
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
@ -78,4 +60,8 @@ spec:
|
|||||||
key: node.cloudprovider.kubernetes.io/uninitialized
|
key: node.cloudprovider.kubernetes.io/uninitialized
|
||||||
operator: Equal
|
operator: Equal
|
||||||
value: "true"
|
value: "true"
|
||||||
|
volumes:
|
||||||
|
- name: azureconfig
|
||||||
|
secret:
|
||||||
|
secretName: azureconfig
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
{{- if eq .Values.csp "Azure" -}}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
namespace: {{ .Release.Namespace }}
|
|
||||||
data:
|
|
||||||
ClientID: {{ .Values.Azure.clientID | b64enc }}
|
|
||||||
ClientSecret: {{ .Values.Azure.clientSecret | b64enc }}
|
|
||||||
ResourceGroup: {{ .Values.Azure.resourceGroup | b64enc }}
|
|
||||||
SubscriptionID: {{ .Values.Azure.subscriptionID | b64enc }}
|
|
||||||
TenantID: {{ .Values.Azure.tenantID | b64enc }}
|
|
||||||
{{/* b64encode("vmss") */}}
|
|
||||||
VMType: dm1zcw==
|
|
||||||
{{- end -}}
|
|
@ -16,60 +16,12 @@
|
|||||||
"examples": [
|
"examples": [
|
||||||
"registry.k8s.io/autoscaling/cluster-autoscaler:v1.23.1"
|
"registry.k8s.io/autoscaling/cluster-autoscaler:v1.23.1"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"Azure": {
|
|
||||||
"description": "Config values required for deployment on Azure",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"clientID": {
|
|
||||||
"description": "Client ID of the service account used to access the Azure API.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"clientSecret": {
|
|
||||||
"description": "Secret of the service account used to access the Azure API.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"resourceGroup": {
|
|
||||||
"description": "Resource group in which the cluster is running.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"subscriptionID": {
|
|
||||||
"description": "Subscription ID of the Azure subscription.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tenantID": {
|
|
||||||
"description": "Tenant ID of the Azure subscription.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"clientID",
|
|
||||||
"clientSecret",
|
|
||||||
"resourceGroup",
|
|
||||||
"subscriptionID",
|
|
||||||
"tenantID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"csp",
|
"csp",
|
||||||
"image"
|
"image"
|
||||||
],
|
],
|
||||||
"if": {
|
|
||||||
"properties": {
|
|
||||||
"csp": {
|
|
||||||
"const": "Azure"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"csp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": {
|
|
||||||
"required": [
|
|
||||||
"Azure"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title": "Values",
|
"title": "Values",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
|
@ -23,43 +23,25 @@ spec:
|
|||||||
- name: cluster-autoscaler
|
- name: cluster-autoscaler
|
||||||
image: autoscalerImage
|
image: autoscalerImage
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- ./cluster-autoscaler
|
||||||
|
args:
|
||||||
|
- --cloud-provider=azure
|
||||||
|
- --cloud-config=/etc/azure/azure.json
|
||||||
|
- --logtostderr=true
|
||||||
|
- --namespace=kube-system
|
||||||
|
- --stderrthreshold=info
|
||||||
|
- --v=2
|
||||||
|
volumeMounts:
|
||||||
|
- name: azureconfig
|
||||||
|
mountPath: /etc/azure
|
||||||
|
readOnly: true
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health-check
|
path: /health-check
|
||||||
port: 8085
|
port: 8085
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8085
|
- containerPort: 8085
|
||||||
env:
|
|
||||||
- name: ARM_SUBSCRIPTION_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: SubscriptionID
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_RESOURCE_GROUP
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: ResourceGroup
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_TENANT_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: TenantID
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_CLIENT_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: ClientID
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_CLIENT_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: ClientSecret
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
- name: ARM_VM_TYPE
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: VMType
|
|
||||||
name: cluster-autoscaler-azure
|
|
||||||
resources: {}
|
resources: {}
|
||||||
dnsPolicy: ClusterFirst
|
dnsPolicy: ClusterFirst
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
@ -77,3 +59,7 @@ spec:
|
|||||||
key: node.cloudprovider.kubernetes.io/uninitialized
|
key: node.cloudprovider.kubernetes.io/uninitialized
|
||||||
operator: Equal
|
operator: Equal
|
||||||
value: "true"
|
value: "true"
|
||||||
|
volumes:
|
||||||
|
- name: azureconfig
|
||||||
|
secret:
|
||||||
|
secretName: azureconfig
|
||||||
|
@ -125,19 +125,30 @@ func (c *Cloud) GetCCMConfig(ctx context.Context, providerID string, cloudServic
|
|||||||
return nil, fmt.Errorf("could not dereference load balancer name")
|
return nil, fmt.Errorf("could not dereference load balancer name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uamiClientID string
|
||||||
|
useManagedIdentityExtension := creds.PreferredAuthMethod == azureshared.AuthMethodUserAssignedIdentity
|
||||||
|
if useManagedIdentityExtension {
|
||||||
|
uamiClientID, err = c.getUAMIClientIDFromURI(ctx, providerID, creds.UamiResourceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("retrieving user-assigned managed identity client ID: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config := cloudConfig{
|
config := cloudConfig{
|
||||||
Cloud: "AzurePublicCloud",
|
Cloud: "AzurePublicCloud",
|
||||||
TenantID: creds.TenantID,
|
TenantID: creds.TenantID,
|
||||||
SubscriptionID: subscriptionID,
|
SubscriptionID: subscriptionID,
|
||||||
ResourceGroup: resourceGroup,
|
ResourceGroup: resourceGroup,
|
||||||
LoadBalancerSku: "standard",
|
LoadBalancerSku: "standard",
|
||||||
SecurityGroupName: securityGroupName,
|
SecurityGroupName: securityGroupName,
|
||||||
LoadBalancerName: *loadBalancer.Name,
|
LoadBalancerName: *loadBalancer.Name,
|
||||||
UseInstanceMetadata: true,
|
UseInstanceMetadata: true,
|
||||||
VMType: "vmss",
|
VMType: "vmss",
|
||||||
Location: creds.Location,
|
Location: creds.Location,
|
||||||
AADClientID: creds.AppClientID,
|
UseManagedIdentityExtension: useManagedIdentityExtension,
|
||||||
AADClientSecret: creds.ClientSecretValue,
|
UserAssignedIdentityID: uamiClientID,
|
||||||
|
AADClientID: creds.AppClientID,
|
||||||
|
AADClientSecret: creds.ClientSecretValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(config)
|
return json.Marshal(config)
|
||||||
@ -304,6 +315,24 @@ func (c *Cloud) getInstance(ctx context.Context, providerID string) (metadata.In
|
|||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cloud) getUAMIClientIDFromURI(ctx context.Context, providerID, resourceID string) (string, error) {
|
||||||
|
// userAssignedIdentityURI := "/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identity-name}"
|
||||||
|
_, resourceGroup, scaleSet, instanceID, err := azureshared.ScaleSetInformationFromProviderID(providerID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid provider ID: %w", err)
|
||||||
|
}
|
||||||
|
vmResp, err := c.scaleSetsVMAPI.Get(ctx, resourceGroup, scaleSet, instanceID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("retrieving instance: %w", err)
|
||||||
|
}
|
||||||
|
for rID, v := range vmResp.Identity.UserAssignedIdentities {
|
||||||
|
if rID == resourceID {
|
||||||
|
return *v.ClientID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no user assinged identity found for resource ID %s", resourceID)
|
||||||
|
}
|
||||||
|
|
||||||
// getNetworkSecurityGroupName returns the security group name of the resource group.
|
// getNetworkSecurityGroupName returns the security group name of the resource group.
|
||||||
func (c *Cloud) getNetworkSecurityGroupName(ctx context.Context, resourceGroup, uid string) (string, error) {
|
func (c *Cloud) getNetworkSecurityGroupName(ctx context.Context, resourceGroup, uid string) (string, error) {
|
||||||
pager := c.secGroupAPI.NewListPager(resourceGroup, nil)
|
pager := c.secGroupAPI.NewListPager(resourceGroup, nil)
|
||||||
@ -383,23 +412,25 @@ func (c *Cloud) getVMInterfaces(ctx context.Context, vm armcompute.VirtualMachin
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cloudConfig struct {
|
type cloudConfig struct {
|
||||||
Cloud string `json:"cloud,omitempty"`
|
Cloud string `json:"cloud,omitempty"`
|
||||||
TenantID string `json:"tenantId,omitempty"`
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||||
ResourceGroup string `json:"resourceGroup,omitempty"`
|
ResourceGroup string `json:"resourceGroup,omitempty"`
|
||||||
Location string `json:"location,omitempty"`
|
Location string `json:"location,omitempty"`
|
||||||
SubnetName string `json:"subnetName,omitempty"`
|
SubnetName string `json:"subnetName,omitempty"`
|
||||||
SecurityGroupName string `json:"securityGroupName,omitempty"`
|
SecurityGroupName string `json:"securityGroupName,omitempty"`
|
||||||
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
|
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
|
||||||
LoadBalancerName string `json:"loadBalancerName,omitempty"`
|
LoadBalancerName string `json:"loadBalancerName,omitempty"`
|
||||||
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
|
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
|
||||||
VNetName string `json:"vnetName,omitempty"`
|
VNetName string `json:"vnetName,omitempty"`
|
||||||
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
|
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
|
||||||
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`
|
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`
|
||||||
UseInstanceMetadata bool `json:"useInstanceMetadata,omitempty"`
|
UseInstanceMetadata bool `json:"useInstanceMetadata,omitempty"`
|
||||||
VMType string `json:"vmType,omitempty"`
|
VMType string `json:"vmType,omitempty"`
|
||||||
AADClientID string `json:"aadClientId,omitempty"`
|
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension,omitempty"`
|
||||||
AADClientSecret string `json:"aadClientSecret,omitempty"`
|
UserAssignedIdentityID string `json:"userAssignedIdentityID,omitempty"`
|
||||||
|
AADClientID string `json:"aadClientId,omitempty"`
|
||||||
|
AADClientSecret string `json:"aadClientSecret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertToInstanceMetadata converts a armcomputev2.VirtualMachineScaleSetVM to a metadata.InstanceMetadata.
|
// convertToInstanceMetadata converts a armcomputev2.VirtualMachineScaleSetVM to a metadata.InstanceMetadata.
|
||||||
|
@ -52,10 +52,13 @@ func TestGetCCMConfig(t *testing.T) {
|
|||||||
Name: to.Ptr("security-group"),
|
Name: to.Ptr("security-group"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uamiClientID := "uami-client-id"
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
imdsAPI imdsAPI
|
imdsAPI imdsAPI
|
||||||
loadBalancerAPI loadBalancerAPI
|
loadBalancerAPI loadBalancerAPI
|
||||||
secGroupAPI securityGroupsAPI
|
secGroupAPI securityGroupsAPI
|
||||||
|
scaleSetsVMAPI virtualMachineScaleSetVMsAPI
|
||||||
providerID string
|
providerID string
|
||||||
cloudServiceAccountURI string
|
cloudServiceAccountURI string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
@ -75,8 +78,50 @@ func TestGetCCMConfig(t *testing.T) {
|
|||||||
list: []armnetwork.SecurityGroup{goodSecurityGroup},
|
list: []armnetwork.SecurityGroup{goodSecurityGroup},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scaleSetsVMAPI: &stubVirtualMachineScaleSetVMsAPI{
|
||||||
|
getVM: armcompute.VirtualMachineScaleSetVM{
|
||||||
|
Identity: &armcompute.VirtualMachineIdentity{
|
||||||
|
UserAssignedIdentities: map[string]*armcompute.UserAssignedIdentitiesValue{
|
||||||
|
"subscriptions/9b352db0-82af-408c-a02c-36fbffbf7015/resourceGroups/resourceGroupName/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UAMIName": {ClientID: &uamiClientID},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set/virtualMachines/0",
|
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set/virtualMachines/0",
|
||||||
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=westeurope",
|
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=westeurope&preferred_auth_method=userassignedidentity&uami_resource_id=subscriptions%2F9b352db0-82af-408c-a02c-36fbffbf7015%2FresourceGroups%2FresourceGroupName%2Fproviders%2FMicrosoft.ManagedIdentity%2FuserAssignedIdentities%2FUAMIName",
|
||||||
|
wantConfig: cloudConfig{
|
||||||
|
Cloud: "AzurePublicCloud",
|
||||||
|
TenantID: "tenant-id",
|
||||||
|
SubscriptionID: "subscription-id",
|
||||||
|
ResourceGroup: "resource-group",
|
||||||
|
LoadBalancerSku: "standard",
|
||||||
|
SecurityGroupName: "security-group",
|
||||||
|
LoadBalancerName: "load-balancer",
|
||||||
|
UseInstanceMetadata: true,
|
||||||
|
UseManagedIdentityExtension: true,
|
||||||
|
UserAssignedIdentityID: uamiClientID,
|
||||||
|
VMType: "vmss",
|
||||||
|
Location: "westeurope",
|
||||||
|
AADClientID: "client-id",
|
||||||
|
AADClientSecret: "client-secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"no app registration": {
|
||||||
|
imdsAPI: &stubIMDSAPI{
|
||||||
|
uidVal: "uid",
|
||||||
|
},
|
||||||
|
loadBalancerAPI: &stubLoadBalancersAPI{
|
||||||
|
pager: &stubLoadBalancersClientListPager{
|
||||||
|
list: []armnetwork.LoadBalancer{goodLB},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secGroupAPI: &stubSecurityGroupsAPI{
|
||||||
|
pager: &stubSecurityGroupsClientListPager{
|
||||||
|
list: []armnetwork.SecurityGroup{goodSecurityGroup},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set/virtualMachines/0",
|
||||||
|
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&location=westeurope",
|
||||||
wantConfig: cloudConfig{
|
wantConfig: cloudConfig{
|
||||||
Cloud: "AzurePublicCloud",
|
Cloud: "AzurePublicCloud",
|
||||||
TenantID: "tenant-id",
|
TenantID: "tenant-id",
|
||||||
@ -88,8 +133,6 @@ func TestGetCCMConfig(t *testing.T) {
|
|||||||
UseInstanceMetadata: true,
|
UseInstanceMetadata: true,
|
||||||
VMType: "vmss",
|
VMType: "vmss",
|
||||||
Location: "westeurope",
|
Location: "westeurope",
|
||||||
AADClientID: "client-id",
|
|
||||||
AADClientSecret: "client-secret",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"missing UID tag": {
|
"missing UID tag": {
|
||||||
@ -303,6 +346,7 @@ func TestGetCCMConfig(t *testing.T) {
|
|||||||
imds: tc.imdsAPI,
|
imds: tc.imdsAPI,
|
||||||
loadBalancerAPI: tc.loadBalancerAPI,
|
loadBalancerAPI: tc.loadBalancerAPI,
|
||||||
secGroupAPI: tc.secGroupAPI,
|
secGroupAPI: tc.secGroupAPI,
|
||||||
|
scaleSetsVMAPI: tc.scaleSetsVMAPI,
|
||||||
}
|
}
|
||||||
config, err := cloud.GetCCMConfig(context.Background(), tc.providerID, tc.cloudServiceAccountURI)
|
config, err := cloud.GetCCMConfig(context.Background(), tc.providerID, tc.cloudServiceAccountURI)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
|
@ -5,6 +5,7 @@ go_library(
|
|||||||
name = "azureshared",
|
name = "azureshared",
|
||||||
srcs = [
|
srcs = [
|
||||||
"appcredentials.go",
|
"appcredentials.go",
|
||||||
|
"authmethod_string.go",
|
||||||
"azureshared.go",
|
"azureshared.go",
|
||||||
"metadata.go",
|
"metadata.go",
|
||||||
],
|
],
|
||||||
|
@ -9,15 +9,19 @@ package azureshared
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ApplicationCredentials is a set of Azure AD application credentials.
|
// ApplicationCredentials is a set of Azure API credentials.
|
||||||
|
// It can contain a client secret and carries the preferred authentication method.
|
||||||
// It is the equivalent of a service account key in other cloud providers.
|
// It is the equivalent of a service account key in other cloud providers.
|
||||||
type ApplicationCredentials struct {
|
type ApplicationCredentials struct {
|
||||||
TenantID string
|
TenantID string
|
||||||
AppClientID string
|
AppClientID string
|
||||||
ClientSecretValue string
|
ClientSecretValue string
|
||||||
Location string
|
Location string
|
||||||
|
UamiResourceID string
|
||||||
|
PreferredAuthMethod AuthMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplicationCredentialsFromURI converts a cloudServiceAccountURI into Azure ApplicationCredentials.
|
// ApplicationCredentialsFromURI converts a cloudServiceAccountURI into Azure ApplicationCredentials.
|
||||||
@ -33,11 +37,14 @@ func ApplicationCredentialsFromURI(cloudServiceAccountURI string) (ApplicationCr
|
|||||||
return ApplicationCredentials{}, fmt.Errorf("invalid service account URI: invalid host: %s", uri.Host)
|
return ApplicationCredentials{}, fmt.Errorf("invalid service account URI: invalid host: %s", uri.Host)
|
||||||
}
|
}
|
||||||
query := uri.Query()
|
query := uri.Query()
|
||||||
|
preferredAuthMethod := FromString(query.Get("preferred_auth_method"))
|
||||||
return ApplicationCredentials{
|
return ApplicationCredentials{
|
||||||
TenantID: query.Get("tenant_id"),
|
TenantID: query.Get("tenant_id"),
|
||||||
AppClientID: query.Get("client_id"),
|
AppClientID: query.Get("client_id"),
|
||||||
ClientSecretValue: query.Get("client_secret"),
|
ClientSecretValue: query.Get("client_secret"),
|
||||||
Location: query.Get("location"),
|
Location: query.Get("location"),
|
||||||
|
UamiResourceID: query.Get("uami_resource_id"),
|
||||||
|
PreferredAuthMethod: preferredAuthMethod,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,9 +52,19 @@ func ApplicationCredentialsFromURI(cloudServiceAccountURI string) (ApplicationCr
|
|||||||
func (c ApplicationCredentials) ToCloudServiceAccountURI() string {
|
func (c ApplicationCredentials) ToCloudServiceAccountURI() string {
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Add("tenant_id", c.TenantID)
|
query.Add("tenant_id", c.TenantID)
|
||||||
query.Add("client_id", c.AppClientID)
|
|
||||||
query.Add("client_secret", c.ClientSecretValue)
|
|
||||||
query.Add("location", c.Location)
|
query.Add("location", c.Location)
|
||||||
|
if c.AppClientID != "" {
|
||||||
|
query.Add("client_id", c.AppClientID)
|
||||||
|
}
|
||||||
|
if c.ClientSecretValue != "" {
|
||||||
|
query.Add("client_secret", c.ClientSecretValue)
|
||||||
|
}
|
||||||
|
if c.UamiResourceID != "" {
|
||||||
|
query.Add("uami_resource_id", c.UamiResourceID)
|
||||||
|
}
|
||||||
|
if c.PreferredAuthMethod != AuthMethodUnknown {
|
||||||
|
query.Add("preferred_auth_method", c.PreferredAuthMethod.String())
|
||||||
|
}
|
||||||
uri := url.URL{
|
uri := url.URL{
|
||||||
Scheme: "serviceaccount",
|
Scheme: "serviceaccount",
|
||||||
Host: "azure",
|
Host: "azure",
|
||||||
@ -55,3 +72,29 @@ func (c ApplicationCredentials) ToCloudServiceAccountURI() string {
|
|||||||
}
|
}
|
||||||
return uri.String()
|
return uri.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:generate stringer -type=AuthMethod -trimprefix=AuthMethod
|
||||||
|
|
||||||
|
// AuthMethod is the authentication method used for the Azure API.
|
||||||
|
type AuthMethod uint32
|
||||||
|
|
||||||
|
// FromString converts a string into an AuthMethod.
|
||||||
|
func FromString(s string) AuthMethod {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case strings.ToLower(AuthMethodServicePrincipal.String()):
|
||||||
|
return AuthMethodServicePrincipal
|
||||||
|
case strings.ToLower(AuthMethodUserAssignedIdentity.String()):
|
||||||
|
return AuthMethodUserAssignedIdentity
|
||||||
|
default:
|
||||||
|
return AuthMethodUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthMethodUnknown is default value for AuthMethod.
|
||||||
|
AuthMethodUnknown AuthMethod = iota
|
||||||
|
// AuthMethodServicePrincipal uses a client ID and secret.
|
||||||
|
AuthMethodServicePrincipal
|
||||||
|
// AuthMethodUserAssignedIdentity uses a user assigned identity.
|
||||||
|
AuthMethodUserAssignedIdentity
|
||||||
|
)
|
||||||
|
@ -21,6 +21,20 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestApplicationCredentialsFromURI(t *testing.T) {
|
func TestApplicationCredentialsFromURI(t *testing.T) {
|
||||||
creds := ApplicationCredentials{
|
creds := ApplicationCredentials{
|
||||||
|
TenantID: "tenant-id",
|
||||||
|
AppClientID: "client-id",
|
||||||
|
ClientSecretValue: "client-secret",
|
||||||
|
Location: "location",
|
||||||
|
UamiResourceID: "subscriptions/9b352db0-82af-408c-a02c-36fbffbf7015/resourceGroups/resourceGroupName/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UAMIName",
|
||||||
|
PreferredAuthMethod: AuthMethodServicePrincipal,
|
||||||
|
}
|
||||||
|
credsWithoutSecret := ApplicationCredentials{
|
||||||
|
TenantID: "tenant-id",
|
||||||
|
Location: "location",
|
||||||
|
UamiResourceID: "subscriptions/9b352db0-82af-408c-a02c-36fbffbf7015/resourceGroups/resourceGroupName/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UAMIName",
|
||||||
|
PreferredAuthMethod: AuthMethodUserAssignedIdentity,
|
||||||
|
}
|
||||||
|
credsWithoutPreferrredAuthMethod := ApplicationCredentials{
|
||||||
TenantID: "tenant-id",
|
TenantID: "tenant-id",
|
||||||
AppClientID: "client-id",
|
AppClientID: "client-id",
|
||||||
ClientSecretValue: "client-secret",
|
ClientSecretValue: "client-secret",
|
||||||
@ -32,9 +46,17 @@ func TestApplicationCredentialsFromURI(t *testing.T) {
|
|||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"getApplicationCredentials works": {
|
"getApplicationCredentials works": {
|
||||||
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
|
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location&preferred_auth_method=serviceprincipal&uami_resource_id=subscriptions%2F9b352db0-82af-408c-a02c-36fbffbf7015%2FresourceGroups%2FresourceGroupName%2Fproviders%2FMicrosoft.ManagedIdentity%2FuserAssignedIdentities%2FUAMIName",
|
||||||
wantCreds: creds,
|
wantCreds: creds,
|
||||||
},
|
},
|
||||||
|
"can parse URI without app registration / secret": {
|
||||||
|
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&location=location&preferred_auth_method=userassignedidentity&uami_resource_id=subscriptions%2F9b352db0-82af-408c-a02c-36fbffbf7015%2FresourceGroups%2FresourceGroupName%2Fproviders%2FMicrosoft.ManagedIdentity%2FuserAssignedIdentities%2FUAMIName",
|
||||||
|
wantCreds: credsWithoutSecret,
|
||||||
|
},
|
||||||
|
"can parse URI without preferred auth method": {
|
||||||
|
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
|
||||||
|
wantCreds: credsWithoutPreferrredAuthMethod,
|
||||||
|
},
|
||||||
"invalid URI fails": {
|
"invalid URI fails": {
|
||||||
cloudServiceAccountURI: "\x00",
|
cloudServiceAccountURI: "\x00",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -66,25 +88,66 @@ func TestApplicationCredentialsFromURI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestToCloudServiceAccountURI(t *testing.T) {
|
func TestToCloudServiceAccountURI(t *testing.T) {
|
||||||
assert := assert.New(t)
|
testCases := map[string]struct {
|
||||||
require := require.New(t)
|
credentials ApplicationCredentials
|
||||||
key := ApplicationCredentials{
|
wantURLValues url.Values
|
||||||
TenantID: "tenant-id",
|
}{
|
||||||
AppClientID: "client-id",
|
"client id and secret without preferred auth method": {
|
||||||
ClientSecretValue: "client-secret",
|
credentials: ApplicationCredentials{
|
||||||
Location: "location",
|
TenantID: "tenant-id",
|
||||||
|
AppClientID: "client-id",
|
||||||
|
ClientSecretValue: "client-secret",
|
||||||
|
Location: "location",
|
||||||
|
},
|
||||||
|
wantURLValues: url.Values{
|
||||||
|
"tenant_id": []string{"tenant-id"},
|
||||||
|
"client_id": []string{"client-id"},
|
||||||
|
"client_secret": []string{"client-secret"},
|
||||||
|
"location": []string{"location"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"client id and secret with preferred auth method": {
|
||||||
|
credentials: ApplicationCredentials{
|
||||||
|
TenantID: "tenant-id",
|
||||||
|
AppClientID: "client-id",
|
||||||
|
ClientSecretValue: "client-secret",
|
||||||
|
Location: "location",
|
||||||
|
PreferredAuthMethod: AuthMethodServicePrincipal,
|
||||||
|
},
|
||||||
|
wantURLValues: url.Values{
|
||||||
|
"tenant_id": []string{"tenant-id"},
|
||||||
|
"client_id": []string{"client-id"},
|
||||||
|
"client_secret": []string{"client-secret"},
|
||||||
|
"location": []string{"location"},
|
||||||
|
"preferred_auth_method": []string{"ServicePrincipal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"only preferred auth method": {
|
||||||
|
credentials: ApplicationCredentials{
|
||||||
|
TenantID: "tenant-id",
|
||||||
|
Location: "location",
|
||||||
|
PreferredAuthMethod: AuthMethodUserAssignedIdentity,
|
||||||
|
},
|
||||||
|
wantURLValues: url.Values{
|
||||||
|
"tenant_id": []string{"tenant-id"},
|
||||||
|
"location": []string{"location"},
|
||||||
|
"preferred_auth_method": []string{"UserAssignedIdentity"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudServiceAccountURI := key.ToCloudServiceAccountURI()
|
for name, tc := range testCases {
|
||||||
uri, err := url.Parse(cloudServiceAccountURI)
|
t.Run(name, func(t *testing.T) {
|
||||||
require.NoError(err)
|
assert := assert.New(t)
|
||||||
query := uri.Query()
|
require := require.New(t)
|
||||||
assert.Equal("serviceaccount", uri.Scheme)
|
|
||||||
assert.Equal("azure", uri.Host)
|
cloudServiceAccountURI := tc.credentials.ToCloudServiceAccountURI()
|
||||||
assert.Equal(url.Values{
|
uri, err := url.Parse(cloudServiceAccountURI)
|
||||||
"tenant_id": []string{"tenant-id"},
|
require.NoError(err)
|
||||||
"client_id": []string{"client-id"},
|
query := uri.Query()
|
||||||
"client_secret": []string{"client-secret"},
|
assert.Equal("serviceaccount", uri.Scheme)
|
||||||
"location": []string{"location"},
|
assert.Equal("azure", uri.Host)
|
||||||
}, query)
|
assert.Equal(tc.wantURLValues, query)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
25
internal/cloud/azureshared/authmethod_string.go
Normal file
25
internal/cloud/azureshared/authmethod_string.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by "stringer -type=AuthMethod -trimprefix=AuthMethod"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package azureshared
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[AuthMethodUnknown-0]
|
||||||
|
_ = x[AuthMethodServicePrincipal-1]
|
||||||
|
_ = x[AuthMethodUserAssignedIdentity-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _AuthMethod_name = "UnknownServicePrincipalUserAssignedIdentity"
|
||||||
|
|
||||||
|
var _AuthMethod_index = [...]uint8{0, 7, 23, 43}
|
||||||
|
|
||||||
|
func (i AuthMethod) String() string {
|
||||||
|
if i >= AuthMethod(len(_AuthMethod_index)-1) {
|
||||||
|
return "AuthMethod(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _AuthMethod_name[_AuthMethod_index[i]:_AuthMethod_index[i+1]]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user