/* Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ package azureshared import ( "fmt" "net/url" "regexp" "strings" ) var ( subscriptionPattern = regexp.MustCompile(`subscriptions/([^/]+)/`) rgPattern = regexp.MustCompile(`resourceGroups/([^/]+)/`) ) // 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 { SubscriptionID string ResourceGroup string TenantID string AppClientID string ClientSecretValue string Location string UamiResourceID string PreferredAuthMethod AuthMethod } // ApplicationCredentialsFromURI converts a cloudServiceAccountURI into Azure ApplicationCredentials. func ApplicationCredentialsFromURI(cloudServiceAccountURI string) (ApplicationCredentials, error) { uri, err := url.Parse(cloudServiceAccountURI) if err != nil { return ApplicationCredentials{}, err } if uri.Scheme != "serviceaccount" { return ApplicationCredentials{}, fmt.Errorf("invalid service account URI: invalid scheme: %s", uri.Scheme) } if uri.Host != "azure" { return ApplicationCredentials{}, fmt.Errorf("invalid service account URI: invalid host: %s", uri.Host) } query := uri.Query() subscriptionID := getFirstMatchOrEmpty(subscriptionPattern, query.Get("uami_resource_id")) resourceGroup := getFirstMatchOrEmpty(rgPattern, query.Get("uami_resource_id")) preferredAuthMethod := FromString(query.Get("preferred_auth_method")) return ApplicationCredentials{ SubscriptionID: subscriptionID, ResourceGroup: resourceGroup, 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 } func getFirstMatchOrEmpty(pattern *regexp.Regexp, str string) string { subscriptionMatches := pattern.FindStringSubmatch(str) var subscriptionID string if len(subscriptionMatches) > 1 { subscriptionID = subscriptionMatches[1] } return subscriptionID } // ToCloudServiceAccountURI converts the ApplicationCredentials into a cloud service account URI. func (c ApplicationCredentials) ToCloudServiceAccountURI() string { query := url.Values{} query.Add("tenant_id", c.TenantID) 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", RawQuery: query.Encode(), } 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 )