diff options
author | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2022-02-08 11:51:45 +0100 |
---|---|---|
committer | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2022-02-08 11:51:49 +0100 |
commit | eff99e3e61f4e216b055a805002f5ece8567a915 (patch) | |
tree | 69771d87bfc3302980625e5524d02d2b455baadb | |
parent | 74e2687fa98ea5ba25fbe07c038253d1fc14584e (diff) | |
download | puppet.KATH-eff99e3e61f4e216b055a805002f5ece8567a915.tar.gz puppet.KATH-eff99e3e61f4e216b055a805002f5ece8567a915.tar.bz2 puppet.KATH-eff99e3e61f4e216b055a805002f5ece8567a915.zip |
code/environments/production/modules: Add ca_extend module instead.
27 files changed, 1608 insertions, 0 deletions
diff --git a/code/environments/production/modules/ca_extend/.github/workflows/auto_release.yml b/code/environments/production/modules/ca_extend/.github/workflows/auto_release.yml new file mode 100644 index 0000000..fd8dfb2 --- /dev/null +++ b/code/environments/production/modules/ca_extend/.github/workflows/auto_release.yml @@ -0,0 +1,92 @@ +name: "Auto release" + +on: + schedule: + - cron: '0 3 * * 6' + workflow_dispatch: + +env: + HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 + HONEYCOMB_DATASET: litmus tests + CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + auto_release: + name: "Automatic release prep" + runs-on: ubuntu-20.04 + + steps: + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + + - name: "Honeycomb: start first step" + run: | + echo STEP_ID="auto-release" >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + + - name: "Checkout Source" + if: ${{ github.repository_owner == 'puppetlabs' }} + uses: actions/checkout@v2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: "PDK Release prep" + uses: docker://puppet/iac_release:ci + with: + args: 'release prep --force' + env: + CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "Get Version" + if: ${{ github.repository_owner == 'puppetlabs' }} + id: gv + run: | + echo "::set-output name=ver::$(jq --raw-output .version metadata.json)" + + - name: "Check if a release is necessary" + if: ${{ github.repository_owner == 'puppetlabs' }} + id: check + run: | + git diff --quiet CHANGELOG.md && echo "::set-output name=release::false" || echo "::set-output name=release::true" + + - name: "Commit changes" + if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }} + run: | + git config --local user.email "${{ github.repository_owner }}@users.noreply.github.com" + git config --local user.name "GitHub Action" + git add . + git commit -m "Release prep v${{ steps.gv.outputs.ver }}" + + - name: Create Pull Request + id: cpr + uses: puppetlabs/peter-evans-create-pull-request@v3 + if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "Release prep v${{ steps.gv.outputs.ver }}" + branch: "release-prep" + delete-branch: true + title: "Release prep v${{ steps.gv.outputs.ver }}" + body: | + Automated release-prep through [pdk-templates](https://github.com/puppetlabs/pdk-templates/blob/main/moduleroot/.github/workflows/auto_release.yml.erb) from commit ${{ github.sha }}. + Please verify before merging: + - [ ] last [nightly](https://github.com/${{ github.repository }}/actions/workflows/nightly.yml) run is green + - [ ] [Changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) is readable and has no unlabeled pull requests + - [ ] Ensure the [changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) version and [metadata](https://github.com/${{ github.repository }}/blob/release-prep/metadata.json) version match + labels: "maintenance" + + - name: PR outputs + if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }} + run: | + echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" + + - name: "Honeycomb: Record finish step" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Finished auto release workflow' diff --git a/code/environments/production/modules/ca_extend/.github/workflows/pe_latest_testing.yml b/code/environments/production/modules/ca_extend/.github/workflows/pe_latest_testing.yml new file mode 100644 index 0000000..fe1db3b --- /dev/null +++ b/code/environments/production/modules/ca_extend/.github/workflows/pe_latest_testing.yml @@ -0,0 +1,168 @@ +name: "PE Latest Acceptance Testing" + +on: + workflow_dispatch: + pull_request: + +env: + HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 + HONEYCOMB_DATASET: litmus tests + +jobs: + setup_matrix: + name: "Setup Test Matrix" + runs-on: ubuntu-20.04 + outputs: + matrix: ${{ steps.get-matrix.outputs.matrix }} + + steps: + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + + - name: "Honeycomb: Start first step" + run: | + echo STEP_ID=setup-environment >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Checkout Source + uses: actions/checkout@v2 + if: ${{ github.repository_owner == 'puppetlabs' }} + + - name: Activate Ruby 2.7 + uses: ruby/setup-ruby@v1 + if: ${{ github.repository_owner == 'puppetlabs' }} + with: + ruby-version: "2.7" + bundler-cache: true + + - name: Print bundle environment + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + echo ::group::bundler environment + buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env + echo ::endgroup:: + - name: "Honeycomb: Record Setup Environment time" + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment' + echo STEP_ID=Setup-Acceptance-Test-Matrix >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Setup Acceptance Test Matrix + id: get-matrix + run: | + echo "::set-output name=matrix::$(cat spec/fixtures/matrix/latest.json)" + - name: "Honeycomb: Record Setup Test Matrix time" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix' + Acceptance: + name: "${{matrix.platforms.label}}, ${{matrix.collection}}" + needs: + - setup_matrix + if: ${{ needs.setup_matrix.outputs.matrix != '{}' }} + + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} + + env: + BUILDEVENT_FILE: '../buildevents.txt' + + steps: + - run: | + echo 'platform=${{ matrix.platforms.image }}' >> $BUILDEVENT_FILE + echo 'collection=${{ matrix.collection }}' >> $BUILDEVENT_FILE + echo 'label=${{ matrix.platforms.label }}' >> $BUILDEVENT_FILE + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + matrix-key: ${{ matrix.platforms.label }}-${{ matrix.collection }} + + - name: "Honeycomb: start first step" + run: | + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-1 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Checkout Source + uses: actions/checkout@v2 + + - name: Activate Ruby 2.7 + uses: ruby/setup-ruby@v1 + with: + ruby-version: "2.7" + bundler-cache: true + + - name: Print bundle environment + run: | + echo ::group::bundler environment + buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env + echo ::endgroup:: + - name: "Honeycomb: Record Setup Environment time" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment' + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-2 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Provision test environment + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:provision ${{ matrix.platforms.image }}' -- bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]' + echo ::group::=== REQUEST === + cat request.json || true + echo + echo ::endgroup:: + echo ::group::=== INVENTORY === + if [ -f 'spec/fixtures/litmus_inventory.yaml' ]; + then + FILE='spec/fixtures/litmus_inventory.yaml' + elif [ -f 'inventory.yaml' ]; + then + FILE='inventory.yaml' + fi + sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true + echo ::endgroup:: + echo INVENTORY_PATH=$FILE >> $GITHUB_ENV + - name: Install PE + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake deploy_pe::provision_master' -- bundle exec bolt --tmpdir /tmp --log-level debug --modulepath spec/fixtures/modules -i ./$INVENTORY_PATH plan run deploy_pe::provision_master --params '{"version":"${{ matrix.collection }}","pe_settings":{"password":"puppetlabs", "configure_tuning": false}}' --targets all --stream + + - name: Install module + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_module' -- bundle exec rake 'litmus:install_module' + - name: "Honeycomb: Record deployment times" + if: ${{ always() }} + run: | + echo ::group::honeycomb step + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Deploy test system' + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-3 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + echo ::endgroup:: + - name: Run acceptance tests + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:acceptance:parallel' -- bundle exec rake 'litmus:acceptance:parallel' + - name: "Honeycomb: Record acceptance testing times" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Run acceptance tests' + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-4 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Remove test environment + if: ${{ always() }} + continue-on-error: true + run: | + if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:tear_down' -- bundle exec rake 'litmus:tear_down' + echo ::group::=== REQUEST === + cat request.json || true + echo + echo ::endgroup:: + fi + - name: "Honeycomb: Record removal times" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Remove test environment' diff --git a/code/environments/production/modules/ca_extend/.github/workflows/pe_lts_testing.yml b/code/environments/production/modules/ca_extend/.github/workflows/pe_lts_testing.yml new file mode 100644 index 0000000..12338ff --- /dev/null +++ b/code/environments/production/modules/ca_extend/.github/workflows/pe_lts_testing.yml @@ -0,0 +1,168 @@ +name: "PE LTS Acceptance Testing" + +on: + workflow_dispatch: + pull_request: + +env: + HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 + HONEYCOMB_DATASET: litmus tests + +jobs: + setup_matrix: + name: "Setup Test Matrix" + runs-on: ubuntu-20.04 + outputs: + matrix: ${{ steps.get-matrix.outputs.matrix }} + + steps: + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + + - name: "Honeycomb: Start first step" + run: | + echo STEP_ID=setup-environment >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Checkout Source + uses: actions/checkout@v2 + if: ${{ github.repository_owner == 'puppetlabs' }} + + - name: Activate Ruby 2.7 + uses: ruby/setup-ruby@v1 + if: ${{ github.repository_owner == 'puppetlabs' }} + with: + ruby-version: "2.7" + bundler-cache: true + + - name: Print bundle environment + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + echo ::group::bundler environment + buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env + echo ::endgroup:: + - name: "Honeycomb: Record Setup Environment time" + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment' + echo STEP_ID=Setup-Acceptance-Test-Matrix >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Setup Acceptance Test Matrix + id: get-matrix + run: | + echo "::set-output name=matrix::$(cat spec/fixtures/matrix/lts.json)" + - name: "Honeycomb: Record Setup Test Matrix time" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix' + Acceptance: + name: "${{matrix.platforms.label}}, ${{matrix.collection}}" + needs: + - setup_matrix + if: ${{ needs.setup_matrix.outputs.matrix != '{}' }} + + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} + + env: + BUILDEVENT_FILE: '../buildevents.txt' + + steps: + - run: | + echo 'platform=${{ matrix.platforms.image }}' >> $BUILDEVENT_FILE + echo 'collection=${{ matrix.collection }}' >> $BUILDEVENT_FILE + echo 'label=${{ matrix.platforms.label }}' >> $BUILDEVENT_FILE + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + matrix-key: ${{ matrix.platforms.label }}-${{ matrix.collection }} + + - name: "Honeycomb: start first step" + run: | + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-1 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Checkout Source + uses: actions/checkout@v2 + + - name: Activate Ruby 2.7 + uses: ruby/setup-ruby@v1 + with: + ruby-version: "2.7" + bundler-cache: true + + - name: Print bundle environment + run: | + echo ::group::bundler environment + buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env + echo ::endgroup:: + - name: "Honeycomb: Record Setup Environment time" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment' + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-2 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Provision test environment + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:provision ${{ matrix.platforms.image }}' -- bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]' + echo ::group::=== REQUEST === + cat request.json || true + echo + echo ::endgroup:: + echo ::group::=== INVENTORY === + if [ -f 'spec/fixtures/litmus_inventory.yaml' ]; + then + FILE='spec/fixtures/litmus_inventory.yaml' + elif [ -f 'inventory.yaml' ]; + then + FILE='inventory.yaml' + fi + sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true + echo ::endgroup:: + echo INVENTORY_PATH=$FILE >> $GITHUB_ENV + - name: Install PE + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake deploy_pe::provision_master' -- bundle exec bolt --tmpdir /tmp --log-level debug --modulepath spec/fixtures/modules -i ./$INVENTORY_PATH plan run deploy_pe::provision_master --params '{"version":"${{ matrix.collection }}","pe_settings":{"password":"puppetlabs", "configure_tuning": false}}' --targets all --stream + + - name: Install module + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_module' -- bundle exec rake 'litmus:install_module' + - name: "Honeycomb: Record deployment times" + if: ${{ always() }} + run: | + echo ::group::honeycomb step + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Deploy test system' + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-3 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + echo ::endgroup:: + - name: Run acceptance tests + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:acceptance:parallel' -- bundle exec rake 'litmus:acceptance:parallel' + - name: "Honeycomb: Record acceptance testing times" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Run acceptance tests' + echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-4 >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + - name: Remove test environment + if: ${{ always() }} + continue-on-error: true + run: | + if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then + buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:tear_down' -- bundle exec rake 'litmus:tear_down' + echo ::group::=== REQUEST === + cat request.json || true + echo + echo ::endgroup:: + fi + - name: "Honeycomb: Record removal times" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Remove test environment' diff --git a/code/environments/production/modules/ca_extend/.github/workflows/release.yml b/code/environments/production/modules/ca_extend/.github/workflows/release.yml new file mode 100644 index 0000000..76cf31c --- /dev/null +++ b/code/environments/production/modules/ca_extend/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: "Publish module" + +on: + workflow_dispatch: + +jobs: + create-github-release: + name: Deploy GitHub Release + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + clean: true + fetch-depth: 0 + - name: Get Version + id: gv + run: | + echo "::set-output name=ver::$(jq --raw-output .version metadata.json)" + - name: Create Release + uses: actions/create-release@v1 + id: create_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: "v${{ steps.gv.outputs.ver }}" + draft: false + prerelease: false + + deploy-forge: + name: Deploy to Forge + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + clean: true + - name: "PDK Build" + uses: docker://puppet/pdk:2.1.0.0 + with: + args: 'build' + - name: "Push to Forge" + uses: docker://puppet/pdk:2.1.0.0 + with: + args: 'release publish --forge-token ${{ secrets.FORGE_API_KEY }} --force' diff --git a/code/environments/production/modules/ca_extend/.github/workflows/spec.yml b/code/environments/production/modules/ca_extend/.github/workflows/spec.yml new file mode 100644 index 0000000..80fd4c9 --- /dev/null +++ b/code/environments/production/modules/ca_extend/.github/workflows/spec.yml @@ -0,0 +1,137 @@ +name: "Spec Tests" + +on: + workflow_dispatch: + pull_request: + +env: + HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 + HONEYCOMB_DATASET: litmus tests + SHELLCHECK_OPTS: '-e SC1090 -e SC1091' + +jobs: + setup_matrix: + name: "Setup Test Matrix" + runs-on: ubuntu-20.04 + outputs: + spec_matrix: ${{ steps.get-matrix.outputs.spec_matrix }} + + steps: + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + + - name: "Honeycomb: Start first step" + run: | + echo STEP_ID=setup-environment >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + + - name: Checkout Source + uses: actions/checkout@v2 + if: ${{ github.repository_owner == 'puppetlabs' }} + + - name: Activate Ruby 2.7 + uses: ruby/setup-ruby@v1 + if: ${{ github.repository_owner == 'puppetlabs' }} + with: + ruby-version: "2.7" + bundler-cache: true + + - name: Print bundle environment + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + echo ::group::bundler environment + buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env + echo ::endgroup:: + + - name: "Honeycomb: Record Setup Environment time" + if: ${{ github.repository_owner == 'puppetlabs' }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment' + echo STEP_ID=Setup-Acceptance-Test-Matrix >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + + - name: Setup Spec Test Matrix + id: get-matrix + run: | + if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then + buildevents cmd $TRACE_ID $STEP_ID matrix_from_metadata -- bundle exec matrix_from_metadata_v2 + else + echo "::set-output name=spec_matrix::{}" + fi + + - name: "Honeycomb: Record Setup Test Matrix time" + if: ${{ always() }} + run: | + buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix' + + Spec: + name: "Spec Tests (Puppet: ${{matrix.puppet_version}}, Ruby Ver: ${{matrix.ruby_version}})" + needs: + - setup_matrix + if: ${{ needs.setup_matrix.outputs.spec_matrix != '{}' }} + + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: ${{fromJson(needs.setup_matrix.outputs.spec_matrix)}} + + env: + BUILDEVENT_FILE: '../buildevents.txt' + PUPPET_GEM_VERSION: ${{ matrix.puppet_version }} + + steps: + - run: | + echo "SANITIZED_PUPPET_VERSION=$(echo '${{ matrix.puppet_version }}' | sed 's/~> //g')" >> $GITHUB_ENV + + - run: | + echo 'puppet_version=${{ env.SANITIZED_PUPPET_VERSION }}' >> $BUILDEVENT_FILE + + - name: "Honeycomb: Start first step" + run: | + echo "STEP_ID=${{ env.SANITIZED_PUPPET_VERSION }}-spec" >> $GITHUB_ENV + echo STEP_START=$(date +%s) >> $GITHUB_ENV + + - name: "Honeycomb: Start recording" + uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1 + with: + apikey: ${{ env.HONEYCOMB_WRITEKEY }} + dataset: ${{ env.HONEYCOMB_DATASET }} + job-status: ${{ job.status }} + matrix-key: ${{ env.SANITIZED_PUPPET_VERSION }} + + - name: Checkout Source + uses: actions/checkout@v2 + + - name: "Activate Ruby ${{ matrix.ruby_version }}" + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby_version}} + bundler-cache: true + + - name: Print bundle environment + run: | + echo ::group::bundler environment + buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env + echo ::endgroup:: + + - name: Run Static & Syntax Tests + run: | + buildevents cmd $TRACE_ID $STEP_ID 'static_syntax_checks Puppet ${{ matrix.puppet_version }}, Ruby ${{ matrix.ruby_version }}' -- bundle exec rake syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop + + - name: Run parallel_spec tests + run: | + buildevents cmd $TRACE_ID $STEP_ID 'rake parallel_spec Puppet ${{ matrix.puppet_version }}, Ruby ${{ matrix.ruby_version }}' -- bundle exec rake parallel_spec + shellcheck: + name: Shellcheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + + + diff --git a/code/environments/production/modules/ca_extend/CHANGELOG.md b/code/environments/production/modules/ca_extend/CHANGELOG.md new file mode 100644 index 0000000..71be072 --- /dev/null +++ b/code/environments/production/modules/ca_extend/CHANGELOG.md @@ -0,0 +1,26 @@ +# Change log + +All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org). + +## [v3.0.0](https://github.com/puppetlabs/ca_extend/tree/v3.0.0) (2021-08-18) + +[Full Changelog](https://github.com/puppetlabs/ca_extend/compare/fd9f05b17f65770910b3146688ed702011b62802...v3.0.0) + +### Changed + +- Remove harmful terms from ca\_extend [\#40](https://github.com/puppetlabs/ca_extend/pull/40) ([gavindidrichsen](https://github.com/gavindidrichsen)) +- \(SUP-2497\) Remove EOL platforms and versions [\#39](https://github.com/puppetlabs/ca_extend/pull/39) ([m0dular](https://github.com/m0dular)) + +### Added + +- Updated the readme file to include "How to Report an issue or contribute to the module" section \(SUP-2376\) [\#16](https://github.com/puppetlabs/ca_extend/pull/16) ([asselvakumar](https://github.com/asselvakumar)) +- Add option to regenerate the primary agent cert. [\#10](https://github.com/puppetlabs/ca_extend/pull/10) ([m0dular](https://github.com/m0dular)) + +### Fixed + +- Remove hard-coded paths from scripts [\#30](https://github.com/puppetlabs/ca_extend/pull/30) ([m0dular](https://github.com/m0dular)) +- Check for cadir during primary cert regen [\#28](https://github.com/puppetlabs/ca_extend/pull/28) ([m0dular](https://github.com/m0dular)) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/code/environments/production/modules/ca_extend/CODEOWNERS b/code/environments/production/modules/ca_extend/CODEOWNERS new file mode 100644 index 0000000..367c845 --- /dev/null +++ b/code/environments/production/modules/ca_extend/CODEOWNERS @@ -0,0 +1,7 @@ +# This will cause the code owners of this repo to be assigned review of any +# opened PRs against the branches containing this file. +# See https://help.github.com/en/articles/about-code-owners for info on how to +# take ownership of parts of the code base that should be reviewed by another +# team. + +* @puppetlabs/support diff --git a/code/environments/production/modules/ca_extend/README.md b/code/environments/production/modules/ca_extend/README.md new file mode 100644 index 0000000..035f992 --- /dev/null +++ b/code/environments/production/modules/ca_extend/README.md @@ -0,0 +1,219 @@ +# ca_extend + +#### Table of Contents + +1. [Overview](#overview) +1. [Description - What the module does and why it is useful](#description) +1. [Setup - The basics of getting started with this module](#setup) +1. [Usage - Configuration options and additional functionality](#usage) +1. [Reference - An under-the-hood peek at what the module is doing](#reference) +1. [Development - Guide for contributing to the module](#How-to-Report-an-issue-or-contribute-to-the-module) + +## Overview + +This module can extend a certificate authority (CA) that's about to expire or has already expired. + +A Puppet CA certificate is only valid for a finite time (a new installation of PE 2019.x / Puppet 6.x will create a 15 year CA, while earlier versions will create a 5 year CA; and upgrading does not extend the CA.), after which it expires. +When a CA certificate expires, Puppet services will no longer accept any certificates signed by that CA, and your Puppet infrastructure will immediately stop working. + +If your CA certificate is expiring soon (or it's already expired), you need to: + +* Generate a new CA certificate using the existing CA keypair. +* Distribute the new CA certificate to agents. + +This module can automate those tasks. + +## Description + +This module is composed of Plans and Tasks to extend the expiration date of the CA certificate in Puppet Enterprise (and Puppet Open Source) and distribute that CA certificate to agents. + +Note that, with Puppet Open Source, if the CA certificate is only used by the Puppet CA and no other integrations, there is no further action to take after using the two Plans. +However, if it is used for other integrations (such as SSL encrypted PuppetDB traffic) then those integrations will need to have their copy of the CA certificate updated. +If the CA certificate is stored in any keystores, those will also need to be updated. + +The functionality of this module is composed into two Plans: + +* `ca_extend::extend_ca_cert` + * Extend the CA certificate and configure the primary Puppet server and any Compilers to use that extended certificate. +* `ca_extend::upload_ca_cert` + * Distribute the CA certificate to agents using any transport supported by Puppet Bolt, such as `ssh`, `winrm`, or `pcp`. + +Regardless of whether the CA certificate is expired, the `extend_ca_cert` plan may be used to extend its expiration date in-place and configure the primary Puppet server and any Compilers to use it. + +After the CA certificate has been extended, there are two methods for distributing it to agents. + +* Using the `ca_extend::upload_ca_cert` plan or another method to copy the CA certificate to agents. +* Manually deleting `ca.pem` on agents and letting them download that file as part of the next Puppet agent run. The agent will download that file only if it is absent, so it must be deleted to use this method. + +There are also two complementary tasks to check the expiration date of the CA certificate or any agent certificates. + +* `ca_extend::check_ca_expiry` + * Checks if the CA certificate expires by a certain date. Defaults to three months from today. +* `ca_extend::check_agent_expiry` + * Checks if any agent certificate expires by a certain date. Defaults to three months from today. + +** If the CA certificate is expiring or expired, you must extend it as soon as possible. ** + +## Setup + +This module requires [Puppet Bolt](https://puppet.com/docs/bolt/latest/bolt_installing.html) >= 1.2.0 on either on the primary Puppet server or a workstation with connectivity to the primary. + +The installation procedure will differ depending on the version of Bolt. If possible, using Bolt >= 3.0.0 is recommended. For example, this will install the latest Bolt version on EL 7. + +```bash +sudo rpm -Uvh https://yum.puppet.com/puppet-tools-release-el-7.noarch.rpm +sudo yum install puppet-bolt +``` + +The following two sections show how to install the module dependencies depending on the installed version of Bolt. + +### Bolt >= 1.2.0 < 3.0.0 + +The recommended procedure for these versions is to use a [Bolt Puppetfile](https://puppet.com/docs/bolt/latest/installing_tasks_from_the_forge.html#task-8928). +From within a [Boltdir](https://puppet.com/docs/bolt/latest/bolt_project_directories.html#embedded-project-directory), specify this module and `puppetlabs-stdlib` as dependencies and run `bolt puppetfile install`. For example: + +```bash +mkdir -p ~/Boltdir +cd !$ + +cat >>Puppetfile <<EOF +mod 'puppetlabs-stdlib' + +mod 'puppetlabs-ca_extend' +EOF + +bolt puppetfile install +``` + +### Bolt >= 3.0.0 + +The recommended procedure for these versions is to use a Bolt Project. When creating a [Bolt project](https://puppet.com/docs/bolt/latest/bolt_project_directories.html#embedded-project-directory), specify this module and `puppetlabs-stdlib` as dependencies and initialize the project. For example: + +```bash +sudo rpm -Uvh https://yum.puppet.com/puppet-tools-release-el-7.noarch.rpm +sudo yum install puppet-bolt +``` + +If your primary Puppet server or workstation has internet access, the project can be initialized with the needed dependencies with the following: +```bash +mkdir ca_extend +cd !$ + +bolt project init expiry --modules puppetlabs-stdlib,puppetlabs-ca_extend +``` + +Otherwise, if your primary Puppet server or workstation operates behind a proxy, initialize the project without the `--modules` option +```bash +mkdir ca_extend +cd !$ + +bolt project init expiry +``` + +Then edit your `bolt-project.yaml` to use the proxy according to the [documentation](https://puppet.com/docs/bolt/latest/bolt_installing_modules.html#install-modules-using-a-proxy). Next, add the module dependencies to `bolt-project.yaml`: + +``` +--- +name: expiry +modules: + - name: puppetlabs-stdlib + - name: puppetlabs-ca_extend + +``` + +Finally, install the modules. + +```bash +bolt module install +``` + +See the "Usage" section for how to run the tasks and plans remotely or locally on the primary Puppet server. + +### Dependencies + +* A [Puppet Bolt](https://puppet.com/docs/bolt/latest/bolt_installing.html) >= 1.21.0 +* [puppetlabs-stdlib](https://puppet.com/docs/bolt/latest/bolt_installing.html) +* A `base64` binary on the primary Puppet server which supports the `-w` flag +* `bash` >= 4.0 on the primary Puppet server + +### Configuration + +#### Inventory + +This module works best with a Bolt [inventory file](https://puppet.com/docs/bolt/latest/inventory_file.html) to allow for simultaneous uploads to \*nix and Windows agents. +See the Bolt documentation for how to configure an inventory file. +See the `REFERENCE.md` for a sample inventory file. + +Alternatively, you can use an `ssh` config file if you will only use that transport to upload the CA certificate to agents. +Bolt defaults to using the `ssh` transport, which in turn will use `~/.ssh/config` for options such as `username` and `private-key`. + +#### PuppetDB + +A convenient way to specify targets for the `ca_extend::upload_ca_cert` plan is by connecting Bolt to [PuppetDB](https://puppet.com/docs/bolt/latest/bolt_connect_puppetdb.html), after which [--query](https://puppet.com/docs/bolt/latest/bolt_command_reference.html#command-options) can be used to specify targets. +See `REFERENCE.md` for an example. + +#### PCP + +Note that you cannot use the Bolt `pcp` transport if your CA certificate has already expired, as the PXP-Agent service itself depends upon a valid CA certificate. + +## Usage + +First, check the expiration of the Puppet agent certificate by running the following command as root on the primary Puppet server: + +``` +/opt/puppetlabs/puppet/bin/openssl x509 -in "$(/opt/puppetlabs/bin/puppet config print hostcert)" -enddate -noout +``` + +If, and only if, the `notAfter` date printed has already passed, then the primary Puppet server certificate has expired and must be cleaned up before the CA can be regenerated. This can be accomplished by passing `regen_primary_cert=true` to the `ca_extend::extend_ca_cert` plan. + + +```bash +bolt plan run ca_extend::extend_ca_cert regen_primary_cert=true --targets <primary_fqdn> compilers=<comma_separated_compiler_fqdns> --run-as root +``` + +Note that if you are running `extend_ca_cert` locally on the primary Puppet server, you can avoid potential Bolt transport issues by specifying `--targets local://$(hostname -f)`, e.g. + +``` +bolt plan run ca_extend::extend_ca_cert --targets local://$(hostname -f) --run-as root +``` + +```bash +bolt plan run ca_extend::upload_ca_cert cert=<path_to_cert> --targets <TargetSpec> +``` + +```bash +bolt task run ca_extend::check_ca_expiry --targets <TargetSpec> +``` + +```bash +bolt task run ca_extend::check_agent_expiry --targets <TargetSpec> +``` + +See `REFERENCE.md` for more detailed examples. + +## Reference + +Puppet's security is based on a PKI using X.509 certificates. + +This module's `ca_extend::extend_ca_cert` plan creates a new self-signed CA certificate using the same keypair as the prior self-signed CA. The new CA has the same: + +* Keypair. +* Subject. +* Issuer. +* X509v3 Subject Key Identifier (the fingerprint of the public key). + +The new CA has a different: + +* Authority Key Identifier (just the serial number, since it's self-signed). +* Validity period (the point of the whole exercise). +* Signature (since we changed the serial number and validity period). + +Since Puppet's services (and other services that use Puppet's PKI) validate certificates by trusting a self-signed CA and comparing its public key to the Signatures and Authority Key Identifiers of the certificates it has issued, +it's possible to issue a new self-signed CA certificate based on a prior keypair without invalidating any certificates issued by the old CA. +Once you've done that, it's just a matter of delivering the new CA certificate to every participant in the PKI. + +## How to Report an issue or contribute to the module + +If you are a PE user and need support using this module or are encountering issues, our Support team would be happy to help you resolve your issue and help reproduce any bugs. Just raise a ticket on the [support portal](https://support.puppet.com/hc/en-us/requests/new). + +If you have a reproducible bug or are a community user you can raise it directly on the Github issues page of the module [here.](https://github.com/puppetlabs/ca_extend/issues) We also welcome PR contributions to improve the module. Please see further details about contributing [here](https://puppet.com/docs/puppet/7.5/contributing.html#contributing_changes_to_module_repositories) diff --git a/code/environments/production/modules/ca_extend/REFERENCE.md b/code/environments/production/modules/ca_extend/REFERENCE.md new file mode 100644 index 0000000..2657a3d --- /dev/null +++ b/code/environments/production/modules/ca_extend/REFERENCE.md @@ -0,0 +1,188 @@ +# Reference + +<!-- DO NOT EDIT: This document was generated by Puppet Strings --> + +## Table of Contents + +### Tasks + +* [`check_agent_expiry`](#check_agent_expiry): Check the expiration date of all agent certificates +* [`check_ca_expiry`](#check_ca_expiry): Check the expiration date of a CA certificate +* [`check_primary_cert`](#check_primary_cert): Check the expiration date of the primary server cert +* [`configure_primary`](#configure_primary): Backup ssldir and copy newly generated CA certificate +* [`extend_ca_cert`](#extend_ca_cert): Extend CA certificate expiry date + +### Plans + +* [`ca_extend::extend_ca_cert`](#ca_extendextend_ca_cert): Plan that extends the Puppet CA certificate and configures the primary Puppet server +and Compilers to use the extended certificate. +* [`ca_extend::get_agent_facts`](#ca_extendget_agent_facts): A plan to work around BOLT-1168 so that one agent failing in apply_prep won't cause the whole plan to fail. +* [`ca_extend::upload_ca_cert`](#ca_extendupload_ca_cert): A plan to upload a given CA certificate to a number of Puppet agent nodes + +## Tasks + +### <a name="check_agent_expiry"></a>`check_agent_expiry` + +Check the expiration date of all agent certificates + +**Supports noop?** false + +#### Parameters + +##### `date` + +Data type: `Optional[String[1]]` + +YYYY-MM-DD date to test whether the certificates will expire by. Defaults to three months from today + +### <a name="check_ca_expiry"></a>`check_ca_expiry` + +Check the expiration date of a CA certificate + +**Supports noop?** false + +#### Parameters + +##### `cert` + +Data type: `Optional[String[1]]` + +Location of the CA certificate to check. Defaults to Puppet's default location + +##### `date` + +Data type: `Optional[String[1]]` + +YYYY-MM-DD date to test whether the certificate will expire by. Defaults to three months from today + +### <a name="check_primary_cert"></a>`check_primary_cert` + +Check the expiration date of the primary server cert + +**Supports noop?** false + +### <a name="configure_primary"></a>`configure_primary` + +Backup ssldir and copy newly generated CA certificate + +**Supports noop?** false + +#### Parameters + +##### `new_cert` + +Data type: `String` + +Location of the newly generated CA certificate + +##### `regen_primary_cert` + +Data type: `Boolean` + +Flag to regerate the primary server's certificate. Set to true to perform the regeneration + +### <a name="extend_ca_cert"></a>`extend_ca_cert` + +Extend CA certificate expiry date + +**Supports noop?** false + +## Plans + +### <a name="ca_extendextend_ca_cert"></a>`ca_extend::extend_ca_cert` + +Plan that extends the Puppet CA certificate and configures the primary Puppet server +and Compilers to use the extended certificate. + +#### Examples + +##### Extend the CA cert and regenerate the primary agent cert locally on the primary Puppet server + +```puppet +bolt plan run ca_extend::extend_ca_cert regen_primary_cert=true --targets local://$(hostname -f) --run-as root +``` + +##### Extend the CA cert by running the plan remotely + +```puppet +bolt plan run ca_extend::extend_ca_cert --targets <primary_fqdn> --run-as root +``` + +#### Parameters + +The following parameters are available in the `ca_extend::extend_ca_cert` plan: + +* [`targets`](#targets) +* [`compilers`](#compilers) +* [`ssldir`](#ssldir) +* [`regen_primary_cert`](#regen_primary_cert) + +##### <a name="targets"></a>`targets` + +Data type: `TargetSpec` + +The target node on which to run the plan. Should be the primary Puppet server + +##### <a name="compilers"></a>`compilers` + +Data type: `Optional[TargetSpec]` + +Optional comma separated list of compilers to upload the certificate to + +Default value: ``undef`` + +##### <a name="ssldir"></a>`ssldir` + +Data type: `Any` + +Location of the ssldir on disk + +Default value: `'/etc/puppetlabs/puppet/ssl'` + +##### <a name="regen_primary_cert"></a>`regen_primary_cert` + +Data type: `Any` + +Whether to also regenerate the agent certificate of the primary Puppet server + +Default value: ``false`` + +### <a name="ca_extendget_agent_facts"></a>`ca_extend::get_agent_facts` + +A plan to work around BOLT-1168 so that one agent failing in apply_prep won't cause the whole plan to fail. + +#### Parameters + +The following parameters are available in the `ca_extend::get_agent_facts` plan: + +* [`nodes`](#nodes) + +##### <a name="nodes"></a>`nodes` + +Data type: `TargetSpec` + +The targets to run apply_prep on + +### <a name="ca_extendupload_ca_cert"></a>`ca_extend::upload_ca_cert` + +A plan to upload a given CA certificate to a number of Puppet agent nodes + +#### Parameters + +The following parameters are available in the `ca_extend::upload_ca_cert` plan: + +* [`nodes`](#nodes) +* [`cert`](#cert) + +##### <a name="nodes"></a>`nodes` + +Data type: `TargetSpec` + +The targets to upload the certificate to + +##### <a name="cert"></a>`cert` + +Data type: `String` + +The location of the CA certificate on disk of the local machine + diff --git a/code/environments/production/modules/ca_extend/data/common.yaml b/code/environments/production/modules/ca_extend/data/common.yaml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/code/environments/production/modules/ca_extend/data/common.yaml @@ -0,0 +1 @@ +--- diff --git a/code/environments/production/modules/ca_extend/files/common.sh b/code/environments/production/modules/ca_extend/files/common.sh new file mode 100644 index 0000000..be052f7 --- /dev/null +++ b/code/environments/production/modules/ca_extend/files/common.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# TODO: helper task? + +# Exit with an error message and error code, defaulting to 1 +fail() { + # Print a stderr: entry if there were anything printed to stderr + if [[ -s $_tmp ]]; then + # Hack to try and output valid json by replacing newlines with spaces. + echo "{ \"status\": \"error\", \"message\": \"$1\", \"stderr\": \"$(tr '\n' ' ' <"$_tmp")\" }" + else + echo "{ \"status\": \"error\", \"message\": \"$1\" }" + fi + + exit "${2:-1}" +} + +success() { + echo "$1" + exit 0 +} + +# Test for colors. If unavailable, unset variables are ok +# shellcheck disable=SC2034 +if tput colors &>/dev/null; then + green="$(tput setaf 2)" + red="$(tput setaf 1)" + reset="$(tput sgr0)" +fi + +_tmp="$(mktemp)" +exec 2>>"$_tmp" + +# Use indirection to munge PT_ environment variables +# e.g. "$PT_version" becomes "$version" +for v in ${!PT_*}; do + declare "${v#*PT_}"="${!v}" +done diff --git a/code/environments/production/modules/ca_extend/files/extend.sh b/code/environments/production/modules/ca_extend/files/extend.sh new file mode 100644 index 0000000..10fb837 --- /dev/null +++ b/code/environments/production/modules/ca_extend/files/extend.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# Credit: https://github.com/Sharpie + +set -e + +PUPPET_BIN='/opt/puppetlabs/puppet/bin' + +ca_cert=$("${PUPPET_BIN}/puppet" config print --section master cacert) +ca_key=$("${PUPPET_BIN}/puppet" config print --section master cakey) +ca_dir=$(dirname "${ca_cert}") + +printf 'CA certificate file: %s\n' "${ca_cert}" >&2 +printf 'CA private key file: %s\n' "${ca_key}" >&2 + +printf '\nChecking CA chain length...\n' >&2 +chain_length=$(grep -cF 'BEGIN CERTIFICATE' "${ca_cert}") + +if (( chain_length > 1 )); then + printf '%s certificates were found in: %s\n' "${chain_length}" "${ca_cert}" >&2 + printf 'This script only works on CA files that contain a single certificate.\n' >&2 + exit 1 +elif (( chain_length != 1 )); then + printf 'No certificates found in: %s\n' "${ca_cert}" >&2 + exit 1 +else + printf '%s certificate found in: %s\n' "${chain_length}" "${ca_cert}" >&2 +fi + +# Compute start and end dates for new certificate. +# Formats the year as YY instead of YYYY because the latter isn't supported +# until OpenSSL 1.1.1. +start_date=$(date -u --date='-24 hours' '+%y%m%d%H%M%SZ') +end_date=$(date -u --date='+15 years' '+%y%m%d%H%M%SZ') +ca_serial_num=$("${PUPPET_BIN}/openssl" x509 -in "${ca_cert}" -noout -serial|cut -d= -f2) + +# Build a temporary directory with files required to renew the CA cert. +workdir=$(mktemp -d -t renew_ca_cert.XXX) +printf '%s' "${ca_serial_num}" > "${workdir}/serial" +touch "${workdir}/inventory" +touch "${workdir}/inventory.attr" + +cat <<EOT > "${workdir}/openssl.cnf" +[ca] +default_ca=ca_settings + +[ca_settings] +serial=${workdir}/serial +new_certs_dir=${workdir} +database=${workdir}/inventory +default_md=sha256 +policy=ca_policy +x509_extensions=cert_extensions + +[ca_policy] +commonName=supplied + +[cert_extensions] +basicConstraints=critical,CA:TRUE +keyUsage=keyCertSign,cRLSign +subjectKeyIdentifier=hash +authorityKeyIdentifier=issuer:always +EOT + +# Generate a signing request from the existing certificate +"${PUPPET_BIN}/openssl" x509 -x509toreq \ + -in "${ca_cert}" \ + -signkey "${ca_key}" \ + -out "${workdir}/ca_csr.pem" + +# Sign the request +new_ca_cert="${ca_dir}/ca_crt-expires-${end_date}.pem" + +yes | "${PUPPET_BIN}/openssl" ca \ + -in "${workdir}/ca_csr.pem" \ + -keyfile "${ca_key}" \ + -config "${workdir}/openssl.cnf" \ + -selfsign \ + -startdate "${start_date}" \ + -enddate "${end_date}" \ + -out "${new_ca_cert}" >&2 + +printf '\nRenewed CA certificate\n' >&2 +printf '%s\n' "${new_ca_cert}" diff --git a/code/environments/production/modules/ca_extend/hiera.yaml b/code/environments/production/modules/ca_extend/hiera.yaml new file mode 100644 index 0000000..806d448 --- /dev/null +++ b/code/environments/production/modules/ca_extend/hiera.yaml @@ -0,0 +1,10 @@ +--- +version: 5 + +defaults: # Used for any hierarchy level that omits these keys. + datadir: data # This path is relative to hiera.yaml's directory. + data_hash: yaml_data # Use the built-in YAML backend. + +hierarchy: + - name: 'common' + path: 'common.yaml' diff --git a/code/environments/production/modules/ca_extend/metadata.json b/code/environments/production/modules/ca_extend/metadata.json new file mode 100644 index 0000000..6d7b3e4 --- /dev/null +++ b/code/environments/production/modules/ca_extend/metadata.json @@ -0,0 +1,59 @@ +{ + "name": "puppetlabs-ca_extend", + "version": "3.0.0", + "author": "Adrian Parreiras Horta", + "summary": "A set of Bolt Plans and Tasks to extend the CA cert in Puppet Enterprise", + "license": "GPL-2.0-only", + "source": "https://github.com/puppetlabs/ca_extend", + "project_page": "https://github.com/puppetlabs/ca_extend", + "issues_url": "https://github.com/puppetlabs/ca_extend/issues", + "dependencies": [ + { + "name": "puppetlabs/stdlib", + "version_requirement": ">= 4.10.0 < 7.0.0" + } + ], + "operatingsystem_support": [ + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "7", + "8" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "7" + ] + }, + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "7", + "8" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "7" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "18.04" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">= 6.16.0 < 8.0.0" + } + ], + "pdk-version": "2.1.0", + "template-url": "https://github.com/puppetlabs/pdk-templates#2.1.0", + "template-ref": "tags/2.1.0-0-ga675ea5" +} diff --git a/code/environments/production/modules/ca_extend/plans/extend_ca_cert.pp b/code/environments/production/modules/ca_extend/plans/extend_ca_cert.pp new file mode 100644 index 0000000..84e4d42 --- /dev/null +++ b/code/environments/production/modules/ca_extend/plans/extend_ca_cert.pp @@ -0,0 +1,84 @@ +# @summary +# Plan that extends the Puppet CA certificate and configures the primary Puppet server +# and Compilers to use the extended certificate. +# @param targets The target node on which to run the plan. Should be the primary Puppet server +# @param compilers Optional comma separated list of compilers to upload the certificate to +# @param ssldir Location of the ssldir on disk +# @param regen_primary_cert Whether to also regenerate the agent certificate of the primary Puppet server +# @example Extend the CA cert and regenerate the primary agent cert locally on the primary Puppet server +# bolt plan run ca_extend::extend_ca_cert regen_primary_cert=true --targets local://$(hostname -f) --run-as root +# @example Extend the CA cert by running the plan remotely +# bolt plan run ca_extend::extend_ca_cert --targets <primary_fqdn> --run-as root +plan ca_extend::extend_ca_cert( + TargetSpec $targets, + Optional[TargetSpec] $compilers = undef, + $ssldir = '/etc/puppetlabs/puppet/ssl', + $regen_primary_cert = false, +) { + $targets.apply_prep + $primary_facts = run_task('facts', $targets, '_catch_errors' => true).first + + if $primary_facts['pe_build'] { + $is_pe = true + $services = ['puppet', 'pe-puppetserver', 'pe-postgresql'] + } + elsif $primary_facts['puppetversion'] { + $is_pe = false + $services = ['puppet', 'puppetserver'] + } + else { + fail_plan("Puppet not detected on ${targets}") + } + + if $is_pe and ! $regen_primary_cert{ + $out = run_task('ca_extend::check_primary_cert', $targets, '_catch_errors' => true).first + unless $out.ok { + fail_plan($out.value['message']) + } + if $out.value['status'] == 'warn' { + warning($out.value['message']) + } + } + + out::message("INFO: Stopping Puppet services on ${targets}") + $services.each |$service| { + run_task('service::linux', $targets, 'action' => 'stop', 'name' => $service) + } + + out::message("INFO: Extending CA certificate on ${targets}") + $regen_results = run_task('ca_extend::extend_ca_cert', $targets) + $new_cert = $regen_results.first.value + $cert_contents = base64('decode', $new_cert['contents']) + + out::message("INFO: Configuring ${targets} to use the extended CA certificate") + if $is_pe { + run_task('ca_extend::configure_primary', $targets, + 'new_cert' => $new_cert['new_cert'], 'regen_primary_cert' => $regen_primary_cert + ) + } + else { + run_command("/bin/cp ${new_cert['new_cert']} ${ssldir}/certs/ca.pem", $targets) + run_command("/bin/cp ${new_cert['new_cert']} ${ssldir}/ca/ca_crt.pem", $targets) + run_task('service::linux', $targets, 'action' => 'start', 'name' => 'puppetserver') + } + run_task('service::linux', $targets, 'action' => 'start', 'name' => 'puppet') + + $tmp = run_command('mktemp', 'localhost', '_run_as' => system::env('USER')) + $tmp_file = $tmp.first.value['stdout'].chomp + file::write($tmp_file, $cert_contents) + + if $compilers { + out::message("INFO: Stopping Puppet services on compilers (${compilers})") + run_task('service::linux', $compilers, 'action' => 'stop', 'name' => 'puppet') + + out::message("INFO: Configuring compilers (${compilers}) to use the extended CA certificate") + upload_file($tmp_file, '/etc/puppetlabs/puppet/ssl/certs/ca.pem', $compilers) + + # Just running Puppet with the new CA certificate in place should be enough. + run_command('/opt/puppetlabs/bin/puppet agent --no-daemonize --no-noop --onetime', $compilers) + run_task('service::linux', $compilers, 'action' => 'start', 'name' => 'puppet') + } + + out::message("INFO: Extended CA certificate decoded and stored at ${tmp_file}") + out::message("INFO: Run the 'ca_extend::upload_ca_cert' plan to distribute the extended CA certificate to agents") +} diff --git a/code/environments/production/modules/ca_extend/plans/get_agent_facts.pp b/code/environments/production/modules/ca_extend/plans/get_agent_facts.pp new file mode 100644 index 0000000..6be14f4 --- /dev/null +++ b/code/environments/production/modules/ca_extend/plans/get_agent_facts.pp @@ -0,0 +1,6 @@ +# @summary +# A plan to work around BOLT-1168 so that one agent failing in apply_prep won't cause the whole plan to fail. +# @param nodes The targets to run apply_prep on +plan ca_extend::get_agent_facts(TargetSpec $nodes) { + $nodes.apply_prep +} diff --git a/code/environments/production/modules/ca_extend/plans/upload_ca_cert.pp b/code/environments/production/modules/ca_extend/plans/upload_ca_cert.pp new file mode 100644 index 0000000..3a1af75 --- /dev/null +++ b/code/environments/production/modules/ca_extend/plans/upload_ca_cert.pp @@ -0,0 +1,84 @@ +# @summary +# A plan to upload a given CA certificate to a number of Puppet agent nodes +# @param nodes The targets to upload the certificate to +# @param cert The location of the CA certificate on disk of the local machine +# @return JSON object with two keys: success and failure. Each key contains any number of objects consisting of the agent certname and the output of the upload_file command +plan ca_extend::upload_ca_cert( + TargetSpec $nodes, + String $cert +) { + # Work around BOLT-1168 + run_plan('ca_extend::get_agent_facts', 'nodes' => $nodes, '_catch_errors' => true) + $tmp = run_plan('facts', 'targets' => $nodes, '_catch_errors' => true) + + # Extract the ResultSet from an error object + case $tmp { + Error['bolt/run-failure']: { + $results = $tmp.details['result_set'] + $not_ok = $results.error_set + } + default: { + $results = $tmp + $not_ok = undef + } + } + + # The os.family fact should consistantly be "windows" on, well, Windows. + $windows_targets = $results.ok_set.filter |$n| { "${n.value['os']['family']}" == 'windows' } + $linux_targets = $results.ok_set.filter |$n| { ! ("${n.value['os']['family']}" == 'windows') } + + $windows_results = upload_file( + $cert, + 'C:\ProgramData\PuppetLabs\puppet\etc\ssl\certs\ca.pem', + $windows_targets.map |$item| { $item.target.name }, + '_catch_errors' => true + ) + + $linux_results = upload_file( + $cert, + '/etc/puppetlabs/puppet/ssl/certs/ca.pem', + $linux_targets.map |$item| { $item.target.name }, + '_catch_errors' => true + ) + + # Create a hash for *nix and Windows successful and failed uploads and merge them together + # filter will return nil if anything doesn't match the lambda, and deep merge will + # crunch the left hashes if the rightmost value isn't a hash, so check for that + $good = deep_merge( + if $linux_results.any |$r| { $r.ok } { + { 'success' => $linux_results.filter |$result| { $result.ok }.map |$result| { + { $result.target.name => $result.value } + }.reduce |$memo, $value| { $memo + $value } + } + }, + if $windows_results.any |$r| { $r.ok } { + { 'success' => $windows_results.filter |$result| { $result.ok }.map |$result| { + { $result.target.name => $result.value } + }.reduce |$memo, $value| { $memo + $value } + } + } + ) + + $bad = deep_merge( + if ! $windows_results.ok { + { 'failure' => $windows_results.filter |$result| { ! $result.ok }.map |$result| { + { $result.target.name => $result.value } + }.reduce |$memo, $value| { $memo + $value } + } + }, + if ! $linux_results.ok { + { 'failure' => $linux_results.filter |$result| { ! $result.ok }.map |$result| { + { $result.target.name => $result.value } + }.reduce |$memo, $value| { $memo + $value } + } + }, + if $not_ok { + { 'failure' => $not_ok.map |$result| { + { $result.target.name => $result.value } + }.reduce |$memo, $value| { $memo + $value } + } + } + ) + + return deep_merge($good, $bad) +} diff --git a/code/environments/production/modules/ca_extend/tasks/check_agent_expiry.json b/code/environments/production/modules/ca_extend/tasks/check_agent_expiry.json new file mode 100644 index 0000000..a3792cd --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/check_agent_expiry.json @@ -0,0 +1,13 @@ +{ + "description": "Check the expiration date of all agent certificates", + "parameters": { + "date": { + "description": "YYYY-MM-DD date to test whether the certificates will expire by. Defaults to three months from today", + "type": "Optional[String[1]]" + } + }, + + "implementations": [ + {"name": "check_agent_expiry.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh"], "input_method": "environment"} + ] +} diff --git a/code/environments/production/modules/ca_extend/tasks/check_agent_expiry.sh b/code/environments/production/modules/ca_extend/tasks/check_agent_expiry.sh new file mode 100644 index 0000000..780f4a6 --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/check_agent_expiry.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +declare PT__installdir +# shellcheck disable=SC1090 +source "$PT__installdir/ca_extend/files/common.sh" +PUPPET_BIN='/opt/puppetlabs/puppet/bin' + +valid=() +expired=() + +to_date="${date:-+3 months}" +to_date="$(date --date="$to_date" +"%s")" || fail "Error calculating date" + +# It's possible that we are not on a Puppet AIO system. If we cannot find a +# openssl binary in the AIO directory, we accept one in $PATH +if [ "$(command -v "${PUPPET_BIN}/openssl")" ]; then + openssl="${PUPPET_BIN}/openssl" +else + openssl="$(command -v openssl)" +fi + +shopt -s nullglob + +for f in "$($PUPPET_BIN/puppet config print signeddir)"/*; do + # The -checkend command in openssl takes a number of seconds as an argument + # However, on older versions we may overflow a 32 bit integer if we use that + # So, we'll use bash arithmetic and `date` to do the comparison + expiry_date="$(${openssl} x509 -enddate -noout -in "${f}")" + expiry_date="${expiry_date#*=}" + expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from enddate" + + if (( to_date >= expiry_seconds )); then + expired+=("\"$f\"") + else + valid+=("\"$f\"") + fi +done + +# This is ugly, we as of now we don't include jq binaries in Bolt +# As long as there aren't weird characters in certnames it should be ok +(IFS=,; printf '{"valid": [%s], "expiring": [%s]}' "${valid[*]}" "${expired[*]}") diff --git a/code/environments/production/modules/ca_extend/tasks/check_ca_expiry.json b/code/environments/production/modules/ca_extend/tasks/check_ca_expiry.json new file mode 100644 index 0000000..6eaee1c --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/check_ca_expiry.json @@ -0,0 +1,17 @@ +{ + "description": "Check the expiration date of a CA certificate", + "parameters": { + "cert": { + "description": "Location of the CA certificate to check. Defaults to Puppet's default location", + "type": "Optional[String[1]]" + }, + "date": { + "description": "YYYY-MM-DD date to test whether the certificate will expire by. Defaults to three months from today", + "type": "Optional[String[1]]" + } + }, + + "implementations": [ + {"name": "check_ca_expiry.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh"], "input_method": "environment"} + ] +} diff --git a/code/environments/production/modules/ca_extend/tasks/check_ca_expiry.sh b/code/environments/production/modules/ca_extend/tasks/check_ca_expiry.sh new file mode 100644 index 0000000..5df1aa0 --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/check_ca_expiry.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +declare PT__installdir +# shellcheck disable=SC1090 +source "$PT__installdir/ca_extend/files/common.sh" +PUPPET_BIN='/opt/puppetlabs/puppet/bin' + +cert="${cert:-/etc/puppetlabs/puppet/ssl/certs/ca.pem}" +[[ -e $cert ]] || fail "cert $cert not found" + +to_date="${date:-+3 months}" +to_date="$(date --date="$to_date" +"%s")" || fail "Error calculating date" + +# Sanity check that we're dealing with a valid cert +"${PUPPET_BIN}/openssl" x509 -in "$cert" >/dev/null || fail "Error checking $cert" + +# The -checkend command in openssl takes a number of seconds as an argument +# However, on older versions we may overflow a 32 bit integer if we use that +# So, we'll use bash arithmetic and `date` to do the comparison +expiry_date="$("${PUPPET_BIN}/openssl" x509 -enddate -noout -in "$cert")" +expiry_date="${expiry_date#*=}" +expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from enddate" + +if (( to_date >= expiry_seconds )); then + success "{ \"status\": \"will expire\", \"expiry date\": \"$expiry_date\" }" +else + success "{ \"status\": \"valid\", \"expiry date\": \"$expiry_date\" }" +fi diff --git a/code/environments/production/modules/ca_extend/tasks/check_primary_cert.json b/code/environments/production/modules/ca_extend/tasks/check_primary_cert.json new file mode 100644 index 0000000..3167d9a --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/check_primary_cert.json @@ -0,0 +1,8 @@ +{ + "description": "Check the expiration date of the primary server cert", + "parameters": {}, + + "implementations": [ + {"name": "check_primary_cert.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh"], "input_method": "environment"} + ] +} diff --git a/code/environments/production/modules/ca_extend/tasks/check_primary_cert.sh b/code/environments/production/modules/ca_extend/tasks/check_primary_cert.sh new file mode 100644 index 0000000..9a5f74e --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/check_primary_cert.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +declare PT__installdir +# shellcheck disable=SC1090 +source "$PT__installdir/ca_extend/files/common.sh" +PUPPET_BIN='/opt/puppetlabs/puppet/bin' + +hostcert="$($PUPPET_BIN/puppet config print hostcert)" +[[ -e $hostcert ]] || fail "ERROR: primary server cert not found. pass regen_primary_cert=true to the plan to regenerate it if needed." + +expiry_date="$($PUPPET_BIN/openssl x509 -enddate -noout -in "$hostcert")" +expiry_date="${expiry_date#*=}" +expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from enddate" + +if (( $(date +"%s") >= expiry_seconds )); then + fail "ERROR: the primary server certificate has expired. Please pass regen_primary_cert=true to the plan to regenerate it." +elif (( $(date --date="+3 months" +"%s") >= expiry_seconds )); then + success '{ "status": "warn", "message": "WARN: Primary cert expiring within 3 months. Either regenerate manually or pass regen_primary_cert=true to the plan to regenerate it." }' +else + success '{ "status": "success", "message": "Primary cert ok" }' +fi diff --git a/code/environments/production/modules/ca_extend/tasks/configure_primary.json b/code/environments/production/modules/ca_extend/tasks/configure_primary.json new file mode 100644 index 0000000..0520327 --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/configure_primary.json @@ -0,0 +1,17 @@ +{ + "description": "Backup ssldir and copy newly generated CA certificate", + "parameters": { + "new_cert": { + "description": "Location of the newly generated CA certificate", + "type": "String" + }, + "regen_primary_cert": { + "description": "Flag to regerate the primary server's certificate. Set to true to perform the regeneration", + "type": "Boolean" + } + }, + + "implementations": [ + {"name": "configure_primary.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh", "ca_extend/files/extend.sh"], "input_method": "environment"} + ] +} diff --git a/code/environments/production/modules/ca_extend/tasks/configure_primary.sh b/code/environments/production/modules/ca_extend/tasks/configure_primary.sh new file mode 100644 index 0000000..dd47f54 --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/configure_primary.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +declare PT__installdir +# shellcheck disable=SC1090 +source "$PT__installdir/ca_extend/files/common.sh" + +PUPPET_BIN='/opt/puppetlabs/puppet/bin' +ssldir="$($PUPPET_BIN/puppet config print ssldir)" +cadir="$($PUPPET_BIN/puppet config print cadir)" +ca_dirs=("$ssldir" "$cadir") + +mkdir -p /var/puppetlabs/backups/ +cp -aR "$ssldir" /var/puppetlabs/backups || fail "Error backing up '/etc/puppetlabs/puppet/ssl'" + +# shellcheck disable=SC2154 +[[ $regen_primary_cert == "true" ]] && { + # add the command substitutions to get ssldir and cadir to an array + find "${ca_dirs[@]}" -name "$($PUPPET_BIN/puppet config print certname).pem" -delete +} + +# shellcheck disable=SC2154 +cp "$new_cert" "${cadir}/ca_crt.pem" || fail "Error copying 'ca_crt.pem'" +cp "$new_cert" "${ssldir}/certs/ca.pem" || fail "Error copying 'ca.pem'" + +PATH="${PATH}:/opt/puppetlabs/bin" puppet infrastructure configure --no-recover || fail "Error running 'puppet infrastructure configure'" + +success '{ "status": "success" }' diff --git a/code/environments/production/modules/ca_extend/tasks/extend_ca_cert.json b/code/environments/production/modules/ca_extend/tasks/extend_ca_cert.json new file mode 100644 index 0000000..825f323 --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/extend_ca_cert.json @@ -0,0 +1,6 @@ +{ + "description": "Extend CA certificate expiry date", + "implementations": [ + {"name": "extend_ca_cert.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh", "ca_extend/files/extend.sh"], "input_method": "environment"} + ] +} diff --git a/code/environments/production/modules/ca_extend/tasks/extend_ca_cert.sh b/code/environments/production/modules/ca_extend/tasks/extend_ca_cert.sh new file mode 100755 index 0000000..b5e1b16 --- /dev/null +++ b/code/environments/production/modules/ca_extend/tasks/extend_ca_cert.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +declare PT__installdir +# shellcheck disable=SC1090 +source "$PT__installdir/ca_extend/files/common.sh" + +echo "test" | base64 -w 0 - &>/dev/null || fail "This script requires a version of base64 with the -w flag" + +new_cert="$(bash "$PT__installdir/ca_extend/files/extend.sh")" || fail "Error extending CA certificate expiry date" +contents="$(base64 -w 0 "$new_cert")" || fail "Error encoding CA certificate" + +success "{ \"status\": \"success\", \"new_cert\": \"$new_cert\", \"contents\": \"$contents\" }" |