Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
Provide the ability to create a new cli application registration (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
chkeita authored Nov 2, 2020
1 parent 2d5cda7 commit e026e50
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 31 deletions.
15 changes: 9 additions & 6 deletions src/deployment/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
assign_scaleset_role,
authorize_application,
get_application,
OnefuzzAppRole,
register_application,
update_pool_registration,
)
Expand Down Expand Up @@ -230,7 +231,7 @@ def create_password(self, object_id):
password = add_application_password(object_id)
if password:
return password
if count > timeout_seconds/wait:
if count > timeout_seconds / wait:
raise Exception("creating password failed, trying again")

def setup_rbac(self):
Expand Down Expand Up @@ -260,19 +261,19 @@ def setup_rbac(self):
app_roles = [
AppRole(
allowed_member_types=["Application"],
display_name="CliClient",
display_name=OnefuzzAppRole.CliClient.value,
id=str(uuid.uuid4()),
is_enabled=True,
description="Allows access from the CLI.",
value="CliClient",
value=OnefuzzAppRole.CliClient.value,
),
AppRole(
allowed_member_types=["Application"],
display_name="ManagedNode",
display_name=OnefuzzAppRole.ManagedNode.value,
id=str(uuid.uuid4()),
is_enabled=True,
description="Allow access from a lab machine.",
value="ManagedNode",
value=OnefuzzAppRole.ManagedNode.value,
),
]

