From 6e973f768ed2e31c56cbcfc612bf7a328e637104 Mon Sep 17 00:00:00 2001 From: Tope Emmanuel Date: Fri, 13 Oct 2023 16:41:57 +0100 Subject: [PATCH] Added Review DFE sign and modified secrets sources from yaml to vault WHY: DFE Signin is a requirement and migrating the secrets is also requisite after migration HOW: by adaping the get_next_mapping.sh to use AKS and by fetching secrets from vault --- .github/common_environment.yml | 1 + .../workflows/actions/deploy_v2/action.yml | 1 + .github/workflows/build.yml | 160 ++++++++++-------- Makefile | 5 +- script/get_next_mapping_aks.sh | 71 ++++++++ terraform/aks/application.tf | 1 + terraform/aks/output.tf | 3 + terraform/aks/variables.tf | 6 +- 8 files changed, 174 insertions(+), 74 deletions(-) create mode 100755 script/get_next_mapping_aks.sh create mode 100644 terraform/aks/output.tf diff --git a/.github/common_environment.yml b/.github/common_environment.yml index ee82b4a278..ad3eee7339 100644 --- a/.github/common_environment.yml +++ b/.github/common_environment.yml @@ -10,3 +10,4 @@ REVIEW_APPLICATION: review-school-experience AKS_REVIEW_APPLICATION: get-school-experience-review-pr PAAS_APPLICATION_NAME: school-experience-app + AKS_APPLICATION_NAME: get-school-experience diff --git a/.github/workflows/actions/deploy_v2/action.yml b/.github/workflows/actions/deploy_v2/action.yml index fb30a33003..587c7eaf1d 100644 --- a/.github/workflows/actions/deploy_v2/action.yml +++ b/.github/workflows/actions/deploy_v2/action.yml @@ -14,6 +14,7 @@ inputs: description: Pull Request Reference required: false + outputs: deploy-url: value: ${{ steps.set_env_var.outputs.deploy_url }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e826fb92f..219278c930 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,7 @@ permissions: pull-requests: write jobs: + build: name: Build runs-on: ubuntu-latest @@ -28,16 +29,7 @@ jobs: - uses: Azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - uses: DfE-Digital/keyvault-yaml-secret@v1 - id: keyvault-yaml-secret - with: - keyvault: ${{ secrets.KEY_VAULT}} - secret: SE-INFRA-SECRETS - key: SLACK-WEBHOOK - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + creds: ${{ secrets.GSE_REPO_AZ_CREDENTIALS }} - name: Lint Dockerfile uses: brpaz/hadolint-action@master @@ -82,6 +74,15 @@ jobs: build-args: SHA=${{ steps.sha.outputs.short }} + - name: Fetch slack web hook + uses: azure/CLI@v1 + id: slack-web-hook + with: + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SLACK-WEBHOOK" --vault-name "${{ secrets.INF_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SLACK-WEBHOOK=$SECRET_VALUE" >> $GITHUB_OUTPUT + - name: Slack Notification if: failure() && github.ref == 'refs/heads/master' uses: rtCamp/action-slack-notify@master @@ -89,7 +90,7 @@ jobs: SLACK_COLOR: ${{env.SLACK_ERROR}} SLACK_MESSAGE: 'There has been a failure building the application' SLACK_TITLE: 'Failure Building Application' - SLACK_WEBHOOK: ${{ steps.keyvault-yaml-secret.outputs.SLACK-WEBHOOK }} + SLACK_WEBHOOK: " ${{ steps.slack-web-hook.outputs.SLACK-WEBHOOK }} " spec_tests: name: Unit Tests @@ -106,15 +107,6 @@ jobs: with: creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: DfE-Digital/keyvault-yaml-secret@v1 - id: keyvault-yaml-secret - with: - keyvault: ${{ secrets.KEY_VAULT}} - secret: SE-INFRA-SECRETS - key: SLACK-WEBHOOK - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: @@ -149,7 +141,6 @@ jobs: name: unit_tests path: ${{ github.workspace }}/out/test-report.xml - - name: Keep Code Coverage Report if: always() uses: actions/upload-artifact@v3 @@ -170,16 +161,7 @@ jobs: - uses: Azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - uses: DfE-Digital/keyvault-yaml-secret@v1 - id: keyvault-yaml-secret - with: - keyvault: ${{ secrets.KEY_VAULT}} - secret: SE-INFRA-SECRETS - key: SNYK-TOKEN - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + creds: ${{ secrets.GSE_REPO_AZ_CREDENTIALS }} - name: Login to GitHub Container Registry uses: docker/login-action@v3 @@ -188,10 +170,20 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Fetch synk token from key vault + uses: azure/CLI@v1 + id: fetch-synk-token + with: + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SNYK-TOKEN" --vault-name "${{ secrets.INF_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SNYK-TOKEN=$SECRET_VALUE" >> $GITHUB_OUTPUT + - name: Run Snyk to check Docker image for vulnerabilities uses: snyk/actions/docker@master env: - SNYK_TOKEN: ${{ steps.keyvault-yaml-secret.outputs.SNYK-TOKEN }} + SNYK_TOKEN: ${{ steps.fetch-synk-token.outputs.SNYK-TOKEN }} + with: image: ${{needs.build.outputs.DOCKER_IMAGE}} args: --severity-threshold=high --file=Dockerfile --exclude-app-vulns --policy-path=/.snyk @@ -311,16 +303,7 @@ jobs: - uses: Azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - uses: DfE-Digital/keyvault-yaml-secret@v1 - id: keyvault-yaml-secret - with: - keyvault: ${{ secrets.KEY_VAULT}} - secret: SE-INFRA-SECRETS - key: SONAR-TOKEN - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + creds: ${{ secrets.GSE_REPO_AZ_CREDENTIALS }} - name: Download Test Artifacts uses: actions/download-artifact@v3 @@ -330,11 +313,20 @@ jobs: - name: Fixup report file paths run: sudo sed -i "s?/app/app?/github/workspace/app?" ${{ github.workspace }}/out/Code_Coverage/coverage.json + - name: Fetch Sonar token from key vault + uses: azure/CLI@v1 + id: fetch-sonar-token + with: + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SONAR-TOKEN" --vault-name "${{ secrets.INF_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SONAR-TOKEN=$SECRET_VALUE" >> $GITHUB_OUTPUT + - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ steps.keyvault-yaml-secret.outputs.SONAR-TOKEN }} + SONAR_TOKEN: ${{ steps.fetch-sonar-token.outputs.SONAR-TOKEN }} prepare: name: Configure Matrix Deployments @@ -390,7 +382,7 @@ jobs: environment: name: ${{matrix.environment}}_aks concurrency: ${{matrix.environment}}_${{github.event.number}}_aks - needs: [ prepare ] + needs: [prepare ] runs-on: ubuntu-latest steps: - name: Check out the repo @@ -403,7 +395,14 @@ jobs: with: creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Get dsi_hostname AKS + if: matrix.environment == 'Review' + run: | + dsi_static_hostname=$( ${GITHUB_WORKSPACE}/script/get_next_mapping_aks.sh ${{github.event.number}} get-school-experience-review-pr-${{github.event.number}}.test.teacherservices.cloud ) + echo "dsi_static_hostname=${dsi_static_hostname}" >> $GITHUB_ENV + - name: Trigger Deployment to ${{matrix.environment}} + id: deploy-aks uses: ./.github/workflows/actions/deploy_v2 with: environment: ${{matrix.environment}} @@ -411,18 +410,19 @@ jobs: azure-credentials: ${{ secrets.AZURE_CREDENTIALS }} pr: ${{github.event.number}} - - name: Determine DfE Sign In Message + + - name: Determine DfE Sign In Message - AKS + if: matrix.environment == 'Review' uses: haya14busa/action-cond@v1 id: dsiMessage with: - cond: ${{ env.STATIC_ROUTE != '' }} - if_true: ':white_check_mark: DfE sign in route obtained: https://${{env.STATIC_ROUTE}}.london.cloudapps.digital' - if_false: ':warning: **DfE sign in route pool exhausted (close some open PRs!)**' + cond: ${{ env.dsi_static_hostname != '' }} + if_true: ':white_check_mark: DfE AKS sign in route obtained: https://${{ env.dsi_static_hostname }}' + if_false: ':warning: **DfE AKS sign in route pool for AKS exhausted (close some open PRs!)**' - name: Post sticky pull request comment if: matrix.environment == 'Review' uses: marocchino/sticky-pull-request-comment@v2 - with: recreate: true header: AKS @@ -451,14 +451,23 @@ jobs: with: release_id: ${{steps.tag_id.outputs.release_id}} + - name: Fetch slack token + uses: azure/CLI@v1 + id: fetch-slack-secret + with: + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SLACK-WEBHOOK" --vault-name "${{ secrets.INF_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SLACK-WEBHOOK=$SECRET_VALUE" >> $GITHUB_OUTPUT + - name: Slack Release Notification if: matrix.environment == 'Production' && steps.tag_id.outputs.release_id uses: rtCamp/action-slack-notify@master env: SLACK_COLOR: ${{env.SLACK_SUCCESS}} + SLACK_MESSAGE: ${{ fromJson( steps.tag_id.outputs.release_body) }} SLACK_TITLE: "Release Published: ${{steps.tag_id.outputs.release_name}}" - SLACK_MESSAGE: ${{ fromJson( steps.tag_id.outputs.release_body) }} - SLACK_WEBHOOK: ${{ steps.keyvault-yaml-secret.outputs.SLACK-RELEASE-NOTE-WEBHOOK }} + SLACK_WEBHOOK: "${{steps.fetch-slack-secret.outputs.SLACK-WEBHOOK}}" MSG_MINIMAL: true - name: Slack Notification @@ -468,12 +477,12 @@ jobs: SLACK_COLOR: ${{env.SLACK_ERROR}} SLACK_TITLE: Failure in Post-Development Deploy SLACK_MESSAGE: Failure with initialising ${{matrix.environment}} deployment for ${{env.APPLICATION}} - SLACK_WEBHOOK: ${{ steps.keyvault-yaml-secret.outputs.SLACK-WEBHOOK }} + SLACK_WEBHOOK: "${{steps.fetch-slack-secret.outputs.SLACK-WEBHOOK}}" owasp: name: 'OWASP Test' runs-on: ubuntu-latest - needs: [ deployments_aks ] + needs: [deployments_aks ] if: github.event_name == 'push' && github.ref == 'refs/heads/master' steps: - name: Checkout @@ -484,40 +493,49 @@ jobs: - uses: Azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + creds: ${{ secrets.GSE_REPO_AZ_CREDENTIALS }} - - uses: DfE-Digital/keyvault-yaml-secret@v1 - id: keyvault-infra-secret + - name: Fetch SECURE USERNAME + uses: azure/CLI@v1 + id: fetch-username with: - keyvault: ${{ secrets.KEY_VAULT}} - secret: SE-INFRA-SECRETS - key: SLACK-WEBHOOK - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SECURE-USERNAME" --vault-name "${{ secrets.APP_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SECURE_USERNAME=$SECRET_VALUE" >> $GITHUB_OUTPUT - - uses: DfE-Digital/keyvault-yaml-secret@v1 - id: keyvault-yaml-secret + - name: Fetch SECURE PASSWORD + uses: azure/CLI@v1 + id: fetch-password with: - keyvault: ${{ secrets.KEY_VAULT}} - secret: SE-SECRETS - key: SECURE_USERNAME , SECURE_PASSWORD - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SECURE-PASSWORD" --vault-name "${{ secrets.APP_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SECURE_PASSWORD=$SECRET_VALUE" >> $GITHUB_OUTPUT - name: ZAP Scan uses: zaproxy/action-full-scan@v0.7.0 with: token: ${{ secrets.GITHUB_TOKEN }} docker_name: 'owasp/zap2docker-stable' - target: 'https://${{ steps.keyvault-yaml-secret.outputs.SECURE_USERNAME }}:${{ steps.keyvault-yaml-secret.outputs.SECURE_PASSWORD }}@${{env.PAAS_APPLICATION_NAME}}-dev.${{env.DOMAIN}}' + target: 'https://${{ steps.fetch-username.outputs.SECURE_USERNAME}}:${{ steps.fetch-password.outputs.SECURE_PASSWORD }}@${{env.AKS_APPLICATION_NAME}}-development.${{env.REVIEW_AKS_DOMAIN}}' rules_file_name: '.zap/rules.tsv' cmd_options: '-a' + - name: Fetch secrets from key vault + uses: azure/CLI@v1 + id: fetch-slack-secret + with: + inlineScript: | + SECRET_VALUE=$(az keyvault secret show --name "SLACK-WEBHOOK" --vault-name "${{ secrets.INF_KEY_VAULT}}" --query "value" -o tsv) + echo "::add-mask::$SECRET_VALUE" + echo "SLACK-WEBHOOK=$SECRET_VALUE" >> $GITHUB_OUTPUT + - name: Slack Notification if: failure() uses: rtCamp/action-slack-notify@master env: SLACK_COLOR: ${{env.SLACK_FAILURE}} - SLACK_MESSAGE: 'Pipeline Failure carrying out OWASP Testing on https://${{env.PAAS_APPLICATION_NAME}}-dev.${{env.DOMAIN}}/' + SLACK_MESSAGE: 'Pipeline Failure carrying out OWASP Testing on https://${{env.AKS_APPLICATION_NAME}}-development.${{env.REVIEW_AKS_DOMAIN}}/' SLACK_TITLE: 'Failure: OWSAP Testing has failed on Development' - SLACK_WEBHOOK: ${{ steps.keyvault-infra-secret.outputs.SLACK-WEBHOOK }} + SLACK_WEBHOOK: "${{ steps.fetch-slack-secret.outputs.SLACK-WEBHOOK}}" diff --git a/Makefile b/Makefile index 2fc6ba1c16..cc53304509 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,9 @@ review: review_aks: $(eval include global_config/review.sh) $(if $(PR_NUMBER), , $(error Missing environment variable "PR_NUMBER")) - $(eval export PR_NAME=review-school-experience-${PR_NUMBER}) - $(eval export TF_VAR_static_route=$(shell script/get_next_mapping.sh ${PR_NAME})) + $(eval export PR_NAME=get-school-experience-review-pr-${PR_NUMBER}.test.teacherservices.cloud) + $(eval export TF_VAR_paas_application_name=${PR_NAME}) + $(eval export TF_VAR_dsi_hostname=$(shell script/get_next_mapping_aks.sh ${PR_NUMBER} ${PR_NAME})) $(eval export TF_VAR_environment=review-pr-$(PR_NUMBER)) .PHONY: staging diff --git a/script/get_next_mapping_aks.sh b/script/get_next_mapping_aks.sh new file mode 100755 index 0000000000..79825767c7 --- /dev/null +++ b/script/get_next_mapping_aks.sh @@ -0,0 +1,71 @@ +pr_number=${1} +pr_name=${2} +maximun_ing_num=20 + +get_all_relevant_ingresses() { + # Find if it is already in the list of ingresses + ings=($(kubectl get ing -n git-development -o json | \ + jq -r '.items[] | select(.metadata.name | startswith("get-school-experience-review-pr")) | .metadata.name')) + + echo "${ings[@]}" +} + +check_existing_dsi_ingress() { + # Find if it is already in the list of existing linked DSI ingresses + servicename="get-school-experience-review-pr-${pr_number}" + ings=($(kubectl get ing -o=custom-columns='NAME:.metadata.name,SVCs:..service.name' -n git-development | grep "${servicename}" | grep -v "${pr_name}")) + echo "${ings}" +} + +extract_numbers_from_list() { + local all_existing_ings=$1 + local pattern="get-school-experience-review-pr-([0-9]+)\.test\.teacherservices\.cloud" + local all_existing_review_ings=() + for input_string in ${all_existing_ings}; do + if [[ "$input_string" =~ $pattern ]]; then + itemval="${BASH_REMATCH[1]}" + if ((1 <= itemval && itemval <= maximun_ing_num)); then + all_existing_review_ings+=("${BASH_REMATCH[1]}") + fi + fi + done + echo "${all_existing_review_ings[@]}" +} + +is_number_in_list() { + local number=$1 + shift + local all_list=$* + for i in ${all_list}; do + if [ "$i" = "$number" ]; then + return 0 + fi + done + return 1 +} + +# check if there is an existing linked ingress +existing_ing=$(check_existing_dsi_ingress) + +if [ -n "$existing_ing" ]; then + echo "$existing_ing" +else + # get all existing ingresses + ings_list_result=$(get_all_relevant_ingresses) + + # extract the number part of the ingresses from ingress lists + numbers_from_ings=$(extract_numbers_from_list "${ings_list_result[@]}") + all_possible_ings=$(seq 1 $maximun_ing_num) + selectednumber=0 + for possible_ing in ${all_possible_ings}; do + if ! is_number_in_list "$possible_ing" "${numbers_from_ings[@]}" ; then + selectednumber=$possible_ing + break + fi + done + if [ "$selectednumber" = 0 ]; then + echo "" + else + echo "get-school-experience-review-pr-$selectednumber.test.teacherservices.cloud" + fi +fi diff --git a/terraform/aks/application.tf b/terraform/aks/application.tf index 90be3f07cc..3708f85c03 100644 --- a/terraform/aks/application.tf +++ b/terraform/aks/application.tf @@ -39,6 +39,7 @@ module "web_application" { docker_image = var.docker_image command = ["/app/docker-entrypoint.sh", "-m", "-f"] probe_path = null + web_external_hostnames = local.web_external_hostnames } module "worker_application" { diff --git a/terraform/aks/output.tf b/terraform/aks/output.tf new file mode 100644 index 0000000000..aed4120d9d --- /dev/null +++ b/terraform/aks/output.tf @@ -0,0 +1,3 @@ +output "dsi_hostname" { + value = var.dsi_hostname +} diff --git a/terraform/aks/variables.tf b/terraform/aks/variables.tf index e705cc4d92..dcd52c0c07 100644 --- a/terraform/aks/variables.tf +++ b/terraform/aks/variables.tf @@ -145,9 +145,13 @@ variable "statuscake_password_name" { default = "SC-PASSWORD" description = "The name of the statuscake password" } +variable "dsi_hostname" { + description = "The static hostname for DFE sign-in " + default = "" +} locals { azure_credentials = try(jsondecode(var.azure_credentials_json), null) - postgres_ssl_mode = var.enable_postgres_ssl ? "require" : "disable" app_name_suffix = var.app_name == null ? var.environment : var.app_name + web_external_hostnames = var.dsi_hostname == "" ? [] : [var.dsi_hostname] }