diff --git a/.github/actions/containerize/action.yml b/.github/actions/containerize/action.yml index 2c43266a6ff0..c0809d3afd6f 100644 --- a/.github/actions/containerize/action.yml +++ b/.github/actions/containerize/action.yml @@ -10,31 +10,24 @@ description: | inputs: docker: - type: boolean description: | Package the binary into a Docker container suitable for the Docker and AWS registries. We'll automatically determine the correct tags and target depending on the vault edition. - default: true + default: 'true' goarch: - type: string description: The Go GOARCH value environment variable to set during the build. goos: - type: string description: The Go GOOS value environment variable to set during the build. redhat: - type: boolean description: Package the binary into a UBI container suitable for the Redhat Quay registry. - default: false + default: 'false' vault-binary-path: - type: string description: The path to the vault binary. default: dist/vault vault-edition: - type: string description: The edition of vault to build. default: ce vault-version: - type: string description: The vault version. outputs: @@ -48,31 +41,52 @@ runs: - id: vars shell: bash run: | - if [[ '${{ inputs.vault-edition }}' =~ 'ce' ]]; then - # CE containers - container_version='${{ inputs.vault-version }}' - docker_container_tags='docker.io/hashicorp/vault:${{ inputs.vault-version }} public.ecr.aws/hashicorp/vault:${{ inputs.vault-version }}' - docker_container_target='default' - redhat_container_tags='quay.io/redhat-isv-containers/5f89bb5e0b94cf64cfeb500a:${{ inputs.vault-version }}-ubi' - redhat_container_target='ubi' - else - # Ent containers - container_version='${{ inputs.vault-version }}+${{ inputs.vault-edition }}' - - if [[ '${{ inputs.vault-edition }}' =~ 'fips' ]]; then - # Ent FIPS 140-2 containers - docker_container_tags='docker.io/hashicorp/vault-enterprise-fips:${{ inputs.vault-version }}-${{ inputs.vault-edition }} public.ecr.aws/hashicorp/vault-enterprise-fips:${{ inputs.vault-version }}-${{ inputs.vault-edition }}' - docker_container_target='ubi-fips' - redhat_container_tags='quay.io/redhat-isv-containers/6283f645d02c6b16d9caeb8e:${{ inputs.vault-version }}-${{ inputs.vault-edition }}-ubi' - redhat_container_target='ubi-fips' - else - # All other Ent containers + case '${{ inputs.vault-edition }}' in + "ce") + container_version='${{ inputs.vault-version }}' + docker_container_tags='docker.io/hashicorp/vault:${{ inputs.vault-version }} public.ecr.aws/hashicorp/vault:${{ inputs.vault-version }}' + docker_container_target='default' + redhat_container_tags='quay.io/redhat-isv-containers/5f89bb5e0b94cf64cfeb500a:${{ inputs.vault-version }}-ubi' + redhat_container_target='ubi' + ;; + "ent") + container_version='${{ inputs.vault-version }}+${{ inputs.vault-edition }}' docker_container_tags='docker.io/hashicorp/vault-enterprise:${{ inputs.vault-version }}-${{ inputs.vault-edition}} public.ecr.aws/hashicorp/vault-enterprise:${{ inputs.vault-version }}-${{ inputs.vault-edition }}' docker_container_target='default' redhat_container_tags='quay.io/redhat-isv-containers/5f89bb9242e382c85087dce2:${{ inputs.vault-version }}-${{ inputs.vault-edition }}-ubi' redhat_container_target='ubi' - fi - fi + ;; + "ent.hsm") + container_version='${{ inputs.vault-version }}+${{ inputs.vault-edition }}' + docker_container_tags='docker.io/hashicorp/vault-enterprise:${{ inputs.vault-version }}-${{ inputs.vault-edition}} public.ecr.aws/hashicorp/vault-enterprise:${{ inputs.vault-version }}-${{ inputs.vault-edition }}' + docker_container_target='ubi-hsm' + redhat_container_tags='quay.io/redhat-isv-containers/5f89bb9242e382c85087dce2:${{ inputs.vault-version }}-${{ inputs.vault-edition }}-ubi' + redhat_container_target='ubi-hsm' + ;; + "ent.hsm.fips1402") + container_version='${{ inputs.vault-version }}+${{ inputs.vault-edition }}' + docker_container_tags='docker.io/hashicorp/vault-enterprise:${{ inputs.vault-version }}-${{ inputs.vault-edition}} public.ecr.aws/hashicorp/vault-enterprise:${{ inputs.vault-version }}-${{ inputs.vault-edition }}' + docker_container_target='ubi-hsm-fips' + redhat_container_tags='quay.io/redhat-isv-containers/5f89bb9242e382c85087dce2:${{ inputs.vault-version }}-${{ inputs.vault-edition }}-ubi' + redhat_container_target='ubi-hsm-fips' + ;; + "ent.fips1402") + # NOTE: For compatibility we still publish the ent.fips1402 containers to different + # namespaces. All ent, ent.hsm, and ent.hsm.fips1402 containers are released in the + # enterprise namespaces. After we've updated the upstream docker action to support + # multiple tags we can start to tag images with both namespaces, publish to both, and + # eventually sunset the fips1402 specific namespaces. + container_version='${{ inputs.vault-version }}+${{ inputs.vault-edition }}' + docker_container_tags='docker.io/hashicorp/vault-enterprise-fips:${{ inputs.vault-version }}-${{ inputs.vault-edition }} public.ecr.aws/hashicorp/vault-enterprise-fips:${{ inputs.vault-version }}-${{ inputs.vault-edition }}' + docker_container_target='ubi-fips' + redhat_container_tags='quay.io/redhat-isv-containers/6283f645d02c6b16d9caeb8e:${{ inputs.vault-version }}-${{ inputs.vault-edition }}-ubi' + redhat_container_target='ubi-fips' + ;; + *) + echo "Cannot generate container tags for unknown vault edition: ${{ inputs.vault-edition }}" 2>&1 + exit 1 + ;; + esac { echo "container-version=${container_version}" echo "docker-container-tags=${docker_container_tags}" diff --git a/.github/workflows/build-artifacts-ce.yml b/.github/workflows/build-artifacts-ce.yml index 9669ae3f78dc..0d0a0731bc50 100644 --- a/.github/workflows/build-artifacts-ce.yml +++ b/.github/workflows/build-artifacts-ce.yml @@ -9,12 +9,15 @@ on: inputs: build-all: type: boolean + description: Build all extended artifacts default: false build-date: type: string + description: The date associated with the revision SHA required: true checkout-ref: type: string + description: The repo Git SHA to checkout default: "" compute-build: type: string # JSON encoded to support passing arrays @@ -30,15 +33,19 @@ on: required: true vault-revision: type: string + description: The revision SHA of vault required: true vault-version: type: string + description: The version of vault required: true vault-version-package: type: string + description: Whether or not to package the binary as Debian and RPM packages required: true web-ui-cache-key: type: string + description: The UI asset cache key required: true workflow_call: inputs: @@ -119,7 +126,26 @@ jobs: # Outputs are strings so we need to encode our collection outputs as JSON. testable-containers: | [ - { "artifact": "${{ github.event.repository.name }}_default_linux_amd64_${{ inputs.vault-version }}_${{ inputs.vault-revision }}.docker.tar" } + { + "sample": "ce_default_linux_amd64_ent_docker", + "artifact": "${{ github.event.repository.name }}_default_linux_amd64_${{ inputs.vault-version }}_${{ inputs.vault-revision }}.docker.tar", + "edition": "ce" + }, + { + "sample": "ce_default_linux_arm64_ce_docker", + "artifact": "${{ github.event.repository.name }}_default_linux_arm64_${{ inputs.vault-version }}_${{ inputs.vault-revision }}.docker.tar", + "edition": "ce" + }, + { + "sample": "ce_ubi_linux_amd64_ce_redhat", + "artifact": "${{ github.event.repository.name}}_ubi_linux_amd64_${{ inputs.vault-version}}_${{ inputs.vault-revision }}.docker.redhat.tar", + "edition": "ce" + }, + { + "sample": "ce_ubi_linux_arm64_ce_redhat", + "artifact": "${{ github.event.repository.name}}_ubi_linux_arm64_${{ inputs.vault-version}}_${{ inputs.vault-revision }}.docker.redhat.tar", + "edition": "ce" + } ] testable-packages: | [ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1ab18b15ebe5..e6e142905110 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -255,16 +255,18 @@ jobs: - setup - ui - artifacts - uses: ./.github/workflows/enos-run-k8s.yml + uses: ./.github/workflows/test-run-enos-scenario-containers.yml strategy: fail-fast: false matrix: include: ${{ fromJSON(needs.artifacts.outputs.testable-containers) }} with: - artifact-build-date: ${{ needs.setup.outputs.build-date }} - artifact-name: ${{ matrix.artifact }} - artifact-revision: ${{ needs.setup.outputs.vault-revision }} - artifact-version: ${{ needs.setup.outputs.vault-version-metadata }} + build-artifact-name: ${{ matrix.artifact }} + sample-max: 1 + sample-name: ${{ matrix.sample }} + vault-edition: ${{ matrix.edition }} + vault-revision: ${{ needs.setup.outputs.vault-revision }} + vault-version: ${{ needs.setup.outputs.vault-version-metadata }} secrets: inherit completed-successfully: diff --git a/.github/workflows/enos-run-k8s.yml b/.github/workflows/enos-run-k8s.yml deleted file mode 100644 index 0edf7f034e6a..000000000000 --- a/.github/workflows/enos-run-k8s.yml +++ /dev/null @@ -1,113 +0,0 @@ ---- -name: enos-k8s - -on: - workflow_call: - inputs: - artifact-build-date: - required: false - type: string - artifact-name: - required: true - type: string - artifact-revision: - required: true - type: string - artifact-version: - required: true - type: string - -env: - ARTIFACT_BUILD_DATE: ${{ inputs.artifact-build-date }} - ARTIFACT_NAME: ${{ inputs.artifact-name }} - ARTIFACT_REVISION: ${{ inputs.artifact-revision }} - ARTIFACT_VERSION: ${{ inputs.artifact-version }} - -jobs: - enos: - name: Integration - runs-on: ${{ fromJSON(contains(inputs.artifact-name, 'vault-enterprise') && (contains(inputs.artifact-name, 'arm64') && '["self-hosted","ondemand","os=ubuntu-arm","type=c6g.xlarge"]' || '["self-hosted","linux","small"]') || '"ubuntu-latest"') }} - env: - GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Set up Terraform - uses: hashicorp/setup-terraform@v3 - with: - # the Terraform wrapper will break Terraform execution in Enos because - # it changes the output to text when we expect it to be JSON. - terraform_wrapper: false - - name: Set up Enos - uses: hashicorp/action-setup-enos@v1 - with: - github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - - name: Download Docker Image - id: download - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: ${{ inputs.artifact-name }} - path: ./enos/support/downloads - - name: Prepare for scenario execution - env: - IS_ENT: ${{ contains(env.ARTIFACT_NAME, 'vault-enterprise' ) }} - run: | - mkdir -p ./enos/support/terraform-plugin-cache - if [ "$IS_ENT" == true ]; then - echo "${{ secrets.VAULT_LICENSE }}" > ./enos/support/vault.hclic || true - echo "edition=ent" >> "$GITHUB_ENV" - echo "edition set to 'ent'" - echo "image_repo=hashicorp/vault-enterprise" >> "$GITHUB_ENV" - echo "image repo set to 'hashicorp/vault-enterprise'" - else - echo "edition=ce" >> "$GITHUB_ENV" - echo "edition set to 'ce'" - echo "image_repo=hashicorp/vault" >> "$GITHUB_ENV" - echo "image repo set to 'hashicorp/vault'" - fi - - name: Run Enos scenario - id: run - # Continue once and retry to handle occasional blips when creating - # infrastructure. - continue-on-error: true - env: - ENOS_VAR_tfc_api_token: ${{ secrets.TF_API_TOKEN }} - ENOS_VAR_terraform_plugin_cache_dir: ../support/terraform-plugin-cache - ENOS_VAR_vault_build_date: ${{ env.ARTIFACT_BUILD_DATE }} - ENOS_VAR_vault_product_version: ${{ env.ARTIFACT_VERSION }} - ENOS_VAR_vault_product_revision: ${{ env.ARTIFACT_REVISION }} - ENOS_VAR_vault_docker_image_archive: ${{steps.download.outputs.download-path}}/${{ env.ARTIFACT_NAME }} - ENOS_VAR_vault_image_repository: ${{ env.image_repo }} - run: | - enos scenario run --timeout 10m0s --chdir ./enos/k8s edition:${{ env.edition }} - - name: Retry Enos scenario - id: run_retry - if: steps.run.outcome == 'failure' - env: - ENOS_VAR_tfc_api_token: ${{ secrets.TF_API_TOKEN }} - ENOS_VAR_terraform_plugin_cache_dir: ../support/terraform-plugin-cache - ENOS_VAR_vault_build_date: ${{ env.ARTIFACT_BUILD_DATE }} - ENOS_VAR_vault_product_version: ${{ env.ARTIFACT_VERSION }} - ENOS_VAR_vault_product_revision: ${{ env.ARTIFACT_REVISION }} - ENOS_VAR_vault_docker_image_archive: ${{steps.download.outputs.download-path}}/${{ env.ARTIFACT_NAME }} - ENOS_VAR_vault_image_repository: ${{ env.image_repo }} - run: | - enos scenario run --timeout 10m0s --chdir ./enos/k8s edition:${{ env.edition }} - - name: Destroy Enos scenario - if: ${{ always() }} - env: - ENOS_VAR_tfc_api_token: ${{ secrets.TF_API_TOKEN }} - ENOS_VAR_terraform_plugin_cache_dir: ./support/terraform-plugin-cache - ENOS_VAR_vault_build_date: ${{ env.ARTIFACT_BUILD_DATE }} - ENOS_VAR_vault_product_version: ${{ env.ARTIFACT_VERSION }} - ENOS_VAR_vault_product_revision: ${{ env.ARTIFACT_REVISION }} - ENOS_VAR_vault_docker_image_archive: ${{steps.download.outputs.download-path}} - ENOS_VAR_vault_image_repository: ${{ env.image_repo }} - run: | - enos scenario destroy --timeout 10m0s --chdir ./enos/k8s edition:${{ env.edition }} - - name: Cleanup Enos runtime directories - if: ${{ always() }} - run: | - rm -rf /tmp/enos* - rm -rf ./enos/support - rm -rf ./enos/k8s/.enos diff --git a/.github/workflows/test-run-enos-scenario-containers.yml b/.github/workflows/test-run-enos-scenario-containers.yml new file mode 100644 index 000000000000..8dcd24dee7bb --- /dev/null +++ b/.github/workflows/test-run-enos-scenario-containers.yml @@ -0,0 +1,140 @@ +--- +name: enos-containers + +on: + # Only trigger this working using workflow_call. This workflow requires many + # secrets that must be inherited from the caller workflow. + workflow_call: + inputs: + # The name of the artifact that we're going to use for testing. This should + # match exactly to build artifacts uploaded to Github and Artifactory. + build-artifact-name: + required: true + type: string + # The maximum number of scenarios to include in the test sample. + sample-max: + default: 1 + type: number + # The name of the enos scenario sample that defines compatible scenarios we can + # can test with. + sample-name: + required: true + type: string + vault-edition: + required: false + type: string + default: ce + # The Git commit SHA used as the revision when building vault + vault-revision: + required: true + type: string + vault-version: + required: true + type: string + +jobs: + metadata: + runs-on: ubuntu-latest + outputs: + build-date: ${{ steps.metadata.outputs.build-date }} + sample: ${{ steps.metadata.outputs.sample }} + vault-version: ${{ steps.metadata.outputs.vault-version }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ inputs.vault-revision }} + - uses: hashicorp/action-setup-enos@v1 + with: + github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + - id: metadata + run: | + build_date=$(make ci-get-date) + sample_seed=$(date +%s) + if ! sample=$(enos scenario sample observe "${{ inputs.sample-name }}" --chdir ./enos/k8s --min 1 --max "${{ inputs.sample-max }}" --seed "${sample_seed}" --format json | jq -c ".observation.elements"); then + echo "failed to do sample observation: $sample" 2>&1 + exit 1 + fi + if [[ "${{ inputs.vault-edition }}" == "ce" ]]; then + vault_version="${{ inputs.vault-version }}" + else + # shellcheck disable=2001 + vault_version="$(sed 's/+ent/+${{ inputs.vault-edition }}/g' <<< '${{ inputs.vault-version }}')" + fi + { + echo "build-date=${build_date}" + echo "vault-version=${vault_version}" + echo "sample=${sample}" + echo "sample-seed=${sample_seed}" # This isn't used outside of here but is nice to know for duplicating observations + } | tee -a "$GITHUB_OUTPUT" + + run: + needs: metadata + name: run ${{ matrix.scenario.id.filter }} + runs-on: ${{ fromJSON(contains(inputs.build-artifact-name, 'vault-enterprise') && (contains(inputs.build-artifact-name, 'arm64') && '["self-hosted","ondemand","os=ubuntu-arm","type=c6g.xlarge"]' || '["self-hosted","linux","small"]') || '"ubuntu-latest"') }} + strategy: + fail-fast: false # don't fail as that can skip required cleanup steps for jobs + matrix: + include: ${{ fromJSON(needs.metadata.outputs.sample) }} + env: + GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: hashicorp/setup-terraform@v3 + with: + # the Terraform wrapper will break Terraform execution in Enos because + # it changes the output to text when we expect it to be JSON. + terraform_wrapper: false + - uses: hashicorp/action-setup-enos@v1 + with: + github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + - name: Download Docker Image + id: download + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: ${{ inputs.build-artifact-name }} + path: ./enos/support/downloads + - if: inputs.vault-edition != 'ce' + name: Configure license + run: | + echo "${{ secrets.VAULT_LICENSE }}" > ./enos/support/vault.hclic || true + - name: Run Enos scenario + id: run + # Continue once and retry to handle occasional blips when creating + # infrastructure. + continue-on-error: true + env: + ENOS_VAR_terraform_plugin_cache_dir: ../support/terraform-plugin-cache + ENOS_VAR_vault_build_date: ${{ needs.metadata.outputs.build-date }} + ENOS_VAR_vault_version: ${{ needs.metadata.outputs.vault-version }} + ENOS_VAR_vault_revision: ${{ inputs.vault-revision }} + ENOS_VAR_container_image_archive: ${{steps.download.outputs.download-path}}/${{ inputs.build-artifact-name }} + run: | + mkdir -p ./enos/support/terraform-plugin-cache + enos scenario run --timeout 10m0s --chdir ./enos/k8s ${{ matrix.scenario.id.filter }} + - name: Retry Enos scenario + id: run_retry + if: steps.run.outcome == 'failure' + env: + ENOS_VAR_terraform_plugin_cache_dir: ../support/terraform-plugin-cache + ENOS_VAR_vault_build_date: ${{ needs.metadata.outputs.build-date }} + ENOS_VAR_vault_version: ${{ needs.metadata.outputs.vault-version }} + ENOS_VAR_vault_revision: ${{ inputs.vault-revision }} + ENOS_VAR_container_image_archive: ${{steps.download.outputs.download-path}}/${{ inputs.build-artifact-name }} + run: | + enos scenario run --timeout 10m0s --chdir ./enos/k8s ${{ matrix.scenario.id.filter }} + - name: Destroy Enos scenario + if: ${{ always() }} + env: + ENOS_VAR_terraform_plugin_cache_dir: ../support/terraform-plugin-cache + ENOS_VAR_vault_build_date: ${{ needs.metadata.outputs.build-date }} + ENOS_VAR_vault_version: ${{ needs.metadata.outputs.vault-version }} + ENOS_VAR_vault_revision: ${{ inputs.vault-revision }} + ENOS_VAR_container_image_archive: ${{steps.download.outputs.download-path}}/${{ inputs.build-artifact-name }} + run: | + enos scenario destroy --timeout 10m0s --grpc-listen http://localhost --chdir ./enos/k8s ${{ matrix.scenario.id.filter }} + - name: Cleanup Enos runtime directories + if: ${{ always() }} + run: | + rm -rf /tmp/enos* + rm -rf ./enos/support + rm -rf ./enos/k8s/.enos diff --git a/.github/workflows/test-run-enos-scenario-matrix.yml b/.github/workflows/test-run-enos-scenario-matrix.yml index 6ab0d4176df3..826a5a51c7f8 100644 --- a/.github/workflows/test-run-enos-scenario-matrix.yml +++ b/.github/workflows/test-run-enos-scenario-matrix.yml @@ -59,7 +59,10 @@ jobs: run: | build_date=$(make ci-get-date) sample_seed=$(date +%s) - sample=$(enos scenario sample observe "${{ inputs.sample-name }}" --chdir ./enos --min 1 --max "${{ inputs.sample-max }}" --seed "${sample_seed}" --format json | jq -c ".observation.elements") + if ! sample=$(enos scenario sample observe "${{ inputs.sample-name }}" --chdir ./enos --min 1 --max "${{ inputs.sample-max }}" --seed "${sample_seed}" --format json | jq -c ".observation.elements"); then + echo "failed to do sample observation: $sample" 2>&1 + exit 1 + fi if [[ "${{ inputs.vault-edition }}" == "ce" ]]; then vault_version="${{ inputs.vault-version }}" else diff --git a/.github/workflows/test-run-enos-scenario.yml b/.github/workflows/test-run-enos-scenario.yml new file mode 100644 index 000000000000..b8f1a5c0ca65 --- /dev/null +++ b/.github/workflows/test-run-enos-scenario.yml @@ -0,0 +1,129 @@ +# Reusable workflow called by interactive scenario tests in GHA +name: Test run Vault Enos scenario + +on: + workflow_call: + inputs: + artifact-source: + type: string + description: "The artifact source to test artifactory or local (use local for current branch)" + required: true + artifact-type: + type: string + description: "The Vault artifact type to test" + required: true + distro: + type: string + description: "Linux distribution that Vault replication will be tested on" + required: true + product-version: + type: string + description: "Vault version to test (vault_product_version)" + required: false + scenario: + type: string + description: "Enos test scenario to run" + required: true + ssh-key-name: + type: string + default: ${{ github.event.repository.name }}-ci-ssh-key + vault-revision: + type: string + description: "The git SHA of the Vault release (vault_revision)" + required: false + +jobs: + enos-run-vault-interactive-test: + name: Enos run Vault interactive test + runs-on: ubuntu-latest + timeout-minutes: 120 + env: + GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + # Pass in enos variables + ENOS_VAR_aws_ssh_keypair_name: ${{ inputs.ssh-key-name }} + ENOS_VAR_vault_log_level: trace + ENOS_VAR_aws_ssh_private_key_path: ./support/private_key.pem + ENOS_VAR_tfc_api_token: ${{ secrets.TF_API_TOKEN }} + ENOS_VAR_artifactory_username: ${{ secrets.ARTIFACTORY_USER }} + ENOS_VAR_artifactory_token: ${{ secrets.ARTIFACTORY_TOKEN }} + ENOS_VAR_terraform_plugin_cache_dir: ./support/terraform-plugin-cache + ENOS_VAR_vault_license_path: ./support/vault.hclic + ENOS_DEBUG_DATA_ROOT_DIR: /tmp/enos-debug-data + VAULT_METADATA: ent + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set product version and revision + # If the Vault version and revision are not provided as workflow inputs, incase of + # testing local artifact, the environment variables ENOS_VAR_vault_product_version + # and ENOS_VAR_vault_revision are set using the current branch + id: set-version-sha + run: | + [[ -n "${{ inputs.product-version }}" ]] && echo "ENOS_VAR_vault_product_version=${{ inputs.product-version }}" >> "$GITHUB_ENV" || echo "ENOS_VAR_vault_product_version=$(make ci-get-version)" >> "$GITHUB_ENV" + [[ -n "${{ inputs.vault-revision }}" ]] && echo "ENOS_VAR_vault_revision=${{ inputs.vault-revision }}" >> "$GITHUB_ENV" || echo "ENOS_VAR_vault_revision=$(make ci-get-revision)" >> "$GITHUB_ENV" + - uses: ./.github/actions/set-up-go + with: + github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + - name: Configure Git + run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" + - name: Set up node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: 14 + cache-dependency-path: ui/yarn.lock + - uses: hashicorp/setup-terraform@v2 + with: + # the Terraform wrapper will break Terraform execution in Enos because + # it changes the output to text when we expect it to be JSON. + terraform_wrapper: false + - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_CI }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_CI }} + aws-region: 'us-west-1' + role-to-assume: ${{ secrets.AWS_ROLE_ARN_CI }} + role-skip-session-tagging: true + role-duration-seconds: 3600 + - uses: hashicorp/action-setup-enos@v1 + with: + github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + - name: Prepare scenario dependencies + id: scenario-deps + run: | + mkdir -p ./enos/support/terraform-plugin-cache + mkdir -p /tmp/enos-scenario-logs + echo logsdir="/tmp/enos-scenario-logs" >> "$GITHUB_OUTPUT" + echo "${{ secrets.SSH_KEY_PRIVATE_CI }}" > ./enos/support/private_key.pem + chmod 600 ./enos/support/private_key.pem + - name: Setup Vault Enterprise License + id: license + run: echo "${{ secrets.VAULT_LICENSE }}" > ./enos/support/vault.hclic + - name: Run Enos scenario + id: run + run: enos scenario run --timeout 60m0s --chdir ./enos ${{ inputs.scenario }} + - name: Collect logs when scenario fails + id: collect_logs + if: ${{ always() }} + run: | + bash -x ./scripts/gha_enos_logs.sh "${{ steps.scenario-deps.outputs.logsdir }}" "${{ inputs.scenario }}" "${{ inputs.distro }}" "${{ inputs.artifact-type }}" 2>/dev/null + find "${{ steps.scenario-deps.outputs.logsdir }}" -maxdepth 0 -empty -exec rmdir {} \; + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: ${{ always() }} + with: + name: enos-scenario-logs + path: ${{ steps.scenario-deps.outputs.logsdir }} + retention-days: 1 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: ${{ always() }} + with: + name: enos-debug-data-logs + path: ${{ env.ENOS_DEBUG_DATA_ROOT_DIR }} + retention-days: 1 + - name: Ensure scenario has been destroyed + if: ${{ always() }} + run: enos scenario destroy --timeout 60m0s --grpc-listen http://localhost --chdir ./enos ${{ inputs.scenario }} + - name: Clean up Enos runtime directories + if: ${{ always() }} + run: | + rm -rf /tmp/enos* + rm -rf ./enos/support + rm -rf ./enos/.enos diff --git a/Dockerfile b/Dockerfile index 254a264319f9..4bdf1b0ce5b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # SPDX-License-Identifier: BUSL-1.1 ## DOCKERHUB DOCKERFILE ## -FROM alpine:3 as default +FROM alpine:3 AS default ARG BIN_NAME # NAME and PRODUCT_VERSION are the name of the software in releases.hashicorp.com @@ -34,7 +34,11 @@ ENV VERSION=$VERSION # Create a non-root user to run the software. RUN addgroup ${NAME} && adduser -S -G ${NAME} ${NAME} -RUN apk add --no-cache libcap su-exec dumb-init tzdata +RUN apk add --no-cache libcap su-exec dumb-init tzdata curl && \ + mkdir -p /usr/share/doc/vault && \ + curl -o /usr/share/doc/vault/EULA.txt https://eula.hashicorp.com/EULA.txt && \ + curl -o /usr/share/doc/vault/TermsOfEvaluation.txt https://eula.hashicorp.com/TermsOfEvaluation.txt && \ + apk del curl COPY dist/$TARGETOS/$TARGETARCH/$BIN_NAME /bin/ @@ -75,7 +79,7 @@ CMD ["server", "-dev"] ## UBI DOCKERFILE ## -FROM registry.access.redhat.com/ubi8/ubi-minimal as ubi +FROM registry.access.redhat.com/ubi8/ubi-minimal AS ubi ARG BIN_NAME # NAME and PRODUCT_VERSION are the name of the software in releases.hashicorp.com @@ -111,7 +115,7 @@ COPY LICENSE /licenses/LICENSE.txt # this (https://github.com/hashicorp/docker-vault/blob/master/ubi/Dockerfile), # we copy in the Vault binary from CRT. RUN set -eux; \ - microdnf install -y ca-certificates gnupg openssl libcap tzdata procps shadow-utils util-linux + microdnf install -y ca-certificates gnupg openssl libcap tzdata procps shadow-utils util-linux tar # Create a non-root user to run the software. RUN groupadd --gid 1000 vault && \ @@ -127,7 +131,7 @@ COPY dist/$TARGETOS/$TARGETARCH/$BIN_NAME /bin/ # storage backend, if desired; the server will be started with /vault/config as # the configuration directory so you can add additional config files in that # location. -ENV HOME /home/vault +ENV HOME=/home/vault RUN mkdir -p /vault/logs && \ mkdir -p /vault/file && \ mkdir -p /vault/config && \ @@ -136,6 +140,11 @@ RUN mkdir -p /vault/logs && \ chgrp -R 0 $HOME && chmod -R g+rwX $HOME && \ chgrp -R 0 /vault && chmod -R g+rwX /vault +# Include EULA and Terms of Eval +RUN mkdir -p /usr/share/doc/vault && \ + curl -o /usr/share/doc/vault/EULA.txt https://eula.hashicorp.com/EULA.txt && \ + curl -o /usr/share/doc/vault/TermsOfEvaluation.txt https://eula.hashicorp.com/TermsOfEvaluation.txt + # Expose the logs directory as a volume since there's potentially long-running # state in there VOLUME /vault/logs @@ -162,3 +171,9 @@ USER vault # # By default you'll get a single-node development server that stores everything # # in RAM and bootstraps itself. Don't use this configuration for production. CMD ["server", "-dev"] + +FROM ubi AS ubi-fips + +FROM ubi AS ubi-hsm + +FROM ubi AS ubi-hsm-fips diff --git a/enos/k8s/enos-modules-k8s.hcl b/enos/k8s/enos-modules-k8s.hcl index 2e815eac62b6..3350535016b8 100644 --- a/enos/k8s/enos-modules-k8s.hcl +++ b/enos/k8s/enos-modules-k8s.hcl @@ -12,39 +12,39 @@ module "load_docker_image" { module "k8s_deploy_vault" { source = "../modules/k8s_deploy_vault" - vault_instance_count = var.vault_instance_count + vault_instance_count = var.instance_count } module "k8s_verify_build_date" { source = "../modules/k8s_vault_verify_build_date" - vault_instance_count = var.vault_instance_count + vault_instance_count = var.instance_count } module "k8s_verify_replication" { source = "../modules/k8s_vault_verify_replication" - vault_instance_count = var.vault_instance_count + vault_instance_count = var.instance_count } module "k8s_verify_ui" { source = "../modules/k8s_vault_verify_ui" - vault_instance_count = var.vault_instance_count + vault_instance_count = var.instance_count } module "k8s_verify_version" { source = "../modules/k8s_vault_verify_version" - vault_instance_count = var.vault_instance_count - vault_product_version = var.vault_product_version - vault_product_revision = var.vault_product_revision + vault_instance_count = var.instance_count + vault_product_version = var.vault_version + vault_product_revision = var.vault_revision } module "k8s_verify_write_data" { source = "../modules/k8s_vault_verify_write_data" - vault_instance_count = var.vault_instance_count + vault_instance_count = var.instance_count } module "read_license" { diff --git a/enos/k8s/enos-qualities.hcl b/enos/k8s/enos-qualities.hcl new file mode 100644 index 000000000000..2dfe81f97ae7 --- /dev/null +++ b/enos/k8s/enos-qualities.hcl @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +quality "vault_artifact_container_alpine" { + description = "The candidate binary packaged as an Alpine package is used for testing" +} + +quality "vault_artifact_container_ubi" { + description = "The candidate binary packaged as an UBI package is used for testing" +} + +quality "vault_artifact_container_tags" { + description = "The candidate binary has the expected tags" +} diff --git a/enos/k8s/enos-samples-ce.hcl b/enos/k8s/enos-samples-ce.hcl new file mode 100644 index 000000000000..7839b5a52177 --- /dev/null +++ b/enos/k8s/enos-samples-ce.hcl @@ -0,0 +1,38 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +sample "ce_default_linux_amd64_ent_docker" { + subset "k8s" { + matrix { + repo = ["docker", "ecr"] + edition = ["ce"] + } + } +} + +sample "ce_default_linux_arm64_ce_docker" { + subset "k8s" { + matrix { + repo = ["docker", "ecr"] + edition = ["ce"] + } + } +} + +sample "ce_ubi_linux_amd64_ce_redhat" { + subset "k8s" { + matrix { + repo = ["quay"] + edition = ["ce"] + } + } +} + +sample "ce_ubi_linux_arm64_ce_redhat" { + subset "k8s" { + matrix { + repo = ["quay"] + edition = ["ce"] + } + } +} diff --git a/enos/k8s/enos-scenario-k8s.hcl b/enos/k8s/enos-scenario-k8s.hcl index 33508249c645..7ba9be2c0355 100644 --- a/enos/k8s/enos-scenario-k8s.hcl +++ b/enos/k8s/enos-scenario-k8s.hcl @@ -2,8 +2,17 @@ # SPDX-License-Identifier: BUSL-1.1 scenario "k8s" { + description = <<-EOF + The k8s scenario verifies Vault when running in Kubernetes mode. The build can be a container + in a remote repository or a local container archive tarball. + + The scenario creates a new kind kubernetes cluster in Docker and creates a Vault Cluster using + the candidate artifact and verifies behavior against the Vault cluster. + EOF + matrix { - edition = ["ce", "ent"] + edition = ["ce", "ent", "ent.fips1402", "ent.hsm", "ent.hsm.fips1402"] + repo = ["docker", "ecr", "quay"] } terraform_cli = terraform_cli.default @@ -15,15 +24,106 @@ scenario "k8s" { ] locals { - image_path = abspath(var.vault_docker_image_archive) - - image_repo = var.vault_image_repository != null ? var.vault_image_repository : matrix.edition == "ce" ? "hashicorp/vault" : "hashicorp/vault-enterprise" - image_tag = replace(var.vault_product_version, "+ent", "-ent") - + // For now this works as the vault_version includes metadata. If we ever get to the point that + // vault_version excludes metadata we'll have to include the matrix.edition here as well. + tag_version = replace(var.vault_version, "+ent", "-ent") + tag_version_ubi = "${local.tag_version}-ubi" + // When we load candidate images into our k8s cluster we verify that the archives embedded + // repository and tag match our expectations. This is the source of truth for what we _expect_ + // various artifacts to have. The source of truth for what we use when building is defined in + // .github/actions/containerize. If you are modifying these expectations you likely need to + // modify the source of truth there. + repo_metadata = { + "ce" = { + docker = { + // https://hub.docker.com/r/hashicorp/vault + repo = "hashicorp/vault" + tag = local.tag_version + } + ecr = { + // https://gallery.ecr.aws/hashicorp/vault + repo = "public.ecr.aws/hashicorp/vault" + tag = local.tag_version + } + quay = { + // https://catalog.redhat.com/software/containers/hashicorp/vault/5fda55bd2937386820429e0c + repo = "quay.io/redhat-isv-containers/5f89bb5e0b94cf64cfeb500a" + tag = local.tag_version_ubi + } + }, + "ent" = { + docker = { + // https://hub.docker.com/r/hashicorp/vault-enterprise + repo = "hashicorp/vault-enterprise" + tag = local.tag_version + } + ecr = { + // https://gallery.ecr.aws/hashicorp/vault-enterprise + repo = "public.ecr.aws/hashicorp/vault-enterprise" + tag = local.tag_version + } + quay = { + // https://catalog.redhat.com/software/containers/hashicorp/vault-enterprise/5fda5633ac3db90370a26443 + repo = "quay.io/redhat-isv-containers/5f89bb9242e382c85087dce2" + tag = local.tag_version_ubi + } + }, + "ent.fips1402" = { + docker = { + // https://hub.docker.com/r/hashicorp/vault-enterprise-fips + repo = "hashicorp/vault-enterprise-fips" + tag = local.tag_version + } + ecr = { + // https://gallery.ecr.aws/hashicorp/vault-enterprise-fips + repo = "public.ecr.aws/hashicorp/vault-enterprise-fips" + tag = local.tag_version + } + quay = { + // https://catalog.redhat.com/software/containers/hashicorp/vault-enterprise-fips/628d50e37ff70c66a88517ea + repo = "quay.io/redhat-isv-containers/6283f645d02c6b16d9caeb8e" + tag = local.tag_version_ubi + } + }, + "ent.hsm" = { + docker = { + // https://hub.docker.com/r/hashicorp/vault-enterprise + repo = "hashicorp/vault-enterprise" + tag = local.tag_version + } + ecr = { + // https://gallery.ecr.aws/hashicorp/vault-enterprise + repo = "public.ecr.aws/hashicorp/vault-enterprise" + tag = local.tag_version + } + quay = { + // https://catalog.redhat.com/software/containers/hashicorp/vault-enterprise/5fda5633ac3db90370a26443 + repo = "quay.io/redhat-isv-containers/5f89bb9242e382c85087dce2" + tag = local.tag_version_ubi + } + }, + "ent.hsm.fips1402" = { + docker = { + // https://hub.docker.com/r/hashicorp/vault-enterprise + repo = "hashicorp/vault-enterprise" + tag = local.tag_version + } + ecr = { + // https://gallery.ecr.aws/hashicorp/vault-enterprise + repo = "public.ecr.aws/hashicorp/vault-enterprise" + tag = local.tag_version + } + quay = { + // https://catalog.redhat.com/software/containers/hashicorp/vault-enterprise/5fda5633ac3db90370a26443 + repo = "quay.io/redhat-isv-containers/5f89bb9242e382c85087dce2" + tag = local.tag_version_ubi + } + }, + } // The additional '-0' is required in the constraint since without it, the semver function will // only compare the non-pre-release parts (Major.Minor.Patch) of the version and the constraint, // which can lead to unexpected results. - version_includes_build_date = semverconstraint(var.vault_product_version, ">=1.11.0-0") + version_includes_build_date = semverconstraint(var.vault_version, ">=1.11.0-0") } step "read_license" { @@ -44,20 +144,34 @@ scenario "k8s" { } step "load_docker_image" { - module = module.load_docker_image + description = <<-EOF + Load an verify the tags of a Vault container image into the kind k8s cluster. If no + var.container_image_archive has been set it will attempt to load an image matching the + var.vault_version from the matrix.repo. + EOF + module = module.load_docker_image + depends_on = [step.create_kind_cluster] + + verifies = [ + quality.vault_artifact_container_alpine, + quality.vault_artifact_container_ubi, + quality.vault_artifact_container_tags, + ] variables { cluster_name = step.create_kind_cluster.cluster_name - image = local.image_repo - tag = local.image_tag - archive = var.vault_docker_image_archive + image = local.repo_metadata[matrix.edition][matrix.repo].repo + tag = local.repo_metadata[matrix.edition][matrix.repo].tag + archive = var.container_image_archive } - - depends_on = [step.create_kind_cluster] } step "deploy_vault" { module = module.k8s_deploy_vault + depends_on = [ + step.load_docker_image, + step.create_kind_cluster, + ] variables { image_tag = step.load_docker_image.tag @@ -65,29 +179,14 @@ scenario "k8s" { image_repository = step.load_docker_image.repository kubeconfig_base64 = step.create_kind_cluster.kubeconfig_base64 vault_edition = matrix.edition - vault_log_level = var.vault_log_level + vault_log_level = var.log_level ent_license = matrix.edition != "ce" ? step.read_license.license : null } - - depends_on = [step.load_docker_image, step.create_kind_cluster] - } - - step "verify_build_date" { - skip_step = !local.version_includes_build_date - module = module.k8s_verify_build_date - - variables { - vault_pods = step.deploy_vault.vault_pods - vault_root_token = step.deploy_vault.vault_root_token - kubeconfig_base64 = step.create_kind_cluster.kubeconfig_base64 - context_name = step.create_kind_cluster.context_name - } - - depends_on = [step.deploy_vault] } step "verify_replication" { - module = module.k8s_verify_replication + module = module.k8s_verify_replication + depends_on = [step.deploy_vault] variables { vault_pods = step.deploy_vault.vault_pods @@ -95,12 +194,11 @@ scenario "k8s" { kubeconfig_base64 = step.create_kind_cluster.kubeconfig_base64 context_name = step.create_kind_cluster.context_name } - - depends_on = [step.deploy_vault] } step "verify_version" { - module = module.k8s_verify_version + module = module.k8s_verify_version + depends_on = [step.deploy_vault] variables { vault_pods = step.deploy_vault.vault_pods @@ -111,12 +209,11 @@ scenario "k8s" { check_build_date = local.version_includes_build_date vault_build_date = var.vault_build_date } - - depends_on = [step.deploy_vault] } step "verify_write_data" { - module = module.k8s_verify_write_data + module = module.k8s_verify_write_data + depends_on = [step.deploy_vault] variables { vault_pods = step.deploy_vault.vault_pods @@ -124,7 +221,5 @@ scenario "k8s" { kubeconfig_base64 = step.create_kind_cluster.kubeconfig_base64 context_name = step.create_kind_cluster.context_name } - - depends_on = [step.deploy_vault] } } diff --git a/enos/k8s/enos-terraform-k8s.hcl b/enos/k8s/enos-terraform-k8s.hcl index 9b884ef12109..d7a14538c5e6 100644 --- a/enos/k8s/enos-terraform-k8s.hcl +++ b/enos/k8s/enos-terraform-k8s.hcl @@ -6,7 +6,7 @@ terraform "k8s" { required_providers { enos = { - source = "registry.terraform.io/hashicorp-forge/enos" + source = "registry.terraform.io/hashicorp-forge/enos" } helm = { diff --git a/enos/k8s/enos-variables-k8s.hcl b/enos/k8s/enos-variables-k8s.hcl index 52ffd8d8225c..26ea3d0ce8ec 100644 --- a/enos/k8s/enos-variables-k8s.hcl +++ b/enos/k8s/enos-variables-k8s.hcl @@ -1,37 +1,19 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 -variable "vault_image_repository" { - description = "The repository for the docker image to load, i.e. hashicorp/vault" +variable "container_image_archive" { + description = "The path to the location of the container image archive to test" type = string - default = null + default = null # If none is given we'll simply load a container from a repo } -variable "vault_log_level" { +variable "log_level" { description = "The server log level for Vault logs. Supported values (in order of detail) are trace, debug, info, warn, and err." type = string - default = "info" -} - -variable "vault_product_version" { - description = "The vault product version to test" - type = string - default = null -} - -variable "vault_product_revision" { - type = string - description = "The vault product revision to test" - default = null + default = "trace" } -variable "vault_docker_image_archive" { - description = "The path to the location of the docker image archive to test" - type = string - default = null -} - -variable "vault_instance_count" { +variable "instance_count" { description = "How many instances to create for the Vault cluster" type = number default = 3 @@ -44,7 +26,17 @@ variable "terraform_plugin_cache_dir" { } variable "vault_build_date" { - description = "The build date for the vault docker image" + description = "The expected vault build date" type = string default = "" } + +variable "vault_revision" { + type = string + description = "The expected vault revision" +} + +variable "vault_version" { + description = "The expected vault version" + type = string +} diff --git a/enos/modules/k8s_vault_verify_build_date/main.tf b/enos/modules/k8s_vault_verify_build_date/main.tf deleted file mode 100644 index dadf92ee5f30..000000000000 --- a/enos/modules/k8s_vault_verify_build_date/main.tf +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: BUSL-1.1 - - -terraform { - required_providers { - enos = { - source = "registry.terraform.io/hashicorp-forge/enos" - } - } -} - -locals { - vault_instances = toset([for idx in range(var.vault_instance_count) : tostring(idx)]) -} - -# Get the date from the vault status command - status_date -# Format the original status output with ISO-8601 - formatted_date -# Format the original status output with awk - awk_date -# Compare the formatted outputs - date_comparison -resource "enos_remote_exec" "status_date" { - for_each = local.vault_instances - - transport = { - kubernetes = { - kubeconfig_base64 = var.kubeconfig_base64 - context_name = var.context_name - pod = var.vault_pods[each.key].name - namespace = var.vault_pods[each.key].namespace - } - } - - inline = ["${var.vault_bin_path} status -format=json | grep build_date | cut -d \\\" -f 4"] -} - -resource "enos_remote_exec" "formatted_date" { - for_each = local.vault_instances - - transport = { - kubernetes = { - kubeconfig_base64 = var.kubeconfig_base64 - context_name = var.context_name - pod = var.vault_pods[each.key].name - namespace = var.vault_pods[each.key].namespace - } - } - - inline = ["date -d \"${enos_remote_exec.status_date[each.key].stdout}\" -D '%Y-%m-%dT%H:%M:%SZ' -I"] -} - -resource "enos_local_exec" "awk_date" { - for_each = local.vault_instances - - inline = ["echo ${enos_remote_exec.status_date[each.key].stdout} | awk -F\"T\" '{printf $1}'"] -} - -resource "enos_local_exec" "date_comparison" { - for_each = local.vault_instances - - inline = ["[[ ${enos_local_exec.awk_date[each.key].stdout} == ${enos_remote_exec.formatted_date[each.key].stdout} ]] && echo \"Verification for build date format ${enos_remote_exec.status_date[each.key].stdout} succeeded\" || \"invalid build_date, must be formatted as RFC 3339: ${enos_remote_exec.status_date[each.key].stdout}\""] -} diff --git a/enos/modules/k8s_vault_verify_build_date/variables.tf b/enos/modules/k8s_vault_verify_build_date/variables.tf deleted file mode 100644 index 4e1754ebe9f1..000000000000 --- a/enos/modules/k8s_vault_verify_build_date/variables.tf +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: BUSL-1.1 - -variable "vault_instance_count" { - type = number - description = "How many vault instances are in the cluster" -} - -variable "vault_pods" { - type = list(object({ - name = string - namespace = string - })) - description = "The vault instances for the cluster to verify" -} - -variable "vault_bin_path" { - type = string - description = "The path to the vault binary" - default = "/bin/vault" -} - -variable "vault_root_token" { - type = string - description = "The vault root token" -} - -variable "kubeconfig_base64" { - type = string - description = "The base64 encoded version of the Kubernetes configuration file" -} - -variable "context_name" { - type = string - description = "The name of the k8s context for Vault" -}