diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 384617e0..391034bc 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -30,7 +30,7 @@ const azureDefaultAudience = "https://management.azure.com/" // azureXMSMirIDRegExp is the regular expression used to parse the xms_mirid claim. // Using case insensitive as resourceGroups appears as resourcegroups. -var azureXMSMirIDRegExp = regexp.MustCompile(`(?i)^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`) +var azureXMSMirIDRegExp = regexp.MustCompile(`(?i)^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.(Compute/virtualMachines|ManagedIdentity/userAssignedIdentities)/([^/]+)$`) type azureConfig struct { oidcDiscoveryURL string @@ -263,11 +263,19 @@ func (p *Azure) authorizeToken(token string) (*azurePayload, string, string, str } re := azureXMSMirIDRegExp.FindStringSubmatch(claims.XMSMirID) - if len(re) != 4 { + if len(re) != 5 { return nil, "", "", "", "", errs.Unauthorized("azure.authorizeToken; error parsing xms_mirid claim - %s", claims.XMSMirID) } + + var subscription, group, name string identityObjectID := claims.ObjectID - subscription, group, name := re[1], re[2], re[3] + + if strings.Contains(claims.XMSMirID, "virtualMachines") { + subscription, group, name = re[1], re[2], re[4] + } else { + // This is not a VM resource ID so we don't have the VM name so set that to the empty string + subscription, group, name = re[1], re[2], "" + } return &claims, name, group, subscription, identityObjectID, nil } diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index 4ab734d5..69f98502 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -95,7 +95,7 @@ func TestAzure_GetIdentityToken(t *testing.T) { assert.FatalError(t, err) t1, err := generateAzureToken("subject", p1.oidcConfig.Issuer, azureDefaultAudience, - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p1.keyStore.keySet.Keys[0]) assert.FatalError(t, err) @@ -237,7 +237,7 @@ func TestAzure_authorizeToken(t *testing.T) { jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) assert.FatalError(t, err) tok, err := generateAzureToken("subject", p.oidcConfig.Issuer, azureDefaultAudience, - p.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), jwk) assert.FatalError(t, err) return test{ @@ -252,7 +252,7 @@ func TestAzure_authorizeToken(t *testing.T) { assert.FatalError(t, err) defer srv.Close() tok, err := generateAzureToken("subject", "bad-issuer", azureDefaultAudience, - p.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p.keyStore.keySet.Keys[0]) assert.FatalError(t, err) return test{ @@ -267,7 +267,7 @@ func TestAzure_authorizeToken(t *testing.T) { assert.FatalError(t, err) defer srv.Close() tok, err := generateAzureToken("subject", p.oidcConfig.Issuer, azureDefaultAudience, - "foo", "subscriptionID", "resourceGroup", "virtualMachine", + "foo", "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p.keyStore.keySet.Keys[0]) assert.FatalError(t, err) return test{ @@ -321,7 +321,7 @@ func TestAzure_authorizeToken(t *testing.T) { assert.FatalError(t, err) defer srv.Close() tok, err := generateAzureToken("subject", p.oidcConfig.Issuer, azureDefaultAudience, - p.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p.keyStore.keySet.Keys[0]) assert.FatalError(t, err) return test{ @@ -437,28 +437,28 @@ func TestAzure_AuthorizeSign(t *testing.T) { assert.FatalError(t, err) t11, err := generateAzureToken("subject", p1.oidcConfig.Issuer, azureDefaultAudience, - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p1.keyStore.keySet.Keys[0]) assert.FatalError(t, err) failIssuer, err := generateAzureToken("subject", "bad-issuer", azureDefaultAudience, - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p1.keyStore.keySet.Keys[0]) assert.FatalError(t, err) failAudience, err := generateAzureToken("subject", p1.oidcConfig.Issuer, "bad-audience", - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &p1.keyStore.keySet.Keys[0]) assert.FatalError(t, err) failExp, err := generateAzureToken("subject", p1.oidcConfig.Issuer, azureDefaultAudience, - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now().Add(-360*time.Second), &p1.keyStore.keySet.Keys[0]) assert.FatalError(t, err) failNbf, err := generateAzureToken("subject", p1.oidcConfig.Issuer, azureDefaultAudience, - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now().Add(360*time.Second), &p1.keyStore.keySet.Keys[0]) assert.FatalError(t, err) failKey, err := generateAzureToken("subject", p1.oidcConfig.Issuer, azureDefaultAudience, - p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", + p1.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), badKey) assert.FatalError(t, err) diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index fe2678fc..d0992f0a 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -671,7 +671,7 @@ func generateAzureWithServer() (*Azure, *httptest.Server, error) { w.Header().Add("Cache-Control", "max-age=5") writeJSON(w, getPublic(az.keyStore.keySet)) case "/metadata/identity/oauth2/token": - tok, err := generateAzureToken("subject", issuer, "https://management.azure.com/", az.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", time.Now(), &az.keyStore.keySet.Keys[0]) + tok, err := generateAzureToken("subject", issuer, "https://management.azure.com/", az.TenantID, "subscriptionID", "resourceGroup", "virtualMachine", "vm", time.Now(), &az.keyStore.keySet.Keys[0]) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { @@ -1009,7 +1009,7 @@ func generateAWSToken(p *AWS, sub, iss, aud, accountID, instanceID, privateIP, r return jose.Signed(sig).Claims(claims).CompactSerialize() } -func generateAzureToken(sub, iss, aud, tenantID, subscriptionID, resourceGroup, virtualMachine string, iat time.Time, jwk *jose.JSONWebKey) (string, error) { +func generateAzureToken(sub, iss, aud, tenantID, subscriptionID, resourceGroup, resourceName string, resourceType string, iat time.Time, jwk *jose.JSONWebKey) (string, error) { sig, err := jose.NewSigner( jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID), @@ -1017,6 +1017,12 @@ func generateAzureToken(sub, iss, aud, tenantID, subscriptionID, resourceGroup, if err != nil { return "", err } + var xmsMirID string + if resourceType == "vm" { + xmsMirID = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", subscriptionID, resourceGroup, resourceName) + } else if resourceType == "uai" { + xmsMirID = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ManagedIdentity/userAssignedIdentities/%s", subscriptionID, resourceGroup, resourceName) + } claims := azurePayload{ Claims: jose.Claims{ @@ -1034,7 +1040,7 @@ func generateAzureToken(sub, iss, aud, tenantID, subscriptionID, resourceGroup, ObjectID: "the-oid", TenantID: tenantID, Version: "the-version", - XMSMirID: fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", subscriptionID, resourceGroup, virtualMachine), + XMSMirID: xmsMirID, } return jose.Signed(sig).Claims(claims).CompactSerialize() }