diff --git a/src/deployment/azuredeploy.bicep b/src/deployment/azuredeploy.bicep index 043ae66fd89..951271a4673 100644 --- a/src/deployment/azuredeploy.bicep +++ b/src/deployment/azuredeploy.bicep @@ -4,7 +4,7 @@ param clientId string param clientSecret string param signedExpiry string param app_func_issuer string -param app_func_audience string +param app_func_audiences array param multi_tenant_domain string param location string = resourceGroup().location @@ -31,8 +31,8 @@ var scaleset_identity = '${name}-scalesetid' var signalr_name = 'onefuzz-${suffix}' var storage_account_sas = { signedExpiry: signedExpiry - signedPermissions: 'rwdlacup' - signedResrouceTypes: 'sco' + signedPermission: 'rwdlacup' + signedResourceTypes: 'sco' signedServices: 'bfqt' } @@ -71,35 +71,29 @@ var roleAssignmentsParams = [ ] var onefuzz = { - severitiesAtMostInfo: { - parameters: [] - output: { - type: 'array' - value: [ + severitiesAtMostInfo: [ { severity: 'emerg' } { - severity: 'alert' + severity: 'alert' } { - severity: 'crit' + severity: 'crit' } { - severity: 'err' + severity: 'err' } { - severity: 'warning' + severity: 'warning' } { - severity: 'notice' + severity: 'notice' } { - severity: 'info' + severity: 'info' } - ] - } - } + ] } resource scalesetIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { @@ -221,11 +215,6 @@ resource autoscaleSettings 'Microsoft.Insights/autoscalesettings@2015-04-01' = { } } -resource insightsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { - name: name - location: location -} - var linuxDataSources = [ { name: 'syslogDataSourcesKern' @@ -313,9 +302,13 @@ resource insightsMonitorAccount 'Microsoft.OperationalInsights/workspaces@2021-0 resource vmInsights 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = { name: 'VMInsights(${monitorAccountName})' + location: location dependsOn: [ insightsMonitorAccount ] + properties: { + workspaceResourceId: resourceId('Microsoft.OperationalInsights/workspaces', monitorAccountName) + } plan: { name: 'VMInsights(${monitorAccountName})' publisher: 'Microsoft' @@ -331,7 +324,7 @@ resource insightsComponents 'Microsoft.Insights/components@2020-02-02' = { properties: { Application_Type: 'other' RetentionInDays: log_retention - WorkspaceResourceId: insightsWorkspace.id + WorkspaceResourceId: insightsMonitorAccount.id } tags: { OWNER: owner @@ -351,17 +344,6 @@ resource insightsWorkbooks 'Microsoft.Insights/workbooks@2021-08-01' = { } } -var storageAccountsParams = [ - { - name: storageAccountName - } - { - name: storageAccountNameFunc - } -] - -var storageAccountIndex = 0 -var storageAccountFuncIndex = 1 var storageAccountFuncContainersParams = [ 'vm-scripts' 'repro-scripts' @@ -380,8 +362,10 @@ var storageAccountFuncQueuesParams = [ 'signalr-events' ] -resource storageAccounts 'Microsoft.Storage/storageAccounts@2021-08-01' = [for p in storageAccountsParams : { - name: p.name +var fileChangesIndex = 0 + +resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: storageAccountName location: location sku: { name: 'Standard_LRS' @@ -395,54 +379,84 @@ resource storageAccounts 'Microsoft.Storage/storageAccounts@2021-08-01' = [for p tags: { OWNER: owner } -}] -resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2021-08-01' = [for (p,i) in storageAccountsParams : { - name: 'default' - properties: { - deleteRetentionPolicy: { - enabled: true - days: 30 + resource blobServices 'blobServices' = { + name: 'default' + properties: { + deleteRetentionPolicy: { + enabled: true + days: 30 + } } } - parent: storageAccounts[i] -}] +} + +resource storageAccountFunc 'Microsoft.Storage/storageAccounts@2021-08-01' = { + name: storageAccountNameFunc + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + supportsHttpsTrafficOnly: true + accessTier: 'Hot' + allowBlobPublicAccess: false + } + tags: { + OWNER: owner + } -resource storageAccountFuncQueueServices 'Microsoft.Storage/storageAccounts/queueServices@2021-08-01' = { - name: 'funcQueues' - parent: storageAccounts[storageAccountFuncIndex] + resource blobServices 'blobServices' = { + name: 'default' + properties: { + deleteRetentionPolicy: { + enabled: true + days: 30 + } + } + } } -resource storageAccountFunBlobContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = [for c in storageAccountFuncContainersParams: { - name: c - parent: blobServices[storageAccountFuncIndex] +resource storageAccountFuncQueues 'Microsoft.Storage/storageAccounts/queueServices/queues@2021-08-01' = [for q in storageAccountFuncQueuesParams: { + name: '${storageAccountNameFunc}/default/${q}' + dependsOn: [ + storageAccountFunc + ] }] -resource storageAccountFuncQueues 'Microsoft.Storage/storageAccounts/queueServices/queues@2021-08-01' = [for q in storageAccountFuncQueuesParams: { - name: q - parent: storageAccountFuncQueueServices +resource storageAccountFunBlobContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = [for c in storageAccountFuncContainersParams: { + name: '${storageAccountNameFunc}/default/${c}' + dependsOn: [ + storageAccountFunc + ] }] +// try to make role assignments to deploy as late as possible in order to has principalId ready resource roleAssigments 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = [for r in roleAssignmentsParams: { name: guid('${resourceGroup().id}${r.suffix}') properties: { roleDefinitionId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${r.role}' - principalId: reference(resourceId('Microsoft.Web/sites', name), '2018-02-01', 'Full').identity.principalId + principalId: reference(pythonFunction.id, pythonFunction.apiVersion, 'Full').identity.principalId } dependsOn: [ - pythonFunction + eventSubscriptions + keyVault + serverFarms ] }] -//this gets configured differently from roleAssignments -resource readBlogUserAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = { +// try to make role assignments to deploy as late as possible in order to has principalId ready +resource readBlobUserAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = { name: guid('${resourceGroup().id}-user_managed_idenity_read_blob') properties: { roleDefinitionId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${StorageBlobDataReader}' - principalId: reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', scaleset_identity), '2018-11-30', 'Full').properties.principalId + principalId: reference(scalesetIdentity.id, scalesetIdentity.apiVersion, 'Full').properties.principalId } dependsOn: [ - storageAccounts[storageAccountFuncIndex] + eventSubscriptions + keyVault + serverFarms ] } @@ -477,32 +491,42 @@ resource signalR 'Microsoft.SignalRService/signalR@2021-10-01' = { resource eventGridSystemTopics 'Microsoft.EventGrid/systemTopics@2021-12-01' = { name: fuzz_blob_topic_name + dependsOn: [ + storageAccountFuncQueues[fileChangesIndex] + storageAccountFunc + ] location: location properties: { - source: storageAccounts[storageAccountIndex].id + source: storageAccount.id topicType: 'microsoft.storage.storageaccounts' } - resource evetnSubscriptions 'eventSubscriptions' = { - name: 'onefuzz1_subscription' - properties: { - destination: { - properties: { - resourceId: storageAccounts[storageAccountFuncIndex].id - queueName: 'file-changes' - } - endpointType: 'StorageQueue' - } - filter: { - includedEventTypes: [ - 'Microsoft.Storage.BlobCreated' - 'Microsoft.Storage.BlobDeleted' - ] - } - eventDeliverySchema: 'EventGridSchema' - retryPolicy: { - maxDeliveryAttempts: 30 - eventTimeToLiveInMinutes: 1440 +} + +resource eventSubscriptions 'Microsoft.EventGrid/systemTopics/eventSubscriptions@2021-12-01' = { + name: 'onefuzz1_subscription' + parent: eventGridSystemTopics + dependsOn: [ + storageAccountFuncQueues[fileChangesIndex] + storageAccount + ] + properties: { + destination: { + properties: { + resourceId: storageAccountFunc.id + queueName: storageAccountFuncQueuesParams[fileChangesIndex] } + endpointType: 'StorageQueue' + } + filter: { + includedEventTypes: [ + 'Microsoft.Storage.BlobCreated' + 'Microsoft.Storage.BlobDeleted' + ] + } + eventDeliverySchema: 'EventGridSchema' + retryPolicy: { + maxDeliveryAttempts: 30 + eventTimeToLiveInMinutes: 1440 } } } @@ -514,7 +538,7 @@ resource funcLogs 'Microsoft.Web/sites/config@2021-03-01' = { azureBlobStorage: { level: diagnosticsLogLevel retentionInDays: log_retention - sasUrl: '${storageAccounts[storageAccountIndex].properties.primaryEndpoints.blob}app-logs?${storageAccounts[storageAccountFuncIndex].listAccountSas('2021-08-01', storage_account_sas).accountSasToken}' + sasUrl: '${storageAccountFunc.properties.primaryEndpoints.blob}app-logs?${storageAccountFunc.listAccountSas('2021-08-01', storage_account_sas).accountSasToken}' } } } @@ -546,9 +570,7 @@ resource funcAuthSettings 'Microsoft.Web/sites/config@2021-03-01' = { clientSecretSettingName: 'ONEFUZZ_CLIENT_SECRET' } validation: { - allowedAudiences: [ - app_func_audience - ] + allowedAudiences: app_func_audiences } } } @@ -595,7 +617,7 @@ resource pythonFunction 'Microsoft.Web/sites@2021-03-01' = { } { name: 'AzureWebJobsStorage' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccounts[storageAccountFuncIndex].name};AccountKey=${storageAccounts[storageAccountFuncIndex].listKeys().keys[0]};EndpointSuffix=core.windows.net' + value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountFunc.name};AccountKey=${storageAccountFunc.listKeys().keys[0].value};EndpointSuffix=core.windows.net' } { name: 'MULTI_TENANT_DOMAIN' @@ -627,11 +649,11 @@ resource pythonFunction 'Microsoft.Web/sites@2021-03-01' = { } { name: 'ONEFUZZ_DATA_STORAGE' - value: storageAccounts[storageAccountIndex].id + value: storageAccount.id } { name: 'ONEFUZZ_FUNC_STORAGE' - value: storageAccounts[storageAccountFuncIndex].id + value: storageAccountFunc.id } { name: 'ONEFUZZ_MONITOR' @@ -665,13 +687,15 @@ resource pythonFunction 'Microsoft.Web/sites@2021-03-01' = { } } -output fuzz_storage string = storageAccounts[storageAccountIndex].id +var fuzz_key = storageAccount.listKeys().keys[0].value +output fuzz_storage string = storageAccount.id output fuzz_name string = storageAccountName -output fuzz_key string = storageAccounts[storageAccountIndex].listKeys().keys[0].value +output fuzz_key string = fuzz_key -output func_storage string = storageAccounts[storageAccountFuncIndex].id +var func_key = storageAccountFunc.listKeys().keys[0].value +output func_storage string = storageAccountFunc.id output func_name string = storageAccountNameFunc -output func_key string = storageAccounts[storageAccountFuncIndex].listKeys().keys[0].value +output func_key string = func_key output scaleset_identity string = scaleset_identity output tenant_id string = tenantId diff --git a/src/deployment/azuredeploy.json b/src/deployment/azuredeploy.json index 83cbe9c72ce..b839272cbe8 100644 --- a/src/deployment/azuredeploy.json +++ b/src/deployment/azuredeploy.json @@ -921,31 +921,31 @@ } ], "outputs": { - "fuzz-storage": { + "fuzz_storage": { "type": "string", "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" }, - "fuzz-name": { + "fuzz_name": { "type": "string", "value": "[variables('storageAccountName')]" }, - "fuzz-key": { + "fuzz_key": { "type": "string", "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value]" }, - "func-name": { + "func_name": { "type": "string", "value": "[variables('storageAccountNameFunc')]" }, - "func-storage": { + "func_storage": { "type": "string", "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFunc'))]" }, - "func-key": { + "func_key": { "type": "string", "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFunc')), '2019-06-01').keys[0].value]" }, - "scaleset-identity": { + "scaleset_identity": { "type": "string", "value": "[variables('scaleset_identity')]" }, diff --git a/src/deployment/deploy.py b/src/deployment/deploy.py index 645ac695b5e..0652162af9b 100644 --- a/src/deployment/deploy.py +++ b/src/deployment/deploy.py @@ -100,14 +100,31 @@ def gen_guid() -> str: return str(uuid.uuid4()) + def bicep_to_arm(bicep_template: str) -> str: - from azure.cli.core import ( - get_default_cli - ) + from azure.cli.core import get_default_cli + az_cli = get_default_cli() - az_cli.invoke(['bicep', 'install']) - az_cli.invoke(['bicep', 'build', '--file', bicep_template, '--outfile', 'azuredeploy-bicep.json']) - return 'azuredeploy-bicep.json' + az_cli.invoke(["bicep", "install"]) + az_cli.invoke( + [ + "bicep", + "build", + "--file", + bicep_template, + "--outfile", + "azuredeploy-bicep.json", + ] + ) + from importlib import reload + + # az_cli hijacks logging, so need to reset it + logging.shutdown() + reload(logging) + global logger + logger = logging.getLogger("deploy") + return "azuredeploy-bicep.json" + class Client: def __init__( @@ -167,7 +184,7 @@ def __init__( if arm_or_bicep_template: file_name, file_extension = os.path.splitext(arm_or_bicep_template) - if file_extension == '.bicep': + if file_extension == ".bicep": self.arm_template = bicep_to_arm(arm_or_bicep_template) else: self.arm_template = arm_or_bicep_template @@ -225,37 +242,42 @@ def check_region(self) -> None: client = ResourceManagementClient( credential, subscription_id=self.get_subscription_id() ) - providers = {x.namespace: x for x in client.providers.list()} - + providers = {x.namespace.lower(): x for x in client.providers.list()} unsupported = [] + # we cannot validate site/config resources since they require resource group + # to exist. check_region only validates subscription level resources. + resource_group_level_resources = ["sites/config"] + for resource in arm["resources"]: - namespace, name = resource["type"].split("/", 1) + namespace, name = resource["type"].lower().split("/", 1) # resource types are in the form of a/b/c.... # only the top two are listed as resource types within providers name = "/".join(name.split("/")[:2]) - if namespace not in providers: unsupported.append("Unsupported provider: %s" % namespace) continue provider = providers[namespace] - resource_types = {x.resource_type: x for x in provider.resource_types} - if name not in resource_types: - unsupported.append( - "Unsupported resource type: %s/%s" % (namespace, name) - ) - continue + resource_types = { + x.resource_type.lower(): x for x in provider.resource_types + } + if name not in resource_group_level_resources: + if name not in resource_types: + unsupported.append( + "Unsupported resource type: %s/%s" % (namespace, name) + ) + continue - resource_type = resource_types[name] - if ( - location not in resource_type.locations - and len(resource_type.locations) > 0 - ): - unsupported.append( - "%s/%s is unsupported in %s" % (namespace, name, self.location) - ) + resource_type = resource_types[name] + if ( + location not in resource_type.locations + and len(resource_type.locations) > 0 + ): + unsupported.append( + "%s/%s is unsupported in %s" % (namespace, name, self.location) + ) if unsupported: print("The following resources required by onefuzz are not supported:") @@ -619,7 +641,7 @@ def assign_scaleset_identity_role(self) -> None: logger.info("assigning the user managed identity role") assign_instance_app_role( self.application_name, - self.results["deploy"]["scaleset-identity"]["value"], + self.results["deploy"]["scaleset_identity"]["value"], self.get_subscription_id(), OnefuzzAppRole.ManagedNode, ) @@ -660,15 +682,15 @@ def assign_user_access(self) -> None: def apply_migrations(self) -> None: logger.info("applying database migrations") - name = self.results["deploy"]["func-name"]["value"] - key = self.results["deploy"]["func-key"]["value"] + name = self.results["deploy"]["func_name"]["value"] + key = self.results["deploy"]["func_key"]["value"] table_service = TableService(account_name=name, account_key=key) migrate(table_service, self.migrations) def set_instance_config(self) -> None: logger.info("setting instance config") - name = self.results["deploy"]["func-name"]["value"] - key = self.results["deploy"]["func-key"]["value"] + name = self.results["deploy"]["func_name"]["value"] + key = self.results["deploy"]["func_key"]["value"] tenant = UUID(self.results["deploy"]["tenant_id"]["value"]) table_service = TableService(account_name=name, account_key=key) @@ -761,8 +783,8 @@ def add_instance_id(self) -> None: container_name = "base-config" blob_name = "instance_id" - account_name = self.results["deploy"]["func-name"]["value"] - key = self.results["deploy"]["func-key"]["value"] + account_name = self.results["deploy"]["func_name"]["value"] + key = self.results["deploy"]["func_key"]["value"] account_url = "https://%s.blob.core.windows.net" % account_name client = BlobServiceClient(account_url, credential=key) if container_name not in [x["name"] for x in client.list_containers()]: @@ -787,8 +809,8 @@ def add_log_export(self) -> None: container_name = "app-insights" logger.info("adding appinsight log export") - account_name = self.results["deploy"]["func-name"]["value"] - key = self.results["deploy"]["func-key"]["value"] + account_name = self.results["deploy"]["func_name"]["value"] + key = self.results["deploy"]["func_key"]["value"] account_url = "https://%s.blob.core.windows.net" % account_name client = BlobServiceClient(account_url, credential=key) if container_name not in [x["name"] for x in client.list_containers()]: @@ -847,8 +869,8 @@ def add_log_export(self) -> None: def upload_tools(self) -> None: logger.info("uploading tools from %s", self.tools) - account_name = self.results["deploy"]["func-name"]["value"] - key = self.results["deploy"]["func-key"]["value"] + account_name = self.results["deploy"]["func_name"]["value"] + key = self.results["deploy"]["func_key"]["value"] account_url = "https://%s.blob.core.windows.net" % account_name client = BlobServiceClient(account_url, credential=key) if "tools" not in [x["name"] for x in client.list_containers()]: @@ -884,8 +906,8 @@ def upload_tools(self) -> None: def upload_instance_setup(self) -> None: logger.info("uploading instance-specific-setup from %s", self.instance_specific) - account_name = self.results["deploy"]["func-name"]["value"] - key = self.results["deploy"]["func-key"]["value"] + account_name = self.results["deploy"]["func_name"]["value"] + key = self.results["deploy"]["func_key"]["value"] account_url = "https://%s.blob.core.windows.net" % account_name client = BlobServiceClient(account_url, credential=key) if "instance-specific-setup" not in [ @@ -930,8 +952,8 @@ def upload_instance_setup(self) -> None: def upload_third_party(self) -> None: logger.info("uploading third-party tools from %s", self.third_party) - account_name = self.results["deploy"]["fuzz-name"]["value"] - key = self.results["deploy"]["fuzz-key"]["value"] + account_name = self.results["deploy"]["fuzz_name"]["value"] + key = self.results["deploy"]["fuzz_key"]["value"] account_url = "https://%s.blob.core.windows.net" % account_name client = BlobServiceClient(account_url, credential=key) @@ -1182,7 +1204,6 @@ def main() -> None: logger.error(FUNC_TOOLS_ERROR) sys.exit(1) - client = Client( resource_group=args.resource_group, location=args.location,