Esempio n. 1
0
    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)))
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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
Esempio n. 5
0
    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))
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
    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)))
Esempio n. 10
0
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
    }
Esempio n. 11
0
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)
Esempio n. 12
0
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
    }
Esempio n. 13
0
    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)))
Esempio n. 14
0
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
Esempio n. 15
0
    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)
Esempio n. 16
0
    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)