mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04: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 = [
|
||||
"//bootstrapper/internal/kubernetes/k8sapi",
|
||||
"//bootstrapper/internal/kubernetes/kubewaiter",
|
||||
"//internal/cloud/azureshared",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/cloud/gcpshared",
|
||||
"//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/kubewaiter"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
|
||||
@ -477,24 +476,6 @@ func (k *KubeWrapper) setupExtraVals(ctx context.Context, serviceConfig constell
|
||||
"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:
|
||||
creds, err := openstack.AccountKeyFromURI(serviceConfig.cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
|
@ -451,6 +451,9 @@ func (i *initCmd) getMarshaledServiceAccountURI(provider cloudprovider.Provider,
|
||||
AppClientID: config.Provider.Azure.AppClientID,
|
||||
ClientSecretValue: config.Provider.Azure.ClientSecretValue,
|
||||
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
|
||||
|
||||
|
@ -181,7 +181,6 @@ go_library(
|
||||
"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/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/clusterrolebinding.yaml",
|
||||
"charts/edgeless/constellation-services/charts/autoscaler/templates/gcp-deployment.yaml",
|
||||
|
@ -24,43 +24,25 @@ spec:
|
||||
- name: cluster-autoscaler
|
||||
image: {{ .Values.image | quote }}
|
||||
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:
|
||||
httpGet:
|
||||
path: /health-check
|
||||
port: 8085
|
||||
ports:
|
||||
- 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: {}
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
@ -78,4 +60,8 @@ spec:
|
||||
key: node.cloudprovider.kubernetes.io/uninitialized
|
||||
operator: Equal
|
||||
value: "true"
|
||||
volumes:
|
||||
- name: azureconfig
|
||||
secret:
|
||||
secretName: azureconfig
|
||||
{{- 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": [
|
||||
"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": [
|
||||
"csp",
|
||||
"image"
|
||||
],
|
||||
"if": {
|
||||
"properties": {
|
||||
"csp": {
|
||||
"const": "Azure"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"csp"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"Azure"
|
||||
]
|
||||
},
|
||||
"title": "Values",
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -23,43 +23,25 @@ spec:
|
||||
- name: cluster-autoscaler
|
||||
image: autoscalerImage
|
||||
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:
|
||||
httpGet:
|
||||
path: /health-check
|
||||
port: 8085
|
||||
ports:
|
||||
- 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: {}
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
@ -77,3 +59,7 @@ spec:
|
||||
key: node.cloudprovider.kubernetes.io/uninitialized
|
||||
operator: Equal
|
||||
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")
|
||||
}
|
||||
|
||||
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{
|
||||
Cloud: "AzurePublicCloud",
|
||||
TenantID: creds.TenantID,
|
||||
SubscriptionID: subscriptionID,
|
||||
ResourceGroup: resourceGroup,
|
||||
LoadBalancerSku: "standard",
|
||||
SecurityGroupName: securityGroupName,
|
||||
LoadBalancerName: *loadBalancer.Name,
|
||||
UseInstanceMetadata: true,
|
||||
VMType: "vmss",
|
||||
Location: creds.Location,
|
||||
AADClientID: creds.AppClientID,
|
||||
AADClientSecret: creds.ClientSecretValue,
|
||||
Cloud: "AzurePublicCloud",
|
||||
TenantID: creds.TenantID,
|
||||
SubscriptionID: subscriptionID,
|
||||
ResourceGroup: resourceGroup,
|
||||
LoadBalancerSku: "standard",
|
||||
SecurityGroupName: securityGroupName,
|
||||
LoadBalancerName: *loadBalancer.Name,
|
||||
UseInstanceMetadata: true,
|
||||
VMType: "vmss",
|
||||
Location: creds.Location,
|
||||
UseManagedIdentityExtension: useManagedIdentityExtension,
|
||||
UserAssignedIdentityID: uamiClientID,
|
||||
AADClientID: creds.AppClientID,
|
||||
AADClientSecret: creds.ClientSecretValue,
|
||||
}
|
||||
|
||||
return json.Marshal(config)
|
||||
@ -304,6 +315,24 @@ func (c *Cloud) getInstance(ctx context.Context, providerID string) (metadata.In
|
||||
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.
|
||||
func (c *Cloud) getNetworkSecurityGroupName(ctx context.Context, resourceGroup, uid string) (string, error) {
|
||||
pager := c.secGroupAPI.NewListPager(resourceGroup, nil)
|
||||
@ -383,23 +412,25 @@ func (c *Cloud) getVMInterfaces(ctx context.Context, vm armcompute.VirtualMachin
|
||||
}
|
||||
|
||||
type cloudConfig struct {
|
||||
Cloud string `json:"cloud,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
ResourceGroup string `json:"resourceGroup,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
SubnetName string `json:"subnetName,omitempty"`
|
||||
SecurityGroupName string `json:"securityGroupName,omitempty"`
|
||||
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
|
||||
LoadBalancerName string `json:"loadBalancerName,omitempty"`
|
||||
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
|
||||
VNetName string `json:"vnetName,omitempty"`
|
||||
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
|
||||
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`
|
||||
UseInstanceMetadata bool `json:"useInstanceMetadata,omitempty"`
|
||||
VMType string `json:"vmType,omitempty"`
|
||||
AADClientID string `json:"aadClientId,omitempty"`
|
||||
AADClientSecret string `json:"aadClientSecret,omitempty"`
|
||||
Cloud string `json:"cloud,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
ResourceGroup string `json:"resourceGroup,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
SubnetName string `json:"subnetName,omitempty"`
|
||||
SecurityGroupName string `json:"securityGroupName,omitempty"`
|
||||
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
|
||||
LoadBalancerName string `json:"loadBalancerName,omitempty"`
|
||||
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
|
||||
VNetName string `json:"vnetName,omitempty"`
|
||||
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
|
||||
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`
|
||||
UseInstanceMetadata bool `json:"useInstanceMetadata,omitempty"`
|
||||
VMType string `json:"vmType,omitempty"`
|
||||
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension,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.
|
||||
|
@ -52,10 +52,13 @@ func TestGetCCMConfig(t *testing.T) {
|
||||
Name: to.Ptr("security-group"),
|
||||
}
|
||||
|
||||
uamiClientID := "uami-client-id"
|
||||
|
||||
testCases := map[string]struct {
|
||||
imdsAPI imdsAPI
|
||||
loadBalancerAPI loadBalancerAPI
|
||||
secGroupAPI securityGroupsAPI
|
||||
scaleSetsVMAPI virtualMachineScaleSetVMsAPI
|
||||
providerID string
|
||||
cloudServiceAccountURI string
|
||||
wantErr bool
|
||||
@ -75,8 +78,50 @@ func TestGetCCMConfig(t *testing.T) {
|
||||
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",
|
||||
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{
|
||||
Cloud: "AzurePublicCloud",
|
||||
TenantID: "tenant-id",
|
||||
@ -88,8 +133,6 @@ func TestGetCCMConfig(t *testing.T) {
|
||||
UseInstanceMetadata: true,
|
||||
VMType: "vmss",
|
||||
Location: "westeurope",
|
||||
AADClientID: "client-id",
|
||||
AADClientSecret: "client-secret",
|
||||
},
|
||||
},
|
||||
"missing UID tag": {
|
||||
@ -303,6 +346,7 @@ func TestGetCCMConfig(t *testing.T) {
|
||||
imds: tc.imdsAPI,
|
||||
loadBalancerAPI: tc.loadBalancerAPI,
|
||||
secGroupAPI: tc.secGroupAPI,
|
||||
scaleSetsVMAPI: tc.scaleSetsVMAPI,
|
||||
}
|
||||
config, err := cloud.GetCCMConfig(context.Background(), tc.providerID, tc.cloudServiceAccountURI)
|
||||
if tc.wantErr {
|
||||
|
@ -5,6 +5,7 @@ go_library(
|
||||
name = "azureshared",
|
||||
srcs = [
|
||||
"appcredentials.go",
|
||||
"authmethod_string.go",
|
||||
"azureshared.go",
|
||||
"metadata.go",
|
||||
],
|
||||
|
@ -9,15 +9,19 @@ package azureshared
|
||||
import (
|
||||
"fmt"
|
||||
"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.
|
||||
type ApplicationCredentials struct {
|
||||
TenantID string
|
||||
AppClientID string
|
||||
ClientSecretValue string
|
||||
Location string
|
||||
TenantID string
|
||||
AppClientID string
|
||||
ClientSecretValue string
|
||||
Location string
|
||||
UamiResourceID string
|
||||
PreferredAuthMethod AuthMethod
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
query := uri.Query()
|
||||
preferredAuthMethod := FromString(query.Get("preferred_auth_method"))
|
||||
return ApplicationCredentials{
|
||||
TenantID: query.Get("tenant_id"),
|
||||
AppClientID: query.Get("client_id"),
|
||||
ClientSecretValue: query.Get("client_secret"),
|
||||
Location: query.Get("location"),
|
||||
TenantID: query.Get("tenant_id"),
|
||||
AppClientID: query.Get("client_id"),
|
||||
ClientSecretValue: query.Get("client_secret"),
|
||||
Location: query.Get("location"),
|
||||
UamiResourceID: query.Get("uami_resource_id"),
|
||||
PreferredAuthMethod: preferredAuthMethod,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -45,9 +52,19 @@ func ApplicationCredentialsFromURI(cloudServiceAccountURI string) (ApplicationCr
|
||||
func (c ApplicationCredentials) ToCloudServiceAccountURI() string {
|
||||
query := url.Values{}
|
||||
query.Add("tenant_id", c.TenantID)
|
||||
query.Add("client_id", c.AppClientID)
|
||||
query.Add("client_secret", c.ClientSecretValue)
|
||||
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{
|
||||
Scheme: "serviceaccount",
|
||||
Host: "azure",
|
||||
@ -55,3 +72,29 @@ func (c ApplicationCredentials) ToCloudServiceAccountURI() 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) {
|
||||
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",
|
||||
AppClientID: "client-id",
|
||||
ClientSecretValue: "client-secret",
|
||||
@ -32,9 +46,17 @@ func TestApplicationCredentialsFromURI(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
"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,
|
||||
},
|
||||
"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": {
|
||||
cloudServiceAccountURI: "\x00",
|
||||
wantErr: true,
|
||||
@ -66,25 +88,66 @@ func TestApplicationCredentialsFromURI(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToCloudServiceAccountURI(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
key := ApplicationCredentials{
|
||||
TenantID: "tenant-id",
|
||||
AppClientID: "client-id",
|
||||
ClientSecretValue: "client-secret",
|
||||
Location: "location",
|
||||
testCases := map[string]struct {
|
||||
credentials ApplicationCredentials
|
||||
wantURLValues url.Values
|
||||
}{
|
||||
"client id and secret without preferred auth method": {
|
||||
credentials: ApplicationCredentials{
|
||||
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()
|
||||
uri, err := url.Parse(cloudServiceAccountURI)
|
||||
require.NoError(err)
|
||||
query := uri.Query()
|
||||
assert.Equal("serviceaccount", uri.Scheme)
|
||||
assert.Equal("azure", uri.Host)
|
||||
assert.Equal(url.Values{
|
||||
"tenant_id": []string{"tenant-id"},
|
||||
"client_id": []string{"client-id"},
|
||||
"client_secret": []string{"client-secret"},
|
||||
"location": []string{"location"},
|
||||
}, query)
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloudServiceAccountURI := tc.credentials.ToCloudServiceAccountURI()
|
||||
uri, err := url.Parse(cloudServiceAccountURI)
|
||||
require.NoError(err)
|
||||
query := uri.Query()
|
||||
assert.Equal("serviceaccount", uri.Scheme)
|
||||
assert.Equal("azure", uri.Host)
|
||||
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