diff --git a/src/ApiService/ApiService/Functions/Config.cs b/src/ApiService/ApiService/Functions/Config.cs new file mode 100644 index 000000000..097ed6c20 --- /dev/null +++ b/src/ApiService/ApiService/Functions/Config.cs @@ -0,0 +1,34 @@ +using System.Net; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace Microsoft.OneFuzz.Service.Functions; + +public class Config { + private readonly ILogTracer _log; + private readonly IOnefuzzContext _context; + + public Config(ILogTracer log, IOnefuzzContext context) { + _log = log; + _context = context; + } + + [Function("Config")] + public Async.Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req) { + return Get(req); + } + public async Async.Task Get(HttpRequestData req) { + _log.Info($"getting endpoint config parameters"); + + var endpointParams = new ConfigResponse( + Authority: _context.ServiceConfiguration.Authority, + ClientId: _context.ServiceConfiguration.CliAppId, + TenantDomain: _context.ServiceConfiguration.TenantDomain); + + var response = req.CreateResponse(HttpStatusCode.OK); + await response.WriteAsJsonAsync(endpointParams); + + return response; + } +} diff --git a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs index b5391f7ad..39f5f9fd2 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs @@ -159,6 +159,12 @@ public record ScalesetResponse( Nodes: null); } +public record ConfigResponse( + string? Authority, + string? ClientId, + string? TenantDomain +) : BaseResponse(); + public class BaseResponseConverter : JsonConverter { public override BaseResponse? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return null; diff --git a/src/ApiService/ApiService/ServiceConfiguration.cs b/src/ApiService/ApiService/ServiceConfiguration.cs index fb0a30848..c959a90d5 100644 --- a/src/ApiService/ApiService/ServiceConfiguration.cs +++ b/src/ApiService/ApiService/ServiceConfiguration.cs @@ -26,7 +26,9 @@ public interface IServiceConfig { public string? DiagnosticsAzureBlobContainerSasUrl { get; } public string? DiagnosticsAzureBlobRetentionDays { get; } - + public string? CliAppId { get; } + public string? Authority { get; } + public string? TenantDomain { get; } public string? MultiTenantDomain { get; } public ResourceIdentifier? OneFuzzDataStorage { get; } public ResourceIdentifier? OneFuzzFuncStorage { get; } @@ -97,7 +99,9 @@ public class ServiceConfiguration : IServiceConfig { public string? DiagnosticsAzureBlobContainerSasUrl { get => GetEnv("DIAGNOSTICS_AZUREBLOBCONTAINERSASURL"); } public string? DiagnosticsAzureBlobRetentionDays { get => GetEnv("DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS"); } - + public string? CliAppId { get => GetEnv("CLI_APP_ID"); } + public string? Authority { get => GetEnv("AUTHORITY"); } + public string? TenantDomain { get => GetEnv("TENANT_DOMAIN"); } public string? MultiTenantDomain { get => GetEnv("MULTI_TENANT_DOMAIN"); } public ResourceIdentifier? OneFuzzDataStorage { diff --git a/src/ApiService/ApiService/UserCredentials.cs b/src/ApiService/ApiService/UserCredentials.cs index fb9bf2d7f..807247195 100644 --- a/src/ApiService/ApiService/UserCredentials.cs +++ b/src/ApiService/ApiService/UserCredentials.cs @@ -92,7 +92,7 @@ public class UserCredentials : IUserCredentials { } else { var tenantsStr = allowedTenants.OkV is null ? "null" : String.Join(';', allowedTenants.OkV!); _log.Error($"issuer not from allowed tenant. issuer: {token.Issuer:Tag:Issuer} - tenants: {tenantsStr:Tag:Tenants}"); - return OneFuzzResult.Error(ErrorCode.INVALID_REQUEST, new[] { "unauthorized AAD issuer" }); + return OneFuzzResult.Error(ErrorCode.INVALID_REQUEST, new[] { "unauthorized AAD issuer. If multi-tenant auth is failing, make sure to include all tenant_ids in the `allowed_aad_tenants` list in the instance_config. To see the current instance_config, run `onefuzz instance_config get`. " }); } } else { _log.Error($"Failed to get allowed tenants due to {allowedTenants.ErrorV:Tag:Error}"); diff --git a/src/ApiService/ApiService/onefuzzlib/ConfigOperations.cs b/src/ApiService/ApiService/onefuzzlib/ConfigOperations.cs index 39e3f23c1..f139be1f1 100644 --- a/src/ApiService/ApiService/onefuzzlib/ConfigOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/ConfigOperations.cs @@ -26,7 +26,7 @@ public class ConfigOperations : Orm, IConfigOperations { private static readonly InstanceConfigCacheKey _key = new(); // singleton key public Task Fetch() => _cache.GetOrCreateAsync(_key, async entry => { - entry = entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(10)); // cached for 10 minutes + entry = entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(1)); // cached for 1 minute var key = _context.ServiceConfiguration.OneFuzzInstanceName ?? throw new Exception("Environment variable ONEFUZZ_INSTANCE_NAME is not set"); return await GetEntityAsync(key, key); }); diff --git a/src/ApiService/IntegrationTests/Fakes/TestServiceConfiguration.cs b/src/ApiService/IntegrationTests/Fakes/TestServiceConfiguration.cs index 725675b37..ae84d11ef 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestServiceConfiguration.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestServiceConfiguration.cs @@ -25,6 +25,11 @@ public sealed class TestServiceConfiguration : IServiceConfig { public string? OneFuzzTelemetry => "TestOneFuzzTelemetry"; + public string? CliAppId => "TestGuid"; + + public string? Authority => "TestAuthority"; + + public string? TenantDomain => "TestDomain"; public string? MultiTenantDomain => null; public string? OneFuzzInstanceName => "UnitTestInstance"; diff --git a/src/cli/onefuzz/api.py b/src/cli/onefuzz/api.py index a7b579c57..04eb3f3d5 100644 --- a/src/cli/onefuzz/api.py +++ b/src/cli/onefuzz/api.py @@ -41,8 +41,9 @@ from .ssh import build_ssh_command, ssh_connect, temp_file UUID_EXPANSION = TypeVar("UUID_EXPANSION", UUID, str) DEFAULT = BackendConfig( - authority="https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47", - client_id="72f1562a-8c0c-41ea-beb9-fa2b71c80134", + authority="", + client_id="", + tenant_domain="", ) # This was generated randomly and should be preserved moving forwards @@ -122,6 +123,10 @@ class Endpoint: as_params: bool = False, alternate_endpoint: Optional[str] = None, ) -> A: + + # Retrieve Auth Parameters + self._req_config_params() + response = self._req_base( method, data=data, @@ -153,6 +158,33 @@ class Endpoint: return [model.parse_obj(x) for x in response] + def _req_config_params( + self, + ) -> None: + + if self.onefuzz._backend.config.endpoint is None: + raise Exception("Endpoint Not Configured") + + endpoint = self.onefuzz._backend.config.endpoint + + response = self.onefuzz._backend.session.request( + "GET", endpoint + "/api/config" + ) + + logging.debug(response.json()) + endpoint_params = responses.Config.parse_obj(response.json()) + + logging.debug(self.onefuzz._backend.config.authority) + # Will override values in storage w/ provided values for SP use + if self.onefuzz._backend.config.client_id == "": + self.onefuzz._backend.config.client_id = endpoint_params.client_id + if self.onefuzz._backend.config.authority == "": + self.onefuzz._backend.config.authority = endpoint_params.authority + if self.onefuzz._backend.config.tenant_domain == "": + self.onefuzz._backend.config.tenant_domain = endpoint_params.tenant_domain + + self.onefuzz._backend.save_config() + def _disambiguate( self, name: str, @@ -1862,7 +1894,9 @@ class Onefuzz: self.logger.debug("set config") if reset: - self._backend.config = BackendConfig(authority="", client_id="") + self._backend.config = BackendConfig( + authority="", client_id="", tenant_domain="" + ) if endpoint is not None: # The normal path for calling the API always uses the oauth2 workflow, diff --git a/src/cli/onefuzz/backend.py b/src/cli/onefuzz/backend.py index dd7b3a962..9587ee106 100644 --- a/src/cli/onefuzz/backend.py +++ b/src/cli/onefuzz/backend.py @@ -95,7 +95,7 @@ class BackendConfig(BaseModel): client_id: str endpoint: Optional[str] features: Set[str] = Field(default_factory=set) - tenant_domain: Optional[str] + tenant_domain: str class Backend: @@ -181,7 +181,7 @@ class Backend: if not self.config.endpoint: raise Exception("endpoint not configured") - if self.config.tenant_domain: + if "https://login.microsoftonline.com/common" in self.config.authority: endpoint = urlparse(self.config.endpoint).netloc.split(".")[0] scopes = [ f"api://{self.config.tenant_domain}/{endpoint}/.default", diff --git a/src/deployment/azuredeploy.bicep b/src/deployment/azuredeploy.bicep index 14123b818..dd5c1fe8c 100644 --- a/src/deployment/azuredeploy.bicep +++ b/src/deployment/azuredeploy.bicep @@ -8,6 +8,9 @@ param clientSecret string param signedExpiry string param app_func_issuer string param app_func_audiences array +param cli_app_id string +param authority string +param tenant_domain string param multi_tenant_domain string param enable_remote_debugging bool = false param enable_profiler bool = false @@ -239,6 +242,9 @@ module functionSettings 'bicep-templates/function-settings.bicep' = { fuzz_storage_resource_id: storage.outputs.FuzzId keyvault_name: keyVaultName monitor_account_name: operationalInsights.outputs.monitorAccountName + cli_app_id: cli_app_id + authority: authority + tenant_domain: tenant_domain multi_tenant_domain: multi_tenant_domain enable_profiler: enable_profiler app_config_endpoint: featureFlags.outputs.AppConfigEndpoint diff --git a/src/deployment/bicep-templates/function-settings.bicep b/src/deployment/bicep-templates/function-settings.bicep index 2f65f8d5f..742f4f39d 100644 --- a/src/deployment/bicep-templates/function-settings.bicep +++ b/src/deployment/bicep-templates/function-settings.bicep @@ -8,6 +8,9 @@ param app_insights_key string @secure() param func_sas_url string +param cli_app_id string +param authority string +param tenant_domain string param multi_tenant_domain string @secure() @@ -37,7 +40,7 @@ resource function 'Microsoft.Web/sites@2021-02-01' existing = { } var enable_profilers = enable_profiler ? { - APPINSIGHTS_PROFILERFEATURE_VERSION : '1.0.0' + APPINSIGHTS_PROFILERFEATURE_VERSION: '1.0.0' DiagnosticServices_EXTENSION_VERSION: '~3' } : {} @@ -52,6 +55,9 @@ resource functionSettings 'Microsoft.Web/sites/config@2021-03-01' = { APPINSIGHTS_APPID: app_insights_app_id ONEFUZZ_TELEMETRY: telemetry AzureWebJobsStorage: func_sas_url + CLI_APP_ID: cli_app_id + AUTHORITY: authority + TENANT_DOMAIN: tenant_domain MULTI_TENANT_DOMAIN: multi_tenant_domain AzureWebJobsDisableHomepage: 'true' AzureSignalRConnectionString: signal_r_connection_string @@ -66,5 +72,5 @@ resource functionSettings 'Microsoft.Web/sites/config@2021-03-01' = { ONEFUZZ_KEYVAULT: keyvault_name ONEFUZZ_OWNER: owner ONEFUZZ_CLIENT_SECRET: client_secret - }, enable_profilers) + }, enable_profilers) } diff --git a/src/deployment/bicep-templates/function.bicep b/src/deployment/bicep-templates/function.bicep index 617e8366e..a6e695ffb 100644 --- a/src/deployment/bicep-templates/function.bicep +++ b/src/deployment/bicep-templates/function.bicep @@ -23,7 +23,6 @@ param diagnostics_log_level string param log_retention int param linux_fx_version string - var siteconfig = (use_windows) ? { } : { linuxFxVersion: linux_fx_version @@ -57,17 +56,17 @@ resource function 'Microsoft.Web/sites@2021-03-01' = { type: 'SystemAssigned' } properties: union({ - siteConfig: union(siteconfig, commonSiteConfig) - httpsOnly: true - serverFarmId: server_farm_id - clientAffinityEnabled: true - }, extraProperties) + siteConfig: union(siteconfig, commonSiteConfig) + httpsOnly: true + serverFarmId: server_farm_id + clientAffinityEnabled: true + }, extraProperties) } resource funcAuthSettings 'Microsoft.Web/sites/config@2021-03-01' = { name: 'authsettingsV2' properties: { - login:{ + login: { tokenStore: { enabled: true } @@ -75,6 +74,7 @@ resource funcAuthSettings 'Microsoft.Web/sites/config@2021-03-01' = { globalValidation: { unauthenticatedClientAction: 'RedirectToLoginPage' requireAuthentication: true + excludedPaths: [ '/api/config' ] } httpSettings: { requireHttps: true diff --git a/src/deployment/config.json b/src/deployment/config.json index 3b138fa5f..c041d2b8d 100644 --- a/src/deployment/config.json +++ b/src/deployment/config.json @@ -1,6 +1,12 @@ { + "tenant_id": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "tenant_domain": "azurewebsites.net", + "multi_tenant_domain": "", + "cli_client_id": "72f1562a-8c0c-41ea-beb9-fa2b71c80134", "proxy_nsg_config": { - "allowed_ips": ["*"], + "allowed_ips": [ + "*" + ], "allowed_service_tags": [] } } \ No newline at end of file diff --git a/src/deployment/deploy.py b/src/deployment/deploy.py index cbeef8762..23d06a424 100644 --- a/src/deployment/deploy.py +++ b/src/deployment/deploy.py @@ -43,8 +43,9 @@ from azure.storage.blob import ( from msrest.serialization import TZ_UTC from deploylib.configuration import ( + Config, InstanceConfigClient, - NetworkSecurityConfig, + NsgRule, parse_rules, update_admins, update_allowed_aad_tenants, @@ -61,7 +62,6 @@ from deploylib.registration import ( get_application, get_service_principal, get_signed_in_user, - get_tenant_id, query_microsoft_graph, register_application, set_app_audience, @@ -74,10 +74,6 @@ from deploylib.registration import ( USER_READ_PERMISSION = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" MICROSOFT_GRAPH_APP_ID = "00000003-0000-0000-c000-000000000000" -ONEFUZZ_CLI_AUTHORITY = ( - "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47" -) -COMMON_AUTHORITY = "https://login.microsoftonline.com/common" TELEMETRY_NOTICE = ( "Telemetry collection on stats and OneFuzz failures are sent to Microsoft. " "To disable, delete the ONEFUZZ_TELEMETRY application setting in the " @@ -139,7 +135,7 @@ class Client: location: str, application_name: str, owner: str, - nsg_config: str, + config: str, client_id: Optional[str], client_secret: Optional[str], app_zip: str, @@ -167,7 +163,7 @@ class Client: self.location = location self.application_name = application_name self.owner = owner - self.nsg_config = nsg_config + self.config = config self.app_zip = app_zip self.tools = tools self.instance_specific = instance_specific @@ -180,10 +176,6 @@ class Client: "client_id": client_id, "client_secret": client_secret, } - if self.multi_tenant_domain: - authority = COMMON_AUTHORITY - else: - authority = ONEFUZZ_CLI_AUTHORITY self.migrations = migrations self.export_appinsights = export_appinsights self.admins = admins @@ -196,9 +188,15 @@ class Client: self.host_dotnet_on_windows = host_dotnet_on_windows self.enable_profiler = enable_profiler + self.rules: List[NsgRule] = [] + + self.tenant_id = "" + self.tenant_domain = "" + self.authority = "" + self.cli_config: Dict[str, Union[str, UUID]] = { - "client_id": self.cli_app_id, - "authority": authority, + "client_id": "", + "authority": "", } machine = platform.machine() @@ -305,7 +303,7 @@ class Client: # The url to access the instance # This also represents the legacy identifier_uris of the application # registration - if self.multi_tenant_domain: + if self.multi_tenant_domain != "": return "https://%s/%s" % (self.multi_tenant_domain, self.application_name) else: return "https://%s.azurewebsites.net" % self.application_name @@ -316,14 +314,14 @@ class Client: # to be from an approved domain The format of this value is derived # from the default value proposed by azure when creating an application # registration api://{guid}/... - if self.multi_tenant_domain: + if self.multi_tenant_domain != "": return "api://%s/%s" % (self.multi_tenant_domain, self.application_name) else: return "api://%s.azurewebsites.net" % self.application_name def get_signin_audience(self) -> str: # https://docs.microsoft.com/en-us/azure/active-directory/develop/supported-accounts-validation - if self.multi_tenant_domain: + if self.multi_tenant_domain != "": return "AzureADMultipleOrgs" else: return "AzureADMyOrg" @@ -382,7 +380,7 @@ class Client: else: self.update_existing_app_registration(app, app_roles) - if self.multi_tenant_domain and app["signInAudience"] == "AzureADMyOrg": + if self.multi_tenant_domain != "" and app["signInAudience"] == "AzureADMyOrg": set_app_audience( app["id"], "AzureADMultipleOrgs", @@ -419,13 +417,10 @@ class Client: 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, + "authority": self.authority, } else: logger.error( @@ -437,14 +432,10 @@ class Client: else: onefuzz_cli_app = cli_app authorize_application(uuid.UUID(onefuzz_cli_app["appId"]), app["appId"]) - if self.multi_tenant_domain: - authority = COMMON_AUTHORITY - else: - tenant_id = get_tenant_id(self.get_subscription_id()) - authority = "https://login.microsoftonline.com/%s" % tenant_id + self.cli_config = { "client_id": onefuzz_cli_app["appId"], - "authority": authority, + "authority": self.authority, } # ensure replyURLs is set properly @@ -641,7 +632,7 @@ class Client: # Add --custom_domain value to Allowed token audiences setting if self.custom_domain: - if self.multi_tenant_domain: + if self.multi_tenant_domain != "": root_domain = self.multi_tenant_domain else: root_domain = "%s.azurewebsites.net" % self.application_name @@ -653,7 +644,7 @@ class Client: app_func_audiences.extend(custom_domains) - if self.multi_tenant_domain: + if self.multi_tenant_domain != "": # clear the value in the Issuer Url field: # https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aadhttpclient-enterpriseapi-multitenant app_func_issuer = "" @@ -680,6 +671,9 @@ class Client: "clientSecret": {"value": self.results["client_secret"]}, "app_func_issuer": {"value": app_func_issuer}, "signedExpiry": {"value": expiry}, + "cli_app_id": {"value": self.cli_app_id}, + "authority": {"value": self.authority}, + "tenant_domain": {"value": self.tenant_domain}, "multi_tenant_domain": multi_tenant_domain, "workbookData": {"value": self.workbook_data}, "enable_remote_debugging": {"value": self.host_dotnet_on_windows}, @@ -778,6 +772,38 @@ class Client: table_service = TableService(account_name=name, account_key=key) migrate(table_service, self.migrations) + def parse_config(self) -> None: + logger.info("parsing config: %s", self.config) + + if self.config: + + with open(self.config, "r") as template_handle: + config_template = json.load(template_handle) + + try: + config = Config(config_template) + self.rules = parse_rules(config) + + ## Values provided via the CLI will override what's in the config.json + if self.authority == "": + self.authority = ( + "https://login.microsoftonline.com/" + config.tenant_id + ) + if self.tenant_domain == "": + self.tenant_domain = config.tenant_domain + if self.multi_tenant_domain == "": + self.multi_tenant_domain = config.multi_tenant_domain + if self.cli_app_id == "": + self.cli_app_id = config.cli_client_id + + except Exception as ex: + logging.info( + "An Exception was encountered while parsing config file: %s", ex + ) + raise Exception( + "config and sub-values were not properly included in config." + ) + def set_instance_config(self) -> None: logger.info("setting instance config") name = self.results["deploy"]["func_name"]["value"] @@ -787,26 +813,7 @@ class Client: config_client = InstanceConfigClient(table_service, self.application_name) - if self.nsg_config: - logger.info("deploying arm template: %s", self.nsg_config) - - with open(self.nsg_config, "r") as template_handle: - config_template = json.load(template_handle) - - try: - config = NetworkSecurityConfig(config_template) - rules = parse_rules(config) - except Exception as ex: - logging.info( - "An Exception was encountered while parsing nsg_config file: %s", ex - ) - raise Exception( - "proxy_nsg_config and sub-values were not properly included in config." - + "Please submit a configuration resembling" - + " { 'proxy_nsg_config': { 'allowed_ips': [], 'allowed_service_tags': [] } }" - ) - - update_nsg(config_client, rules) + update_nsg(config_client, self.rules) if self.admins: update_admins(config_client, self.admins) @@ -1136,18 +1143,11 @@ class Client: "config", "--endpoint", f"https://{self.application_name}.azurewebsites.net", - "--authority", - str(self.cli_config["authority"]), - "--client_id", - str(self.cli_config["client_id"]), ] if "client_secret" in self.cli_config: cmd += ["--client_secret", "YOUR_CLIENT_SECRET_HERE"] - if self.multi_tenant_domain: - cmd += ["--tenant_domain", str(self.multi_tenant_domain)] - as_str = " ".join(cmd) logger.info(f"Update your CLI config via: {as_str}") @@ -1174,6 +1174,7 @@ def lower_case(arg: str) -> str: def main() -> None: rbac_only_states = [ + ("parse_config", Client.parse_config), ("check_region", Client.check_region), ("rbac", Client.setup_rbac), ("eventgrid", Client.remove_eventgrid), @@ -1200,7 +1201,7 @@ def main() -> None: parser.add_argument("resource_group") parser.add_argument("application_name", type=lower_case) parser.add_argument("owner") - parser.add_argument("nsg_config") + parser.add_argument("config") parser.add_argument( "--bicep-template", type=arg_file, @@ -1272,7 +1273,7 @@ def main() -> None: parser.add_argument( "--multi_tenant_domain", type=str, - default=None, + default="", help="enable multi-tenant authentication with this tenant domain", ) parser.add_argument( @@ -1299,7 +1300,7 @@ def main() -> None: parser.add_argument( "--cli_app_id", type=str, - default="72f1562a-8c0c-41ea-beb9-fa2b71c80134", + default="", help="CLI App Registration to be used during deployment.", ) parser.add_argument( @@ -1337,7 +1338,7 @@ def main() -> None: location=args.location, application_name=args.application_name, owner=args.owner, - nsg_config=args.nsg_config, + config=args.config, client_id=args.client_id, client_secret=args.client_secret, app_zip=args.app_zip, diff --git a/src/deployment/deploylib/configuration.py b/src/deployment/deploylib/configuration.py index dd1bcacd2..4c56ee7fe 100644 --- a/src/deployment/deploylib/configuration.py +++ b/src/deployment/deploylib/configuration.py @@ -48,12 +48,17 @@ class InstanceConfigClient: self.enable_storage_client_logging() -class NetworkSecurityConfig: +class Config: + cli_client_id: str + tenant_id: str + tenant_domain: str + multi_tenant_domain: str allowed_ips: List[str] allowed_service_tags: List[str] def __init__(self, config: Any): self.parse_nsg_json(config) + self.parse_endpoint_json(config) def parse_nsg_json(self, config: Any) -> None: if not isinstance(config, Dict): @@ -107,6 +112,66 @@ class NetworkSecurityConfig: self.allowed_ips = proxy_config["allowed_ips"] self.allowed_service_tags = proxy_config["allowed_service_tags"] + def parse_endpoint_json(self, config: Any) -> None: + + if "cli_client_id" not in config: + raise Exception( + "CLI client_id not provided as valid key. Please Provide Valid Config." + ) + + if ( + not isinstance(config["cli_client_id"], str) + or config["cli_client_id"] == "" + ): + raise Exception( + "client_id is not a string. Please provide valid client_id." + ) + + try: + UUID(config["cli_client_id"]) + except ValueError: + raise Exception( + "client_id is not a valid UUID. Please provide valid client_id." + ) + + if "tenant_id" not in config: + raise Exception( + "tenant_id not provided as valid key. Please provide valid config." + ) + + if not isinstance(config["tenant_id"], str) or config["tenant_id"] == "": + raise Exception( + "tenant_id is not a string. Please provide valid tenant_id." + ) + + if "tenant_domain" not in config: + raise Exception( + "tenant_domain not provided as valid key. Please provide valid config." + ) + + if ( + not isinstance(config["tenant_domain"], str) + or config["tenant_domain"] == "" + ): + raise Exception( + "tenant_domain is not a string. Please provide valid tenant_domain." + ) + + if "multi_tenant_domain" not in config: + raise Exception( + "multi_tenant_domain not provided as valid key. Please provide valid config. If the instance is not multi-tenant, please provide an empty string." + ) + + if not isinstance(config["multi_tenant_domain"], str): + raise Exception( + "multi_tenant_domain is not a string. Please provide valid multi_tenant_domain. If the instance is not multi-tenant, please provide an empty string." + ) + + self.cli_client_id = config["cli_client_id"] + self.tenant_id = config["tenant_id"] + self.tenant_domain = config["tenant_domain"] + self.multi_tenant_domain = config["multi_tenant_domain"] + class NsgRule: rule: str @@ -175,7 +240,7 @@ def update_admins(config_client: InstanceConfigClient, admins: List[UUID]) -> No ) -def parse_rules(proxy_config: NetworkSecurityConfig) -> List[NsgRule]: +def parse_rules(proxy_config: Config) -> List[NsgRule]: allowed_ips = proxy_config.allowed_ips allowed_service_tags = proxy_config.allowed_service_tags diff --git a/src/deployment/deploylib/tests/test_deploy_config.py b/src/deployment/deploylib/tests/test_deploy_config.py index dc0bdf852..aba328ef5 100644 --- a/src/deployment/deploylib/tests/test_deploy_config.py +++ b/src/deployment/deploylib/tests/test_deploy_config.py @@ -6,7 +6,7 @@ import unittest from typing import Any -from deploylib.configuration import NetworkSecurityConfig +from deploylib.configuration import Config class DeployTests(unittest.TestCase): @@ -15,33 +15,33 @@ class DeployTests(unittest.TestCase): # Test Dictionary invalid_config: Any = "" with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test Empty Dic invalid_config = {} with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test Invalid Outer Keys invalid_config = {"": ""} with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test Inner Dictionary invalid_config = {"proxy_nsg_config": ""} with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test Inner Keys invalid_config = {"proxy_nsg_config": {}} with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test Inner Keys invalid_config = {"proxy_nsg_config": {"allowed_ips": ""}} with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test Inner Dict Values (lists) invalid_config = { "proxy_nsg_config": {"allowed_ips": [], "allowed_service_tags": ""} } with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) # Test List Values invalid_config = { "proxy_nsg_config": { @@ -50,19 +50,19 @@ class DeployTests(unittest.TestCase): } } with self.assertRaises(Exception): - NetworkSecurityConfig(invalid_config) + Config(invalid_config) ## Test Valid Configs # Test Empty Lists valid_config: Any = { "proxy_nsg_config": {"allowed_ips": [], "allowed_service_tags": []} } - NetworkSecurityConfig(valid_config) + Config(valid_config) # Test Wild Card Lists valid_config = { "proxy_nsg_config": {"allowed_ips": ["*"], "allowed_service_tags": []} } - NetworkSecurityConfig(valid_config) + Config(valid_config) # Test IPs Lists valid_config = { "proxy_nsg_config": { @@ -70,7 +70,7 @@ class DeployTests(unittest.TestCase): "allowed_service_tags": [], } } - NetworkSecurityConfig(valid_config) + Config(valid_config) # Test Tags Lists valid_config = { "proxy_nsg_config": { @@ -78,7 +78,7 @@ class DeployTests(unittest.TestCase): "allowed_service_tags": ["Internet"], } } - NetworkSecurityConfig(valid_config) + Config(valid_config) if __name__ == "__main__": diff --git a/src/pytypes/onefuzztypes/responses.py b/src/pytypes/onefuzztypes/responses.py index 24f62151b..7926deb2d 100644 --- a/src/pytypes/onefuzztypes/responses.py +++ b/src/pytypes/onefuzztypes/responses.py @@ -52,6 +52,12 @@ class Info(BaseResponse): insights_instrumentation_key: Optional[str] +class Config(BaseResponse): + authority: str + client_id: str + tenant_domain: str + + class ContainerInfoBase(BaseResponse): name: str metadata: Optional[Dict[str, str]]