mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-15 19:38:11 +00:00
Provide the ability to create a new cli application registration (#236)
This commit is contained in:
@ -65,6 +65,7 @@ from registration import (
|
|||||||
assign_scaleset_role,
|
assign_scaleset_role,
|
||||||
authorize_application,
|
authorize_application,
|
||||||
get_application,
|
get_application,
|
||||||
|
OnefuzzAppRole,
|
||||||
register_application,
|
register_application,
|
||||||
update_pool_registration,
|
update_pool_registration,
|
||||||
)
|
)
|
||||||
@ -230,7 +231,7 @@ class Client:
|
|||||||
password = add_application_password(object_id)
|
password = add_application_password(object_id)
|
||||||
if password:
|
if password:
|
||||||
return password
|
return password
|
||||||
if count > timeout_seconds/wait:
|
if count > timeout_seconds / wait:
|
||||||
raise Exception("creating password failed, trying again")
|
raise Exception("creating password failed, trying again")
|
||||||
|
|
||||||
def setup_rbac(self):
|
def setup_rbac(self):
|
||||||
@ -260,19 +261,19 @@ class Client:
|
|||||||
app_roles = [
|
app_roles = [
|
||||||
AppRole(
|
AppRole(
|
||||||
allowed_member_types=["Application"],
|
allowed_member_types=["Application"],
|
||||||
display_name="CliClient",
|
display_name=OnefuzzAppRole.CliClient.value,
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
description="Allows access from the CLI.",
|
description="Allows access from the CLI.",
|
||||||
value="CliClient",
|
value=OnefuzzAppRole.CliClient.value,
|
||||||
),
|
),
|
||||||
AppRole(
|
AppRole(
|
||||||
allowed_member_types=["Application"],
|
allowed_member_types=["Application"],
|
||||||
display_name="ManagedNode",
|
display_name=OnefuzzAppRole.ManagedNode.value,
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
description="Allow access from a lab machine.",
|
description="Allow access from a lab machine.",
|
||||||
value="ManagedNode",
|
value=OnefuzzAppRole.ManagedNode.value,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -340,7 +341,9 @@ class Client:
|
|||||||
"Could not find the default CLI application under the current "
|
"Could not find the default CLI application under the current "
|
||||||
"subscription, creating a new one"
|
"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 = {
|
self.cli_config = {
|
||||||
"client_id": app_info.client_id,
|
"client_id": app_info.client_id,
|
||||||
"authority": app_info.authority,
|
"authority": app_info.authority,
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from enum import Enum
|
||||||
from typing import Dict, List, NamedTuple, Optional, Tuple
|
from typing import Dict, List, NamedTuple, Optional, Tuple
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ from azure.graphrbac import GraphRbacManagementClient
|
|||||||
from azure.graphrbac.models import (
|
from azure.graphrbac.models import (
|
||||||
Application,
|
Application,
|
||||||
ApplicationCreateParameters,
|
ApplicationCreateParameters,
|
||||||
|
AppRole,
|
||||||
RequiredResourceAccess,
|
RequiredResourceAccess,
|
||||||
ResourceAccess,
|
ResourceAccess,
|
||||||
)
|
)
|
||||||
@ -27,7 +30,13 @@ logger = logging.getLogger("deploy")
|
|||||||
|
|
||||||
|
|
||||||
class GraphQueryError(Exception):
|
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(
|
def query_microsoft_graph(
|
||||||
@ -59,7 +68,9 @@ def query_microsoft_graph(
|
|||||||
else:
|
else:
|
||||||
error_text = str(response.content, encoding="utf-8", errors="backslashreplace")
|
error_text = str(response.content, encoding="utf-8", errors="backslashreplace")
|
||||||
raise GraphQueryError(
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -69,20 +80,31 @@ class ApplicationInfo(NamedTuple):
|
|||||||
authority: str
|
authority: str
|
||||||
|
|
||||||
|
|
||||||
|
class OnefuzzAppRole(Enum):
|
||||||
|
ManagedNode = "ManagedNode"
|
||||||
|
CliClient = "CliClient"
|
||||||
|
|
||||||
|
|
||||||
def register_application(
|
def register_application(
|
||||||
registration_name: str, onefuzz_instance_name: str
|
registration_name: str, onefuzz_instance_name: str, approle: OnefuzzAppRole
|
||||||
) -> ApplicationInfo:
|
) -> ApplicationInfo:
|
||||||
logger.debug("retrieving the application registration")
|
logger.info("retrieving the application registration %s" % registration_name)
|
||||||
client = get_client_from_cli_profile(GraphRbacManagementClient)
|
client = get_client_from_cli_profile(GraphRbacManagementClient)
|
||||||
apps: List[Application] = list(
|
apps: List[Application] = list(
|
||||||
client.applications.list(filter="displayName eq '%s'" % registration_name)
|
client.applications.list(filter="displayName eq '%s'" % registration_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(apps) == 0:
|
if len(apps) == 0:
|
||||||
logger.debug("No existing registration found. creating a new one")
|
logger.info("No existing registration found. creating a new one")
|
||||||
app = create_application_registration(onefuzz_instance_name, registration_name)
|
app = create_application_registration(
|
||||||
|
onefuzz_instance_name, registration_name, approle
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
app = apps[0]
|
app = apps[0]
|
||||||
|
logger.info(
|
||||||
|
"Found existing application objectId '%s' - appid '%s"
|
||||||
|
% (app.object_id, app.app_id)
|
||||||
|
)
|
||||||
|
|
||||||
onefuzz_apps: List[Application] = list(
|
onefuzz_apps: List[Application] = list(
|
||||||
client.applications.list(filter="displayName eq '%s'" % onefuzz_instance_name)
|
client.applications.list(filter="displayName eq '%s'" % onefuzz_instance_name)
|
||||||
@ -113,6 +135,7 @@ def register_application(
|
|||||||
def create_application_credential(application_name: str) -> str:
|
def create_application_credential(application_name: str) -> str:
|
||||||
""" Add a new password to the application registration """
|
""" Add a new password to the application registration """
|
||||||
|
|
||||||
|
logger.info("creating application credential for '%s'" % application_name)
|
||||||
client = get_client_from_cli_profile(GraphRbacManagementClient)
|
client = get_client_from_cli_profile(GraphRbacManagementClient)
|
||||||
apps: List[Application] = list(
|
apps: List[Application] = list(
|
||||||
client.applications.list(filter="displayName eq '%s'" % application_name)
|
client.applications.list(filter="displayName eq '%s'" % application_name)
|
||||||
@ -125,7 +148,7 @@ def create_application_credential(application_name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def create_application_registration(
|
def create_application_registration(
|
||||||
onefuzz_instance_name: str, name: str
|
onefuzz_instance_name: str, name: str, approle: OnefuzzAppRole
|
||||||
) -> Application:
|
) -> Application:
|
||||||
""" Create an application registration """
|
""" Create an application registration """
|
||||||
|
|
||||||
@ -136,7 +159,9 @@ def create_application_registration(
|
|||||||
|
|
||||||
app = apps[0]
|
app = apps[0]
|
||||||
resource_access = [
|
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(
|
params = ApplicationCreateParameters(
|
||||||
@ -158,16 +183,33 @@ def create_application_registration(
|
|||||||
|
|
||||||
registered_app: Application = client.applications.create(params)
|
registered_app: Application = client.applications.create(params)
|
||||||
|
|
||||||
query_microsoft_graph(
|
atttempts = 5
|
||||||
method="PATCH",
|
while True:
|
||||||
resource="applications/%s" % registered_app.object_id,
|
if atttempts < 0:
|
||||||
body={
|
raise Exception(
|
||||||
"publicClient": {
|
"Unable to create application registration, Please try again"
|
||||||
"redirectUris": ["https://%s.azurewebsites.net" % onefuzz_instance_name]
|
)
|
||||||
},
|
|
||||||
"isFallbackPublicClient": True,
|
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))
|
authorize_application(UUID(registered_app.app_id), UUID(app.app_id))
|
||||||
return registered_app
|
return registered_app
|
||||||
|
|
||||||
@ -250,12 +292,14 @@ 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")
|
logger.info("Updating application registration")
|
||||||
application_info = register_application(
|
application_info = register_application(
|
||||||
registration_name=("%s_pool" % application_name),
|
registration_name=registration_name,
|
||||||
onefuzz_instance_name=application_name,
|
onefuzz_instance_name=onefuzz_instance_name,
|
||||||
|
approle=approle,
|
||||||
)
|
)
|
||||||
logger.info("Registration complete")
|
logger.info("Registration complete")
|
||||||
logger.info("These generated credentials are valid for a year")
|
logger.info("These generated credentials are valid for a year")
|
||||||
@ -263,6 +307,14 @@ def update_pool_registration(application_name: str):
|
|||||||
logger.info("client_secret: %s" % application_info.client_secret)
|
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):
|
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 """
|
""" Allows the nodes in the scaleset to access the service by assigning their managed identity to the ManagedNode Role """
|
||||||
|
|
||||||
@ -300,7 +352,7 @@ def assign_scaleset_role(onefuzz_instance_name: str, scaleset_name: str):
|
|||||||
|
|
||||||
managed_node_role = (
|
managed_node_role = (
|
||||||
seq(onefuzz_service_principal["appRoles"])
|
seq(onefuzz_service_principal["appRoles"])
|
||||||
.filter(lambda x: x["value"] == "ManagedNode")
|
.filter(lambda x: x["value"] == OnefuzzAppRole.ManagedNode.value)
|
||||||
.head_option()
|
.head_option()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -359,6 +411,12 @@ def main():
|
|||||||
"scaleset_name",
|
"scaleset_name",
|
||||||
help="the name of the scaleset",
|
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()
|
args = parser.parse_args()
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
@ -369,10 +427,16 @@ def main():
|
|||||||
logging.basicConfig(format="%(levelname)s:%(message)s", level=level)
|
logging.basicConfig(format="%(levelname)s:%(message)s", level=level)
|
||||||
logging.getLogger("deploy").setLevel(logging.INFO)
|
logging.getLogger("deploy").setLevel(logging.INFO)
|
||||||
|
|
||||||
|
onefuzz_instance_name = args.onefuzz_instance
|
||||||
if args.command == "update_pool_registration":
|
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":
|
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:
|
else:
|
||||||
raise Exception("invalid arguments")
|
raise Exception("invalid arguments")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user