Enable User assigned managed identity for scalesets (#219)

This commit is contained in:
Cheick Keita
2020-10-29 10:53:11 -07:00
committed by GitHub
parent 99b69d3e56
commit 154be220ae
6 changed files with 92 additions and 12 deletions

View File

@ -14,6 +14,7 @@ from onefuzztypes.enums import ErrorCode
from onefuzztypes.models import Error from onefuzztypes.models import Error
from pydantic import BaseModel from pydantic import BaseModel
from .azure.creds import get_scaleset_principal_id
from .pools import Scaleset from .pools import Scaleset
from .request import not_ok from .request import not_ok
@ -54,6 +55,12 @@ def try_get_token_auth_header(request: func.HttpRequest) -> Union[Error, TokenDa
@cached(ttl=60) @cached(ttl=60)
def is_authorized(token_data: TokenData) -> bool: def is_authorized(token_data: TokenData) -> bool:
# verify object_id against the user assigned managed identity
if get_scaleset_principal_id() == token_data.object_id:
return True
# backward compatibility case for scalesets deployed before the migration
# to user assigned managed id
scalesets = Scaleset.get_by_object_id(token_data.object_id) scalesets = Scaleset.get_by_object_id(token_data.object_id)
return len(scalesets) > 0 return len(scalesets) > 0

View File

@ -6,6 +6,7 @@
import logging import logging
import os import os
from typing import Any, List, Optional, Tuple from typing import Any, List, Optional, Tuple
from uuid import UUID
from azure.cli.core import CLIError from azure.cli.core import CLIError
from azure.common.client_factory import get_client_from_cli_profile from azure.common.client_factory import get_client_from_cli_profile
@ -123,3 +124,24 @@ def is_member_of(group_id: str, member_id: str) -> bool:
CheckGroupMembershipParameters(group_id=group_id, member_id=member_id) CheckGroupMembershipParameters(group_id=group_id, member_id=member_id)
).value ).value
) )
@cached
def get_scaleset_identity_resource_path() -> str:
scaleset_id_name = "%s-scalesetid" % get_instance_name()
resource_group_path = "/subscriptions/%s/resourceGroups/%s/providers" % (
get_subscription(),
get_base_resource_group(),
)
return "%s/Microsoft.ManagedIdentity/userAssignedIdentities/%s" % (
resource_group_path,
scaleset_id_name,
)
@cached
def get_scaleset_principal_id() -> UUID:
api_version = "2018-11-30" # matches the apiversion in the deplyoment template
client = mgmt_client_factory(ResourceManagementClient)
uid = client.resources.get_by_id(get_scaleset_identity_resource_path(), api_version)
return UUID(uid.properties["principalId"])

View File

@ -16,7 +16,11 @@ from onefuzztypes.enums import OS, ErrorCode
from onefuzztypes.models import Error from onefuzztypes.models import Error
from onefuzztypes.primitives import Region from onefuzztypes.primitives import Region
from .creds import get_base_resource_group, mgmt_client_factory from .creds import (
get_base_resource_group,
get_scaleset_identity_resource_path,
mgmt_client_factory,
)
from .image import get_os from .image import get_os
@ -234,7 +238,10 @@ def create_vmss(
"do_not_run_extensions_on_overprovisioned_vms": True, "do_not_run_extensions_on_overprovisioned_vms": True,
"upgrade_policy": {"mode": "Manual"}, "upgrade_policy": {"mode": "Manual"},
"sku": sku, "sku": sku,
"identity": {"type": "SystemAssigned"}, "identity": {
"type": "userAssigned",
"userAssignedIdentities": {get_scaleset_identity_resource_path(): {}},
},
"virtual_machine_profile": { "virtual_machine_profile": {
"priority": "Regular", "priority": "Regular",
"storage_profile": {"image_reference": image_ref}, "storage_profile": {"image_reference": image_ref},

View File

@ -38,6 +38,7 @@
} }
}, },
"variables": { "variables": {
"scaleset_identity": "[concat(parameters('name'), '-scalesetid')]",
"telemetry": "d7a73cf4-5a1a-4030-85e1-e5b25867e45a", "telemetry": "d7a73cf4-5a1a-4030-85e1-e5b25867e45a",
"signalr-name": "[concat('onefuzz-', uniquestring(resourceGroup().id))]", "signalr-name": "[concat('onefuzz-', uniquestring(resourceGroup().id))]",
"monitorAccountName": "[concat('logs-wb-', uniquestring(resourceGroup().id))]", "monitorAccountName": "[concat('logs-wb-', uniquestring(resourceGroup().id))]",
@ -47,6 +48,7 @@
"Storage Account Contributor": "17d1049b-9a84-46fb-8f53-869881c3d3ab", "Storage Account Contributor": "17d1049b-9a84-46fb-8f53-869881c3d3ab",
"Virtual Machine Contributor": "9980e02c-c2be-4d73-94e8-173b1dc7cf3c", "Virtual Machine Contributor": "9980e02c-c2be-4d73-94e8-173b1dc7cf3c",
"Log Analytics Contributor": "92aaf0da-9dab-42b6-94a3-d43ce8d16293", "Log Analytics Contributor": "92aaf0da-9dab-42b6-94a3-d43ce8d16293",
"Managed Identity Operator": "f1a07417-d97a-45cb-824c-7a7467783830",
"storage_account_sas": { "storage_account_sas": {
"signedServices": "bfqt", "signedServices": "bfqt",
"signedPermission": "rwdlacup", "signedPermission": "rwdlacup",
@ -93,6 +95,12 @@
} }
], ],
"resources": [ "resources": [
{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"name": "[variables('scaleset_identity')]",
"apiVersion": "2018-11-30",
"location": "[resourceGroup().location]"
},
{ {
"apiVersion": "2018-11-01", "apiVersion": "2018-11-01",
"name": "[parameters('name')]", "name": "[parameters('name')]",
@ -580,6 +588,21 @@
"OWNER": "[parameters('owner')]" "OWNER": "[parameters('owner')]"
} }
}, },
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2017-09-01",
"name": "[guid(concat(resourceGroup().id, '-user_managed_idenity'))]",
"properties": {
"roleDefinitionId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', variables('Managed Identity Operator'))]",
"principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('name')), '2018-02-01', 'Full').identity.principalId]"
},
"DependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('name'))]"
],
"tags": {
"OWNER": "[parameters('owner')]"
}
},
{ {
"type": "Microsoft.SignalRService/SignalR", "type": "Microsoft.SignalRService/SignalR",
"apiVersion": "2018-10-01", "apiVersion": "2018-10-01",
@ -640,6 +663,10 @@
"func-key": { "func-key": {
"type": "string", "type": "string",
"value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFunc')), '2019-06-01').keys[0].value]" "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFunc')), '2019-06-01').keys[0].value]"
},
"scaleset-identity": {
"type": "string",
"value": "[variables('scaleset_identity')]"
} }
} }
} }

