diff --git a/.github/workflows/deploy_tre.yml b/.github/workflows/deploy_tre.yml index b86b95b19..988cf4d0b 100644 --- a/.github/workflows/deploy_tre.yml +++ b/.github/workflows/deploy_tre.yml @@ -38,6 +38,7 @@ jobs: TF_VAR_acr_name: ${{ secrets.ACR_NAME }} TF_VAR_address_space: ${{ secrets.ADDRESS_SPACE }} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} + AZURE_CONTRIBUTOR_SP: ${{ secrets.AZURE_CONTRIBUTOR_SP }} run: | export env export USE_ENV_VARS_NOT_FILES=true @@ -46,6 +47,9 @@ jobs: export ARM_SUBSCRIPTION_ID=$(echo "$AZURE_CREDENTIALS" | jq -r '.subscriptionId') export ARM_TENANT_ID=$(echo "$AZURE_CREDENTIALS" | jq -r '.tenantId') + export CONTRIBUTOR_SP_CLIENT_ID=$(echo "$AZURE_CONTRIBUTOR_SP" | jq -r '.clientId') + export CONTRIBUTOR_SP_CLIENT_SECRET=$(echo "$AZURE_CONTRIBUTOR_SP" | jq -r '.clientSecret') + echo $GITHUB_REF if [ $GITHUB_EVENT_NAME == 'push' ] && [ $GITHUB_REF == 'refs/heads/develop' ]; then TF_VAR_image_tag='develop-latest' @@ -58,7 +62,7 @@ jobs: export TF_VAR_image_tag make all - + deploy_vanilla_workspace: name: Deploy Vanilla Workspace needs: deploy_tre @@ -66,11 +70,11 @@ jobs: environment: Dev steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v2 - name: Azure Login uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Publish and deploy vanilla workspace bundle shell: bash env: @@ -108,7 +112,7 @@ jobs: - name: Azure Login uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Publish and deploy azureml_devtestlabs workspace bundle shell: bash env: @@ -122,7 +126,7 @@ jobs: TF_VAR_tfstate_resource_group_name: ${{ secrets.MGMT_RESOURCE_GROUP }} TF_VAR_tfstate_storage_account_name: ${{ secrets.STATE_STORAGE_ACCOUNT_NAME }} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} - + run: | export USE_ENV_VARS_NOT_FILES=true export env @@ -130,10 +134,10 @@ jobs: export ARM_CLIENT_SECRET=$(echo "$AZURE_CREDENTIALS" | jq -r '.clientSecret') export ARM_SUBSCRIPTION_ID=$(echo "$AZURE_CREDENTIALS" | jq -r '.subscriptionId') export ARM_TENANT_ID=$(echo "$AZURE_CREDENTIALS" | jq -r '.tenantId') - + curl -L https://cdn.porter.sh/latest/install-linux.sh | bash && ~/.porter/porter mixin install docker export PATH=~/.porter/:$PATH - + make services-azureml-porter-publish make services-devtestlabs-porter-publish make workspaces-azureml_devtestlabs-porter-publish diff --git a/Makefile b/Makefile index a5de60fa6..141487994 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,8 @@ tre-deploy: && . ./devops/scripts/check_dependencies.sh nodocker \ && . ./devops/scripts/load_env.sh ./devops/terraform/.env \ && . ./devops/scripts/load_env.sh ./templates/core/terraform/.env \ - && cd ./templates/core/terraform/ && ./deploy.sh + && cd ./templates/core/terraform/ && ./deploy.sh \ + && cd ../../../ && ./devops/scripts/set_contributor_sp_secrets.sh letsencrypt: echo -e "\n\e[34m»»» 🧩 \e[96mRequesting LetsEncrypt SSL certificate\e[0m..." \ diff --git a/devops/scripts/set_contributor_sp_secrets.sh b/devops/scripts/set_contributor_sp_secrets.sh new file mode 100755 index 000000000..4276314e9 --- /dev/null +++ b/devops/scripts/set_contributor_sp_secrets.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# This script adds the client (app) ID and the client secret (app password) of the service principal used for deploying +# resources (workspaces and workspace services) to Key Vault. +# +# Running the script requires that Azure CLI login has been done with the credentials that have privileges to access +# the Key Vault. +# +# Required environment variables: +# +# - TF_VAR_tre_id - The TRE ID, used to deduce the Key Vault name +# - SUB_ID - The Azure subscription ID +# - CONTRIBUTOR_SP_CLIENT_ID - The client ID of the service principal +# - CONTRIBUTOR_SP_CLIENT_SECRET - The client secret of the service principal +# + +echo -e "\n\e[34m»»» 🤖 \e[96mCreating (or updating) service principal ID and secret to Key Vault\e[0m..." +key_vault_name="kv-$TF_VAR_tre_id" +az account set --subscription $SUB_ID +az keyvault secret set --name deployment-processor-azure-client-id --vault-name $key_vault_name --value $CONTRIBUTOR_SP_CLIENT_ID +az keyvault secret set --name deployment-processor-azure-client-secret --vault-name $key_vault_name --value $CONTRIBUTOR_SP_CLIENT_SECRET > /dev/null diff --git a/devops/terraform/.env.sample b/devops/terraform/.env.sample index b9294c208..fecf440dd 100644 --- a/devops/terraform/.env.sample +++ b/devops/terraform/.env.sample @@ -1,4 +1,4 @@ -# Used by backend state +# Used by backend state TF_VAR_state_storage=__CHANGE_ME__ TF_VAR_mgmt_res_group=__CHANGE_ME__ TF_VAR_state_container=tfstate diff --git a/devops/terraform/bootstrap.sh b/devops/terraform/bootstrap.sh index 0ce9b7256..ee8178173 100755 --- a/devops/terraform/bootstrap.sh +++ b/devops/terraform/bootstrap.sh @@ -25,7 +25,7 @@ BOOTSTRAP_BACKEND # Set up Terraform echo -e "\n\e[34m»»» ✨ \e[96mTerraform init\e[0m..." -terraform init -input=false -backend=true -reconfigure +terraform init -input=false -backend=true -reconfigure # Import the storage account & res group into state echo -e "\n\e[34m»»» 📤 \e[96mImporting resources to state\e[0m..." diff --git a/devops/terraform/variables.tf b/devops/terraform/variables.tf index da77ee8e4..80fdc8f53 100644 --- a/devops/terraform/variables.tf +++ b/devops/terraform/variables.tf @@ -22,4 +22,4 @@ variable "acr_sku" { variable "acr_name" { type = string description = "Name of ACR" -} \ No newline at end of file +} diff --git a/docs/cd-setup.md b/docs/cd-setup.md index 6c34edc6f..f8ebf9a67 100644 --- a/docs/cd-setup.md +++ b/docs/cd-setup.md @@ -1,15 +1,34 @@ -# Continuous Delivery with GitHub Actions +# Setting up GitHub Actions workflows -## Setup +These are onetime configuration steps required to set up the GitHub Actions workflows (pipelines). After the steps the [TRE deployment workflow](../.github/workflows/deploy_tre.yml) is ready to run. -Create an SPN that will be used to provision resources in your Azure subscription: +## Create service principals -```cmd -az account set -s {SubID} -az ad sp create-for-rbac -n "MyTREAppDeployment" --role Owner --scopes /subscriptions/{SubID} --sdk-auth -``` +Two service principals need to be created: One to authorize the workflow itself and another for the deployment processor to provision resources for the TRE workspaces and workspace services. -Save JSON the output in a GitHub secret called `AZURE_CREDENTIALS`. +1. Login with Azure CLI and set the subscription used by TRE: + + ```cmd + az account set --subscription + ``` + +1. Create the workflow service principal with "owner" role: + + ```cmd + az ad sp create-for-rbac --name "MyTREAppDeployment" --role Owner --scopes /subscriptions/ --sdk-auth + ``` + +1. Save the JSON output in a [GitHub secret](https://docs.github.com/en/actions/reference/encrypted-secrets) called `AZURE_CREDENTIALS`. + +1. Create the service principal, used by the deployment processor, that has contributor privileges: + + ```cmd + az ad sp create-for-rbac --name "sp-msfttre-deployment-processor" --role Contributor --scopes /subscriptions/ --sdk-auth + ``` + +1. Save the JSON output in a GitHub secret called `AZURE_CONTRIBUTOR_SP`. + +## Set other sercrets You will also need to create the following secrets: @@ -21,6 +40,4 @@ You will also need to create the following secrets: - `LOCATION` - `ADDRESS_SPACE` -For descriptions of what each of these variables are, and example values, please review [docs/developer-quickstart.md](docs/developer-quickstart.md). - -The `Deploy TRE` workflow can then be run. +For descriptions of what each of these variables are, and example values, please review [Developer quickstart guide](./developer-quickstart.md). diff --git a/templates/core/terraform/keyvault/keyvault.tf b/templates/core/terraform/keyvault/keyvault.tf index e3d191899..7078df6cf 100644 --- a/templates/core/terraform/keyvault/keyvault.tf +++ b/templates/core/terraform/keyvault/keyvault.tf @@ -1,10 +1,10 @@ resource "azurerm_key_vault" "kv" { - name = "kv-${var.tre_id}" - location = var.location - resource_group_name = var.resource_group_name - sku_name = "standard" + name = "kv-${var.tre_id}" + tenant_id = var.tenant_id + location = var.location + resource_group_name = var.resource_group_name + sku_name = "standard" purge_protection_enabled = true - tenant_id = var.tenant_id } data "azurerm_client_config" "deployer" {} @@ -14,9 +14,19 @@ resource "azurerm_key_vault_access_policy" "deploy_user" { tenant_id = data.azurerm_client_config.deployer.tenant_id object_id = data.azurerm_client_config.deployer.object_id - key_permissions = [ "Get", "List", "Update", "Create", "Import", "Delete" ] - secret_permissions = [ "Get", "List", "Set", "Delete" ] - certificate_permissions = [ "Get", "List", "Update", "Create", "Import", "Delete" ] + key_permissions = [ "Get", "List", "Update", "Create", "Import", "Delete", ] + secret_permissions = [ "Get", "List", "Set", "Delete", ] + certificate_permissions = [ "Get", "List", "Update", "Create", "Import", "Delete", ] +} + +resource "azurerm_key_vault_access_policy" "managed_identity" { + key_vault_id = azurerm_key_vault.kv.id + tenant_id = var.managed_identity_tenant_id + object_id = var.managed_identity_object_id + + key_permissions = [ "Get", "List", ] + secret_permissions = [ "Get", "List", ] + certificate_permissions = [ "Get", "List", ] } resource "azurerm_private_dns_zone" "vaultcore" { @@ -48,4 +58,4 @@ resource "azurerm_private_endpoint" "kvpe" { is_manual_connection = false subresource_names = ["Vault"] } -} \ No newline at end of file +} diff --git a/templates/core/terraform/keyvault/variables.tf b/templates/core/terraform/keyvault/variables.tf index 2a7832aac..175fb27b9 100644 --- a/templates/core/terraform/keyvault/variables.tf +++ b/templates/core/terraform/keyvault/variables.tf @@ -3,4 +3,6 @@ variable "location" {} variable "resource_group_name" {} variable "core_vnet" {} variable "shared_subnet" {} -variable "tenant_id" {} \ No newline at end of file +variable "tenant_id" {} +variable "managed_identity_tenant_id" {} +variable "managed_identity_object_id" {} diff --git a/templates/core/terraform/main.tf b/templates/core/terraform/main.tf index d8531423d..632132456 100644 --- a/templates/core/terraform/main.tf +++ b/templates/core/terraform/main.tf @@ -16,9 +16,9 @@ resource "azurerm_resource_group" "core" { location = var.location name = "rg-${var.tre_id}" tags = { - project = "Azure Trusted Research Environment" - tre_id = "${var.tre_id}" - source = "https://github.com/microsoft/AzureTRE/" + project = "Azure Trusted Research Environment" + tre_id = "${var.tre_id}" + source = "https://github.com/microsoft/AzureTRE/" } } @@ -76,7 +76,7 @@ module "api-webapp" { state_store_endpoint = module.state-store.endpoint state_store_key = module.state-store.primary_key service_bus_resource_request_queue = module.servicebus.workspacequeue - managed_identity = module.identity.managed_identity + managed_identity = module.identity.managed_identity } module "identity" { @@ -84,7 +84,7 @@ module "identity" { tre_id = var.tre_id location = var.location resource_group_name = azurerm_resource_group.core.name - servicebus_namespace = module.servicebus.servicebus_namespace + servicebus_namespace = module.servicebus.servicebus_namespace } module "processor_function" { @@ -117,22 +117,28 @@ module "servicebus" { } module "keyvault" { - source = "./keyvault" - tre_id = var.tre_id - location = var.location - resource_group_name = azurerm_resource_group.core.name - shared_subnet = module.network.shared - core_vnet = module.network.core - tenant_id = data.azurerm_client_config.current.tenant_id + source = "./keyvault" + tre_id = var.tre_id + location = var.location + resource_group_name = azurerm_resource_group.core.name + shared_subnet = module.network.shared + core_vnet = module.network.core + tenant_id = data.azurerm_client_config.current.tenant_id + managed_identity_tenant_id = module.identity.managed_identity.tenant_id + managed_identity_object_id = module.identity.managed_identity.principal_id + + depends_on = [ + module.identity + ] } module "firewall" { - source = "./firewall" - tre_id = var.tre_id - location = var.location - resource_group_name = azurerm_resource_group.core.name - firewall_subnet = module.network.azure_firewall - shared_subnet = module.network.shared + source = "./firewall" + tre_id = var.tre_id + location = var.location + resource_group_name = azurerm_resource_group.core.name + firewall_subnet = module.network.azure_firewall + shared_subnet = module.network.shared } module "routetable" { @@ -145,12 +151,12 @@ module "routetable" { } module "acr" { - source = "./acr" - tre_id = var.tre_id - location = var.location - resource_group_name = azurerm_resource_group.core.name - core_vnet = module.network.core - shared_subnet = module.network.shared + source = "./acr" + tre_id = var.tre_id + location = var.location + resource_group_name = azurerm_resource_group.core.name + core_vnet = module.network.core + shared_subnet = module.network.shared } module "state-store" { @@ -163,9 +169,9 @@ module "state-store" { } module "bastion" { - source = "./bastion" - tre_id = var.tre_id - location = var.location - resource_group_name = azurerm_resource_group.core.name - bastion_subnet = module.network.bastion + source = "./bastion" + tre_id = var.tre_id + location = var.location + resource_group_name = azurerm_resource_group.core.name + bastion_subnet = module.network.bastion } diff --git a/templates/core/terraform/variables.tf b/templates/core/terraform/variables.tf index 0f5140d75..24efad066 100644 --- a/templates/core/terraform/variables.tf +++ b/templates/core/terraform/variables.tf @@ -42,4 +42,5 @@ variable "docker_registry_username" { variable "docker_registry_password" { type = string description = "Docker registry password" + sensitive = true }