def check_service_principal(cli_ctx, resource_id, namespace, resource_type, root_type=None): from azure.cli.core._profile import ( ServicePrincipalAuth, _authentication_context_factory, ) try: # Get SP token sp_auth = ServicePrincipalAuth(SELF_DESTRUCT["clientSecret"]) context = _authentication_context_factory(cli_ctx, SELF_DESTRUCT["tenantId"], None) resource = cli_ctx.cloud.endpoints.active_directory_resource_id token = sp_auth.acquire_token(context, resource, SELF_DESTRUCT["clientId"]) # Check permissions for resource uri = "https://management.azure.com{}/providers/Microsoft.Authorization/permissions?api-version=2015-07-01".format( resource_id) headers = {"Authorization": "Bearer {}".format(token["accessToken"])} r = requests.get(uri, headers=headers) # TODO: Check expiration date of SP # If we can't read permissions - it may or may not be an actual problem # It's perfectly valid to create a "Delete Storage Accounts in resource group X only" cleanup role that can't read roleAssignments # I'd prefer always-correct behavior - so fail fast here if r.status_code != 200: error = r.json() LOGGER.error( "There was a failure when checking service principal permissions. Your self-destruct sequence cannot be activated!" ) LOGGER.error("%s: %s", error["error"]["code"], error["error"]["message"]) return False except Exception: # pylint: disable=broad-except LOGGER.error( "There was a failure when checking service principal permissions. Your self-destruct sequence cannot be activated!" ) return False # This is a mine field of complex behavior # action: '*' means everything, unless there's an overriding notAction # There can be wildcards inlined in both actions and notActions # Namespaces often don't match product names, etc permissions = r.json()["value"][0] reqs = { "*", "{}/*".format(namespace), # Microsoft.Storage/* "{}/*/delete".format(namespace), # Microsoft.Storage/*/delete "{}/{}/*".format(namespace, resource_type), # Microsoft.Storage/storageAccounts "{}/{}/delete".format( namespace, resource_type), # Microsoft.Storage/storageAccounts/delete } if root_type: reqs.add("{}/{}/{}/*".format(namespace, root_type, resource_type)) reqs.add("{}/{}/{}/delete".format(namespace, root_type, resource_type)) actions = set(permissions["actions"]) not_actions = set(permissions["notActions"]) if not reqs.intersection(actions) or reqs.intersection(not_actions): LOGGER.error( "Service principal delete permissions could not be verified. Your self-destruct sequence cannot be activated!" ) LOGGER.error("Needed: one of %s", reqs) LOGGER.error("Allow: %s", actions) LOGGER.error("Deny: %s", not_actions) return False return True
def check_service_principal(cli_ctx, resource_id, namespace, resource_type, root_type=None): from azure.cli.core._profile import ServicePrincipalAuth, _authentication_context_factory try: # Get SP token sp_auth = ServicePrincipalAuth(self_destruct['clientSecret']) context = _authentication_context_factory(cli_ctx, self_destruct['tenantId'], None) resource = cli_ctx.cloud.endpoints.active_directory_resource_id token = sp_auth.acquire_token(context, resource, self_destruct['clientId']) # Check permissions for resource uri = "https://management.azure.com{}/providers/Microsoft.Authorization/permissions?api-version=2015-07-01".format( resource_id) headers = {'Authorization': 'Bearer {}'.format(token['accessToken'])} r = requests.get(uri, headers=headers) # TODO: Check expiration date of SP # If we can't read permissions - it may or may not be an actual problem # It's perfectly valid to create a "Delete Storage Accounts in resource group X only" cleanup role that can't read roleAssignments # I'd prefer always-correct behavior - so fail fast here if r.status_code != 200: error = r.json() logger.error( 'There was a failure when checking service principal permissions. Your self-destruct sequence cannot be activated!' ) logger.error('{}: {}'.format(error['error']['code'], error['error']['message'])) return False except Exception: logger.error( 'There was a failure when checking service principal permissions. Your self-destruct sequence cannot be activated!' ) return False # This is a mine field of complex behavior # action: '*' means everything, unless there's an overriding notAction # There can be wildcards inlined in both actions and notActions # Namespaces often don't match product names, etc permissions = r.json()['value'][0] reqs = { '*', '{}/*'.format(namespace), # Microsoft.Storage/* '{}/*/delete'.format(namespace), # Microsoft.Storage/*/delete '{}/{}/*'.format(namespace, resource_type), # Microsoft.Storage/storageAccounts '{}/{}/delete'.format( namespace, resource_type) # Microsoft.Storage/storageAccounts/delete } if root_type: reqs.add('{}/{}/{}/*'.format(namespace, root_type, resource_type)) reqs.add('{}/{}/{}/delete'.format(namespace, root_type, resource_type)) actions = set(permissions['actions']) not_actions = set(permissions['notActions']) if not reqs.intersection(actions) or reqs.intersection(not_actions): logger.error( 'Service principal delete permissions could not be verified. Your self-destruct sequence cannot be activated!' ) logger.error('Needed: one of {}'.format(reqs)) logger.error('Allow: {}'.format(actions)) logger.error('Deny: {}'.format(not_actions)) return False return True