View File

@ -62,6 +62,7 @@ from msrest.serialization import TZ_UTC
from data_migration import migrate from data_migration import migrate
from registration import ( from registration import (
add_application_password, add_application_password,
assign_scaleset_role,
authorize_application, authorize_application,
get_application, get_application,
register_application, register_application,
@ -395,6 +396,11 @@ class Client:
sys.exit(1) sys.exit(1)
self.results["deploy"] = result.properties.outputs self.results["deploy"] = result.properties.outputs
logger.info("assigning the user managed identity role")
assign_scaleset_role(
self.application_name, self.results["deploy"]["scaleset-identity"]["value"]
)
def apply_migrations(self): def apply_migrations(self):
self.results["deploy"]["func-storage"]["value"] self.results["deploy"]["func-storage"]["value"]
name = self.results["deploy"]["func-name"]["value"] name = self.results["deploy"]["func-name"]["value"]

View File

@ -298,28 +298,39 @@ def assign_scaleset_role(onefuzz_instance_name: str, scaleset_name: str):
raise Exception("scaleset service principal not found") raise Exception("scaleset service principal not found")
scaleset_service_principal = scaleset_service_principals["value"][0] scaleset_service_principal = scaleset_service_principals["value"][0]
lab_machine_role = ( managed_node_role = (
seq(onefuzz_service_principal["appRoles"]) seq(onefuzz_service_principal["appRoles"])
.filter(lambda x: x["value"] == "ManagedNode") .filter(lambda x: x["value"] == "ManagedNode")
.head_option() .head_option()
) )
if not lab_machine_role: if not managed_node_role:
raise Exception( raise Exception(
"ManagedNode role not found int the onefuzz application registration. Please redeploy the instance" "ManagedNode role not found int the onefuzz application registration. Please redeploy the instance"
) )
query_microsoft_graph( assignments = query_microsoft_graph(
method="POST", method="GET",
resource="servicePrincipals/%s/appRoleAssignedTo" resource="servicePrincipals/%s/appRoleAssignments"
% scaleset_service_principal["id"], % scaleset_service_principal["id"],
body={
"principalId": scaleset_service_principal["id"],
"resourceId": onefuzz_service_principal["id"],
"appRoleId": lab_machine_role["id"],
},
) )
# check if the role is already assigned
role_assigned = seq(assignments["value"]).find(
lambda assignment: assignment["appRoleId"] == managed_node_role["id"]
)
if not role_assigned:
query_microsoft_graph(
method="POST",
resource="servicePrincipals/%s/appRoleAssignedTo"
% scaleset_service_principal["id"],
body={
"principalId": scaleset_service_principal["id"],
"resourceId": onefuzz_service_principal["id"],
"appRoleId": managed_node_role["id"],
},
)
def main(): def main():
formatter = argparse.ArgumentDefaultsHelpFormatter formatter = argparse.ArgumentDefaultsHelpFormatter