Skip to content

Commit

Permalink
appservice: support assign managed service identity to webapp/functio…
Browse files Browse the repository at this point in the history
…napp (#4837)
  • Loading branch information
yugangw-msft authored Nov 8, 2017
1 parent e6d3f3c commit 950f300
Show file tree
Hide file tree
Showing 9 changed files with 1,642 additions and 57 deletions.
75 changes: 75 additions & 0 deletions src/azure-cli-core/azure/cli/core/commands/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,3 +730,78 @@ def _find_property(instance, path):
for part in path:
instance = _update_instance(instance, part, path)
return instance


def assign_implict_identity(getter, setter, identity_role=None, identity_scope=None):
import time
from azure.mgmt.authorization import AuthorizationManagementClient
from azure.mgmt.authorization.models import RoleAssignmentProperties
from msrestazure.azure_exceptions import CloudError

# get
resource = getter()
if resource.identity:
logger.warning('Implict identity is already configured')
else:
resource = setter(resource)

# create role assignment:
if identity_scope:
principal_id = resource.identity.principal_id

identity_role_id = resolve_role_id(identity_role, identity_scope)
assignments_client = get_mgmt_service_client(AuthorizationManagementClient).role_assignments
properties = RoleAssignmentProperties(identity_role_id, principal_id)

logger.info("Creating an assignment with a role '%s' on the scope of '%s'", identity_role_id, identity_scope)
retry_times = 36
assignment_id = _gen_guid()
for l in range(0, retry_times):
try:
assignments_client.create(identity_scope, assignment_id, properties)
break
except CloudError as ex:
if 'role assignment already exists' in ex.message:
logger.info('Role assignment already exists')
break
elif l < retry_times and ' does not exist in the directory ' in ex.message:
time.sleep(5)
logger.warning('Retrying role assignment creation: %s/%s', l + 1,
retry_times)
continue
else:
raise
return resource


def resolve_role_id(role, scope):
import uuid
from azure.mgmt.authorization import AuthorizationManagementClient
client = get_mgmt_service_client(AuthorizationManagementClient).role_definitions

role_id = None
if re.match(r'/subscriptions/[^/]+/providers/Microsoft.Authorization/roleDefinitions/',
role, re.I):
role_id = role
else:
try:
uuid.UUID(role)
role_id = '/subscriptions/{}/providers/Microsoft.Authorization/roleDefinitions/{}'.format(
client.config.subscription_id, role)
except ValueError:
pass
if not role_id: # retrieve role id
role_defs = list(client.list(scope, "roleName eq '{}'".format(role)))
if not role_defs:
raise CLIError("Role '{}' doesn't exist.".format(role))
elif len(role_defs) > 1:
ids = [r.id for r in role_defs]
err = "More than one role matches the given name '{}'. Please pick an id from '{}'"
raise CLIError(err.format(role, ids))
role_id = role_defs[0].id
return role_id


def _gen_guid():
import uuid
return uuid.uuid4()
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@
--facebook-oauth-scopes public_profile email
"""

helps['webapp assign-identity'] = """
type: command
short-summary: (PREVIEW) assign managed service identity to the webapp
examples:
- name: assign local identity and assign a reader role to the current resource group.
text: >
az webapp assign-identity -g MyResourceGroup -n MyUniqueApp --role reader --scope /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/MyResourceGroup
- name: disable the identity when there is need.
text: >
az webapp config appsettings set -g MyResourceGroup -n MyUniqueApp --settings WEBSITE_DISABLE_MSI=true
"""

helps['functionapp assign-identity'] = helps['webapp assign-identity'].replace('webapp', 'functionapp')

helps['webapp config'] = """
type: group
short-summary: Configure a web app.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ def get_hostname_completion_list(prefix, action, parsed_args, **kwargs): # pyli
register_cli_argument(scope + ' create', 'deployment_zip', options_list=('--deployment-zip', '-z'), help='perform deployment using zip file')
register_cli_argument(scope + ' create', 'deployment_source_url', options_list=('--deployment-source-url', '-u'), help='Git repository URL to link with manual integration')
register_cli_argument(scope + ' create', 'deployment_source_branch', options_list=('--deployment-source-branch', '-b'), help='the branch to deploy')
register_cli_argument(scope + ' assign-identity', 'disable_msi', action='store_true', help='disable the identity')
register_cli_argument(scope + ' assign-identity', 'scope', help="The scope the managed identity has access to")
register_cli_argument(scope + ' assign-identity', 'role', help="Role name or id the managed identity will be assigned")

register_cli_argument('webapp config hostname', 'webapp_name', help="webapp name. You can configure the default using 'az configure --defaults web=<name>'", configured_default='web',
completer=get_resource_name_completion_list('Microsoft.Web/sites'), id_part='name')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def _polish_bad_errors(ex):
cli_command(__name__, 'webapp auth show', custom_path + 'get_auth_settings')
cli_command(__name__, 'webapp auth update', custom_path + 'update_auth_settings')

cli_command(__name__, 'webapp assign-identity', custom_path + 'assign_identity')

if not supported_api_version(PROFILE_TYPE, max_api='2017-03-09-profile'):
cli_command(__name__, 'appservice plan create', custom_path + 'create_app_service_plan', exception_handler=ex_handler_factory(creating_plan=True))
Expand Down Expand Up @@ -187,3 +188,4 @@ def _polish_bad_errors(ex):
cli_command(__name__, 'functionapp deployment list-publishing-profiles',
custom_path + 'list_publish_profiles')
cli_command(__name__, 'functionapp deployment user show', 'azure.mgmt.web.web_site_management_client#WebSiteManagementClient.get_publishing_user', cf_web_client, exception_handler=empty_on_404)
cli_command(__name__, 'functionapp assign-identity', custom_path + 'assign_identity')
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
SkuDescription, SslState, HostNameBinding, NameValuePair,
BackupRequest, DatabaseBackupSetting, BackupSchedule,
RestoreRequest, FrequencyUnit, Certificate, HostNameSslState,
RampUpRule, UnauthenticatedClientAction)
RampUpRule, UnauthenticatedClientAction, ManagedServiceIdentity)

from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands import LongRunningOperation
Expand Down Expand Up @@ -132,6 +132,23 @@ def _list_app(app_types, resource_group_name=None):
return result


def assign_identity(resource_group_name, name, role='Contributor', scope=None, disable_msi=False):
client = web_client_factory()

def getter():
return _generic_site_operation(resource_group_name, name, 'get')

def setter(webapp):
webapp.identity = ManagedServiceIdentity(type='SystemAssigned')
poller = client.web_apps.create_or_update(resource_group_name, name, webapp)
return LongRunningOperation()(poller)

from azure.cli.core.commands.arm import assign_implict_identity
webapp = assign_implict_identity(getter, setter, role, scope)
update_app_settings(resource_group_name, name, ['WEBSITE_DISABLE_MSI={}'.format(disable_msi)])
return webapp.identity


def get_auth_settings(resource_group_name, name, slot=None):
return _generic_site_operation(resource_group_name, name, 'get_auth_settings', slot)

Expand Down
Loading

0 comments on commit 950f300

Please sign in to comment.