diff --git a/infrastructure/app-registrations/sp-non-prod.json b/infrastructure/app-registrations/sp-non-prod.json new file mode 100644 index 000000000..b31bc2971 --- /dev/null +++ b/infrastructure/app-registrations/sp-non-prod.json @@ -0,0 +1,52 @@ +{ + "id": "fab7c06b-0779-4eee-b628-87fd25361f80", + // appId: b6dc16db-85f7-41e4-afad-5f1a07c5961c + + "displayName": "DevOps SP - Fusion resource allocation - non-production", + "notes": "DevOps Service Principal - Non-Production\nFusion Resource Allocation solution.\n\nShould have access to non-production environments/resources.", + "serviceManagementReference": "107767", + "tags": ["fra", "fusion", "iac-automation", "production", "backup-enabled"], + "web": { + "homePageUrl": "https://statoil-proview.visualstudio.com/Fusion%20Resource%20Allocation/_build", + "implicitGrantSettings": { + "enableAccessTokenIssuance": false, + "enableIdTokenIssuance": false + }, + "logoutUrl": null, + "redirectUriSettings": [], + "redirectUris": [] + }, + "requiredResourceAccess": [ + { + // Statoil ProView Test + "resourceAppId": "5a842df8-3238-415d-b168-9f16a6a6031b", + "resourceAccess": [ + { + // Fusion.Apps.Create + "id": "885260ee-8b4c-4841-863f-85f5289fcbfb", + "type": "Role" + } + ] + }, + { + // Fusion Infrastructure Support + "resourceAppId": "8ab89850-219a-4929-a726-5a7a496efac2", + "resourceAccess": [ + { + // Fusion.Infrastructure.Database.Manage + "id": "345143e5-eecd-47ff-a6e5-d8b8f54cad5f", + "type": "Role" + } + ] + }, + { + "resourceAppId": "00000003-0000-0000-c000-000000000000", + "resourceAccess": [ + { + "id": "18a4783c-866b-4cc7-a460-3d5e5662c884", + "type": "Role" + } + ] + } + ] + } \ No newline at end of file diff --git a/infrastructure/app-registrations/sp-prod.json b/infrastructure/app-registrations/sp-prod.json new file mode 100644 index 000000000..6d859ac1c --- /dev/null +++ b/infrastructure/app-registrations/sp-prod.json @@ -0,0 +1,52 @@ +{ + "displayName": "DevOps SP - Fusion resource allocation - production", + "id": "555b9404-0898-421a-b094-41c5b671bc84", + // appId: 3363e160-679c-48c0-8b87-022352cd565c + "notes": "DevOps Service Principal - Non-Production\nFusion Resource Allocation solution.\n\nShould have access to production environments/resources.\n- azure resource groups\n- fusion app creation\n- fusion infra management", + "serviceManagementReference": "107767", + "tags": ["fra", "fusion", "iac-automation", "production", "backup-enabled"], + + "web": { + "homePageUrl": "https://statoil-proview.visualstudio.com/Fusion%20Resource%20Allocation/_build", + "implicitGrantSettings": { + "enableAccessTokenIssuance": false, + "enableIdTokenIssuance": false + }, + "logoutUrl": null, + "redirectUriSettings": [], + "redirectUris": [] + }, + "requiredResourceAccess": [ + { + // Statoil ProView + "resourceAppId": "97978493-9777-4d48-b38a-67b0b9cd88d2", + "resourceAccess": [ + { + // Fusion.Apps.Create + "id": "885260ee-8b4c-4841-863f-85f5289fcbfb", + "type": "Role" + } + ] + }, + { + "resourceAppId": "00000003-0000-0000-c000-000000000000", + "resourceAccess": [ + { + "id": "18a4783c-866b-4cc7-a460-3d5e5662c884", + "type": "Role" + } + ] + }, + { + // Fusion Infrastructure Support, + "resourceAppId": "8ab89850-219a-4929-a726-5a7a496efac2", + "resourceAccess": [ + { + // Fusion.Infrastructure.Database.Manage + "id": "345143e5-eecd-47ff-a6e5-d8b8f54cad5f", + "type": "Role" + } + ] + } + ] + } \ No newline at end of file diff --git a/infrastructure/arm/database.template.json b/infrastructure/arm/database.template.json new file mode 100644 index 000000000..ca297e61f --- /dev/null +++ b/infrastructure/arm/database.template.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "env-name": { "type": "string" }, + + "sqlserver_name": { + "defaultValue": "fusion-test-sqlserver", + "type": "String" + }, + "sql-elastic-pool-id": { + "type": "string" + }, + "fcore-env": { "type": "string", "allowedValues": ["test", "prod"] } + }, + "variables": { + "db-name": "[concat('Fusion-Apps-Resources-', toUpper(parameters('env-name')), '-DB')]", + "summary-db-name": "[concat('sqldb-fapp-fra-summary-db-', toUpper(parameters('env-name')))]" + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases", + "apiVersion": "2019-06-01-preview", + "name": "[concat(parameters('sqlserver_name'), '/', variables('db-name'))]", + "location": "northeurope", + "tags": { + "fusion-app": "resources", + "fusion-app-env": "[toLower(parameters('env-name'))]", + "fusion-app-component-type": "db", + "fusion-app-component-id": "[concat('resources-api-db-', toLower(parameters('env-name')))]", + "adm-acg": "acg-fusion-resource-allocation", + "dev-acg": "acg-fusion-resource-allocation", + "fcore-env": "[parameters('fcore-env')]" + }, + "kind": "v12.0,user,pool", + "properties": { + "collation": "SQL_Latin1_General_CP1_CI_AS", + "maxSizeBytes": 268435456000, + "elasticPoolId": "[parameters('sql-elastic-pool-id')]", + "catalogCollation": "SQL_Latin1_General_CP1_CI_AS", + "zoneRedundant": false, + "readScale": "Disabled", + "readReplicaCount": 0, + "storageAccountType": "GRS" + } + }, + { + "type": "Microsoft.Sql/servers/databases", + "apiVersion": "2019-06-01-preview", + "name": "[concat(parameters('sqlserver_name'), '/', variables('summary-db-name'))]", + "location": "northeurope", + "tags": { + "fusion-app": "resources", + "fusion-app-env": "[toLower(parameters('env-name'))]", + "fusion-app-component-type": "db", + "fusion-app-component-id": "[concat('summary-api-db-', toLower(parameters('env-name')))]", + "adm-acg": "acg-fusion-resource-allocation", + "dev-acg": "acg-fusion-resource-allocation", + "fcore-env": "[parameters('fcore-env')]" + }, + "kind": "v12.0,user,pool", + "properties": { + "collation": "SQL_Latin1_General_CP1_CI_AS", + "maxSizeBytes": 268435456000, + "elasticPoolId": "[parameters('sql-elastic-pool-id')]", + "catalogCollation": "SQL_Latin1_General_CP1_CI_AS", + "zoneRedundant": false, + "readScale": "Disabled", + "readReplicaCount": 0, + "storageAccountType": "GRS" + } + } + ] +} \ No newline at end of file diff --git a/src/backend/infrastructure/arm-templates/environment.template.json b/infrastructure/arm/environment.template.json similarity index 93% rename from src/backend/infrastructure/arm-templates/environment.template.json rename to infrastructure/arm/environment.template.json index 07a9345f1..ae84448ca 100644 --- a/src/backend/infrastructure/arm-templates/environment.template.json +++ b/infrastructure/arm/environment.template.json @@ -3,7 +3,8 @@ "contentVersion": "1.0.0.0", "parameters": { "env-name": { "type": "string" }, - "sql-connection-string": { "type": "string", "metadata": { "description": "Conection string without username/password"} } + "sql-connection-string": { "type": "string", "metadata": { "description": "Conection string without username/password"} }, + "summary-sql-connection-string": { "type": "string", "metadata": { "description": "Conection string without username/password"} } }, "variables": { "env-name": "[toLower(parameters('env-name'))]", @@ -34,6 +35,7 @@ "enabledForTemplateDeployment": false, "enabledForDiskEncryption": false, "enableRbacAuthorization": false, + "enableSoftDelete": true, "tenantId": "[subscription().tenantId]", "accessPolicies": [ { @@ -219,6 +221,17 @@ "dependsOn": [ "[concat('Microsoft.KeyVault/vaults/', variables('keyvault-name'))]" ] + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2015-06-01", + "name": "[concat(variables('keyvault-name'), '/', 'Connectionstrings--SummaryDbContext')]", + "properties": { + "value": "[parameters('summary-sql-connection-string')]" + }, + "dependsOn": [ + "[concat('Microsoft.KeyVault/vaults/', variables('keyvault-name'))]" + ] } ] } \ No newline at end of file diff --git a/infrastructure/azure-infra-setup.ps1 b/infrastructure/azure-infra-setup.ps1 new file mode 100644 index 000000000..bfa694793 --- /dev/null +++ b/infrastructure/azure-infra-setup.ps1 @@ -0,0 +1,83 @@ +# +# Idempotent script to ensure relevant baseline-infrastructure is available +# +# This script should be safe to execute multiple times. +# App owners are not removed, only appended. +# +# App registrations should be created be a normal user. +# - Required roles will differ, as non-prod and prod targets different external apis. +# +# +# User running the script must have permissions +# - Owner on subscription -> to set roles +# - Owner on app registrations -> to update +# - Owner on service principals connected to app registrations +# +# https://github.com/equinor/fusion-core-services/pull/969/files + +param( + [switch]$ServicePrincipals +) + +function Configure-DevOps-AppRegistration($appReg, $owners) { + Write-Host "Patching [$($appReg.name)]" + Write-Host (ConvertTo-Json $appReg) + az rest -m patch -u "https://graph.microsoft.com/v1.0/applications/$($appReg.objectId)" ` + --headers 'Content-Type=application/json' ` + --body "@$PSScriptRoot\app-registrations\$($appReg.configFile)" + + $existingServicePrincipalOwners = (az ad sp owner list --id $appReg.managedIdentity) | ConvertFrom-Json + $existingServicePrincipalOwners + + Write-Host "Setting owners" + foreach ($owner in $owners) { + Write-Host "- Adding [$($owner.upn)]" + az ad app owner add --id $appReg.appId --owner-object-id $owner.objectId + + if ($existingServicePrincipalOwners | Where-Object { $_.id -eq $owner.objectId }) { + Write-Host "- User is already owner on service principal" + } else { + Write-Host "- Adding [$($owner.upn)] to service principal owner list" + + ## Construct the payload. + ## Shitty stuff.. doublequotes must be escaped for the json to be valid. + $ownerData = @{ "owners@odata.bind" = @("https://graph.microsoft.com/v1.0/users/$($owner.objectId)") } | ConvertTo-Json -Compress + + az rest -m patch -u "https://graph.microsoft.com/v1.0/servicePrincipals/$($appReg.managedIdentity)" ` + --headers 'Content-Type=application/json' ` + --body $ownerData.Replace("`"", "\`"") + } + } +} + +# Load infra config. +$infraConfig = ConvertFrom-Json (Get-Content -Raw "$PSScriptRoot\config.json") + +## +## Patch app regs to match the defined json in config files `app-registrations/*`. +## - Name, description, CI ++ +## - Api permissions +## Adds owners +## +foreach ($appReg in @($infraConfig.spAppRegs.nonProduction, $infraConfig.spAppRegs.production)) { + Configure-DevOps-AppRegistration ` + -appReg $appReg ` + -owners $infraConfig.appRegOwners +} + +if ($ServicePrincipals.IsPresent) { + return +} + +## +## Subscription permission setup +## Add roles to resource groups based on config json. +## +foreach ($roleAssignment in $infraConfig.resourceGroups) { + Write-Host "Creating role assignment [$($roleAssignment.roleName)] for rg [$($roleAssignment.name)] to sp [$($roleAssignment.managedIdentity)]" + az role assignment create ` + --assignee $roleAssignment.managedIdentity ` + --role $roleAssignment.roleName ` + --scope "/subscriptions/$($infraConfig.subscriptionId)/resourceGroups/$($roleAssignment.name)" +} + diff --git a/infrastructure/config.json b/infrastructure/config.json new file mode 100644 index 000000000..2a4251a03 --- /dev/null +++ b/infrastructure/config.json @@ -0,0 +1,89 @@ +{ + "subscriptionId": "63b791ae-b2bc-41a1-ac66-806c4e69bffe", + "appRegOwners": [ + { + "upn": "handah@equinor.com", + "objectId": "f59e967d-8422-41ae-8980-a47f3ac0b70c" + }, + { + "upn": "az_handah@equinor.com", + "objectId": "0a9c3e47-52b3-43fe-ab2c-0c56ffc3155b" + }, + { + "upn": "ogjo@equinor.com", + "objectId": "0bd99c23-64f4-477e-a18c-79a42c4cd709" + }, + { + "upn": "tebr@equinor.com", + "objectId": "d8d9fa2f-ecee-4cfb-a371-a39e8c1b76aa" + }, + { + "upn": "alund@equinor.com", + "objectId": "f9158061-e8e3-494a-acbe-afcb6bc9f7ab" + } + ], + + "spAppRegs": { + "production": { + "name": "DevOps SP - Fusion resource allocation - production", + "appId": "3363e160-679c-48c0-8b87-022352cd565c", + "objectId": "555b9404-0898-421a-b094-41c5b671bc84", + "managedIdentity": "ad3ca9c7-dd10-423c-96e0-137c8b26ae9f", + "configFile": "sp-prod.json", + "apiPermissions": [ + { + "id": "18a4783c-866b-4cc7-a460-3d5e5662c884", + "name": "Application.ReadWrite.OwnedBy", + "type": "graph" + } + ] + }, + "nonProduction": { + "name": "DevOps SP - Fusion resource allocation - non-production", + "appId": "b6dc16db-85f7-41e4-afad-5f1a07c5961c", + "objectId": "fab7c06b-0779-4eee-b628-87fd25361f80", + "managedIdentity": "6f29795a-2712-4e66-a119-ea082d8b4201", + "configFile": "sp-non-prod.json", + "apiPermissions": [ + { + "id": "18a4783c-866b-4cc7-a460-3d5e5662c884", + "name": "Application.ReadWrite.OwnedBy", + "type": "graph" + } + ] + } + }, + + "resourceGroups": [ + { + "name": "fusion-apps-resources-ci", + "managedIdentity": "6f29795a-2712-4e66-a119-ea082d8b4201", + "roleName": "Contributor" + }, + { + "name": "fusion-apps-resources-fqa", + "managedIdentity": "6f29795a-2712-4e66-a119-ea082d8b4201", + "roleName": "Contributor" + }, + { + "name": "fusion-apps-resources-pr", + "managedIdentity": "6f29795a-2712-4e66-a119-ea082d8b4201", + "roleName": "Contributor" + }, + { + "name": "fusion-apps-resources-tr", + "managedIdentity": "6f29795a-2712-4e66-a119-ea082d8b4201", + "roleName": "Contributor" + }, + { + "name": "fusion-apps-resources-fprd", + "managedIdentity": "ad3ca9c7-dd10-423c-96e0-137c8b26ae9f", + "roleName": "Contributor" + }, + { + "name": "fusion-apps-resources-hosting", + "managedIdentity": "ad3ca9c7-dd10-423c-96e0-137c8b26ae9f", + "roleName": "Contributor" + } + ] +} \ No newline at end of file diff --git a/infrastructure/pipelines.md b/infrastructure/pipelines.md new file mode 100644 index 000000000..88f72e015 --- /dev/null +++ b/infrastructure/pipelines.md @@ -0,0 +1,54 @@ +# FRA Pipelines + +An overview of pipelines in FRA. + +## Pipeline structure + +Pipelines are added to the devops project, (Fusion Resource Allocation)[https://statoil-proview.visualstudio.com/Fusion%20Resource%20Allocation/_build?view=folders]. +They are grouped into different folders, as described in seperate headings below. + +### Frontend + +Pipelines related to ci/cd for fusion apps. The pipelines runs to published completed releases of the apps to ci->production and for individual prs. These pr apps will be cleaned up automatically by the AKS infra. + +### Backend + +Apis and function app deployment. All non-fusion-app prs should be here, where there are "customers". + +Naming convention: + +- API - [Domain] +- FUNC - [Domain] + + +### Maintenance + +Pipelines to do mainly scheduled work. E.g. key rotation, cleanup, monitoring etc. + +Naming convention: +- OPR - [Domain] - [Job] +- OPR - [Job] +- MON - [?] +- BACKUP - [Name | Data] // e.g. BACKUP - App Registrations + +### Infrastructure + +Pipelines that sets up baseline infra, e.g. shared resources like app insights, key vaults for environments etc. +These should not be triggered frequently due to changes, however they could perhaps be triggered scheduled, just to ensure they are working when needed. This would require them to be designed to be idempotent. + + +## Service Connection + + +## Guide / Getting started + +### Adding new pipeline + +1. Identify the correct folder +1. Add new pipeline +1. Select repository. If the repository is not located, the github connection might need to be updated to include a new repository + +> When updating the github connection, try to align this with the person that did it last. If the person updating it does not have access to the already added repos, they will be removed, which will cause existing repos to not run. + +1. Save (without running) +1. Update the name of the pipeline immediatly (... -> Rename). The default name is just the name of the repo, which is not good. Utilize the naming convention for a PROPER name. diff --git a/infrastructure/readme.md b/infrastructure/readme.md new file mode 100644 index 000000000..d03ae361b --- /dev/null +++ b/infrastructure/readme.md @@ -0,0 +1,37 @@ +# Infrastructure readme + +## Guidelines + +https://github.com/equinor/fusion-infrastructure/blob/main/docs/fusion_standardization_of_azure_resources.md + +## Service principals + +There are two service principals responsible for doing IAC operations for the FRA solution. These are seggregated into non-production and production environments. + +The SP's are used by pipelines to: +- Create and upload fusion apps to the portal +- Create azure resources in relevant FRA resource groups +- Manage app registrations used by api + +**Service principal names**: +- DevOps SP - Fusion resource allocation - production +- DevOps SP - Fusion resource allocation - non-production + + +### Role assignements + +- Reader for subscription S923-Proview +- Contributor for FRA resource groups +- Fusion.Apps.Create +- Fusion database management +- Manage owned applications in graph api + +### Run book + +#### Upadting service principals + +Updates to the service principals should be done by updating the `app-registrations/*.json` file. + +New roles should be added to the `requiredResourceAccess` collection, by using the app reg client id as `resourceAppId` and the applicable role id. + +> Roles can be granted by using `az approle assignment add -s 'DevOps SP - Fusion resource allocation - non-production' -r '{Role name}' -a '{Api name}'` (Requires custom extension to az cli) diff --git a/src/backend/infrastructure/deploy-database.ps1 b/infrastructure/scripts/deploy-database.ps1 similarity index 68% rename from src/backend/infrastructure/deploy-database.ps1 rename to infrastructure/scripts/deploy-database.ps1 index 2b22c320c..20f4803e8 100644 --- a/src/backend/infrastructure/deploy-database.ps1 +++ b/infrastructure/scripts/deploy-database.ps1 @@ -17,6 +17,9 @@ if ($null -eq $server) { throw "Could not locate any sql servers" } +$fcoreEnv = "test" +if ($environment -eq "fprd") { $fcoreEnv = "prod" } + $sqlServer = Get-AzSqlServer -ResourceGroupName $server.ResourceGroupName -ServerName $server.Name $ePools = Get-AzSqlElasticPool -ServerName $sqlServer.ServerName -ResourceGroupName $sqlServer.ResourceGroupName @@ -26,12 +29,18 @@ if ($ePools.Length -gt 1) { $pool = $ePools | Select-Object -First 1 } -New-AzResourceGroupDeployment -Mode Incremental -Name "fusion-app-resources-database-$environment" -ResourceGroupName $server.ResourceGroupName -TemplateFile "$($env:BUILD_SOURCESDIRECTORY)/src/backend/infrastructure/arm-templates/database.template.json" ` +New-AzResourceGroupDeployment -Mode Incremental -Name "fusion-app-resources-database-$environment" -ResourceGroupName $server.ResourceGroupName -TemplateFile "$($env:BUILD_SOURCESDIRECTORY)/infrastructure/arm/database.template.json" ` -env-name $environment ` -sqlserver_name $server.Name ` - -sql-elastic-pool-id $pool.ResourceId + -sql-elastic-pool-id $pool.ResourceId ` + -fcore-env $fcoreEnv + $connectionString = "Server=tcp:$sqlServerName.database.windows.net,1433;Initial Catalog=Fusion-Apps-Resources-$environment-DB;Persist Security Info=False;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" +$summaryConnectionString = "Server=tcp:$sqlServerName.database.windows.net,1433;Initial Catalog=sqldb-fapp-fra-summary-db-$environment;Persist Security Info=False;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" + Write-Host "##vso[task.setvariable variable=SqlConnectionString]$connectionString" Write-Host "##vso[task.setvariable variable=SqlDatabaseName]Fusion-Apps-Resources-$environment-DB" +Write-Host "##vso[task.setvariable variable=SummarySqlConnectionString]$summaryConnectionString" +Write-Host "##vso[task.setvariable variable=SummarySqlDatabaseName]sqldb-fapp-fra-summary-db-$environment" diff --git a/infrastructure/scripts/deploy-resources.ps1 b/infrastructure/scripts/deploy-resources.ps1 new file mode 100644 index 000000000..b4f482096 --- /dev/null +++ b/infrastructure/scripts/deploy-resources.ps1 @@ -0,0 +1,18 @@ +param( + [string]$environment +) + +Write-Host "Starting deployment of general resources" + +$resourceGroup = "fusion-apps-resources-$environment" +$envKeyVault = "kv-fap-resources-$environment" + +Write-Host "Using resource group $resourceGroup" + + +Write-Host "Deploying template" + +New-AzResourceGroupDeployment -Mode Incremental -Name "fusion-app-resources-environment" -ResourceGroupName $resourceGroup -TemplateFile "$($env:BUILD_SOURCESDIRECTORY)/infrastructure/arm/environment.template.json" ` + -env-name $environment ` + -sql-connection-string $env:SQLCONNECTIONSTRING ` + -summary-sql-connection-string $env:SUMMARYSQLCONNECTIONSTRING diff --git a/src/backend/infrastructure/ensure-resourcegroup.ps1 b/infrastructure/scripts/ensure-resourcegroup.ps1 similarity index 100% rename from src/backend/infrastructure/ensure-resourcegroup.ps1 rename to infrastructure/scripts/ensure-resourcegroup.ps1 diff --git a/src/backend/infrastructure/grant-adapp-sql-access.ps1 b/infrastructure/scripts/grant-adapp-sql-access.ps1 similarity index 89% rename from src/backend/infrastructure/grant-adapp-sql-access.ps1 rename to infrastructure/scripts/grant-adapp-sql-access.ps1 index cca072f59..9da57a5ca 100644 --- a/src/backend/infrastructure/grant-adapp-sql-access.ps1 +++ b/infrastructure/scripts/grant-adapp-sql-access.ps1 @@ -38,7 +38,7 @@ function ConvertTo-Sid { return "0x" + $byteGuid } -$sqlpasswordSecret = Get-AzKeyVaultSecret -VaultName $sqlPasswordKeyVault -Name fusion-sql-password -AsPlainText +$token = Get-AzAccessToken -ResourceUrl "https://database.windows.net/" $sqlServer = Get-SqlServer $sp = Get-AzADApplication -ApplicationId $clientId @@ -53,7 +53,6 @@ END Invoke-Sqlcmd -ServerInstance $sqlServer.FullyQualifiedDomainName ` -Database $sqlDatabaseName ` - -Username $sqlServer.SqlAdministratorLogin ` - -Password $sqlpasswordSecret ` + -AccessToken $token.Token ` -Query $sql ` -ConnectionTimeout 120 \ No newline at end of file diff --git a/infrastructure/scripts/set-default-keyvault-permissions.ps1 b/infrastructure/scripts/set-default-keyvault-permissions.ps1 new file mode 100644 index 000000000..e912b2e18 --- /dev/null +++ b/infrastructure/scripts/set-default-keyvault-permissions.ps1 @@ -0,0 +1,40 @@ +param( + [string]$environment, + [string]$apiAppRegistrationClientId +) + +$resourceGroup = "fusion-apps-resources-$environment" +$envKeyVault = "kv-fap-resources-$environment" + +## Grant the current deploying service principal access + +Write-Host "Setting service principal key vault access" +$spName = (Get-AzContext).Account.Id +Set-AzKeyVaultAccessPolicy -VaultName $envKeyVault -ServicePrincipalName $spName -PermissionsToSecrets get,list,set,delete + +## LEGACY - Need to grant permissions to the core app registration (VisualStudioSPNb8d5119a-cbba-4511-bc05-d9d9cd034c77) +## This should be removed when all pipelines have been updated +Write-Host "Setting LEGACY VS service principal access" +Set-AzKeyVaultAccessPolicy -VaultName $envKeyVault -ObjectId b3fcda7c-7d40-4e46-bc29-be0fb4c1506f -PermissionsToSecrets get,list,set,delete + + +## Grant permission to the app registration specified in params. +## This should be the one acting as the api. + +Write-Host "Setting ad app service principal key vault access" +$appSpId = (Get-AzADServicePrincipal -ApplicationId $apiAppRegistrationClientId).Id +Set-AzKeyVaultAccessPolicy -VaultName $envKeyVault -ObjectId $appSpId -PermissionsToSecrets get,list + + + + +## Grant all web apps with identity get & list permissions in the vault. +## This is needed so we do not remove them when running infra deploy + +$webApps = Get-AzWebApp -ResourceGroupName $resourceGroup +foreach ($webApp in $webApps) { + if ($null -ne $webApp.Identity) { + Write-Host "Granting GET,LIST permission to web app $($webApp.Name)" + Set-AzKeyVaultAccessPolicy -VaultName $envKeyVault -ResourceGroupName $resourceGroup -ObjectId $webApp.Identity.PrincipalId -PermissionsToSecrets get,list + } +} \ No newline at end of file diff --git a/pipelines/api-pipeline.yml b/pipelines/api-pipeline.yml index 5d526b2f1..c060d792e 100644 --- a/pipelines/api-pipeline.yml +++ b/pipelines/api-pipeline.yml @@ -25,7 +25,8 @@ variables: fusionAcr: 'fusioncr.azurecr.io' imageRepo: resources/fusion-resouces-api imageName: $(imageRepo):$(imageTag) - subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + subscriptionServiceNonProd: 'FRA Automation Non-Prod' + subscriptionServiceProd: 'FRA Automation Prod' deploymentManifest: '$(Pipeline.Workspace)/k8s-deployment/deployment-test-env.yml' fullImageName: $(fusionAcr)/$(imageName) @@ -87,10 +88,10 @@ stages: runOnce: deploy: steps: - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Get secrets' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceNonProd) ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' @@ -101,12 +102,15 @@ stages: - template: templates/get-appinsights-key.yml parameters: environment: $(envName) + azureSubscription: $(subscriptionServiceNonProd) + # Load the key vault url into variable, so it can be used when replacing tokens. - template: templates/get-keyvault-url.yml parameters: environment: $(envName) variableName: envKeyVaultUrl + azureSubscription: $(subscriptionServiceNonProd) - template: templates/replace-tokens.yml @@ -116,7 +120,8 @@ stages: - template: templates/execute-sql-migration.yml parameters: artifact: 'k8s-deployment' - environment: $(envName) + environment: $(envName) + azureSubscription: $(subscriptionServiceNonProd) - task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster @@ -135,15 +140,15 @@ stages: jobs: - deployment: DeployFQA - environment: fusion-resources-fqa.fusion-resources-app-fqa + environment: fusion-fqa.fusion-resources-app-fqa strategy: runOnce: deploy: steps: - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Get secrets' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceNonProd) ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' @@ -154,12 +159,14 @@ stages: - template: templates/get-appinsights-key.yml parameters: environment: $(envName) + azureSubscription: $(subscriptionServiceNonProd) # Load the key vault url into variable, so it can be used when replacing tokens. - template: templates/get-keyvault-url.yml parameters: environment: $(envName) variableName: envKeyVaultUrl + azureSubscription: $(subscriptionServiceNonProd) - template: templates/replace-tokens.yml parameters: @@ -169,6 +176,7 @@ stages: parameters: artifact: 'k8s-deployment' environment: $(envName) + azureSubscription: $(subscriptionServiceNonProd) - task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster @@ -192,10 +200,10 @@ stages: runOnce: deploy: steps: - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Get secrets' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceProd) ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' @@ -206,12 +214,14 @@ stages: - template: templates/get-appinsights-key.yml parameters: environment: $(envName) + azureSubscription: $(subscriptionServiceProd) # Load the key vault url into variable, so it can be used when replacing tokens. - template: templates/get-keyvault-url.yml parameters: environment: $(envName) variableName: envKeyVaultUrl + azureSubscription: $(subscriptionServiceProd) - template: templates/replace-tokens.yml parameters: @@ -221,6 +231,7 @@ stages: parameters: artifact: 'k8s-deployment' environment: $(envName) + azureSubscription: $(subscriptionServiceProd) - task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster @@ -230,6 +241,7 @@ stages: - template: templates/deploy-container-app.yml parameters: + azureSubscription: $(subscriptionServiceProd) environment: $(envName) fusionEnvironment: $(fusionEnvironment) clientId: $(clientId) @@ -246,15 +258,15 @@ stages: jobs: - deployment: DeployTR - environment: fusion-resources-tr.fusion-resources-app-tr + environment: fusion-tr.fusion-resources-app-tr strategy: runOnce: deploy: steps: - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Get secrets' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceNonProd) ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' @@ -265,12 +277,14 @@ stages: - template: templates/get-appinsights-key.yml parameters: environment: $(envName) + azureSubscription: $(subscriptionServiceNonProd) # Load the key vault url into variable, so it can be used when replacing tokens. - template: templates/get-keyvault-url.yml parameters: environment: $(envName) variableName: envKeyVaultUrl + azureSubscription: $(subscriptionServiceNonProd) - template: templates/replace-tokens.yml parameters: @@ -280,6 +294,8 @@ stages: parameters: artifact: 'k8s-deployment' environment: $(envName) + azureSubscription: $(subscriptionServiceNonProd) + - task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster diff --git a/pipelines/api-pr-pipeline.yml b/pipelines/api-pr-pipeline.yml index 51f2f2041..8dcdd6720 100644 --- a/pipelines/api-pr-pipeline.yml +++ b/pipelines/api-pr-pipeline.yml @@ -22,7 +22,8 @@ variables: fusionAcr: 'fusioncr.azurecr.io' imageRepo: resources/fusion-resouces-api imageName: $(imageRepo):$(prNumber) - subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + subscriptionService: 'FRA Automation Non-Prod' + subscriptionServiceCore: PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe) deploymentManifest: '$(Pipeline.Workspace)/k8s-deployment/deployment-pr-env.yml' fullImageName: $(fusionAcr)/$(imageName) buildNr: $(Build.BuildNumber) @@ -87,14 +88,14 @@ stages: jobs: - deployment: DeployPR displayName: 'Deploy API to PR' - environment: fusion-resources-pr.fusion-resources-app-pr + environment: fra-pr.fusion-resources-app-pr strategy: runOnce: deploy: steps: - template: templates/install-fusion-ps.yml - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Get secrets' inputs: azureSubscription: $(subscriptionService) @@ -105,10 +106,11 @@ stages: $secretText = Get-AzKeyVaultSecret -VaultName kv-fap-resources-pr -Name AzureAd--ClientSecret -AsPlainText Write-Output "##vso[task.setvariable variable=clientSecret;issecret=true]$($secretText)" - - task: AzurePowerShell@4 + ## MUST RUN THIS AS THE CORE SERVICE PRINCIPAL, UNTILL NEW DB DEPLOY IS ESTABLISHED + - task: AzurePowerShell@5 displayName: 'Provision database' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceCore) ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' @@ -140,12 +142,14 @@ stages: - template: templates/get-appinsights-key.yml parameters: environment: $(envName) + azureSubscription: $(subscriptionService) # Load the key vault url into variable, so it can be used when replacing tokens. - template: templates/get-keyvault-url.yml parameters: environment: $(envName) variableName: envKeyVaultUrl + azureSubscription: $(subscriptionService) - template: templates/replace-tokens.yml parameters: @@ -155,6 +159,8 @@ stages: parameters: artifact: 'k8s-deployment' environment: '$(envName)-$(prNumber)' + azureSubscription: $(subscriptionService) + - task: KubernetesManifest@0 displayName: Deploy to Kubernetes cluster diff --git a/pipelines/environment-pipeline.yml b/pipelines/environment-pipeline.yml index 973cfd774..bd8ecaad9 100644 --- a/pipelines/environment-pipeline.yml +++ b/pipelines/environment-pipeline.yml @@ -13,7 +13,7 @@ stages: displayName: CI Azure Infra jobs: - deployment: DeployInfra - environment: fusion-ci + environment: fra-ci variables: environmentName: ci strategy: @@ -24,7 +24,10 @@ stages: parameters: environment: $(environmentName) clientId: 5a842df8-3238-415d-b168-9f16a6a6031b + clientIdDbOwner: b6dc16db-85f7-41e4-afad-5f1a07c5961c sqlServer: fusion-test-sqlserver + azureSubscription: 'FRA Automation Non-Prod' + coreAzureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' - stage: DeployPR displayName: PR Azure Infra @@ -32,7 +35,7 @@ stages: condition: succeeded() jobs: - deployment: DeployInfra - environment: fusion-pullrequests + environment: fra-pr variables: environmentName: pr strategy: @@ -43,7 +46,10 @@ stages: parameters: environment: $(environmentName) clientId: 5a842df8-3238-415d-b168-9f16a6a6031b + clientIdDbOwner: b6dc16db-85f7-41e4-afad-5f1a07c5961c sqlServer: fusion-test-sqlserver + azureSubscription: 'FRA Automation Non-Prod' + coreAzureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' ## To get the pr slots to talk to the correct databases, we need to kill the key vault secret for connection string. ## The secret would override the direct env property used to target the correct db. @@ -65,7 +71,7 @@ stages: condition: succeeded() jobs: - deployment: DeployInfra - environment: fusion-fqa + environment: fra-fqa variables: environmentName: fqa strategy: @@ -76,7 +82,10 @@ stages: parameters: environment: $(environmentName) clientId: 5a842df8-3238-415d-b168-9f16a6a6031b + clientIdDbOwner: b6dc16db-85f7-41e4-afad-5f1a07c5961c sqlServer: fusion-test-sqlserver + azureSubscription: 'FRA Automation Non-Prod' + coreAzureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' - stage: DeployTR displayName: TR Azure Infra @@ -84,7 +93,7 @@ stages: condition: succeeded() jobs: - deployment: DeployInfra - environment: fusion-tr + environment: fra-tr variables: environmentName: tr strategy: @@ -95,7 +104,10 @@ stages: parameters: environment: $(environmentName) clientId: 5a842df8-3238-415d-b168-9f16a6a6031b + clientIdDbOwner: b6dc16db-85f7-41e4-afad-5f1a07c5961c sqlServer: fusion-test-sqlserver + azureSubscription: 'FRA Automation Non-Prod' + coreAzureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' - stage: DeployPROD displayName: PROD Azure Infra @@ -103,7 +115,7 @@ stages: condition: succeeded() jobs: - deployment: DeployInfra - environment: fusion-prod + environment: fra-fprd variables: environmentName: fprd strategy: @@ -114,6 +126,8 @@ stages: parameters: environment: $(environmentName) clientId: 97978493-9777-4d48-b38a-67b0b9cd88d2 + clientIdDbOwner: 3363e160-679c-48c0-8b87-022352cd565c sqlServer: fusion-prod-sqlserver clientSecretName: ClientSecret-Resources-Prod - \ No newline at end of file + azureSubscription: 'FRA Automation Prod' + coreAzureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' diff --git a/pipelines/function-pipeline.yml b/pipelines/function-pipeline.yml index 004b0f940..3b11ccffd 100644 --- a/pipelines/function-pipeline.yml +++ b/pipelines/function-pipeline.yml @@ -15,7 +15,8 @@ trigger: pr: none variables: - subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + subscriptionServiceNonProd: 'FRA Automation Non-Prod' + subscriptionServiceProd: 'FRA Automation Prod' webPackage: '$(Pipeline.Workspace)/drop/Fusion.Resources.Functions.zip' stages: @@ -47,10 +48,11 @@ stages: parameters: envName: $(environment) clientId: '5a842df8-3238-415d-b168-9f16a6a6031b' + azureSubscription: $(subscriptionServiceNonProd) - task: AzureFunctionApp@1 inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceNonProd) appType: 'functionApp' appName: $(functionServiceName) package: $(webPackage) @@ -80,10 +82,11 @@ stages: envName: $(environment) clientId: '5a842df8-3238-415d-b168-9f16a6a6031b' fusionEnvironment: fqa + azureSubscription: $(subscriptionServiceNonProd) - task: AzureFunctionApp@1 inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceNonProd) appType: 'functionApp' appName: $(functionServiceName) package: $(webPackage) @@ -113,10 +116,11 @@ stages: envName: $(environment) clientId: '5a842df8-3238-415d-b168-9f16a6a6031b' fusionEnvironment: tr + azureSubscription: $(subscriptionServiceNonProd) - task: AzureFunctionApp@1 inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceNonProd) appType: 'functionApp' appName: $(functionServiceName) package: $(webPackage) @@ -147,10 +151,11 @@ stages: clientId: '97978493-9777-4d48-b38a-67b0b9cd88d2' fusionResource: '97978493-9777-4d48-b38a-67b0b9cd88d2' fusionEnvironment: fprd + azureSubscription: $(subscriptionServiceProd) - task: AzureFunctionApp@1 inputs: - azureSubscription: $(subscriptionService) + azureSubscription: $(subscriptionServiceProd) appType: 'functionApp' appName: $(functionServiceName) package: $(webPackage) diff --git a/pipelines/function-pr-pipeline.yml b/pipelines/function-pr-pipeline.yml index d4b216343..b0e9169cc 100644 --- a/pipelines/function-pr-pipeline.yml +++ b/pipelines/function-pr-pipeline.yml @@ -15,7 +15,7 @@ pr: - src/backend/integration/* variables: - subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + subscriptionService: 'FRA Automation Non-Prod' webPackage: '$(Pipeline.Workspace)/drop/Fusion.Resources.Functions.zip' prNumber: $(System.PullRequest.PullRequestNumber) @@ -38,7 +38,7 @@ stages: - deployment: Deploy displayName: 'Deploy function app' - environment: fusion-pr + environment: fra-pr strategy: runOnce: diff --git a/pipelines/pr-cleanup-pipeline.yml b/pipelines/pr-cleanup-pipeline.yml index 02a97edf3..3581c1263 100644 --- a/pipelines/pr-cleanup-pipeline.yml +++ b/pipelines/pr-cleanup-pipeline.yml @@ -15,7 +15,7 @@ schedules: jobs: - deployment: DeployPR displayName: 'Remove pull request environment artifacts' - environment: fusion-resources-pr.fusion-resources-app-pr + environment: fra-pr strategy: runOnce: deploy: @@ -32,7 +32,7 @@ jobs: Write-Output "##vso[task.setvariable variable=activePrs]$([string]::Join(",", $activePRs))" displayName: Detect active pull requests - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Delete expired databases' inputs: azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' diff --git a/pipelines/secret-rotation-pipeline.yml b/pipelines/secret-rotation-pipeline.yml index 1dde93fbc..92654f590 100644 --- a/pipelines/secret-rotation-pipeline.yml +++ b/pipelines/secret-rotation-pipeline.yml @@ -29,7 +29,9 @@ pool: vmImage: "windows-latest" variables: - subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + subscriptionServiceCore: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + subscriptionServiceNonProd: 'FRA Automation Non-Prod' + subscriptionServiceProd: 'FRA Automation Prod' stages: - stage: SecretsCI @@ -41,28 +43,14 @@ stages: vaultName: 'kv-fap-resources-ci' aadApplicationId: '5a842df8-3238-415d-b168-9f16a6a6031b' steps: - - task: AzurePowerShell@5 - displayName: Update client secret CI - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'OtherVersion' - preferredAzurePowerShellVersion: '7.2.0' - ScriptPath: src/backend/infrastructure/generate-secret.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) - - - task: AzurePowerShell@5 - displayName: Cleanup secrets - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/cleanup-secrets.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) + - template: templates/secret-rotation/ensure-secret.yml + parameters: + azureSubscription: $(subscriptionServiceNonProd) + azureSubscriptionCore: $(azureSubscriptionCore) + name: 'resourceApi' + secretName: 'AzureAd--ClientSecret' + vaultName: $(vaultName) + aadApplicationId: $(aadApplicationId) - stage: SecretsPR displayName: "Rotate keys PR" @@ -73,28 +61,14 @@ stages: vaultName: 'kv-fap-resources-pr' aadApplicationId: '5a842df8-3238-415d-b168-9f16a6a6031b' steps: - - task: AzurePowerShell@5 - displayName: Update client secret - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'OtherVersion' - preferredAzurePowerShellVersion: '7.2.0' - ScriptPath: src/backend/infrastructure/generate-secret.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) - - - task: AzurePowerShell@5 - displayName: Cleanup secrets - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/cleanup-secrets.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) + - template: templates/secret-rotation/ensure-secret.yml + parameters: + azureSubscription: $(subscriptionServiceNonProd) + azureSubscriptionCore: $(azureSubscriptionCore) + name: 'resourceApi' + secretName: 'AzureAd--ClientSecret' + vaultName: $(vaultName) + aadApplicationId: $(aadApplicationId) - stage: SecretsQA displayName: "Rotate keys FQA" @@ -106,28 +80,14 @@ stages: vaultName: 'kv-fap-resources-fqa' aadApplicationId: '5a842df8-3238-415d-b168-9f16a6a6031b' steps: - - task: AzurePowerShell@5 - displayName: Update client secret - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'OtherVersion' - preferredAzurePowerShellVersion: '7.2.0' - ScriptPath: src/backend/infrastructure/generate-secret.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) - - - task: AzurePowerShell@5 - displayName: Cleanup secrets - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/cleanup-secrets.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) + - template: templates/secret-rotation/ensure-secret.yml + parameters: + azureSubscription: $(subscriptionServiceNonProd) + azureSubscriptionCore: $(azureSubscriptionCore) + name: 'resourceApi' + secretName: 'AzureAd--ClientSecret' + vaultName: $(vaultName) + aadApplicationId: $(aadApplicationId) - stage: SecretsTR displayName: "Rotate keys TR" @@ -139,28 +99,15 @@ stages: vaultName: 'kv-fap-resources-tr' aadApplicationId: '5a842df8-3238-415d-b168-9f16a6a6031b' steps: - - task: AzurePowerShell@5 - displayName: Update client secret - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'OtherVersion' - preferredAzurePowerShellVersion: '7.2.0' - ScriptPath: src/backend/infrastructure/generate-secret.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) + - template: templates/secret-rotation/ensure-secret.yml + parameters: + azureSubscription: $(subscriptionServiceNonProd) + azureSubscriptionCore: $(azureSubscriptionCore) + name: 'resourceApi' + secretName: 'AzureAd--ClientSecret' + vaultName: $(vaultName) + aadApplicationId: $(aadApplicationId) - - task: AzurePowerShell@5 - displayName: Cleanup secrets - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/cleanup-secrets.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) - stage: SecretsFPRD displayName: "Rotate keys FPRD" @@ -172,27 +119,13 @@ stages: vaultName: 'kv-fap-resources-fprd' aadApplicationId: '97978493-9777-4d48-b38a-67b0b9cd88d2' steps: - - task: AzurePowerShell@5 - displayName: Update client secret - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'OtherVersion' - preferredAzurePowerShellVersion: '7.2.0' - ScriptPath: src/backend/infrastructure/generate-secret.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) + - template: templates/secret-rotation/ensure-secret.yml + parameters: + azureSubscription: $(subscriptionServiceNonProd) + azureSubscriptionCore: $(azureSubscriptionCore) + name: 'resourceApi' + secretName: 'AzureAd--ClientSecret' + vaultName: $(vaultName) + aadApplicationId: $(aadApplicationId) - - task: AzurePowerShell@5 - displayName: Cleanup secrets - - inputs: - azureSubscription: $(subscriptionService) - ScriptType: FilePath - FailOnStandardError: true - azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/cleanup-secrets.ps1 - ScriptArguments: -applicationId $(aadApplicationId) -keyVaultName $(vaultName) - diff --git a/pipelines/templates/arm-environments.yml b/pipelines/templates/arm-environments.yml index fcaff7156..dde0f2b67 100644 --- a/pipelines/templates/arm-environments.yml +++ b/pipelines/templates/arm-environments.yml @@ -1,50 +1,96 @@ parameters: - environment: '' - clientId: '' - sqlServer: '' - clientSecretName: 'ClientSecret-Resources-Test' + environment: '' + clientId: '' + clientIdDbOwner: '' + sqlServer: '' + clientSecretName: 'ClientSecret-Resources-Test' + azureSubscription: 'FRA Automation Non-Prod' + coreAzureSubscription: '' steps: - checkout: self - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Ensure environment resource group' inputs: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: FilePath FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/ensure-resourcegroup.ps1 + ScriptPath: infrastructure/scripts/ensure-resourcegroup.ps1 ScriptArguments: -environment ${{ parameters.environment }} - - task: AzurePowerShell@4 + ## DATABASE SHOULD BE DEPLOYED BY API PIPELINE, NOT INFRA + ## TODO - Move provisioning of db to api pipeline + + - task: AzurePowerShell@5 displayName: 'Deploy sql database' inputs: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: ${{ parameters.coreAzureSubscription }} ScriptType: FilePath FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/deploy-database.ps1 + ScriptPath: infrastructure/scripts/deploy-database.ps1 ScriptArguments: -environment ${{ parameters.environment }} -sqlServerName ${{ parameters.sqlServer }} - - task: AzurePowerShell@4 - displayName: 'Grant azure ad app sql database access' + - task: AzurePowerShell@5 + displayName: 'Grant azure ad app sql database access (Resources-db)' inputs: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: ${{ parameters.coreAzureSubscription }} ScriptType: FilePath FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/grant-adapp-sql-access.ps1 + ScriptPath: infrastructure/scripts/grant-adapp-sql-access.ps1 ScriptArguments: -clientId ${{ parameters.clientId }} -sqlServerName ${{ parameters.sqlServer }} -sqlDatabaseName $(SqlDatabaseName) - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 + displayName: 'Grant azure ad app sql database access (Resources-db)' + inputs: + azureSubscription: ${{ parameters.coreAzureSubscription }} + ScriptType: FilePath + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + ScriptPath: infrastructure/scripts/grant-adapp-sql-access.ps1 + ScriptArguments: -clientId ${{ parameters.clientIdDbOwner }} -sqlServerName ${{ parameters.sqlServer }} -sqlDatabaseName $(SqlDatabaseName) + + - task: AzurePowerShell@5 + displayName: 'Grant azure ad app sql database access (Summary-db)' + inputs: + azureSubscription: ${{ parameters.coreAzureSubscription }} + ScriptType: FilePath + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + ScriptPath: infrastructure/scripts/grant-adapp-sql-access.ps1 + ScriptArguments: -clientId ${{ parameters.clientId }} -sqlServerName ${{ parameters.sqlServer }} -sqlDatabaseName $(SummarySqlDatabaseName) + + - task: AzurePowerShell@5 + displayName: 'Grant azure ad app sql database access (Summary-db)' + inputs: + azureSubscription: ${{ parameters.coreAzureSubscription }} + ScriptType: FilePath + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + ScriptPath: infrastructure/scripts/grant-adapp-sql-access.ps1 + ScriptArguments: -clientId ${{ parameters.clientIdDbOwner }} -sqlServerName ${{ parameters.sqlServer }} -sqlDatabaseName $(SummarySqlDatabaseName) + + - task: AzurePowerShell@5 displayName: 'Deploy ARM template' inputs: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: ${{ parameters.azureSubscription }} + ScriptType: FilePath + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + ScriptPath: infrastructure/scripts/deploy-resources.ps1 + ScriptArguments: -environment ${{ parameters.environment }} + + - task: AzurePowerShell@5 + displayName: 'Set default env keyvault permissions' + inputs: + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: FilePath FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' - ScriptPath: src/backend/infrastructure/deploy-resources.ps1 - ScriptArguments: -environment ${{ parameters.environment }} -clientId ${{ parameters.clientId }} + ScriptPath: infrastructure/scripts/set-default-keyvault-permissions.ps1 + ScriptArguments: -environment ${{ parameters.environment }} -apiAppRegistrationClientId ${{ parameters.clientId }} \ No newline at end of file diff --git a/pipelines/templates/deploy-container-app.yml b/pipelines/templates/deploy-container-app.yml index 6a70783b3..d04462f39 100644 --- a/pipelines/templates/deploy-container-app.yml +++ b/pipelines/templates/deploy-container-app.yml @@ -1,4 +1,5 @@ parameters: + azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' environment: '' clientId: '' imageName: '' @@ -10,7 +11,7 @@ steps: - task: AzurePowerShell@4 displayName: 'Deploy ARM template' inputs: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: FilePath FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' diff --git a/pipelines/templates/deploy-function-template.yml b/pipelines/templates/deploy-function-template.yml index 96b963fdb..9cb362ee2 100644 --- a/pipelines/templates/deploy-function-template.yml +++ b/pipelines/templates/deploy-function-template.yml @@ -5,13 +5,14 @@ parameters: fusionResource: '5a842df8-3238-415d-b168-9f16a6a6031b' templateFile: $(Build.SourcesDirectory)/src/backend/function/Fusion.Resources.Functions/Deployment/function.template.json disabledFunctionsFile: $(Build.SourcesDirectory)/src/backend/function/Fusion.Resources.Functions/Deployment/disabled-functions.json + azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' steps: - checkout: self - task: AzurePowerShell@4 displayName: 'Deploy Function ARM template' inputs: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' diff --git a/pipelines/templates/execute-sql-migration.yml b/pipelines/templates/execute-sql-migration.yml index 7c69b3929..faa3fa3f9 100644 --- a/pipelines/templates/execute-sql-migration.yml +++ b/pipelines/templates/execute-sql-migration.yml @@ -1,5 +1,5 @@ parameters: - azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + azureSubscription: '' sqlFile: '' artifact: 'drop' environment: '' @@ -10,7 +10,7 @@ steps: # inputs: # buildType: 'current' - - task: AzurePowerShell@4 + - task: AzurePowerShell@5 displayName: 'Execute sql migrations' inputs: azureSubscription: ${{ parameters.azureSubscription }} diff --git a/pipelines/templates/get-appinsights-key.yml b/pipelines/templates/get-appinsights-key.yml index 6150e35d3..055d6a30c 100644 --- a/pipelines/templates/get-appinsights-key.yml +++ b/pipelines/templates/get-appinsights-key.yml @@ -2,12 +2,13 @@ parameters: environment: '' variableName: 'instrumentationKey' + azureSubscription: $(subscriptionService) steps: -- task: AzurePowerShell@4 +- task: AzurePowerShell@5 displayName: 'Get Application Insights telemetry key for ${{ parameters.environment }}' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' diff --git a/pipelines/templates/get-keyvault-url.yml b/pipelines/templates/get-keyvault-url.yml index 68a791190..c365fe593 100644 --- a/pipelines/templates/get-keyvault-url.yml +++ b/pipelines/templates/get-keyvault-url.yml @@ -2,12 +2,13 @@ parameters: environment: '' variableName: 'envKeyVaultUrl' + azureSubscription: $(subscriptionService) steps: -- task: AzurePowerShell@4 +- task: AzurePowerShell@5 displayName: 'Get key vault url for environment ${{ parameters.environment }}' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' diff --git a/pipelines/templates/grant-database-app-access.yml b/pipelines/templates/grant-database-app-access.yml index dded8a47a..563644bf8 100644 --- a/pipelines/templates/grant-database-app-access.yml +++ b/pipelines/templates/grant-database-app-access.yml @@ -3,12 +3,13 @@ parameters: clientId: '' sqlEnvironment: 'test' ## || production sqlDatabaseName: '' + azureSubscription: $(subscriptionService) steps: -- task: AzurePowerShell@4 +- task: AzurePowerShell@5 displayName: 'Grant DB access to Azure ad application' inputs: - azureSubscription: $(subscriptionService) + azureSubscription: ${{ parameters.azureSubscription }} ScriptType: 'InlineScript' FailOnStandardError: true azurePowerShellVersion: 'LatestVersion' diff --git a/pipelines/templates/secret-rotation/check-for-expiration.yml b/pipelines/templates/secret-rotation/check-for-expiration.yml new file mode 100644 index 000000000..09160cad4 --- /dev/null +++ b/pipelines/templates/secret-rotation/check-for-expiration.yml @@ -0,0 +1,87 @@ +parameters: + azureSubscription: '' + name: '' + secretName: '' + vaultName: '' + +steps: +- task: AzurePowerShell@5 + displayName: 'Check expiration' + name: ${{ parameters.name }} + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + ScriptType: InlineScript + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + Inline: | + $SECRETVAULTNAME = "${{ parameters.vaultName }}" + $SECRET_NAME = "${{ parameters.secretName }}" + Write-Host "Key vault: $SECRETVAULTNAME" + + $secret = Get-AzKeyVaultSecret -VaultName $SECRETVAULTNAME -Name $SECRET_NAME + + $generateNew = $false + + if ($null -ne $secret.Tags -and $secret.Tags["auto-generated"] -eq "true") { + Write-Host "Located autogenerated secret" + + ## Check expires flag + $delta = $secret.Expires - (Get-Date) + + Write-Host "Secret expires in $($delta.Days) days" + if ($delta.Days -lt 60) { + $generateNew = $true + } + + } else { + Write-Host "Secret not autogenerated, creating new..." + $generateNew = $true + } + + if (-not $generateNew) { + Write-Host "No need to generate secret..." + Write-Host "##vso[task.setvariable variable=generateNew;isOutput=true]false" + Write-Host "Set variable [${{ parameters.name }}.generateNew] -> false" + } else { + Write-Host "Generating new secret..." + Write-Host "##vso[task.setvariable variable=generateNew;isOutput=true]true" + Write-Host "Set variable [${{ parameters.name }}.generateNew] -> true" + } + + + ## Detect expired keys to purge from ad app + Write-Host "----- Checking for secrets to prune -----" + + $keysToDelete = @() + + $secretWithVersions = Get-AzKeyVaultSecret -VaultName $SECRETVAULTNAME -Name $SECRET_NAME -IncludeVersions + foreach ($s in $secretWithVersions) { + Write-Host "Processing key: $($s.Id)" + $isAutoGenerated = $null -ne $s.Tags -and $s.Tags["auto-generated"] -eq "true" + + if ($isAutoGenerated) { + $keyId = $s.Tags["keyId"] + $isDeleted = $s.Tags.ContainsKey("deleted") + + if ([string]::IsNullOrEmpty($keyId)) { + Write-Host "`tCould not locate any key id, skipping" + continue + } + + if ($isDeleted -eq $true) { Write-Host "`tAlready deleted @ $($s.Tags["deleted"])"; continue } + if ($s.Expires -lt (Get-Date)) { + Write-Host "`tExpired secret.. Deleting key id: $keyId" + $newTags = $s.Tags + $newTags["deleted"] = (Get-Date).ToString() + Update-AzKeyVaultSecret $s -Version $s.Version -Tag $newTags + + $keysToDelete += $keyId + } + } else { + Write-Host "Non auto generated key, skipping" + } + } + + $keysToDeleteString = [string]::Join(";", $keysToDelete) + Write-Host "##vso[task.setvariable variable=keysToDelete;isOutput=true]$keysToDeleteString" + \ No newline at end of file diff --git a/pipelines/templates/secret-rotation/cleanup-expired-secrets.yml b/pipelines/templates/secret-rotation/cleanup-expired-secrets.yml new file mode 100644 index 000000000..cf791b31f --- /dev/null +++ b/pipelines/templates/secret-rotation/cleanup-expired-secrets.yml @@ -0,0 +1,46 @@ +parameters: + - name: azureSubscription + type: string + - name: clientId + type: string + displayName: 'AAD Application to create secret on' + - name: keyIds + +steps: +- task: AzurePowerShell@5 + displayName: 'Prune secrets' + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + ScriptType: InlineScript + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + Inline: | + $AAD_APP_ID = "${{ parameters.clientId }}" + $KEY_IDS = "${{ parameters.keyIds }}" + + Write-Host "Application Client Id: $AAD_APP_ID" + Write-Host "Key Ids marked for deletion: $KEY_IDS" + + if ([string]::IsNullOrEmpty($KEY_IDS)) { + Write-Host "No keys to delete, skipping..." + return + } + + $keys = $KEY_IDS.Split(";") + foreach ($keyId in $keys) { + if ([string]::IsNullOrEmpty($keyId)) { + Write-Host "Key is empty, skipping..." + continue + } + + Write-Host "Deleting key [$keyId] from app registration $AAD_APP_ID" + + try { + Remove-AzADAppCredential -ApplicationId $AAD_APP_ID -KeyId $keyId -ErrorAction Stop + } + catch { + Write-Host "Unable to remove credential with key id: '$keyId' from application '$AAD_APP_ID'. The key has probably already been removed." + } + } + + \ No newline at end of file diff --git a/pipelines/templates/secret-rotation/ensure-secret.yml b/pipelines/templates/secret-rotation/ensure-secret.yml new file mode 100644 index 000000000..bf5cf2878 --- /dev/null +++ b/pipelines/templates/secret-rotation/ensure-secret.yml @@ -0,0 +1,45 @@ +parameters: + - name: azureSubscription + - name: subscriptionServiceCore + ## The name is used to scope variables if multiple secrets are rotated within the pipeline. + - name: name + displayName: Secret id, to scope variables + - name: secretName + - name: vaultName + - name: aadApplicationId + +## Check if key is about expired + ## If yes, set variable generateNew => true, else false. + ## At the same time check all version of the key, to identify expired keys that can be deleted from the app registration. + ## These are stored in a variable, as the env dev ops principal should not have access to the fusion core app registration. + +steps: + - template: check-for-expiration.yml + parameters: + azureSubscription: ${{ parameters.azureSubscription }} + vaultName: ${{ parameters.vaultName }} + name: ${{ parameters.name }}_Check + secretName: ${{ parameters.secretName }} + + - template: generate-secret.yml + parameters: + condition: eq(variables['${{ parameters.name }}_Check.generateNew'], 'true') + azureSubscription: ${{ parameters.subscriptionServiceCore }} + name: ${{ parameters.name }}_SecretGen + clientId: ${{ parameters.aadApplicationId }} + secretDescription: 'FRA - $(vaultName) - ${{ parameters.secretName }}' + + - template: update-key-vault.yml + parameters: + condition: eq(variables['${{ parameters.name }}_Check.generateNew'], 'true') + azureSubscription: ${{ parameters.azureSubscription }} + vaultName: ${{ parameters.vaultName }} + secretName: ${{ parameters.secretName }} + secret: $(${{ parameters.name }}_SecretGen.secret) + secretMeta: $(${{ parameters.name }}_SecretGen.secretMeta) + + - template: cleanup-expired-secrets.yml + parameters: + azureSubscription: ${{ parameters.subscriptionServiceCore }} + clientId: ${{ parameters.aadApplicationId }} + keyIds: $(${{ parameters.name }}_Check.keysToDelete) \ No newline at end of file diff --git a/pipelines/templates/secret-rotation/generate-secret.yml b/pipelines/templates/secret-rotation/generate-secret.yml new file mode 100644 index 000000000..61975658e --- /dev/null +++ b/pipelines/templates/secret-rotation/generate-secret.yml @@ -0,0 +1,46 @@ +parameters: + - name: azureSubscription + type: string + - name: clientId + type: string + displayName: 'AAD Application to create secret on' + - name: name + type: string + default: checkSecret + - name: secretDescription + default: 'FRA auto-generated' + - name: condition + +steps: +- task: AzurePowerShell@5 + displayName: 'Generate secret' + name: ${{ parameters.name }} + condition: ${{ parameters.condition }} + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + ScriptType: InlineScript + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + Inline: | + $AAD_APP_ID = "${{ parameters.clientId }}" + Write-Host "Application Client Id: $AAD_APP_ID" + Write-Host "Secret display name: ${{ parameters.secretDescription }}" + + $startDate = Get-Date + $endDate = $startDate.AddMonths(6) + + $credential = @{ + DisplayName = "${{ parameters.secretDescription }}" + StartDateTime = $startDate + EndDateTime = $endDate + } + + $newSecret = New-AzADAppCredential -ApplicationId $AAD_APP_ID -PasswordCredentials $credential + + Write-Host "New secret [$($newSecret.Hint)************] generated with expiration date $endDate, key id [$($newSecret.KeyId)]" + + Write-Host "##vso[task.setvariable variable=secret;isSecret=true;isOutput=true]$($newSecret.SecretText)" + Write-Host "##vso[task.setvariable variable=secretMeta;isOutput=true]$($newSecret.KeyId);$($newSecret.Hint);$($endDate.ToString("u"))" + Write-Host "Set variable [${{ parameters.name }}.secret] -> $($newSecret.Hint)************" + + \ No newline at end of file diff --git a/pipelines/templates/secret-rotation/update-key-vault.yml b/pipelines/templates/secret-rotation/update-key-vault.yml new file mode 100644 index 000000000..8115658e5 --- /dev/null +++ b/pipelines/templates/secret-rotation/update-key-vault.yml @@ -0,0 +1,44 @@ +parameters: + azureSubscription: '' + secretName: '' + vaultName: '' + secret: '' + secretMeta: '' + condition: '' + +steps: +- task: AzurePowerShell@5 + condition: ${{ parameters.condition }} + displayName: 'Persist secret to keyvault' + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + ScriptType: InlineScript + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + Inline: | + $SECRETVAULTNAME = "${{ parameters.vaultName }}" + $SECRET_NAME = "${{ parameters.secretName }}" + $SECRET_VALUE = "${{ parameters.secret }}" + $SECRET_META = "${{ parameters.secretMeta }}" + + Write-Host "Key vault: $SECRETVAULTNAME" + Write-Host "Secret name: $SECRET_NAME" + + if ([string]::IsNullOrEmpty("${{ parameters.secret }}")) { + throw "Secret provided is empty..." + } + + $keyId, $hint, $endDateString = $SECRET_META.Split(";") + + $secretValue = ConvertTo-SecureString -String $SECRET_VALUE -AsPlainText -Force + $endDate = Get-Date $endDateString + + Write-Host "New secret [$hint************] generated with expiration date $endDate, key id [$keyId]" + + Set-AzKeyVaultSecret -VaultName $SECRETVAULTNAME -Name $SECRET_NAME -SecretValue $secretValue -Expires $endDate -Tag @{ "auto-generated" = "true"; "keyId" = $keyId; "hint" = $hint } + + Write-Host "Key vault updated..." + + + + \ No newline at end of file diff --git a/src/backend/infrastructure/arm-templates/database.template.json b/src/backend/infrastructure/arm-templates/database.template.json deleted file mode 100644 index 09b5282f1..000000000 --- a/src/backend/infrastructure/arm-templates/database.template.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "env-name": { "type": "string" }, - - "sqlserver_name": { - "defaultValue": "fusion-test-sqlserver", - "type": "String" - }, - "sql-elastic-pool-id": { - "type": "string" - } - }, - "variables": { - "db-name": "[concat('Fusion-Apps-Resources-', toUpper(parameters('env-name')), '-DB')]" - }, - "resources": [ - { - "type": "Microsoft.Sql/servers/databases", - "apiVersion": "2019-06-01-preview", - "name": "[concat(parameters('sqlserver_name'), '/', variables('db-name'))]", - "location": "northeurope", - "tags": { - "fusion-app": "resources", - "fusion-app-env": "[toLower(parameters('env-name'))]", - "fusion-app-component-type": "db", - "fusion-app-component-id": "[concat('resources-api-db-', toLower(parameters('env-name')))]" - }, - "kind": "v12.0,user,pool", - "properties": { - "collation": "SQL_Latin1_General_CP1_CI_AS", - "maxSizeBytes": 268435456000, - "elasticPoolId": "[parameters('sql-elastic-pool-id')]", - "catalogCollation": "SQL_Latin1_General_CP1_CI_AS", - "zoneRedundant": false, - "readScale": "Disabled", - "readReplicaCount": 0, - "storageAccountType": "GRS" - } - } - ] -} \ No newline at end of file diff --git a/src/backend/infrastructure/cleanup-secrets.ps1 b/src/backend/infrastructure/cleanup-secrets.ps1 deleted file mode 100644 index 50174384e..000000000 --- a/src/backend/infrastructure/cleanup-secrets.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -param( - [string]$applicationId, - [string]$keyVaultName -) - -$SECRET_NAME = "AzureAd--ClientSecret" -$AAD_APP_ID = $applicationId -$SECRETVAULTNAME = $keyVaultName - - -## Cleanup old keys.. -$secretWithVersions = Get-AzKeyVaultSecret -VaultName $SECRETVAULTNAME -Name $SECRET_NAME -IncludeVersions -foreach ($s in $secretWithVersions) { - Write-Host "Processing key: $($s.Id)" - $isAutoGenerated = $null -ne $s.Tags -and $s.Tags["auto-generated"] -eq "true" - - if ($isAutoGenerated) { - $keyId = $s.Tags["keyId"] - $isDeleted = $s.Tags.ContainsKey("deleted") - - if ([string]::IsNullOrEmpty($keyId)) { - Write-Host "`tCould not locate any key id, skipping" - } - - if ($isDeleted -eq $true) { Write-Host "`tAlready deleted @ $($s.Tags["deleted"])"; continue } - if ($s.Expires -lt (Get-Date)) { - Write-Host "`tExpired secret.. Deleting key id: $keyId" - $newTags = $s.Tags - $newTags["deleted"] = (Get-Date).ToString() - Update-AzKeyVaultSecret $s -Version $s.Version -Tag $newTags - - try { - Remove-AzADAppCredential -ApplicationId $AAD_APP_ID -KeyId $keyId -ErrorAction Stop - } - catch { - Write-Host "Unable to remove credential with key id: '$keyId' from application '$AAD_APP_ID'. The key has probably already been removed." - } - - } - } else { - Write-Host "Non auto generated key, skipping" - } -} \ No newline at end of file diff --git a/src/backend/infrastructure/deploy-resources.ps1 b/src/backend/infrastructure/deploy-resources.ps1 deleted file mode 100644 index d90fa129a..000000000 --- a/src/backend/infrastructure/deploy-resources.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -param( - [string]$environment, - [string]$clientId -) - -Write-Host "Starting deployment of general resources" - -$resourceGroup = "fusion-apps-resources-$environment" -$envKeyVault = "kv-fap-resources-$environment" - -Write-Host "Using resource group $resourceGroup" - - -Write-Host "Deploying template" - -New-AzResourceGroupDeployment -Mode Incremental -Name "fusion-app-resources-environment" -ResourceGroupName $resourceGroup -TemplateFile "$($env:BUILD_SOURCESDIRECTORY)/src/backend/infrastructure/arm-templates/environment.template.json" ` - -env-name $environment ` - -sql-connection-string $env:SQLCONNECTIONSTRING - -Write-Host "Setting service principal key vault access" -$spName = (Get-AzContext).Account.Id -Set-AzKeyVaultAccessPolicy -VaultName $envKeyVault -ServicePrincipalName $spName -PermissionsToSecrets get,list,set,delete - -Write-Host "Setting ad app service principal key vault access" -$appSpId = (Get-AzADServicePrincipal -ApplicationId $clientId).Id -Set-AzKeyVaultAccessPolicy -VaultName $envKeyVault -ObjectId $appSpId -PermissionsToSecrets get,list diff --git a/src/backend/infrastructure/fusion-db-config.json b/src/backend/infrastructure/fusion-db-config.json new file mode 100644 index 000000000..8be7c26ad --- /dev/null +++ b/src/backend/infrastructure/fusion-db-config.json @@ -0,0 +1,13 @@ +{ + "name": "fra-resources", + + "metadata": { + "application": "Fusion Resource Allocation", + "team": "FRA" + }, + + "accessControl": { + "administratorGroupName": "acg-fusion-resource-allocation", + "developerGroupName": "acg-fusion-resource-allocation" + } +} \ No newline at end of file diff --git a/src/backend/infrastructure/generate-secret.ps1 b/src/backend/infrastructure/generate-secret.ps1 deleted file mode 100644 index 7b8109531..000000000 --- a/src/backend/infrastructure/generate-secret.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -param( - [string]$applicationId, - [string]$keyVaultName -) - -$SECRET_NAME = "AzureAd--ClientSecret" -$AAD_APP_ID = $applicationId -$SECRETVAULTNAME = $keyVaultName - -Write-Host "Application ID: $AAD_APP_ID" -Write-Host "Key vault: $SECRETVAULTNAME" - -$secret = Get-AzKeyVaultSecret -VaultName $SECRETVAULTNAME -Name $SECRET_NAME - -$generateNew = $false - -if ($null -ne $secret.Tags -and $secret.Tags["auto-generated"] -eq "true") { - Write-Host "Located autogenerated secret" - - ## Check expires flag - $delta = $secret.Expires - (Get-Date) - - Write-Host "Secret expires in $($delta.Days) days" - if ($delta.Days -lt 60) { - $generateNew = $true - } - -} else { - Write-Host "Secret not autogenerated, creating new..." - $generateNew = $true -} - -if (-not $generateNew) { - Write-Host "No need to generate secret..." - return -} else { - Write-Host "Generating new secret..." -} - -## Generate secret on aad app -$startDate = Get-Date -$endDate = $startDate.AddMonths(6) - -$credential = @{ - DisplayName = "Resources - $keyVaultName" - StartDateTime = $startDate - EndDateTime = $endDate -} - -$newSecret = New-AzADAppCredential -ApplicationId $AAD_APP_ID -PasswordCredentials $credential - -Write-Host "New secret [$($newSecret.Hint)************] generated with expiration date $endDate, key id [$($newSecret.KeyId)]" - -$secretValue = ConvertTo-SecureString -String $newSecret.SecretText -AsPlainText -Force -## Add to key vault -Set-AzKeyVaultSecret -VaultName $SECRETVAULTNAME -Name $SECRET_NAME -SecretValue $secretValue -Expires $endDate -Tag @{ "auto-generated" = "true"; "keyId" = $newSecret.KeyId; "hint" = $newSecret.Hint } - -