Expand Down Expand Up @@ -340,7 +341,9 @@ def setup_rbac(self):
"Could not find the default CLI application under the current "
"subscription, creating a new one"
)
app_info = register_application("onefuzz-cli", self.application_name)
app_info = register_application(
"onefuzz-cli", self.application_name, OnefuzzAppRole.CliClient
)
self.cli_config = {
"client_id": app_info.client_id,
"authority": app_info.authority,
Expand Down
114 changes: 89 additions & 25 deletions src/deployment/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

import argparse
import logging
import time
import urllib.parse
from datetime import datetime, timedelta
from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Tuple
from uuid import UUID, uuid4

Expand All @@ -17,6 +19,7 @@
from azure.graphrbac.models import (
Application,
ApplicationCreateParameters,
AppRole,
RequiredResourceAccess,
ResourceAccess,
)
Expand All @@ -27,7 +30,13 @@


class GraphQueryError(Exception):
pass
def __init__(self, message, status_code):
super(GraphQueryError, self).__init__(message)
self.status_code = status_code

@property
def status_code():
return self.status_code


def query_microsoft_graph(
Expand Down Expand Up @@ -59,7 +68,9 @@ def query_microsoft_graph(
else:
error_text = str(response.content, encoding="utf-8", errors="backslashreplace")
raise GraphQueryError(
"request did not succeed: HTTP %s - %s" % (response.status_code, error_text)
"request did not succeed: HTTP %s - %s"
% (response.status_code, error_text),
response.status_code,
)


Expand All @@ -69,20 +80,31 @@ class ApplicationInfo(NamedTuple):
authority: str


class OnefuzzAppRole(Enum):
ManagedNode = "ManagedNode"
CliClient = "CliClient"


def register_application(
registration_name: str, onefuzz_instance_name: str
registration_name: str, onefuzz_instance_name: str, approle: OnefuzzAppRole
) -> ApplicationInfo:
logger.debug("retrieving the application registration")
logger.info("retrieving the application registration %s" % registration_name)
client = get_client_from_cli_profile(GraphRbacManagementClient)
apps: List[Application] = list(
client.applications.list(filter="displayName eq '%s'" % registration_name)
)

if len(apps) == 0:
logger.debug("No existing registration found. creating a new one")
app = create_application_registration(onefuzz_instance_name, registration_name)
logger.info("No existing registration found. creating a new one")
app = create_application_registration(
onefuzz_instance_name, registration_name, approle
)
else:
app = apps[0]
logger.info(
"Found existing application objectId '%s' - appid '%s"
% (app.object_id, app.app_id)
)

onefuzz_apps: List[Application] = list(
client.applications.list(filter="displayName eq '%s'" % onefuzz_instance_name)
Expand Down Expand Up @@ -113,6 +135,7 @@ def register_application(
def create_application_credential(application_name: str) -> str:
""" Add a new password to the application registration """

logger.info("creating application credential for '%s'" % application_name)
client = get_client_from_cli_profile(GraphRbacManagementClient)
apps: List[Application] = list(
client.applications.list(filter="displayName eq '%s'" % application_name)
Expand All @@ -125,7 +148,7 @@ def create_application_credential(application_name: str) -> str:


def create_application_registration(
onefuzz_instance_name: str, name: str
onefuzz_instance_name: str, name: str, approle: OnefuzzAppRole
) -> Application:
""" Create an application registration """

Expand All @@ -136,7 +159,9 @@ def create_application_registration(

app = apps[0]
resource_access = [
ResourceAccess(id=role.id, type="Role") for role in app.app_roles
ResourceAccess(id=role.id, type="Role")
for role in app.app_roles
if role.value == approle.value
]

params = ApplicationCreateParameters(
Expand All @@ -158,16 +183,33 @@ def create_application_registration(

registered_app: Application = client.applications.create(params)

query_microsoft_graph(
method="PATCH",
resource="applications/%s" % registered_app.object_id,
body={
"publicClient": {
"redirectUris": ["https://%s.azurewebsites.net" % onefuzz_instance_name]
},
"isFallbackPublicClient": True,
},
)
atttempts = 5
while True:
if atttempts < 0:
raise Exception(
"Unable to create application registration, Please try again"
)

atttempts = atttempts - 1
try:
time.sleep(5)
query_microsoft_graph(
method="PATCH",
resource="applications/%s" % registered_app.object_id,
body={
"publicClient": {
"redirectUris": [
"https://%s.azurewebsites.net" % onefuzz_instance_name
]
},
"isFallbackPublicClient": True,
},
)
break
except GraphQueryError as err:
if err.status_code == 404:
continue

authorize_application(UUID(registered_app.app_id), UUID(app.app_id))
return registered_app

Expand Down Expand Up @@ -250,19 +292,29 @@ def authorize_application(
)


def update_pool_registration(application_name: str):

def create_and_display_registration(
onefuzz_instance_name: str, registration_name: str, approle: OnefuzzAppRole
):
logger.info("Updating application registration")
application_info = register_application(
registration_name=("%s_pool" % application_name),
onefuzz_instance_name=application_name,
registration_name=registration_name,
onefuzz_instance_name=onefuzz_instance_name,
approle=approle,
)
logger.info("Registration complete")
logger.info("These generated credentials are valid for a year")
logger.info("client_id: %s" % application_info.client_id)
logger.info("client_secret: %s" % application_info.client_secret)


def update_pool_registration(onefuzz_instance_name: str):
create_and_display_registration(
onefuzz_instance_name,
"%s_pool" % onefuzz_instance_name,
OnefuzzAppRole.ManagedNode,
)


def assign_scaleset_role(onefuzz_instance_name: str, scaleset_name: str):
""" Allows the nodes in the scaleset to access the service by assigning their managed identity to the ManagedNode Role """

Expand Down Expand Up @@ -300,7 +352,7 @@ def assign_scaleset_role(onefuzz_instance_name: str, scaleset_name: str):

managed_node_role = (
seq(onefuzz_service_principal["appRoles"])
.filter(lambda x: x["value"] == "ManagedNode")
.filter(lambda x: x["value"] == OnefuzzAppRole.ManagedNode.value)
.head_option()
)

Expand Down Expand Up @@ -359,6 +411,12 @@ def main():
"scaleset_name",
help="the name of the scaleset",
)
cli_registration_parser = subparsers.add_parser(
"create_cli_registration", parents=[parent_parser]
)
cli_registration_parser.add_argument(
"--registration_name", help="the name of the cli registration"
)

args = parser.parse_args()
if args.verbose:
Expand All @@ -369,10 +427,16 @@ def main():
logging.basicConfig(format="%(levelname)s:%(message)s", level=level)
logging.getLogger("deploy").setLevel(logging.INFO)

onefuzz_instance_name = args.onefuzz_instance
if args.command == "update_pool_registration":
update_pool_registration(args.onefuzz_instance)
update_pool_registration(onefuzz_instance_name)
elif args.command == "create_cli_registration":
registration_name = args.registration_name or ("%s_cli" % onefuzz_instance_name)
create_and_display_registration(
onefuzz_instance_name, registration_name, OnefuzzAppRole.CliClient
)
elif args.command == "assign_scaleset_role":
assign_scaleset_role(args.onefuzz_instance, args.scaleset_name)
assign_scaleset_role(onefuzz_instance_name, args.scaleset_name)
else:
raise Exception("invalid arguments")

Expand Down

0 comments on commit e026e50

Please sign in to comment.