diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8dd2c9fef5..297da51838 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ # [Choice] Python version: 3.11 3.12, 3.13 ARG VARIANT="3.12" ARG TARGETPLATFORM="linux/amd64" -FROM --platform="${TARGETPLATFORM}" mcr.microsoft.com/vscode/devcontainers/python:dev-${VARIANT}-bullseye +FROM --platform="${TARGETPLATFORM}" mcr.microsoft.com/vscode/devcontainers/python:dev-${VARIANT}-bookworm # This will be set to true when running in VSCode ARG INTERACTIVE="false" @@ -33,11 +33,15 @@ COPY .devcontainer/scripts/docker-client.sh /tmp/ RUN /tmp/docker-client.sh $USERNAME # Install Docker +ARG DOCKER_CE_VERSION="5:27.4.1-1~debian.12~bookworm" +ARG DOCKER_CE_CLI_VERSION="5:27.4.1-1~debian.12~bookworm" +ARG DOCKER_COMPOSE_PLUGIN_VERSION="2.32.1-1~debian.12~bookworm" +ARG DOCKER_CONTAINERD_VERSION="1.7.24-1" RUN apt-get update && apt-get install -y ca-certificates curl gnupg lsb-release --no-install-recommends \ && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \ | tee /etc/apt/sources.list.d/docker.list > /dev/null \ - && apt-get update && apt-get install -y docker-ce="5:24.0.0-1~debian.11~bullseye" docker-ce-cli="5:24.0.0-1~debian.11~bullseye" docker-compose-plugin="2.21.0-1~debian.11~bullseye" containerd.io="1.6.24-1" docker-buildx-plugin --no-install-recommends \ + && apt-get update && apt-get install -y docker-ce="$DOCKER_CE_VERSION" docker-ce-cli="$DOCKER_CE_CLI_VERSION" docker-compose-plugin="$DOCKER_COMPOSE_PLUGIN_VERSION" containerd.io="$DOCKER_CONTAINERD_VERSION" docker-buildx-plugin --no-install-recommends \ && apt-get clean -y && rm -rf /var/lib/apt/lists/* # Install Certbot @@ -48,8 +52,9 @@ RUN if [ "${INTERACTIVE}" = "true" ]; then \ && /opt/certbot/bin/pip install --no-cache-dir certbot \ && apt-get clean -y && rm -rf /var/lib/apt/lists/* ; fi +# Install Porter, updates should be propagated to the RP. ARG PORTER_HOME_V1=/home/$USERNAME/.porter/ -ARG PORTER_VERSION=v1.2.0 +ARG PORTER_VERSION=v1.2.1 ARG PORTER_TERRAFORM_MIXIN_VERSION=v1.0.5 ARG PORTER_AZ_MIXIN_VERSION=v1.0.4 ARG PORTER_AZURE_PLUGIN_VERSION=v1.2.3 @@ -75,12 +80,12 @@ COPY ["airlock_processor/requirements.txt", "/tmp/pip-tmp/airlock_processor/"] RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt # Install azure-cli -ARG AZURE_CLI_VERSION=2.67.0-1~bullseye +ARG AZURE_CLI_VERSION=2.67.0-1~bookworm COPY .devcontainer/scripts/azure-cli.sh /tmp/ RUN export AZURE_CLI_VERSION=${AZURE_CLI_VERSION} \ && /tmp/azure-cli.sh -ARG YQ_VERSION="v4.33.3" +ARG YQ_VERSION="v4.44.6" RUN curl -L --fail -o /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ && chmod +x /usr/local/bin/yq diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9d49130458..68cd00e034 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -29,7 +29,7 @@ // Mount docker socket for docker builds "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock", // Mounts the github cli login details from the host machine to the container (~/.config/gh/hosts.yml) - "type=bind,source=${env:HOME}${env:USERPROFILE}/.config,target=/home/vscode/.config", + "type=bind,source=${env:HOME}${env:USERPROFILE}/.config,target=/home/vscode/.config" ], "remoteUser": "vscode", "containerEnv": { @@ -44,20 +44,13 @@ // Set *default* container specific settings.json values on container create. "settings": { "terminal.integrated.defaultProfile.linux": "bash", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, "python.pythonPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, "python.formatting.provider": "black", "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ @@ -275,8 +268,12 @@ "extensions": [ "ms-python.python", "ms-python.pylance", + "ms-python.flake8", + "nwgh.bandit", "hashicorp.terraform", "github.vscode-pull-request-github", + "gitHub.copilot", + "github.copilot-chat", "getporter.porter-vscode", "davidanson.vscode-markdownlint", "editorconfig.editorconfig", @@ -291,5 +288,8 @@ 8000 ], // Run commands after the container is created. - "postCreateCommand": "./.devcontainer/scripts/post-create.sh" + "postCreateCommand": "./.devcontainer/scripts/post-create.sh", + "initializeCommand": [ + "./.devcontainer/scripts/initialize" + ] } diff --git a/.devcontainer/scripts/initialize b/.devcontainer/scripts/initialize new file mode 100755 index 0000000000..8cfb7eb8df --- /dev/null +++ b/.devcontainer/scripts/initialize @@ -0,0 +1,3 @@ +#!/bin/bash + +mkdir -p "$HOME/.azure" "$HOME/.config" || true diff --git a/.devcontainer/scripts/initialize.cmd b/.devcontainer/scripts/initialize.cmd new file mode 100644 index 0000000000..d8d6060e09 --- /dev/null +++ b/.devcontainer/scripts/initialize.cmd @@ -0,0 +1,2 @@ +@echo off +mkdir %USERPROFILE%\.azure %USERPROFILE%\.config || exit /b 0 diff --git a/.github/actions/devcontainer_run_command/action.yml b/.github/actions/devcontainer_run_command/action.yml index 55157e1716..ba790c563c 100644 --- a/.github/actions/devcontainer_run_command/action.yml +++ b/.github/actions/devcontainer_run_command/action.yml @@ -178,9 +178,19 @@ runs: - name: Run command in DevContainer shell: bash run: | + # Write command to a command.sh script file + cat <> ./command.sh + #!/bin/bash + set -x + ${{ inputs.COMMAND }} + EOF + + chmod +x ./command.sh + docker run --rm --mount \ "type=bind,src=${{ github.workspace }},dst=/workspaces/tre" \ -v /var/run/docker.sock:/var/run/docker.sock \ + -v "./command.sh:/workspaces/tre/command.sh" \ --workdir /workspaces/tre \ --user vscode \ -e TF_INPUT="0" \ @@ -246,4 +256,7 @@ runs: -e TF_VAR_app_gateway_sku=${{ inputs.APP_GATEWAY_SKU }} \ -e E2E_TESTS_NUMBER_PROCESSES="${{ inputs.E2E_TESTS_NUMBER_PROCESSES }}" \ '${{ inputs.CI_CACHE_ACR_NAME }}${{ env.ACR_DOMAIN_SUFFIX }}/tredev:${{ inputs.DEVCONTAINER_TAG }}' \ - bash -c "${{ inputs.COMMAND }}" + bash -c -x "./command.sh" + + # Clean up temporary script file + rm ./command.sh diff --git a/.github/workflows/build_validation_develop.yml b/.github/workflows/build_validation_develop.yml index c44cba4b9d..a9364293d8 100644 --- a/.github/workflows/build_validation_develop.yml +++ b/.github/workflows/build_validation_develop.yml @@ -1,11 +1,11 @@ --- name: Build Validation -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy pull_request: branches: - main - - 'feature/**' + - "feature/**" # for each ref (branch/pr) run just the most recent, # cancel other pending/running ones @@ -52,6 +52,16 @@ jobs: terraform_workspace_services: - templates/workspace_services/**/terraform/**/*.tf + - uses: hashicorp/setup-terraform@v3 + if: ${{ steps.filter.outputs.terraform == 'true' }} + with: + terraform_version: "1.9.8" + + - uses: hashicorp/setup-terraform@v3 + if: ${{ steps.filter.outputs.terraform == 'true' }} + with: + terraform_version: "1.9.8" + - name: Terraform format check if: ${{ steps.filter.outputs.terraform == 'true' }} run: terraform fmt -check -recursive @@ -107,7 +117,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_TERRAFORM_TFLINT: true TERRAFORM_TFLINT_CONFIG_FILE: .tflint_core.hcl - FILTER_REGEX_INCLUDE: './core/.*' + FILTER_REGEX_INCLUDE: "./core/.*" - name: Workspace Tags if: ${{ steps.filter.outputs.terraform_workspaces == 'true' }} @@ -118,7 +128,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_TERRAFORM_TFLINT: true TERRAFORM_TFLINT_CONFIG_FILE: .tflint_workspaces.hcl - FILTER_REGEX_INCLUDE: './templates/workspaces/.*' + FILTER_REGEX_INCLUDE: "./templates/workspaces/.*" - name: Workspace Services Tags if: ${{ steps.filter.outputs.terraform_workspace_services == 'true' }} @@ -129,8 +139,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_TERRAFORM_TFLINT: true TERRAFORM_TFLINT_CONFIG_FILE: .tflint_workspace_services.hcl - FILTER_REGEX_INCLUDE: './templates/workspaces/.*' - FILTER_REGEX_EXCLUDE: '.*user_resource.*' + FILTER_REGEX_INCLUDE: "./templates/workspaces/.*" + FILTER_REGEX_EXCLUDE: ".*user_resource.*" - name: User Resources Tags if: ${{ steps.filter.outputs.terraform_workspace_services == 'true' }} @@ -141,7 +151,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_TERRAFORM_TFLINT: true TERRAFORM_TFLINT_CONFIG_FILE: .tflint_user_resources.hcl - FILTER_REGEX_INCLUDE: './templates/workspace_services/.*/user_resources/.*' + FILTER_REGEX_INCLUDE: "./templates/workspace_services/.*/user_resources/.*" - name: Shared Services Tags if: ${{ steps.filter.outputs.terraform_shared_services == 'true' }} @@ -152,4 +162,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_TERRAFORM_TFLINT: true TERRAFORM_TFLINT_CONFIG_FILE: .tflint_shared_services.hcl - FILTER_REGEX_INCLUDE: './templates/shared_services/.*' + FILTER_REGEX_INCLUDE: "./templates/shared_services/.*" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 176b3b6a39..92327b53ff 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,13 +12,13 @@ # name: "CodeQL" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy push: branches: [main] pull_request: branches: [main] schedule: - - cron: '41 3 * * 5' + - cron: "41 3 * * 5" # for each ref (branch/pr) run just the most recent, # cancel other pending/running ones @@ -29,7 +29,7 @@ concurrency: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: actions: read contents: read @@ -38,7 +38,7 @@ jobs: strategy: fail-fast: false matrix: - language: ['python', 'java', 'javascript', 'typescript'] + language: ["python", "java", "javascript", "typescript"] steps: - name: Checkout repository diff --git a/.github/workflows/deploy_tre_reusable.yml b/.github/workflows/deploy_tre_reusable.yml index e775bed80b..3942f9c4cb 100644 --- a/.github/workflows/deploy_tre_reusable.yml +++ b/.github/workflows/deploy_tre_reusable.yml @@ -424,9 +424,14 @@ jobs: uses: ./.github/actions/devcontainer_run_command with: # Although porter publish will build automatically, our makefile build target includes logic that should run - COMMAND: >- - for i in {1..3}; do make bundle-build bundle-publish DIR=${{ matrix.BUNDLE_DIR }} - && ec=0 && break || ec=\$? && sleep 30; done; (exit \$ec) + COMMAND: | + # Loop to retry the make command up to 3 times + for i in {1..3}; do + make bundle-build bundle-publish DIR=${{ matrix.BUNDLE_DIR }} && ec=0 && break || ec=\$? && sleep 30; + done; + + # Exit with the last status code + (exit \$ec) DEVCONTAINER_TAG: ${{ inputs.DEVCONTAINER_TAG }} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} AZURE_ENVIRONMENT: ${{ vars.AZURE_ENVIRONMENT }} @@ -470,9 +475,14 @@ jobs: uses: ./.github/actions/devcontainer_run_command with: # Although porter publish will build automatically, our makefile build target includes logic that should run - COMMAND: >- - for i in {1..3}; do make bundle-build bundle-publish DIR=${{ matrix.BUNDLE_DIR }} - && ec=0 && break || ec=\$? && sleep 30; done; (exit \$ec) + COMMAND: | + # Loop to retry the make command up to 3 times + for i in {1..3}; do + make bundle-build bundle-publish DIR=${{ matrix.BUNDLE_DIR }} && ec=0 && break || ec=\$? && sleep 30; + done + + # Exit with the last status code + (exit \$ec) DEVCONTAINER_TAG: ${{ inputs.DEVCONTAINER_TAG }} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} AZURE_ENVIRONMENT: ${{ vars.AZURE_ENVIRONMENT }} @@ -514,9 +524,14 @@ jobs: - name: Register bundle uses: ./.github/actions/devcontainer_run_command with: - COMMAND: >- - for i in {1..3}; do make bundle-register DIR=${{ matrix.BUNDLE_DIR }} - && ec=0 && break || ec=\$? && sleep 10; done; (exit \$ec) + COMMAND: | + # Loop to retry the make command up to 3 times + for i in {1..3}; do + make bundle-register DIR=${{ matrix.BUNDLE_DIR }} && ec=0 && break || ec=\$? && sleep 10; + done + + # Exit with the last status code + (exit \$ec) DEVCONTAINER_TAG: ${{ inputs.DEVCONTAINER_TAG }} CI_CACHE_ACR_NAME: ${{ secrets.CI_CACHE_ACR_NAME}} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} @@ -574,9 +589,14 @@ jobs: - name: Register bundle uses: ./.github/actions/devcontainer_run_command with: - COMMAND: >- - for i in {1..3}; do make bundle-register DIR=${{ matrix.BUNDLE_DIR }} - && ec=0 && break || ec=\$? && sleep 10; done; (exit \$ec) + COMMAND: | + # Loop to retry the make command up to 3 times + for i in {1..3}; do + make bundle-register DIR=${{ matrix.BUNDLE_DIR }} && ec=0 && break || ec=\$? && sleep 10; + done; + + # Exit with the last status code + (exit \$ec) DEVCONTAINER_TAG: ${{ inputs.DEVCONTAINER_TAG }} CI_CACHE_ACR_NAME: ${{ secrets.CI_CACHE_ACR_NAME}} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} @@ -623,9 +643,14 @@ jobs: - name: Register bundle uses: ./.github/actions/devcontainer_run_command with: - COMMAND: >- - for i in {1..3}; do make bundle-register DIR=${{ matrix.BUNDLE_DIR }} - && ec=0 && break || ec=\$? && sleep 10; done; (exit \$ec) + COMMAND: | + # Loop to retry the make command up to 3 times + for i in {1..3}; do + make bundle-register DIR=${{ matrix.BUNDLE_DIR }} && ec=0 && break || ec=\$? && sleep 10; + done; + + # Exit with the last status code + (exit \$ec) DEVCONTAINER_TAG: ${{ inputs.DEVCONTAINER_TAG }} CI_CACHE_ACR_NAME: ${{ secrets.CI_CACHE_ACR_NAME}} AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} @@ -863,3 +888,4 @@ jobs: with: junit_files: "artifacts/**/*.xml" check_name: "E2E Test Results" + comment_mode: off diff --git a/CHANGELOG.md b/CHANGELOG.md index 71fd30deac..c596b48c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,36 @@ -## 0.20.0 (Unreleased) +## 0.21.0 (Unreleased) **BREAKING CHANGES & MIGRATIONS**: -* InnerEye and MLFlow bundles depreciated and removed from main. If you wish to update and deploy these worksapce services they can be retrieved from release 0.19.1. ([#4127](https://github.com/microsoft/AzureTRE/issues/4127)) -* Upgrade Python version from 3.8 to 3.12 ([#3949](https://github.com/microsoft/AzureTRE/issues/3949)) + +ENHANCEMENTS: +* Core key vault firewall should not be set to "Allow public access from all networks" ([#4250](https://github.com/microsoft/AzureTRE/issues/4250)) +* Allow workspace App Service Plan SKU to be updated ([#4331](https://github.com/microsoft/AzureTRE/issues/4331)) +* Add core requests endpoint and UI to enable requests to be managed TRE wide. ([[#2510](https://github.com/microsoft/AzureTRE/issues/2510)]) +* Remove public IP from TRE's firewall when forced tunneling is configured ([#4346](https://github.com/microsoft/AzureTRE/pull/4346)) +* Upgrade AzureRM Terraform provider from `3.117.0` to `4.14.0`. ([[#4255](https://github.com/microsoft/AzureTRE/pull/4255/)]) +* Subnet definitions are now inline in the `azurerm_virtual_network` resource, and NSG associations are set using `security_group` in each subnet block (no separate `azurerm_subnet_network_security_group_association` needed). ([[#4255](https://github.com/microsoft/AzureTRE/pull/4255/)]) +* Azure Cosmos DB should disable public network access ([#4322](https://github.com/microsoft/AzureTRE/issues/4322)) +* Add bundle target to Makefile for handling different bundle types in single command ([#4372](https://github.com/microsoft/AzureTRE/issues/4372)) +* Migrate UI to Vite build engine and update dependencies ([#4368](https://github.com/microsoft/AzureTRE/pull/4368)) +* Add Windows image field to the Admin VM template ([#4274](https://github.com/microsoft/AzureTRE/pull/4274)) +* Update TLS to the latest version for web apps / function apps (([#4351](https://github.com/microsoft/AzureTRE/issues/4351)) + +BUG FIXES: +* Fix upgrade when porter install has failed ([#4338](https://github.com/microsoft/AzureTRE/pull/4338)) +* Certs shared service: Secret nexus-ssl-password is currently in a deleted but recoverable state ([#4294](https://github.com/microsoft/AzureTRE/issues/4294)]) +* Fix Cosmos DB local debugging configuration ([#4340](https://github.com/microsoft/AzureTRE/pull/4340)) + +COMPONENTS: + +## 0.20.0 (Feburary 9, 2025) + +**BREAKING CHANGES & MIGRATIONS**: +* InnerEye and MLFlow bundles depreciated and removed from main. If you wish to update and deploy these workspace services they can be retrieved from release 0.19.1. ([#4127](https://github.com/microsoft/AzureTRE/issues/4127)) +* This release removed support for Porter v0.*. If you're upgrading from a much earlier version you can't go directly to this one. ([#4228](https://github.com/microsoft/AzureTRE/issues/4228)) FEATURES: +* Add support for customer-managed keys encryption. Core support ([#4141](https://github.com/microsoft/AzureTRE/issues/4142), [#4144](https://github.com/microsoft/AzureTRE/issues/4144)), Base workspace ([#4161](https://github.com/microsoft/AzureTRE/pull/4161)), other templates ([#4145](https://github.com/microsoft/AzureTRE/issues/4145)) ENHANCEMENTS: * Disable storage account cross tenant replication ([#4116](https://github.com/microsoft/AzureTRE/pull/4116)) @@ -13,19 +38,34 @@ ENHANCEMENTS: * Split log entries with [Log chunk X of Y] for better readability. ([#3992](https://github.com/microsoft/AzureTRE/issues/3992)) * Expose APP_SERVICE_SKU build variable to allow enablement of App Gateway WAF ([#4111](https://github.com/microsoft/AzureTRE/pull/4111)) * Update Terraform to use Azure AD authentication rather than storage account keys ([#4103](https://github.com/microsoft/AzureTRE/issues/4103)) +* Consolidate Terraform upgrade scripts ([#4099](https://github.com/microsoft/AzureTRE/issues/4099)) * Storage accounts should use infrastructure encryption ([#4001](https://github.com/microsoft/AzureTRE/issues/4001)) * Update obsolete Terraform properties ([#4136](https://github.com/microsoft/AzureTRE/issues/4136)) * Update Guacamole version and dependencies ([#4140](https://github.com/microsoft/AzureTRE/issues/4140)) -* Add partial (core resources only) support for customer-managed keys ([#4141](https://github.com/microsoft/AzureTRE/issues/4142), [#4144](https://github.com/microsoft/AzureTRE/issues/4144)) * Update the Azure CLI version to 2.67.0 in dev container and vmss ([#4157](https://github.com/microsoft/AzureTRE/pull/4157)) -* Upgrade Python version from 3.8 to 3.12 ([#3949](https://github.com/microsoft/AzureTRE/issues/3949)) * Move Github PR bot commands into main documentation ([#4167](https://github.com/microsoft/AzureTRE/pull/4167)) * Block Authentication with keys to CosmosDB SQL account ([#4175](https://github.com/microsoft/AzureTRE/pull/4175)) -* Add support for customer-managed keys encryption in base workspace ([#4161](https://github.com/microsoft/AzureTRE/pull/4161)) +* Change the way "inherited" workspaces retrieve the base workspace code ([#4162](https://github.com/microsoft/AzureTRE/issues/4162)) * Add option to configure auto shutdown for Linux VM ([#4186](https://github.com/microsoft/AzureTRE/issues/4186)) * Add ability to download VSCode Extensions ([[#4187](https://github.com/microsoft/AzureTRE/issues/4187)]) * Update Windows VM Images ([#4198](https://github.com/microsoft/AzureTRE/pull/4198)) * Enhance DPI of Linux display ([[#4200](https://github.com/microsoft/AzureTRE/issues/4200)]) +* Update Admin VM versions ([[#4217](https://github.com/microsoft/AzureTRE/issues/4217)]) +* Update devcontainer/RP/API package versions: base image, docker, az cli, YQ ([#4225](https://github.com/microsoft/AzureTRE/pull/4225)) +* Purge container repos individually in when using `make tre-destroy` ([#4230](https://github.com/microsoft/AzureTRE/pull/4230)) +* Upgrade Python version from 3.8 to 3.12 ([#3949](https://github.com/microsoft/AzureTRE/issues/3949))Upgrade Python version from 3.8 to 3.12 (#3949) +* Disable storage account key usage ([[#4227](https://github.com/microsoft/AzureTRE/issues/4227)]) +* Update Guacamole dependencies ([[#4232](https://github.com/microsoft/AzureTRE/issues/4232)]) +* Add option to force tunnel TRE's Firewall ([#4237](https://github.com/microsoft/AzureTRE/issues/4237)) +* Add EventGrid diagnostics to identify airlock issues ([#4258](https://github.com/microsoft/AzureTRE/issues/4258)) +* Disable local authentication in ServiceBus ([#4259](https://github.com/microsoft/AzureTRE/issues/4259)) +* Allow enablement of Secure Boot and vTPM for Guacamole VMs ([#4235](https://github.com/microsoft/AzureTRE/issues/4235)) +* Surface the server-layout parameter of Guacamole [server-layout](https://guacamole.apache.org/doc/gug/configuring-guacamole.html#session-settings) ([#4234](https://github.com/microsoft/AzureTRE/issues/4234)) +* Add encryption at host for VMs ([#4263](https://github.com/microsoft/AzureTRE/pull/4263)) +* Downgrade certs shared service App Gateway to Basic SKU ([#4300](https://github.com/microsoft/AzureTRE/issues/4300)) +* Airlock function host storage to use the user-assigned managed identity ([#4276](https://github.com/microsoft/AzureTRE/issues/4276)) +* Disable local authentication in EventGrid ([#4254](https://github.com/microsoft/AzureTRE/issues/4254)) + BUG FIXES: * Update KeyVault references in API to use the version so Terraform cascades the update ([#4112](https://github.com/microsoft/AzureTRE/pull/4112)) @@ -38,9 +78,49 @@ BUG FIXES: * Upgrade aiohttp version for security fixes ([#4197](https://github.com/microsoft/AzureTRE/pull/4197)) * Fix failing tests, .env missing and storage logs ([#4207](https://github.com/microsoft/AzureTRE/issues/4207)) * Unable to delete virtual machines, add skip_shutdown_and_force_delete = true ([#4135](https://github.com/microsoft/AzureTRE/issues/4135)) +* Bump terraform version in windows VM template ([#4212](https://github.com/microsoft/AzureTRE/issues/4212)) +* Upgrade azurerm terraform provider from v3.112.0 to v3.117.0 to mitigate storage account deployment issue ([#4004](https://github.com/microsoft/AzureTRE/issues/4004)) +* Fix VM actions where Workspace shared storage doesn't allow shared key access ([#4222](https://github.com/microsoft/AzureTRE/issues/4222)) +* Fix public exposure in Guacamole service ([[#4199](https://github.com/microsoft/AzureTRE/issues/4199)]) +* Fix Azure ML network tags to use name rather than ID ([[#4151](https://github.com/microsoft/AzureTRE/issues/4151)]) +* Windows R version must be 4.1.2 otherwise post install script doesn't update package mirror URL ([#4288](https://github.com/microsoft/AzureTRE/issues/4288)) +* Recreate tre_output.json if empty. ([[#4292](https://github.com/microsoft/AzureTRE/issues/4292)]) +* Ensure R directory is present before attempting to update package mirror URL ([#4332](https://github.com/microsoft/AzureTRE/pull/4332)) + COMPONENTS: +| name | version | +| ----- | ----- | +| devops | 0.5.5 | +| core | 0.11.23 | +| ui | 0.6.3 | +| tre-shared-service-databricks-private-auth | 0.1.11 | +| tre-shared-service-gitea | 1.1.4 | +| tre-shared-service-sonatype-nexus | 3.3.2 | +| tre-shared-service-firewall | 1.3.0 | +| tre-shared-service-admin-vm | 0.5.2 | +| tre-shared-service-certs | 0.7.3 | +| tre-shared-service-airlock-notifier | 1.0.8 | +| tre-shared-service-cyclecloud | 0.7.2 | +| tre-workspace-airlock-import-review | 0.14.2 | +| tre-workspace-base | 1.9.2 | +| tre-workspace-unrestricted | 0.13.2 | +| tre-workspace-service-gitea | 1.2.2 | +| tre-workspace-service-mysql | 1.0.9 | +| tre-workspace-service-health | 0.2.11 | +| tre-workspace-service-openai | 1.0.6 | +| tre-service-azureml | 0.9.2 | +| tre-user-resource-aml-compute-instance | 0.5.11 | +| tre-service-databricks | 1.0.10 | +| tre-workspace-service-azuresql | 1.0.15 | +| tre-service-guacamole | 0.12.7 | +| tre-service-guacamole-export-reviewvm | 0.2.2 | +| tre-service-guacamole-linuxvm | 1.2.4 | +| tre-service-guacamole-import-reviewvm | 0.3.2 | +| tre-service-guacamole-windowsvm | 1.2.6 | +| tre-workspace-service-ohdsi | 0.3.2 | + ## 0.19.1 **BREAKING CHANGES & MIGRATIONS**: @@ -56,6 +136,7 @@ BUG FIXES: * Workspace creation blocked due to Azure API depreciation ([#4095](https://github.com/microsoft/AzureTRE/issues/4095)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.2 | @@ -114,6 +195,7 @@ BUG FIXES: * Update .NET version on Linux VMs ([#4067](https://github.com/microsoft/AzureTRE/issues/4067)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -177,6 +259,7 @@ BUG FIXES: * Add lifecycle rule to the Gitea Shared Service template for the MySQL resource to stop it recreating on `update` ([#4006](https://github.com/microsoft/AzureTRE/issues/4006)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -231,6 +314,7 @@ BUG FIXES: * Fix issue with firewall failing to deploy on a new TRE deploy ([#3775](https://github.com/microsoft/AzureTRE/issues/3775)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -280,6 +364,7 @@ BUG FIXES: * Airlock Import Review workspace uses dedicated DNS zone to prevent conflict with core ([#3767](https://github.com/microsoft/AzureTRE/pull/3767)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -322,6 +407,7 @@ BUG FIXES: * Fix workspace not loading fails if operation or history roles are not loaded ([#3755](https://github.com/microsoft/AzureTRE/issues/3755)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -360,6 +446,7 @@ BUG FIXES: * SecuredByRole failing if roles are null ([#3740](https://github.com/microsoft/AzureTRE/issues/3740 )) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -409,6 +496,7 @@ BUG FIXES: * Fix issue with cost tags not displaying correctly for some user roles ([#3721](https://github.com/microsoft/AzureTRE/issues/3721)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -445,6 +533,7 @@ BUG FIXES: * Fix firewall config related to Nexus so that `pypi.org` is added to the allow-list ([#3694](https://github.com/microsoft/AzureTRE/issues/3694)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -494,6 +583,7 @@ BUG FIXES: * Added missing region entries in `databricks-udr.json` ([[#3688](https://github.com/microsoft/AzureTRE/pull/3688)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -533,6 +623,7 @@ BUG FIXES: * Upgrade airlock and unrestricted workspaces to base workspace version 0.12.0 ([#3659](https://github.com/microsoft/AzureTRE/pull/3659)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -592,6 +683,7 @@ BUG FIXES: COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -633,6 +725,7 @@ BUG FIXES: * Nexus fails to install due to `az login` and firewall rules ([#3453](https://github.com/microsoft/AzureTRE/issues/3453)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.5.1 | @@ -835,6 +928,7 @@ BUG FIXES: * Fix KeyVault purge error on MLFlow uninstall ([#3082](https://github.com/microsoft/AzureTRE/pull/3082)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.4.4 | @@ -911,6 +1005,7 @@ BUG FIXES: * Handle 429 TooManyRequests and 503 ServiceUnavailable which might return from Azure Cost Management in TRE Cost API ([#2835](https://github.com/microsoft/AzureTRE/issues/2835)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.4.2 | @@ -958,6 +1053,7 @@ BUG FIXES: * Fix issues with AML workspace service deployment ([#2768](https://github.com/microsoft/AzureTRE/pull/2768)) COMPONENTS: + | name | version | | ----- | ----- | | devops | 0.4.2 | diff --git a/Makefile b/Makefile index 4c0b32bafa..b879490f3b 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,7 @@ terraform-upgrade: && . ${MAKEFILE_DIR}/devops/scripts/check_dependencies.sh env \ && . ${MAKEFILE_DIR}/devops/scripts/load_and_validate_env.sh \ && . ${MAKEFILE_DIR}/devops/scripts/load_env.sh ${DIR}/.env \ - && cd ${DIR}/terraform/ && ./upgrade.sh + && ./devops/scripts/upgrade.sh ${DIR} terraform-import: $(call target_title, "Importing ${DIR} with Terraform") \ @@ -200,7 +200,7 @@ bundle-build: && if [ -d terraform ]; then terraform -chdir=terraform init -backend=false; terraform -chdir=terraform validate; fi \ && FULL_IMAGE_NAME_PREFIX=${FULL_IMAGE_NAME_PREFIX} IMAGE_NAME_PREFIX=${IMAGE_NAME_PREFIX} \ ${MAKEFILE_DIR}/devops/scripts/bundle_runtime_image_build.sh \ - && porter build \ + && ${MAKEFILE_DIR}/devops/scripts/porter_build_bundle.sh \ $(MAKE) bundle-check-params bundle-install: bundle-check-params @@ -218,6 +218,15 @@ bundle-install: bundle-check-params --credential-set aad_auth \ --debug +bundle: + case ${BUNDLE_TYPE} in \ + (workspace) $(MAKE) workspace_bundle BUNDLE=${BUNDLE} ;; \ + (workspace_service) $(MAKE) workspace_service_bundle BUNDLE=${BUNDLE} ;; \ + (shared_service) $(MAKE) shared_service_bundle BUNDLE=${BUNDLE} ;; \ + (user_resource) $(MAKE) user_resource_bundle WORKSPACE_SERVICE=${WORKSPACE_SERVICE} BUNDLE=${BUNDLE} ;; \ + (*) echo "Invalid BUNDLE_TYPE: ${BUNDLE_TYPE}"; exit 1 ;; \ + esac + # Validates that the parameters file is synced with the bundle. # The file is used when installing the bundle from a local machine. # We remove arm_use_msi on both sides since it shouldn't take effect locally anyway. @@ -309,8 +318,10 @@ deploy-shared-service: && ${MAKEFILE_DIR}/devops/scripts/deploy_shared_service.sh $${PROPS} firewall-install: - $(MAKE) bundle-build bundle-publish bundle-register deploy-shared-service \ - DIR=${MAKEFILE_DIR}/templates/shared_services/firewall/ BUNDLE_TYPE=shared_service + . ${MAKEFILE_DIR}/devops/scripts/check_dependencies.sh env \ + && $(MAKE) bundle-build bundle-publish bundle-register deploy-shared-service \ + DIR=${MAKEFILE_DIR}/templates/shared_services/firewall/ BUNDLE_TYPE=shared_service \ + PROPS="$${FIREWALL_SKU+--firewall_sku $${FIREWALL_SKU} }$${FIREWALL_FORCE_TUNNEL_IP+--firewall_force_tunnel_ip $${FIREWALL_FORCE_TUNNEL_IP} }" static-web-upload: $(call target_title, "Uploading to static website") \ diff --git a/README.md b/README.md index 2fbbb0b2b5..a0f4abcac9 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,23 @@ ## Background -Across the health industry, be it a pharmaceutical company interrogating clinical trial results, or a public health provider analyzing electronic health records, there is the need to enable researchers, analysts, and developers to work with sensitive data sets. +Across multiple industries, be it a pharmaceutical company interrogating clinical trial results, a public health provider analyzing electronic health records, or an engineering organization working on next-generation systems, there is a need to enable researchers and solution developers to collaborate on sensitive data. -Trusted Research Environments (TREs) enable organisations to provide research teams secure access to these data sets alongside appropriate tooling to ensure researchers can remain efficient and productive despite the security controls in place. +Trusted Research Environments (TREs) enable organisations to provide research and development (R&D) teams secure access to data alongside tooling to ensure productivity while keeping security controls in place. -Further information on TREs in general can be found in many places, one good resource is [HDR UK's website](https://www.hdruk.ac.uk/access-to-health-data/trusted-research-environments/). +Further information on TREs in general can be found in many places, one good resource specifically for healthcare is [HDR UK's website](https://www.hdruk.ac.uk/access-to-health-data/trusted-research-environments/). + +Azure TRE is finding new applications and to date, has been used as the basis for exploring future secure collaboration environments in engineering. Workloads investigated include systems and software development. In truth, any workload in any industry could theoretically be supported with extensions to Azure TRE. The Azure Trusted Research Environment project is an accelerator to assist Microsoft customers and partners who want to build out Trusted Research environments on Azure. This project enables authorized users to deploy and configure secure workspaces and researcher tooling without a dependency on IT teams. -This project is typically implemented alongside a data platform that provides research ready datasets to TRE workspaces. +This project is typically implemented alongside a data platform that provides research ready datasets to TRE workspaces. Azure TRE has also been proven to handle unstructured data scenarios. For example, collaborative Computer Aided Design (CAD), with Product Lifecycle Management integration. TREs are not “one size fits all”, hence although the Azure TRE has a number of out of the box features, the project has been built be extensible, and hence tooling and data platform agnostic. Core features include: - Self-service workspace management for TRE administrators -- Self-service provisioning of research tooling for research teams +- Self-service provisioning of R&D tooling for R&D teams - Package and repository mirroring - PyPi, R-CRAN, Apt and more. - Extensible architecture - build your own service templates as required - Microsoft Entra ID integration diff --git a/airlock_processor/BlobCreatedTrigger/function.json b/airlock_processor/BlobCreatedTrigger/function.json index 5bde252c39..c34edbeeb7 100644 --- a/airlock_processor/BlobCreatedTrigger/function.json +++ b/airlock_processor/BlobCreatedTrigger/function.json @@ -8,20 +8,20 @@ "direction": "in", "topicName": "%BLOB_CREATED_TOPIC_NAME%", "subscriptionName": "%TOPIC_SUBSCRIPTION_NAME%", - "connection": "SB_CONNECTION_STRING" + "connection": "%SERVICEBUS_CONNECTION_NAME%", + "accessRights": "listen", + "autoComplete": true }, { "type": "eventGrid", "name": "stepResultEvent", - "topicEndpointUri": "EVENT_GRID_STEP_RESULT_TOPIC_URI_SETTING", - "topicKeySetting": "EVENT_GRID_STEP_RESULT_TOPIC_KEY_SETTING", + "connection": "EVENT_GRID_STEP_RESULT_CONNECTION", "direction": "out" }, { "type": "eventGrid", "name": "dataDeletionEvent", - "topicEndpointUri": "EVENT_GRID_DATA_DELETION_TOPIC_URI_SETTING", - "topicKeySetting": "EVENT_GRID_DATA_DELETION_TOPIC_KEY_SETTING", + "connection": "EVENT_GRID_DATA_DELETION_CONNECTION", "direction": "out" } ] diff --git a/airlock_processor/DataDeletionTrigger/function.json b/airlock_processor/DataDeletionTrigger/function.json index 2b2bb580da..0cb7f66eab 100644 --- a/airlock_processor/DataDeletionTrigger/function.json +++ b/airlock_processor/DataDeletionTrigger/function.json @@ -7,7 +7,9 @@ "type": "serviceBusTrigger", "direction": "in", "queueName": "%AIRLOCK_DATA_DELETION_QUEUE_NAME%", - "connection": "SB_CONNECTION_STRING" + "connection": "%SERVICEBUS_CONNECTION_NAME%", + "accessRights": "listen", + "autoComplete": true } ] } diff --git a/airlock_processor/ScanResultTrigger/function.json b/airlock_processor/ScanResultTrigger/function.json index 4dee63e389..266bd059fe 100644 --- a/airlock_processor/ScanResultTrigger/function.json +++ b/airlock_processor/ScanResultTrigger/function.json @@ -7,13 +7,14 @@ "type": "serviceBusTrigger", "direction": "in", "queueName": "%AIRLOCK_SCAN_RESULT_QUEUE_NAME%", - "connection": "SB_CONNECTION_STRING" + "connection": "%SERVICEBUS_CONNECTION_NAME%", + "accessRights": "listen", + "autoComplete": true }, { "type": "eventGrid", "name": "outputEvent", - "topicEndpointUri": "EVENT_GRID_STEP_RESULT_TOPIC_URI_SETTING", - "topicKeySetting": "EVENT_GRID_STEP_RESULT_TOPIC_KEY_SETTING", + "connection": "EVENT_GRID_STEP_RESULT_CONNECTION", "direction": "out" } ] diff --git a/airlock_processor/StatusChangedQueueTrigger/function.json b/airlock_processor/StatusChangedQueueTrigger/function.json index c5e7be3356..b96de6710c 100644 --- a/airlock_processor/StatusChangedQueueTrigger/function.json +++ b/airlock_processor/StatusChangedQueueTrigger/function.json @@ -6,20 +6,20 @@ "type": "serviceBusTrigger", "direction": "in", "queueName": "%AIRLOCK_STATUS_CHANGED_QUEUE_NAME%", - "connection": "SB_CONNECTION_STRING" + "connection": "%SERVICEBUS_CONNECTION_NAME%", + "accessRights": "listen", + "autoComplete": true }, { "type": "eventGrid", "name": "stepResultEvent", - "topicEndpointUri": "EVENT_GRID_STEP_RESULT_TOPIC_URI_SETTING", - "topicKeySetting": "EVENT_GRID_STEP_RESULT_TOPIC_KEY_SETTING", + "connection": "EVENT_GRID_STEP_RESULT_CONNECTION", "direction": "out" }, { "type": "eventGrid", "name": "dataDeletionEvent", - "topicEndpointUri": "EVENT_GRID_DATA_DELETION_TOPIC_URI_SETTING", - "topicKeySetting": "EVENT_GRID_DATA_DELETION_TOPIC_KEY_SETTING", + "connection": "EVENT_GRID_DATA_DELETION_CONNECTION", "direction": "out" } ] diff --git a/airlock_processor/_version.py b/airlock_processor/_version.py index 777f190df0..732155f8df 100644 --- a/airlock_processor/_version.py +++ b/airlock_processor/_version.py @@ -1 +1 @@ -__version__ = "0.8.0" +__version__ = "0.8.3" diff --git a/airlock_processor/host.json b/airlock_processor/host.json index 95b6b4b7d6..f9667b1f23 100644 --- a/airlock_processor/host.json +++ b/airlock_processor/host.json @@ -8,7 +8,7 @@ } } }, - "extensionBundle": { +"extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.0.0, 5.0.0)" } diff --git a/airlock_processor/run_tests_and_exit_succesfully.sh b/airlock_processor/run_tests_and_exit_succesfully.sh index 0b50ba6067..12884a743d 100755 --- a/airlock_processor/run_tests_and_exit_succesfully.sh +++ b/airlock_processor/run_tests_and_exit_succesfully.sh @@ -6,6 +6,6 @@ rm -f ../test-results/pytest_airlock_processor* mkdir -p ../test-results -if ! pytest --junit-xml ../test-results/pytest_airlock_processor_unit.xml --ignore e2e_tests; then +if ! python -m pytest --junit-xml ../test-results/pytest_airlock_processor_unit.xml --ignore e2e_tests; then touch ../test-results/pytest_airlock_processor_unit_failed fi diff --git a/resource_processor/resources/__init__.py b/airlock_processor/tests/shared_code/__init__.py similarity index 100% rename from resource_processor/resources/__init__.py rename to airlock_processor/tests/shared_code/__init__.py diff --git a/api_app/Dockerfile b/api_app/Dockerfile index eaa74dd72a..fc8b93c676 100644 --- a/api_app/Dockerfile +++ b/api_app/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-slim-bullseye AS base +FROM python:3.12-slim-bookworm AS base COPY requirements.txt /. RUN pip3 install --no-cache-dir -r requirements.txt diff --git a/api_app/_version.py b/api_app/_version.py index be6f6da961..76f24586d4 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.20.2" +__version__ = "0.21.1" diff --git a/api_app/api/dependencies/database.py b/api_app/api/dependencies/database.py index 7bfc89ff22..3220314e75 100644 --- a/api_app/api/dependencies/database.py +++ b/api_app/api/dependencies/database.py @@ -1,7 +1,6 @@ from azure.cosmos.aio import CosmosClient, DatabaseProxy, ContainerProxy -from azure.mgmt.cosmosdb.aio import CosmosDBManagementClient -from core.config import MANAGED_IDENTITY_CLIENT_ID, STATE_STORE_ENDPOINT, STATE_STORE_KEY, STATE_STORE_SSL_VERIFY, SUBSCRIPTION_ID, RESOURCE_MANAGER_ENDPOINT, CREDENTIAL_SCOPES, RESOURCE_GROUP_NAME, COSMOSDB_ACCOUNT_NAME, STATE_STORE_DATABASE +from core.config import STATE_STORE_ENDPOINT, STATE_STORE_KEY, STATE_STORE_SSL_VERIFY, STATE_STORE_DATABASE from core.credentials import get_credential_async from services.logging import logger @@ -27,53 +26,32 @@ def __init__(cls): async def _connect_to_db(cls) -> CosmosClient: logger.debug(f"Connecting to {STATE_STORE_ENDPOINT}") - credential = await get_credential_async() - if MANAGED_IDENTITY_CLIENT_ID: - logger.debug("Connecting with managed identity") - cosmos_client = CosmosClient( - url=STATE_STORE_ENDPOINT, - credential=credential - ) - else: + if STATE_STORE_KEY: logger.debug("Connecting with key") - primary_master_key = await cls._get_store_key(credential) - if STATE_STORE_SSL_VERIFY: logger.debug("Connecting with SSL verification") cosmos_client = CosmosClient( url=STATE_STORE_ENDPOINT, - credential=primary_master_key + credential=STATE_STORE_KEY ) else: logger.debug("Connecting without SSL verification") # ignore TLS (setup is a pain) when using local Cosmos emulator. cosmos_client = CosmosClient( url=STATE_STORE_ENDPOINT, - credential=primary_master_key, + credential=STATE_STORE_KEY, connection_verify=False ) - logger.debug("Connection established") - return cosmos_client - - @classmethod - async def _get_store_key(cls, credential) -> str: - logger.debug("Getting store key") - if STATE_STORE_KEY: - primary_master_key = STATE_STORE_KEY else: - async with CosmosDBManagementClient( - credential, - subscription_id=SUBSCRIPTION_ID, - base_url=RESOURCE_MANAGER_ENDPOINT, - credential_scopes=CREDENTIAL_SCOPES - ) as cosmosdb_mng_client: - database_keys = await cosmosdb_mng_client.database_accounts.list_keys( - resource_group_name=RESOURCE_GROUP_NAME, - account_name=COSMOSDB_ACCOUNT_NAME, - ) - primary_master_key = database_keys.primary_master_key + logger.debug("Connecting with managed identity") + credential = await get_credential_async() + cosmos_client = CosmosClient( + url=STATE_STORE_ENDPOINT, + credential=credential + ) - return primary_master_key + logger.debug("Connection established") + return cosmos_client @classmethod async def get_container_proxy(cls, container_name) -> ContainerProxy: diff --git a/api_app/api/routes/api.py b/api_app/api/routes/api.py index 0dd1a7d2c5..6dabc88275 100644 --- a/api_app/api/routes/api.py +++ b/api_app/api/routes/api.py @@ -8,7 +8,7 @@ from api.helpers import get_repository from db.repositories.workspaces import WorkspaceRepository from api.routes import health, ping, workspaces, workspace_templates, workspace_service_templates, user_resource_templates, \ - shared_services, shared_service_templates, migrations, costs, airlock, operations, metadata + shared_services, shared_service_templates, migrations, costs, airlock, operations, metadata, requests from core import config from resources import strings @@ -49,6 +49,7 @@ core_router.include_router(migrations.migrations_core_router, tags=["migrations"]) core_router.include_router(costs.costs_core_router, tags=["costs"]) core_router.include_router(costs.costs_workspace_router, tags=["costs"]) +core_router.include_router(requests.router, tags=["requests"]) core_swagger_router = APIRouter() swagger_disabled_router = APIRouter() @@ -112,7 +113,7 @@ async def get_disabled_swagger(): def get_scope(workspace) -> str: # Cope with the fact that scope id can have api:// at the front. - return f"api://{workspace.properties['scope_id'].replace('api://','')}/user_impersonation" + return f"api://{workspace.properties['scope_id'].replace('api://', '')}/user_impersonation" @workspace_swagger_router.get("/workspaces/{workspace_id}/openapi.json", include_in_schema=False, name="openapi_definitions") diff --git a/api_app/api/routes/requests.py b/api_app/api/routes/requests.py new file mode 100644 index 0000000000..1fffa6f356 --- /dev/null +++ b/api_app/api/routes/requests.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, Depends, HTTPException, status as status_code +from typing import List, Optional + +from api.helpers import get_repository +from resources import strings +from db.repositories.airlock_requests import AirlockRequestRepository +from models.domain.airlock_request import AirlockRequest, AirlockRequestStatus, AirlockRequestType +from services.authentication import get_current_tre_user_or_tre_admin + +router = APIRouter(dependencies=[Depends(get_current_tre_user_or_tre_admin)]) + + +@router.get("/requests", response_model=List[AirlockRequest], name=strings.API_LIST_REQUESTS) +async def get_requests( + user=Depends(get_current_tre_user_or_tre_admin), + airlock_request_repo: AirlockRequestRepository = Depends(get_repository(AirlockRequestRepository)), + airlock_manager: bool = False, + creator_user_id: Optional[str] = None, type: Optional[AirlockRequestType] = None, status: Optional[AirlockRequestStatus] = None, + order_by: Optional[str] = None, order_ascending: bool = True +) -> List[AirlockRequest]: + try: + if not airlock_manager: + requests = await airlock_request_repo.get_airlock_requests( + creator_user_id=creator_user_id or user.id, + type=type, + status=status, + order_by=order_by, + order_ascending=order_ascending, + ) + else: + requests = await airlock_request_repo.get_airlock_requests_for_airlock_manager(user) + + return requests + + except ValueError as ve: + raise HTTPException(status_code=status_code.HTTP_400_BAD_REQUEST, detail=str(ve)) + except Exception as e: + raise HTTPException(status_code=status_code.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) diff --git a/api_app/db/repositories/airlock_requests.py b/api_app/db/repositories/airlock_requests.py index f4a1926348..8bb8275aa6 100644 --- a/api_app/db/repositories/airlock_requests.py +++ b/api_app/db/repositories/airlock_requests.py @@ -7,6 +7,8 @@ from azure.cosmos.exceptions import CosmosResourceNotFoundError, CosmosAccessConditionFailedError from fastapi import HTTPException, status from pydantic import parse_obj_as +from db.repositories.workspaces import WorkspaceRepository +from services.authentication import get_access_service from models.domain.authentication import User from db.errors import EntityDoesNotExist from models.domain.airlock_request import AirlockFile, AirlockRequest, AirlockRequestStatus, \ @@ -107,27 +109,33 @@ def create_airlock_request_item(self, airlock_request_input: AirlockRequestInCre return airlock_request - async def get_airlock_requests(self, workspace_id: str, creator_user_id: Optional[str] = None, type: Optional[AirlockRequestType] = None, status: Optional[AirlockRequestStatus] = None, order_by: Optional[str] = None, order_ascending=True) -> List[AirlockRequest]: - query = self.airlock_requests_query() + f' WHERE c.workspaceId = "{workspace_id}"' + async def get_airlock_requests(self, workspace_id: Optional[str] = None, creator_user_id: Optional[str] = None, type: Optional[AirlockRequestType] = None, status: Optional[AirlockRequestStatus] = None, order_by: Optional[str] = None, order_ascending=True) -> List[AirlockRequest]: + query = self.airlock_requests_query() # optional filters + conditions = [] + parameters = [] + if workspace_id: + conditions.append('c.workspaceId=@workspace_id') + parameters.append({"name": "@workspace_id", "value": workspace_id}) if creator_user_id: - query += ' AND c.createdBy.id=@user_id' + conditions.append('c.createdBy.id=@user_id') + parameters.append({"name": "@user_id", "value": creator_user_id}) if status: - query += ' AND c.status=@status' + conditions.append('c.status=@status') + parameters.append({"name": "@status", "value": status}) if type: - query += ' AND c.type=@type' + conditions.append('c.type=@type') + parameters.append({"name": "@type", "value": type}) + + if conditions: + query += ' WHERE ' + ' AND '.join(conditions) # optional sorting if order_by: query += ' ORDER BY c.' + order_by query += ' ASC' if order_ascending else ' DESC' - parameters = [ - {"name": "@user_id", "value": creator_user_id}, - {"name": "@status", "value": status}, - {"name": "@type", "value": type}, - ] airlock_requests = await self.query(query=query, parameters=parameters) return parse_obj_as(List[AirlockRequest], airlock_requests) @@ -138,6 +146,27 @@ async def get_airlock_request_by_id(self, airlock_request_id: UUID4) -> AirlockR raise EntityDoesNotExist return parse_obj_as(AirlockRequest, airlock_requests) + async def get_airlock_requests_for_airlock_manager(self, user: User, type: Optional[AirlockRequestType] = None, status: Optional[AirlockRequestStatus] = None, order_by: Optional[str] = None, order_ascending=True) -> List[AirlockRequest]: + workspace_repo = await WorkspaceRepository.create() + access_service = get_access_service() + + workspaces = await workspace_repo.get_active_workspaces() + user_role_assignments = access_service.get_identity_role_assignments(user.id) + + valid_roles = {ra.role_id for ra in user_role_assignments} + + workspace_ids = [ + workspace.id + for workspace in workspaces + if workspace.properties["app_role_id_workspace_airlock_manager"] in valid_roles + ] + requests = [] + + for workspace_id in workspace_ids: + requests += await self.get_airlock_requests(workspace_id=workspace_id, type=type, status=status, order_by=order_by, order_ascending=order_ascending) + + return requests + async def update_airlock_request( self, original_request: AirlockRequest, diff --git a/api_app/resources/strings.py b/api_app/resources/strings.py index a9184cf289..896b385f85 100644 --- a/api_app/resources/strings.py +++ b/api_app/resources/strings.py @@ -34,6 +34,8 @@ API_UPDATE_USER_RESOURCE = "Update an existing user resource" API_INVOKE_ACTION_ON_USER_RESOURCE = "Invoke action on a user resource" +API_LIST_REQUESTS = "Get requests" + API_CREATE_AIRLOCK_REQUEST = "Create an airlock request" API_GET_AIRLOCK_REQUEST = "Get an airlock request" API_LIST_AIRLOCK_REQUESTS = "Get all airlock requests for a workspace" diff --git a/api_app/run_tests_and_exit_succesfully.sh b/api_app/run_tests_and_exit_succesfully.sh index 34873d4c16..311a59f2d1 100755 --- a/api_app/run_tests_and_exit_succesfully.sh +++ b/api_app/run_tests_and_exit_succesfully.sh @@ -6,6 +6,6 @@ rm -f ../test-results/pytest_api* mkdir -p ../test-results -if ! pytest --junit-xml ../test-results/pytest_api_unit.xml --ignore e2e_tests -W ignore::pytest.PytestUnraisableExceptionWarning -W ignore::DeprecationWarning; then +if ! python -m pytest --junit-xml ../test-results/pytest_api_unit.xml --ignore e2e_tests -W ignore::pytest.PytestUnraisableExceptionWarning -W ignore::DeprecationWarning; then touch ../test-results/pytest_api_unit_failed fi diff --git a/api_app/tests_ma/conftest.py b/api_app/tests_ma/conftest.py index 0bd06e076d..6245ec23ec 100644 --- a/api_app/tests_ma/conftest.py +++ b/api_app/tests_ma/conftest.py @@ -578,7 +578,6 @@ def simple_pipeline_step() -> PipelineStep: @pytest_asyncio.fixture(autouse=True) async def no_database(): with patch('api.dependencies.database.get_credential_async', return_value=AsyncMock()), \ - patch('api.dependencies.database.CosmosDBManagementClient', return_value=AsyncMock()), \ - patch('api.dependencies.database.CosmosClient', return_value=AsyncMock(spec=CosmosClient)) as cosmos_client_mock: + patch('api.dependencies.database.CosmosClient', return_value=AsyncMock(spec=CosmosClient)) as cosmos_client_mock: cosmos_client_mock.return_value.get_database_client.return_value = AsyncMock(spec=DatabaseProxy) yield Database() diff --git a/api_app/tests_ma/test_api/test_routes/test_requests.py b/api_app/tests_ma/test_api/test_routes/test_requests.py new file mode 100644 index 0000000000..4a10544ca1 --- /dev/null +++ b/api_app/tests_ma/test_api/test_routes/test_requests.py @@ -0,0 +1,42 @@ +import pytest +from fastapi import status +from mock import patch + +from resources import strings +from services.authentication import get_current_tre_user_or_tre_admin + + +pytestmark = pytest.mark.asyncio + + +class TestRequestsThatDontRequireAdminRigths: + @pytest.fixture(autouse=True, scope='class') + def log_in_with_non_admin_user(self, app, non_admin_user): + with patch('services.aad_authentication.AzureADAuthorization._get_user_from_token', return_value=non_admin_user()): + app.dependency_overrides[get_current_tre_user_or_tre_admin] = non_admin_user + yield + app.dependency_overrides = {} + + # [GET] /requests/ - get_requests + @patch("api.routes.airlock.AirlockRequestRepository.get_airlock_requests", return_value=[]) + async def test_get_all_requests_returns_200(self, _, app, client): + response = await client.get(app.url_path_for(strings.API_LIST_REQUESTS)) + assert response.status_code == status.HTTP_200_OK + + @patch("api.routes.airlock.AirlockRequestRepository.get_airlock_requests_for_airlock_manager") + async def test_get_airlock_manager_requests_returns_200(self, mock_get_airlock_requests_for_airlock_manager, app, client): + mock_get_airlock_requests_for_airlock_manager.return_value = [] + response = await client.get(app.url_path_for(strings.API_LIST_REQUESTS), params={"airlock_manager": True}) + + assert response.status_code == status.HTTP_200_OK + mock_get_airlock_requests_for_airlock_manager.assert_called_once() + + @patch("api.routes.airlock.AirlockRequestRepository.get_airlock_requests", side_effect=Exception("Internal Server Error")) + async def test_get_all_requests_returns_500(self, _, app, client): + response = await client.get(app.url_path_for(strings.API_LIST_REQUESTS)) + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + + @patch("api.routes.airlock.AirlockRequestRepository.get_airlock_requests_for_airlock_manager", side_effect=Exception("Internal Server Error")) + async def test_get_airlock_manager_requests_returns_500(self, _, app, client): + response = await client.get(app.url_path_for(strings.API_LIST_REQUESTS), params={"airlock_manager": True}) + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR diff --git a/api_app/tests_ma/test_db/test_repositories/test_airlock_request_repository.py b/api_app/tests_ma/test_db/test_repositories/test_airlock_request_repository.py index 4c773db327..92ae8b7ae7 100644 --- a/api_app/tests_ma/test_db/test_repositories/test_airlock_request_repository.py +++ b/api_app/tests_ma/test_db/test_repositories/test_airlock_request_repository.py @@ -3,6 +3,8 @@ from mock import patch import pytest import pytest_asyncio +from models.domain.authentication import RoleAssignment, User +from models.domain.workspace import Workspace from tests_ma.test_api.conftest import create_test_user from models.schemas.airlock_request import AirlockRequestInCreate from models.domain.airlock_request import AirlockRequest, AirlockRequestStatus, AirlockRequestType @@ -63,6 +65,18 @@ def verify_dictionary_contains_all_enum_values(): raise Exception(f"Status '{status}' was not added to the ALLOWED_STATUS_CHANGES dictionary") +def sample_workspace(workspace_id=WORKSPACE_ID, workspace_properties: dict = {}) -> Workspace: + workspace = Workspace( + id=workspace_id, + templateName="tre-workspace-base", + templateVersion="0.1.0", + etag="", + properties=workspace_properties, + resourcePath=f'/workspaces/{workspace_id}' + ) + return workspace + + def airlock_request_mock(status=AirlockRequestStatus.Draft): airlock_request = AirlockRequest( id=AIRLOCK_REQUEST_ID, @@ -143,12 +157,186 @@ async def test_update_airlock_request_should_retry_update_when_etag_is_not_up_to async def test_get_airlock_requests_queries_db(airlock_request_repo): airlock_request_repo.container.query_items = MagicMock() - expected_query = airlock_request_repo.airlock_requests_query() + f' WHERE c.workspaceId = "{WORKSPACE_ID}"' + expected_query = airlock_request_repo.airlock_requests_query() + ' WHERE c.workspaceId=@workspace_id' expected_parameters = [ - {"name": "@user_id", "value": None}, - {"name": "@status", "value": None}, - {"name": "@type", "value": None}, + {"name": "@workspace_id", "value": WORKSPACE_ID}, ] await airlock_request_repo.get_airlock_requests(WORKSPACE_ID) airlock_request_repo.container.query_items.assert_called_once_with(query=expected_query, parameters=expected_parameters) + + +async def test_get_airlock_requests_with_user_id(airlock_request_repo): + airlock_request_repo.container.query_items = MagicMock() + user_id = "test_user_id" + expected_query = airlock_request_repo.airlock_requests_query() + ' WHERE c.createdBy.id=@user_id' + expected_parameters = [ + {"name": "@user_id", "value": user_id}, + ] + + await airlock_request_repo.get_airlock_requests(creator_user_id=user_id) + airlock_request_repo.container.query_items.assert_called_once_with(query=expected_query, parameters=expected_parameters) + + +async def test_get_airlock_requests_with_status(airlock_request_repo): + airlock_request_repo.container.query_items = MagicMock() + status = AirlockRequestStatus.Submitted + expected_query = airlock_request_repo.airlock_requests_query() + ' WHERE c.status=@status' + expected_parameters = [ + {"name": "@status", "value": status} + ] + + await airlock_request_repo.get_airlock_requests(status=status) + airlock_request_repo.container.query_items.assert_called_once_with(query=expected_query, parameters=expected_parameters) + + +async def test_get_airlock_requests_with_type(airlock_request_repo): + airlock_request_repo.container.query_items = MagicMock() + request_type = AirlockRequestType.Import + expected_query = airlock_request_repo.airlock_requests_query() + ' WHERE c.type=@type' + expected_parameters = [ + {"name": "@type", "value": request_type}, + ] + + await airlock_request_repo.get_airlock_requests(type=request_type) + airlock_request_repo.container.query_items.assert_called_once_with(query=expected_query, parameters=expected_parameters) + + +async def test_get_airlock_requests_with_multiple_filters(airlock_request_repo): + airlock_request_repo.container.query_items = MagicMock() + user_id = "test_user_id" + status = AirlockRequestStatus.Submitted + request_type = AirlockRequestType.Import + expected_query = airlock_request_repo.airlock_requests_query() + ' WHERE c.createdBy.id=@user_id AND c.status=@status AND c.type=@type' + expected_parameters = [ + {"name": "@user_id", "value": user_id}, + {"name": "@status", "value": status}, + {"name": "@type", "value": request_type}, + ] + + await airlock_request_repo.get_airlock_requests(creator_user_id=user_id, status=status, type=request_type) + airlock_request_repo.container.query_items.assert_called_once_with(query=expected_query, parameters=expected_parameters) + + +@pytest.mark.asyncio +@patch.object(AirlockRequestRepository, 'get_airlock_requests', new_callable=AsyncMock) +@patch('db.repositories.airlock_requests.get_access_service', autospec=True) +@patch('db.repositories.airlock_requests.WorkspaceRepository', autospec=True) +async def test_get_airlock_requests_for_airlock_manager_no_roles( + mock_workspace_repo, + mock_access_service, + mock_get_requests, + airlock_request_repo +): + # Mock no user roles + mock_access_service.return_value.get_identity_role_assignments.return_value = [] + + # Mock active workspaces + mock_workspace_instance = MagicMock() + mock_workspace_instance.get_active_workspaces = AsyncMock(return_value=[]) + mock_workspace_repo.create = AsyncMock(return_value=mock_workspace_instance) + + # Call function + user = User(id="user1", name="TestUser") + result = await airlock_request_repo.get_airlock_requests_for_airlock_manager(user) + + # validate + assert result == [] + mock_get_requests.assert_not_called() + + +@pytest.mark.asyncio +@patch.object(AirlockRequestRepository, 'get_airlock_requests', new_callable=AsyncMock) +@patch('db.repositories.airlock_requests.get_access_service', autospec=True) +@patch('db.repositories.airlock_requests.WorkspaceRepository', autospec=True) +async def test_get_airlock_requests_for_airlock_manager_single_workspace( + mock_workspace_repo, + mock_access_service, + mock_get_requests, + airlock_request_repo +): + # Setup workspace and manager role + workspace = sample_workspace(workspace_properties={"app_role_id_workspace_airlock_manager": "manager-role-1"}) + mock_workspace_instance = MagicMock() + mock_workspace_instance.get_active_workspaces = AsyncMock(return_value=[workspace]) + mock_workspace_repo.create = AsyncMock(return_value=mock_workspace_instance) + + # Setup user roles + role_assignment = RoleAssignment(resource_id="resource_id", role_id="manager-role-1") + mock_access_service.return_value.get_identity_role_assignments.return_value = [role_assignment] + + # Setup corresponding requests from that workspace + request_mock = AirlockRequest(id="request-1", workspaceId=WORKSPACE_ID, type=AirlockRequestType.Import, reviews=[]) + mock_get_requests.return_value = [request_mock] + + user = User(id="user1", name="TestUser") + result = await airlock_request_repo.get_airlock_requests_for_airlock_manager(user) + + assert len(result) == 1 + assert result[0].id == "request-1" + mock_get_requests.assert_called_once_with(workspace_id=WORKSPACE_ID, type=None, status=None, order_by=None, order_ascending=True) + + +@pytest.mark.asyncio +@patch.object(AirlockRequestRepository, 'get_airlock_requests', new_callable=AsyncMock) +@patch('db.repositories.airlock_requests.get_access_service', autospec=True) +@patch('db.repositories.airlock_requests.WorkspaceRepository', autospec=True) +async def test_get_airlock_requests_for_airlock_manager_multiple_workspaces( + mock_workspace_repo, + mock_access_service, + mock_get_requests, + airlock_request_repo +): + # Setup multiple workspaces + workspace1 = sample_workspace(workspace_properties={"app_role_id_workspace_airlock_manager": "manager-role-1"}) + workspace2 = sample_workspace(workspace_properties={"app_role_id_workspace_airlock_manager": "manager-role-2"}) + mock_workspace_instance = MagicMock() + mock_workspace_instance.get_active_workspaces = AsyncMock(return_value=[workspace1, workspace2]) + mock_workspace_repo.create = AsyncMock(return_value=mock_workspace_instance) + + # Setup user roles + role_assignment_1 = RoleAssignment(resource_id="resource_id", role_id="manager-role-1") + role_assignment_2 = RoleAssignment(resource_id="resource_id", role_id="manager-role-2") + mock_access_service.return_value.get_identity_role_assignments.return_value = [role_assignment_1, role_assignment_2] + + # Setup requests for each workspace + first_ws_requests = [AirlockRequest(id="request-1", workspaceId="workspace-1", type=AirlockRequestType.Import, reviews=[])] + second_ws_requests = [AirlockRequest(id="request-2", workspaceId="workspace-2", type=AirlockRequestType.Import, reviews=[])] + mock_get_requests.side_effect = [first_ws_requests, second_ws_requests] + + user = User(id="user1", name="TestUser") + result = await airlock_request_repo.get_airlock_requests_for_airlock_manager(user) + + # combined requests from both + assert len(result) == 2 + assert result[0].id == "request-1" + assert result[1].id == "request-2" + assert mock_get_requests.call_count == 2 + + +@pytest.mark.asyncio +@patch.object(AirlockRequestRepository, 'get_airlock_requests', new_callable=AsyncMock) +@patch('db.repositories.airlock_requests.get_access_service', autospec=True) +@patch('db.repositories.airlock_requests.WorkspaceRepository', autospec=True) +async def test_get_airlock_requests_for_airlock_manager_active_workspaces_but_no_manager_role( + mock_workspace_repo, + mock_access_service, + mock_get_requests, + airlock_request_repo +): + # Setup multiple workspaces, but user doesn't have manager roles + workspace1 = sample_workspace(workspace_properties={"app_role_id_workspace_airlock_manager": "manager-role-1"}) + workspace2 = sample_workspace(workspace_properties={"app_role_id_workspace_airlock_manager": "manager-role-2"}) + mock_workspace_instance = MagicMock() + mock_workspace_instance.get_active_workspaces = AsyncMock(return_value=[workspace1, workspace2]) + mock_workspace_repo.create = AsyncMock(return_value=mock_workspace_instance) + + # No matching roles for these workspaces + mock_access_service.return_value.get_identity_role_assignments.return_value = [ + RoleAssignment(resource_id="resource_id", role_id="some-other-role") + ] + + user = User(id="user1", name="TestUser") + result = await airlock_request_repo.get_airlock_requests_for_airlock_manager(user) + assert result == [] + mock_get_requests.assert_not_called() diff --git a/config.sample.yaml b/config.sample.yaml index 44777e89a3..009f017920 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -10,7 +10,7 @@ management: acr_name: __CHANGE_ME__ # ID of external Key Vault to store CMKs in (only required if enable_cmk_encryption is true) # external_key_store_id: __CHANGE_ME__ - # Name of Key Vault for encryption keys, required only if enable_cmk_encryption is true and not using external_key_store_id + # Name of Key Vault for encryption, required if enable_cmk_encryption is true and external_key_store_id is not set # encryption_kv_name: __CHANGE_ME__ # Azure Resource Manager credentials used for CI/CD pipelines arm_subscription_id: __CHANGE_ME__ @@ -46,6 +46,7 @@ tre: # The TRE Web UI is deployed by default. # Uncomment the following to disable deployment of the Web UI. # deploy_ui: false + # firewall_force_tunnel_ip: __CHANGE_ME__ firewall_sku: Standard app_gateway_sku: Standard_v2 diff --git a/core/terraform/.terraform.lock.hcl b/core/terraform/.terraform.lock.hcl index 482b86714e..41d8da1a19 100644 --- a/core/terraform/.terraform.lock.hcl +++ b/core/terraform/.terraform.lock.hcl @@ -6,7 +6,6 @@ provider "registry.terraform.io/azure/azapi" { constraints = ">= 1.15.0, ~> 1.15.0" hashes = [ "h1:Y7ruMuPh8UJRTRl4rm+cdpGtmURx2taqiuqfYaH3o48=", - "h1:gIOgxVmFSxHrR+XOzgUEA+ybOmp8kxZlZH3eYeB/eFI=", "zh:0627a8bc77254debc25dc0c7b62e055138217c97b03221e593c3c56dc7550671", "zh:2fe045f07070ef75d0bec4b0595a74c14394daa838ddb964e2fd23cc98c40c34", "zh:343009f39c957883b2c06145a5954e524c70f93585f943f1ea3d28ef6995d0d0", @@ -23,22 +22,22 @@ provider "registry.terraform.io/azure/azapi" { } provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = ">= 3.112.0, 3.112.0" + version = "4.14.0" + constraints = ">= 3.117.0, 4.14.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:FYZ9qh8i3X2gDmUTe1jJ/VzdSyjGjVmhBzv2R8D6CBo=", + "zh:05aaea16fc5f27b14d9fbad81654edf0638949ed3585576b2219c76a2bee095a", + "zh:065ce6ed16ba3fa7efcf77888ea582aead54e6a28f184c6701b73d71edd64bb0", + "zh:3c0cd17c249d18aa2e0120acb5f0c14810725158b379a67fec1331110e7c50df", + "zh:5a3ba3ffb2f1ce519fe3bf84a7296aa5862c437c70c62f0b0a5293bea9f2d01c", + "zh:7a8e9d72fa2714f4d567270b1761d4b4e788de7c15dada7db0cf0e29933185a2", + "zh:a11e190073f31c1238c15af29b9162e0f4564f6b0cd0310a3fa94102738450dc", + "zh:a5c004114410cc6dcb8fed584c9f3b84283b58025b0073a7e88d2bdb27840dfa", + "zh:a674a41db118e244eda7591e455d2ec338626664e0856e4125e909eb038f78db", + "zh:b5139010e4cbb2cb1a27c775610593c1c8063d3a7c82b00a65006509c434df2f", + "zh:cbb031223ccd8b099ac4d19b92641142f330b90f2fc6452843e445bae28f832c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f7e7db1b94082a4ac3d4af3dabe7bbd335e1679305bf8e29d011f0ee440724ca", ] } @@ -82,25 +81,6 @@ provider "registry.terraform.io/hashicorp/local" { ] } -provider "registry.terraform.io/hashicorp/null" { - version = "3.2.3" - hashes = [ - "h1:+AnORRgFbRO6qqcfaQyeX80W0eX3VmjadjnUFUJTiXo=", - "zh:22d062e5278d872fe7aed834f5577ba0a5afe34a3bdac2b81f828d8d3e6706d2", - "zh:23dead00493ad863729495dc212fd6c29b8293e707b055ce5ba21ee453ce552d", - "zh:28299accf21763ca1ca144d8f660688d7c2ad0b105b7202554ca60b02a3856d3", - "zh:55c9e8a9ac25a7652df8c51a8a9a422bd67d784061b1de2dc9fe6c3cb4e77f2f", - "zh:756586535d11698a216291c06b9ed8a5cc6a4ec43eee1ee09ecd5c6a9e297ac1", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:9d5eea62fdb587eeb96a8c4d782459f4e6b73baeece4d04b4a40e44faaee9301", - "zh:a6355f596a3fb8fc85c2fb054ab14e722991533f87f928e7169a486462c74670", - "zh:b5a65a789cff4ada58a5baffc76cb9767dc26ec6b45c00d2ec8b1b027f6db4ed", - "zh:db5ab669cf11d0e9f81dc380a6fdfcac437aea3d69109c7aef1a5426639d2d65", - "zh:de655d251c470197bcbb5ac45d289595295acb8f829f6c781d4a75c8c8b7c7dd", - "zh:f5c68199f2e6076bce92a12230434782bf768103a427e9bb9abee99b116af7b5", - ] -} - provider "registry.terraform.io/hashicorp/random" { version = "3.6.3" constraints = ">= 3.0.0, ~> 3.6" diff --git a/core/terraform/airlock/airlock_processor.tf b/core/terraform/airlock/airlock_processor.tf index 5416d858ad..d293927ea0 100644 --- a/core/terraform/airlock/airlock_processor.tf +++ b/core/terraform/airlock/airlock_processor.tf @@ -1,11 +1,3 @@ -data "local_file" "airlock_processor_version" { - filename = "${path.root}/../../airlock_processor/_version.py" -} - -locals { - version = replace(replace(replace(data.local_file.airlock_processor_version.content, "__version__ = \"", ""), "\"", ""), "\n", "") -} - resource "azurerm_service_plan" "airlock_plan" { name = "plan-airlock-${var.tre_id}" resource_group_name = var.resource_group_name @@ -28,6 +20,8 @@ resource "azurerm_storage_account" "sa_airlock_processor_func_app" { queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + local_user_enabled = false + shared_access_key_enabled = false tags = var.tre_core_tags dynamic "identity" { @@ -41,28 +35,30 @@ resource "azurerm_storage_account" "sa_airlock_processor_func_app" { # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true - lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } -} + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } -resource "azurerm_storage_account_customer_managed_key" "sa_airlock_processor_func_app_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.sa_airlock_processor_func_app.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id + lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } resource "azurerm_linux_function_app" "airlock_function_app" { - name = local.airlock_function_app_name - resource_group_name = var.resource_group_name - location = var.location - https_only = true - virtual_network_subnet_id = var.airlock_processor_subnet_id - service_plan_id = azurerm_service_plan.airlock_plan.id - storage_account_name = azurerm_storage_account.sa_airlock_processor_func_app.name - # consider moving to a managed identity here - storage_account_access_key = azurerm_storage_account.sa_airlock_processor_func_app.primary_access_key - tags = var.tre_core_tags + name = local.airlock_function_app_name + resource_group_name = var.resource_group_name + location = var.location + https_only = true + virtual_network_subnet_id = var.airlock_processor_subnet_id + service_plan_id = azurerm_service_plan.airlock_plan.id + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + storage_account_name = azurerm_storage_account.sa_airlock_processor_func_app.name + storage_uses_managed_identity = true + + tags = var.tre_core_tags identity { type = "UserAssigned" @@ -70,7 +66,12 @@ resource "azurerm_linux_function_app" "airlock_function_app" { } app_settings = { - "SB_CONNECTION_STRING" = var.airlock_servicebus.default_primary_connection_string + "SERVICEBUS_CONNECTION_NAME" = local.servicebus_connection + "${local.servicebus_connection}__tenantId" = azurerm_user_assigned_identity.airlock_id.tenant_id + "${local.servicebus_connection}__clientId" = azurerm_user_assigned_identity.airlock_id.client_id + "${local.servicebus_connection}__credential" = "managedidentity" + "${local.servicebus_connection}__fullyQualifiedNamespace" = var.airlock_servicebus_fqdn + "BLOB_CREATED_TOPIC_NAME" = azurerm_servicebus_topic.blob_created.name "TOPIC_SUBSCRIPTION_NAME" = azurerm_servicebus_subscription.airlock_processor.name "EVENT_GRID_STEP_RESULT_TOPIC_URI_SETTING" = azurerm_eventgrid_topic.step_result.endpoint @@ -87,6 +88,20 @@ resource "azurerm_linux_function_app" "airlock_function_app" { "TRE_ID" = var.tre_id "WEBSITE_CONTENTOVERVNET" = 1 "STORAGE_ENDPOINT_SUFFIX" = module.terraform_azurerm_environment_configuration.storage_suffix + + "TOPIC_SUBSCRIPTION_NAME" = azurerm_servicebus_subscription.airlock_processor.name + "AzureWebJobsStorage__clientId" = azurerm_user_assigned_identity.airlock_id.client_id + "AzureWebJobsStorage__credential" = "managedidentity" + + "EVENT_GRID_STEP_RESULT_CONNECTION" = local.step_result_eventgrid_connection + "${local.step_result_eventgrid_connection}__topicEndpointUri" = azurerm_eventgrid_topic.step_result.endpoint + "${local.step_result_eventgrid_connection}__credential" = "managedidentity" + "${local.step_result_eventgrid_connection}__clientId" = azurerm_user_assigned_identity.airlock_id.client_id + + "EVENT_GRID_DATA_DELETION_CONNECTION" = local.data_deletion_eventgrid_connection + "${local.data_deletion_eventgrid_connection}__topicEndpointUri" = azurerm_eventgrid_topic.data_deletion.endpoint + "${local.data_deletion_eventgrid_connection}__credential" = "managedidentity" + "${local.data_deletion_eventgrid_connection}__clientId" = azurerm_user_assigned_identity.airlock_id.client_id } site_config { @@ -96,6 +111,7 @@ resource "azurerm_linux_function_app" "airlock_function_app" { container_registry_use_managed_identity = true vnet_route_all_enabled = true ftps_state = "Disabled" + minimum_tls_version = "1.3" application_stack { docker { diff --git a/core/terraform/airlock/data.tf b/core/terraform/airlock/data.tf new file mode 100644 index 0000000000..73915e6b6a --- /dev/null +++ b/core/terraform/airlock/data.tf @@ -0,0 +1,21 @@ +data "local_file" "airlock_processor_version" { + filename = "${path.root}/../../airlock_processor/_version.py" +} + +data "azurerm_private_dns_zone" "eventgrid" { + name = module.terraform_azurerm_environment_configuration.private_links["privatelink.eventgrid.azure.net"] + resource_group_name = var.resource_group_name +} + +data "azurerm_container_registry" "mgmt_acr" { + name = var.mgmt_acr_name + resource_group_name = var.mgmt_resource_group_name +} + +data "azurerm_monitor_diagnostic_categories" "eventgrid_custom_topics" { + resource_id = azurerm_eventgrid_topic.airlock_notification.id +} + +data "azurerm_monitor_diagnostic_categories" "eventgrid_system_topics" { + resource_id = azurerm_eventgrid_system_topic.export_approved_blob_created.id +} diff --git a/core/terraform/airlock/eventgrid_topics.tf b/core/terraform/airlock/eventgrid_topics.tf index 991325d2e1..d9faaef013 100644 --- a/core/terraform/airlock/eventgrid_topics.tf +++ b/core/terraform/airlock/eventgrid_topics.tf @@ -1,8 +1,3 @@ -data "azurerm_private_dns_zone" "eventgrid" { - name = module.terraform_azurerm_environment_configuration.private_links["privatelink.eventgrid.azure.net"] - resource_group_name = var.resource_group_name -} - # Below we assign a SYSTEM-assigned identity for the topics. note that a user-assigned identity will not work. # Event grid topics @@ -11,6 +6,7 @@ resource "azurerm_eventgrid_topic" "step_result" { location = var.location resource_group_name = var.resource_group_name public_network_access_enabled = var.enable_local_debugging + local_auth_enabled = false identity { type = "SystemAssigned" @@ -65,6 +61,7 @@ resource "azurerm_eventgrid_topic" "status_changed" { location = var.location resource_group_name = var.resource_group_name public_network_access_enabled = var.enable_local_debugging + local_auth_enabled = false identity { type = "SystemAssigned" @@ -118,6 +115,7 @@ resource "azurerm_eventgrid_topic" "data_deletion" { location = var.location resource_group_name = var.resource_group_name public_network_access_enabled = var.enable_local_debugging + local_auth_enabled = false identity { type = "SystemAssigned" @@ -168,6 +166,7 @@ resource "azurerm_eventgrid_topic" "scan_result" { resource_group_name = var.resource_group_name # This is mandatory for the scan result to be published since private networks are not supported yet public_network_access_enabled = true + local_auth_enabled = false identity { type = "SystemAssigned" @@ -328,6 +327,7 @@ resource "azurerm_eventgrid_topic" "airlock_notification" { location = var.location resource_group_name = var.resource_group_name public_network_access_enabled = var.enable_local_debugging + local_auth_enabled = false identity { type = "SystemAssigned" @@ -511,3 +511,50 @@ resource "azurerm_eventgrid_event_subscription" "export_approved_blob_created" { ] } +resource "azurerm_monitor_diagnostic_setting" "eventgrid_custom_topics" { + for_each = merge({ + (azurerm_eventgrid_topic.airlock_notification.name) = azurerm_eventgrid_topic.airlock_notification.id, + (azurerm_eventgrid_topic.step_result.name) = azurerm_eventgrid_topic.step_result.id, + (azurerm_eventgrid_topic.status_changed.name) = azurerm_eventgrid_topic.status_changed.id, + (azurerm_eventgrid_topic.data_deletion.name) = azurerm_eventgrid_topic.data_deletion.id, + }, + var.enable_malware_scanning ? { (azurerm_eventgrid_topic.scan_result[0].name) = azurerm_eventgrid_topic.scan_result[0].id } : null + ) + + name = "${each.key}-diagnostics" + target_resource_id = each.value + log_analytics_workspace_id = var.log_analytics_workspace_id + dynamic "enabled_log" { + for_each = data.azurerm_monitor_diagnostic_categories.eventgrid_custom_topics.log_category_types + content { + category = enabled_log.value + } + } + + metric { + category = "AllMetrics" + } +} + +resource "azurerm_monitor_diagnostic_setting" "eventgrid_system_topics" { + for_each = { + (azurerm_eventgrid_system_topic.import_inprogress_blob_created.name) = azurerm_eventgrid_system_topic.import_inprogress_blob_created.id, + (azurerm_eventgrid_system_topic.import_rejected_blob_created.name) = azurerm_eventgrid_system_topic.import_rejected_blob_created.id, + (azurerm_eventgrid_system_topic.import_blocked_blob_created.name) = azurerm_eventgrid_system_topic.import_blocked_blob_created.id, + (azurerm_eventgrid_system_topic.export_approved_blob_created.name) = azurerm_eventgrid_system_topic.export_approved_blob_created.id, + } + + name = "${each.key}-diagnostics" + target_resource_id = each.value + log_analytics_workspace_id = var.log_analytics_workspace_id + dynamic "enabled_log" { + for_each = data.azurerm_monitor_diagnostic_categories.eventgrid_system_topics.log_category_types + content { + category = enabled_log.value + } + } + + metric { + category = "AllMetrics" + } +} diff --git a/core/terraform/airlock/identity.tf b/core/terraform/airlock/identity.tf index abca21c60d..a21a26f562 100644 --- a/core/terraform/airlock/identity.tf +++ b/core/terraform/airlock/identity.tf @@ -1,8 +1,3 @@ -data "azurerm_container_registry" "mgmt_acr" { - name = var.mgmt_acr_name - resource_group_name = var.mgmt_resource_group_name -} - resource "azurerm_user_assigned_identity" "airlock_id" { resource_group_name = var.resource_group_name location = var.location @@ -30,7 +25,7 @@ resource "azurerm_role_assignment" "servicebus_receiver" { principal_id = azurerm_user_assigned_identity.airlock_id.principal_id } -resource "azurerm_role_assignment" "eventgrid_data_sender" { +resource "azurerm_role_assignment" "eventgrid_data_sender_status_changed" { scope = azurerm_eventgrid_topic.status_changed.id role_definition_name = "EventGrid Data Sender" principal_id = var.api_principal_id @@ -42,6 +37,18 @@ resource "azurerm_role_assignment" "eventgrid_data_sender_notification" { principal_id = var.api_principal_id } +resource "azurerm_role_assignment" "eventgrid_data_sender_step_result" { + scope = azurerm_eventgrid_topic.step_result.id + role_definition_name = "EventGrid Data Sender" + principal_id = azurerm_user_assigned_identity.airlock_id.principal_id +} + +resource "azurerm_role_assignment" "eventgrid_data_sender_data_deletion" { + scope = azurerm_eventgrid_topic.data_deletion.id + role_definition_name = "EventGrid Data Sender" + principal_id = azurerm_user_assigned_identity.airlock_id.principal_id +} + resource "azurerm_role_assignment" "airlock_blob_data_contributor" { count = length(local.airlock_sa_blob_data_contributor) scope = local.airlock_sa_blob_data_contributor[count.index] @@ -57,3 +64,11 @@ resource "azurerm_role_assignment" "api_sa_data_contributor" { role_definition_name = "Storage Blob Data Contributor" principal_id = var.api_principal_id } + +# Permissions needed for the Function Host to work correctly. +resource "azurerm_role_assignment" "function_host_storage" { + for_each = toset(["Storage Account Contributor", "Storage Blob Data Owner", "Storage Queue Data Contributor"]) + scope = azurerm_storage_account.sa_airlock_processor_func_app.id + role_definition_name = each.value + principal_id = azurerm_user_assigned_identity.airlock_id.principal_id +} diff --git a/core/terraform/airlock/locals.tf b/core/terraform/airlock/locals.tf index 37f7fdead0..838ddf091a 100644 --- a/core/terraform/airlock/locals.tf +++ b/core/terraform/airlock/locals.tf @@ -1,4 +1,6 @@ locals { + version = replace(replace(replace(data.local_file.airlock_processor_version.content, "__version__ = \"", ""), "\"", ""), "\n", "") + # STorage AirLock EXternal import_external_storage_name = lower(replace("stalimex${var.tre_id}", "-", "")) # STorage AirLock IMport InProgress @@ -58,4 +60,8 @@ locals { azurerm_storage_account.sa_import_in_progress.id, azurerm_storage_account.sa_export_approved.id ] + + servicebus_connection = "SERVICEBUS_CONNECTION" + step_result_eventgrid_connection = "EVENT_GRID_STEP_RESULT_CONNECTION" + data_deletion_eventgrid_connection = "EVENT_GRID_DATA_DELETION_CONNECTION" } diff --git a/core/terraform/airlock/main.tf b/core/terraform/airlock/main.tf index f0f6b069ad..ee659770f9 100644 --- a/core/terraform/airlock/main.tf +++ b/core/terraform/airlock/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112" + version = ">= 4.14.0" } azapi = { source = "Azure/azapi" diff --git a/core/terraform/airlock/storage_accounts.tf b/core/terraform/airlock/storage_accounts.tf index 74bcd3fb30..660850b95c 100644 --- a/core/terraform/airlock/storage_accounts.tf +++ b/core/terraform/airlock/storage_accounts.tf @@ -10,7 +10,8 @@ resource "azurerm_storage_account" "sa_import_external" { table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" cross_tenant_replication_enabled = false - + shared_access_key_enabled = false + local_user_enabled = false # Don't allow anonymous access (unrelated to the 'public' networking rules) allow_nested_items_to_be_public = false @@ -29,6 +30,14 @@ resource "azurerm_storage_account" "sa_import_external" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;external" }) @@ -58,14 +67,6 @@ resource "azurerm_private_endpoint" "stg_import_external_pe" { } } -resource "azurerm_storage_account_customer_managed_key" "sa_import_external_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.sa_import_external.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} - # 'Approved' export resource "azurerm_storage_account" "sa_export_approved" { name = local.export_approved_storage_name @@ -76,6 +77,8 @@ resource "azurerm_storage_account" "sa_export_approved" { table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Don't allow anonymous access (unrelated to the 'public' networking rules) allow_nested_items_to_be_public = false @@ -95,6 +98,14 @@ resource "azurerm_storage_account" "sa_export_approved" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge(var.tre_core_tags, { description = "airlock;export;approved" }) @@ -124,14 +135,6 @@ resource "azurerm_private_endpoint" "stg_export_approved_pe" { } } -resource "azurerm_storage_account_customer_managed_key" "sa_export_approved_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.sa_export_approved.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} - # 'In-Progress' storage account resource "azurerm_storage_account" "sa_import_in_progress" { name = local.import_in_progress_storage_name @@ -143,6 +146,8 @@ resource "azurerm_storage_account" "sa_import_in_progress" { queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -159,6 +164,14 @@ resource "azurerm_storage_account" "sa_import_in_progress" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;in-progress" }) @@ -171,15 +184,6 @@ resource "azurerm_storage_account" "sa_import_in_progress" { lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } -resource "azurerm_storage_account_customer_managed_key" "sa_import_in_progress_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.sa_import_in_progress.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} - - # Enable Airlock Malware Scanning on Core TRE resource "azapi_resource_action" "enable_defender_for_storage" { count = var.enable_malware_scanning ? 1 : 0 @@ -239,6 +243,8 @@ resource "azurerm_storage_account" "sa_import_rejected" { queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -255,6 +261,14 @@ resource "azurerm_storage_account" "sa_import_rejected" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;rejected" }) @@ -290,14 +304,6 @@ resource "azurerm_private_endpoint" "stg_import_rejected_pe" { lifecycle { ignore_changes = [tags] } } -resource "azurerm_storage_account_customer_managed_key" "sa_import_rejected_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.sa_import_rejected.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} - # 'Blocked' storage account resource "azurerm_storage_account" "sa_import_blocked" { name = local.import_blocked_storage_name @@ -309,6 +315,8 @@ resource "azurerm_storage_account" "sa_import_blocked" { queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -325,6 +333,14 @@ resource "azurerm_storage_account" "sa_import_blocked" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge(var.tre_core_tags, { description = "airlock;import;blocked" }) @@ -360,10 +376,3 @@ resource "azurerm_private_endpoint" "stg_import_blocked_pe" { lifecycle { ignore_changes = [tags] } } -resource "azurerm_storage_account_customer_managed_key" "sa_import_blocked_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.sa_import_blocked.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} diff --git a/core/terraform/airlock/variables.tf b/core/terraform/airlock/variables.tf index f88f1fc50f..bb0fad04df 100644 --- a/core/terraform/airlock/variables.tf +++ b/core/terraform/airlock/variables.tf @@ -62,6 +62,9 @@ variable "airlock_servicebus" { default_primary_connection_string = string }) } +variable "airlock_servicebus_fqdn" { + type = string +} variable "tre_core_tags" { type = map(string) } @@ -102,13 +105,7 @@ variable "enable_cmk_encryption" { description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" } -variable "key_store_id" { - type = string - description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" - default = null -} - -variable "kv_encryption_key_name" { +variable "encryption_key_versionless_id" { type = string - description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" + description = "Versionless ID of the encryption key in the key vault" } diff --git a/core/terraform/api-identity.tf b/core/terraform/api-identity.tf index 8aabadab1a..8209e37143 100644 --- a/core/terraform/api-identity.tf +++ b/core/terraform/api-identity.tf @@ -57,6 +57,4 @@ resource "azurerm_cosmosdb_sql_role_assignment" "tre_db_contributor" { role_definition_id = data.azurerm_cosmosdb_sql_role_definition.cosmosdb_db_contributor.id principal_id = azurerm_user_assigned_identity.id.principal_id scope = azurerm_cosmosdb_account.tre_db_account.id - - depends_on = [null_resource.tre_db_account_enable_cmk] } diff --git a/core/terraform/api-webapp.tf b/core/terraform/api-webapp.tf index eabbf728bb..b35cc0ba7c 100644 --- a/core/terraform/api-webapp.tf +++ b/core/terraform/api-webapp.tf @@ -18,15 +18,17 @@ resource "azurerm_service_plan" "core" { } resource "azurerm_linux_web_app" "api" { - name = "api-${var.tre_id}" - resource_group_name = azurerm_resource_group.core.name - location = azurerm_resource_group.core.location - service_plan_id = azurerm_service_plan.core.id - https_only = true - key_vault_reference_identity_id = azurerm_user_assigned_identity.id.id - virtual_network_subnet_id = module.network.web_app_subnet_id - public_network_access_enabled = false - tags = local.tre_core_tags + name = "api-${var.tre_id}" + resource_group_name = azurerm_resource_group.core.name + location = azurerm_resource_group.core.location + service_plan_id = azurerm_service_plan.core.id + https_only = true + key_vault_reference_identity_id = azurerm_user_assigned_identity.id.id + virtual_network_subnet_id = module.network.web_app_subnet_id + public_network_access_enabled = false + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + tags = local.tre_core_tags app_settings = { "APPLICATIONINSIGHTS_CONNECTION_STRING" = module.azure_monitor.app_insights_connection_string @@ -81,7 +83,7 @@ resource "azurerm_linux_web_app" "api" { vnet_route_all_enabled = true container_registry_use_managed_identity = true container_registry_managed_identity_client_id = azurerm_user_assigned_identity.id.client_id - minimum_tls_version = "1.2" + minimum_tls_version = "1.3" ftps_state = "Disabled" application_stack { diff --git a/core/terraform/appgateway/main.tf b/core/terraform/appgateway/main.tf index c77ae64ef3..a4eb095f9c 100644 --- a/core/terraform/appgateway/main.tf +++ b/core/terraform/appgateway/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112" + version = ">= 3.117" } } } diff --git a/core/terraform/appgateway/staticweb.tf b/core/terraform/appgateway/staticweb.tf index c0ff13ea11..ee6a0b44bc 100644 --- a/core/terraform/appgateway/staticweb.tf +++ b/core/terraform/appgateway/staticweb.tf @@ -8,9 +8,11 @@ resource "azurerm_storage_account" "staticweb" { account_replication_type = "LRS" table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" - enable_https_traffic_only = true + https_traffic_only_enabled = true allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false tags = local.tre_core_tags # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below @@ -35,14 +37,14 @@ resource "azurerm_storage_account" "staticweb" { identity_ids = [var.encryption_identity_id] } } -} -resource "azurerm_storage_account_customer_managed_key" "staticweb_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.staticweb.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } } # Assign the "Storage Blob Data Contributor" role needed for uploading certificates to the storage account diff --git a/core/terraform/appgateway/variables.tf b/core/terraform/appgateway/variables.tf index 21d50f61b6..8124cdabcd 100644 --- a/core/terraform/appgateway/variables.tf +++ b/core/terraform/appgateway/variables.tf @@ -29,21 +29,12 @@ variable "log_analytics_workspace_id" { variable "app_gateway_sku" { type = string } - variable "encryption_identity_id" { - type = string - description = "User Managed Identity with permissions to get encryption keys from key vault" + type = string } variable "enable_cmk_encryption" { - type = bool - description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" + type = bool } -variable "key_store_id" { - type = string - description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" - default = null -} -variable "kv_encryption_key_name" { - type = string - description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" +variable "encryption_key_versionless_id" { + type = string } diff --git a/core/terraform/azure-monitor/azure-monitor.tf b/core/terraform/azure-monitor/azure-monitor.tf index de19ac16b2..81f5c3c18d 100644 --- a/core/terraform/azure-monitor/azure-monitor.tf +++ b/core/terraform/azure-monitor/azure-monitor.tf @@ -24,8 +24,12 @@ resource "azurerm_storage_account" "az_monitor" { queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + local_user_enabled = false tags = var.tre_core_tags + # unclear the implications on az-monitor, so leaving it for now. + # shared_access_key_enabled = false + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -42,15 +46,15 @@ resource "azurerm_storage_account" "az_monitor" { } } - lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } -} + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } -resource "azurerm_storage_account_customer_managed_key" "az_monitor_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.az_monitor.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id + lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } resource "azurerm_log_analytics_linked_storage_account" "workspace_storage_ingestion" { diff --git a/core/terraform/azure-monitor/main.tf b/core/terraform/azure-monitor/main.tf index c77ae64ef3..a4eb095f9c 100644 --- a/core/terraform/azure-monitor/main.tf +++ b/core/terraform/azure-monitor/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112" + version = ">= 3.117" } } } diff --git a/core/terraform/azure-monitor/variables.tf b/core/terraform/azure-monitor/variables.tf index 30b99dc0a7..4b370d74ba 100644 --- a/core/terraform/azure-monitor/variables.tf +++ b/core/terraform/azure-monitor/variables.tf @@ -33,19 +33,11 @@ variable "enable_local_debugging" { } variable "encryption_identity_id" { - type = string - description = "User Managed Identity with permissions to get encryption keys from key vault" + type = string } variable "enable_cmk_encryption" { - type = bool - description = "A boolean indicating if customer managed keys will be used for encryption of supporting resources" -} -variable "key_store_id" { - type = string - description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" - default = null + type = bool } -variable "kv_encryption_key_name" { - type = string - description = "Name of Key Vault Encryption Key (only used if enable_cmk_encryption is true)" +variable "encryption_key_versionless_id" { + type = string } diff --git a/core/terraform/cmk_encryption.tf b/core/terraform/cmk_encryption.tf index 4c0b5a1b1c..056a471478 100644 --- a/core/terraform/cmk_encryption.tf +++ b/core/terraform/cmk_encryption.tf @@ -4,7 +4,7 @@ resource "azurerm_user_assigned_identity" "encryption" { location = azurerm_resource_group.core.location tags = local.tre_core_tags - name = "id-encryption-${var.tre_id}" + name = local.encryption_identity_name lifecycle { ignore_changes = [tags] } } diff --git a/core/terraform/cosmos_mongo.tf b/core/terraform/cosmos_mongo.tf index 38231a9c2f..b43c060ac7 100644 --- a/core/terraform/cosmos_mongo.tf +++ b/core/terraform/cosmos_mongo.tf @@ -1,12 +1,13 @@ resource "azurerm_cosmosdb_account" "mongo" { - name = "cosmos-mongo-${var.tre_id}" - location = azurerm_resource_group.core.location - resource_group_name = azurerm_resource_group.core.name - offer_type = "Standard" - kind = "MongoDB" - automatic_failover_enabled = false - mongo_server_version = 4.2 - ip_range_filter = "${local.azure_portal_cosmos_ips}${var.enable_local_debugging ? ",${local.myip}" : ""}" + name = "cosmos-mongo-${var.tre_id}" + location = azurerm_resource_group.core.location + resource_group_name = azurerm_resource_group.core.name + offer_type = "Standard" + kind = "MongoDB" + automatic_failover_enabled = false + mongo_server_version = 4.2 + ip_range_filter = local.cosmos_ip_filter_set + public_network_access_enabled = var.enable_local_debugging capabilities { name = "EnableServerless" @@ -43,30 +44,14 @@ resource "azurerm_cosmosdb_account" "mongo" { } } + key_vault_key_id = var.enable_cmk_encryption ? azurerm_key_vault_key.tre_encryption[0].versionless_id : null default_identity_type = var.enable_cmk_encryption ? "UserAssignedIdentity=${azurerm_user_assigned_identity.encryption[0].id}" : null tags = local.tre_core_tags - # since key_vault_key_id is created by the 'mongo_enable_cmk' null_resource, terraform forces re-creation of the resource - lifecycle { ignore_changes = [tags, key_vault_key_id] } -} - -# Using the az CLI command since terraform forces a re-creation of the resource -# https://github.com/hashicorp/terraform-provider-azurerm/issues/24781 -resource "null_resource" "mongo_enable_cmk" { - count = var.enable_cmk_encryption ? 1 : 0 - - provisioner "local-exec" { - command = "az cosmosdb update --name ${azurerm_cosmosdb_account.mongo.name} --resource-group ${azurerm_cosmosdb_account.mongo.resource_group_name} --key-uri ${azurerm_key_vault_key.tre_encryption[0].versionless_id}" - } - - depends_on = [ - azurerm_cosmosdb_account.mongo, - azurerm_role_assignment.kv_encryption_key_user[0] - ] + lifecycle { ignore_changes = [tags] } } - resource "azurerm_cosmosdb_mongo_database" "mongo" { name = "porter" resource_group_name = azurerm_resource_group.core.name diff --git a/core/terraform/deploy.sh b/core/terraform/deploy.sh index e71fb14ae1..ef547a8aa7 100755 --- a/core/terraform/deploy.sh +++ b/core/terraform/deploy.sh @@ -5,13 +5,16 @@ set -o pipefail set -o nounset # set -o xtrace +# shellcheck disable=SC1091 +source "../../devops/scripts/kv_add_network_exception.sh" + # This is where we can migrate any Terraform before we plan and apply # For instance deprecated Terraform resources # shellcheck disable=SC1091 source ./migrate.sh -PLAN_FILE="tfplan$$" TS=$(date +"%s") +PLAN_FILE="${TS}-tre-core.tfplan" LOG_FILE="${TS}-tre-core.log" # This variables are loaded in for us diff --git a/core/terraform/destroy.sh b/core/terraform/destroy.sh index 92b6b75c4c..7c8506beef 100755 --- a/core/terraform/destroy.sh +++ b/core/terraform/destroy.sh @@ -5,6 +5,9 @@ set -o pipefail set -o nounset # set -o xtrace +# shellcheck disable=SC1091 +source "../../devops/scripts/kv_add_network_exception.sh" + # These variables are loaded in for us # shellcheck disable=SC2154 ../../devops/scripts/terraform_wrapper.sh -g "${TF_VAR_mgmt_resource_group_name}" \ diff --git a/core/terraform/keyvault.tf b/core/terraform/keyvault.tf index 5d75ae9176..c491a09517 100644 --- a/core/terraform/keyvault.tf +++ b/core/terraform/keyvault.tf @@ -1,5 +1,5 @@ resource "azurerm_key_vault" "kv" { - name = "kv-${var.tre_id}" + name = local.kv_name tenant_id = data.azurerm_client_config.current.tenant_id location = azurerm_resource_group.core.location resource_group_name = azurerm_resource_group.core.name @@ -8,7 +8,27 @@ resource "azurerm_key_vault" "kv" { purge_protection_enabled = var.kv_purge_protection_enabled tags = local.tre_core_tags - lifecycle { ignore_changes = [access_policy, tags] } + public_network_access_enabled = local.kv_public_network_access_enabled + + network_acls { + default_action = local.kv_network_default_action + bypass = local.kv_network_bypass + ip_rules = [local.myip] # exception for deployment IP, this is removed in kv_remove_network_exception.sh + } + + lifecycle { + ignore_changes = [access_policy, tags] + } + + # create provisioner required due to https://github.com/hashicorp/terraform-provider-azurerm/issues/18970 + # + provisioner "local-exec" { + when = create + command = < azurerm_service_plan -# core_app_service_plan_id=$(echo "${terraform_show_json}" \ -# | jq -r 'select(.values.root_module.resources != null) | .values.root_module.resources[] | select(.address=="azurerm_app_service_plan.core") | .values.id') -# if [ -n "${core_app_service_plan_id}" ]; then -# echo "Migrating ${core_app_service_plan_id}" -# terraform state rm azurerm_app_service_plan.core -# if [[ $(az resource list --query "[?id=='${core_app_service_plan_id}'] | length(@)") == 0 ]]; -# then -# echo "The resource doesn't exist on Azure. Skipping importing it back to state." -# else -# terraform import azurerm_service_plan.core "${core_app_service_plan_id}" -# fi -# fi - -echo "*** Migration is done. ***" + # Remove and re-import the VNet + vnet_address="module.network.azurerm_virtual_network.core" + vnet_id=$(get_resource_id "${terraform_show_json}" "$vnet_address" "vnet") + if [ -n "${vnet_id}" ] && [ "${vnet_id}" != "null" ]; then + terraform state rm "${vnet_address}" + terraform import "${vnet_address}" "${vnet_id}" + else + echo "VNet resource not found in state: ${vnet_address}" + fi + echo "*** Migration Done ***" +else + echo "No old resources found in the state, skipping migration." + echo "*** Migration Skipped ***" +fi diff --git a/core/terraform/network/.terraform.lock.hcl b/core/terraform/network/.terraform.lock.hcl new file mode 100644 index 0000000000..ec690305f6 --- /dev/null +++ b/core/terraform/network/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "4.14.0" + constraints = "4.14.0" + hashes = [ + "h1:FYZ9qh8i3X2gDmUTe1jJ/VzdSyjGjVmhBzv2R8D6CBo=", + "zh:05aaea16fc5f27b14d9fbad81654edf0638949ed3585576b2219c76a2bee095a", + "zh:065ce6ed16ba3fa7efcf77888ea582aead54e6a28f184c6701b73d71edd64bb0", + "zh:3c0cd17c249d18aa2e0120acb5f0c14810725158b379a67fec1331110e7c50df", + "zh:5a3ba3ffb2f1ce519fe3bf84a7296aa5862c437c70c62f0b0a5293bea9f2d01c", + "zh:7a8e9d72fa2714f4d567270b1761d4b4e788de7c15dada7db0cf0e29933185a2", + "zh:a11e190073f31c1238c15af29b9162e0f4564f6b0cd0310a3fa94102738450dc", + "zh:a5c004114410cc6dcb8fed584c9f3b84283b58025b0073a7e88d2bdb27840dfa", + "zh:a674a41db118e244eda7591e455d2ec338626664e0856e4125e909eb038f78db", + "zh:b5139010e4cbb2cb1a27c775610593c1c8063d3a7c82b00a65006509c434df2f", + "zh:cbb031223ccd8b099ac4d19b92641142f330b90f2fc6452843e445bae28f832c", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f7e7db1b94082a4ac3d4af3dabe7bbd335e1679305bf8e29d011f0ee440724ca", + ] +} diff --git a/core/terraform/network/locals.tf b/core/terraform/network/locals.tf index aaa2aea7d1..82ae26fb2d 100644 --- a/core/terraform/network/locals.tf +++ b/core/terraform/network/locals.tf @@ -32,4 +32,6 @@ locals { "privatelink.queue.core.windows.net", "privatelink.table.core.windows.net" ]) + + subnet_ids_map = { for subnet in azurerm_virtual_network.core.subnet : subnet.name => subnet.id } } diff --git a/core/terraform/network/main.tf b/core/terraform/network/main.tf index c77ae64ef3..5cced47bb0 100644 --- a/core/terraform/network/main.tf +++ b/core/terraform/network/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112" + version = ">= 4.14.0" } } } diff --git a/core/terraform/network/network.tf b/core/terraform/network/network.tf index db71fe554f..a511365326 100644 --- a/core/terraform/network/network.tf +++ b/core/terraform/network/network.tf @@ -5,146 +5,112 @@ resource "azurerm_virtual_network" "core" { address_space = [var.core_address_space] tags = local.tre_core_tags lifecycle { ignore_changes = [tags] } -} -resource "azurerm_subnet" "bastion" { - name = "AzureBastionSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.bastion_subnet_address_prefix] -} + subnet { + name = "AzureBastionSubnet" + address_prefixes = [local.bastion_subnet_address_prefix] + security_group = azurerm_network_security_group.bastion.id + } -resource "azurerm_subnet" "azure_firewall" { - name = "AzureFirewallSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.firewall_subnet_address_space] - depends_on = [azurerm_subnet.bastion] -} + subnet { + name = "AzureFirewallSubnet" + address_prefixes = [local.firewall_subnet_address_space] + } -resource "azurerm_subnet" "app_gw" { - name = "AppGwSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.app_gw_subnet_address_prefix] - private_endpoint_network_policies = "Disabled" - private_link_service_network_policies_enabled = true - depends_on = [azurerm_subnet.azure_firewall] -} + subnet { + name = "AppGwSubnet" + address_prefixes = [local.app_gw_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + private_link_service_network_policies_enabled = true + security_group = azurerm_network_security_group.app_gw.id + } -resource "azurerm_subnet" "web_app" { - name = "WebAppSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.web_app_subnet_address_prefix] - private_endpoint_network_policies = "Disabled" - private_link_service_network_policies_enabled = true - depends_on = [azurerm_subnet.app_gw] - - delegation { - name = "delegation" - - service_delegation { - name = "Microsoft.Web/serverFarms" - actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + subnet { + name = "WebAppSubnet" + address_prefixes = [local.web_app_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + private_link_service_network_policies_enabled = true + security_group = azurerm_network_security_group.default_rules.id + + delegation { + name = "delegation" + + service_delegation { + name = "Microsoft.Web/serverFarms" + actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + } } } -} -resource "azurerm_subnet" "shared" { - name = "SharedSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.shared_services_subnet_address_prefix] - # notice that private endpoints do not adhere to NSG rules - private_endpoint_network_policies = "Disabled" - depends_on = [azurerm_subnet.web_app] -} + subnet { + name = "SharedSubnet" + address_prefixes = [local.shared_services_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + security_group = azurerm_network_security_group.default_rules.id + } -resource "azurerm_subnet" "resource_processor" { - name = "ResourceProcessorSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.resource_processor_subnet_address_prefix] - # notice that private endpoints do not adhere to NSG rules - private_endpoint_network_policies = "Disabled" - depends_on = [azurerm_subnet.shared] -} + subnet { + name = "ResourceProcessorSubnet" + address_prefixes = [local.resource_processor_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + security_group = azurerm_network_security_group.default_rules.id + } + + subnet { + name = "AirlockProcessorSubnet" + address_prefixes = [local.airlock_processor_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + security_group = azurerm_network_security_group.default_rules.id -resource "azurerm_subnet" "airlock_processor" { - name = "AirlockProcessorSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.airlock_processor_subnet_address_prefix] - # notice that private endpoints do not adhere to NSG rules - private_endpoint_network_policies = "Disabled" - depends_on = [azurerm_subnet.resource_processor] - - delegation { - name = "delegation" - - service_delegation { - name = "Microsoft.Web/serverFarms" - actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + delegation { + name = "delegation" + + service_delegation { + name = "Microsoft.Web/serverFarms" + actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + } } + + service_endpoints = ["Microsoft.Storage"] } - # Todo: needed as we want to open the fw for this subnet in some of the airlock storages (export inprogress) - # https://github.com/microsoft/AzureTRE/issues/2098 - service_endpoints = ["Microsoft.Storage"] -} + subnet { + name = "AirlockNotifiactionSubnet" + address_prefixes = [local.airlock_notifications_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + security_group = azurerm_network_security_group.default_rules.id + + delegation { + name = "delegation" -resource "azurerm_subnet" "airlock_notification" { - name = "AirlockNotifiactionSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.airlock_notifications_subnet_address_prefix] - # notice that private endpoints do not adhere to NSG rules - private_endpoint_network_policies = "Disabled" - depends_on = [azurerm_subnet.airlock_processor] - - delegation { - name = "delegation" - - service_delegation { - name = "Microsoft.Web/serverFarms" - actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + service_delegation { + name = "Microsoft.Web/serverFarms" + actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + } } + service_endpoints = ["Microsoft.ServiceBus"] } - service_endpoints = ["Microsoft.ServiceBus"] -} -resource "azurerm_subnet" "airlock_storage" { - name = "AirlockStorageSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.airlock_storage_subnet_address_prefix] - # notice that private endpoints do not adhere to NSG rules - private_endpoint_network_policies = "Disabled" - depends_on = [azurerm_subnet.airlock_notification] -} + subnet { + name = "AirlockStorageSubnet" + address_prefixes = [local.airlock_storage_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + security_group = azurerm_network_security_group.default_rules.id + } -resource "azurerm_subnet" "airlock_events" { - name = "AirlockEventsSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.airlock_events_subnet_address_prefix] - # notice that private endpoints do not adhere to NSG rules - private_endpoint_network_policies = "Disabled" - depends_on = [azurerm_subnet.airlock_storage] - - # Eventgrid CAN'T send messages over private endpoints, hence we need to allow service endpoints to the service bus - # We are using service endpoints + managed identity to send these messaages - # https://docs.microsoft.com/en-us/azure/event-grid/consume-private-endpoints - service_endpoints = ["Microsoft.ServiceBus"] -} + subnet { + name = "AirlockEventsSubnet" + address_prefixes = [local.airlock_events_subnet_address_prefix] + private_endpoint_network_policies = "Disabled" + security_group = azurerm_network_security_group.default_rules.id -resource "azurerm_subnet" "firewall_management" { - name = "AzureFirewallManagementSubnet" - virtual_network_name = azurerm_virtual_network.core.name - resource_group_name = var.resource_group_name - address_prefixes = [local.firewall_management_subnet_address_prefix] - depends_on = [azurerm_subnet.airlock_events] + service_endpoints = ["Microsoft.ServiceBus"] + } + + subnet { + name = "AzureFirewallManagementSubnet" + address_prefixes = [local.firewall_management_subnet_address_prefix] + } } resource "azurerm_ip_group" "resource_processor" { diff --git a/core/terraform/network/network_security_groups.tf b/core/terraform/network/network_security_groups.tf index 50accf846b..34371dc145 100644 --- a/core/terraform/network/network_security_groups.tf +++ b/core/terraform/network/network_security_groups.tf @@ -105,13 +105,6 @@ resource "azurerm_network_security_group" "bastion" { lifecycle { ignore_changes = [tags] } } -resource "azurerm_subnet_network_security_group_association" "bastion" { - subnet_id = azurerm_subnet.bastion.id - network_security_group_id = azurerm_network_security_group.bastion.id - # depend on the last subnet we created in the vnet - depends_on = [azurerm_subnet.firewall_management] -} - # Network security group for Application Gateway # See https://docs.microsoft.com/azure/application-gateway/configuration-infrastructure#network-security-groups resource "azurerm_network_security_group" "app_gw" { @@ -147,12 +140,6 @@ resource "azurerm_network_security_group" "app_gw" { lifecycle { ignore_changes = [tags] } } -resource "azurerm_subnet_network_security_group_association" "app_gw" { - subnet_id = azurerm_subnet.app_gw.id - network_security_group_id = azurerm_network_security_group.app_gw.id - depends_on = [azurerm_subnet_network_security_group_association.bastion] -} - # Network security group with only default security rules # See https://docs.microsoft.com/azure/virtual-network/network-security-groups-overview#default-security-rules resource "azurerm_network_security_group" "default_rules" { @@ -163,45 +150,3 @@ resource "azurerm_network_security_group" "default_rules" { lifecycle { ignore_changes = [tags] } } - -resource "azurerm_subnet_network_security_group_association" "shared" { - subnet_id = azurerm_subnet.shared.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.app_gw] -} - -resource "azurerm_subnet_network_security_group_association" "web_app" { - subnet_id = azurerm_subnet.web_app.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.shared] -} - -resource "azurerm_subnet_network_security_group_association" "resource_processor" { - subnet_id = azurerm_subnet.resource_processor.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.web_app] -} - -resource "azurerm_subnet_network_security_group_association" "airlock_processor" { - subnet_id = azurerm_subnet.airlock_processor.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.resource_processor] -} - -resource "azurerm_subnet_network_security_group_association" "airlock_storage" { - subnet_id = azurerm_subnet.airlock_storage.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.airlock_processor] -} - -resource "azurerm_subnet_network_security_group_association" "airlock_events" { - subnet_id = azurerm_subnet.airlock_events.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.airlock_storage] -} - -resource "azurerm_subnet_network_security_group_association" "airlock_notification" { - subnet_id = azurerm_subnet.airlock_notification.id - network_security_group_id = azurerm_network_security_group.default_rules.id - depends_on = [azurerm_subnet_network_security_group_association.airlock_events] -} diff --git a/core/terraform/network/outputs.tf b/core/terraform/network/outputs.tf index 3e0aab407d..e2a7fba134 100644 --- a/core/terraform/network/outputs.tf +++ b/core/terraform/network/outputs.tf @@ -3,43 +3,43 @@ output "core_vnet_id" { } output "bastion_subnet_id" { - value = azurerm_subnet.bastion.id + value = local.subnet_ids_map["AzureBastionSubnet"] } output "azure_firewall_subnet_id" { - value = azurerm_subnet.azure_firewall.id + value = local.subnet_ids_map["AzureFirewallSubnet"] } output "app_gw_subnet_id" { - value = azurerm_subnet.app_gw.id + value = local.subnet_ids_map["AppGwSubnet"] } output "web_app_subnet_id" { - value = azurerm_subnet.web_app.id + value = local.subnet_ids_map["WebAppSubnet"] } output "shared_subnet_id" { - value = azurerm_subnet.shared.id + value = local.subnet_ids_map["SharedSubnet"] } output "airlock_processor_subnet_id" { - value = azurerm_subnet.airlock_processor.id + value = local.subnet_ids_map["AirlockProcessorSubnet"] } output "airlock_storage_subnet_id" { - value = azurerm_subnet.airlock_storage.id + value = local.subnet_ids_map["AirlockStorageSubnet"] } output "airlock_events_subnet_id" { - value = azurerm_subnet.airlock_events.id + value = local.subnet_ids_map["AirlockEventsSubnet"] } output "resource_processor_subnet_id" { - value = azurerm_subnet.resource_processor.id + value = local.subnet_ids_map["ResourceProcessorSubnet"] } output "airlock_notification_subnet_id" { - value = azurerm_subnet.airlock_notification.id + value = local.subnet_ids_map["AirlockNotifiactionSubnet"] } # DNS Zones diff --git a/core/terraform/outputs.sh b/core/terraform/outputs.sh old mode 100755 new mode 100644 index e00090bbc1..078b20be53 --- a/core/terraform/outputs.sh +++ b/core/terraform/outputs.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -if [ ! -f ../tre_output.json ]; then +if [ ! -f ../tre_output.json ] || [ ! -s ../tre_output.json ]; then # Connect to the remote backend of Terraform export TF_LOG="" # shellcheck disable=SC2154 diff --git a/core/terraform/resource_processor/vmss_porter/cloud-config.yaml b/core/terraform/resource_processor/vmss_porter/cloud-config.yaml index ace477b132..a405dcc3ff 100644 --- a/core/terraform/resource_processor/vmss_porter/cloud-config.yaml +++ b/core/terraform/resource_processor/vmss_porter/cloud-config.yaml @@ -83,11 +83,8 @@ runcmd: # (https://microsoft.github.io/AzureTRE/troubleshooting-faq/troubleshooting-rp/#Logs) - printf '\nalias dlf="docker logs --since 1m --follow"' >> /etc/bash.bashrc - printf '\nalias dlf1='\''dlf $(docker ps -q | head -n 1)'\''' >> /etc/bash.bashrc - - > - printf '\nalias rpstatus='\''tmux new-session -d "watch docker ps"; \ - tmux split-window -p 100 -v "docker logs --since 1m --follow resource_processor1"; \ - tmux split-window -v -p 90; \ - tmux -2 attach-session -d'\''\n' >> /etc/bash.bashrc + # the following line can't be split + - printf '\nalias rpstatus='\''tmux new-session -d "watch docker ps"; tmux split-window -p 100 -v "docker logs --since 1m --follow resource_processor1"; tmux split-window -v -p 90; tmux -2 attach-session -d'\''\n' >> /etc/bash.bashrc - export DEBIAN_FRONTEND=noninteractive - az cloud set --name ${azure_environment} - az login --identity -u ${vmss_msi_id} diff --git a/core/terraform/resource_processor/vmss_porter/main.tf b/core/terraform/resource_processor/vmss_porter/main.tf index 64a9133501..f390be1863 100644 --- a/core/terraform/resource_processor/vmss_porter/main.tf +++ b/core/terraform/resource_processor/vmss_porter/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112" + version = ">= 3.117" } random = { source = "hashicorp/random" @@ -79,9 +79,11 @@ resource "azurerm_linux_virtual_machine_scale_set" "vm_linux" { disable_password_authentication = false admin_password = random_password.password.result custom_data = data.template_cloudinit_config.config.rendered - encryption_at_host_enabled = false + encryption_at_host_enabled = true upgrade_mode = "Automatic" tags = local.tre_core_tags + secure_boot_enabled = true + vtpm_enabled = true extension { auto_upgrade_minor_version = true @@ -127,7 +129,7 @@ resource "azurerm_linux_virtual_machine_scale_set" "vm_linux" { source_image_reference { publisher = "Canonical" offer = "0001-com-ubuntu-server-jammy" - sku = "22_04-lts" + sku = "22_04-lts-gen2" version = "latest" } diff --git a/core/terraform/scripts/letsencrypt.sh b/core/terraform/scripts/letsencrypt.sh index cb1e68f4a1..c2b6d0c5d0 100755 --- a/core/terraform/scripts/letsencrypt.sh +++ b/core/terraform/scripts/letsencrypt.sh @@ -8,6 +8,11 @@ if [[ -z ${STORAGE_ACCOUNT} ]]; then exit 1 fi +if [[ -n ${KEYVAULT} ]]; then + # shellcheck disable=SC1091 + source "$script_dir/../../../devops/scripts/kv_add_network_exception.sh" +fi + # The storage account is protected by network rules # # The rules need to be temporarily lifted so that the script can determine if the index.html file diff --git a/core/terraform/servicebus.tf b/core/terraform/servicebus.tf index faef9322d7..7c03d661c0 100644 --- a/core/terraform/servicebus.tf +++ b/core/terraform/servicebus.tf @@ -5,6 +5,7 @@ resource "azurerm_servicebus_namespace" "sb" { sku = "Premium" premium_messaging_partitions = "1" capacity = "1" + local_auth_enabled = false tags = local.tre_core_tags # Block public access @@ -32,8 +33,9 @@ resource "azurerm_servicebus_namespace" "sb" { dynamic "customer_managed_key" { for_each = var.enable_cmk_encryption ? [1] : [] content { - key_vault_key_id = azurerm_key_vault_key.tre_encryption[0].id - identity_id = azurerm_user_assigned_identity.encryption[0].id + key_vault_key_id = azurerm_key_vault_key.tre_encryption[0].id + identity_id = azurerm_user_assigned_identity.encryption[0].id + infrastructure_encryption_enabled = true } } diff --git a/core/terraform/statestore.tf b/core/terraform/statestore.tf index 0bc9c9c51f..69b1ba74c3 100644 --- a/core/terraform/statestore.tf +++ b/core/terraform/statestore.tf @@ -5,7 +5,8 @@ resource "azurerm_cosmosdb_account" "tre_db_account" { offer_type = "Standard" kind = "GlobalDocumentDB" automatic_failover_enabled = false - ip_range_filter = "${local.azure_portal_cosmos_ips}${var.enable_local_debugging ? ",${local.myip}" : ""}" + public_network_access_enabled = var.enable_local_debugging + ip_range_filter = local.cosmos_ip_filter_set local_authentication_disabled = true tags = local.tre_core_tags @@ -25,6 +26,7 @@ resource "azurerm_cosmosdb_account" "tre_db_account" { } } + key_vault_key_id = var.enable_cmk_encryption ? azurerm_key_vault_key.tre_encryption[0].versionless_id : null default_identity_type = var.enable_cmk_encryption ? "UserAssignedIdentity=${azurerm_user_assigned_identity.encryption[0].id}" : null consistency_policy { @@ -38,8 +40,7 @@ resource "azurerm_cosmosdb_account" "tre_db_account" { failover_priority = 0 } - # since key_vault_key_id is created by the 'tre_db_account_enable_cmk' null_resource, terraform forces re-creation of the resource - lifecycle { ignore_changes = [tags, key_vault_key_id] } + lifecycle { ignore_changes = [tags] } } moved { @@ -107,18 +108,3 @@ resource "azurerm_private_endpoint" "sspe" { subresource_names = ["Sql"] } } - -# Using the az CLI command since terraform forces a re-creation of the resource -# https://github.com/hashicorp/terraform-provider-azurerm/issues/24781 -resource "null_resource" "tre_db_account_enable_cmk" { - count = var.enable_cmk_encryption ? 1 : 0 - - provisioner "local-exec" { - command = "az cosmosdb update --name ${azurerm_cosmosdb_account.tre_db_account.name} --resource-group ${azurerm_cosmosdb_account.tre_db_account.resource_group_name} --key-uri ${azurerm_key_vault_key.tre_encryption[0].versionless_id}" - } - - depends_on = [ - azurerm_cosmosdb_account.tre_db_account, - azurerm_role_assignment.kv_encryption_key_user[0] - ] -} diff --git a/core/terraform/storage.tf b/core/terraform/storage.tf index 4fa985104e..5471e8cc79 100644 --- a/core/terraform/storage.tf +++ b/core/terraform/storage.tf @@ -8,6 +8,8 @@ resource "azurerm_storage_account" "stg" { queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -20,6 +22,14 @@ resource "azurerm_storage_account" "stg" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = azurerm_key_vault_key.tre_encryption[0].versionless_id + user_assigned_identity_id = azurerm_user_assigned_identity.encryption[0].id + } + } + tags = local.tre_core_tags lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } @@ -77,16 +87,3 @@ resource "azurerm_private_endpoint" "filepe" { azurerm_private_endpoint.blobpe ] } - -resource "azurerm_storage_account_customer_managed_key" "encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.stg.id - key_vault_id = local.key_store_id - key_name = local.cmk_name - user_assigned_identity_id = azurerm_user_assigned_identity.encryption[0].id - - depends_on = [ - azurerm_role_assignment.kv_encryption_key_user[0], - azurerm_key_vault_key.tre_encryption[0] - ] -} diff --git a/core/terraform/upgrade.sh b/core/terraform/upgrade.sh deleted file mode 100644 index 861e28d2f1..0000000000 --- a/core/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TF_VAR_tre_resource_id}-core" diff --git a/core/version.txt b/core/version.txt index 20cc868f1e..8e1395bd35 100644 --- a/core/version.txt +++ b/core/version.txt @@ -1 +1 @@ -__version__ = "0.11.9" +__version__ = "0.12.3" diff --git a/devops/scripts/destroy_env_no_terraform.sh b/devops/scripts/destroy_env_no_terraform.sh index d120860c75..628d8a5d65 100755 --- a/devops/scripts/destroy_env_no_terraform.sh +++ b/devops/scripts/destroy_env_no_terraform.sh @@ -66,6 +66,11 @@ then no_wait_option="--no-wait" fi +script_dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")") + +# shellcheck disable=SC1091 +source "$script_dir/kv_add_network_exception.sh" + group_show_result=$(az group show --name "${core_tre_rg}" > /dev/null 2>&1; echo $?) if [[ "$group_show_result" != "0" ]]; then echo "Resource group ${core_tre_rg} not found - skipping destroy" @@ -153,11 +158,35 @@ if [ "${workspace}" != "0" ]; then | xargs -P 10 -I {} az rest --method delete --uri "{}?api-version=2020-08-01" fi +# delete container repositories individually otherwise defender doesn't purge image scans +function purge_container_repositories() { + local rg=$1 + + local acrs + acrs=$(az acr list --resource-group "$rg" --query [].name --output tsv) + + local acr + for acr in $acrs; do + echo "Found container registry ${acr}, deleting repositories..." + + local repositories + repositories=$(az acr repository list --name "$acr" --output tsv) + + local repository + for repository in $repositories; do + echo " Deleting: $repository" + az acr repository delete --name "$acr" --repository "$repository" --yes --output none + done + done +} + # this will find the mgmt, core resource groups as well as any workspace ones # we are reverse-sorting to first delete the workspace groups (might not be # good enough because we use no-wait sometimes) az group list --query "[?starts_with(name, '${core_tre_rg}')].[name]" -o tsv | sort -r | while read -r rg_item; do + purge_container_repositories "$rg_item" + echo "Deleting resource group: ${rg_item}" az group delete --resource-group "${rg_item}" --yes ${no_wait_option} done diff --git a/devops/scripts/key_vault_list.sh b/devops/scripts/key_vault_list.sh deleted file mode 100755 index faa1aa9384..0000000000 --- a/devops/scripts/key_vault_list.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -if [[ -z ${TRE_ID:-} ]]; then - echo "TRE_ID environment variable must be set." - exit 1 -fi - -echo "DEBUG: Check keyvault and secrets exist" - -echo "az keyvault show" -az keyvault show --name kv-${TRE_ID} - -echo "az keyvault secret list" -az keyvault secret list --vault-name kv-${TRE_ID} - -echo "az keyvault secret list-deleted" -az keyvault secret list-deleted --vault-name kv-${TRE_ID} diff --git a/devops/scripts/kv_add_network_exception.sh b/devops/scripts/kv_add_network_exception.sh new file mode 100755 index 0000000000..bc252cc656 --- /dev/null +++ b/devops/scripts/kv_add_network_exception.sh @@ -0,0 +1,131 @@ +#!/bin/bash + +# +# Add an IP exception to the Key Vault firewall for deployment, and remove on script exit +# The current machine's IP address is used, or $PUBLIC_DEPLOYMENT_IP_ADDRESS if set +# +# Note: Ensure you "source" this script, or else the EXIT trap won't fire at the right time +# + + +function kv_add_network_exception() { + + # set up variables + # + local KV_NAME + KV_NAME=$(get_kv_name) + + local MY_IP + MY_IP=$(get_my_ip) + + echo -e "\nAdding deployment network exception to key vault $KV_NAME..." + + # ensure kv exists + # + if ! does_kv_exist "$KV_NAME"; then + return 0 # don't cause outer sourced script to fail + fi + + # add keyvault network exception + # + az keyvault network-rule add --name "$KV_NAME" --ip-address "$MY_IP" --output none + + local ATTEMPT=1 + local MAX_ATTEMPTS=10 + + while true; do + + if KV_OUTPUT=$(az keyvault secret list --vault-name "$KV_NAME" --query '[].name' --output tsv 2>&1); then + echo -e " Keyvault $KV_NAME is now accessible\n" + break + fi + + if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo -e "Could not add deployment network exception for $KV_NAME" + echo -e "Unable to access keyvault $KV_NAME after $ATTEMPT/$MAX_ATTEMPTS.\n" + echo -e "$KV_OUTPUT\n" + + exit 1 + fi + + echo " Unable to access keyvault $KV_NAME after $ATTEMPT/$MAX_ATTEMPTS. Waiting for network rules to take effect." + sleep 5 + ((ATTEMPT++)) + + done + +} + +function kv_remove_network_exception() { + + # set up variables + # + local KV_NAME + KV_NAME=$(get_kv_name) + + local MY_IP + MY_IP=$(get_my_ip) + + echo -e "\nRemoving deployment network exception to key vault $KV_NAME..." + + # ensure kv exists + # + if ! does_kv_exist "$KV_NAME"; then + return 0 # don't cause outer sourced script to fail + fi + + # remove keyvault network exception + # + az keyvault network-rule remove --name "$KV_NAME" --ip-address "$MY_IP" --output none + echo -e " Deployment network exception removed\n" +} + + +function get_kv_name() { + + local TRE_ID_LOCAL="${TRE_ID:-}" + + if [[ -z "$TRE_ID_LOCAL" ]]; then + if [[ "${core_tre_rg:-}" == rg-* ]]; then # TRE_ID may not be available when called from destroy_env_no_terraform.sh + TRE_ID_LOCAL="${core_tre_rg#rg-}" + fi + fi + + if [[ -z "$TRE_ID_LOCAL" ]]; then + echo -e "Could not add/remove keyvault deployment network exception: TRE_ID is not set\nExiting...\n" + exit 1 + fi + + echo "kv-${TRE_ID_LOCAL}" +} + +function get_my_ip() { + + local MY_IP="${PUBLIC_DEPLOYMENT_IP_ADDRESS:-}" + + if [[ -z "$MY_IP" ]]; then + MY_IP=$(curl -s "ipecho.net/plain"; echo) + fi + + echo "$MY_IP" +} + + +function does_kv_exist() { + + KV_NAME=$1 + + if [[ -z "$(az keyvault list --query "[?name=='$KV_NAME'].id" --output tsv)" ]]; then + echo -e " Core key vault $KV_NAME not found\n" + return 1 + fi + + return 0 +} + + +# setup the trap to remove network exception on exit +trap kv_remove_network_exception EXIT + +# now add the network exception +kv_add_network_exception "$@" diff --git a/devops/scripts/porter_build_bundle.sh b/devops/scripts/porter_build_bundle.sh new file mode 100755 index 0000000000..f44b48767d --- /dev/null +++ b/devops/scripts/porter_build_bundle.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ -f "porter-build-context.env" ]; then + + # shellcheck disable=SC1091 + source "porter-build-context.env" + + echo "Found additional porter build context PORTER_BUILD_CONTEXT of $PORTER_BUILD_CONTEXT" + porter build --build-context "$PORTER_BUILD_CONTEXT" + +else + porter build +fi diff --git a/devops/scripts/set_contributor_sp_secrets.sh b/devops/scripts/set_contributor_sp_secrets.sh deleted file mode 100755 index 95a07da877..0000000000 --- a/devops/scripts/set_contributor_sp_secrets.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/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: -# -# - TRE_ID - The TRE ID, used to deduce the Key Vault name -# - ARM_SUBSCRIPTION_ID - The Azure subscription ID -# - RESOURCE_PROCESSOR_CLIENT_ID - The client ID of the service principal -# - RESOURCE_PROCESSOR_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-$TRE_ID" -az account set --subscription $ARM_SUBSCRIPTION_ID -az keyvault secret set --name deployment-processor-azure-client-id --vault-name $key_vault_name --value $RESOURCE_PROCESSOR_CLIENT_ID -az keyvault secret set --name deployment-processor-azure-client-secret --vault-name $key_vault_name --value $RESOURCE_PROCESSOR_CLIENT_SECRET > /dev/null diff --git a/devops/scripts/setup_local_debugging.sh b/devops/scripts/setup_local_debugging.sh index 4cb2dbdb55..2bf70a63d0 100755 --- a/devops/scripts/setup_local_debugging.sh +++ b/devops/scripts/setup_local_debugging.sh @@ -91,6 +91,19 @@ az role assignment create \ --assignee "${LOGGED_IN_OBJECT_ID}" \ --scope "${STATE_STORE_RESOURCE_ID}" +ROLE_DEFINITION_ID=$(az cosmosdb sql role definition list \ + --resource-group "${RESOURCE_GROUP_NAME}" \ + --account-name "${COSMOSDB_ACCOUNT_NAME}" \ + --query "[?roleName=='Cosmos DB Built-in Data Contributor'].id" \ + --output tsv) + +az cosmosdb sql role assignment create \ + --resource-group "${RESOURCE_GROUP_NAME}" \ + --account-name "${COSMOSDB_ACCOUNT_NAME}" \ + --role-definition-id "${ROLE_DEFINITION_ID}" \ + --principal-id "${LOGGED_IN_OBJECT_ID}" \ + --scope "${STATE_STORE_RESOURCE_ID}" + az role assignment create \ --role "Contributor" \ --assignee "${LOGGED_IN_OBJECT_ID}" \ diff --git a/devops/scripts/upgrade.sh b/devops/scripts/upgrade.sh new file mode 100755 index 0000000000..89733492f7 --- /dev/null +++ b/devops/scripts/upgrade.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +# This script is used to upgrade terraform providers in a specified directory + +# Usage: ./upgrade.sh + +DIR=$1 + +# Load environment variables from .env file +if [ -f "$DIR/.env" ]; then + set -a + # shellcheck source=/dev/null + . "$DIR/.env" + set +a +fi + +# Ensure TF_VAR_mgmt_resource_group_name is set +if [ -z "${TF_VAR_mgmt_resource_group_name}" ]; then + echo "Error: TF_VAR_mgmt_resource_group_name is not set." + exit 1 +fi + +# Ensure TF_VAR_mgmt_storage_account_name is set +if [ -z "${TF_VAR_mgmt_storage_account_name}" ]; then + echo "Error: TF_VAR_mgmt_storage_account_name is not set." + exit 1 +fi + +# Ensure TF_VAR_terraform_state_container_name is set +if [ -z "${TF_VAR_terraform_state_container_name}" ]; then + echo "Error: TF_VAR_terraform_state_container_name is not set." + exit 1 +fi + +# Ensure TRE_ID is set +if [ -z "${TRE_ID}" ]; then + echo "Error: TRE_ID is not set." + exit 1 +fi + +# Infer the key from the directory names +PARENT_DIR=$(basename "$(dirname "$DIR")") +GRANDPARENT_DIR=$(basename "$(dirname "$(dirname "$DIR")")") + +if [[ "$GRANDPARENT_DIR" == "workspaces" || "$GRANDPARENT_DIR" == "shared_services" ]]; then + KEY="${TRE_ID?}_${TF_VAR_id?}_${PARENT_DIR}" +elif [[ "$GRANDPARENT_DIR" == "workspace_services" ]]; then + KEY="${TRE_ID?}_${TF_VAR_workspace_id?}_${TF_VAR_id?}_${PARENT_DIR}" +elif [[ "$GRANDPARENT_DIR" == "user_resources" ]]; then + KEY="${TRE_ID?}_${TF_VAR_workspace_id?}_${TF_VAR_workspace_service_id?}_${TF_VAR_id?}_${PARENT_DIR}" +else + KEY="${TRE_ID?}_${PARENT_DIR}" +fi + +# Run terraform init with upgrade and reconfigure options +terraform -chdir="$DIR/terraform" init -upgrade -reconfigure -input=false -backend=true \ + -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ + -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ + -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ + -backend-config="key=${KEY}" diff --git a/devops/terraform/.terraform.lock.hcl b/devops/terraform/.terraform.lock.hcl index b76fcebd94..6e8b87ea6b 100644 --- a/devops/terraform/.terraform.lock.hcl +++ b/devops/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/devops/terraform/bootstrap.sh b/devops/terraform/bootstrap.sh index 8f67d0b47e..f4ffed11c6 100755 --- a/devops/terraform/bootstrap.sh +++ b/devops/terraform/bootstrap.sh @@ -18,7 +18,7 @@ if ! az storage account show --resource-group "$TF_VAR_mgmt_resource_group_name" # shellcheck disable=SC2154 az storage account create --resource-group "$TF_VAR_mgmt_resource_group_name" \ --name "$TF_VAR_mgmt_storage_account_name" --location "$LOCATION" \ - --allow-blob-public-access false \ + --allow-blob-public-access false --min-tls-version TLS1_2 \ --kind StorageV2 --sku Standard_LRS -o table \ --encryption-key-type-for-queue "$encryption_type" \ --encryption-key-type-for-table "$encryption_type" \ diff --git a/devops/terraform/main.tf b/devops/terraform/main.tf index bd178f37d0..7dd4086153 100644 --- a/devops/terraform/main.tf +++ b/devops/terraform/main.tf @@ -32,6 +32,7 @@ resource "azurerm_storage_account" "state_storage" { cross_tenant_replication_enabled = false allow_nested_items_to_be_public = false shared_access_key_enabled = false + local_user_enabled = false dynamic "identity" { for_each = var.enable_cmk_encryption ? [1] : [] @@ -41,27 +42,20 @@ resource "azurerm_storage_account" "state_storage" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = azurerm_key_vault_key.tre_mgmt_encryption[0].versionless_id + user_assigned_identity_id = azurerm_user_assigned_identity.tre_mgmt_encryption[0].id + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } -resource "azurerm_storage_account_customer_managed_key" "state_storage_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.state_storage.id - key_vault_id = local.key_store_id - key_name = var.kv_mgmt_encryption_key_name - user_assigned_identity_id = azurerm_user_assigned_identity.tre_mgmt_encryption[0].id - - depends_on = [ - azurerm_role_assignment.kv_mgmt_encryption_key_user, - azurerm_key_vault_key.tre_mgmt_encryption[0] - ] -} - - - # Shared container registry resource "azurerm_container_registry" "shared_acr" { name = var.acr_name diff --git a/devops/terraform/terraform.tf b/devops/terraform/terraform.tf index c65c843d00..53167b3ca5 100644 --- a/devops/terraform/terraform.tf +++ b/devops/terraform/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.112.0" + version = "3.117.0" } } } diff --git a/devops/terraform/upgrade.sh b/devops/terraform/upgrade.sh deleted file mode 100644 index a05032d544..0000000000 --- a/devops/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TF_VAR_tre_resource_id}-devops" diff --git a/devops/version.txt b/devops/version.txt index 43a1e95ba4..86716a713a 100644 --- a/devops/version.txt +++ b/devops/version.txt @@ -1 +1 @@ -__version__ = "0.5.3" +__version__ = "0.5.5" diff --git a/docs/azure-tre-overview/airlock.md b/docs/azure-tre-overview/airlock.md index 91b50ee77c..720343a7b9 100644 --- a/docs/azure-tre-overview/airlock.md +++ b/docs/azure-tre-overview/airlock.md @@ -141,6 +141,21 @@ graph LR ``` > Data movement in an Airlock import request +```mermaid +graph LR + subgraph TRE workspace + data(Data to export)-->A + A[(stalexint
export internal)]-->|Request Submitted| B + B[(stalexip
export in-progress)]-->|Security issues found| D[(stalexblocked
export blocked)] + B-->|No security issues found| review{Manual
Approval} + review-->|Rejected| C[(stalexrej
export rejected)] + end + subgraph External + review-->|Approved| E[(stalexapp
export approved)] + end +``` +> Data movement in an Airlock export request + TRE: diff --git a/docs/index.md b/docs/index.md index daa36b31fb..9499aba262 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,22 +2,26 @@ ## What is Azure TRE? -Across the health industry, be it a pharmaceutical company interrogating clinical trial results, or a public health provider analyzing electronic health records, there is the need to enable researchers, analysts, and developers to work with sensitive data sets. +Across multiple industries, be it a pharmaceutical company interrogating clinical trial results, a public health provider analyzing electronic health records, or an engineering organization working on next-generation systems, there is a need to enable researchers and solution developers to collaborate on sensitive data. -Trusted Research Environments (TREs) enable organisations to provide research teams secure access to these data sets along side tooling to ensure a researchers can be productive. Further information on TREs in general can be found in many places, one good resource is [HDR UK](https://www.hdruk.ac.uk/access-to-health-data/trusted-research-environments/). +Trusted Research Environments (TREs) enable organisations to provide research and development (R&D) teams secure access to data alongside tooling to ensure productivity. Further information on TREs in general can be found in many places, one good resource specifically for healthcare is [HDR UK](https://www.hdruk.ac.uk/access-to-health-data/trusted-research-environments/). -The Azure Trusted Research Environment project is an accelerator to assist Microsoft customers and partners who want to build out Trusted Research environments on Azure. This project enables authorized users to deploy and configure secure workspaces and researcher tooling without a dependency on IT teams. +Azure TRE is finding new applications and to date, has been used as the basis for exploring future secure collaboration environments in engineering. Workloads investigated include systems and software development. In truth, any workload in any industry could theoretically be supported with extensions to Azure TRE. + +The Azure Trusted Research Environment project is an accelerator to assist Microsoft customers and partners who want to build out Trusted Research environments on Azure. This project enables authorized users to deploy and configure secure workspaces and R&D tooling without a dependency on IT teams. This project is typically implemented alongside a data platform that provides research ready datasets to TRE workspaces: ![Concepts](assets/TRE_Overview.png) +Azure TRE has also been proven to handle unstructured data scenarios. For example, collaborative Computer Aided Design (CAD), with Product Lifecycle Management integration. + TREs are not “one size fits all”, hence although the Azure TRE has a number of out of the box features, the project has been built be extensible, and hence tooling and data platform agnostic. Core features include: - Self-service for administrators – workspace creation and administration -- Self-service for research teams – research tooling creation and administration +- Self-service for R&D teams – R&D tooling creation and administration - Package and repository mirroring - Extensible architecture - build your own service templates as required - Microsoft Entra ID integration diff --git a/docs/tre-admins/configure-firewall-force-tunneling.md b/docs/tre-admins/configure-firewall-force-tunneling.md new file mode 100644 index 0000000000..68d5886c93 --- /dev/null +++ b/docs/tre-admins/configure-firewall-force-tunneling.md @@ -0,0 +1,21 @@ +# Forced Tunneling to External Firewall in TRE + +Azure TRE deploys and manages an Azure firewall to ensure creation of workspace level rules can be automated when TRE workspaces and other services are created without manual intervention. +It is highly recommended leaving the Azure TRE firewall in place. If there is still the requirement to send all traffic through a centralized enterprise firewall, such as that deployed as part of an Azure landing zone, then forced tunnelling should be used. The centralized firewall will need a superset of rules used by the TRE. + +To setup forced tunneling to an external firewall, follow these steps: + +## 1. Set the firewall_force_tunnel_ip parameter in the config.yaml file +Provide the external firewall's IP address: + +```json +firewall_force_tunnel_ip: 192.168.0.4 +``` +This automatically creates a route table to direct TRE’s traffic to the specified IP. + +## 2. Manually Connect TRE to Your Firewall +Configure connectivity between TRE’s VNet and your external firewall using one of the following methods: + +1. **VNet Peering**: Peer the TRE VNet with your firewall’s VNet. +1. **ExpressRoute**: Use a private connection for firewalls located on-premises. +1. **Site-to-Site VPN**: Establish a VPN connection as an alternative. diff --git a/docs/tre-admins/customer-managed-keys.md b/docs/tre-admins/customer-managed-keys.md index e97bc61655..252d16e87b 100644 --- a/docs/tre-admins/customer-managed-keys.md +++ b/docs/tre-admins/customer-managed-keys.md @@ -2,11 +2,6 @@ You can enable customer-managed keys (CMK) for supporting resources in Azure TRE. -!!! warning - Currently Azure TRE only supports CMK encryption for resources in the TRE core and Base Workspace. - CMK encryption is not supported for the rest of the resources such as those deployed by a TRE workspace. - - !!! caution Currently, it is not possible to redeploy TRE with CMK enabled if it has previously been deployed without it. This is due to limitations of resources such as Azure Container Registry (ACR) that only allow enabling the CMK encryption at the time of resource creation. diff --git a/docs/tre-developers/release.md b/docs/tre-developers/release.md index 19aeb12866..40a431eea5 100644 --- a/docs/tre-developers/release.md +++ b/docs/tre-developers/release.md @@ -21,6 +21,7 @@ The process follows these steps: 5. Include a final line with a link to the full changelog similar to this: **Full Changelog**: https://github.com/microsoft/AzureTRE/compare/v0.9.1...v0.9.2 + 7. Update [AzureTRE-Deployment](https://github.com/microsoft/AzureTRE-Deployment). The procedure may vary depending on the level of changes introduced in the new version but should include the following steps: 1. Update the tag used in [devcontainer.json](https://github.com/microsoft/AzureTRE-Deployment/blob/main/.devcontainer/devcontainer.json). 2. Rebuild the container. diff --git a/docs/tre-developers/ui.md b/docs/tre-developers/ui.md index e9b4a04441..872a883d5f 100644 --- a/docs/tre-developers/ui.md +++ b/docs/tre-developers/ui.md @@ -4,7 +4,7 @@ This project contains a React-based web UI which covers the core aspects of a TR ## Chosen UI Stack + Components The UI is built upon several popular web frameworks: -- React v18 (created via create-react-app, with all build configurations left as defaults) +- React v18 (with Vite) - Typescript - React Router v6 for client side routing - Fluent UI [Fluent UI Docs](https://developer.microsoft.com/en-us/fluentui#/controls/web) @@ -54,4 +54,53 @@ The UI is deployed as part of the `tre-deploy` make target (unless you set `depl To re-deploy _just_ the UI (after an initial deploy), run `make build-and-deploy-ui` from the root of the dev container. This will: - Use the environment variables from your deployment to create a `config.json` file for the UI - Build the source code, via `yarn build` -- Deploy the code to Azure blob storage, where it will be statically served behind the App Gateway that also fronts the APi. +- Deploy the code to Azure blob storage, where it will be statically served behind the App Gateway that also fronts the API. + +## Run the UI +- Ensure `deploy_ui=false` is not set in your `./config.yaml` file +- In the root of the repo, run `make tre-deploy`. This will provision the necessary resources in Azure, build and deploy the UI to Azure blob storage, behind the App Gateway used for the API. The deployment process will also create the necessary `config.json`, using the `config.source.json` as a template. +- In Microsoft Entra ID, locate the TRE Client Apps app (possibly called Swagger App). In the Authentication section add reply URIs for: + - `http://localhost:3000` (if wanting to run locally) + - Your deployed App Url - `https://{TRE_ID}.{LOCATION}.cloudapp.azure.com`. + +At this point you should be able to navigate to the web app in Azure, log in, and see your workspaces. + +## Available Scripts + +In the UI directory, you can run: + +### `yarn start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `yarn test` + +Launches the test runner in the interactive watch mode.
+ +### `yarn run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +### `yarn run serve` + +Serves the production build from the `build` folder.
+ +### `yarn run test:coverage` + +Runs the tests and generates a coverage report.
+ +### `yarn lint` + +Runs the linter on the project.
+ +### `yarn format` + +Runs the formatter on the project.
diff --git a/e2e_tests/config.py b/e2e_tests/config.py index cd43a78181..31ccb34c37 100644 --- a/e2e_tests/config.py +++ b/e2e_tests/config.py @@ -1,11 +1,9 @@ +import warnings from starlette.config import Config +warnings.filterwarnings("ignore", message="Config file '.env' not found.") -try: - config = Config('.env') -# Workaround needed until FastAPI uses Starlette >= 3.7.1 -except FileNotFoundError: - config = Config() +config = Config('.env') # Resource Info RESOURCE_LOCATION: str = config("RESOURCE_LOCATION", default="") diff --git a/e2e_tests/conftest.py b/e2e_tests/conftest.py index 7195a14588..39589e1696 100644 --- a/e2e_tests/conftest.py +++ b/e2e_tests/conftest.py @@ -12,23 +12,13 @@ LOGGER = logging.getLogger(__name__) -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") def pytest_addoption(parser): parser.addoption("--verify", action="store", default="true") -@pytest.fixture(scope="session") -def event_loop(): - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - yield loop - loop.close() - - @pytest.fixture(scope="session") def verify(pytestconfig): if pytestconfig.getoption("verify").lower() == "true": diff --git a/e2e_tests/pytest.ini b/e2e_tests/pytest.ini index 72fe2d3414..3e3cf490e3 100644 --- a/e2e_tests/pytest.ini +++ b/e2e_tests/pytest.ini @@ -10,6 +10,7 @@ markers = workspace_services asyncio_mode = auto +asyncio_default_fixture_loop_scope = session log_cli = 1 log_cli_level = INFO diff --git a/e2e_tests/test_airlock.py b/e2e_tests/test_airlock.py index 43e2df71bb..051a5c9d81 100644 --- a/e2e_tests/test_airlock.py +++ b/e2e_tests/test_airlock.py @@ -14,7 +14,7 @@ from helpers import get_admin_token -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") LOGGER = logging.getLogger(__name__) BLOB_FILE_PATH = "./test_airlock_sample.txt" BLOB_NAME = os.path.basename(BLOB_FILE_PATH) diff --git a/e2e_tests/test_performance.py b/e2e_tests/test_performance.py index 6c6d836d9d..f6e7637fe2 100644 --- a/e2e_tests/test_performance.py +++ b/e2e_tests/test_performance.py @@ -8,7 +8,7 @@ from helpers import get_admin_token -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") @pytest.mark.performance diff --git a/e2e_tests/test_provisioned_health_api.py b/e2e_tests/test_provisioned_health_api.py index 01c3cbeecc..92636d12dc 100644 --- a/e2e_tests/test_provisioned_health_api.py +++ b/e2e_tests/test_provisioned_health_api.py @@ -5,7 +5,7 @@ from resources import strings -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") @pytest.mark.smoke diff --git a/e2e_tests/test_ui.py b/e2e_tests/test_ui.py index 6e34da518b..8d71827fdb 100644 --- a/e2e_tests/test_ui.py +++ b/e2e_tests/test_ui.py @@ -4,7 +4,7 @@ import config -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") @pytest.mark.smoke diff --git a/e2e_tests/test_workspace_service_templates.py b/e2e_tests/test_workspace_service_templates.py index 34545d9af1..8ea70e6368 100644 --- a/e2e_tests/test_workspace_service_templates.py +++ b/e2e_tests/test_workspace_service_templates.py @@ -8,7 +8,7 @@ from resources import strings from helpers import get_admin_token -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") workspace_service_templates = [ (strings.AZUREML_SERVICE), diff --git a/e2e_tests/test_workspace_services.py b/e2e_tests/test_workspace_services.py index cd48910817..3013fef370 100644 --- a/e2e_tests/test_workspace_services.py +++ b/e2e_tests/test_workspace_services.py @@ -5,7 +5,7 @@ from resources.resource import get_resource, post_resource from resources import strings -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") workspace_services = [ strings.AZUREML_SERVICE, diff --git a/e2e_tests/test_workspace_templates.py b/e2e_tests/test_workspace_templates.py index d0ccb1f64e..1fed11daaa 100644 --- a/e2e_tests/test_workspace_templates.py +++ b/e2e_tests/test_workspace_templates.py @@ -11,7 +11,7 @@ from helpers import get_admin_token -pytestmark = pytest.mark.asyncio +pytestmark = pytest.mark.asyncio(loop_scope="session") workspace_templates = [ diff --git a/mkdocs.yml b/mkdocs.yml index d81cd08297..ed2b2c72aa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -140,6 +140,7 @@ nav: - Supported Clouds: tre-admins/supported-clouds.md - Customer Managed Keys: tre-admins/customer-managed-keys.md - Custom Domain Name: tre-admins/custom-domain.md + - Firewall Force Tunneling: tre-admins/configure-firewall-force-tunneling.md - Development: # Docs related to the developing code for the AzureTRE - Local Development: using-tre/local-development/local-development.md diff --git a/resource_processor/_version.py b/resource_processor/_version.py index 17c1a6260b..def467e071 100644 --- a/resource_processor/_version.py +++ b/resource_processor/_version.py @@ -1 +1 @@ -__version__ = "0.10.2" +__version__ = "0.12.1" diff --git a/resource_processor/helpers/__init__.py b/resource_processor/helpers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/resource_processor/resources/commands.py b/resource_processor/helpers/commands.py similarity index 89% rename from resource_processor/resources/commands.py rename to resource_processor/helpers/commands.py index 0111b52358..59c0eaea82 100644 --- a/resource_processor/resources/commands.py +++ b/resource_processor/helpers/commands.py @@ -4,14 +4,13 @@ import logging from urllib.parse import urlparse -from resources.helpers import get_installation_id from shared.logging import logger, shell_output_logger def azure_login_command(config): set_cloud_command = f"az cloud set --name {config['azure_environment']} >/dev/null " - if config["vmss_msi_id"]: + if config.get("vmss_msi_id"): # Use the Managed Identity when in VMSS context login_command = f"az login --identity -u {config['vmss_msi_id']} >/dev/null " @@ -23,7 +22,7 @@ def azure_login_command(config): def apply_porter_credentials_sets_command(config): - if config["vmss_msi_id"]: + if config.get("vmss_msi_id"): # Use the Managed Identity when in VMSS context porter_credential_sets = "porter credentials apply vmss_porter/arm_auth_local_debugging.json >/dev/null 2>&1 && porter credentials apply vmss_porter/aad_auth.json >/dev/null 2>&1" @@ -80,25 +79,31 @@ async def build_porter_command(config, msg_body, custom_action=False): val_base64_bytes = base64.b64encode(val_bytes) parameter_value = val_base64_bytes.decode("ascii") - porter_parameters = porter_parameters + f" --param {parameter_name}=\"{parameter_value}\"" + porter_parameters += f" --param {parameter_name}=\"{parameter_value}\"" - installation_id = get_installation_id(msg_body) + installation_id = msg_body['id'] command_line = [f"porter" # If a custom action (i.e. not install, uninstall, upgrade) we need to use 'invoke' - f"{' invoke --action' if custom_action else ''}" - f" {msg_body['action']} \"{installation_id}\"" - f" --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']}" - f" {porter_parameters} --force" - f" --credential-set arm_auth" - f" --credential-set aad_auth" + f"{' invoke --action' if custom_action else ''} " + f"{msg_body['action']} \"{installation_id}\" " + f"--reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']}" + f"{porter_parameters} " + f"--force " + f"--credential-set arm_auth " + f"--credential-set aad_auth " ] + if msg_body['action'] == 'upgrade': + command_line[0] = command_line[0] + "--force-upgrade " + + command_line[0] = command_line[0].strip() + return command_line async def build_porter_command_for_outputs(msg_body): - installation_id = get_installation_id(msg_body) + installation_id = msg_body['id'] command_line = [f"porter installations output list --installation {installation_id} --output json"] return command_line diff --git a/resource_processor/resources/httpserver.py b/resource_processor/helpers/httpserver.py similarity index 100% rename from resource_processor/resources/httpserver.py rename to resource_processor/helpers/httpserver.py diff --git a/resource_processor/resources/statuses.py b/resource_processor/helpers/statuses.py similarity index 96% rename from resource_processor/resources/statuses.py rename to resource_processor/helpers/statuses.py index 952dcef24b..2e1941f482 100644 --- a/resource_processor/resources/statuses.py +++ b/resource_processor/helpers/statuses.py @@ -1,5 +1,5 @@ from collections import defaultdict -from resources import strings +from helpers import strings # Specify pass and fail status strings so we can return the right statuses to the api depending on the action type (with a default of custom action) diff --git a/resource_processor/resources/strings.py b/resource_processor/helpers/strings.py similarity index 100% rename from resource_processor/resources/strings.py rename to resource_processor/helpers/strings.py diff --git a/resource_processor/resources/helpers.py b/resource_processor/resources/helpers.py deleted file mode 100644 index 98ef4d2e0d..0000000000 --- a/resource_processor/resources/helpers.py +++ /dev/null @@ -1,5 +0,0 @@ -def get_installation_id(msg_body): - """ - This is used to identify each bundle install within the porter state store. - """ - return msg_body['id'] diff --git a/resource_processor/run.sh b/resource_processor/run.sh index 7fd1a8837f..76c4ba2953 100755 --- a/resource_processor/run.sh +++ b/resource_processor/run.sh @@ -6,55 +6,6 @@ set -o nounset # Uncomment this line to see each command for debugging (careful: this will show secrets!) # set -o xtrace -# Generate required configuration for Porter Azure plugin - -# TODO: Remove porter v0 https://github.com/microsoft/AzureTRE/issues/2990 -# Documentation here: - https://github.com/vdice/porter-bundles/tree/master/azure-keyvault -cat > /"${PORTER_HOME_V0}"/config.toml << EOF -default-storage = "azurestorage" -default-secrets = "aad_auth" -no-logs = true - -[[storage]] -name = "azurestorage" -plugin = "azure.table" - -[storage.config] -account="${MGMT_STORAGE_ACCOUNT_NAME}" -resource-group="${MGMT_RESOURCE_GROUP_NAME}" - -[[secrets]] -name = "aad_auth" -plugin = "azure.keyvault" - -[secrets.config] -vault = "${KEY_VAULT_NAME}" -EOF - -# TODO: Remove porter v0 https://github.com/microsoft/AzureTRE/issues/2990 -echo "Azure cli login..." -az cloud set --name "${AZURE_ENVIRONMENT}" -az login --identity -u "${VMSS_MSI_ID}" - -echo "Checking if porter v0 state exists..." -exists=$(az storage table exists --account-name "${MGMT_STORAGE_ACCOUNT_NAME}" --name "porter" --auth-mode "login" --output tsv) -if [ "${exists}" = "True" ]; then - echo "v0 state exists. Checking if migration was completed once before..." - migration_complete_container_name="porter-migration-completed" - exists=$(az storage container exists --account-name "${MGMT_STORAGE_ACCOUNT_NAME}" --name "${migration_complete_container_name}" --auth-mode "login" --output tsv) - if [ "${exists}" = "False" ]; then - echo "${migration_complete_container_name} container doesn't exist. Running porter migration..." - porter storage migrate --old-home "${PORTER_HOME_V0}" --old-account "azurestorage" - echo "Porter migration complete. Creating ${migration_complete_container_name} container to prevent migrating again in the future..." - az storage container create --account-name "${MGMT_STORAGE_ACCOUNT_NAME}" --name "${migration_complete_container_name}" --auth-mode "login" --fail-on-exist - echo "Migration is done." - else - echo "${migration_complete_container_name} container is present. Skipping porter migration." - fi -else - echo "Porter v0 state doesn't exist." -fi - # Launch the runner echo "Starting resource processor..." python -u vmss_porter/runner.py diff --git a/resource_processor/tests_rp/test_commands.py b/resource_processor/tests_rp/test_commands.py new file mode 100644 index 0000000000..bb2f9f20e0 --- /dev/null +++ b/resource_processor/tests_rp/test_commands.py @@ -0,0 +1,94 @@ +import json +import pytest +from unittest.mock import patch, AsyncMock +from helpers.commands import azure_login_command, apply_porter_credentials_sets_command, azure_acr_login_command, build_porter_command, build_porter_command_for_outputs, get_porter_parameter_keys + + +@pytest.fixture +def mock_get_porter_parameter_keys(): + with patch("helpers.commands.get_porter_parameter_keys", new_callable=AsyncMock) as mock: + yield mock + + +@pytest.mark.parametrize("config, expected_command", [ + ({"azure_environment": "AzureCloud", "vmss_msi_id": "msi_id"}, "az cloud set --name AzureCloud >/dev/null && az login --identity -u msi_id >/dev/null "), + ({"azure_environment": "AzureCloud", "arm_client_id": "client_id", "arm_client_secret": "client_secret", "arm_tenant_id": "tenant_id"}, "az cloud set --name AzureCloud >/dev/null && az login --service-principal --username client_id --password client_secret --tenant tenant_id >/dev/null") +]) +def test_azure_login_command(config, expected_command): + """Test azure_login_command function.""" + assert azure_login_command(config) == expected_command + + +@pytest.mark.parametrize("config, expected_command", [ + ({"vmss_msi_id": "msi_id"}, "porter credentials apply vmss_porter/arm_auth_local_debugging.json >/dev/null 2>&1 && porter credentials apply vmss_porter/aad_auth.json >/dev/null 2>&1"), + ({}, "porter credentials apply vmss_porter/arm_auth_local_debugging.json >/dev/null 2>&1 && porter credentials apply vmss_porter/aad_auth_local_debugging.json >/dev/null 2>&1") +]) +def test_apply_porter_credentials_sets_command(config, expected_command): + """Test apply_porter_credentials_sets_command function.""" + assert apply_porter_credentials_sets_command(config) == expected_command + + +@pytest.mark.parametrize("config, expected_command", [ + ({"registry_server": "myregistry.azurecr.io"}, "az acr login --name myregistry >/dev/null ") +]) +def test_azure_acr_login_command(config, expected_command): + """Test azure_acr_login_command function.""" + assert azure_acr_login_command(config) == expected_command + + +@pytest.mark.asyncio +async def test_build_porter_command(mock_get_porter_parameter_keys): + """Test build_porter_command function.""" + config = {"registry_server": "myregistry.azurecr.io"} + msg_body = {"id": "guid", "action": "install", "name": "mybundle", "version": "1.0.0", "parameters": {"param1": "value1"}} + mock_get_porter_parameter_keys.return_value = ["param1"] + + expected_command = [ + "porter install \"guid\" --reference myregistry.azurecr.io/mybundle:v1.0.0 --param param1=\"value1\" --force --credential-set arm_auth --credential-set aad_auth" + ] + + command = await build_porter_command(config, msg_body) + assert command == expected_command + + +@pytest.mark.asyncio +async def test_build_porter_command_for_upgrade(mock_get_porter_parameter_keys): + """Test build_porter_command function for upgrade action.""" + config = {"registry_server": "myregistry.azurecr.io"} + msg_body = {"id": "guid", "action": "upgrade", "name": "mybundle", "version": "1.0.0", "parameters": {"param1": "value1"}} + mock_get_porter_parameter_keys.return_value = ["param1"] + + expected_command = [ + "porter upgrade \"guid\" --reference myregistry.azurecr.io/mybundle:v1.0.0 --param param1=\"value1\" --force --credential-set arm_auth --credential-set aad_auth --force-upgrade" + ] + + command = await build_porter_command(config, msg_body) + assert command == expected_command + + +@pytest.mark.asyncio +async def test_build_porter_command_for_outputs(): + """Test build_porter_command_for_outputs function.""" + msg_body = {"id": "guid", "action": "install", "name": "mybundle", "version": "1.0.0"} + expected_command = ["porter installations output list --installation guid --output json"] + + command = await build_porter_command_for_outputs(msg_body) + assert command == expected_command + + +@pytest.mark.asyncio +@patch("helpers.commands.azure_login_command", return_value="az login command") +@patch("helpers.commands.azure_acr_login_command", return_value="az acr login command") +@patch("asyncio.create_subprocess_shell") +async def test_get_porter_parameter_keys(mock_create_subprocess_shell, mock_azure_acr_login_command, mock_azure_login_command): + """Test get_porter_parameter_keys function.""" + config = {"registry_server": "myregistry.azurecr.io", "porter_env": {}} + msg_body = {"name": "mybundle", "version": "1.0.0"} + mock_proc = AsyncMock() + mock_proc.communicate.return_value = (json.dumps({"parameters": [{"name": "param1"}]}).encode(), b"") + mock_create_subprocess_shell.return_value = mock_proc + + expected_keys = ["param1"] + + keys = await get_porter_parameter_keys(config, msg_body) + assert keys == expected_keys diff --git a/resource_processor/tests_rp/test_runner.py b/resource_processor/tests_rp/test_runner.py new file mode 100644 index 0000000000..7c1dc6d2e4 --- /dev/null +++ b/resource_processor/tests_rp/test_runner.py @@ -0,0 +1,284 @@ +import json +from unittest.mock import patch, AsyncMock, Mock +import pytest +from resource_processor.vmss_porter.runner import ( + set_up_config, receive_message, invoke_porter_action, get_porter_outputs, check_runners, runner +) +from azure.servicebus.aio import ServiceBusClient +from azure.servicebus import ServiceBusSessionFilter + + +@pytest.fixture +def mock_service_bus_client(): + with patch("resource_processor.vmss_porter.runner.ServiceBusClient") as mock: + yield mock + + +@pytest.fixture +def mock_default_credential(): + with patch("resource_processor.vmss_porter.runner.default_credentials") as mock: + yield mock + + +@pytest.fixture +def mock_auto_lock_renewer(): + with patch("resource_processor.vmss_porter.runner.AutoLockRenewer") as mock: + yield mock + + +@pytest.fixture +def mock_logger(): + with patch("resource_processor.vmss_porter.runner.logger") as mock: + yield mock + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.get_config", return_value={"resource_request_queue": "test_queue", "service_bus_namespace": "test_namespace", "vmss_msi_id": "test_msi_id", "porter_env": {}}) +async def test_set_up_config(mock_get_config): + """Test setting up configuration.""" + config = set_up_config() + assert config == {"resource_request_queue": "test_queue", "service_bus_namespace": "test_namespace", "vmss_msi_id": "test_msi_id", "porter_env": {}} + + +async def setup_service_bus_client_and_credential(mock_service_bus_client, mock_default_credential, msi_id): + mock_credential = AsyncMock() + mock_default_credential.return_value.__aenter__.return_value = mock_credential + mock_service_bus_client_instance = mock_service_bus_client.return_value + return mock_service_bus_client_instance, mock_credential + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.receive_message") +async def test_runner(mock_receive_message, mock_service_bus_client, mock_default_credential): + """Test runner with valid MSI ID.""" + mock_service_bus_client_instance, mock_credential = await setup_service_bus_client_and_credential(mock_service_bus_client, mock_default_credential, 'test_msi_id') + + config = {"vmss_msi_id": "test_msi_id", "service_bus_namespace": "test_namespace"} + + await runner(0, config) + + mock_default_credential.assert_called_once_with('test_msi_id') + mock_service_bus_client.assert_called_once_with("test_namespace", mock_credential) + mock_receive_message.assert_called_once_with(mock_service_bus_client_instance, config) + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.receive_message") +async def test_runner_no_msi_id(mock_receive_message, mock_service_bus_client, mock_default_credential): + """Test runner with no MSI ID.""" + mock_service_bus_client_instance, mock_credential = await setup_service_bus_client_and_credential(mock_service_bus_client, mock_default_credential, None) + + config = {"vmss_msi_id": None, "service_bus_namespace": "test_namespace"} + + await runner(0, config) + + mock_default_credential.assert_called_once_with(None) + mock_service_bus_client.assert_called_once_with("test_namespace", mock_credential) + mock_receive_message.assert_called_once_with(mock_service_bus_client_instance, config) + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.receive_message") +async def test_runner_exception(mock_receive_message, mock_service_bus_client, mock_default_credential): + """Test runner with an exception.""" + mock_service_bus_client_instance, mock_credential = await setup_service_bus_client_and_credential(mock_service_bus_client, mock_default_credential, 'test_msi_id') + mock_receive_message.side_effect = Exception("Test Exception") + + config = {"vmss_msi_id": "test_msi_id", "service_bus_namespace": "test_namespace"} + + with pytest.raises(Exception, match="Test Exception"): + await runner(0, config) + + mock_default_credential.assert_called_once_with('test_msi_id') + mock_service_bus_client.assert_called_once_with("test_namespace", mock_credential) + mock_receive_message.assert_called_once_with(mock_service_bus_client_instance, config) + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.invoke_porter_action", return_value=True) +async def test_receive_message(mock_invoke_porter_action, mock_service_bus_client, mock_auto_lock_renewer): + mock_service_bus_client_instance = mock_service_bus_client.return_value + mock_auto_lock_renewer.return_value = AsyncMock() + + mock_receiver = AsyncMock() + mock_receiver.__aenter__.return_value = mock_receiver + mock_receiver.__aexit__.return_value = None + mock_receiver.session.session_id = "test_session_id" + mock_receiver.__aiter__.return_value = [AsyncMock()] + mock_receiver.__aiter__.return_value[0] = json.dumps({"id": "test_id", "action": "install", "stepId": "test_step_id", "operationId": "test_operation_id"}) + + mock_service_bus_client_instance.get_queue_receiver.return_value.__aenter__.return_value = mock_receiver + + run_once = Mock(side_effect=[True, False]) + + config = {"resource_request_queue": "test_queue"} + + await receive_message(mock_service_bus_client_instance, config, keep_running=run_once) + mock_receiver.complete_message.assert_called_once() + mock_service_bus_client_instance.get_queue_receiver.assert_called_once_with(queue_name="test_queue", max_wait_time=1, session_id=ServiceBusSessionFilter.NEXT_AVAILABLE) + + +@pytest.mark.asyncio +async def test_receive_message_unknown_exception(mock_auto_lock_renewer, mock_service_bus_client, mock_logger): + """Test receiving a message with an unknown exception.""" + mock_service_bus_client_instance = mock_service_bus_client.return_value + mock_auto_lock_renewer.return_value = AsyncMock() + + mock_receiver = AsyncMock() + mock_receiver.__aenter__.return_value = mock_receiver + mock_receiver.__aexit__.return_value = None + mock_receiver.session.session_id = "test_session_id" + mock_receiver.__aiter__.return_value = [AsyncMock()] + mock_receiver.__aiter__.return_value[0] = json.dumps({"id": "test_id", "action": "install", "stepId": "test_step_id", "operationId": "test_operation_id"}) + + mock_service_bus_client_instance.get_queue_receiver.return_value.__aenter__.return_value = mock_receiver + + run_once = Mock(side_effect=[True, False]) + + config = {"resource_request_queue": "test_queue"} + + with patch("resource_processor.vmss_porter.runner.receive_message", side_effect=Exception("Test Exception")): + await receive_message(mock_service_bus_client_instance, config, keep_running=run_once) + mock_logger.exception.assert_any_call("Unknown exception. Will retry...") + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command", return_value=["porter install"]) +@patch("resource_processor.vmss_porter.runner.run_porter", return_value=(0, "stdout", "stderr")) +@patch("resource_processor.vmss_porter.runner.service_bus_message_generator", return_value="test_message") +async def test_invoke_porter_action(mock_service_bus_message_generator, mock_run_porter, mock_build_porter_command, mock_service_bus_client): + """Test invoking a porter action.""" + mock_sb_sender = AsyncMock() + mock_service_bus_client.get_queue_sender.return_value = mock_sb_sender + + config = {"deployment_status_queue": "test_queue"} + msg_body = {"id": "test_id", "action": "install", "stepId": "test_step_id", "operationId": "test_operation_id"} + + result = await invoke_porter_action(msg_body, mock_service_bus_client, config) + + assert result is True + mock_sb_sender.send_messages.assert_called() + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command", return_value=["porter install"]) +@patch("resource_processor.vmss_porter.runner.run_porter", return_value=(1, "", "error")) +@patch("resource_processor.vmss_porter.runner.service_bus_message_generator", return_value="test_message") +async def test_invoke_porter_action_failure(mock_service_bus_message_generator, mock_run_porter, mock_build_porter_command, mock_service_bus_client): + """Test invoking a porter action with failure.""" + mock_sb_client = AsyncMock(spec=ServiceBusClient) + mock_sb_sender = AsyncMock() + mock_sb_client.get_queue_sender.return_value = mock_sb_sender + + config = {"deployment_status_queue": "test_queue"} + msg_body = {"id": "test_id", "action": "install", "stepId": "test_step_id", "operationId": "test_operation_id"} + + result = await invoke_porter_action(msg_body, mock_sb_client, config) + + assert result is False + mock_sb_sender.send_messages.assert_called() + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command", return_value=["porter install"]) +@patch("resource_processor.vmss_porter.runner.run_porter", side_effect=[(1, "", "could not find installation"), (0, "", "")]) +@patch("resource_processor.vmss_porter.runner.service_bus_message_generator", return_value="test_message") +async def test_invoke_porter_action_upgrade_failure_install_success(mock_service_bus_message_generator, mock_run_porter, mock_build_porter_command, mock_service_bus_client): + """Test invoking a porter action with upgrade failure and install success.""" + mock_sb_client = AsyncMock(spec=ServiceBusClient) + mock_sb_sender = AsyncMock() + mock_sb_client.get_queue_sender.return_value = mock_sb_sender + + config = {"deployment_status_queue": "test_queue"} + msg_body = {"id": "test_id", "action": "upgrade", "stepId": "test_step_id", "operationId": "test_operation_id"} + + result = await invoke_porter_action(msg_body, mock_sb_client, config) + + assert result is True + mock_sb_sender.send_messages.assert_called() + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command", return_value=["porter install"]) +@patch("resource_processor.vmss_porter.runner.run_porter", side_effect=[(1, "", "could not find installation"), (1, "", "installation failed")]) +@patch("resource_processor.vmss_porter.runner.service_bus_message_generator", return_value="test_message") +async def test_invoke_porter_action_upgrade_failure_install_failure(mock_service_bus_message_generator, mock_run_porter, mock_build_porter_command, mock_service_bus_client): + """Test invoking a porter action with upgrade and install failure.""" + mock_sb_client = AsyncMock(spec=ServiceBusClient) + mock_sb_sender = AsyncMock() + mock_sb_client.get_queue_sender.return_value = mock_sb_sender + + config = {"deployment_status_queue": "test_queue"} + msg_body = {"id": "test_id", "action": "upgrade", "stepId": "test_step_id", "operationId": "test_operation_id"} + + result = await invoke_porter_action(msg_body, mock_sb_client, config) + + assert result is False + mock_sb_sender.send_messages.assert_called() + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command", return_value=["porter install"]) +@patch("resource_processor.vmss_porter.runner.run_porter", return_value=(1, "", "could not find installation")) +@patch("resource_processor.vmss_porter.runner.service_bus_message_generator", return_value="test_message") +async def test_invoke_porter_action_uninstall_failure(mock_service_bus_message_generator, mock_run_porter, mock_build_porter_command, mock_service_bus_client): + """Test invoking a porter action with uninstall failure.""" + mock_sb_client = AsyncMock(spec=ServiceBusClient) + mock_sb_sender = AsyncMock() + mock_sb_client.get_queue_sender.return_value = mock_sb_sender + + config = {"deployment_status_queue": "test_queue"} + msg_body = {"id": "test_id", "action": "uninstall", "stepId": "test_step_id", "operationId": "test_operation_id"} + + result = await invoke_porter_action(msg_body, mock_sb_client, config) + + assert result is True + mock_sb_sender.send_messages.assert_called() + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command", return_value=["porter custom-action"]) +@patch("resource_processor.vmss_porter.runner.run_porter", return_value=(0, "stdout", "stderr")) +@patch("resource_processor.vmss_porter.runner.service_bus_message_generator", return_value="test_message") +async def test_invoke_porter_action_custom_action(mock_service_bus_message_generator, mock_run_porter, mock_build_porter_command, mock_service_bus_client): + """Test invoking a porter custom action.""" + mock_sb_client = AsyncMock(spec=ServiceBusClient) + mock_sb_sender = AsyncMock() + mock_sb_client.get_queue_sender.return_value = mock_sb_sender + + config = {"deployment_status_queue": "test_queue"} + msg_body = {"id": "test_id", "action": "custom-action", "stepId": "test_step_id", "operationId": "test_operation_id"} + + result = await invoke_porter_action(msg_body, mock_sb_client, config) + + assert result is True + mock_sb_sender.send_messages.assert_called() + + +@pytest.mark.asyncio +@patch("resource_processor.vmss_porter.runner.build_porter_command_for_outputs", return_value=["porter installations output list"]) +@patch("resource_processor.vmss_porter.runner.run_porter", return_value=(0, json.dumps([{"name": "output1", "value": "value1"}]), "stderr")) +async def test_get_porter_outputs(mock_run_porter, mock_build_porter_command_for_outputs): + """Test getting porter outputs.""" + config = {} + msg_body = {"id": "test_id", "action": "install"} + + success, outputs = await get_porter_outputs(msg_body, config) + + assert success is True + assert outputs == [{"name": "output1", "value": "value1"}] + + +@pytest.mark.asyncio +@patch("asyncio.sleep", new_callable=AsyncMock) +async def test_check_runners(_): + """Test checking runners.""" + mock_process = Mock() + mock_process.is_alive.return_value = False + processes = [mock_process] + mock_httpserver = AsyncMock() + + run_once = Mock(side_effect=[True, False]) + + await check_runners(processes, mock_httpserver, keep_running=run_once) + mock_httpserver.kill.assert_called_once() diff --git a/resource_processor/vmss_porter/Dockerfile b/resource_processor/vmss_porter/Dockerfile index ffa2a4e65c..117ac6a604 100644 --- a/resource_processor/vmss_porter/Dockerfile +++ b/resource_processor/vmss_porter/Dockerfile @@ -1,42 +1,21 @@ # syntax=docker/dockerfile:1 -FROM python:3.12-slim-bullseye +FROM python:3.12-slim-bookworm SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache # Install Azure CLI -ARG AZURE_CLI_VERSION=2.67.0-1~bullseye +ARG AZURE_CLI_VERSION=2.67.0-1~bookworm COPY scripts/azure-cli.sh /tmp/ RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ export AZURE_CLI_VERSION=${AZURE_CLI_VERSION} \ && /tmp/azure-cli.sh -# TODO: Remove porter v0 https://github.com/microsoft/AzureTRE/issues/2990 -# Install Porter -ARG PORTER_MIRROR=https://cdn.porter.sh -ARG PORTER_VERSION=v0.38.13 -ARG PORTER_TERRAFORM_MIXIN_VERSION=v1.0.0-rc.1 -ARG PORTER_AZ_MIXIN_VERSION=v0.7.3 -ARG PORTER_AZURE_PLUGIN_VERSION=v0.11.2 -ARG PORTER_HOME=/root/.porter-v0/ -COPY scripts/porter.sh /tmp/ -RUN export PORTER_MIRROR=${PORTER_MIRROR} \ - PORTER_VERSION=${PORTER_VERSION} \ - PORTER_TERRAFORM_MIXIN_VERSION=${PORTER_TERRAFORM_MIXIN_VERSION} \ - PORTER_AZ_MIXIN_VERSION=${PORTER_AZ_MIXIN_VERSION} \ - PORTER_AZURE_PLUGIN_VERSION=${PORTER_AZURE_PLUGIN_VERSION} \ - PORTER_HOME=${PORTER_HOME} \ - && /tmp/porter.sh - -ENV PORTER_HOME_V0 ${PORTER_HOME} - -# can't be in a non default path -# ARG PORTER_HOME_V1=/home/$USERNAME/.porter-v1/ ARG PORTER_HOME_V1=/root/.porter/ -ARG PORTER_VERSION=v1.1.1 -ARG PORTER_TERRAFORM_MIXIN_VERSION=v1.0.3 -ARG PORTER_AZ_MIXIN_VERSION=v1.0.2 +ARG PORTER_VERSION=v1.2.1 +ARG PORTER_TERRAFORM_MIXIN_VERSION=v1.0.5 +ARG PORTER_AZ_MIXIN_VERSION=v1.0.4 ARG PORTER_AZURE_PLUGIN_VERSION=v1.2.3 COPY scripts/porter-v1.sh /tmp/ RUN export PORTER_VERSION=${PORTER_VERSION} \ @@ -46,19 +25,22 @@ RUN export PORTER_VERSION=${PORTER_VERSION} \ PORTER_HOME=${PORTER_HOME_V1} \ && /tmp/porter-v1.sh -ENV PATH ${PORTER_HOME_V1}:$PATH +ENV PATH=${PORTER_HOME_V1}:$PATH # Install Docker +ARG DOCKER_CE_VERSION="5:27.4.1-1~debian.12~bookworm" +ARG DOCKER_CE_CLI_VERSION="5:27.4.1-1~debian.12~bookworm" +ARG DOCKER_CONTAINERD_VERSION="1.7.24-1" RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ apt-get update && apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release --no-install-recommends \ && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \ | tee /etc/apt/sources.list.d/docker.list > /dev/null \ - && apt-get update && apt-get install -y docker-ce="5:23.0.3-1~debian.11~bullseye" docker-ce-cli="5:23.0.3-1~debian.11~bullseye" containerd.io="1.6.20-1" --no-install-recommends + && apt-get update && apt-get install -y docker-ce="$DOCKER_CE_VERSION" docker-ce-cli="$DOCKER_CE_CLI_VERSION" containerd.io="$DOCKER_CONTAINERD_VERSION" --no-install-recommends COPY ./vmss_porter/config.yaml ${PORTER_HOME_V1}/ -ENV PYTHONPATH . +ENV PYTHONPATH=. COPY ./vmss_porter/requirements.txt /tmp/ RUN pip3 --no-cache-dir install -r /tmp/requirements.txt diff --git a/resource_processor/vmss_porter/runner.py b/resource_processor/vmss_porter/runner.py index 3de4ac06fe..6f6d6e21e1 100644 --- a/resource_processor/vmss_porter/runner.py +++ b/resource_processor/vmss_porter/runner.py @@ -4,14 +4,13 @@ import asyncio import logging import sys -from resources.commands import azure_acr_login_command, azure_login_command, build_porter_command, build_porter_command_for_outputs, apply_porter_credentials_sets_command +from helpers.commands import azure_acr_login_command, azure_login_command, build_porter_command, build_porter_command_for_outputs, apply_porter_credentials_sets_command from shared.config import get_config -from resources.helpers import get_installation_id -from resources.httpserver import start_server +from helpers.httpserver import start_server from shared.logging import initialize_logging, logger, shell_output_logger, tracer from shared.config import VERSION -from resources import statuses +from helpers import statuses from contextlib import asynccontextmanager from azure.servicebus import ServiceBusMessage, NEXT_AVAILABLE_SESSION from azure.servicebus.exceptions import OperationTimeoutError, ServiceBusConnectionError @@ -38,7 +37,7 @@ async def default_credentials(msi_id): await credential.close() -async def receive_message(service_bus_client, config: dict): +async def receive_message(service_bus_client, config: dict, keep_running=lambda: True): """ This method is run per process. Each process will connect to service bus and try to establish a session. If messages are there, the process will continue to receive all the messages associated with that session. @@ -46,7 +45,7 @@ async def receive_message(service_bus_client, config: dict): """ q_name = config["resource_request_queue"] - while True: + while keep_running(): try: logger.info("Looking for new session...") # max_wait_time=1 -> don't hold the session open after processing of the message has finished @@ -94,6 +93,7 @@ async def receive_message(service_bus_client, config: dict): except Exception: # Catch all other exceptions, log them via .exception to get the stack trace, sleep, and reconnect + logger.exception("Unknown exception. Will retry...") @@ -135,7 +135,7 @@ def service_bus_message_generator(sb_message: dict, status: str, deployment_mess """ Generate a resource request message """ - installation_id = get_installation_id(sb_message) + installation_id = sb_message["id"] message_dict = { "operationId": sb_message["operationId"], "stepId": sb_message["stepId"], @@ -156,7 +156,7 @@ async def invoke_porter_action(msg_body: dict, sb_client: ServiceBusClient, conf Handle resource message by invoking specified porter action (i.e. install, uninstall) """ - installation_id = get_installation_id(msg_body) + installation_id = msg_body["id"] action = msg_body["action"] logger.info(f"{action} action starting for {installation_id}...") sb_sender = sb_client.get_queue_sender(queue_name=config["deployment_status_queue"]) @@ -173,13 +173,25 @@ async def invoke_porter_action(msg_body: dict, sb_client: ServiceBusClient, conf logger.debug("Starting to run porter execution command...") returncode, _, err = await run_porter(porter_command, config) logger.debug("Finished running porter execution command.") - action_completed_without_error = True + + action_completed_without_error = False + + if returncode == 0: + action_completed_without_error = True # Handle command output if returncode != 0 and err is not None: error_message = "Error message: " + " ".join(err.split('\n')) + "; Command executed: " + " ".join(porter_command) action_completed_without_error = False + if "upgrade" == action and ("could not find installation" in err or "The installation cannot be upgraded, because it is not installed." in err): + logger.warning("Upgrade failed, attempting install...") + msg_body['action'] = "install" + porter_command = await build_porter_command(config, msg_body, False) + returncode, _, err = await run_porter(porter_command, config) + if returncode == 0: + action_completed_without_error = True + if "uninstall" == action and "could not find installation" in err: logger.warning("The installation doesn't exist. Treating as a successful action to allow the flow to proceed.") action_completed_without_error = True @@ -227,7 +239,8 @@ async def get_porter_outputs(msg_body: dict, config: dict): if returncode != 0: error_message = "Error context message = " + " ".join(err.split('\n')) - logger.info(f"{get_installation_id(msg_body)}: Failed to get outputs with error = {error_message}") + installation_id = msg_body["id"] + logger.info(f"{installation_id}: Failed to get outputs with error = {error_message}") return False, {} else: outputs_json = {} @@ -253,10 +266,10 @@ async def runner(process_number: int, config: dict): await receive_message(service_bus_client, config) -async def check_runners(processes: list, httpserver: Process): +async def check_runners(processes: list, httpserver: Process, keep_running=lambda: True): logger.info("Starting runners check...") - while True: + while keep_running(): await asyncio.sleep(30) if all(not process.is_alive() for process in processes): logger.error("All runner processes have failed!") diff --git a/templates/shared_services/admin-vm/parameters.json b/templates/shared_services/admin-vm/parameters.json index ac546acffc..de37ff3528 100755 --- a/templates/shared_services/admin-vm/parameters.json +++ b/templates/shared_services/admin-vm/parameters.json @@ -45,6 +45,30 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } + }, + { + "name": "os_image", + "source": { + "env": "OS_IMAGE" + } + }, + { + "name": "image_gallery_id", + "source": { + "env": "IMAGE_GALLERY_ID" + } } ] } diff --git a/templates/shared_services/admin-vm/porter.yaml b/templates/shared_services/admin-vm/porter.yaml index 4f9977a8b6..204b08dff1 100644 --- a/templates/shared_services/admin-vm/porter.yaml +++ b/templates/shared_services/admin-vm/porter.yaml @@ -1,11 +1,27 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-admin-vm -version: 0.4.8 +version: 0.5.3 description: "An admin vm shared service" dockerfile: Dockerfile.tmpl registry: azuretre +custom: + image_options: + "Windows 11": + source_image_reference: + publisher: MicrosoftWindowsDesktop + offer: windows-11 + sku: win11-24h2-pro + version: latest + secure_boot_enabled: true + vtpm_enabled: true + # For information on using custom images, see README.me in the guacamole/user-resources folder + # "Custom Image From Gallery": + # source_image_name: your-image + # secure_boot_enabled: false + # vtpm_enabled: false + credentials: - name: azure_tenant_id env: ARM_TENANT_ID @@ -44,6 +60,19 @@ parameters: env: ADMIN_JUMPBOX_VM_SKU type: string default: Standard_B2s + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" + - name: os_image + type: string + default: "Windows 11" + - name: image_gallery_id + type: string + description: Azure resource ID for the compute image gallery to pull images from (if specifying custom images by name) + default: "" mixins: - terraform: @@ -56,6 +85,10 @@ install: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } admin_jumpbox_vm_sku: ${ bundle.parameters.admin_jumpbox_vm_sku } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } + image_gallery_id: ${ bundle.parameters.image_gallery_id } + image: ${ bundle.parameters.os_image } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -71,6 +104,10 @@ upgrade: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } admin_jumpbox_vm_sku: ${ bundle.parameters.admin_jumpbox_vm_sku } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } + image_gallery_id: ${ bundle.parameters.image_gallery_id } + image: ${ bundle.parameters.os_image } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -86,6 +123,10 @@ uninstall: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } admin_jumpbox_vm_sku: ${ bundle.parameters.admin_jumpbox_vm_sku } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } + image_gallery_id: ${ bundle.parameters.image_gallery_id } + image: ${ bundle.parameters.os_image } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/shared_services/admin-vm/template_schema.json b/templates/shared_services/admin-vm/template_schema.json index d644d49be7..e0f08f17fd 100644 --- a/templates/shared_services/admin-vm/template_schema.json +++ b/templates/shared_services/admin-vm/template_schema.json @@ -6,6 +6,16 @@ "description": "Provides VM in the core network", "required": [], "properties": { + "os_image": { + "$id": "#/properties/os_image", + "type": "string", + "title": "Windows image", + "description": "Select Windows image to use for VM", + "enum": [ + "Windows 11" + ], + "default": "Windows 11" + }, "admin_jumpbox_vm_sku": { "$id": "#/properties/admin_jumpbox_vm_sku", "type": "string", diff --git a/templates/shared_services/admin-vm/terraform/.terraform.lock.hcl b/templates/shared_services/admin-vm/terraform/.terraform.lock.hcl index 6b3604f3c1..d715ea2300 100644 --- a/templates/shared_services/admin-vm/terraform/.terraform.lock.hcl +++ b/templates/shared_services/admin-vm/terraform/.terraform.lock.hcl @@ -2,41 +2,41 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } provider "registry.terraform.io/hashicorp/random" { - version = "3.4.3" - constraints = "3.4.3" + version = "3.6.3" + constraints = "3.6.3" hashes = [ - "h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=", - "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752", - "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b", - "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53", + "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3", - "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5", - "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda", - "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6", - "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1", - "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d", - "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8", - "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", ] } diff --git a/templates/shared_services/admin-vm/terraform/admin-jumpbox.tf b/templates/shared_services/admin-vm/terraform/admin-jumpbox.tf index 3ef4b8734b..ce343e48f4 100644 --- a/templates/shared_services/admin-vm/terraform/admin-jumpbox.tf +++ b/templates/shared_services/admin-vm/terraform/admin-jumpbox.tf @@ -36,21 +36,48 @@ resource "azurerm_windows_virtual_machine" "jumpbox" { admin_username = "adminuser" admin_password = random_password.password.result tags = local.tre_shared_service_tags + encryption_at_host_enabled = true + secure_boot_enabled = local.secure_boot_enabled + vtpm_enabled = local.vtpm_enabled - source_image_reference { - publisher = "MicrosoftWindowsDesktop" - offer = "windows-10" - sku = "win10-21h2-pro-g2" - version = "latest" + # set source_image_id/reference depending on the config for the selected image + source_image_id = local.selected_image_source_id + dynamic "source_image_reference" { + for_each = local.selected_image_source_refs + content { + publisher = source_image_reference.value["publisher"] + offer = source_image_reference.value["offer"] + sku = source_image_reference.value["sku"] + version = source_image_reference.value["version"] + } } os_disk { - name = "vm-dsk-${var.tre_id}" - caching = "ReadWrite" - storage_account_type = "Standard_LRS" + name = "vm-dsk-${var.tre_id}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.jumpbox_disk_encryption[0].id : null } - lifecycle { ignore_changes = [tags] } + # ignore changes to secure_boot_enabled and vtpm_enabled as these are destructive + # (may be allowed once https://github.com/hashicorp/terraform-provider-azurerm/issues/25808 is fixed) + # + lifecycle { ignore_changes = [tags, secure_boot_enabled, vtpm_enabled] } +} + +resource "azurerm_disk_encryption_set" "jumpbox_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "disk-encryption-jumpbox-${var.tre_id}-${var.tre_resource_id}" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name + key_vault_key_id = data.azurerm_key_vault_key.tre_encryption_key[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.tre_encryption_identity[0].id] + } } resource "azurerm_key_vault_secret" "jumpbox_credentials" { diff --git a/templates/shared_services/admin-vm/terraform/data.tf b/templates/shared_services/admin-vm/terraform/data.tf index 69c133b06e..b6ad53211c 100644 --- a/templates/shared_services/admin-vm/terraform/data.tf +++ b/templates/shared_services/admin-vm/terraform/data.tf @@ -11,3 +11,15 @@ data "azurerm_key_vault" "keyvault" { data "azurerm_resource_group" "rg" { name = local.core_resource_group_name } + +data "azurerm_key_vault_key" "tre_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "tre_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = local.core_resource_group_name +} diff --git a/templates/shared_services/admin-vm/terraform/locals.tf b/templates/shared_services/admin-vm/terraform/locals.tf index f9ad84b852..a6f25ed263 100644 --- a/templates/shared_services/admin-vm/terraform/locals.tf +++ b/templates/shared_services/admin-vm/terraform/locals.tf @@ -6,4 +6,18 @@ locals { tre_id = var.tre_id tre_shared_service_id = var.tre_resource_id } + cmk_name = "tre-encryption-${var.tre_id}" + encryption_identity_name = "id-encryption-${var.tre_id}" + + # Load image details from porter.yaml + porter_yaml = yamldecode(file("${path.module}/../porter.yaml")) + image_details = local.porter_yaml["custom"]["image_options"] + + # Create local variables to support the VM resource + selected_image = local.image_details[var.image] + # selected_image_source_refs is an array to enable easy use of a dynamic block + selected_image_source_refs = lookup(local.selected_image, "source_image_reference", null) == null ? [] : [local.selected_image.source_image_reference] + selected_image_source_id = lookup(local.selected_image, "source_image_name", null) == null ? null : "${var.image_gallery_id}/images/${local.selected_image.source_image_name}" + secure_boot_enabled = lookup(local.selected_image, "secure_boot_enabled", false) + vtpm_enabled = lookup(local.selected_image, "vtpm_enabled", false) } diff --git a/templates/shared_services/admin-vm/terraform/main.tf b/templates/shared_services/admin-vm/terraform/main.tf index 1e8acf7334..e5ab5975c4 100644 --- a/templates/shared_services/admin-vm/terraform/main.tf +++ b/templates/shared_services/admin-vm/terraform/main.tf @@ -3,11 +3,11 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } random = { source = "hashicorp/random" - version = "=3.4.3" + version = "=3.6.3" } } diff --git a/templates/shared_services/admin-vm/terraform/upgrade.sh b/templates/shared_services/admin-vm/terraform/upgrade.sh deleted file mode 100644 index 12597ee325..0000000000 --- a/templates/shared_services/admin-vm/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TF_VAR_tre_resource_id}-shared-adminvm" diff --git a/templates/shared_services/admin-vm/terraform/variables.tf b/templates/shared_services/admin-vm/terraform/variables.tf index b52d21ea1e..f11048bf29 100644 --- a/templates/shared_services/admin-vm/terraform/variables.tf +++ b/templates/shared_services/admin-vm/terraform/variables.tf @@ -11,3 +11,20 @@ variable "tre_resource_id" { variable "admin_jumpbox_vm_sku" { type = string } + +variable "enable_cmk_encryption" { + type = bool + default = false +} + +variable "key_store_id" { + type = string +} + +variable "image_gallery_id" { + type = string +} + +variable "image" { + type = string +} diff --git a/templates/shared_services/airlock_notifier/porter.yaml b/templates/shared_services/airlock_notifier/porter.yaml index 09020d4de4..41cd4c1529 100644 --- a/templates/shared_services/airlock_notifier/porter.yaml +++ b/templates/shared_services/airlock_notifier/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-airlock-notifier -version: 1.0.7 +version: 1.0.8 description: "A shared service notifying on Airlock Operations" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/shared_services/airlock_notifier/terraform/.terraform.lock.hcl b/templates/shared_services/airlock_notifier/terraform/.terraform.lock.hcl index b76fcebd94..6e8b87ea6b 100644 --- a/templates/shared_services/airlock_notifier/terraform/.terraform.lock.hcl +++ b/templates/shared_services/airlock_notifier/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/shared_services/airlock_notifier/terraform/providers.tf b/templates/shared_services/airlock_notifier/terraform/providers.tf index 11eee8e326..6bdc13d6c3 100644 --- a/templates/shared_services/airlock_notifier/terraform/providers.tf +++ b/templates/shared_services/airlock_notifier/terraform/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } } backend "azurerm" {} diff --git a/templates/shared_services/airlock_notifier/terraform/upgrade.sh b/templates/shared_services/airlock_notifier/terraform/upgrade.sh deleted file mode 100755 index 72785516f5..0000000000 --- a/templates/shared_services/airlock_notifier/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TF_VAR_tre_resource_id}-shared-airlock-notifier" diff --git a/templates/shared_services/certs/parameters.json b/templates/shared_services/certs/parameters.json index 53dd18791e..ba8512e794 100755 --- a/templates/shared_services/certs/parameters.json +++ b/templates/shared_services/certs/parameters.json @@ -57,6 +57,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/shared_services/certs/porter.yaml b/templates/shared_services/certs/porter.yaml index 6c4fee6453..ede2be3478 100755 --- a/templates/shared_services/certs/porter.yaml +++ b/templates/shared_services/certs/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-certs -version: 0.6.1 +version: 0.7.4 description: "An Azure TRE shared service to generate certificates for a specified internal domain using Letsencrypt" registry: azuretre dockerfile: Dockerfile.tmpl @@ -51,6 +51,12 @@ parameters: - name: id type: string description: "Resource ID" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" mixins: - exec @@ -67,6 +73,8 @@ install: domain_prefix: ${ bundle.parameters.domain_prefix } cert_name: ${ bundle.parameters.cert_name } tre_resource_id: ${ bundle.parameters.id } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -80,6 +88,7 @@ install: - name: storage_account_name - name: resource_group_name - name: keyvault_name + - name: password_name - az: description: "Set Azure Cloud Environment" arguments: @@ -106,6 +115,7 @@ install: resource_group_name: ${ bundle.outputs.resource_group_name } keyvault_name: ${ bundle.outputs.keyvault_name } cert_name: ${ bundle.parameters.cert_name } + password_name: ${ bundle.outputs.password_name } - az: description: "Stop application gateway" arguments: @@ -116,7 +126,6 @@ install: resource-group: ${ bundle.outputs.resource_group_name } name: ${ bundle.outputs.application_gateway_name } - upgrade: - exec: description: "Upgrade shared service" @@ -132,6 +141,8 @@ uninstall: domain_prefix: ${ bundle.parameters.domain_prefix } cert_name: ${ bundle.parameters.cert_name } tre_resource_id: ${ bundle.parameters.id } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -158,6 +169,7 @@ renew: - name: storage_account_name - name: resource_group_name - name: keyvault_name + - name: password_name - az: description: "Set Azure Cloud Environment" arguments: @@ -193,6 +205,7 @@ renew: resource_group_name: ${ bundle.outputs.resource_group_name } keyvault_name: ${ bundle.outputs.keyvault_name } cert_name: ${ bundle.parameters.cert_name } + password_name: ${ bundle.outputs.password_name } - az: description: "Stop application gateway" arguments: diff --git a/templates/shared_services/certs/scripts/letsencrypt.sh b/templates/shared_services/certs/scripts/letsencrypt.sh index 4339990f43..bd88397215 100755 --- a/templates/shared_services/certs/scripts/letsencrypt.sh +++ b/templates/shared_services/certs/scripts/letsencrypt.sh @@ -29,6 +29,10 @@ while [ "$1" != "" ]; do shift cert_name=$1 ;; + --password_name) + shift + password_name=$1 + ;; *) echo "Unexpected argument: '$1'" usage @@ -122,8 +126,8 @@ sid=$(az keyvault certificate import \ --password "${CERT_PASSWORD}" \ | jq -r '.sid') -echo "Saving certificate password to KV with key ${cert_name}-password" -az keyvault secret set --name "${cert_name}"-password \ +echo "Saving certificate password to KV with key ${password_name}" +az keyvault secret set --name "$password_name" \ --vault-name "${keyvault_name}" \ --value "${CERT_PASSWORD}" diff --git a/templates/shared_services/certs/terraform/.terraform.lock.hcl b/templates/shared_services/certs/terraform/.terraform.lock.hcl index b76fcebd94..41771bac4d 100644 --- a/templates/shared_services/certs/terraform/.terraform.lock.hcl +++ b/templates/shared_services/certs/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "4.17.0" + constraints = "4.17.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:gpFgaBSkRTxhavgPAuqQcElHJqmRJ1RpQGr1K0dvVW8=", + "zh:163b81a3bf29c8f161a1c100a48164b1bd1af434cd564b44596cb71a6c33f03d", + "zh:2996b107d3c05a9db14458b32b6f22f8cde0adb96263196d82d3dc302907a257", + "zh:361abd84b6e73016ebebb9ef9cd14c237d8b1e4500ea75f73243ff0534e5e4fb", + "zh:4872445dcb109fe8bbaba439d3dffaaef849a92645df3f8a854d3a40ac962f68", + "zh:61974eb7379acadbceb47b001ae1de2cdefe8cf078a15fff3a6fcc753cd24273", + "zh:75c60ca6e7851fe1d52fe9f5a0ae3d219e300ee5aa63bc8f807e3e0cab569ff0", + "zh:7c79305cff7849e6c5d9d60fe570510f95fb2e2bd5ae801da0281702f21dd779", + "zh:964b7da03f2dc55583cda3c277fef3511824b183a3a88344ae4ff9823af79109", + "zh:cad1593d364eb22b68578a1da4fd4d84749dc81f20e6591b27c6cb1eed9d2072", + "zh:db1a2ca17aae78813e8e0676bb9ef941e1a1e32d9fc6e1b239c24661605a8425", + "zh:e3a65d2f6f5a63cd1beeeb60a23e7e6b7328ebbd46ffe994792aaac6738186c3", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/shared_services/certs/terraform/appgateway.tf b/templates/shared_services/certs/terraform/appgateway.tf index 909b3cb784..69d2b08db3 100644 --- a/templates/shared_services/certs/terraform/appgateway.tf +++ b/templates/shared_services/certs/terraform/appgateway.tf @@ -26,8 +26,8 @@ resource "azurerm_application_gateway" "agw" { tags = local.tre_shared_service_tags sku { - name = "Standard_v2" - tier = "Standard_v2" + name = "Basic" + tier = "Basic" capacity = 1 } diff --git a/templates/shared_services/certs/terraform/certificate.tf b/templates/shared_services/certs/terraform/certificate.tf index 0a825c491d..2ceb183ab5 100644 --- a/templates/shared_services/certs/terraform/certificate.tf +++ b/templates/shared_services/certs/terraform/certificate.tf @@ -36,3 +36,18 @@ resource "azurerm_key_vault_certificate" "tlscert" { } } + +# pre-create in advance of the real password being created +# so if there is a deleted secret it will be recovered +# +resource "azurerm_key_vault_secret" "cert_password" { + name = local.password_name + value = "0000000000" + key_vault_id = data.azurerm_key_vault.key_vault.id + tags = local.tre_shared_service_tags + + # The password will get replaced with a real one, so we don't want Terraform to try and revert it. + lifecycle { + ignore_changes = all + } +} diff --git a/templates/shared_services/certs/terraform/data.tf b/templates/shared_services/certs/terraform/data.tf index bb577f11ae..db35778306 100644 --- a/templates/shared_services/certs/terraform/data.tf +++ b/templates/shared_services/certs/terraform/data.tf @@ -1,5 +1,3 @@ -data "azurerm_client_config" "current" {} - data "azurerm_resource_group" "rg" { name = "rg-${var.tre_id}" } @@ -19,3 +17,15 @@ data "azurerm_user_assigned_identity" "resource_processor_vmss_id" { name = "id-vmss-${var.tre_id}" resource_group_name = "rg-${var.tre_id}" } + +data "azurerm_user_assigned_identity" "tre_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.rg.name +} + +data "azurerm_key_vault_key" "encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} diff --git a/templates/shared_services/certs/terraform/locals.tf b/templates/shared_services/certs/terraform/locals.tf index 47cb8f6843..19aa23c554 100644 --- a/templates/shared_services/certs/terraform/locals.tf +++ b/templates/shared_services/certs/terraform/locals.tf @@ -23,4 +23,8 @@ locals { tre_id = var.tre_id tre_shared_service_id = var.tre_resource_id } + + cmk_name = "tre-encryption-${var.tre_id}" + encryption_identity_name = "id-encryption-${var.tre_id}" + password_name = "${var.cert_name}-password" } diff --git a/templates/shared_services/certs/terraform/main.tf b/templates/shared_services/certs/terraform/main.tf index 50ef865b7b..9f483e7251 100644 --- a/templates/shared_services/certs/terraform/main.tf +++ b/templates/shared_services/certs/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=4.17.0" } } diff --git a/templates/shared_services/certs/terraform/outputs.tf b/templates/shared_services/certs/terraform/outputs.tf index 882e91b2da..844163ebb0 100644 --- a/templates/shared_services/certs/terraform/outputs.tf +++ b/templates/shared_services/certs/terraform/outputs.tf @@ -17,3 +17,7 @@ output "resource_group_name" { output "keyvault_name" { value = data.azurerm_key_vault.key_vault.name } + +output "password_name" { + value = local.password_name +} diff --git a/templates/shared_services/certs/terraform/staticweb.tf b/templates/shared_services/certs/terraform/staticweb.tf index 49f20fd03c..7c044b6be2 100644 --- a/templates/shared_services/certs/terraform/staticweb.tf +++ b/templates/shared_services/certs/terraform/staticweb.tf @@ -6,7 +6,9 @@ resource "azurerm_storage_account" "staticweb" { account_kind = "StorageV2" account_tier = "Standard" account_replication_type = "LRS" - enable_https_traffic_only = true + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + https_traffic_only_enabled = true allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false tags = local.tre_shared_service_tags @@ -19,6 +21,22 @@ resource "azurerm_storage_account" "staticweb" { error_404_document = "404.html" } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.tre_encryption_identity[0].id] + } + } + + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = data.azurerm_key_vault_key.encryption_key[0].versionless_id + user_assigned_identity_id = data.azurerm_user_assigned_identity.tre_encryption_identity[0].id + } + } + lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } diff --git a/templates/shared_services/certs/terraform/upgrade.sh b/templates/shared_services/certs/terraform/upgrade.sh deleted file mode 100644 index aae4c58cef..0000000000 --- a/templates/shared_services/certs/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}-certs" diff --git a/templates/shared_services/certs/terraform/variables.tf b/templates/shared_services/certs/terraform/variables.tf index 4aff0698f5..228654d989 100644 --- a/templates/shared_services/certs/terraform/variables.tf +++ b/templates/shared_services/certs/terraform/variables.tf @@ -14,3 +14,12 @@ variable "tre_resource_id" { type = string description = "Resource ID" } + +variable "enable_cmk_encryption" { + type = bool + default = false +} + +variable "key_store_id" { + type = string +} diff --git a/templates/shared_services/cyclecloud/parameters.json b/templates/shared_services/cyclecloud/parameters.json index fe722bc241..c39909257d 100755 --- a/templates/shared_services/cyclecloud/parameters.json +++ b/templates/shared_services/cyclecloud/parameters.json @@ -45,6 +45,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/shared_services/cyclecloud/porter.yaml b/templates/shared_services/cyclecloud/porter.yaml index f8a2ae9272..8111768286 100644 --- a/templates/shared_services/cyclecloud/porter.yaml +++ b/templates/shared_services/cyclecloud/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-cyclecloud -version: 0.6.7 +version: 0.7.2 description: "An Azure TRE Shared Service Template for Azure Cyclecloud" registry: azuretre dockerfile: Dockerfile.tmpl @@ -46,6 +46,12 @@ parameters: env: ARM_ENVIRONMENT type: string default: "public" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: connection_uri @@ -74,6 +80,8 @@ install: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -92,6 +100,8 @@ upgrade: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -110,6 +120,8 @@ uninstall: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/shared_services/cyclecloud/terraform/.terraform.lock.hcl b/templates/shared_services/cyclecloud/terraform/.terraform.lock.hcl index 66c2832260..9cf5f7aba1 100644 --- a/templates/shared_services/cyclecloud/terraform/.terraform.lock.hcl +++ b/templates/shared_services/cyclecloud/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/shared_services/cyclecloud/terraform/data.tf b/templates/shared_services/cyclecloud/terraform/data.tf new file mode 100644 index 0000000000..6bc1dea20c --- /dev/null +++ b/templates/shared_services/cyclecloud/terraform/data.tf @@ -0,0 +1,11 @@ +data "azurerm_user_assigned_identity" "tre_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = local.core_resource_group_name +} + +data "azurerm_key_vault_key" "encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} diff --git a/templates/shared_services/cyclecloud/terraform/locals.tf b/templates/shared_services/cyclecloud/terraform/locals.tf index 5c3777bc06..9d75e9f53b 100644 --- a/templates/shared_services/cyclecloud/terraform/locals.tf +++ b/templates/shared_services/cyclecloud/terraform/locals.tf @@ -8,4 +8,6 @@ locals { tre_id = var.tre_id tre_shared_service_id = var.tre_resource_id } + cmk_name = "tre-encryption-${var.tre_id}" + encryption_identity_name = "id-encryption-${var.tre_id}" } diff --git a/templates/shared_services/cyclecloud/terraform/main.tf b/templates/shared_services/cyclecloud/terraform/main.tf index 7bd348a184..45850f34d7 100644 --- a/templates/shared_services/cyclecloud/terraform/main.tf +++ b/templates/shared_services/cyclecloud/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } random = { source = "hashicorp/random" diff --git a/templates/shared_services/cyclecloud/terraform/storage.tf b/templates/shared_services/cyclecloud/terraform/storage.tf index 4803993332..4b02f51d18 100644 --- a/templates/shared_services/cyclecloud/terraform/storage.tf +++ b/templates/shared_services/cyclecloud/terraform/storage.tf @@ -4,12 +4,30 @@ resource "azurerm_storage_account" "cyclecloud" { resource_group_name = data.azurerm_resource_group.rg.name account_tier = "Standard" account_replication_type = "GRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" cross_tenant_replication_enabled = false tags = local.tre_shared_service_tags # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.tre_encryption_identity[0].id] + } + } + + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = data.azurerm_key_vault_key.encryption_key[0].versionless_id + user_assigned_identity_id = data.azurerm_user_assigned_identity.tre_encryption_identity[0].id + } + } + lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } diff --git a/templates/shared_services/cyclecloud/terraform/upgrade.sh b/templates/shared_services/cyclecloud/terraform/upgrade.sh deleted file mode 100644 index 5368cb1a55..0000000000 --- a/templates/shared_services/cyclecloud/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TF_VAR_tre_resource_id}-${ID}" diff --git a/templates/shared_services/cyclecloud/terraform/variables.tf b/templates/shared_services/cyclecloud/terraform/variables.tf index 1c064dbb76..48a64a1945 100644 --- a/templates/shared_services/cyclecloud/terraform/variables.tf +++ b/templates/shared_services/cyclecloud/terraform/variables.tf @@ -6,4 +6,11 @@ variable "tre_resource_id" { } variable "arm_environment" { type = string -} \ No newline at end of file +} +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/shared_services/databricks-auth/porter.yaml b/templates/shared_services/databricks-auth/porter.yaml index d37fb67612..aed960f165 100644 --- a/templates/shared_services/databricks-auth/porter.yaml +++ b/templates/shared_services/databricks-auth/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-databricks-private-auth -version: 0.1.10 +version: 0.1.11 description: "An Azure TRE shared service for Azure Databricks authentication." registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/shared_services/databricks-auth/terraform/.terraform.lock.hcl b/templates/shared_services/databricks-auth/terraform/.terraform.lock.hcl index 57c968ed2c..deab8fe52c 100644 --- a/templates/shared_services/databricks-auth/terraform/.terraform.lock.hcl +++ b/templates/shared_services/databricks-auth/terraform/.terraform.lock.hcl @@ -20,21 +20,21 @@ provider "registry.terraform.io/databricks/databricks" { } provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/shared_services/databricks-auth/terraform/providers.tf b/templates/shared_services/databricks-auth/terraform/providers.tf index e9fc34979a..d034f8e2c9 100644 --- a/templates/shared_services/databricks-auth/terraform/providers.tf +++ b/templates/shared_services/databricks-auth/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } databricks = { source = "databricks/databricks" diff --git a/templates/shared_services/databricks-auth/terraform/upgrade.sh b/templates/shared_services/databricks-auth/terraform/upgrade.sh deleted file mode 100644 index 70b3863871..0000000000 --- a/templates/shared_services/databricks-auth/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=tre-workspace-service-gitea-${TF_VAR_id}" diff --git a/templates/shared_services/firewall/parameters.json b/templates/shared_services/firewall/parameters.json index 7883f0aa13..6a1df98da8 100755 --- a/templates/shared_services/firewall/parameters.json +++ b/templates/shared_services/firewall/parameters.json @@ -63,6 +63,12 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "firewall_force_tunnel_ip", + "source": { + "env": "FIREWALL_FORCE_TUNNEL_IP" + } } ] } diff --git a/templates/shared_services/firewall/porter.yaml b/templates/shared_services/firewall/porter.yaml index 9324023a37..19e820c4d7 100644 --- a/templates/shared_services/firewall/porter.yaml +++ b/templates/shared_services/firewall/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-firewall -version: 1.2.7 +version: 1.3.2 description: "An Azure TRE Firewall shared service" dockerfile: Dockerfile.tmpl registry: azuretre @@ -54,6 +54,9 @@ parameters: default: "graph.microsoft.com" - name: arm_environment type: string + - name: firewall_force_tunnel_ip + type: string + default: "" mixins: - terraform: @@ -69,6 +72,7 @@ install: api_driven_network_rule_collections_b64: ${ bundle.parameters.network_rule_collections } firewall_sku: ${ bundle.parameters.firewall_sku } microsoft_graph_fqdn: ${ bundle.parameters.microsoft_graph_fqdn } + firewall_force_tunnel_ip: ${ bundle.parameters.firewall_force_tunnel_ip } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -87,6 +91,7 @@ upgrade: api_driven_network_rule_collections_b64: ${ bundle.parameters.network_rule_collections } firewall_sku: ${ bundle.parameters.firewall_sku } microsoft_graph_fqdn: ${ bundle.parameters.microsoft_graph_fqdn } + firewall_force_tunnel_ip: ${ bundle.parameters.firewall_force_tunnel_ip } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -105,6 +110,7 @@ uninstall: api_driven_network_rule_collections_b64: ${ bundle.parameters.network_rule_collections } firewall_sku: ${ bundle.parameters.firewall_sku } microsoft_graph_fqdn: ${ bundle.parameters.microsoft_graph_fqdn } + firewall_force_tunnel_ip: ${ bundle.parameters.firewall_force_tunnel_ip } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/shared_services/firewall/template_schema.json b/templates/shared_services/firewall/template_schema.json index cd3aab13b3..23b0f8a526 100644 --- a/templates/shared_services/firewall/template_schema.json +++ b/templates/shared_services/firewall/template_schema.json @@ -6,111 +6,127 @@ "description": "Provides Firewall shared service", "required": [], "properties": { + "firewall_sku": { + "type": "string", + "title": "Firewall SKU", + "description": "The SKU that will be used when deploying The Firewall.", + "default": "Standard", + "enum": [ + "Basic", + "Standard", + "Premium" + ] + }, + "firewall_force_tunnel_ip": { + "type": "string", + "title": "Force Tunnel IP", + "description": "Optionally specify an IP address to forward all traffic to" + }, "rule_collections": { - "$id": "#properties/rule_collections", - "title": "application rule collections", - "type": "array", - "default": [], + "$id": "#properties/rule_collections", + "title": "application rule collections", + "type": "array", + "default": [], "updateable": true, - "items":{ - "title": "items", - "type": "object", - "required": [ - "name", - "rules" - ], - "properties": { - "name": { - "title": "name", - "type": "string", - "examples": [ - "my-rule" - ], - "pattern": "^.*$" - }, - "action": { - "title": "action DEPRECATED", - "type": "string", - "examples": [ - "Allow" - ], + "items": { + "title": "items", + "type": "object", + "required": [ + "name", + "rules" + ], + "properties": { + "name": { + "title": "name", + "type": "string", + "examples": [ + "my-rule" + ], + "pattern": "^.*$" + }, + "action": { + "title": "action DEPRECATED", + "type": "string", + "examples": [ + "Allow" + ], "enum": [ "Allow", "Deny" ] }, - "rules": { - "title": "rules", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "title": "name", - "type": "string", - "examples": [ - "rule 1" - ], - "pattern": "^.*$" - }, - "description": { - "title": "description", - "type": "string", - "default": "", - "examples": [ - "My rule description here" - ], - "pattern": "^.*$" - }, - "protocols": { - "title": "protocols", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "object", - "required": [ - "port", - "type" - ], - "properties": { - "port": { - "title": "port", - "type": "string", - "examples": [ - "1234" - ], - "pattern": "^.*$" - }, - "type": { - "title": "type", - "type": "string", + "rules": { + "title": "rules", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "name", + "type": "string", + "examples": [ + "rule 1" + ], + "pattern": "^.*$" + }, + "description": { + "title": "description", + "type": "string", + "default": "", + "examples": [ + "My rule description here" + ], + "pattern": "^.*$" + }, + "protocols": { + "title": "protocols", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "object", + "required": [ + "port", + "type" + ], + "properties": { + "port": { + "title": "port", + "type": "string", + "examples": [ + "1234" + ], + "pattern": "^.*$" + }, + "type": { + "title": "type", + "type": "string", "enum": [ "Http", "Https", "Mssql" ], - "examples": [ - "Http" - ] - } - } - } - }, + "examples": [ + "Http" + ] + } + } + } + }, "fqdn_tags": { - "title": "fqdn tags", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "enum":[ + "title": "fqdn tags", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "enum": [ "AppServiceEnvironment", "AzureBackup", "AzureKubernetesService", @@ -120,246 +136,246 @@ "WindowsUpdate", "WindowsVirtualDesktop" ], - "examples": [ - "AzureKubernetesService" - ] - } - }, - "target_fqdns": { - "title": "destination fqdns", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "one.two.three.com" - ] - } - }, - "source_addresses": { - "title": "source addresses", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "172.196.0.0" - ] - } - }, + "examples": [ + "AzureKubernetesService" + ] + } + }, + "target_fqdns": { + "title": "destination fqdns", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "one.two.three.com" + ] + } + }, + "source_addresses": { + "title": "source addresses", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "172.196.0.0" + ] + } + }, "source_ip_group_ids": { - "title": "source ip group ids", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "some_ip_group_id" - ] - } - }, + "title": "source ip group ids", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "some_ip_group_id" + ] + } + }, "source_ip_groups_in_core": { - "title": "source ip group names in core", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "ip_group_name_in_core_resource_group" - ] - } - } - } - } - } - } - } + "title": "source ip group names in core", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "ip_group_name_in_core_resource_group" + ] + } + } + } + } + } + } + } }, "network_rule_collections": { - "$id": "#properties/network_rule_collections", - "title": "network rule collections", - "type": "array", - "default": [], + "$id": "#properties/network_rule_collections", + "title": "network rule collections", + "type": "array", + "default": [], "updateable": true, - "items":{ - "title": "items", - "type": "object", - "required": [ - "name", - "rules" - ], - "properties": { - "name": { - "title": "name", - "type": "string", - "examples": [ - "my-rule" - ], - "pattern": "^.*$" - }, - "action": { - "title": "action DEPRECATED", - "type": "string", - "examples": [ - "Allow" - ], + "items": { + "title": "items", + "type": "object", + "required": [ + "name", + "rules" + ], + "properties": { + "name": { + "title": "name", + "type": "string", + "examples": [ + "my-rule" + ], + "pattern": "^.*$" + }, + "action": { + "title": "action DEPRECATED", + "type": "string", + "examples": [ + "Allow" + ], "enum": [ "Allow", "Deny" ] }, - "rules": { - "title": "rules", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "title": "name", - "type": "string", - "examples": [ - "rule 1" - ], - "pattern": "^.{5,80}$" - }, - "description": { - "title": "description DEPRECATED", - "type": "string", - "default": "", - "examples": [ - "My rule description here" - ], - "pattern": "^.*$" - }, - "source_addresses": { - "title": "source addresses", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "172.196.0.0" - ] - } - }, + "rules": { + "title": "rules", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "title": "name", + "type": "string", + "examples": [ + "rule 1" + ], + "pattern": "^.{5,80}$" + }, + "description": { + "title": "description DEPRECATED", + "type": "string", + "default": "", + "examples": [ + "My rule description here" + ], + "pattern": "^.*$" + }, + "source_addresses": { + "title": "source addresses", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "172.196.0.0" + ] + } + }, "source_ip_group_ids": { - "title": "source ip group ids", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "some_ip_group_id" - ] - } - }, + "title": "source ip group ids", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "some_ip_group_id" + ] + } + }, "source_ip_groups_in_core": { - "title": "source ip group names in core", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "some_ip_group_name" - ] - } - }, - "destination_addresses": { - "title": "destination addresses", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "172.196.0.0" - ] - } - }, + "title": "source ip group names in core", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "some_ip_group_name" + ] + } + }, + "destination_addresses": { + "title": "destination addresses", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "172.196.0.0" + ] + } + }, "destination_ip_group_ids": { - "title": "destination ip group ids", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "some_ip_group_id" - ] - } - }, + "title": "destination ip group ids", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "some_ip_group_id" + ] + } + }, "destination_fqdns": { - "title": "destination fqdns", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "one.two.three.com" - ] - } - }, + "title": "destination fqdns", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "one.two.three.com" + ] + } + }, "destination_ports": { - "title": "destination ports", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", - "default": "", - "examples": [ - "80", + "title": "destination ports", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", + "default": "", + "examples": [ + "80", "443", "*" - ] - } - }, - "protocols": { - "title": "protocols", - "type": "array", - "default": [], - "items":{ - "title": "items", - "type": "string", + ] + } + }, + "protocols": { + "title": "protocols", + "type": "array", + "default": [], + "items": { + "title": "items", + "type": "string", "enum": [ "Any", "ICMP", "TCP", "UDP" ], - "examples": [ - "TCP" - ] - } - } - } - } - } - } - } + "examples": [ + "TCP" + ] + } + } + } + } + } + } + } } } } diff --git a/templates/shared_services/firewall/terraform/.terraform.lock.hcl b/templates/shared_services/firewall/terraform/.terraform.lock.hcl index b76fcebd94..6e8b87ea6b 100644 --- a/templates/shared_services/firewall/terraform/.terraform.lock.hcl +++ b/templates/shared_services/firewall/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/shared_services/firewall/terraform/firewall.tf b/templates/shared_services/firewall/terraform/firewall.tf index ae94aecff0..6f4dedc816 100644 --- a/templates/shared_services/firewall/terraform/firewall.tf +++ b/templates/shared_services/firewall/terraform/firewall.tf @@ -1,4 +1,5 @@ resource "azurerm_public_ip" "fwtransit" { + count = var.firewall_force_tunnel_ip != "" ? 0 : 1 name = "pip-fw-${var.tre_id}" resource_group_name = local.core_resource_group_name location = data.azurerm_resource_group.rg.location @@ -10,12 +11,12 @@ resource "azurerm_public_ip" "fwtransit" { } moved { - from = azurerm_public_ip.fwpip - to = azurerm_public_ip.fwtransit + from = azurerm_public_ip.fwtransit + to = azurerm_public_ip.fwtransit[0] } resource "azurerm_public_ip" "fwmanagement" { - count = local.effective_firewall_sku == "Basic" ? 1 : 0 + count = (var.firewall_force_tunnel_ip != "" || local.effective_firewall_sku == "Basic") ? 1 : 0 name = "pip-fw-management-${var.tre_id}" resource_group_name = local.core_resource_group_name location = data.azurerm_resource_group.rg.location @@ -38,11 +39,11 @@ resource "azurerm_firewall" "fw" { ip_configuration { name = "fw-ip-configuration" subnet_id = data.azurerm_subnet.firewall.id - public_ip_address_id = azurerm_public_ip.fwtransit.id + public_ip_address_id = var.firewall_force_tunnel_ip != "" ? null : azurerm_public_ip.fwtransit[0].id } dynamic "management_ip_configuration" { - for_each = local.effective_firewall_sku == "Basic" ? [1] : [] + for_each = (var.firewall_force_tunnel_ip != "" || local.effective_firewall_sku == "Basic") ? [1] : [] content { name = "mgmtconfig" subnet_id = data.azurerm_subnet.firewall_management.id diff --git a/templates/shared_services/firewall/terraform/providers.tf b/templates/shared_services/firewall/terraform/providers.tf index 50ef865b7b..001ccd9042 100644 --- a/templates/shared_services/firewall/terraform/providers.tf +++ b/templates/shared_services/firewall/terraform/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } } diff --git a/templates/shared_services/firewall/terraform/routetable.tf b/templates/shared_services/firewall/terraform/routetable.tf index f0e4388d9b..23202788eb 100644 --- a/templates/shared_services/firewall/terraform/routetable.tf +++ b/templates/shared_services/firewall/terraform/routetable.tf @@ -87,3 +87,28 @@ resource "azurerm_subnet_route_table_association" "rt_airlock_events_subnet_asso azurerm_firewall_policy_rule_collection_group.dynamic_application ] } + +resource "azurerm_route_table" "fw_tunnel_rt" { + count = var.firewall_force_tunnel_ip != "" ? 1 : 0 + name = "rt-fw-tunnel-${var.tre_id}" + resource_group_name = local.core_resource_group_name + location = data.azurerm_resource_group.rg.location + bgp_route_propagation_enabled = true + tags = local.tre_shared_service_tags + + lifecycle { ignore_changes = [tags] } + + route { + name = "ForceTunnelRoute" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = var.firewall_force_tunnel_ip + } +} + +resource "azurerm_subnet_route_table_association" "rt_fw_tunnel_subnet_association" { + count = var.firewall_force_tunnel_ip != "" ? 1 : 0 + subnet_id = data.azurerm_subnet.firewall.id + route_table_id = azurerm_route_table.fw_tunnel_rt[0].id +} + diff --git a/templates/shared_services/firewall/terraform/upgrade.sh b/templates/shared_services/firewall/terraform/upgrade.sh deleted file mode 100755 index d690d433a7..0000000000 --- a/templates/shared_services/firewall/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}-shared-service-firewall" diff --git a/templates/shared_services/firewall/terraform/variables.tf b/templates/shared_services/firewall/terraform/variables.tf index a1017e157f..1140c25f40 100644 --- a/templates/shared_services/firewall/terraform/variables.tf +++ b/templates/shared_services/firewall/terraform/variables.tf @@ -27,3 +27,8 @@ variable "firewall_sku" { type = string default = "" } + +variable "firewall_force_tunnel_ip" { + type = string + default = "" +} diff --git a/templates/shared_services/gitea/porter.yaml b/templates/shared_services/gitea/porter.yaml index 2eb1a45c80..8b7eaaebc0 100644 --- a/templates/shared_services/gitea/porter.yaml +++ b/templates/shared_services/gitea/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-gitea -version: 1.1.1 +version: 1.1.5 description: "A Gitea shared service" dockerfile: Dockerfile.tmpl registry: azuretre diff --git a/templates/shared_services/gitea/terraform/.terraform.lock.hcl b/templates/shared_services/gitea/terraform/.terraform.lock.hcl index 18dd46c782..410b9232d0 100644 --- a/templates/shared_services/gitea/terraform/.terraform.lock.hcl +++ b/templates/shared_services/gitea/terraform/.terraform.lock.hcl @@ -2,22 +2,22 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "4.14.0" + constraints = "4.14.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:FYZ9qh8i3X2gDmUTe1jJ/VzdSyjGjVmhBzv2R8D6CBo=", + "zh:05aaea16fc5f27b14d9fbad81654edf0638949ed3585576b2219c76a2bee095a", + "zh:065ce6ed16ba3fa7efcf77888ea582aead54e6a28f184c6701b73d71edd64bb0", + "zh:3c0cd17c249d18aa2e0120acb5f0c14810725158b379a67fec1331110e7c50df", + "zh:5a3ba3ffb2f1ce519fe3bf84a7296aa5862c437c70c62f0b0a5293bea9f2d01c", + "zh:7a8e9d72fa2714f4d567270b1761d4b4e788de7c15dada7db0cf0e29933185a2", + "zh:a11e190073f31c1238c15af29b9162e0f4564f6b0cd0310a3fa94102738450dc", + "zh:a5c004114410cc6dcb8fed584c9f3b84283b58025b0073a7e88d2bdb27840dfa", + "zh:a674a41db118e244eda7591e455d2ec338626664e0856e4125e909eb038f78db", + "zh:b5139010e4cbb2cb1a27c775610593c1c8063d3a7c82b00a65006509c434df2f", + "zh:cbb031223ccd8b099ac4d19b92641142f330b90f2fc6452843e445bae28f832c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f7e7db1b94082a4ac3d4af3dabe7bbd335e1679305bf8e29d011f0ee440724ca", ] } diff --git a/templates/shared_services/gitea/terraform/gitea-webapp.tf b/templates/shared_services/gitea/terraform/gitea-webapp.tf index 26d8a62527..5cbf0c462f 100644 --- a/templates/shared_services/gitea/terraform/gitea-webapp.tf +++ b/templates/shared_services/gitea/terraform/gitea-webapp.tf @@ -18,14 +18,16 @@ resource "azurerm_user_assigned_identity" "gitea_id" { } resource "azurerm_linux_web_app" "gitea" { - name = local.webapp_name - resource_group_name = local.core_resource_group_name - location = data.azurerm_resource_group.rg.location - service_plan_id = data.azurerm_service_plan.core.id - https_only = true - key_vault_reference_identity_id = azurerm_user_assigned_identity.gitea_id.id - virtual_network_subnet_id = data.azurerm_subnet.web_app.id - tags = local.tre_shared_service_tags + name = local.webapp_name + resource_group_name = local.core_resource_group_name + location = data.azurerm_resource_group.rg.location + service_plan_id = data.azurerm_service_plan.core.id + https_only = true + key_vault_reference_identity_id = azurerm_user_assigned_identity.gitea_id.id + virtual_network_subnet_id = data.azurerm_subnet.web_app.id + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + tags = local.tre_shared_service_tags app_settings = { WEBSITES_PORT = "3000" @@ -64,12 +66,12 @@ resource "azurerm_linux_web_app" "gitea" { container_registry_managed_identity_client_id = azurerm_user_assigned_identity.gitea_id.client_id ftps_state = "Disabled" always_on = true - minimum_tls_version = "1.2" + minimum_tls_version = "1.3" vnet_route_all_enabled = true application_stack { - docker_image = "${data.azurerm_container_registry.mgmt_acr.login_server}/microsoft/azuretre/gitea" - docker_image_tag = local.version + docker_registry_url = "https://${data.azurerm_container_registry.mgmt_acr.login_server}" + docker_image_name = "microsoft/azuretre/gitea:${local.version}" } } @@ -127,11 +129,14 @@ resource "azurerm_monitor_diagnostic_setting" "webapp_gitea" { target_resource_id = azurerm_linux_web_app.gitea.id log_analytics_workspace_id = data.azurerm_log_analytics_workspace.tre.id - dynamic "log" { - for_each = data.azurerm_monitor_diagnostic_categories.webapp.log_category_types + dynamic "enabled_log" { + for_each = [ + for category in data.azurerm_monitor_diagnostic_categories.webapp.log_category_types : + category if contains(local.webapp_diagnostic_categories_enabled, category) + ] content { - category = log.value - enabled = contains(local.webapp_diagnostic_categories_enabled, log.value) ? true : false + category = enabled_log.value + } } diff --git a/templates/shared_services/gitea/terraform/main.tf b/templates/shared_services/gitea/terraform/main.tf index 555fd8aca5..61aca317c0 100644 --- a/templates/shared_services/gitea/terraform/main.tf +++ b/templates/shared_services/gitea/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=4.14.0" } local = { source = "hashicorp/local" diff --git a/templates/shared_services/gitea/terraform/upgrade.sh b/templates/shared_services/gitea/terraform/upgrade.sh deleted file mode 100644 index 6618fcb445..0000000000 --- a/templates/shared_services/gitea/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}-gitea" diff --git a/templates/shared_services/sonatype-nexus-vm/parameters.json b/templates/shared_services/sonatype-nexus-vm/parameters.json old mode 100755 new mode 100644 index 0e7c0c4e58..41cf3bf32a --- a/templates/shared_services/sonatype-nexus-vm/parameters.json +++ b/templates/shared_services/sonatype-nexus-vm/parameters.json @@ -51,6 +51,24 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } + }, + { + "name": "vm_size", + "source": { + "env": "VM_SIZE" + } } ] } diff --git a/templates/shared_services/sonatype-nexus-vm/porter.yaml b/templates/shared_services/sonatype-nexus-vm/porter.yaml index 5f62ec6321..526b8e5d6e 100644 --- a/templates/shared_services/sonatype-nexus-vm/porter.yaml +++ b/templates/shared_services/sonatype-nexus-vm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-sonatype-nexus -version: 3.2.2 +version: 3.3.2 description: "A Sonatype Nexus shared service" dockerfile: Dockerfile.tmpl registry: azuretre @@ -47,6 +47,16 @@ parameters: type: string default: "nexus-ssl" description: "Name of the certificate for configuring Nexus SSL with (stored in the core KeyVault)" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" + - name: vm_size + type: string + default: "Standard_B2ms" + description: "The size of the VM to be deployed" outputs: - name: workspace_vm_allowed_fqdns_list type: string @@ -85,6 +95,9 @@ install: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } ssl_cert_name: ${ bundle.parameters.ssl_cert_name } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } + vm_size: ${ bundle.parameters.vm_size } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -106,6 +119,9 @@ upgrade: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } ssl_cert_name: ${ bundle.parameters.ssl_cert_name } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } + vm_size: ${ bundle.parameters.vm_size } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -127,6 +143,9 @@ uninstall: tre_id: ${ bundle.parameters.tre_id } tre_resource_id: ${ bundle.parameters.id } ssl_cert_name: ${ bundle.parameters.ssl_cert_name } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } + vm_size: ${ bundle.parameters.vm_size } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/shared_services/sonatype-nexus-vm/template_schema.json b/templates/shared_services/sonatype-nexus-vm/template_schema.json index a63d7fefcd..9c6f0ff9b1 100644 --- a/templates/shared_services/sonatype-nexus-vm/template_schema.json +++ b/templates/shared_services/sonatype-nexus-vm/template_schema.json @@ -41,6 +41,19 @@ "title": "Expose externally", "description": "Is the Sonatype Nexus accessible from outside of the TRE network.", "default": false + }, + "vm_size": { + "type": "string", + "title": "VM Size", + "description": "The size of the VM to be deployed", + "default": "Standard_B2ms", + "enum": [ + "Standard_B2ms", + "Standard_B4ms", + "Standard_D2s_v3", + "Standard_D4s_v3", + "Standard_D2ads_v5" + ] } }, "uiSchema": { diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/.terraform.lock.hcl b/templates/shared_services/sonatype-nexus-vm/terraform/.terraform.lock.hcl index 819c124189..2b56c48f1c 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/.terraform.lock.hcl +++ b/templates/shared_services/sonatype-nexus-vm/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/data.tf b/templates/shared_services/sonatype-nexus-vm/terraform/data.tf index ad0ed71585..37ccf7d9b6 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/data.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/data.tf @@ -38,3 +38,14 @@ data "azurerm_private_dns_zone" "nexus" { resource_group_name = local.core_resource_group_name } +data "azurerm_key_vault_key" "tre_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "tre_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = local.core_resource_group_name +} diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf b/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf index 85bb6d34f6..be9698931f 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf @@ -10,4 +10,6 @@ locals { tre_id = var.tre_id tre_shared_service_id = var.tre_resource_id } + cmk_name = "tre-encryption-${var.tre_id}" + encryption_identity_name = "id-encryption-${var.tre_id}" } diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/main.tf b/templates/shared_services/sonatype-nexus-vm/terraform/main.tf index b4f87358f8..f233fd63b5 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/main.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } random = { source = "hashicorp/random" diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/upgrade.sh b/templates/shared_services/sonatype-nexus-vm/terraform/upgrade.sh deleted file mode 100644 index f15b4a2ee6..0000000000 --- a/templates/shared_services/sonatype-nexus-vm/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID:?}-shared-service-sonatype-nexus" diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/variables.tf b/templates/shared_services/sonatype-nexus-vm/terraform/variables.tf index 23c2fa3826..7212edfb15 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/variables.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/variables.tf @@ -10,3 +10,18 @@ variable "tre_resource_id" { variable "ssl_cert_name" { type = string } + +variable "enable_cmk_encryption" { + type = bool + default = false +} + +variable "key_store_id" { + type = string +} + +variable "vm_size" { + type = string + description = "The size of the VM to be deployed" + default = "Standard_B2ms" +} diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf b/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf index cd27bd2c80..224143937a 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf @@ -98,15 +98,21 @@ resource "azurerm_linux_virtual_machine" "nexus" { resource_group_name = local.core_resource_group_name location = data.azurerm_resource_group.rg.location network_interface_ids = [azurerm_network_interface.nexus.id] - size = "Standard_D2s_v3" + size = var.vm_size disable_password_authentication = false admin_username = "adminuser" admin_password = random_password.nexus_vm_password.result tags = local.tre_shared_service_tags + encryption_at_host_enabled = true + secure_boot_enabled = true + vtpm_enabled = true custom_data = data.template_cloudinit_config.nexus_config.rendered - lifecycle { ignore_changes = [tags] } + # ignore changes to secure_boot_enabled and vtpm_enabled as these are destructive + # (may be allowed once https://github.com/hashicorp/terraform-provider-azurerm/issues/25808 is fixed) + # + lifecycle { ignore_changes = [tags, secure_boot_enabled, vtpm_enabled] } source_image_reference { publisher = "Canonical" @@ -116,10 +122,11 @@ resource "azurerm_linux_virtual_machine" "nexus" { } os_disk { - name = "osdisk-nexus-${var.tre_id}" - caching = "ReadWrite" - storage_account_type = "Standard_LRS" - disk_size_gb = 64 + name = "osdisk-nexus-${var.tre_id}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + disk_size_gb = 64 + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.nexus_disk_encryption[0].id : null } identity { @@ -145,6 +152,21 @@ resource "azurerm_linux_virtual_machine" "nexus" { } } +resource "azurerm_disk_encryption_set" "nexus_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "disk-encryption-nexus-${var.tre_id}-${var.tre_resource_id}" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name + key_vault_key_id = data.azurerm_key_vault_key.tre_encryption_key[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.tre_encryption_identity[0].id] + } +} + data "template_cloudinit_config" "nexus_config" { gzip = true base64_encode = true diff --git a/templates/workspace_services/azureml/parameters.json b/templates/workspace_services/azureml/parameters.json index e538071afc..db593f4b28 100755 --- a/templates/workspace_services/azureml/parameters.json +++ b/templates/workspace_services/azureml/parameters.json @@ -69,6 +69,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspace_services/azureml/porter.yaml b/templates/workspace_services/azureml/porter.yaml index ca607547b3..24f5488f33 100644 --- a/templates/workspace_services/azureml/porter.yaml +++ b/templates/workspace_services/azureml/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-azureml -version: 0.8.16 +version: 0.9.2 description: "An Azure TRE service for Azure Machine Learning" registry: azuretre dockerfile: Dockerfile.tmpl @@ -63,6 +63,12 @@ parameters: env: ARM_ENVIRONMENT - name: azure_environment env: AZURE_ENVIRONMENT + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: azureml_workspace_name @@ -139,6 +145,8 @@ install: auth_tenant_id: ${ bundle.credentials.auth_tenant_id } arm_environment: ${ bundle.parameters.arm_environment } azure_environment: ${ bundle.parameters.azure_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -175,6 +183,8 @@ upgrade: auth_tenant_id: ${ bundle.credentials.auth_tenant_id } arm_environment: ${ bundle.parameters.arm_environment } azure_environment: ${ bundle.parameters.azure_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -211,6 +221,8 @@ uninstall: auth_tenant_id: ${ bundle.credentials.auth_tenant_id } arm_environment: ${ bundle.parameters.arm_environment } azure_environment: ${ bundle.parameters.azure_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspace_services/azureml/terraform/.terraform.lock.hcl b/templates/workspace_services/azureml/terraform/.terraform.lock.hcl index 973c2cf117..d18c22de62 100644 --- a/templates/workspace_services/azureml/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/azureml/terraform/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/azure/azapi" { version = "1.15.0" constraints = "1.15.0" hashes = [ + "h1:Y7ruMuPh8UJRTRl4rm+cdpGtmURx2taqiuqfYaH3o48=", "h1:gIOgxVmFSxHrR+XOzgUEA+ybOmp8kxZlZH3eYeB/eFI=", "zh:0627a8bc77254debc25dc0c7b62e055138217c97b03221e593c3c56dc7550671", "zh:2fe045f07070ef75d0bec4b0595a74c14394daa838ddb964e2fd23cc98c40c34", @@ -22,21 +23,21 @@ provider "registry.terraform.io/azure/azapi" { } provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/azureml/terraform/acr.tf b/templates/workspace_services/azureml/terraform/acr.tf index a0de2fca71..7fc9295e9a 100644 --- a/templates/workspace_services/azureml/terraform/acr.tf +++ b/templates/workspace_services/azureml/terraform/acr.tf @@ -9,6 +9,24 @@ resource "azurerm_container_registry" "acr" { public_network_access_enabled = false tags = local.tre_workspace_service_tags + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } + } + + dynamic "encryption" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + enabled = true + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].id + identity_client_id = data.azurerm_user_assigned_identity.ws_encryption_identity[0].client_id + } + + } + lifecycle { ignore_changes = [tags] } } diff --git a/templates/workspace_services/azureml/terraform/data.tf b/templates/workspace_services/azureml/terraform/data.tf index 0612a9d634..d9db7e09b9 100644 --- a/templates/workspace_services/azureml/terraform/data.tf +++ b/templates/workspace_services/azureml/terraform/data.tf @@ -47,3 +47,15 @@ data "azurerm_private_dns_zone" "notebooks" { name = module.terraform_azurerm_environment_configuration.private_links["privatelink.notebooks.azure.net"] resource_group_name = local.core_resource_group_name } + +data "azurerm_key_vault_key" "ws_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "ws_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.ws.name +} diff --git a/templates/workspace_services/azureml/terraform/locals.tf b/templates/workspace_services/azureml/terraform/locals.tf index ac11d6c921..2ed4ce0787 100644 --- a/templates/workspace_services/azureml/terraform/locals.tf +++ b/templates/workspace_services/azureml/terraform/locals.tf @@ -14,4 +14,6 @@ locals { tre_workspace_id = var.workspace_id tre_workspace_service_id = var.tre_resource_id } + cmk_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspace_services/azureml/terraform/main.tf b/templates/workspace_services/azureml/terraform/main.tf index d0137fdd49..da6996243f 100644 --- a/templates/workspace_services/azureml/terraform/main.tf +++ b/templates/workspace_services/azureml/terraform/main.tf @@ -20,7 +20,7 @@ resource "azurerm_machine_learning_workspace" "aml_workspace" { ignore_changes = [ tags, image_build_compute_name, - public_access_behind_virtual_network_enabled + public_network_access_enabled ] } diff --git a/templates/workspace_services/azureml/terraform/network.tf b/templates/workspace_services/azureml/terraform/network.tf index edc2529942..b82ed52cc4 100644 --- a/templates/workspace_services/azureml/terraform/network.tf +++ b/templates/workspace_services/azureml/terraform/network.tf @@ -67,7 +67,7 @@ resource "azurerm_subnet" "aml" { address_prefixes = [var.address_space] # need to be disabled for AML private compute - private_endpoint_network_policies_enabled = false + private_endpoint_network_policies = "Disabled" private_link_service_network_policies_enabled = false service_endpoints = [ diff --git a/templates/workspace_services/azureml/terraform/outputs.tf b/templates/workspace_services/azureml/terraform/outputs.tf index 48a152634f..6f75c6547d 100644 --- a/templates/workspace_services/azureml/terraform/outputs.tf +++ b/templates/workspace_services/azureml/terraform/outputs.tf @@ -45,13 +45,13 @@ data "azurerm_network_service_tags" "batch_tag" { } output "storage_tag" { - value = data.azurerm_network_service_tags.storage_tag.id + value = data.azurerm_network_service_tags.storage_tag.name } output "mcr_tag" { - value = data.azurerm_network_service_tags.mcr_tag.id + value = data.azurerm_network_service_tags.mcr_tag.name } output "batch_tag" { - value = data.azurerm_network_service_tags.batch_tag.id + value = data.azurerm_network_service_tags.batch_tag.name } diff --git a/templates/workspace_services/azureml/terraform/providers.tf b/templates/workspace_services/azureml/terraform/providers.tf index 601018cc11..f243be5378 100644 --- a/templates/workspace_services/azureml/terraform/providers.tf +++ b/templates/workspace_services/azureml/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } azapi = { source = "Azure/azapi" diff --git a/templates/workspace_services/azureml/terraform/storage.tf b/templates/workspace_services/azureml/terraform/storage.tf index 2a5966beb8..42ba38b5d6 100644 --- a/templates/workspace_services/azureml/terraform/storage.tf +++ b/templates/workspace_services/azureml/terraform/storage.tf @@ -4,12 +4,30 @@ resource "azurerm_storage_account" "aml" { resource_group_name = data.azurerm_resource_group.ws.name account_tier = "Standard" account_replication_type = "GRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" cross_tenant_replication_enabled = false tags = local.tre_workspace_service_tags network_rules { default_action = "Deny" } + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } + } + + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].versionless_id + user_assigned_identity_id = data.azurerm_user_assigned_identity.ws_encryption_identity[0].id + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -49,7 +67,6 @@ resource "azurerm_private_endpoint" "blobpe" { } - resource "azurerm_private_endpoint" "filepe" { name = "pe-file-${local.storage_name}" location = data.azurerm_resource_group.ws.location diff --git a/templates/workspace_services/azureml/terraform/variables.tf b/templates/workspace_services/azureml/terraform/variables.tf index a47b5588ff..ef994567b2 100644 --- a/templates/workspace_services/azureml/terraform/variables.tf +++ b/templates/workspace_services/azureml/terraform/variables.tf @@ -35,11 +35,16 @@ variable "auth_client_secret" { sensitive = true description = "Used to authenticate into the AAD Tenant to get app role members" } - variable "arm_environment" { type = string } - variable "azure_environment" { type = string } +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml b/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml index a70844a77f..caaa71484b 100644 --- a/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml +++ b/templates/workspace_services/azureml/user_resources/aml_compute/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-user-resource-aml-compute-instance -version: 0.5.10 +version: 0.5.11 description: "Azure Machine Learning Compute Instance" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/azureml/user_resources/aml_compute/terraform/.terraform.lock.hcl b/templates/workspace_services/azureml/user_resources/aml_compute/terraform/.terraform.lock.hcl index a1b1878440..7bbe2a152f 100644 --- a/templates/workspace_services/azureml/user_resources/aml_compute/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/azureml/user_resources/aml_compute/terraform/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/azure/azapi" { version = "1.15.0" constraints = "1.15.0" hashes = [ + "h1:Y7ruMuPh8UJRTRl4rm+cdpGtmURx2taqiuqfYaH3o48=", "h1:gIOgxVmFSxHrR+XOzgUEA+ybOmp8kxZlZH3eYeB/eFI=", "zh:0627a8bc77254debc25dc0c7b62e055138217c97b03221e593c3c56dc7550671", "zh:2fe045f07070ef75d0bec4b0595a74c14394daa838ddb964e2fd23cc98c40c34", @@ -22,21 +23,21 @@ provider "registry.terraform.io/azure/azapi" { } provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/azureml/user_resources/aml_compute/terraform/providers.tf b/templates/workspace_services/azureml/user_resources/aml_compute/terraform/providers.tf index d1938968e6..37b00fce2e 100644 --- a/templates/workspace_services/azureml/user_resources/aml_compute/terraform/providers.tf +++ b/templates/workspace_services/azureml/user_resources/aml_compute/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } azapi = { source = "Azure/azapi" diff --git a/templates/workspace_services/azuresql/porter.yaml b/templates/workspace_services/azuresql/porter.yaml index 5a71843341..daa351d040 100644 --- a/templates/workspace_services/azuresql/porter.yaml +++ b/templates/workspace_services/azuresql/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-service-azuresql -version: 1.0.14 +version: 1.0.15 description: "An Azure SQL workspace service" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl b/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl index 0e171bd3f0..447b1bc5ed 100644 --- a/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/azuresql/terraform/main.tf b/templates/workspace_services/azuresql/terraform/main.tf index a16579000b..2fc73695ef 100644 --- a/templates/workspace_services/azuresql/terraform/main.tf +++ b/templates/workspace_services/azuresql/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.112.0" + version = "3.117.0" } random = { source = "hashicorp/random" diff --git a/templates/workspace_services/databricks/porter.yaml b/templates/workspace_services/databricks/porter.yaml index d499dade92..ba0d1a8097 100644 --- a/templates/workspace_services/databricks/porter.yaml +++ b/templates/workspace_services/databricks/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-databricks -version: 1.0.9 +version: 1.0.10 description: "An Azure TRE service for Azure Databricks." registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/databricks/terraform/.terraform.lock.hcl b/templates/workspace_services/databricks/terraform/.terraform.lock.hcl index a8a85f5416..9d204da231 100644 --- a/templates/workspace_services/databricks/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/databricks/terraform/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/azure/azapi" { version = "1.15.0" constraints = "1.15.0" hashes = [ + "h1:Y7ruMuPh8UJRTRl4rm+cdpGtmURx2taqiuqfYaH3o48=", "h1:gIOgxVmFSxHrR+XOzgUEA+ybOmp8kxZlZH3eYeB/eFI=", "zh:0627a8bc77254debc25dc0c7b62e055138217c97b03221e593c3c56dc7550671", "zh:2fe045f07070ef75d0bec4b0595a74c14394daa838ddb964e2fd23cc98c40c34", @@ -36,21 +37,21 @@ provider "registry.terraform.io/databricks/databricks" { } provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/databricks/terraform/providers.tf b/templates/workspace_services/databricks/terraform/providers.tf index 1a473aeb2a..0aea19c8b9 100644 --- a/templates/workspace_services/databricks/terraform/providers.tf +++ b/templates/workspace_services/databricks/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } azapi = { source = "Azure/azapi" diff --git a/templates/workspace_services/databricks/terraform/upgrade.sh b/templates/workspace_services/databricks/terraform/upgrade.sh deleted file mode 100644 index 70b3863871..0000000000 --- a/templates/workspace_services/databricks/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=tre-workspace-service-gitea-${TF_VAR_id}" diff --git a/templates/workspace_services/gitea/parameters.json b/templates/workspace_services/gitea/parameters.json index 811e0a5f3b..b35183879d 100755 --- a/templates/workspace_services/gitea/parameters.json +++ b/templates/workspace_services/gitea/parameters.json @@ -69,6 +69,18 @@ "source": { "env": "SQL_SKU" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspace_services/gitea/porter.yaml b/templates/workspace_services/gitea/porter.yaml index 6f756cb438..b15bd80a9e 100644 --- a/templates/workspace_services/gitea/porter.yaml +++ b/templates/workspace_services/gitea/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-service-gitea -version: 1.1.1 +version: 1.2.3 description: "A Gitea workspace service" dockerfile: Dockerfile.tmpl registry: azuretre @@ -66,6 +66,12 @@ parameters: - name: aad_authority_url type: string default: "https://login.microsoftonline.com" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" mixins: - exec @@ -106,6 +112,8 @@ install: aad_authority_url: ${ bundle.parameters.aad_authority_url } arm_environment: ${ bundle.parameters.arm_environment } sql_sku: ${ bundle.parameters.sql_sku } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -131,6 +139,8 @@ upgrade: aad_authority_url: ${ bundle.parameters.aad_authority_url } arm_environment: ${ bundle.parameters.arm_environment } sql_sku: ${ bundle.parameters.sql_sku } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -156,6 +166,8 @@ uninstall: aad_authority_url: ${ bundle.parameters.aad_authority_url } arm_environment: ${ bundle.parameters.arm_environment } sql_sku: ${ bundle.parameters.sql_sku } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspace_services/gitea/terraform/.terraform.lock.hcl b/templates/workspace_services/gitea/terraform/.terraform.lock.hcl index 360080ee10..828f6f3414 100644 --- a/templates/workspace_services/gitea/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/gitea/terraform/.terraform.lock.hcl @@ -2,22 +2,22 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "4.14.0" + constraints = "4.14.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:FYZ9qh8i3X2gDmUTe1jJ/VzdSyjGjVmhBzv2R8D6CBo=", + "zh:05aaea16fc5f27b14d9fbad81654edf0638949ed3585576b2219c76a2bee095a", + "zh:065ce6ed16ba3fa7efcf77888ea582aead54e6a28f184c6701b73d71edd64bb0", + "zh:3c0cd17c249d18aa2e0120acb5f0c14810725158b379a67fec1331110e7c50df", + "zh:5a3ba3ffb2f1ce519fe3bf84a7296aa5862c437c70c62f0b0a5293bea9f2d01c", + "zh:7a8e9d72fa2714f4d567270b1761d4b4e788de7c15dada7db0cf0e29933185a2", + "zh:a11e190073f31c1238c15af29b9162e0f4564f6b0cd0310a3fa94102738450dc", + "zh:a5c004114410cc6dcb8fed584c9f3b84283b58025b0073a7e88d2bdb27840dfa", + "zh:a674a41db118e244eda7591e455d2ec338626664e0856e4125e909eb038f78db", + "zh:b5139010e4cbb2cb1a27c775610593c1c8063d3a7c82b00a65006509c434df2f", + "zh:cbb031223ccd8b099ac4d19b92641142f330b90f2fc6452843e445bae28f832c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f7e7db1b94082a4ac3d4af3dabe7bbd335e1679305bf8e29d011f0ee440724ca", ] } diff --git a/templates/workspace_services/gitea/terraform/data.tf b/templates/workspace_services/gitea/terraform/data.tf index 883091089d..eb24688f2a 100644 --- a/templates/workspace_services/gitea/terraform/data.tf +++ b/templates/workspace_services/gitea/terraform/data.tf @@ -74,3 +74,15 @@ data "azurerm_monitor_diagnostic_categories" "gitea" { azurerm_linux_web_app.gitea, ] } + +data "azurerm_user_assigned_identity" "ws_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_key_vault_key" "ws_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} diff --git a/templates/workspace_services/gitea/terraform/gitea-webapp.tf b/templates/workspace_services/gitea/terraform/gitea-webapp.tf index 0e25df6c41..10602796d3 100644 --- a/templates/workspace_services/gitea/terraform/gitea-webapp.tf +++ b/templates/workspace_services/gitea/terraform/gitea-webapp.tf @@ -23,14 +23,16 @@ data "azurerm_service_plan" "workspace" { } resource "azurerm_linux_web_app" "gitea" { - name = local.webapp_name - location = data.azurerm_resource_group.ws.location - resource_group_name = data.azurerm_resource_group.ws.name - service_plan_id = data.azurerm_service_plan.workspace.id - https_only = true - key_vault_reference_identity_id = azurerm_user_assigned_identity.gitea_id.id - virtual_network_subnet_id = data.azurerm_subnet.web_apps.id - tags = local.workspace_service_tags + name = local.webapp_name + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + service_plan_id = data.azurerm_service_plan.workspace.id + https_only = true + key_vault_reference_identity_id = azurerm_user_assigned_identity.gitea_id.id + virtual_network_subnet_id = data.azurerm_subnet.web_apps.id + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + tags = local.workspace_service_tags app_settings = { WEBSITES_PORT = "3000" @@ -74,12 +76,12 @@ resource "azurerm_linux_web_app" "gitea" { container_registry_managed_identity_client_id = azurerm_user_assigned_identity.gitea_id.client_id ftps_state = "Disabled" always_on = true - minimum_tls_version = "1.2" + minimum_tls_version = "1.3" vnet_route_all_enabled = true application_stack { - docker_image = "${data.azurerm_container_registry.mgmt_acr.login_server}/microsoft/azuretre/gitea-workspace-service" - docker_image_tag = local.version + docker_registry_url = "https://${data.azurerm_container_registry.mgmt_acr.login_server}" + docker_image_name = "/microsoft/azuretre/gitea-workspace-service:${local.version}" } } @@ -136,11 +138,13 @@ resource "azurerm_monitor_diagnostic_setting" "gitea" { target_resource_id = azurerm_linux_web_app.gitea.id log_analytics_workspace_id = data.azurerm_log_analytics_workspace.tre.id - dynamic "log" { - for_each = data.azurerm_monitor_diagnostic_categories.gitea.log_category_types + dynamic "enabled_log" { + for_each = [ + for category in data.azurerm_monitor_diagnostic_categories.gitea.log_category_types : + category if contains(local.web_app_diagnostic_categories_enabled, category) + ] content { - category = log.value - enabled = contains(local.web_app_diagnostic_categories_enabled, log.value) ? true : false + category = enabled_log.value } } diff --git a/templates/workspace_services/gitea/terraform/locals.tf b/templates/workspace_services/gitea/terraform/locals.tf index 665181c21e..69f7c9f0df 100644 --- a/templates/workspace_services/gitea/terraform/locals.tf +++ b/templates/workspace_services/gitea/terraform/locals.tf @@ -22,5 +22,7 @@ locals { "AppServiceHTTPLogs", "AppServiceConsoleLogs", "AppServiceAppLogs", "AppServiceAuditLogs", "AppServiceIPSecAuditLogs", "AppServicePlatformLogs", "AppServiceAntivirusScanAuditLogs" ] - gitea_openid_auth = "${var.aad_authority_url}/${data.azurerm_key_vault_secret.aad_tenant_id.value}/v2.0" + gitea_openid_auth = "${var.aad_authority_url}/${data.azurerm_key_vault_secret.aad_tenant_id.value}/v2.0" + cmk_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspace_services/gitea/terraform/main.tf b/templates/workspace_services/gitea/terraform/main.tf index a20c2f52df..8ff9f97222 100644 --- a/templates/workspace_services/gitea/terraform/main.tf +++ b/templates/workspace_services/gitea/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=4.14.0" } random = { source = "hashicorp/random" diff --git a/templates/workspace_services/gitea/terraform/storage.tf b/templates/workspace_services/gitea/terraform/storage.tf index 649ee11092..0e74f16883 100644 --- a/templates/workspace_services/gitea/terraform/storage.tf +++ b/templates/workspace_services/gitea/terraform/storage.tf @@ -4,12 +4,30 @@ resource "azurerm_storage_account" "gitea" { location = data.azurerm_resource_group.ws.location account_tier = "Standard" account_replication_type = "GRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" cross_tenant_replication_enabled = false tags = local.workspace_service_tags # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true + dynamic "identity" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } + } + + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].versionless_id + user_assigned_identity_id = data.azurerm_user_assigned_identity.ws_encryption_identity[0].id + } + } + lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } diff --git a/templates/workspace_services/gitea/terraform/upgrade.sh b/templates/workspace_services/gitea/terraform/upgrade.sh deleted file mode 100644 index 70b3863871..0000000000 --- a/templates/workspace_services/gitea/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=tre-workspace-service-gitea-${TF_VAR_id}" diff --git a/templates/workspace_services/gitea/terraform/variables.tf b/templates/workspace_services/gitea/terraform/variables.tf index 181a27045e..267c41fcc6 100644 --- a/templates/workspace_services/gitea/terraform/variables.tf +++ b/templates/workspace_services/gitea/terraform/variables.tf @@ -27,3 +27,10 @@ variable "arm_environment" { variable "sql_sku" { type = string } +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/workspace_services/guacamole/.env.sample b/templates/workspace_services/guacamole/.env.sample index bd6e4ba57a..cadae76d21 100644 --- a/templates/workspace_services/guacamole/.env.sample +++ b/templates/workspace_services/guacamole/.env.sample @@ -17,6 +17,6 @@ GUAC_DRIVE_NAME="transfer" GUAC_DRIVE_PATH="/guac-transfer" GUAC_DISABLE_DOWNLOAD=true GUAC_DISABLE_UPLOAD=true -IS_EXPOSED_EXTERNALLY=false +IS_EXPOSED_EXTERNALLY=true image_name="guac-server" image_tag="" diff --git a/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile b/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile index fe0b8012a5..b46730523d 100644 --- a/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile +++ b/templates/workspace_services/guacamole/guacamole-server/docker/Dockerfile @@ -13,7 +13,7 @@ COPY --from=client_build /target/surefire-reports/* / FROM guacamole/guacd:1.5.5 -ARG GUACAMOLE_AZURE_VERSION=0.4.0 +ARG GUACAMOLE_AZURE_VERSION=0.4.1 ENV DEBIAN_FRONTEND=noninteractive @@ -26,7 +26,7 @@ RUN apk add --update --no-cache wget openssh openjdk11-jre \ ENV CATALINA_BASE=/usr/share/tomcat9/ RUN TOMCAT_ARCHIVE="tomcat.tar.gz" && \ - TOMCAT_VER="9.0.97" && \ + TOMCAT_VER="9.0.98" && \ wget -O "$TOMCAT_ARCHIVE" -N "https://archive.apache.org/dist/tomcat/tomcat-9/v${TOMCAT_VER}/bin/apache-tomcat-${TOMCAT_VER}.tar.gz" --progress=dot:giga && \ tar xzf "$TOMCAT_ARCHIVE" && \ rm -f "$TOMCAT_ARCHIVE" && \ diff --git a/templates/workspace_services/guacamole/guacamole-server/docker/version.txt b/templates/workspace_services/guacamole/guacamole-server/docker/version.txt index a2fecb4576..e94731c0f2 100644 --- a/templates/workspace_services/guacamole/guacamole-server/docker/version.txt +++ b/templates/workspace_services/guacamole/guacamole-server/docker/version.txt @@ -1 +1 @@ -__version__ = "0.9.2" +__version__ = "0.9.4" diff --git a/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/pom.xml b/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/pom.xml index f0ff33ecf7..6334d72f48 100644 --- a/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/pom.xml +++ b/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/pom.xml @@ -6,7 +6,7 @@ org.apache.guacamole guacamole-auth-tre - 0.4.0 + 0.4.1 jar guacamole-azure-tre @@ -48,12 +48,12 @@ com.azure azure-security-keyvault-secrets - 4.9.0 + 4.9.1 com.azure azure-identity - 1.14.0 + 1.14.2 diff --git a/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/src/main/java/org/apache/guacamole/auth/azuretre/connection/ConnectionService.java b/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/src/main/java/org/apache/guacamole/auth/azuretre/connection/ConnectionService.java index fa1792c82c..26f798ef14 100644 --- a/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/src/main/java/org/apache/guacamole/auth/azuretre/connection/ConnectionService.java +++ b/templates/workspace_services/guacamole/guacamole-server/guacamole-auth-azure/src/main/java/org/apache/guacamole/auth/azuretre/connection/ConnectionService.java @@ -111,6 +111,13 @@ private static void setConfig( config.setParameter("drive-path", System.getenv("GUAC_DRIVE_PATH")); config.setParameter("disable-download", System.getenv("GUAC_DISABLE_DOWNLOAD")); config.setParameter("disable-upload", System.getenv("GUAC_DISABLE_UPLOAD")); + + String serverLayout = System.getenv("GUAC_SERVER_LAYOUT"); + if (serverLayout != null) { + if (!serverLayout.isEmpty()) { + config.setParameter("server-layout", serverLayout); + } + } } private static JSONArray getVMsFromProjectAPI(final AzureTREAuthenticatedUser user) throws GuacamoleException { diff --git a/templates/workspace_services/guacamole/parameters.json b/templates/workspace_services/guacamole/parameters.json index 253f44127d..4e61f8cbb2 100755 --- a/templates/workspace_services/guacamole/parameters.json +++ b/templates/workspace_services/guacamole/parameters.json @@ -94,6 +94,12 @@ "env": "GUAC_ENABLE_DRIVE" } }, + { + "name": "guac_server_layout", + "source": { + "env": "GUAC_SERVER_LAYOUT" + } + }, { "name": "image_tag", "source": { diff --git a/templates/workspace_services/guacamole/porter.yaml b/templates/workspace_services/guacamole/porter.yaml index f38bd2fc72..c1d16965d1 100644 --- a/templates/workspace_services/guacamole/porter.yaml +++ b/templates/workspace_services/guacamole/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole -version: 0.12.2 +version: 0.12.8 description: "An Azure TRE service for Guacamole" dockerfile: Dockerfile.tmpl registry: azuretre @@ -75,6 +75,11 @@ parameters: type: boolean default: true env: GUAC_DISABLE_UPLOAD + - name: guac_server_layout + type: string + default: "" + env: GUAC_SERVER_LAYOUT + description: "Guacamole server keyboard layout (defaults to en-us-qwerty when not set)" - name: is_exposed_externally type: boolean default: true @@ -144,6 +149,7 @@ install: guac_drive_path: ${ bundle.parameters.guac_drive_path } guac_disable_download: ${ bundle.parameters.guac_disable_download } guac_disable_upload: ${ bundle.parameters.guac_disable_upload } + guac_server_layout: ${ bundle.parameters.guac_server_layout } is_exposed_externally: ${ bundle.parameters.is_exposed_externally } tre_resource_id: ${ bundle.parameters.id } aad_authority_url: ${ bundle.parameters.aad_authority_url } @@ -177,6 +183,7 @@ upgrade: guac_drive_path: ${ bundle.parameters.guac_drive_path } guac_disable_download: ${ bundle.parameters.guac_disable_download } guac_disable_upload: ${ bundle.parameters.guac_disable_upload } + guac_server_layout: ${ bundle.parameters.guac_server_layout } is_exposed_externally: ${ bundle.parameters.is_exposed_externally } tre_resource_id: ${ bundle.parameters.id } aad_authority_url: ${ bundle.parameters.aad_authority_url } @@ -210,6 +217,7 @@ uninstall: guac_drive_path: ${ bundle.parameters.guac_drive_path } guac_disable_download: ${ bundle.parameters.guac_disable_download } guac_disable_upload: ${ bundle.parameters.guac_disable_upload } + guac_server_layout: ${ bundle.parameters.guac_server_layout } is_exposed_externally: ${ bundle.parameters.is_exposed_externally } tre_resource_id: ${ bundle.parameters.id } aad_authority_url: ${ bundle.parameters.aad_authority_url } diff --git a/templates/workspace_services/guacamole/terraform/.terraform.lock.hcl b/templates/workspace_services/guacamole/terraform/.terraform.lock.hcl index 8066c24cc3..9e59e77e40 100644 --- a/templates/workspace_services/guacamole/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/guacamole/terraform/.terraform.lock.hcl @@ -2,22 +2,22 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "4.14.0" + constraints = "4.14.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:FYZ9qh8i3X2gDmUTe1jJ/VzdSyjGjVmhBzv2R8D6CBo=", + "zh:05aaea16fc5f27b14d9fbad81654edf0638949ed3585576b2219c76a2bee095a", + "zh:065ce6ed16ba3fa7efcf77888ea582aead54e6a28f184c6701b73d71edd64bb0", + "zh:3c0cd17c249d18aa2e0120acb5f0c14810725158b379a67fec1331110e7c50df", + "zh:5a3ba3ffb2f1ce519fe3bf84a7296aa5862c437c70c62f0b0a5293bea9f2d01c", + "zh:7a8e9d72fa2714f4d567270b1761d4b4e788de7c15dada7db0cf0e29933185a2", + "zh:a11e190073f31c1238c15af29b9162e0f4564f6b0cd0310a3fa94102738450dc", + "zh:a5c004114410cc6dcb8fed584c9f3b84283b58025b0073a7e88d2bdb27840dfa", + "zh:a674a41db118e244eda7591e455d2ec338626664e0856e4125e909eb038f78db", + "zh:b5139010e4cbb2cb1a27c775610593c1c8063d3a7c82b00a65006509c434df2f", + "zh:cbb031223ccd8b099ac4d19b92641142f330b90f2fc6452843e445bae28f832c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f7e7db1b94082a4ac3d4af3dabe7bbd335e1679305bf8e29d011f0ee440724ca", ] } diff --git a/templates/workspace_services/guacamole/terraform/data.tf b/templates/workspace_services/guacamole/terraform/data.tf index 93d29f4d97..35b523ae88 100644 --- a/templates/workspace_services/guacamole/terraform/data.tf +++ b/templates/workspace_services/guacamole/terraform/data.tf @@ -71,3 +71,8 @@ data "azurerm_monitor_diagnostic_categories" "guacamole" { azurerm_linux_web_app.guacamole, ] } + +data "azurerm_service_plan" "workspace" { + name = "plan-${var.workspace_id}" + resource_group_name = data.azurerm_resource_group.ws.name +} diff --git a/templates/workspace_services/guacamole/terraform/providers.tf b/templates/workspace_services/guacamole/terraform/providers.tf index 747560ff8a..62d4d8d07b 100644 --- a/templates/workspace_services/guacamole/terraform/providers.tf +++ b/templates/workspace_services/guacamole/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=4.14.0" } local = { source = "hashicorp/local" diff --git a/templates/workspace_services/guacamole/terraform/upgrade.sh b/templates/workspace_services/guacamole/terraform/upgrade.sh deleted file mode 100644 index edf24afb36..0000000000 --- a/templates/workspace_services/guacamole/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}${TF_VAR_workspace_id}guacamole" diff --git a/templates/workspace_services/guacamole/terraform/variables.tf b/templates/workspace_services/guacamole/terraform/variables.tf index 26fcbb2f05..b62991b3c5 100644 --- a/templates/workspace_services/guacamole/terraform/variables.tf +++ b/templates/workspace_services/guacamole/terraform/variables.tf @@ -54,6 +54,10 @@ variable "guac_disable_upload" { type = bool description = "Disable upload to the Guacamole workspace" } +variable "guac_server_layout" { + type = string + description = "Server keyboard layout" +} variable "is_exposed_externally" { type = bool description = "Is the Guacamole workspace to be exposed externally?" diff --git a/templates/workspace_services/guacamole/terraform/web_app.tf b/templates/workspace_services/guacamole/terraform/web_app.tf index 5cd938a256..d56f6bd3fc 100644 --- a/templates/workspace_services/guacamole/terraform/web_app.tf +++ b/templates/workspace_services/guacamole/terraform/web_app.tf @@ -1,8 +1,3 @@ -data "azurerm_service_plan" "workspace" { - name = "plan-${var.workspace_id}" - resource_group_name = data.azurerm_resource_group.ws.name -} - # we have to use user-assigned to break a cycle in the dependencies: app identity, kv-policy, secrets in app settings resource "azurerm_user_assigned_identity" "guacamole_id" { resource_group_name = data.azurerm_resource_group.ws.name @@ -14,14 +9,17 @@ resource "azurerm_user_assigned_identity" "guacamole_id" { } resource "azurerm_linux_web_app" "guacamole" { - name = local.webapp_name - location = data.azurerm_resource_group.ws.location - resource_group_name = data.azurerm_resource_group.ws.name - service_plan_id = data.azurerm_service_plan.workspace.id - https_only = true - key_vault_reference_identity_id = azurerm_user_assigned_identity.guacamole_id.id - virtual_network_subnet_id = data.azurerm_subnet.web_apps.id - tags = local.workspace_service_tags + name = local.webapp_name + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + service_plan_id = data.azurerm_service_plan.workspace.id + https_only = true + key_vault_reference_identity_id = azurerm_user_assigned_identity.guacamole_id.id + virtual_network_subnet_id = data.azurerm_subnet.web_apps.id + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + tags = local.workspace_service_tags + public_network_access_enabled = var.is_exposed_externally site_config { http2_enabled = true @@ -29,11 +27,11 @@ resource "azurerm_linux_web_app" "guacamole" { container_registry_managed_identity_client_id = azurerm_user_assigned_identity.guacamole_id.client_id ftps_state = "Disabled" vnet_route_all_enabled = true - minimum_tls_version = "1.2" + minimum_tls_version = "1.3" application_stack { - docker_image = "${data.azurerm_container_registry.mgmt_acr.login_server}/microsoft/azuretre/${var.image_name}" - docker_image_tag = local.image_tag + docker_registry_url = "https://${data.azurerm_container_registry.mgmt_acr.login_server}" + docker_image_name = "microsoft/azuretre/${var.image_name}:${local.image_tag}" } } @@ -57,6 +55,7 @@ resource "azurerm_linux_web_app" "guacamole" { GUAC_DRIVE_PATH = var.guac_drive_path GUAC_DISABLE_DOWNLOAD = var.guac_disable_download GUAC_DISABLE_UPLOAD = var.guac_disable_upload + GUAC_SERVER_LAYOUT = var.guac_server_layout AUDIENCE = "@Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.workspace_client_id.id})" ISSUER = local.issuer diff --git a/templates/workspace_services/guacamole/user_resources/README.md b/templates/workspace_services/guacamole/user_resources/README.md index 623098ad42..608509e00d 100644 --- a/templates/workspace_services/guacamole/user_resources/README.md +++ b/templates/workspace_services/guacamole/user_resources/README.md @@ -37,10 +37,14 @@ custom: apt_sku: 22.04 install_ui: true conda_config: false + secure_boot_enabled: true + vtpm_enabled: true # "Custom Image From Gallery": # source_image_name: your-image # install_ui: true # conda_config: true + # secure_boot_enabled: true + # vtpm_enabled: true ``` The `vm_sizes` section is a map of a custom SKU description to the SKU identifier. @@ -55,6 +59,8 @@ Within the image definition in `image_options` there are a few properties that c | `source_image_reference` | Specify VM image to use by `publisher`, `offer`, `sku` & `version` (e.g. for Azure Marketplace images) | | `install_ui` | (Linux only) Set `true` to install desktop environment | | `conda_config` | Set true to configure conda | +| `secure_boot_enabled` | Set true to enable [Secure Boot](https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch#secure-boot). Requires a Requires a [Gen 2](https://learn.microsoft.com/en-us/azure/virtual-machines/generation-2) VM image | +| `vtpm_enabled` | Set true to enable [Secure Boot](https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch#vtpm). Requires a [Gen 2](https://learn.microsoft.com/en-us/azure/virtual-machines/generation-2) VM image | When specifying images using `source_image_name`, the image must be stored in an [image gallery](https://learn.microsoft.com/en-us/azure/virtual-machines/azure-compute-gallery). To enable re-using built user resource templates across environments where the image may vary, the image gallery is configured via the `RP_BUNDLE_VALUES` environment variable when deploying the TRE. diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/parameters.json b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/parameters.json index 9c6ec9b9f3..e6b0e091fe 100755 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/parameters.json +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/parameters.json @@ -75,6 +75,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml index 03e283dd57..8a2fa4c0a0 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole-export-reviewvm -version: 0.1.13 +version: 0.2.2 description: "An Azure TRE User Resource Template for reviewing Airlock export requests" dockerfile: Dockerfile.tmpl registry: azuretre @@ -18,6 +18,8 @@ custom: sku: winserver-2019 version: latest conda_config: true + secure_boot_enabled: false # dsvm-win-2019 is not a gen2 image + vtpm_enabled: false credentials: - name: azure_tenant_id @@ -75,6 +77,12 @@ parameters: type: string description: "A SAS token to access storage resource in workspace under review" env: airlock_request_sas_url + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: ip @@ -118,6 +126,8 @@ install: image: ${ bundle.parameters.os_image } vm_size: ${ bundle.parameters.vm_size } airlock_request_sas_url: ${ bundle.parameters.airlock_request_sas_url } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -142,6 +152,8 @@ upgrade: image: ${ bundle.parameters.os_image } vm_size: ${ bundle.parameters.vm_size } airlock_request_sas_url: "unused" + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -178,6 +190,8 @@ uninstall: image: ${ bundle.parameters.os_image } vm_size: ${ bundle.parameters.vm_size } airlock_request_sas_url: "unused" + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/.terraform.lock.hcl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/.terraform.lock.hcl index d4260fd975..2724e7a83d 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/data.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/data.tf index 2c6512997b..5379f1a599 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/data.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/data.tf @@ -33,3 +33,15 @@ data "azurerm_private_endpoint_connection" "airlock_export_inprogress_pe" { name = "pe-sa-export-ip-blob-${local.short_workspace_id}" resource_group_name = data.azurerm_resource_group.ws.name } + +data "azurerm_key_vault_key" "ws_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "ws_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.ws.name +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/locals.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/locals.tf index 508f1083c2..33784b6275 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/locals.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/locals.tf @@ -24,4 +24,9 @@ locals { # selected_image_source_refs is an array to enable easy use of a dynamic block selected_image_source_refs = lookup(local.selected_image, "source_image_reference", null) == null ? [] : [local.selected_image.source_image_reference] selected_image_source_id = lookup(local.selected_image, "source_image_name", null) == null ? null : "${var.image_gallery_id}/images/${local.selected_image.source_image_name}" + secure_boot_enabled = lookup(local.selected_image, "secure_boot_enabled", false) + vtpm_enabled = lookup(local.selected_image, "vtpm_enabled", false) + + cmk_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/main.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/main.tf index dd51f0ff90..50e90e7350 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/main.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } random = { source = "hashicorp/random" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/upgrade.sh b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/upgrade.sh deleted file mode 100644 index e5bbe41a91..0000000000 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}${TF_VAR_workspace_id}${TF_VAR_parent_service_id}guacamolewindowsvm" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/variables.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/variables.tf index 36cdb77b3c..b557c708cb 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/variables.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/variables.tf @@ -23,3 +23,10 @@ variable "image_gallery_id" { variable "airlock_request_sas_url" { type = string } +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/windowsvm.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/windowsvm.tf index 9efc1661f2..aa25d019a9 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/windowsvm.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-export-reviewvm/terraform/windowsvm.tf @@ -124,6 +124,9 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { allow_extension_operations = true admin_username = random_string.username.result admin_password = random_password.password.result + encryption_at_host_enabled = true + secure_boot_enabled = local.secure_boot_enabled + vtpm_enabled = local.vtpm_enabled custom_data = base64encode(data.template_file.download_review_data_script.rendered) @@ -140,9 +143,10 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { } os_disk { - name = "osdisk-${local.vm_name}" - caching = "ReadWrite" - storage_account_type = "Standard_LRS" + name = "osdisk-${local.vm_name}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.windowsvm_disk_encryption[0].id : null } identity { @@ -151,7 +155,25 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { tags = local.tre_user_resources_tags - lifecycle { ignore_changes = [tags] } + # ignore changes to secure_boot_enabled and vtpm_enabled as these are destructive + # (may be allowed once https://github.com/hashicorp/terraform-provider-azurerm/issues/25808 is fixed) + # + lifecycle { ignore_changes = [tags, secure_boot_enabled, vtpm_enabled] } +} + +resource "azurerm_disk_encryption_set" "windowsvm_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "disk-encryption-windowsvm-${var.tre_id}-${var.tre_resource_id}" + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } } resource "azurerm_virtual_machine_extension" "config_script" { diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/parameters.json b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/parameters.json index 5e12488c7e..07be376ae5 100755 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/parameters.json +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/parameters.json @@ -81,6 +81,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml index a70cea2d47..871eb4d617 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole-import-reviewvm -version: 0.2.13 +version: 0.3.2 description: "An Azure TRE User Resource Template for reviewing Airlock import requests" dockerfile: Dockerfile.tmpl registry: azuretre @@ -18,11 +18,15 @@ custom: sku: winserver-2019 version: latest conda_config: true + secure_boot_enabled: false # dsvm-win-2019 is not a gen2 image + vtpm_enabled: false # For information on using custom images, see README.me in the guacamole/user-resources folder # "Custom Image From Gallery": # source_image_name: sltestwin1 # source_image_reference: null, # conda_config: true + # secure_boot_enabled: false + # vtpm_enabled: false credentials: - name: azure_tenant_id @@ -84,6 +88,12 @@ parameters: type: string description: "A SAS token to access storage resource in workspace under review" env: airlock_request_sas_url + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: ip @@ -128,6 +138,8 @@ install: vm_size: ${ bundle.parameters.vm_size } image_gallery_id: ${ bundle.parameters.image_gallery_id } airlock_request_sas_url: ${ bundle.parameters.airlock_request_sas_url } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -153,6 +165,8 @@ upgrade: vm_size: ${ bundle.parameters.vm_size } image_gallery_id: ${ bundle.parameters.image_gallery_id } airlock_request_sas_url: "unused" + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -190,6 +204,8 @@ uninstall: vm_size: ${ bundle.parameters.vm_size } image_gallery_id: ${ bundle.parameters.image_gallery_id } airlock_request_sas_url: "unused" + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/.terraform.lock.hcl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/.terraform.lock.hcl index d4260fd975..2724e7a83d 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/data.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/data.tf index 04675964b9..d3ce0f2eb3 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/data.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/data.tf @@ -22,3 +22,15 @@ data "azurerm_linux_web_app" "guacamole" { name = "guacamole-${var.tre_id}-ws-${local.short_workspace_id}-svc-${local.short_parent_id}" resource_group_name = data.azurerm_resource_group.ws.name } + +data "azurerm_key_vault_key" "ws_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "ws_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.ws.name +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/locals.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/locals.tf index 508f1083c2..33784b6275 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/locals.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/locals.tf @@ -24,4 +24,9 @@ locals { # selected_image_source_refs is an array to enable easy use of a dynamic block selected_image_source_refs = lookup(local.selected_image, "source_image_reference", null) == null ? [] : [local.selected_image.source_image_reference] selected_image_source_id = lookup(local.selected_image, "source_image_name", null) == null ? null : "${var.image_gallery_id}/images/${local.selected_image.source_image_name}" + secure_boot_enabled = lookup(local.selected_image, "secure_boot_enabled", false) + vtpm_enabled = lookup(local.selected_image, "vtpm_enabled", false) + + cmk_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/main.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/main.tf index dd51f0ff90..50e90e7350 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/main.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } random = { source = "hashicorp/random" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/upgrade.sh b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/upgrade.sh deleted file mode 100644 index e5bbe41a91..0000000000 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}${TF_VAR_workspace_id}${TF_VAR_parent_service_id}guacamolewindowsvm" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/variables.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/variables.tf index c6847da884..b557c708cb 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/variables.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/variables.tf @@ -22,4 +22,11 @@ variable "image_gallery_id" { } variable "airlock_request_sas_url" { type = string -} \ No newline at end of file +} +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/windowsvm.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/windowsvm.tf index 75891d5018..792330d7fd 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/windowsvm.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-import-reviewvm/terraform/windowsvm.tf @@ -45,6 +45,9 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { allow_extension_operations = true admin_username = random_string.username.result admin_password = random_password.password.result + encryption_at_host_enabled = true + secure_boot_enabled = local.secure_boot_enabled + vtpm_enabled = local.vtpm_enabled custom_data = base64encode(data.template_file.download_review_data_script.rendered) @@ -61,9 +64,10 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { } os_disk { - name = "osdisk-${local.vm_name}" - caching = "ReadWrite" - storage_account_type = "Standard_LRS" + name = "osdisk-${local.vm_name}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.windowsvm_disk_encryption[0].id : null } identity { @@ -72,7 +76,25 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { tags = local.tre_user_resources_tags - lifecycle { ignore_changes = [tags] } + # ignore changes to secure_boot_enabled and vtpm_enabled as these are destructive + # (may be allowed once https://github.com/hashicorp/terraform-provider-azurerm/issues/25808 is fixed) + # + lifecycle { ignore_changes = [tags, secure_boot_enabled, vtpm_enabled] } +} + +resource "azurerm_disk_encryption_set" "windowsvm_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "disk-encryption-windowsvm-${var.tre_id}-${var.tre_resource_id}" + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } } resource "azurerm_virtual_machine_extension" "config_script" { diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/parameters.json b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/parameters.json index 0c4dc1a484..6ff93df11d 100755 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/parameters.json +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/parameters.json @@ -87,6 +87,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml index 98b827932f..b8ecdc4c79 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole-linuxvm -version: 1.1.0 +version: 1.2.4 description: "An Azure TRE User Resource Template for Guacamole (Linux)" dockerfile: Dockerfile.tmpl registry: azuretre @@ -23,11 +23,15 @@ custom: apt_sku: 22.04 install_ui: true conda_config: false + secure_boot_enabled: true + vtpm_enabled: true # For information on using custom images, see README.me in the guacamole/user-resources folder # "Custom Image From Gallery": # source_image_name: your-image # install_ui: true # conda_config: true + # secure_boot_enabled: false + # vtpm_enabled: false credentials: - name: azure_tenant_id @@ -106,6 +110,12 @@ parameters: - name: shared_storage_name type: string default: "vm-shared-storage" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: ip @@ -154,6 +164,8 @@ install: enable_shutdown_schedule: ${ bundle.parameters.enable_shutdown_schedule } shutdown_time: ${ bundle.parameters.shutdown_time } shutdown_timezone: ${ bundle.parameters.shutdown_timezone } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -183,6 +195,8 @@ upgrade: enable_shutdown_schedule: ${ bundle.parameters.enable_shutdown_schedule } shutdown_time: ${ bundle.parameters.shutdown_time } shutdown_timezone: ${ bundle.parameters.shutdown_timezone } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -224,6 +238,8 @@ uninstall: enable_shutdown_schedule: ${ bundle.parameters.enable_shutdown_schedule } shutdown_time: ${ bundle.parameters.shutdown_time } shutdown_timezone: ${ bundle.parameters.shutdown_timezone } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/.terraform.lock.hcl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/.terraform.lock.hcl index d4260fd975..2724e7a83d 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/data.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/data.tf new file mode 100644 index 0000000000..d815e97c6c --- /dev/null +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/data.tf @@ -0,0 +1,72 @@ +data "azurerm_resource_group" "ws" { + name = "rg-${var.tre_id}-ws-${local.short_workspace_id}" +} + +data "azurerm_resource_group" "core" { + name = "rg-${var.tre_id}" +} + +data "azurerm_virtual_network" "ws" { + name = "vnet-${var.tre_id}-ws-${local.short_workspace_id}" + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_subnet" "services" { + name = "ServicesSubnet" + virtual_network_name = data.azurerm_virtual_network.ws.name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_key_vault" "ws" { + name = local.keyvault_name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_linux_web_app" "guacamole" { + name = "guacamole-${var.tre_id}-ws-${local.short_workspace_id}-svc-${local.short_parent_id}" + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_public_ip" "app_gateway_ip" { + name = "pip-agw-${var.tre_id}" + resource_group_name = data.azurerm_resource_group.core.name +} + +data "azurerm_key_vault_key" "ws_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "ws_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "template_file" "get_apt_keys" { + template = file("${path.module}/get_apt_keys.sh") + vars = { + NEXUS_PROXY_URL = local.nexus_proxy_url + } +} + +data "template_file" "pypi_sources_config" { + template = file("${path.module}/pypi_sources_config.sh") + vars = { + nexus_proxy_url = local.nexus_proxy_url + } +} + +data "template_file" "apt_sources_config" { + template = file("${path.module}/apt_sources_config.yml") + vars = { + nexus_proxy_url = local.nexus_proxy_url + apt_sku = local.apt_sku + } +} + +data "azurerm_storage_account" "stg" { + name = local.storage_name + resource_group_name = data.azurerm_resource_group.ws.name +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/linuxvm.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/linuxvm.tf index 68b9d36773..30b1fdfcb9 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/linuxvm.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/linuxvm.tf @@ -44,6 +44,9 @@ resource "azurerm_linux_virtual_machine" "linuxvm" { disable_password_authentication = false admin_username = random_string.username.result admin_password = random_password.password.result + encryption_at_host_enabled = true + secure_boot_enabled = local.secure_boot_enabled + vtpm_enabled = local.vtpm_enabled custom_data = data.template_cloudinit_config.config.rendered @@ -60,9 +63,10 @@ resource "azurerm_linux_virtual_machine" "linuxvm" { } os_disk { - name = "osdisk-${local.vm_name}" - caching = "ReadWrite" - storage_account_type = "Standard_LRS" + name = "osdisk-${local.vm_name}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.linuxvm_disk_encryption[0].id : null } identity { @@ -71,7 +75,25 @@ resource "azurerm_linux_virtual_machine" "linuxvm" { tags = local.tre_user_resources_tags - lifecycle { ignore_changes = [tags] } + # ignore changes to secure_boot_enabled and vtpm_enabled as these are destructive + # (may be allowed once https://github.com/hashicorp/terraform-provider-azurerm/issues/25808 is fixed) + # + lifecycle { ignore_changes = [tags, secure_boot_enabled, vtpm_enabled] } +} + +resource "azurerm_disk_encryption_set" "linuxvm_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "disk-encryption-linuxvm-${var.tre_id}-${var.tre_resource_id}" + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } } data "template_cloudinit_config" "config" { @@ -107,7 +129,7 @@ data "template_file" "vm_config" { STORAGE_ACCOUNT_NAME = data.azurerm_storage_account.stg.name STORAGE_ACCOUNT_KEY = data.azurerm_storage_account.stg.primary_access_key HTTP_ENDPOINT = data.azurerm_storage_account.stg.primary_file_endpoint - FILESHARE_NAME = var.shared_storage_access ? data.azurerm_storage_share.shared_storage[0].name : "" + FILESHARE_NAME = var.shared_storage_access ? var.shared_storage_name : "" NEXUS_PROXY_URL = local.nexus_proxy_url CONDA_CONFIG = local.selected_image.conda_config ? 1 : 0 VM_USER = random_string.username.result @@ -115,28 +137,6 @@ data "template_file" "vm_config" { } } -data "template_file" "get_apt_keys" { - template = file("${path.module}/get_apt_keys.sh") - vars = { - NEXUS_PROXY_URL = local.nexus_proxy_url - } -} - -data "template_file" "pypi_sources_config" { - template = file("${path.module}/pypi_sources_config.sh") - vars = { - nexus_proxy_url = local.nexus_proxy_url - } -} - -data "template_file" "apt_sources_config" { - template = file("${path.module}/apt_sources_config.yml") - vars = { - nexus_proxy_url = local.nexus_proxy_url - apt_sku = local.apt_sku - } -} - resource "azurerm_key_vault_secret" "linuxvm_password" { name = local.vm_password_secret_name value = "${random_string.username.result}\n${random_password.password.result}" @@ -146,17 +146,6 @@ resource "azurerm_key_vault_secret" "linuxvm_password" { lifecycle { ignore_changes = [tags] } } -data "azurerm_storage_account" "stg" { - name = local.storage_name - resource_group_name = data.azurerm_resource_group.ws.name -} - -data "azurerm_storage_share" "shared_storage" { - count = var.shared_storage_access ? 1 : 0 - name = var.shared_storage_name - storage_account_name = data.azurerm_storage_account.stg.name -} - resource "azurerm_dev_test_global_vm_shutdown_schedule" "shutdown_schedule" { count = var.enable_shutdown_schedule ? 1 : 0 diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/locals.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/locals.tf index e0281269fd..073adc03ab 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/locals.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/locals.tf @@ -25,5 +25,10 @@ locals { # selected_image_source_refs is an array to enable easy use of a dynamic block selected_image_source_refs = lookup(local.selected_image, "source_image_reference", null) == null ? [] : [local.selected_image.source_image_reference] selected_image_source_id = lookup(local.selected_image, "source_image_name", null) == null ? null : "${var.image_gallery_id}/images/${local.selected_image.source_image_name}" + secure_boot_enabled = lookup(local.selected_image, "secure_boot_enabled", false) + vtpm_enabled = lookup(local.selected_image, "vtpm_enabled", false) apt_sku = local.selected_image_source_refs[0]["apt_sku"] + + cmk_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/main.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/main.tf index 5231913385..f1f1058865 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/main.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } template = { source = "hashicorp/template" @@ -33,43 +33,7 @@ provider "azurerm" { recover_soft_deleted_certificates = true recover_soft_deleted_keys = true } - virtual_machine { - skip_shutdown_and_force_delete = true - } } storage_use_azuread = true } -data "azurerm_resource_group" "ws" { - name = "rg-${var.tre_id}-ws-${local.short_workspace_id}" -} - -data "azurerm_resource_group" "core" { - name = "rg-${var.tre_id}" -} - -data "azurerm_virtual_network" "ws" { - name = "vnet-${var.tre_id}-ws-${local.short_workspace_id}" - resource_group_name = data.azurerm_resource_group.ws.name -} - -data "azurerm_subnet" "services" { - name = "ServicesSubnet" - virtual_network_name = data.azurerm_virtual_network.ws.name - resource_group_name = data.azurerm_resource_group.ws.name -} - -data "azurerm_key_vault" "ws" { - name = local.keyvault_name - resource_group_name = data.azurerm_resource_group.ws.name -} - -data "azurerm_linux_web_app" "guacamole" { - name = "guacamole-${var.tre_id}-ws-${local.short_workspace_id}-svc-${local.short_parent_id}" - resource_group_name = data.azurerm_resource_group.ws.name -} - -data "azurerm_public_ip" "app_gateway_ip" { - name = "pip-agw-${var.tre_id}" - resource_group_name = data.azurerm_resource_group.core.name -} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/upgrade.sh b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/upgrade.sh deleted file mode 100644 index f6d9bbcefe..0000000000 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TF_VAR_tre_id}${TF_VAR_workspace_id}${TF_VAR_parent_service_id}guacamolelinuxvm" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/variables.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/variables.tf index a515e46e30..91bff4de77 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/variables.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/variables.tf @@ -37,3 +37,10 @@ variable "shutdown_timezone" { type = string default = "UTC" } +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/parameters.json b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/parameters.json index 23e54b669b..d9becce90e 100755 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/parameters.json +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/parameters.json @@ -87,6 +87,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml index 3f814c394f..fa2aeae03b 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole-windowsvm -version: 1.0.8 +version: 1.2.7 description: "An Azure TRE User Resource Template for Guacamole (Windows 10)" dockerfile: Dockerfile.tmpl registry: azuretre @@ -21,6 +21,8 @@ custom: sku: win10-22h2-pro-g2 version: latest conda_config: false + secure_boot_enabled: true + vtpm_enabled: true "Windows 11": source_image_reference: publisher: microsoftwindowsdesktop @@ -28,6 +30,8 @@ custom: sku: win11-24h2-pro version: latest conda_config: false + secure_boot_enabled: true + vtpm_enabled: true "Server 2019 Data Science VM": source_image_reference: publisher: microsoft-dsvm @@ -35,10 +39,14 @@ custom: sku: winserver-2019 version: latest conda_config: true + secure_boot_enabled: false # dsvm-win-2019 is not a gen2 image + vtpm_enabled: false # For information on using custom images, see README.me in the guacamole/user-resources folder # "Custom Image From Gallery": # source_image_name: your-image # conda_config: true + # secure_boot_enabled: false + # vtpm_enabled: false credentials: - name: azure_tenant_id @@ -113,6 +121,13 @@ parameters: type: string description: "Timezone for the shutdown schedule" default: "UTC" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" + outputs: - name: ip @@ -141,7 +156,7 @@ outputs: mixins: - exec - terraform: - clientVersion: 1.4.6 + clientVersion: 1.9.8 - az: clientVersion: 2.67.0 @@ -161,6 +176,8 @@ install: enable_shutdown_schedule: ${ bundle.parameters.enable_shutdown_schedule } shutdown_time: ${ bundle.parameters.shutdown_time } shutdown_timezone: ${ bundle.parameters.shutdown_timezone } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -190,6 +207,8 @@ upgrade: enable_shutdown_schedule: ${ bundle.parameters.enable_shutdown_schedule } shutdown_time: ${ bundle.parameters.shutdown_time } shutdown_timezone: ${ bundle.parameters.shutdown_timezone } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -231,6 +250,8 @@ uninstall: enable_shutdown_schedule: ${ bundle.parameters.enable_shutdown_schedule } shutdown_time: ${ bundle.parameters.shutdown_time } shutdown_timezone: ${ bundle.parameters.shutdown_timezone } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/.terraform.lock.hcl b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/.terraform.lock.hcl index e23be37b77..37741e445e 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/data.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/data.tf index b8f4239143..8cd83bb6c3 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/data.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/data.tf @@ -37,8 +37,14 @@ data "azurerm_storage_account" "stg" { resource_group_name = data.azurerm_resource_group.ws.name } -data "azurerm_storage_share" "shared_storage" { - count = var.shared_storage_access ? 1 : 0 - name = var.shared_storage_name - storage_account_name = data.azurerm_storage_account.stg.name +data "azurerm_key_vault_key" "ws_encryption_key" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.cmk_name + key_vault_id = var.key_store_id +} + +data "azurerm_user_assigned_identity" "ws_encryption_identity" { + count = var.enable_cmk_encryption ? 1 : 0 + name = local.encryption_identity_name + resource_group_name = data.azurerm_resource_group.ws.name } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/locals.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/locals.tf index e5137d1967..d4b784f5d8 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/locals.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/locals.tf @@ -26,4 +26,9 @@ locals { # selected_image_source_refs is an array to enable easy use of a dynamic block selected_image_source_refs = lookup(local.selected_image, "source_image_reference", null) == null ? [] : [local.selected_image.source_image_reference] selected_image_source_id = lookup(local.selected_image, "source_image_name", null) == null ? null : "${var.image_gallery_id}/images/${local.selected_image.source_image_name}" + secure_boot_enabled = lookup(local.selected_image, "secure_boot_enabled", false) + vtpm_enabled = lookup(local.selected_image, "vtpm_enabled", false) + + cmk_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/main.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/main.tf index 616fac90a5..6bbc302592 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/main.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } random = { source = "hashicorp/random" @@ -28,9 +28,6 @@ provider "azurerm" { recover_soft_deleted_certificates = true recover_soft_deleted_keys = true } - virtual_machine { - skip_shutdown_and_force_delete = true - } } storage_use_azuread = true } diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/upgrade.sh b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/upgrade.sh deleted file mode 100644 index e5bbe41a91..0000000000 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/upgrade.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# This script is used to install the bundle directly without having to interact with Porter - -# This script assumes you have created an .env from the sample and the variables -# will come from there. -# shellcheck disable=SC2154 -terraform init -upgrade -reconfigure -input=false -backend=true \ - -backend-config="resource_group_name=${TF_VAR_mgmt_resource_group_name}" \ - -backend-config="storage_account_name=${TF_VAR_mgmt_storage_account_name}" \ - -backend-config="container_name=${TF_VAR_terraform_state_container_name}" \ - -backend-config="key=${TRE_ID}${TF_VAR_workspace_id}${TF_VAR_parent_service_id}guacamolewindowsvm" diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/variables.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/variables.tf index a515e46e30..91bff4de77 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/variables.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/variables.tf @@ -37,3 +37,10 @@ variable "shutdown_timezone" { type = string default = "UTC" } +variable "enable_cmk_encryption" { + type = bool + default = false +} +variable "key_store_id" { + type = string +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/vm_config.ps1 b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/vm_config.ps1 index f2604f7a3b..6664c652e8 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/vm_config.ps1 +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/vm_config.ps1 @@ -44,9 +44,6 @@ $DaemonConfig = @" $DaemonConfig | Out-File -Encoding Ascii ( New-Item -Path $env:ProgramData\docker\config\daemon.json -Force ) # R config -# $RconfigFilePathWindows = C:\Progra~1\R\4.1.2\etc\Rprofile.site -#Add-Content $RconfigFilePathWindows "local({`n r <- getOption(`"repos`")`n r[`"Nexus`"] <- `"${nexus_proxy_url}/repository/r-proxy/`"`n options(repos = r)`n})" -# echo "local({`n r <- getOption(`"repos`")`n r[`"Nexus`"] <- `"${nexus_proxy_url}/repository/r-proxy/`"`n options(repos = r)`n})" > $RconfigFilePathWindows $RConfig = @" local({ r <- getOption("repos") @@ -54,5 +51,14 @@ local({ options(repos = r) }) "@ -$RConfig | Out-File -Encoding Ascii ( New-Item -Path $Env:ProgramFiles\R\R-4.1.2\etc\Rprofile.site -Force ) +$RBasePath = "$Env:ProgramFiles\R" + +if (Test-Path $RBasePath) { + $RVersions = Get-ChildItem -Path $RBasePath -Directory | Where-Object { $_.Name -like "R-*" } + + foreach ($RVersion in $RVersions) { + $ConfigPath = Join-Path -Path $RVersion.FullName -ChildPath "etc\Rprofile.site" + $RConfig | Out-File -Encoding Ascii (New-Item -Path $ConfigPath -Force) + } +} diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/windowsvm.tf b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/windowsvm.tf index e2df693bc2..0bb11f7e65 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/windowsvm.tf +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm/terraform/windowsvm.tf @@ -45,6 +45,9 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { allow_extension_operations = true admin_username = random_string.username.result admin_password = random_password.password.result + encryption_at_host_enabled = true + secure_boot_enabled = local.secure_boot_enabled + vtpm_enabled = local.vtpm_enabled custom_data = base64encode(templatefile( "${path.module}/vm_config.ps1", { @@ -53,7 +56,7 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { StorageAccountName = data.azurerm_storage_account.stg.name StorageAccountKey = data.azurerm_storage_account.stg.primary_access_key StorageAccountFileHost = data.azurerm_storage_account.stg.primary_file_host - FileShareName = var.shared_storage_access ? data.azurerm_storage_share.shared_storage[0].name : "" + FileShareName = var.shared_storage_access ? var.shared_storage_name : "" CondaConfig = local.selected_image.conda_config ? 1 : 0 } )) @@ -71,9 +74,10 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { } os_disk { - name = "osdisk-${local.vm_name}" - caching = "ReadWrite" - storage_account_type = "Standard_LRS" + name = "osdisk-${local.vm_name}" + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + disk_encryption_set_id = var.enable_cmk_encryption ? azurerm_disk_encryption_set.windowsvm_disk_encryption[0].id : null } identity { @@ -82,9 +86,28 @@ resource "azurerm_windows_virtual_machine" "windowsvm" { tags = local.tre_user_resources_tags - lifecycle { ignore_changes = [tags] } + # ignore changes to secure_boot_enabled and vtpm_enabled as these are destructive + # (may be allowed once https://github.com/hashicorp/terraform-provider-azurerm/issues/25808 is fixed) + # + lifecycle { ignore_changes = [tags, secure_boot_enabled, vtpm_enabled] } } +resource "azurerm_disk_encryption_set" "windowsvm_disk_encryption" { + count = var.enable_cmk_encryption ? 1 : 0 + name = "disk-encryption-windowsvm-${var.tre_id}-${var.tre_resource_id}" + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + key_vault_key_id = data.azurerm_key_vault_key.ws_encryption_key[0].versionless_id + encryption_type = "EncryptionAtRestWithPlatformAndCustomerKeys" + auto_key_rotation_enabled = true + + identity { + type = "UserAssigned" + identity_ids = [data.azurerm_user_assigned_identity.ws_encryption_identity[0].id] + } +} + + resource "azurerm_virtual_machine_extension" "config_script" { name = "${azurerm_windows_virtual_machine.windowsvm.name}-vmextension" virtual_machine_id = azurerm_windows_virtual_machine.windowsvm.id diff --git a/templates/workspace_services/health-services/porter.yaml b/templates/workspace_services/health-services/porter.yaml index 011ecc8b77..d0632a71fa 100644 --- a/templates/workspace_services/health-services/porter.yaml +++ b/templates/workspace_services/health-services/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-service-health -version: 0.2.10 +version: 0.2.11 description: "An Azure Data Health Services workspace service" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/health-services/terraform/.terraform.lock.hcl b/templates/workspace_services/health-services/terraform/.terraform.lock.hcl index 39704b83be..d090e0430d 100644 --- a/templates/workspace_services/health-services/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/health-services/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/health-services/terraform/providers.tf b/templates/workspace_services/health-services/terraform/providers.tf index c03baf73dc..fbbad1c4a4 100644 --- a/templates/workspace_services/health-services/terraform/providers.tf +++ b/templates/workspace_services/health-services/terraform/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.112.0" + version = "3.117.0" } external = { source = "hashicorp/external" diff --git a/templates/workspace_services/mysql/porter.yaml b/templates/workspace_services/mysql/porter.yaml index fd6c71e370..9b93ba77de 100644 --- a/templates/workspace_services/mysql/porter.yaml +++ b/templates/workspace_services/mysql/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-service-mysql -version: 1.0.8 +version: 1.0.9 description: "A MySQL workspace service" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/mysql/terraform/.terraform.lock.hcl b/templates/workspace_services/mysql/terraform/.terraform.lock.hcl index 66c2832260..9cf5f7aba1 100644 --- a/templates/workspace_services/mysql/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/mysql/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/mysql/terraform/main.tf b/templates/workspace_services/mysql/terraform/main.tf index 09d7037eb5..3c4ad91226 100644 --- a/templates/workspace_services/mysql/terraform/main.tf +++ b/templates/workspace_services/mysql/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.112.0" + version = "3.117.0" } random = { source = "hashicorp/random" diff --git a/templates/workspace_services/ohdsi/porter.yaml b/templates/workspace_services/ohdsi/porter.yaml index 42e92517cf..cc9b60e4eb 100644 --- a/templates/workspace_services/ohdsi/porter.yaml +++ b/templates/workspace_services/ohdsi/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-service-ohdsi -version: 0.3.1 +version: 0.3.3 description: "An OHDSI workspace service" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/ohdsi/terraform/.terraform.lock.hcl b/templates/workspace_services/ohdsi/terraform/.terraform.lock.hcl index 970ec99a31..c94c1c299f 100644 --- a/templates/workspace_services/ohdsi/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/ohdsi/terraform/.terraform.lock.hcl @@ -2,22 +2,22 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "4.14.0" + constraints = "4.14.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:FYZ9qh8i3X2gDmUTe1jJ/VzdSyjGjVmhBzv2R8D6CBo=", + "zh:05aaea16fc5f27b14d9fbad81654edf0638949ed3585576b2219c76a2bee095a", + "zh:065ce6ed16ba3fa7efcf77888ea582aead54e6a28f184c6701b73d71edd64bb0", + "zh:3c0cd17c249d18aa2e0120acb5f0c14810725158b379a67fec1331110e7c50df", + "zh:5a3ba3ffb2f1ce519fe3bf84a7296aa5862c437c70c62f0b0a5293bea9f2d01c", + "zh:7a8e9d72fa2714f4d567270b1761d4b4e788de7c15dada7db0cf0e29933185a2", + "zh:a11e190073f31c1238c15af29b9162e0f4564f6b0cd0310a3fa94102738450dc", + "zh:a5c004114410cc6dcb8fed584c9f3b84283b58025b0073a7e88d2bdb27840dfa", + "zh:a674a41db118e244eda7591e455d2ec338626664e0856e4125e909eb038f78db", + "zh:b5139010e4cbb2cb1a27c775610593c1c8063d3a7c82b00a65006509c434df2f", + "zh:cbb031223ccd8b099ac4d19b92641142f330b90f2fc6452843e445bae28f832c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f7e7db1b94082a4ac3d4af3dabe7bbd335e1679305bf8e29d011f0ee440724ca", ] } diff --git a/templates/workspace_services/ohdsi/terraform/atlas_ui.tf b/templates/workspace_services/ohdsi/terraform/atlas_ui.tf index dd688bcf66..24879a80da 100644 --- a/templates/workspace_services/ohdsi/terraform/atlas_ui.tf +++ b/templates/workspace_services/ohdsi/terraform/atlas_ui.tf @@ -30,12 +30,12 @@ resource "azurerm_linux_web_app" "atlas_ui" { client_affinity_enabled = false site_config { - always_on = false - ftps_state = "Disabled" + always_on = false + ftps_state = "Disabled" + minimum_tls_version = "1.3" application_stack { - docker_image = "index.docker.io/${local.atlas_ui_docker_image_name}" - docker_image_tag = local.atlas_ui_docker_image_tag + docker_image_name = "index.docker.io/${local.atlas_ui_docker_image_name}:${local.atlas_ui_docker_image_tag}" } } diff --git a/templates/workspace_services/ohdsi/terraform/ohdsi_web_api.tf b/templates/workspace_services/ohdsi/terraform/ohdsi_web_api.tf index a3640d4676..2606dbc5cb 100644 --- a/templates/workspace_services/ohdsi/terraform/ohdsi_web_api.tf +++ b/templates/workspace_services/ohdsi/terraform/ohdsi_web_api.tf @@ -33,12 +33,12 @@ resource "azurerm_linux_web_app" "ohdsi_webapi" { client_affinity_enabled = false site_config { - always_on = true - ftps_state = "Disabled" + always_on = true + ftps_state = "Disabled" + minimum_tls_version = "1.3" application_stack { - docker_image = "index.docker.io/${local.ohdsi_api_docker_image_name}" - docker_image_tag = local.ohdsi_api_docker_image_tag + docker_image_name = "index.docker.io/${local.ohdsi_api_docker_image_name}:${local.ohdsi_api_docker_image_tag}" } } diff --git a/templates/workspace_services/ohdsi/terraform/providers.tf b/templates/workspace_services/ohdsi/terraform/providers.tf index 60a908b843..ce7c57ddcc 100644 --- a/templates/workspace_services/ohdsi/terraform/providers.tf +++ b/templates/workspace_services/ohdsi/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=4.14.0" } local = { source = "hashicorp/local" diff --git a/templates/workspace_services/openai/porter.yaml b/templates/workspace_services/openai/porter.yaml index 0ad52412a3..cef921c3dd 100644 --- a/templates/workspace_services/openai/porter.yaml +++ b/templates/workspace_services/openai/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-service-openai -version: 1.0.5 +version: 1.0.6 description: "An OpenAI workspace service" registry: azuretre dockerfile: Dockerfile.tmpl diff --git a/templates/workspace_services/openai/terraform/.terraform.lock.hcl b/templates/workspace_services/openai/terraform/.terraform.lock.hcl index b76fcebd94..6e8b87ea6b 100644 --- a/templates/workspace_services/openai/terraform/.terraform.lock.hcl +++ b/templates/workspace_services/openai/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = "3.112.0" + version = "3.117.0" + constraints = "3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspace_services/openai/terraform/main.tf b/templates/workspace_services/openai/terraform/main.tf index 2b388cf167..1de2b80dfe 100644 --- a/templates/workspace_services/openai/terraform/main.tf +++ b/templates/workspace_services/openai/terraform/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.112.0" + version = "3.117.0" } } diff --git a/templates/workspaces/airlock-import-review/Dockerfile.tmpl b/templates/workspaces/airlock-import-review/Dockerfile.tmpl index dcc43bb677..4a9293704a 100644 --- a/templates/workspaces/airlock-import-review/Dockerfile.tmpl +++ b/templates/workspaces/airlock-import-review/Dockerfile.tmpl @@ -9,14 +9,13 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ apt-get update && apt-get install -y git jq curl ca-certificates patch --no-install-recommends -ARG AZURE_TRE_VERSION="0.19.1" - WORKDIR ${BUNDLE_DIR} -# Copy all files from base workspace (note: some of them will be overwritten with the following COPY command) -RUN curl -o azuretre.tar.gz -L "https://github.com/microsoft/AzureTRE/archive/refs/tags/v${AZURE_TRE_VERSION}.tar.gz" \ - && tar -xzf azuretre.tar.gz "AzureTRE-${AZURE_TRE_VERSION}/templates/workspaces/base" --strip-components=4 --skip-old-files \ - && rm -rf azuretre.tar.gz +# copy files from the base workspace (dir passed to porter via --build-context) +# ignoring hadolint DL3022; for details see https://github.com/hadolint/hadolint/issues/830 +# +# hadolint ignore=DL3022 +COPY --from=base --link . ${BUNDLE_DIR}/ # Copy and change the file extension of .terraform file to .tf COPY ./terraform/import_review_resources.terraform "${BUNDLE_DIR}"/terraform/import_review_resources.tf diff --git a/templates/workspaces/airlock-import-review/parameters.json b/templates/workspaces/airlock-import-review/parameters.json index 3af43908f2..56dd8ec0de 100755 --- a/templates/workspaces/airlock-import-review/parameters.json +++ b/templates/workspaces/airlock-import-review/parameters.json @@ -135,6 +135,18 @@ "source": { "env": "AZURE_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspaces/airlock-import-review/porter-build-context.env b/templates/workspaces/airlock-import-review/porter-build-context.env new file mode 100644 index 0000000000..cd13784bcc --- /dev/null +++ b/templates/workspaces/airlock-import-review/porter-build-context.env @@ -0,0 +1,4 @@ +# Use this file to declare an additional build context to be passed to Porter +# https://github.com/getporter/porter/blob/6f859710675cee5936a810ab654f205264f0feb2/docs/content/docs/references/cli/build.md?plain=1#L41 + +export PORTER_BUILD_CONTEXT="base=../base" diff --git a/templates/workspaces/airlock-import-review/porter.yaml b/templates/workspaces/airlock-import-review/porter.yaml index 3cd8e7c3d2..a651294ea9 100644 --- a/templates/workspaces/airlock-import-review/porter.yaml +++ b/templates/workspaces/airlock-import-review/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-airlock-import-review -version: 0.13.5 +version: 0.14.3 description: "A workspace to do Airlock Data Import Reviews for Azure TRE" dockerfile: Dockerfile.tmpl registry: azuretre @@ -112,6 +112,12 @@ parameters: type: string description: "The SKU used when deploying an Azure App Service Plan" default: "P1v3" + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: app_role_id_workspace_owner @@ -178,6 +184,8 @@ install: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: false arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -219,6 +227,8 @@ upgrade: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: false arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -283,6 +293,8 @@ uninstall: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: false arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspaces/airlock-import-review/template_schema.json b/templates/workspaces/airlock-import-review/template_schema.json index e05a0d87e7..09d7d6be87 100644 --- a/templates/workspaces/airlock-import-review/template_schema.json +++ b/templates/workspaces/airlock-import-review/template_schema.json @@ -15,7 +15,9 @@ "title": "App Service Plan SKU", "description": "The SKU that will be used when deploying an Azure App Service Plan.", "default": "P1v3", + "updateable": true, "enum": [ + "P0v3", "P1v3", "P1v2", "S1" diff --git a/templates/workspaces/base/parameters.json b/templates/workspaces/base/parameters.json index fa261465ca..f95d146600 100755 --- a/templates/workspaces/base/parameters.json +++ b/templates/workspaces/base/parameters.json @@ -151,13 +151,13 @@ { "name": "enable_cmk_encryption", "source": { - "env": "enable_cmk_encryption" + "env": "ENABLE_CMK_ENCRYPTION" } }, { "name": "key_store_id", "source": { - "env": "key_store_id" + "env": "KEY_STORE_ID" } } ] diff --git a/templates/workspaces/base/porter.yaml b/templates/workspaces/base/porter.yaml index 1cdc6d24a6..ed48af1a9b 100644 --- a/templates/workspaces/base/porter.yaml +++ b/templates/workspaces/base/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-base -version: 1.7.1 +version: 1.9.3 description: "A base Azure TRE workspace" dockerfile: Dockerfile.tmpl registry: azuretre @@ -122,6 +122,10 @@ parameters: - name: key_store_id type: string default: "" + - name: storage_account_redundancy + type: string + default: "GRS" + description: "The redundancy option for the storage account in the workspace: GRS (Geo-Redundant Storage) or ZRS (Zone-Redundant Storage)." outputs: - name: app_role_id_workspace_owner @@ -191,6 +195,7 @@ install: arm_environment: ${ bundle.parameters.arm_environment } enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } key_store_id: ${ bundle.parameters.key_store_id } + storage_account_redundancy: ${ bundle.parameters.storage_account_redundancy } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -235,6 +240,7 @@ upgrade: arm_environment: ${ bundle.parameters.arm_environment } enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } key_store_id: ${ bundle.parameters.key_store_id } + storage_account_redundancy: ${ bundle.parameters.storage_account_redundancy } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -302,6 +308,7 @@ uninstall: arm_environment: ${ bundle.parameters.arm_environment } enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } key_store_id: ${ bundle.parameters.key_store_id } + storage_account_redundancy: ${ bundle.parameters.storage_account_redundancy } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspaces/base/template_schema.json b/templates/workspaces/base/template_schema.json index 3d6cdf0e16..24cec47f34 100644 --- a/templates/workspaces/base/template_schema.json +++ b/templates/workspaces/base/template_schema.json @@ -27,12 +27,24 @@ "title": "App Service Plan SKU", "description": "The SKU that will be used when deploying an Azure App Service Plan.", "default": "P1v3", + "updateable": true, "enum": [ + "P0v3", "P1v3", "P1v2", "S1" ] }, + "storage_account_redundancy": { + "type": "string", + "title": "Storage Account Redundancy", + "description": "The redundancy option for the storage account in the workspace: GRS (Geo-Redundant Storage) or ZRS (Zone-Redundant Storage).", + "default": "GRS", + "enum": [ + "GRS", + "ZRS" + ] + }, "address_space_size": { "type": "string", "title": "Address space size", @@ -284,6 +296,7 @@ "description", "overview", "shared_storage_quota", + "storage_account_redundancy", "app_service_plan_sku", "address_space_size", "address_space", diff --git a/templates/workspaces/base/terraform/.terraform.lock.hcl b/templates/workspaces/base/terraform/.terraform.lock.hcl index 4d6d7dab37..8a229681d9 100644 --- a/templates/workspaces/base/terraform/.terraform.lock.hcl +++ b/templates/workspaces/base/terraform/.terraform.lock.hcl @@ -43,21 +43,21 @@ provider "registry.terraform.io/hashicorp/azuread" { } provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.112.0" - constraints = ">= 3.112.0, 3.112.0" + version = "3.117.0" + constraints = ">= 3.117.0, 3.117.0" hashes = [ - "h1:5KSVV/O2eG6ty/3/qpOLQFQqJd96KEPzsTHItslJaMw=", - "zh:341c22454d24a75792aa99fbbc0c156f368534b7bb04eef4701b85995c7526a4", - "zh:3708656d75061c92f7208cc731b946c991ad343a443f8ff0ef082f077b7580b9", - "zh:38ca06f9f45705c648f04f272bd9483397693ea8da6db788cd7955f49ab79d6b", - "zh:3f305adb5ee0032e0ea68d198a089ecfd0127092930e99fa51377a250292b592", - "zh:4ae2fc6065164a819f576f705e634ebf5059f983149a41dad909719fea96145a", - "zh:5d376ac7dd71898a94038d6b6b8036dfec4c0216d832ec1135c855bf3e58eb5f", - "zh:63d2ff296d3aee5787e12c759a6a3d5aa15a574456aebbe11b833f01adf3faef", - "zh:8ad8746741f7f0ac10da6f1d105f26ebeb6e4d944f58ba749e86d7c9a67da3db", - "zh:abec182594ee8a21d72a5f23d3aa7fa45247488539fce6ed648c9c255d8bf972", - "zh:bf704b400be4181333b38c0306949f26326a9aa5ae68b4167e2fb8ee7fb13618", - "zh:c072938f8695f725fc5fbe986a54890f00d520cce570006390dc5bbc51b2a4ea", + "h1:Ynfg+Iy7x6K8M6W1AhqXCe3wkoiqIQhROlca7C3KC3w=", + "zh:2e25f47492366821a786762369f0e0921cc9452d64bfd5075f6fdfcf1a9c6d70", + "zh:41eb34f2f7469bf3eb1019dfb0e7fc28256f809824016f4f8b9d691bf473b2ac", + "zh:48bb9c87b3d928da1abc1d3db75453c9725de4674c612daf3800160cc7145d30", + "zh:5d6b0de0bbd78943fcc65c53944ef4496329e247f434c6eab86ed051c5cea67b", + "zh:78c9f6fdb1206a89cf0e6706b4f46178169a93b6c964a4cad8a321058ccbd9b4", + "zh:793b702c352589d4360b580d4a1cf654a7439d2ad6bdb7bfea91de07bc4b0fac", + "zh:7ed687ff0a5509463a592f97431863574fe5cc80a34e395be06766215b8c6285", + "zh:955ba18789bd15592824eb426a8d0f38595bd09fffc6939c1c58933489c1a71e", + "zh:bf5949a55be0714cd9c8815d472eae4baa48ba06d0f6bf2b96775869acda8a54", + "zh:da5d31f635abd2c645ffc76d6176d73f646128e73720cc368247cc424975c127", + "zh:eed5a66d59883c9c56729b0a964a2b60d758ea7489ef3e920a6fbd48518ce5f5", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/templates/workspaces/base/terraform/aad/providers.tf b/templates/workspaces/base/terraform/aad/providers.tf index 1e5c3de630..4cf4c2b88a 100644 --- a/templates/workspaces/base/terraform/aad/providers.tf +++ b/templates/workspaces/base/terraform/aad/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112.0" + version = ">= 3.117.0" } azuread = { source = "hashicorp/azuread" diff --git a/templates/workspaces/base/terraform/airlock/providers.tf b/templates/workspaces/base/terraform/airlock/providers.tf index 66749ee4e0..3bc52af981 100644 --- a/templates/workspaces/base/terraform/airlock/providers.tf +++ b/templates/workspaces/base/terraform/airlock/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112.0" + version = ">= 3.117.0" } } } diff --git a/templates/workspaces/base/terraform/airlock/storage_accounts.tf b/templates/workspaces/base/terraform/airlock/storage_accounts.tf index 0bcea7c812..2090c08d30 100644 --- a/templates/workspaces/base/terraform/airlock/storage_accounts.tf +++ b/templates/workspaces/base/terraform/airlock/storage_accounts.tf @@ -5,8 +5,12 @@ resource "azurerm_storage_account" "sa_import_approved" { resource_group_name = var.ws_resource_group_name account_tier = "Standard" account_replication_type = "LRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -28,6 +32,14 @@ resource "azurerm_storage_account" "sa_import_approved" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge( var.tre_workspace_tags, { @@ -68,8 +80,12 @@ resource "azurerm_storage_account" "sa_export_internal" { resource_group_name = var.ws_resource_group_name account_tier = "Standard" account_replication_type = "LRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -91,6 +107,14 @@ resource "azurerm_storage_account" "sa_export_internal" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge( var.tre_workspace_tags, { @@ -131,8 +155,12 @@ resource "azurerm_storage_account" "sa_export_inprogress" { resource_group_name = var.ws_resource_group_name account_tier = "Standard" account_replication_type = "LRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -146,6 +174,14 @@ resource "azurerm_storage_account" "sa_export_inprogress" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -201,8 +237,12 @@ resource "azurerm_storage_account" "sa_export_rejected" { resource_group_name = var.ws_resource_group_name account_tier = "Standard" account_replication_type = "LRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -224,6 +264,14 @@ resource "azurerm_storage_account" "sa_export_rejected" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge( var.tre_workspace_tags, { @@ -264,8 +312,12 @@ resource "azurerm_storage_account" "sa_export_blocked" { resource_group_name = var.ws_resource_group_name account_tier = "Standard" account_replication_type = "LRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + shared_access_key_enabled = false + local_user_enabled = false # Important! we rely on the fact that the blob craeted events are issued when the creation of the blobs are done. # This is true ONLY when Hierarchical Namespace is DISABLED @@ -287,6 +339,14 @@ resource "azurerm_storage_account" "sa_export_blocked" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + tags = merge( var.tre_workspace_tags, { @@ -336,18 +396,3 @@ resource "azurerm_role_assignment" "api_sa_data_contributor" { role_definition_name = "Storage Blob Data Contributor" principal_id = data.azurerm_user_assigned_identity.api_id.principal_id } - -resource "azurerm_storage_account_customer_managed_key" "sa_encryption" { - for_each = var.enable_cmk_encryption ? { - "sa_import_approved" = azurerm_storage_account.sa_import_approved, - "sa_export_internal" = azurerm_storage_account.sa_export_internal, - "sa_export_inprogress" = azurerm_storage_account.sa_export_inprogress, - "sa_export_rejected" = azurerm_storage_account.sa_export_rejected, - "sa_export_blocked" = azurerm_storage_account.sa_export_blocked - } : {} - - storage_account_id = each.value.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} diff --git a/templates/workspaces/base/terraform/airlock/variables.tf b/templates/workspaces/base/terraform/airlock/variables.tf index 70e4b1a101..5f299e00fa 100644 --- a/templates/workspaces/base/terraform/airlock/variables.tf +++ b/templates/workspaces/base/terraform/airlock/variables.tf @@ -28,12 +28,9 @@ variable "arm_environment" { variable "enable_cmk_encryption" { type = bool } -variable "key_store_id" { - type = string -} variable "encryption_identity_id" { type = string } -variable "kv_encryption_key_name" { +variable "encryption_key_versionless_id" { type = string } diff --git a/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf b/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf index 89c44962d6..6c8068de24 100644 --- a/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf +++ b/templates/workspaces/base/terraform/azure-monitor/azure-monitor.tf @@ -19,10 +19,16 @@ resource "azurerm_storage_account" "app_insights" { account_kind = "StorageV2" account_tier = "Standard" account_replication_type = "LRS" + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false cross_tenant_replication_enabled = false + local_user_enabled = false tags = var.tre_workspace_tags + # unclear the implications on az-monitor, so leaving it for now. + # shared_access_key_enabled = false + dynamic "identity" { for_each = var.enable_cmk_encryption ? [1] : [] content { @@ -31,6 +37,14 @@ resource "azurerm_storage_account" "app_insights" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = var.encryption_key_versionless_id + user_assigned_identity_id = var.encryption_identity_id + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -42,14 +56,6 @@ resource "azurerm_storage_account" "app_insights" { lifecycle { ignore_changes = [infrastructure_encryption_enabled, tags] } } -resource "azurerm_storage_account_customer_managed_key" "app_insights_stg_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.app_insights.id - key_vault_id = var.key_store_id - key_name = var.kv_encryption_key_name - user_assigned_identity_id = var.encryption_identity_id -} - resource "azurerm_log_analytics_linked_storage_account" "workspace_storage_ingestion" { data_source_type = "Ingestion" resource_group_name = var.resource_group_name diff --git a/templates/workspaces/base/terraform/azure-monitor/providers.tf b/templates/workspaces/base/terraform/azure-monitor/providers.tf index 5bfac97eea..073110f2d1 100644 --- a/templates/workspaces/base/terraform/azure-monitor/providers.tf +++ b/templates/workspaces/base/terraform/azure-monitor/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">= 3.112.0" + version = ">= 3.117.0" } azapi = { diff --git a/templates/workspaces/base/terraform/azure-monitor/variables.tf b/templates/workspaces/base/terraform/azure-monitor/variables.tf index 1171350646..f021759860 100644 --- a/templates/workspaces/base/terraform/azure-monitor/variables.tf +++ b/templates/workspaces/base/terraform/azure-monitor/variables.tf @@ -40,12 +40,9 @@ variable "enable_local_debugging" { variable "enable_cmk_encryption" { type = bool } -variable "key_store_id" { - type = string -} variable "encryption_identity_id" { type = string } -variable "kv_encryption_key_name" { +variable "encryption_key_versionless_id" { type = string } diff --git a/templates/workspaces/base/terraform/cmk-encryption.tf b/templates/workspaces/base/terraform/cmk-encryption.tf index 954bfd2361..9776a62d7e 100644 --- a/templates/workspaces/base/terraform/cmk-encryption.tf +++ b/templates/workspaces/base/terraform/cmk-encryption.tf @@ -4,7 +4,7 @@ resource "azurerm_user_assigned_identity" "encryption_identity" { location = azurerm_resource_group.ws.location tags = local.tre_workspace_tags - name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" + name = local.encryption_identity_name lifecycle { ignore_changes = [tags] } } diff --git a/templates/workspaces/base/terraform/locals.tf b/templates/workspaces/base/terraform/locals.tf index 37a8263266..bc84d98e4d 100644 --- a/templates/workspaces/base/terraform/locals.tf +++ b/templates/workspaces/base/terraform/locals.tf @@ -8,5 +8,6 @@ locals { tre_id = var.tre_id tre_workspace_id = var.tre_resource_id } - kv_encryption_key_name = "tre-encryption-${local.workspace_resource_name_suffix}" + kv_encryption_key_name = "tre-encryption-${local.workspace_resource_name_suffix}" + encryption_identity_name = "id-encryption-${var.tre_id}-${local.short_workspace_id}" } diff --git a/templates/workspaces/base/terraform/network/providers.tf b/templates/workspaces/base/terraform/network/providers.tf index 70f3cb4865..2817aac3ab 100644 --- a/templates/workspaces/base/terraform/network/providers.tf +++ b/templates/workspaces/base/terraform/network/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = ">=3.112.0" + version = ">=3.117.0" } } } diff --git a/templates/workspaces/base/terraform/providers.tf b/templates/workspaces/base/terraform/providers.tf index c5fc4b9740..e541b4a4c8 100644 --- a/templates/workspaces/base/terraform/providers.tf +++ b/templates/workspaces/base/terraform/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.112.0" + version = "=3.117.0" } azuread = { source = "hashicorp/azuread" diff --git a/templates/workspaces/base/terraform/storage.tf b/templates/workspaces/base/terraform/storage.tf index 5992d88d7c..7fc6f00a2c 100644 --- a/templates/workspaces/base/terraform/storage.tf +++ b/templates/workspaces/base/terraform/storage.tf @@ -3,7 +3,9 @@ resource "azurerm_storage_account" "stg" { resource_group_name = azurerm_resource_group.ws.name location = azurerm_resource_group.ws.location account_tier = "Standard" - account_replication_type = "GRS" + account_replication_type = var.storage_account_redundancy + table_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" + queue_encryption_key_type = var.enable_cmk_encryption ? "Account" : "Service" allow_nested_items_to_be_public = false is_hns_enabled = true cross_tenant_replication_enabled = false // not technically needed as cross tenant replication not supported when is_hns_enabled = true @@ -17,6 +19,14 @@ resource "azurerm_storage_account" "stg" { } } + dynamic "customer_managed_key" { + for_each = var.enable_cmk_encryption ? [1] : [] + content { + key_vault_key_id = azurerm_key_vault_key.encryption_key[0].versionless_id + user_assigned_identity_id = azurerm_user_assigned_identity.encryption_identity[0].id + } + } + # changing this value is destructive, hence attribute is in lifecycle.ignore_changes block below infrastructure_encryption_enabled = true @@ -139,13 +149,3 @@ resource "azurerm_private_endpoint" "stgdfspe" { subresource_names = ["dfs"] } } - -resource "azurerm_storage_account_customer_managed_key" "stg_encryption" { - count = var.enable_cmk_encryption ? 1 : 0 - storage_account_id = azurerm_storage_account.stg.id - key_vault_id = var.key_store_id - key_name = local.kv_encryption_key_name - user_assigned_identity_id = azurerm_user_assigned_identity.encryption_identity[0].id - - depends_on = [azurerm_key_vault_key.encryption_key] -} diff --git a/templates/workspaces/base/terraform/variables.tf b/templates/workspaces/base/terraform/variables.tf index 42eecbad69..b3e2812785 100644 --- a/templates/workspaces/base/terraform/variables.tf +++ b/templates/workspaces/base/terraform/variables.tf @@ -132,6 +132,10 @@ variable "enable_cmk_encryption" { variable "key_store_id" { type = string description = "ID of the Key Vault to store CMKs in (only used if enable_cmk_encryption is true)" - default = null } +variable "storage_account_redundancy" { + type = string + default = "GRS" + description = "The redundancy option for the storage account in the workspace: GRS (Geo-Redundant Storage) or ZRS (Zone-Redundant Storage)." +} diff --git a/templates/workspaces/base/terraform/workspace.tf b/templates/workspaces/base/terraform/workspace.tf index 2e6b36d173..10fb74c6a7 100644 --- a/templates/workspaces/base/terraform/workspace.tf +++ b/templates/workspaces/base/terraform/workspace.tf @@ -45,21 +45,20 @@ module "aad" { } module "airlock" { - count = var.enable_airlock ? 1 : 0 - source = "./airlock" - location = var.location - tre_id = var.tre_id - tre_workspace_tags = local.tre_workspace_tags - ws_resource_group_name = azurerm_resource_group.ws.name - enable_local_debugging = var.enable_local_debugging - services_subnet_id = module.network.services_subnet_id - short_workspace_id = local.short_workspace_id - airlock_processor_subnet_id = module.network.airlock_processor_subnet_id - arm_environment = var.arm_environment - enable_cmk_encryption = var.enable_cmk_encryption - key_store_id = var.key_store_id - kv_encryption_key_name = local.kv_encryption_key_name - encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption_identity[0].id : null + count = var.enable_airlock ? 1 : 0 + source = "./airlock" + location = var.location + tre_id = var.tre_id + tre_workspace_tags = local.tre_workspace_tags + ws_resource_group_name = azurerm_resource_group.ws.name + enable_local_debugging = var.enable_local_debugging + services_subnet_id = module.network.services_subnet_id + short_workspace_id = local.short_workspace_id + airlock_processor_subnet_id = module.network.airlock_processor_subnet_id + arm_environment = var.arm_environment + enable_cmk_encryption = var.enable_cmk_encryption + encryption_key_versionless_id = var.enable_cmk_encryption ? azurerm_key_vault_key.encryption_key[0].versionless_id : null + encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption_identity[0].id : null depends_on = [ module.network, ] @@ -81,8 +80,7 @@ module "azure_monitor" { azure_monitor_agentsvc_dns_zone_id = module.network.azure_monitor_agentsvc_dns_zone_id blob_core_dns_zone_id = module.network.blobcore_zone_id enable_cmk_encryption = var.enable_cmk_encryption - key_store_id = var.key_store_id - kv_encryption_key_name = local.kv_encryption_key_name + encryption_key_versionless_id = var.enable_cmk_encryption ? azurerm_key_vault_key.encryption_key[0].versionless_id : null encryption_identity_id = var.enable_cmk_encryption ? azurerm_user_assigned_identity.encryption_identity[0].id : null enable_local_debugging = var.enable_local_debugging depends_on = [ diff --git a/templates/workspaces/unrestricted/Dockerfile.tmpl b/templates/workspaces/unrestricted/Dockerfile.tmpl index 04eaf4f8c0..1b12132325 100644 --- a/templates/workspaces/unrestricted/Dockerfile.tmpl +++ b/templates/workspaces/unrestricted/Dockerfile.tmpl @@ -9,14 +9,13 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ apt-get update && apt-get install -y git jq curl ca-certificates patch --no-install-recommends -ARG AZURE_TRE_VERSION="0.19.1" - WORKDIR $BUNDLE_DIR -# Copy all files from base workspace (note: some of them will be overwritten with the following COPY command) -RUN curl -o azuretre.tar.gz -L "https://github.com/microsoft/AzureTRE/archive/refs/tags/v${AZURE_TRE_VERSION}.tar.gz" \ - && tar -xzf azuretre.tar.gz "AzureTRE-${AZURE_TRE_VERSION}/templates/workspaces/base" --strip-components=4 --skip-old-files \ - && rm -rf azuretre.tar.gz +# copy files from the base workspace (dir passed to porter via --build-context) +# ignoring hadolint DL3022; for details see https://github.com/hadolint/hadolint/issues/830 +# +# hadolint ignore=DL3022 +COPY --from=base --link . ${BUNDLE_DIR}/ # PORTER_MIXINS diff --git a/templates/workspaces/unrestricted/parameters.json b/templates/workspaces/unrestricted/parameters.json index 77e5faf93c..00ce33d3ba 100755 --- a/templates/workspaces/unrestricted/parameters.json +++ b/templates/workspaces/unrestricted/parameters.json @@ -147,6 +147,18 @@ "source": { "env": "ARM_ENVIRONMENT" } + }, + { + "name": "enable_cmk_encryption", + "source": { + "env": "ENABLE_CMK_ENCRYPTION" + } + }, + { + "name": "key_store_id", + "source": { + "env": "KEY_STORE_ID" + } } ] } diff --git a/templates/workspaces/unrestricted/porter-build-context.env b/templates/workspaces/unrestricted/porter-build-context.env new file mode 100644 index 0000000000..cd13784bcc --- /dev/null +++ b/templates/workspaces/unrestricted/porter-build-context.env @@ -0,0 +1,4 @@ +# Use this file to declare an additional build context to be passed to Porter +# https://github.com/getporter/porter/blob/6f859710675cee5936a810ab654f205264f0feb2/docs/content/docs/references/cli/build.md?plain=1#L41 + +export PORTER_BUILD_CONTEXT="base=../base" diff --git a/templates/workspaces/unrestricted/porter.yaml b/templates/workspaces/unrestricted/porter.yaml index 97f1531997..6c9cb1e43b 100644 --- a/templates/workspaces/unrestricted/porter.yaml +++ b/templates/workspaces/unrestricted/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-unrestricted -version: 0.12.5 +version: 0.13.3 description: "A base Azure TRE workspace" dockerfile: Dockerfile.tmpl registry: azuretre @@ -118,6 +118,12 @@ parameters: - name: enable_airlock type: boolean default: false + - name: enable_cmk_encryption + type: boolean + default: false + - name: key_store_id + type: string + default: "" outputs: - name: app_role_id_workspace_owner @@ -185,6 +191,8 @@ install: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: ${ bundle.parameters.enable_airlock } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -227,6 +235,8 @@ upgrade: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: ${ bundle.parameters.enable_airlock } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" @@ -292,6 +302,8 @@ uninstall: app_service_plan_sku: ${ bundle.parameters.app_service_plan_sku } enable_airlock: ${ bundle.parameters.enable_airlock } arm_environment: ${ bundle.parameters.arm_environment } + enable_cmk_encryption: ${ bundle.parameters.enable_cmk_encryption } + key_store_id: ${ bundle.parameters.key_store_id } backendConfig: use_azuread_auth: "true" use_oidc: "true" diff --git a/templates/workspaces/unrestricted/template_schema.json b/templates/workspaces/unrestricted/template_schema.json index 3fbaa16a3a..6ebbb1c159 100644 --- a/templates/workspaces/unrestricted/template_schema.json +++ b/templates/workspaces/unrestricted/template_schema.json @@ -27,7 +27,9 @@ "title": "App Service Plan SKU", "description": "The SKU that will be used when deploying an Azure App Service Plan.", "default": "P1v3", + "updateable": true, "enum": [ + "P0v3", "P1v3", "P1v2", "S1" diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index b605e4bc71..0000000000 --- a/ui/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# TRE UI - -Please see the docs for a full overview and deployment instructions. - -The UI was built using Create React App and Microsoft Fluent UI. Further details on this in the ./app/README. - -## Run the UI -- Ensure `deploy_ui=false` is not set in your `./config.yaml` file -- In the root of the repo, run `make tre-deploy`. This will provision the necessary resources in Azure, build and deploy the UI to Azure blob storage, behind the App Gateway used for the API. The deployment process will also create the necessary `config.json`, using the `config.source.json` as a template. -- In Microsoft Entra ID, locate the TRE Client Apps app (possibly called Swagger App). In the Authentication section add reply URIs for: - - `http://localhost:3000` (if wanting to run locally) - - Your deployed App Url - `https://{TRE_ID}.{LOCATION}.cloudapp.azure.com`. - -At this point you should be able to navigate to the web app in Azure, log in, and see your workspaces. - -### To run locally -- `cd ./ui/app` -- `yarn start` - -After making changes to the code, redeploy to Azure by running `make build-and-deploy-ui` in the root of the dev container. diff --git a/ui/app/.prettierrc b/ui/app/.prettierrc new file mode 100644 index 0000000000..dc6958febb --- /dev/null +++ b/ui/app/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": false, + "semi": true +} diff --git a/ui/app/README.md b/ui/app/README.md deleted file mode 100644 index 387b475e1e..0000000000 --- a/ui/app/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Getting Started with Create React App and Fluent UI - -This is a [Create React App](https://github.com/facebook/create-react-app) based repo that comes with Fluent UI pre-installed! - -## Available Scripts - -In the project directory, you can run: - -### `yarn start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `yarn test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `yarn build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `yarn eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit [CLA](https://cla.microsoft.com). - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/ui/app/eslint.config.js b/ui/app/eslint.config.js new file mode 100644 index 0000000000..c61fac8c72 --- /dev/null +++ b/ui/app/eslint.config.js @@ -0,0 +1,24 @@ +import eslintConfigPrettier from "eslint-config-prettier"; +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import typescriptParser from "@typescript-eslint/parser"; + +export default [ + { + files: ["**/*.{ts,tsx}"], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + }, + plugins: { + "@typescript-eslint": typescriptEslint, + }, + rules: {}, + }, + eslintConfigPrettier, +]; diff --git a/ui/app/index.html b/ui/app/index.html new file mode 100644 index 0000000000..119f64be91 --- /dev/null +++ b/ui/app/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + Azure TRE + + + +
+ + + diff --git a/ui/app/package.json b/ui/app/package.json index c23a15d135..9d8321c24a 100644 --- a/ui/app/package.json +++ b/ui/app/package.json @@ -1,52 +1,67 @@ { "name": "tre-ui", - "version": "0.6.3", + "version": "0.7.0", "private": true, + "type": "module", "dependencies": { "@azure/msal-browser": "^2.35.0", "@azure/msal-react": "^1.5.12", - "@fluentui/react": "^8.120.3", - "@fluentui/react-file-type-icons": "^8.11.20", + "@fluentui/react": "^8.122.1", + "@fluentui/react-file-type-icons": "^8.12.6", "@reduxjs/toolkit": "^1.8.6", - "@rjsf/core": "^4.2.3", - "@rjsf/fluent-ui": "^4.2.3", - "@testing-library/dom": "^7.21.4", - "@testing-library/jest-dom": "^6.2.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.5.0", - "@types/node": "^20.16.12", - "@types/react": "^18.3.3", + "@rjsf/core": "^5.24.3", + "@rjsf/fluent-ui": "^5.24.3", + "@rjsf/utils": "^5.24.3", + "@rjsf/validator-ajv8": "^5.24.3", + "@types/node": "^20.17.14", + "@types/react": "^18.3.16", "@types/react-dom": "^18.2.6", + "@vitejs/plugin-react-swc": "latest", "moment": "^2.29.4", - "node-sass": "^8.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^8.0.3", "react-redux": "^8.0.4", - "react-router-dom": "6.27.0", + "react-router-dom": "6.28.2", "remark-gfm": "^3.0.1", "typescript": "^5.6.3", + "vite": "latest", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-svgr": "latest", + "vite-tsconfig-paths": "latest", "web-vitals": "^3.3.0" }, "devDependencies": { - "@babel/core": "^7.23.7", - "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "@babel/plugin-syntax-flow": "^7.23.3", - "@babel/plugin-transform-react-jsx": "^7.23.4", - "react-scripts": "5.0.1" + "@testing-library/dom": "^7.21.4", + "@testing-library/jest-dom": "^6.2.0", + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.0", + "@types/node": "^20.17.14", + "@types/react": "^18.3.16", + "@types/react-dom": "^18.2.6", + "@typescript-eslint/eslint-plugin": "^8.24.0", + "@typescript-eslint/parser": "^8.24.0", + "@vitest/coverage-v8": "latest", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.14.0", + "jsdom": "latest", + "prettier": "3.5.0", + "sass-embedded": "^1.83.4", + "vitest": "latest" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "start": "vite", + "build": "tsc && vite build", + "serve": "vite preview", + "test": "vitest", + "test:coverage": "vitest run --coverage --watch=false", + "lint": "eslint .", + "format": "prettier --write ." }, "browserslist": { "production": [ diff --git a/ui/app/public/index.html b/ui/app/public/index.html index 3412482b4e..493346824c 100644 --- a/ui/app/public/index.html +++ b/ui/app/public/index.html @@ -1,15 +1,18 @@ - + - + - + - + Azure TRE diff --git a/ui/app/src/App.scss b/ui/app/src/App.scss index 704e6dfeb0..0f7c87cd8b 100644 --- a/ui/app/src/App.scss +++ b/ui/app/src/App.scss @@ -1,7 +1,8 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', - 'Droid Sans', 'Helvetica Neue', sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", + "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -16,7 +17,8 @@ h2 { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: + source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } .tre-logout-message { @@ -88,7 +90,7 @@ ul.tre-notifications-steps-list li { .tre-home-link { color: #fff; text-decoration: none; - font-size:1.2rem; + font-size: 1.2rem; } .tre-user-menu { @@ -118,7 +120,7 @@ ul.tre-notifications-steps-list li { } } -.tre-hide-chevron i[data-icon-name=ChevronDown] { +.tre-hide-chevron i[data-icon-name="ChevronDown"] { display: none; } @@ -174,50 +176,50 @@ ul.tre-notifications-steps-list li { margin-bottom: 10px; } -input[readonly]{ - background-color:#efefef; +input[readonly] { + background-color: #efefef; } -.tre-badge{ - border-radius:4px; +.tre-badge { + border-radius: 4px; background-color: #efefef; - padding:2px 6px; + padding: 2px 6px; text-transform: capitalize; - display:inline-block; - font-size:12px; + display: inline-block; + font-size: 12px; } -.tre-badge-in-progress{ +.tre-badge-in-progress { background-color: #ce7b00; color: #fff; } -.tre-badge-failed{ +.tre-badge-failed { background-color: #990000; color: #fff; padding-top: 4px; padding-left: 7px; font-size: 16px; } -.tre-badge-success{ +.tre-badge-success { background-color: #006600; color: #fff; } -.tre-complex-list{ +.tre-complex-list { list-style: none; - padding:0 0 0 20px; - margin:0; + padding: 0 0 0 20px; + margin: 0; } -.tre-complex-list-border{ +.tre-complex-list-border { border-bottom: 1px #ccc solid; - margin-left:-15px; + margin-left: -15px; } -.tre-complex-list-string{ - padding-left:20px; +.tre-complex-list-string { + padding-left: 20px; } -.tre-complex-list .ms-Icon{ - font-size:12px!important; +.tre-complex-list .ms-Icon { + font-size: 12px !important; font-weight: bold; position: relative; - top:2px; + top: 2px; } // Classes for rendering power state badges @@ -226,7 +228,8 @@ input[readonly]{ color: #636262; margin: 6px; - .tre-power-on, .tre-power-off { + .tre-power-on, + .tre-power-off { height: 8px; width: 8px; background-color: #006600; @@ -267,28 +270,81 @@ input[readonly]{ } /* border around sub-blocks */ -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object, -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-array { +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object, +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-array { border: 1px #ccc dashed; padding: 10px; background-color: #fcfcfc; } /* sub titles and sub-sub titles */ -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > label.ms-Label, -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-array > label.ms-Label { +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > label.ms-Label, +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-array + > label.ms-Label { font-size: 20px; } -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > .ms-Grid > .ms-Grid-row > .ms-Grid-col > label.ms-Label, -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-array > .ms-Grid > .ms-Grid-row > .ms-Grid-col > label.ms-Label { +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > .ms-Grid + > .ms-Grid-row + > .ms-Grid-col + > label.ms-Label, +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-array + > .ms-Grid + > .ms-Grid-row + > .ms-Grid-col + > label.ms-Label { font-size: 16px; } /* remove secondary template description at the bottom of each template + sub blocks */ .rjsf > .ms-Grid-col > span:last-of-type, -.rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > span:last-of-type, -.rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > .ms-Grid > .ms-Grid-row > .ms-Grid-col > span:last-of-type { +.rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > span:last-of-type, +.rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > .ms-Grid + > .ms-Grid-row + > .ms-Grid-col + > span:last-of-type { display: none; } diff --git a/ui/app/src/App.test.tsx b/ui/app/src/App.test.tsx index ac873a8bce..d55194710b 100644 --- a/ui/app/src/App.test.tsx +++ b/ui/app/src/App.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { App } from './App'; +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { App } from "./App"; it('renders "Welcome to Your Fluent UI App"', () => { render(); diff --git a/ui/app/src/App.tsx b/ui/app/src/App.tsx index 8f21c2d260..24d0b5220d 100644 --- a/ui/app/src/App.tsx +++ b/ui/app/src/App.tsx @@ -1,45 +1,65 @@ -import React, { useEffect, useState } from 'react'; -import { DefaultPalette, IStackStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react'; -import './App.scss'; -import { TopNav } from './components/shared/TopNav'; -import { Routes, Route } from 'react-router-dom'; -import { RootLayout } from './components/root/RootLayout'; -import { WorkspaceProvider } from './components/workspaces/WorkspaceProvider'; -import { MsalAuthenticationTemplate } from '@azure/msal-react'; -import { InteractionType } from '@azure/msal-browser'; -import { Workspace } from './models/workspace'; -import { AppRolesContext } from './contexts/AppRolesContext'; -import { WorkspaceContext } from './contexts/WorkspaceContext'; -import { GenericErrorBoundary } from './components/shared/GenericErrorBoundary'; -import { HttpMethod, ResultType, useAuthApiCall } from './hooks/useAuthApiCall'; -import { ApiEndpoint } from './models/apiEndpoints'; -import { CreateUpdateResource } from './components/shared/create-update-resource/CreateUpdateResource'; -import { CreateUpdateResourceContext } from './contexts/CreateUpdateResourceContext'; -import { CreateFormResource, ResourceType } from './models/resourceType'; -import { Footer } from './components/shared/Footer'; -import { initializeFileTypeIcons } from '@fluentui/react-file-type-icons'; -import { CostResource } from './models/costs'; -import { CostsContext } from './contexts/CostsContext'; -import { LoadingState } from './models/loadingState'; +import React, { useEffect, useState } from "react"; +import { + DefaultPalette, + IStackStyles, + MessageBar, + MessageBarType, + Stack, +} from "@fluentui/react"; +import "./App.scss"; +import { TopNav } from "./components/shared/TopNav"; +import { Routes, Route } from "react-router-dom"; +import { RootLayout } from "./components/root/RootLayout"; +import { WorkspaceProvider } from "./components/workspaces/WorkspaceProvider"; +import { MsalAuthenticationTemplate } from "@azure/msal-react"; +import { InteractionType } from "@azure/msal-browser"; +import { Workspace } from "./models/workspace"; +import { AppRolesContext } from "./contexts/AppRolesContext"; +import { WorkspaceContext } from "./contexts/WorkspaceContext"; +import { GenericErrorBoundary } from "./components/shared/GenericErrorBoundary"; +import { HttpMethod, ResultType, useAuthApiCall } from "./hooks/useAuthApiCall"; +import { ApiEndpoint } from "./models/apiEndpoints"; +import { CreateUpdateResource } from "./components/shared/create-update-resource/CreateUpdateResource"; +import { CreateUpdateResourceContext } from "./contexts/CreateUpdateResourceContext"; +import { CreateFormResource, ResourceType } from "./models/resourceType"; +import { Footer } from "./components/shared/Footer"; +import { initializeFileTypeIcons } from "@fluentui/react-file-type-icons"; +import { CostResource } from "./models/costs"; +import { CostsContext } from "./contexts/CostsContext"; +import { LoadingState } from "./models/loadingState"; export const App: React.FunctionComponent = () => { const [appRoles, setAppRoles] = useState([] as Array); const [selectedWorkspace, setSelectedWorkspace] = useState({} as Workspace); const [workspaceRoles, setWorkspaceRoles] = useState([] as Array); - const [workspaceCosts, setWorkspaceCosts] = useState([] as Array); + const [workspaceCosts, setWorkspaceCosts] = useState( + [] as Array, + ); const [costs, setCosts] = useState([] as Array); - const [costsLoadingState, setCostsLoadingState] = useState(LoadingState.Loading); + const [costsLoadingState, setCostsLoadingState] = useState( + LoadingState.Loading, + ); const [createFormOpen, setCreateFormOpen] = useState(false); - const [createFormResource, setCreateFormResource] = useState({ resourceType: ResourceType.Workspace } as CreateFormResource); + const [createFormResource, setCreateFormResource] = useState({ + resourceType: ResourceType.Workspace, + } as CreateFormResource); const apiCall = useAuthApiCall(); // set the app roles useEffect(() => { const setAppRolesOnLoad = async () => { - await apiCall(ApiEndpoint.Workspaces, HttpMethod.Get, undefined, undefined, ResultType.JSON, (roles: Array) => { - setAppRoles(roles); - }, true); + await apiCall( + ApiEndpoint.Workspaces, + HttpMethod.Get, + undefined, + undefined, + ResultType.JSON, + (roles: Array) => { + setAppRoles(roles); + }, + true, + ); }; setAppRolesOnLoad(); }, [apiCall]); @@ -49,77 +69,113 @@ export const App: React.FunctionComponent = () => { return ( <> - - ) => { setAppRoles(roles) } - }}> - { - setCreateFormResource(createFormResource); - setCreateFormOpen(true); - } - }} > - - setCreateFormOpen(false)} - resourceType={createFormResource.resourceType} - parentResource={createFormResource.resourceParent} - onAddResource={createFormResource.onAdd} - workspaceApplicationIdURI={createFormResource.workspaceApplicationIdURI} - updateResource={createFormResource.updateResource} - /> - - - - - - - ) => {setCosts(costs)}, - setLoadingState: (loadingState: LoadingState) => {setCostsLoadingState(loadingState)} - }}> - - } /> - ) => {setWorkspaceRoles(roles)}, - costs: workspaceCosts, - setCosts: (costs: Array) => {setWorkspaceCosts(costs)}, - workspace: selectedWorkspace, - setWorkspace: (w: Workspace) => {setSelectedWorkspace(w)}, - workspaceApplicationIdURI: selectedWorkspace.properties?.scope_id - }}> - - - } /> - - - - - -