def create_password(self, old_passwords): def gen_guid(): return uuid.uuid4() if self.value is None: self.fail( "when creating a new password, module parameter value can't be None" ) start_date = datetime.datetime.now(datetime.timezone.utc) end_date = self.end_date or start_date + relativedelta(years=1) value = self.value key_id = self.key_id or str(gen_guid()) new_password = PasswordCredential(start_date=start_date, end_date=end_date, key_id=key_id, value=value, custom_key_identifier=None) old_passwords.append(new_password) try: client = self.get_graphrbac_client(self.tenant) app_patch_parameters = ApplicationUpdateParameters( password_credentials=old_passwords) client.applications.patch(self.app_object_id, app_patch_parameters) new_passwords = self.get_all_passwords() for pd in new_passwords: if pd.key_id == key_id: self.results['changed'] = True self.results.update(self.to_dict(pd)) except GraphErrorException as ge: self.fail("failed to create new password: {0}".format(str(ge)))
def update_application( client, identifier, display_name=None, homepage=None, # pylint: disable=too-many-arguments identifier_uris=None, password=None, reply_urls=None, key_value=None, key_type=None, key_usage=None, start_date=None, end_date=None): object_id = _resolve_application(client, identifier) password_creds, key_creds = _build_application_creds( password, key_value, key_type, key_usage, start_date, end_date) app_patch_param = ApplicationUpdateParameters( display_name=display_name, homepage=homepage, identifier_uris=identifier_uris, reply_urls=reply_urls, key_credentials=key_creds, password_credentials=password_creds) return client.patch(object_id, app_patch_param)
def update_application(client, identifier, display_name=None, homepage=None, identifier_uris=None, password=None, reply_urls=None, key_value=None, key_type=None, key_usage=None, start_date=None, end_date=None, available_to_other_tenants=None): object_id = _resolve_application(client, identifier) password_creds, key_creds = None, None if any([key_value, key_type, key_usage, start_date, end_date]): password_creds, key_creds = _build_application_creds( password, key_value, key_type, key_usage, start_date, end_date) app_patch_param = ApplicationUpdateParameters( display_name=display_name, homepage=homepage, identifier_uris=identifier_uris, reply_urls=reply_urls, key_credentials=key_creds, password_credentials=password_creds, available_to_other_tenants=available_to_other_tenants) return client.patch(object_id, app_patch_param)
def create_application_registration( onefuzz_instance_name: str, name: str, approle: OnefuzzAppRole ) -> Application: """ Create an application registration """ client = get_graph_client() apps: List[Application] = list( client.applications.list(filter="displayName eq '%s'" % onefuzz_instance_name) ) app = apps[0] resource_access = [ ResourceAccess(id=role.id, type="Role") for role in app.app_roles if role.value == approle.value ] params = ApplicationCreateParameters( is_device_only_auth_supported=True, display_name=name, identifier_uris=[], password_credentials=[], required_resource_access=( [ RequiredResourceAccess( resource_access=resource_access, resource_app_id=app.app_id, ) ] if len(resource_access) > 0 else [] ), ) registered_app: Application = client.applications.create(params) atttempts = 5 while True: if atttempts < 0: raise Exception( "Unable to create application registration, Please try again" ) atttempts = atttempts - 1 try: time.sleep(5) client = get_graph_client() update_param = ApplicationUpdateParameters( reply_urls=["https://%s.azurewebsites.net" % onefuzz_instance_name] ) client.applications.patch(registered_app.object_id, update_param) break except Exception: continue authorize_application(UUID(registered_app.app_id), UUID(app.app_id)) return registered_app
def test_update_application_to_be_single_tenant(self): graph_app_client = mock.MagicMock() app_mock = mock.MagicMock() test_object_id = '11111111-2222-3333-4444-555555555555' app_mock.object_id = test_object_id graph_app_client.list.return_value = [app_mock] update_application(graph_app_client, 'http://any-client', available_to_other_tenants=True) graph_app_client.patch.assert_called_with(test_object_id, ApplicationUpdateParameters(available_to_other_tenants=True))
def update_application(client, app_object_id, display_name): app_update_param = ApplicationUpdateParameters(display_name=display_name) try: return client.patch(app_object_id, app_update_param) except Exception as ex: # pylint: disable=broad-except logger.warning( "Updating service principal failed for appid '%s'. Trace followed:\n%s", app_object_id, ex.response.headers if hasattr(ex, 'response') else ex) # pylint: disable=no-member raise
def reset_service_principal_credential(name, password=None, create_cert=False, cert=None, years=1): '''reset credential, on expiration or you forget it. :param str name: the name, can be the app id uri, app id guid, or display name :param str password: the password used to login. If missing, command will generate one. :param str cert: PEM formatted public certificate. Do not include private key info. :param str years: Years the password will be valid. ''' client = _graph_client_factory() # pylint: disable=no-member # look for the existing application query_exp = "servicePrincipalNames/any(x:x eq \'{0}\') or displayName eq '{0}'".format(name) aad_sps = list(client.service_principals.list(filter=query_exp)) if not aad_sps: raise CLIError("can't find a service principal matching '{}'".format(name)) if len(aad_sps) > 1: raise CLIError( 'more than one entry matches the name, please provide unique names like app id guid, or app id uri') # pylint: disable=line-too-long app = show_application(client.applications, aad_sps[0].app_id) # build a new password/cert credential and patch it public_cert_string = None cert_file = None if len([x for x in [cert, create_cert, password] if x]) > 1: raise CLIError('Usage error: --cert | --create-cert | --password') if create_cert: public_cert_string, cert_file = _create_self_signed_cert(years) elif cert: public_cert_string = cert else: password = password or str(uuid.uuid4()) start_date = datetime.datetime.utcnow() end_date = start_date + relativedelta(years=years) key_id = str(uuid.uuid4()) app_creds = [PasswordCredential(start_date, end_date, key_id, password)] if password else None cert_creds = [KeyCredential(start_date, end_date, public_cert_string, str(uuid.uuid4()), usage='Verify', type='AsymmetricX509Cert')] if public_cert_string else None # pylint: disable=line-too-long app_create_param = ApplicationUpdateParameters(password_credentials=app_creds, key_credentials=cert_creds) client.applications.patch(app.object_id, app_create_param) result = { 'appId': app.app_id, 'password': password, 'name': name, 'tenant': client.config.tenant_id } if cert_file: result['fileWithCertAndPrivateKey'] = cert_file return result
def create_aad_user(credentials, tenant_id, **kwargs): """ Create an AAD application and service principal :param credentials: msrestazure.azure_active_directory.AdalAuthentication :param tenant_id: str :param **application_name: str """ graph_rbac_client = GraphRbacManagementClient( credentials, tenant_id, base_url=AZURE_PUBLIC_CLOUD.endpoints.active_directory_graph_resource_id) application_credential = uuid.uuid4() try: display_name = kwargs.get("application_name", DefaultSettings.application_name) application = graph_rbac_client.applications.create( parameters=ApplicationCreateParameters( available_to_other_tenants=False, identifier_uris=["http://{}.com".format(display_name)], display_name=display_name, password_credentials=[ PasswordCredential( start_date=datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc), end_date=datetime(2299, 12, 31, 0, 0, 0, 0, tzinfo=timezone.utc), value=application_credential, key_id=uuid.uuid4()) ])) service_principal = graph_rbac_client.service_principals.create( ServicePrincipalCreateParameters(app_id=application.app_id, account_enabled=True)) except GraphErrorException as e: if e.inner_exception.code == "Request_BadRequest": application = next( graph_rbac_client.applications.list( filter="identifierUris/any(c:c eq 'http://{}.com')".format(display_name))) confirmation_prompt = "Previously created application with name {} found. "\ "Would you like to use it? (y/n): ".format(application.display_name) prompt_for_confirmation(confirmation_prompt, e, ValueError("Response not recognized. Please try again.")) password_credentials = list( graph_rbac_client.applications.list_password_credentials(application_object_id=application.object_id)) password_credentials.append( PasswordCredential( start_date=datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc), end_date=datetime(2299, 12, 31, 0, 0, 0, 0, tzinfo=timezone.utc), value=application_credential, key_id=uuid.uuid4())) graph_rbac_client.applications.patch( application_object_id=application.object_id, parameters=ApplicationUpdateParameters(password_credentials=password_credentials)) service_principal = next( graph_rbac_client.service_principals.list(filter="appId eq '{}'".format(application.app_id))) else: raise e return application.app_id, service_principal.object_id, str(application_credential)
def delete_all_passwords(self, old_passwords): if len(old_passwords) == 0: self.results['changed'] = False return try: self.client.applications.patch( self.app_object_id, ApplicationUpdateParameters(password_credentials=[])) self.results['changed'] = True except GraphErrorException as ge: self.fail("fail to purge all passwords for app: {0} - {1}".format( self.app_object_id, str(ge)))
def reset_service_principal_credential(name, password=None, years=1): '''reset credential, on expiration or you forget it. :param str name: the uri representing the name of the service principal :param str password: the password used to login. If missing, command will generate one. :param str years: Years the password will be valid. ''' client = _graph_client_factory() #pylint: disable=no-member #look for the existing application query_exp = 'identifierUris/any(x:x eq \'{}\')'.format(name) aad_apps = list(client.applications.list(filter=query_exp)) if not aad_apps: raise CLIError( 'can\'t find an application matching \'{}\''.format(name)) #no need to check 2+ matches, as app id uri is unique app = aad_apps[0] #look for the existing service principal query_exp = 'servicePrincipalNames/any(x:x eq \'{}\')'.format(name) aad_sps = list(client.service_principals.list(filter=query_exp)) if not aad_sps: raise CLIError( 'can\'t find a service principal matching \'{}\''.format(name)) #build a new password credential and patch it password = password or str(uuid.uuid4()) start_date = datetime.datetime.now() end_date = start_date + relativedelta(years=years) key_id = str(uuid.uuid4()) app_cred = PasswordCredential(start_date, end_date, key_id, password) app_create_param = ApplicationUpdateParameters( password_credentials=[app_cred]) client.applications.patch(app.object_id, app_create_param) return { 'appId': app.app_id, 'password': password, 'name': name, 'tenant': client.config.tenant_id }
def reset_service_principal_credential(name, secret=None, years=1): '''reset credential, on expiration or you forget it. :param str name: the uri representing the name of the service principal :param str secret: the secret used to login. If missing, command will generate one. :param str years: Years the secret will be valid. ''' profile = Profile() cred, _, tenant = profile.get_login_credentials(for_graph_client=True) client = GraphRbacManagementClient(cred, tenant) #pylint: disable=no-member #look for the existing application query_exp = 'identifierUris/any(x:x eq \'{}\')'.format(name) aad_apps = list(client.applications.list(filter=query_exp)) if not aad_apps: raise CLIError( 'can\'t find a graph application matching \'{}\''.format(name)) #no need to check 2+ matches, as app id uri is unique app = aad_apps[0] #look for the existing service principal query_exp = 'servicePrincipalNames/any(x:x eq \'{}\')'.format(name) aad_sps = list(client.service_principals.list(filter=query_exp)) if not aad_sps: raise CLIError( 'can\'t find an service principal matching \'{}\''.format(name)) sp_object_id = aad_sps[0].object_id #build a new password credential and patch it secret = secret or str(uuid.uuid4()) start_date = datetime.datetime.now() end_date = start_date + relativedelta(years=years) key_id = str(uuid.uuid4()) app_cred = PasswordCredential(start_date, end_date, key_id, secret) app_create_param = ApplicationUpdateParameters( password_credentials=[app_cred]) client.applications.patch(app.object_id, app_create_param) _build_output_content(name, sp_object_id, secret, tenant)
def reset_service_principal_credential(name, password=None, years=1): '''reset credential, on expiration or you forget it. :param str name: the name, can be the app id uri, app id guid, or display name :param str password: the password used to login. If missing, command will generate one. :param str years: Years the password will be valid. ''' client = _graph_client_factory() #pylint: disable=no-member #look for the existing application query_exp = "servicePrincipalNames/any(x:x eq \'{0}\') or displayName eq '{0}'".format( name) aad_sps = list(client.service_principals.list(filter=query_exp)) if not aad_sps: raise CLIError( "can't find a service principal matching '{}'".format(name)) if len(aad_sps) > 1: raise CLIError('more than one entry matches the name, please provide unique names like app id guid, or app id uri') #pylint: disable=line-too-long app = show_application(client.applications, aad_sps[0].app_id) #build a new password credential and patch it password = password or str(uuid.uuid4()) start_date = datetime.datetime.utcnow() end_date = start_date + relativedelta(years=years) key_id = str(uuid.uuid4()) app_cred = PasswordCredential(start_date, end_date, key_id, password) app_create_param = ApplicationUpdateParameters( password_credentials=[app_cred]) client.applications.patch(app.object_id, app_create_param) return { 'appId': app.app_id, 'password': password, 'name': name, 'tenant': client.config.tenant_id }
def delete_password(self, old_passwords): if not self.key_exists(old_passwords): self.results['changed'] = False return num_of_passwords_before_delete = len(old_passwords) for pd in old_passwords: if pd.key_id == self.key_id: old_passwords.remove(pd) break try: self.client.applications.patch( self.app_object_id, ApplicationUpdateParameters( password_credentials=old_passwords)) num_of_passwords_after_delete = len(self.get_all_passwords()) if num_of_passwords_after_delete != num_of_passwords_before_delete: self.results['changed'] = True except GraphErrorException as ge: self.fail("failed to delete password with key id {0} - {1}".format( self.app_id, str(ge)))
def reset_service_principal_credential(name, password=None, create_cert=False, cert=None, years=None, keyvault=None): import pytz client = _graph_client_factory() # pylint: disable=no-member years = years or 1 # look for the existing application query_exp = "servicePrincipalNames/any(x:x eq \'{0}\') or displayName eq '{0}'".format(name) aad_sps = list(client.service_principals.list(filter=query_exp)) if not aad_sps: raise CLIError("can't find a service principal matching '{}'".format(name)) if len(aad_sps) > 1: raise CLIError( 'more than one entry matches the name, please provide unique names like ' 'app id guid, or app id uri') app = show_application(client.applications, aad_sps[0].app_id) app_start_date = datetime.datetime.now(pytz.utc) app_end_date = app_start_date + relativedelta(years=years or 1) # build a new password/cert credential and patch it public_cert_string = None cert_file = None password, public_cert_string, cert_file, cert_start_date, cert_end_date = \ _process_service_principal_creds(years, app_start_date, app_end_date, cert, create_cert, password, keyvault) app_start_date, app_end_date, cert_start_date, cert_end_date = \ _validate_app_dates(app_start_date, app_end_date, cert_start_date, cert_end_date) app_creds = None cert_creds = None if password: app_creds = [ PasswordCredential( start_date=app_start_date, end_date=app_end_date, key_id=str(uuid.uuid4()), value=password ) ] if public_cert_string: cert_creds = [ KeyCredential( start_date=app_start_date, end_date=app_end_date, value=public_cert_string, key_id=str(uuid.uuid4()), usage='Verify', type='AsymmetricX509Cert' ) ] app_create_param = ApplicationUpdateParameters(password_credentials=app_creds, key_credentials=cert_creds) client.applications.patch(app.object_id, app_create_param) result = { 'appId': app.app_id, 'password': password, 'name': name, 'tenant': client.config.tenant_id } if cert_file: result['fileWithCertAndPrivateKey'] = cert_file return result
def setup_rbac(self) -> None: """ Setup the client application for the OneFuzz instance. By default, Service Principals do not have access to create client applications in AAD. """ if self.results["client_id"] and self.results["client_secret"]: logger.info("using existing client application") return client = get_client_from_cli_profile( GraphRbacManagementClient, subscription_id=self.get_subscription_id()) logger.info("checking if RBAC already exists") try: existing = list( client.applications.list(filter="displayName eq '%s'" % self.application_name)) except GraphErrorException: logger.error( "unable to query RBAC. Provide client_id and client_secret") sys.exit(1) app_roles = [ AppRole( allowed_member_types=["Application"], display_name=OnefuzzAppRole.CliClient.value, id=str(uuid.uuid4()), is_enabled=True, description="Allows access from the CLI.", value=OnefuzzAppRole.CliClient.value, ), AppRole( allowed_member_types=["Application"], display_name=OnefuzzAppRole.ManagedNode.value, id=str(uuid.uuid4()), is_enabled=True, description="Allow access from a lab machine.", value=OnefuzzAppRole.ManagedNode.value, ), ] app: Optional[Application] = None if not existing: logger.info("creating Application registration") if self.multi_tenant_domain: url = "https://%s/%s" % ( self.multi_tenant_domain, self.application_name, ) else: url = "https://%s.azurewebsites.net" % self.application_name params = ApplicationCreateParameters( display_name=self.application_name, identifier_uris=[url], reply_urls=[url + "/.auth/login/aad/callback"], optional_claims=OptionalClaims(id_token=[], access_token=[]), required_resource_access=[ RequiredResourceAccess( resource_access=[ ResourceAccess(id=USER_IMPERSONATION, type="Scope") ], resource_app_id="00000002-0000-0000-c000-000000000000", ) ], app_roles=app_roles, ) app = client.applications.create(params) logger.info("creating service principal") service_principal_params = ServicePrincipalCreateParameters( account_enabled=True, app_role_assignment_required=False, service_principal_type="Application", app_id=app.app_id, ) def try_sp_create() -> None: error: Optional[Exception] = None for _ in range(10): try: client.service_principals.create( service_principal_params) return except GraphErrorException as err: # work around timing issue when creating service principal # https://github.com/Azure/azure-cli/issues/14767 if ("service principal being created must in the local tenant" not in str(err)): raise err logging.warning( "creating service principal failed with an error that occurs " "due to AAD race conditions") time.sleep(60) if error is None: raise Exception("service principal creation failed") else: raise error try_sp_create() else: app = existing[0] existing_role_values = [ app_role.value for app_role in app.app_roles ] has_missing_roles = any( [role.value not in existing_role_values for role in app_roles]) if has_missing_roles: # disabling the existing app role first to allow the update # this is a requirement to update the application roles for role in app.app_roles: role.is_enabled = False client.applications.patch( app.object_id, ApplicationUpdateParameters(app_roles=app.app_roles)) # overriding the list of app roles client.applications.patch( app.object_id, ApplicationUpdateParameters(app_roles=app_roles)) if self.multi_tenant_domain and app.sign_in_audience == "AzureADMyOrg": url = "https://%s/%s" % ( self.multi_tenant_domain, self.application_name, ) client.applications.patch( app.object_id, ApplicationUpdateParameters(identifier_uris=[url])) set_app_audience(app.object_id, "AzureADMultipleOrgs") elif (not self.multi_tenant_domain and app.sign_in_audience == "AzureADMultipleOrgs"): set_app_audience(app.object_id, "AzureADMyOrg") url = "https://%s.azurewebsites.net" % self.application_name client.applications.patch( app.object_id, ApplicationUpdateParameters(identifier_uris=[url])) else: logger.debug("No change to App Registration signInAudence setting") creds = list( client.applications.list_password_credentials(app.object_id)) client.applications.update_password_credentials( app.object_id, creds) (password_id, password) = self.create_password(app.object_id) cli_app = list( client.applications.list(filter="appId eq '%s'" % ONEFUZZ_CLI_APP)) if len(cli_app) == 0: logger.info( "Could not find the default CLI application under the current " "subscription, creating a new one") app_info = register_application( "onefuzz-cli", self.application_name, OnefuzzAppRole.CliClient, self.get_subscription_id(), ) if self.multi_tenant_domain: authority = COMMON_AUTHORITY else: authority = app_info.authority self.cli_config = { "client_id": app_info.client_id, "authority": authority, } else: authorize_application(uuid.UUID(ONEFUZZ_CLI_APP), app.app_id) self.results["client_id"] = app.app_id self.results["client_secret"] = password # Log `client_secret` for consumption by CI. if self.log_service_principal: logger.info("client_id: %s client_secret: %s", app.app_id, password) else: logger.debug("client_id: %s client_secret: %s", app.app_id, password)
def setup_rbac(self): """ Setup the client application for the OneFuzz instance. By default, Service Principals do not have access to create client applications in AAD. """ if self.results["client_id"] and self.results["client_secret"]: logger.info("using existing client application") return client = get_client_from_cli_profile(GraphRbacManagementClient) logger.info("checking if RBAC already exists") try: existing = list( client.applications.list( filter="displayName eq '%s'" % self.application_name ) ) except GraphErrorException: logger.error("unable to query RBAC. Provide client_id and client_secret") sys.exit(1) app_roles = [ AppRole( allowed_member_types=["Application"], display_name="CliClient", id=str(uuid.uuid4()), is_enabled=True, description="Allows access from the CLI.", value="CliClient", ), AppRole( allowed_member_types=["Application"], display_name="ManagedNode", id=str(uuid.uuid4()), is_enabled=True, description="Allow access from a lab machine.", value="ManagedNode", ), ] if not existing: logger.info("creating Application registration") url = "https://%s.azurewebsites.net" % self.application_name params = ApplicationCreateParameters( display_name=self.application_name, identifier_uris=[url], reply_urls=[url + "/.auth/login/aad/callback"], optional_claims=OptionalClaims(id_token=[], access_token=[]), required_resource_access=[ RequiredResourceAccess( resource_access=[ ResourceAccess(id=USER_IMPERSONATION, type="Scope") ], resource_app_id="00000002-0000-0000-c000-000000000000", ) ], app_roles=app_roles, ) app = client.applications.create(params) logger.info("creating service principal") service_principal_params = ServicePrincipalCreateParameters( account_enabled=True, app_role_assignment_required=False, service_principal_type="Application", app_id=app.app_id, ) client.service_principals.create(service_principal_params) else: app: Application = existing[0] existing_role_values = [app_role.value for app_role in app.app_roles] has_missing_roles = any( [role.value not in existing_role_values for role in app_roles] ) if has_missing_roles: # disabling the existing app role first to allow the update # this is a requirement to update the application roles for role in app.app_roles: role.is_enabled = False client.applications.patch( app.object_id, ApplicationUpdateParameters(app_roles=app.app_roles) ) # overriding the list of app roles client.applications.patch( app.object_id, ApplicationUpdateParameters(app_roles=app_roles) ) creds = list(client.applications.list_password_credentials(app.object_id)) client.applications.update_password_credentials(app.object_id, creds) (password_id, password) = self.create_password(app.object_id) onefuzz_cli_app_uuid = uuid.UUID(ONEFUZZ_CLI_APP) cli_app = get_application(onefuzz_cli_app_uuid) if cli_app is None: logger.info( "Could not find the default CLI application under the current " "subscription, creating a new one" ) app_info = register_application("onefuzz-cli", self.application_name) self.cli_config = { "client_id": app_info.client_id, "authority": app_info.authority, } else: authorize_application(onefuzz_cli_app_uuid, app.app_id) self.results["client_id"] = app.app_id self.results["client_secret"] = password # Log `client_secret` for consumption by CI. if self.log_service_principal: logger.info("client_id: %s client_secret: %s", app.app_id, password) else: logger.debug("client_id: %s client_secret: %s", app.app_id, password)