├── .ruby-version ├── data └── common.yaml ├── .yardopts ├── pdk.yaml ├── .rspec ├── .gitattributes ├── .puppet-lint.rc ├── .vscode └── extensions.json ├── spec ├── spec_helper_acceptance.rb ├── spec_helper_acceptance_local.rb ├── acceptance │ └── tasks │ │ ├── check_ca_expiry_spec.rb │ │ └── check_primary_cert_spec.rb ├── default_facts.yml ├── fixtures │ └── matrix │ │ ├── lts.json │ │ └── latest.json └── spec_helper.rb ├── .devcontainer ├── Dockerfile ├── devcontainer.json └── README.md ├── plans ├── get_agent_facts.pp ├── upload_ca_cert.pp └── extend_ca_cert.pp ├── tasks ├── extend_ca_cert.json ├── check_primary_cert.json ├── check_crl_cert.json ├── check_agent_expiry.json ├── extend_ca_cert.sh ├── configure_primary.json ├── check_ca_expiry.json ├── check_crl_cert.sh ├── crl_truncate.json ├── configure_primary.sh ├── check_primary_cert.sh ├── check_ca_expiry.sh ├── check_agent_expiry.sh └── crl_truncate.sh ├── hiera.yaml ├── CODEOWNERS ├── .gitignore ├── .github └── workflows │ ├── call_nightly.yml │ ├── main_pr_testing.yml │ ├── release.yml │ ├── spec.yml │ ├── auto_release.yml │ ├── pe_latest_testing.yml │ ├── pe_lts_testing.yml │ └── pe_nightly_testing.yml ├── .pdkignore ├── .sync.yml ├── files ├── common.sh └── extend.sh ├── .fixtures.yml ├── metadata.json ├── Gemfile ├── Rakefile ├── CHANGELOG.md ├── REFERENCE.md ├── README.md └── .rubocop.yml /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.0 2 | -------------------------------------------------------------------------------- /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | -------------------------------------------------------------------------------- /pdk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignore: [] 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rb eol=lf 2 | *.erb eol=lf 3 | *.pp eol=lf 4 | *.sh eol=lf 5 | *.epp eol=lf 6 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | --relative 2 | --no-unquoted_string_in_case-check 3 | --no-manifest_whitespace_opening_brace_before-check 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "puppet.puppet-vscode", 4 | "rebornix.Ruby" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet_litmus' 4 | PuppetLitmus.configure! 5 | 6 | require 'spec_helper_acceptance_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_acceptance_local.rb')) 7 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance_local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'singleton' 4 | require 'serverspec' 5 | require 'puppetlabs_spec_helper/module_spec_helper' 6 | 7 | class LitmusHelper 8 | include Singleton 9 | include PuppetLitmus 10 | end 11 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM puppet/pdk:latest 2 | 3 | # [Optional] Uncomment this section to install additional packages. 4 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 5 | # && apt-get -y install --no-install-recommends 6 | 7 | -------------------------------------------------------------------------------- /plans/get_agent_facts.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # A plan to work around BOLT-1168 so that one agent failing in apply_prep won't cause the whole plan to fail. 3 | # @param nodes The targets to run apply_prep on 4 | plan ca_extend::get_agent_facts(TargetSpec $nodes) { 5 | $nodes.apply_prep 6 | } 7 | -------------------------------------------------------------------------------- /spec/acceptance/tasks/check_ca_expiry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'check_ca_expiry task' do 4 | it 'returns valid by default' do 5 | result = run_bolt_task('ca_extend::check_ca_expiry') 6 | expect(result.stdout).to contain(%r{valid}) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/acceptance/tasks/check_primary_cert_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'check_primary_cert task' do 4 | it 'returns success' do 5 | result = run_bolt_task('ca_extend::check_primary_cert') 6 | expect(result.stdout).to contain(%r{success}) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /tasks/extend_ca_cert.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Extend CA certificate expiry date", 3 | "implementations": [ 4 | {"name": "extend_ca_cert.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh", "ca_extend/files/extend.sh"], "input_method": "environment"} 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /spec/default_facts.yml: -------------------------------------------------------------------------------- 1 | # Use default_module_facts.yml for module specific facts. 2 | # 3 | # Facts specified here will override the values provided by rspec-puppet-facts. 4 | --- 5 | ipaddress: "172.16.254.254" 6 | ipaddress6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA" 7 | is_pe: false 8 | macaddress: "AA:AA:AA:AA:AA:AA" 9 | -------------------------------------------------------------------------------- /tasks/check_primary_cert.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Check the expiration date of the primary server cert", 3 | "parameters": {}, 4 | 5 | "implementations": [ 6 | {"name": "check_primary_cert.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh"], "input_method": "environment"} 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | 4 | defaults: # Used for any hierarchy level that omits these keys. 5 | datadir: data # This path is relative to hiera.yaml's directory. 6 | data_hash: yaml_data # Use the built-in YAML backend. 7 | 8 | hierarchy: 9 | - name: 'common' 10 | path: 'common.yaml' 11 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This will cause the code owners of this repo to be assigned review of any 2 | # opened PRs against the branches containing this file. 3 | # See https://help.github.com/en/articles/about-code-owners for info on how to 4 | # take ownership of parts of the code base that should be reviewed by another 5 | # team. 6 | 7 | * @puppetlabs/support 8 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Puppet Development Kit (Community)", 3 | "dockerFile": "Dockerfile", 4 | 5 | "settings": { 6 | "terminal.integrated.profiles.linux": { 7 | "bash": { 8 | "path": "bash" 9 | } 10 | } 11 | }, 12 | 13 | "extensions": [ 14 | "puppet.puppet-vscode", 15 | "rebornix.Ruby" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .*.sw[op] 3 | .metadata 4 | .yardoc 5 | .yardwarns 6 | *.iml 7 | /.bundle/ 8 | /.idea/ 9 | /.vagrant/ 10 | /coverage/ 11 | /bin/ 12 | /doc/ 13 | /Gemfile.local 14 | /Gemfile.lock 15 | /junit/ 16 | /log/ 17 | /pkg/ 18 | /spec/fixtures/manifests/ 19 | /spec/fixtures/modules/ 20 | /tmp/ 21 | /vendor/ 22 | /convert_report.txt 23 | /update_report.txt 24 | .DS_Store 25 | .project 26 | .envrc 27 | /inventory.yaml 28 | /spec/fixtures/litmus_inventory.yaml 29 | -------------------------------------------------------------------------------- /tasks/check_crl_cert.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Check the expiration date of the primary server crl", 3 | "parameters": {}, 4 | "implementations": [ 5 | { 6 | "name": "check_crl_cert.sh", 7 | "requirements": [ 8 | "shell" 9 | ], 10 | "files": [ 11 | "ca_extend/files/common.sh" 12 | ], 13 | "input_method": "environment" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tasks/check_agent_expiry.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Check the expiration date of all agent certificates", 3 | "parameters": { 4 | "date": { 5 | "description": "YYYY-MM-DD date to test whether the certificates will expire by. Defaults to three months from today", 6 | "type": "Optional[String[1]]" 7 | } 8 | }, 9 | 10 | "implementations": [ 11 | {"name": "check_agent_expiry.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh"], "input_method": "environment"} 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/call_nightly.yml: -------------------------------------------------------------------------------- 1 | name: Call nightly testing workflows 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | spec_testing: 8 | name: "Spec testing" 9 | uses: ./.github/workflows/spec.yml 10 | 11 | nightly_testing: 12 | if: github.repository != 'puppetlabs/puppetlabs-bash_task_helper' && github.repository != 'puppetlabs/puppetlabs-puppet_operations_appliance' 13 | name: "PE Nightly Testing" 14 | needs: spec_testing 15 | uses: ./.github/workflows/pe_nightly_testing.yml 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /tasks/extend_ca_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare PT__installdir 4 | # shellcheck disable=SC1090 5 | source "$PT__installdir/ca_extend/files/common.sh" 6 | 7 | echo "test" | base64 -w 0 - &>/dev/null || fail "This script requires a version of base64 with the -w flag" 8 | 9 | new_cert="$(bash "$PT__installdir/ca_extend/files/extend.sh")" || fail "Error extending CA certificate expiry date" 10 | contents="$(base64 -w 0 "$new_cert")" || fail "Error encoding CA certificate" 11 | 12 | success "{ \"status\": \"success\", \"new_cert\": \"$new_cert\", \"contents\": \"$contents\" }" 13 | -------------------------------------------------------------------------------- /.github/workflows/main_pr_testing.yml: -------------------------------------------------------------------------------- 1 | name: Call main PR testing workflows 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths-ignore: 7 | - '**.md' 8 | - 'examples/**' 9 | - 'CODEOWNERS' 10 | - 'LICENSE' 11 | 12 | jobs: 13 | spec_testing: 14 | name: "Spec testing" 15 | uses: ./.github/workflows/spec.yml 16 | 17 | lts_testing: 18 | name: "PE LTS Testing" 19 | needs: spec_testing 20 | uses: ./.github/workflows/pe_lts_testing.yml 21 | 22 | latest_testing: 23 | name: "PE latest Testing" 24 | needs: spec_testing 25 | uses: ./.github/workflows/pe_latest_testing.yml 26 | -------------------------------------------------------------------------------- /tasks/configure_primary.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Backup ssldir and copy newly generated CA certificate", 3 | "parameters": { 4 | "new_cert": { 5 | "description": "Location of the newly generated CA certificate", 6 | "type": "String" 7 | }, 8 | "regen_primary_cert": { 9 | "description": "Flag to regerate the primary server's certificate. Set to true to perform the regeneration", 10 | "type": "Boolean" 11 | } 12 | }, 13 | 14 | "implementations": [ 15 | {"name": "configure_primary.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh", "ca_extend/files/extend.sh"], "input_method": "environment"} 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tasks/check_ca_expiry.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Check the expiration date of a CA certificate", 3 | "parameters": { 4 | "cert": { 5 | "description": "Location of the CA certificate to check. Defaults to Puppet's default location", 6 | "type": "Optional[String[1]]" 7 | }, 8 | "date": { 9 | "description": "YYYY-MM-DD date to test whether the certificate will expire by. Defaults to three months from today", 10 | "type": "Optional[String[1]]" 11 | } 12 | }, 13 | 14 | "implementations": [ 15 | {"name": "check_ca_expiry.sh", "requirements": ["shell"], "files": ["ca_extend/files/common.sh"], "input_method": "environment"} 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.pdkignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .*.sw[op] 3 | .metadata 4 | .yardoc 5 | .yardwarns 6 | *.iml 7 | /.bundle/ 8 | /.idea/ 9 | /.vagrant/ 10 | /coverage/ 11 | /bin/ 12 | /doc/ 13 | /Gemfile.local 14 | /Gemfile.lock 15 | /junit/ 16 | /log/ 17 | /pkg/ 18 | /spec/fixtures/manifests/ 19 | /spec/fixtures/modules/ 20 | /tmp/ 21 | /vendor/ 22 | /convert_report.txt 23 | /update_report.txt 24 | .DS_Store 25 | .project 26 | .envrc 27 | /inventory.yaml 28 | /spec/fixtures/litmus_inventory.yaml 29 | /.fixtures.yml 30 | /Gemfile 31 | /.gitattributes 32 | /.gitignore 33 | /.pdkignore 34 | /.puppet-lint.rc 35 | /Rakefile 36 | /rakelib/ 37 | /.rspec 38 | /..yml 39 | /.yardopts 40 | /spec/ 41 | /.vscode/ 42 | /.sync.yml 43 | /.devcontainer/ 44 | -------------------------------------------------------------------------------- /tasks/check_crl_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare PT__installdir 4 | # shellcheck disable=SC1090 5 | source "$PT__installdir/ca_extend/files/common.sh" 6 | PUPPET_BIN='/opt/puppetlabs/puppet/bin' 7 | 8 | hostcert="$($PUPPET_BIN/puppet config print cacrl)" 9 | [[ -e $hostcert ]] || fail "ERROR: primary server CA cert is not found." 10 | 11 | expiry_date="$($PUPPET_BIN/openssl crl -nextupdate -noout -in "$hostcert")" 12 | expiry_date="${expiry_date#*=}" 13 | expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from nextupdate" 14 | 15 | if (( $(date +"%s") >= expiry_seconds )); then 16 | success '{ "status": "expired", "message": "Ca crl cert is expired, run the crl_truncate task to generate a new crl" }' 17 | else 18 | success '{ "status": "success", "message": "Ca crl cert is ok" }' 19 | fi 20 | -------------------------------------------------------------------------------- /tasks/crl_truncate.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Truncate the CRL issued by the Puppet CA", 3 | "parameters": { 4 | "ssldir": { 5 | "description": "The location of the Puppet ssl dir", 6 | "type": "Optional[String[1]]" 7 | }, 8 | "crl_expiration_days": { 9 | "description": "The number of days until the new CRL expires. Defaults to 15 years (5475 days)", 10 | "type": "Integer[1]", 11 | "default": 5475 12 | }, 13 | "run_puppet_agent": { 14 | "description": "Whether to run the Puppet agent after creating the CRL. Defaults to true", 15 | "type": "Boolean", 16 | "default": false 17 | } 18 | }, 19 | "implementations": [ 20 | { 21 | "name": "crl_truncate.sh", 22 | "requirements": [ 23 | "shell" 24 | ], 25 | "input_method": "environment" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | common: 3 | 4 | appveyor.yml: 5 | delete: true 6 | .gitlab-ci.yml: 7 | delete: true 8 | spec/spec_helper.rb: 9 | mock_with: ":rspec" 10 | coverage_report: true 11 | .github/workflows/auto_release.yml: 12 | unmanaged: true 13 | .github/workflows/spec.yml: 14 | checks: 'syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop' 15 | unmanaged: true 16 | .github/workflows/release.yml: 17 | unmanaged: true 18 | .travis.yml: 19 | delete: true 20 | Rakefile: 21 | changelog_user: "puppetlabs" 22 | extra_disabled_lint_checks: 23 | - unquoted_string_in_case 24 | - manifest_whitespace_opening_brace_before 25 | Gemfile: 26 | optional: 27 | ":development": 28 | - gem: github_changelog_generator 29 | version: '= 1.15.2' 30 | - gem: 'octokit' 31 | version: 32 | '= 4.21.0' 33 | -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | # devcontainer 2 | 3 | 4 | For format details, see https://aka.ms/devcontainer.json. 5 | 6 | For config options, see the README at: 7 | https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/puppet 8 | 9 | ``` json 10 | { 11 | "name": "Puppet Development Kit (Community)", 12 | "dockerFile": "Dockerfile", 13 | 14 | // Set *default* container specific settings.json values on container create. 15 | "settings": { 16 | "terminal.integrated.profiles.linux": { 17 | "bash": { 18 | "path": "bash", 19 | } 20 | } 21 | }, 22 | 23 | // Add the IDs of extensions you want installed when the container is created. 24 | "extensions": [ 25 | "puppet.puppet-vscode", 26 | "rebornix.Ruby" 27 | ], 28 | 29 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 30 | "forwardPorts": [], 31 | 32 | // Use 'postCreateCommand' to run commands after the container is created. 33 | "postCreateCommand": "pdk --version", 34 | } 35 | ``` 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /files/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO: helper task? 4 | 5 | # Exit with an error message and error code, defaulting to 1 6 | fail() { 7 | # Print a stderr: entry if there were anything printed to stderr 8 | if [[ -s $_tmp ]]; then 9 | # Hack to try and output valid json by replacing newlines with spaces. 10 | echo "{ \"status\": \"error\", \"message\": \"$1\", \"stderr\": \"$(tr '\n' ' ' <"$_tmp")\" }" 11 | else 12 | echo "{ \"status\": \"error\", \"message\": \"$1\" }" 13 | fi 14 | 15 | exit "${2:-1}" 16 | } 17 | 18 | success() { 19 | echo "$1" 20 | exit 0 21 | } 22 | 23 | # Test for colors. If unavailable, unset variables are ok 24 | # shellcheck disable=SC2034 25 | if tput colors &>/dev/null; then 26 | green="$(tput setaf 2)" 27 | red="$(tput setaf 1)" 28 | reset="$(tput sgr0)" 29 | fi 30 | 31 | _tmp="$(mktemp)" 32 | exec 2>>"$_tmp" 33 | 34 | # Use indirection to munge PT_ environment variables 35 | # e.g. "$PT_version" becomes "$version" 36 | for v in ${!PT_*}; do 37 | declare "${v#*PT_}"="${!v}" 38 | done 39 | -------------------------------------------------------------------------------- /tasks/configure_primary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare PT__installdir 4 | # shellcheck disable=SC1090 5 | source "$PT__installdir/ca_extend/files/common.sh" 6 | 7 | PUPPET_BIN='/opt/puppetlabs/puppet/bin' 8 | ssldir="$($PUPPET_BIN/puppet config print ssldir)" 9 | cadir="$($PUPPET_BIN/puppet config print cadir)" 10 | ca_dirs=("$ssldir" "$cadir") 11 | 12 | mkdir -p /var/puppetlabs/backups/ 13 | cp -aR "$ssldir" /var/puppetlabs/backups || fail "Error backing up '/etc/puppetlabs/puppet/ssl'" 14 | 15 | # shellcheck disable=SC2154 16 | [[ $regen_primary_cert == "true" ]] && { 17 | # add the command substitutions to get ssldir and cadir to an array 18 | find "${ca_dirs[@]}" -name "$($PUPPET_BIN/puppet config print certname).pem" -delete 19 | } 20 | 21 | # shellcheck disable=SC2154 22 | cp "$new_cert" "${cadir}/ca_crt.pem" || fail "Error copying 'ca_crt.pem'" 23 | cp "$new_cert" "${ssldir}/certs/ca.pem" || fail "Error copying 'ca.pem'" 24 | 25 | PATH="${PATH}:/opt/puppetlabs/bin" puppet infrastructure configure --no-recover || fail "Error running 'puppet infrastructure configure'" 26 | 27 | success '{ "status": "success" }' 28 | -------------------------------------------------------------------------------- /tasks/check_primary_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare PT__installdir 4 | # shellcheck disable=SC1090 5 | source "$PT__installdir/ca_extend/files/common.sh" 6 | PUPPET_BIN='/opt/puppetlabs/puppet/bin' 7 | 8 | hostcert="$($PUPPET_BIN/puppet config print hostcert)" 9 | [[ -e $hostcert ]] || fail "ERROR: primary server cert not found. pass regen_primary_cert=true to the plan to regenerate it if needed." 10 | 11 | expiry_date="$($PUPPET_BIN/openssl x509 -enddate -noout -in "$hostcert")" 12 | expiry_date="${expiry_date#*=}" 13 | expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from enddate" 14 | 15 | if (( $(date +"%s") >= expiry_seconds )); then 16 | fail "ERROR: the primary server certificate has expired. Please pass regen_primary_cert=true to the plan to regenerate it." 17 | elif (( $(date --date="+3 months" +"%s") >= expiry_seconds )); then 18 | 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." }' 19 | else 20 | success '{ "status": "success", "message": "Primary cert ok" }' 21 | fi 22 | -------------------------------------------------------------------------------- /tasks/check_ca_expiry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare PT__installdir 4 | # shellcheck disable=SC1090 5 | source "$PT__installdir/ca_extend/files/common.sh" 6 | PUPPET_BIN='/opt/puppetlabs/puppet/bin' 7 | 8 | cert="${cert:-/etc/puppetlabs/puppet/ssl/certs/ca.pem}" 9 | [[ -e $cert ]] || fail "cert $cert not found" 10 | 11 | to_date="${date:-+3 months}" 12 | to_date="$(date --date="$to_date" +"%s")" || fail "Error calculating date" 13 | 14 | # Sanity check that we're dealing with a valid cert 15 | "${PUPPET_BIN}/openssl" x509 -in "$cert" >/dev/null || fail "Error checking $cert" 16 | 17 | # The -checkend command in openssl takes a number of seconds as an argument 18 | # However, on older versions we may overflow a 32 bit integer if we use that 19 | # So, we'll use bash arithmetic and `date` to do the comparison 20 | expiry_date="$("${PUPPET_BIN}/openssl" x509 -enddate -noout -in "$cert")" 21 | expiry_date="${expiry_date#*=}" 22 | expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from enddate" 23 | 24 | if (( to_date >= expiry_seconds )); then 25 | success "{ \"status\": \"will expire\", \"expiry date\": \"$expiry_date\" }" 26 | else 27 | success "{ \"status\": \"valid\", \"expiry date\": \"$expiry_date\" }" 28 | fi 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish module" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | create-github-release: 8 | name: Deploy GitHub Release 9 | runs-on: ubuntu-24.04 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | with: 14 | ref: ${{ github.ref }} 15 | clean: true 16 | fetch-depth: 0 17 | - name: Get Version 18 | id: gv 19 | run: | 20 | echo "ver=$(jq --raw-output .version metadata.json)" >> $GITHUB_OUTPUT 21 | - name: Create release 22 | id: create_release 23 | run: | 24 | gh release create v${{ steps.gv.outputs.ver }} --title v${{ steps.gv.outputs.ver }} 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | deploy-forge: 29 | name: Deploy to Forge 30 | runs-on: ubuntu-24.04 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v3 34 | with: 35 | ref: ${{ github.ref }} 36 | clean: true 37 | - name: "PDK Build" 38 | uses: docker://puppet/pdk:2.1.0.0 39 | with: 40 | args: 'build' 41 | - name: "Push to Forge" 42 | uses: docker://puppet/pdk:2.1.0.0 43 | with: 44 | args: 'release publish --forge-token ${{ secrets.FORGE_API_KEY }} --force' 45 | -------------------------------------------------------------------------------- /spec/fixtures/matrix/lts.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": [ 3 | { 4 | "label": "RedHat-9", 5 | "provider": "provision::provision_service", 6 | "image": "rhel-9" 7 | }, 8 | { 9 | "label": "Ubuntu-2204", 10 | "provider": "provision::provision_service", 11 | "image": "ubuntu-2204-lts" 12 | }, 13 | { 14 | "label": "Ubuntu-2404", 15 | "provider": "provision::provision_service", 16 | "image": "ubuntu-2404-lts-amd64" 17 | }, 18 | { 19 | "label": "RedHat-8", 20 | "provider": "provision::provision_service", 21 | "image": "rhel-8" 22 | }, 23 | { 24 | "label": "AlmaLinux-8", 25 | "provider": "provision::provision_service", 26 | "image": "almalinux-cloud/almalinux-8" 27 | }, 28 | { 29 | "label": "Rocky-Linux-8", 30 | "provider": "provision::provision_service", 31 | "image": "rocky-linux-cloud/rocky-linux-8" 32 | }, 33 | { 34 | "label": "Sles-15", 35 | "provider": "provision::provision_service", 36 | "image": "sles-15" 37 | }, 38 | { 39 | "label": "Rocky-Linux-9", 40 | "provider": "provision::provision_service", 41 | "image": "rocky-linux-cloud/rocky-linux-9" 42 | }, 43 | { 44 | "label": "Alma-Linux-9", 45 | "provider": "provision::provision_service", 46 | "image": "almalinux-cloud/almalinux-9" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /spec/fixtures/matrix/latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": [ 3 | { 4 | "label": "RedHat-9", 5 | "provider": "provision::provision_service", 6 | "image": "rhel-9" 7 | }, 8 | { 9 | "label": "Ubuntu-2404", 10 | "provider": "provision::provision_service", 11 | "image": "ubuntu-2404-lts-amd64" 12 | }, 13 | { 14 | "label": "Ubuntu-2204", 15 | "provider": "provision::provision_service", 16 | "image": "ubuntu-2204-lts" 17 | }, 18 | { 19 | "label": "RedHat-8", 20 | "provider": "provision::provision_service", 21 | "image": "rhel-8" 22 | }, 23 | { 24 | "label": "AlmaLinux-8", 25 | "provider": "provision::provision_service", 26 | "image": "almalinux-cloud/almalinux-8" 27 | }, 28 | { 29 | "label": "Rocky-Linux-8", 30 | "provider": "provision::provision_service", 31 | "image": "rocky-linux-cloud/rocky-linux-8" 32 | }, 33 | { 34 | "label": "Sles-15", 35 | "provider": "provision::provision_service", 36 | "image": "sles-15" 37 | }, 38 | { 39 | "label": "Rocky-Linux-9", 40 | "provider": "provision::provision_service", 41 | "image": "rocky-linux-cloud/rocky-linux-9" 42 | }, 43 | { 44 | "label": "Alma-Linux-9", 45 | "provider": "provision::provision_service", 46 | "image": "almalinux-cloud/almalinux-9" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | # This file can be used to install module dependencies for unit testing 2 | # See https://github.com/puppetlabs/puppetlabs_spec_helper#using-fixtures for details 3 | --- 4 | fixtures: 5 | forge_modules: 6 | # stdlib: "puppetlabs/stdlib" 7 | service: "puppetlabs/service" 8 | package: "puppetlabs/package" 9 | reboot: "puppetlabs/reboot" 10 | repositories: 11 | provision: 'https://github.com/puppetlabs/provision' 12 | bootstrap: 'https://github.com/puppetlabs/puppetlabs-bootstrap' 13 | puppet_conf: 'https://github.com/puppetlabs/puppetlabs-puppet_conf' 14 | deploy_pe: 'https://github.com/jarretlavallee/puppet-deploy_pe' 15 | ruby_task_helper: 'https://git@github.com/puppetlabs/puppetlabs-ruby_task_helper' 16 | stdlib: 'https://github.com/puppetlabs/puppetlabs-stdlib' 17 | facts: 'https://github.com/puppetlabs/puppetlabs-facts' 18 | puppet_agent: 'https://github.com/puppetlabs/puppetlabs-puppet_agent.git' 19 | node_manager: 'https://github.com/MartyEwings/puppet-node_manager.git' 20 | apply_helpers: 'https://github.com/puppetlabs/puppetlabs-apply_helpers' 21 | bolt_shim: 'https://github.com/puppetlabs/puppetlabs-bolt_shim' 22 | debug: 'https://github.com/nwops/puppet-debug' 23 | format: 'https://github.com/voxpupuli/puppet-format' 24 | container_inventory: 'https://gitlab.com/nwops/bolt-container_inventory' 25 | peadm: 'https://github.com/puppetlabs/puppetlabs-peadm.git' 26 | symlinks: 27 | peadm_spec: "#{source_dir}/spec/fixtures/modules/peadm/spec/acceptance/peadm_spec/" 28 | ca_extend: "#{source_dir}" 29 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetlabs-ca_extend", 3 | "version": "3.5.0", 4 | "author": "Adrian Parreiras Horta", 5 | "summary": "A set of Bolt Plans and Tasks to extend the CA cert in Puppet Enterprise", 6 | "license": "GPL-2.0-only", 7 | "source": "https://github.com/puppetlabs/ca_extend", 8 | "project_page": "https://github.com/puppetlabs/ca_extend", 9 | "issues_url": "https://github.com/puppetlabs/ca_extend/issues", 10 | "dependencies": [ 11 | { 12 | "name": "puppetlabs/stdlib", 13 | "version_requirement": ">= 4.10.0 < 10.0.0" 14 | } 15 | ], 16 | "operatingsystem_support": [ 17 | { 18 | "operatingsystem": "CentOS", 19 | "operatingsystemrelease": [ 20 | "7", 21 | "8" 22 | ] 23 | }, 24 | { 25 | "operatingsystem": "OracleLinux", 26 | "operatingsystemrelease": [ 27 | "7" 28 | ] 29 | }, 30 | { 31 | "operatingsystem": "RedHat", 32 | "operatingsystemrelease": [ 33 | "7", 34 | "8" 35 | ] 36 | }, 37 | { 38 | "operatingsystem": "Scientific", 39 | "operatingsystemrelease": [ 40 | "7" 41 | ] 42 | }, 43 | { 44 | "operatingsystem": "Ubuntu", 45 | "operatingsystemrelease": [ 46 | "18.04" 47 | ] 48 | } 49 | ], 50 | "requirements": [ 51 | { 52 | "name": "puppet", 53 | "version_requirement": ">= 6.16.0 < 9.0.0" 54 | } 55 | ], 56 | "pdk-version": "2.7.0", 57 | "template-url": "https://github.com/puppetlabs/pdk-templates#main", 58 | "template-ref": "heads/main-0-gd05508f" 59 | } 60 | -------------------------------------------------------------------------------- /tasks/check_agent_expiry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare PT__installdir 4 | # shellcheck disable=SC1090 5 | source "$PT__installdir/ca_extend/files/common.sh" 6 | PUPPET_BIN='/opt/puppetlabs/puppet/bin' 7 | 8 | valid=() 9 | expired=() 10 | 11 | to_date="${date:-+3 months}" 12 | to_date="$(date --date="$to_date" +"%s")" || fail "Error calculating date" 13 | 14 | # It's possible that we are not on a Puppet AIO system. If we cannot find a 15 | # openssl binary in the AIO directory, we accept one in $PATH 16 | if [ "$(command -v "${PUPPET_BIN}/openssl")" ]; then 17 | openssl="${PUPPET_BIN}/openssl" 18 | else 19 | openssl="$(command -v openssl)" 20 | fi 21 | 22 | shopt -s nullglob 23 | 24 | for cert in "$($PUPPET_BIN/puppet config print signeddir)"/*; do 25 | # The -checkend command in openssl takes a number of seconds as an argument 26 | # However, on older versions we may overflow a 32 bit integer if we use that 27 | # So, we'll use bash arithmetic and `date` to do the comparison 28 | expiry_date="$(${openssl} x509 -enddate -noout -in "${cert}")" 29 | expiry_date="${expiry_date#*=}" 30 | expiry_seconds="$(date --date="$expiry_date" +"%s")" || fail "Error calculating expiry date from enddate" 31 | 32 | # Only use the filename without preceding directories 33 | short_cert="${cert##*/}" 34 | 35 | if (( to_date >= expiry_seconds )); then 36 | expired+=("\"$short_cert\"") 37 | expired+=("\"$expiry_date\"") 38 | else 39 | valid+=("\"$short_cert\"") 40 | valid+=("\"$expiry_date\"") 41 | fi 42 | done 43 | 44 | # This is ugly, we as of now we don't include jq binaries in Bolt 45 | if (( "${#valid[@]}" > 0 )); then 46 | # Construct a string of individual json objects in the form of: 47 | # {"cert_1": "expiration_date"},{"cert_2": "expiration_date"}, 48 | # There will be a trailing comma we strip in the final echo 49 | valid_output=$(printf '{%s: %s},' "${valid[@]}") 50 | fi 51 | 52 | if (( "${#expired[@]}" > 0 )); then 53 | expired_output=$(printf '{%s: %s},' "${expired[@]}") 54 | fi 55 | 56 | # Create json arrays by stripping the trailing comma and adding brackets 57 | echo "{\"valid\": [${valid_output%,}], \"expired\": [${expired_output%,}]}" 58 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |c| 4 | c.mock_with :rspec 5 | end 6 | 7 | require 'puppetlabs_spec_helper/module_spec_helper' 8 | require 'rspec-puppet-facts' 9 | 10 | require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) 11 | 12 | include RspecPuppetFacts 13 | 14 | default_facts = { 15 | puppetversion: Puppet.version, 16 | facterversion: Facter.version, 17 | } 18 | 19 | default_fact_files = [ 20 | File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')), 21 | File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')), 22 | ] 23 | 24 | default_fact_files.each do |f| 25 | next unless File.exist?(f) && File.readable?(f) && File.size?(f) 26 | 27 | begin 28 | default_facts.merge!(YAML.safe_load(File.read(f), permitted_classes: [], permitted_symbols: [], aliases: true)) 29 | rescue StandardError => e 30 | RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" 31 | end 32 | end 33 | 34 | # read default_facts and merge them over what is provided by facterdb 35 | default_facts.each do |fact, value| 36 | add_custom_fact fact, value 37 | end 38 | 39 | RSpec.configure do |c| 40 | c.default_facts = default_facts 41 | c.before :each do 42 | # set to strictest setting for testing 43 | # by default Puppet runs at warning level 44 | Puppet.settings[:strict] = :warning 45 | Puppet.settings[:strict_variables] = true 46 | end 47 | c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] 48 | c.after(:suite) do 49 | RSpec::Puppet::Coverage.report!(0) 50 | end 51 | 52 | # Filter backtrace noise 53 | backtrace_exclusion_patterns = [ 54 | %r{spec_helper}, 55 | %r{gems}, 56 | ] 57 | 58 | if c.respond_to?(:backtrace_exclusion_patterns) 59 | c.backtrace_exclusion_patterns = backtrace_exclusion_patterns 60 | elsif c.respond_to?(:backtrace_clean_patterns) 61 | c.backtrace_clean_patterns = backtrace_exclusion_patterns 62 | end 63 | end 64 | 65 | # Ensures that a module is defined 66 | # @param module_name Name of the module 67 | def ensure_module_defined(module_name) 68 | module_name.split('::').reduce(Object) do |last_module, next_module| 69 | last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) 70 | last_module.const_get(next_module, false) 71 | end 72 | end 73 | 74 | # 'spec_overrides' from sync.yml will appear below this line 75 | -------------------------------------------------------------------------------- /.github/workflows/spec.yml: -------------------------------------------------------------------------------- 1 | name: "Spec Tests" 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | env: 8 | SHELLCHECK_OPTS: '-e SC1090 -e SC1091' 9 | 10 | jobs: 11 | setup_matrix: 12 | name: "Setup Test Matrix" 13 | runs-on: ubuntu-24.04 14 | outputs: 15 | spec_matrix: ${{ steps.get-matrix.outputs.spec_matrix }} 16 | 17 | steps: 18 | 19 | - name: Checkout Source 20 | uses: actions/checkout@v3 21 | if: ${{ github.repository_owner == 'puppetlabs' }} 22 | 23 | - name: Activate Ruby 3.2 24 | uses: ruby/setup-ruby@v1 25 | if: ${{ github.repository_owner == 'puppetlabs' }} 26 | with: 27 | ruby-version: "3.2" 28 | bundler-cache: true 29 | 30 | - name: Print bundle environment 31 | if: ${{ github.repository_owner == 'puppetlabs' }} 32 | run: | 33 | echo ::group::bundler environment 34 | bundle env 35 | echo ::endgroup:: 36 | 37 | - name: Setup Spec Test Matrix 38 | id: get-matrix 39 | run: | 40 | if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then 41 | bundle exec matrix_from_metadata_v2 42 | else 43 | echo "spec_matrix={}" >> $GITHUB_OUTPUT 44 | fi 45 | 46 | Spec: 47 | name: "Spec Tests (Puppet: ${{matrix.puppet_version}}, Ruby Ver: ${{matrix.ruby_version}})" 48 | needs: 49 | - setup_matrix 50 | if: ${{ needs.setup_matrix.outputs.spec_matrix != '{}' }} 51 | 52 | runs-on: ubuntu-24.04 53 | strategy: 54 | fail-fast: false 55 | matrix: ${{fromJson(needs.setup_matrix.outputs.spec_matrix)}} 56 | 57 | env: 58 | PUPPET_GEM_VERSION: ${{ matrix.puppet_version }} 59 | 60 | steps: 61 | - run: | 62 | echo "SANITIZED_PUPPET_VERSION=$(echo '${{ matrix.puppet_version }}' | sed 's/~> //g')" >> $GITHUB_ENV 63 | 64 | - name: Checkout Source 65 | uses: actions/checkout@v3 66 | 67 | - name: "Activate Ruby ${{ matrix.ruby_version }}" 68 | uses: ruby/setup-ruby@v1 69 | with: 70 | ruby-version: ${{matrix.ruby_version}} 71 | bundler-cache: true 72 | 73 | - name: Print bundle environment 74 | run: | 75 | echo ::group::bundler environment 76 | bundle env 77 | echo ::endgroup:: 78 | 79 | - name: Run Static & Syntax Tests 80 | run: | 81 | bundle exec rake syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop 82 | 83 | - name: Run parallel_spec tests 84 | run: | 85 | bundle exec rake parallel_spec 86 | shellcheck: 87 | name: Shellcheck 88 | runs-on: ubuntu-latest 89 | steps: 90 | - uses: actions/checkout@v3 91 | - name: Run ShellCheck 92 | uses: ludeeus/action-shellcheck@master 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /.github/workflows/auto_release.yml: -------------------------------------------------------------------------------- 1 | name: "Auto release" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 8 | 9 | jobs: 10 | auto_release: 11 | name: "Automatic release prep" 12 | runs-on: ubuntu-24.04 13 | 14 | steps: 15 | - name: "Checkout Source" 16 | if: ${{ github.repository_owner == 'puppetlabs' }} 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | persist-credentials: false 21 | 22 | - name: "PDK Release prep" 23 | uses: docker://puppet/puppet-dev-tools:4.x 24 | with: 25 | args: 'pdk release prep --force --debug' 26 | env: 27 | CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - name: "Get Version" 30 | if: ${{ github.repository_owner == 'puppetlabs' }} 31 | id: gv 32 | run: | 33 | echo "ver=$(jq --raw-output .version metadata.json)" >> $GITHUB_OUTPUT 34 | 35 | - name: "Check if a release is necessary" 36 | if: ${{ github.repository_owner == 'puppetlabs' }} 37 | id: check 38 | run: | 39 | git diff --quiet CHANGELOG.md && echo "release=false" >> $GITHUB_OUTPUT || echo "release=true" >> $GITHUB_OUTPUT 40 | 41 | - name: "Commit changes" 42 | if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }} 43 | run: | 44 | git config --local user.email "${{ github.repository_owner }}@users.noreply.github.com" 45 | git config --local user.name "GitHub Action" 46 | git add . 47 | git commit -m "Release prep v${{ steps.gv.outputs.ver }}" 48 | 49 | - name: Create Pull Request 50 | id: cpr 51 | uses: puppetlabs/peter-evans-create-pull-request@v3 52 | if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }} 53 | with: 54 | token: ${{ secrets.GITHUB_TOKEN }} 55 | commit-message: "Release prep v${{ steps.gv.outputs.ver }}" 56 | branch: "release-prep" 57 | delete-branch: true 58 | title: "Release prep v${{ steps.gv.outputs.ver }}" 59 | body: | 60 | 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 }}. 61 | Please verify before merging: 62 | - [ ] last [nightly](https://github.com/${{ github.repository }}/actions/workflows/nightly.yml) run is green 63 | - [ ] [Changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) is readable and has no unlabeled pull requests 64 | - [ ] 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 65 | labels: "maintenance" 66 | 67 | - name: PR outputs 68 | if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }} 69 | run: | 70 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" 71 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" 72 | 73 | -------------------------------------------------------------------------------- /plans/upload_ca_cert.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # A plan to upload a given CA certificate to a number of Puppet agent nodes 3 | # @param nodes The targets to upload the certificate to 4 | # @param cert The location of the CA certificate on disk of the local machine 5 | # @return JSON object with two keys: success and failure. 6 | # Each key contains any number of objects consisting of the agent certname and the output of the upload_file command 7 | plan ca_extend::upload_ca_cert( 8 | TargetSpec $nodes, 9 | String $cert 10 | ) { 11 | # Work around BOLT-1168 12 | run_plan('ca_extend::get_agent_facts', 'nodes' => $nodes, '_catch_errors' => true) 13 | $tmp = run_plan('facts', 'targets' => $nodes, '_catch_errors' => true) 14 | 15 | # Extract the ResultSet from an error object 16 | case $tmp { 17 | Error['bolt/run-failure']: { 18 | $results = $tmp.details['result_set'] 19 | $not_ok = $results.error_set 20 | } 21 | default: { 22 | $results = $tmp 23 | $not_ok = undef 24 | } 25 | } 26 | 27 | # The os.family fact should consistantly be "windows" on, well, Windows. 28 | $windows_targets = $results.ok_set.filter |$n| { "${n.value['os']['family']}" == 'windows' } 29 | $linux_targets = $results.ok_set.filter |$n| { ! ("${n.value['os']['family']}" == 'windows') } 30 | 31 | $windows_results = upload_file( 32 | $cert, 33 | 'C:\ProgramData\PuppetLabs\puppet\etc\ssl\certs\ca.pem', 34 | $windows_targets.map |$item| { $item.target.name }, 35 | '_catch_errors' => true 36 | ) 37 | 38 | $linux_results = upload_file( 39 | $cert, 40 | '/etc/puppetlabs/puppet/ssl/certs/ca.pem', 41 | $linux_targets.map |$item| { $item.target.name }, 42 | '_catch_errors' => true 43 | ) 44 | 45 | # Create a hash for *nix and Windows successful and failed uploads and merge them together 46 | # filter will return nil if anything doesn't match the lambda, and deep merge will 47 | # crunch the left hashes if the rightmost value isn't a hash, so check for that 48 | $good = deep_merge( 49 | if $linux_results.any |$r| { $r.ok } { 50 | { 'success' => $linux_results.filter |$result| { $result.ok }.map |$result| { 51 | { $result.target.name => $result.value } 52 | }.reduce |$memo, $value| { $memo + $value } 53 | } 54 | }, 55 | if $windows_results.any |$r| { $r.ok } { 56 | { 'success' => $windows_results.filter |$result| { $result.ok }.map |$result| { 57 | { $result.target.name => $result.value } 58 | }.reduce |$memo, $value| { $memo + $value } 59 | } 60 | } 61 | ) 62 | 63 | $bad = deep_merge( 64 | if ! $windows_results.ok { 65 | { 'failure' => $windows_results.filter |$result| { ! $result.ok }.map |$result| { 66 | { $result.target.name => $result.value } 67 | }.reduce |$memo, $value| { $memo + $value } 68 | } 69 | }, 70 | if ! $linux_results.ok { 71 | { 'failure' => $linux_results.filter |$result| { ! $result.ok }.map |$result| { 72 | { $result.target.name => $result.value } 73 | }.reduce |$memo, $value| { $memo + $value } 74 | } 75 | }, 76 | if $not_ok { 77 | { 'failure' => $not_ok.map |$result| { 78 | { $result.target.name => $result.value } 79 | }.reduce |$memo, $value| { $memo + $value } 80 | } 81 | } 82 | ) 83 | 84 | return deep_merge($good, $bad) 85 | } 86 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 2 | 3 | def location_for(place_or_version, fake_version = nil) 4 | git_url_regex = %r{\A(?(https?|git)[:@][^#]*)(#(?.*))?} 5 | file_url_regex = %r{\Afile:\/\/(?.*)} 6 | 7 | if place_or_version && (git_url = place_or_version.match(git_url_regex)) 8 | [fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact 9 | elsif place_or_version && (file_url = place_or_version.match(file_url_regex)) 10 | ['>= 0', { path: File.expand_path(file_url[:path]), require: false }] 11 | else 12 | [place_or_version, { require: false }] 13 | end 14 | end 15 | 16 | group :development do 17 | gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 18 | gem "json", '= 2.3.0', require: false if Gem::Requirement.create(['>= 2.7.0', '< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 19 | gem "json", '= 2.5.1', require: false if Gem::Requirement.create(['>= 3.0.0', '< 3.0.5']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 20 | gem "json", '= 2.6.1', require: false if Gem::Requirement.create(['>= 3.1.0', '< 3.1.3']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 21 | gem "json", '= 2.6.3', require: false if Gem::Requirement.create(['>= 3.2.0', '< 4.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 22 | gem "voxpupuli-puppet-lint-plugins", '~> 5.0', require: false 23 | gem "facterdb", '~> 1.18', require: false 24 | gem "metadata-json-lint", '~> 3.0', require: false 25 | gem "puppetlabs_spec_helper", '~> 6.0', require: false 26 | gem "rspec-puppet-facts", '~> 2.0', require: false 27 | gem "codecov", '~> 0.2', require: false 28 | gem "dependency_checker", '~> 1.0.0', require: false 29 | gem "parallel_tests", '= 3.12.1', require: false 30 | gem "pry", '~> 0.10', require: false 31 | gem "simplecov-console", '~> 0.5', require: false 32 | gem "puppet-debugger", '~> 1.0', require: false 33 | gem "rubocop", '= 1.48.1', require: false 34 | gem "rubocop-performance", '= 1.16.0', require: false 35 | gem "rubocop-rspec", '= 2.19.0', require: false 36 | gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] 37 | gem "github_changelog_generator", '= 1.15.2', require: false 38 | gem "octokit", '= 4.21.0', require: false 39 | end 40 | group :system_tests do 41 | gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] 42 | gem "serverspec", '~> 2.41', require: false 43 | end 44 | 45 | puppet_version = ENV['PUPPET_GEM_VERSION'] 46 | facter_version = ENV['FACTER_GEM_VERSION'] 47 | hiera_version = ENV['HIERA_GEM_VERSION'] 48 | 49 | gems = {} 50 | 51 | gems['puppet'] = location_for(puppet_version) 52 | 53 | # If facter or hiera versions have been specified via the environment 54 | # variables 55 | 56 | gems['facter'] = location_for(facter_version) if facter_version 57 | gems['hiera'] = location_for(hiera_version) if hiera_version 58 | 59 | gems.each do |gem_name, gem_params| 60 | gem gem_name, *gem_params 61 | end 62 | 63 | # Evaluate Gemfile.local and ~/.gemfile if they exist 64 | extra_gemfiles = [ 65 | "#{__FILE__}.local", 66 | File.join(Dir.home, '.gemfile'), 67 | ] 68 | 69 | extra_gemfiles.each do |gemfile| 70 | if File.file?(gemfile) && File.readable?(gemfile) 71 | eval(File.read(gemfile), binding) 72 | end 73 | end 74 | # vim: syntax=ruby 75 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler' 4 | require 'puppet_litmus/rake_tasks' if Bundler.rubygems.find_name('puppet_litmus').any? 5 | require 'puppetlabs_spec_helper/rake_tasks' 6 | require 'puppet-syntax/tasks/puppet-syntax' 7 | require 'github_changelog_generator/task' if Bundler.rubygems.find_name('github_changelog_generator').any? 8 | require 'puppet-strings/tasks' if Bundler.rubygems.find_name('puppet-strings').any? 9 | 10 | def changelog_user 11 | return unless Rake.application.top_level_tasks.include? "changelog" 12 | returnVal = "puppetlabs" || JSON.load(File.read('metadata.json'))['author'] 13 | raise "unable to find the changelog_user in .sync.yml, or the author in metadata.json" if returnVal.nil? 14 | puts "GitHubChangelogGenerator user:#{returnVal}" 15 | returnVal 16 | end 17 | 18 | def changelog_project 19 | return unless Rake.application.top_level_tasks.include? "changelog" 20 | 21 | returnVal = nil 22 | returnVal ||= begin 23 | metadata_source = JSON.load(File.read('metadata.json'))['source'] 24 | metadata_source_match = metadata_source && metadata_source.match(%r{.*\/([^\/]*?)(?:\.git)?\Z}) 25 | 26 | metadata_source_match && metadata_source_match[1] 27 | end 28 | 29 | raise "unable to find the changelog_project in .sync.yml or calculate it from the source in metadata.json" if returnVal.nil? 30 | 31 | puts "GitHubChangelogGenerator project:#{returnVal}" 32 | returnVal 33 | end 34 | 35 | def changelog_future_release 36 | return unless Rake.application.top_level_tasks.include? "changelog" 37 | returnVal = "v%s" % JSON.load(File.read('metadata.json'))['version'] 38 | raise "unable to find the future_release (version) in metadata.json" if returnVal.nil? 39 | puts "GitHubChangelogGenerator future_release:#{returnVal}" 40 | returnVal 41 | end 42 | 43 | PuppetLint.configuration.send('disable_relative') 44 | PuppetLint.configuration.send('disable_unquoted_string_in_case') 45 | PuppetLint.configuration.send('disable_manifest_whitespace_opening_brace_before') 46 | 47 | 48 | if Bundler.rubygems.find_name('github_changelog_generator').any? 49 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 50 | raise "Set CHANGELOG_GITHUB_TOKEN environment variable eg 'export CHANGELOG_GITHUB_TOKEN=valid_token_here'" if Rake.application.top_level_tasks.include? "changelog" and ENV['CHANGELOG_GITHUB_TOKEN'].nil? 51 | config.user = "#{changelog_user}" 52 | config.project = "#{changelog_project}" 53 | config.future_release = "#{changelog_future_release}" 54 | config.exclude_labels = ['maintenance'] 55 | config.header = "# Change log\n\nAll 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)." 56 | config.add_pr_wo_labels = true 57 | config.issues = false 58 | config.merge_prefix = "### UNCATEGORIZED PRS; LABEL THEM ON GITHUB" 59 | config.configure_sections = { 60 | "Changed" => { 61 | "prefix" => "### Changed", 62 | "labels" => ["backwards-incompatible"], 63 | }, 64 | "Added" => { 65 | "prefix" => "### Added", 66 | "labels" => ["enhancement", "feature"], 67 | }, 68 | "Fixed" => { 69 | "prefix" => "### Fixed", 70 | "labels" => ["bug", "documentation", "bugfix"], 71 | }, 72 | } 73 | end 74 | else 75 | desc 'Generate a Changelog from GitHub' 76 | task :changelog do 77 | raise < 1.15' 86 | condition: "Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0')" 87 | EOM 88 | end 89 | end 90 | 91 | -------------------------------------------------------------------------------- /.github/workflows/pe_latest_testing.yml: -------------------------------------------------------------------------------- 1 | name: "PE Latest Acceptance Testing" 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | jobs: 8 | setup_matrix: 9 | name: "Setup Test Matrix" 10 | runs-on: ubuntu-24.04 11 | outputs: 12 | matrix: ${{ steps.get-matrix.outputs.matrix }} 13 | 14 | steps: 15 | - name: Checkout Source 16 | uses: actions/checkout@v3 17 | if: ${{ github.repository_owner == 'puppetlabs' }} 18 | 19 | - name: Activate Ruby 3.2 20 | uses: ruby/setup-ruby@v1 21 | if: ${{ github.repository_owner == 'puppetlabs' }} 22 | with: 23 | ruby-version: "3.2" 24 | bundler-cache: true 25 | 26 | - name: Print bundle environment 27 | if: ${{ github.repository_owner == 'puppetlabs' }} 28 | run: | 29 | echo ::group::bundler environment 30 | bundle env 31 | echo ::endgroup:: 32 | 33 | # store the result of the curl call in $forge_response 34 | - name: Curl Forge for PE versions 35 | id: curl_forge 36 | run: | 37 | echo "forge_response=$(curl https://forge.puppet.com/private/versions/pe)" >> $GITHUB_OUTPUT 38 | 39 | - name: Set latest release 40 | id: latest_release 41 | run: | 42 | out=$(jq -c '[.[] | select(.lts == false)][0].latest | {"collection": [.]}' <<<'${{ steps.curl_forge.outputs.forge_response }}') 43 | echo "latest=$out" >> $GITHUB_OUTPUT 44 | 45 | - name: Setup Acceptance Test Matrix 46 | id: get-matrix 47 | run: | 48 | if [[ -e spec/fixtures/matrix/latest.json ]]; then 49 | out=$(jq -c '. + ($matrix | .[])' --slurpfile matrix spec/fixtures/matrix/latest.json <<<'${{ steps.latest_release.outputs.latest }}') 50 | echo "matrix=$out" >> $GITHUB_OUTPUT 51 | else 52 | echo "matrix={}" >> $GITHUB_OUTPUT 53 | fi 54 | 55 | Acceptance: 56 | name: "${{matrix.platforms.label}}, ${{matrix.collection}}" 57 | needs: 58 | - setup_matrix 59 | if: ${{ needs.setup_matrix.outputs.matrix != '{}' }} 60 | 61 | runs-on: ubuntu-24.04 62 | strategy: 63 | fail-fast: false 64 | matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} 65 | env: 66 | PUPPET_GEM_VERSION: '~> 8.0.1' 67 | steps: 68 | - name: Checkout Source 69 | uses: actions/checkout@v3 70 | 71 | - name: Activate Ruby 3.2 72 | uses: ruby/setup-ruby@v1 73 | with: 74 | ruby-version: "3.2" 75 | bundler-cache: true 76 | - name: Print bundle environment 77 | run: | 78 | echo ::group::bundler environment 79 | bundle env 80 | echo ::endgroup:: 81 | 82 | - name: Provision test environment 83 | run: | 84 | bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]' 85 | echo ::group::=== REQUEST === 86 | cat request.json || true 87 | echo 88 | echo ::endgroup:: 89 | echo ::group::=== INVENTORY === 90 | if [ -f 'spec/fixtures/litmus_inventory.yaml' ]; 91 | then 92 | FILE='spec/fixtures/litmus_inventory.yaml' 93 | elif [ -f 'inventory.yaml' ]; 94 | then 95 | FILE='inventory.yaml' 96 | fi 97 | sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true 98 | echo ::endgroup:: 99 | echo INVENTORY_PATH=$FILE >> $GITHUB_ENV 100 | - name: Install PE 101 | run: | 102 | 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":"Puppetlabs1!", "configure_tuning": false}}' --targets all --stream 103 | 104 | - name: Install module 105 | run: | 106 | bundle exec rake 'litmus:install_module' 107 | - name: Run acceptance tests 108 | run: | 109 | bundle exec rake 'litmus:acceptance:parallel' 110 | - name: Remove test environment 111 | if: ${{ always() }} 112 | continue-on-error: true 113 | run: | 114 | if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then 115 | bundle exec rake 'litmus:tear_down' 116 | echo ::group::=== REQUEST === 117 | cat request.json || true 118 | echo 119 | echo ::endgroup:: 120 | fi 121 | -------------------------------------------------------------------------------- /.github/workflows/pe_lts_testing.yml: -------------------------------------------------------------------------------- 1 | name: "PE LTS Acceptance Testing" 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | jobs: 8 | setup_matrix: 9 | name: "Setup Test Matrix" 10 | runs-on: ubuntu-24.04 11 | outputs: 12 | matrix: ${{ steps.get-matrix.outputs.matrix }} 13 | 14 | steps: 15 | - name: Checkout Source 16 | uses: actions/checkout@v3 17 | if: ${{ github.repository_owner == 'puppetlabs' }} 18 | 19 | - name: Activate Ruby 3.2 20 | uses: ruby/setup-ruby@v1 21 | if: ${{ github.repository_owner == 'puppetlabs' }} 22 | with: 23 | ruby-version: "3.2" 24 | bundler-cache: true 25 | 26 | - name: Print bundle environment 27 | if: ${{ github.repository_owner == 'puppetlabs' }} 28 | run: | 29 | echo ::group::bundler environment 30 | bundle env 31 | echo ::endgroup:: 32 | 33 | # Store the result of the curl call in $forge_response 34 | - name: Curl Forge for PE versions 35 | id: curl_forge 36 | run: | 37 | echo "forge_response=$(curl https://forge.puppet.com/private/versions/pe)" >> $GITHUB_OUTPUT 38 | 39 | - name: Set latest release 40 | id: latest_release 41 | run: | 42 | out=$(jq -c '[.[] | select(.lts == true)][0].latest | {"collection": [.]}' <<<'${{ steps.curl_forge.outputs.forge_response }}') 43 | echo "latest=$out" >> $GITHUB_OUTPUT 44 | 45 | - name: Setup Acceptance Test Matrix 46 | id: get-matrix 47 | run: | 48 | if [[ -e spec/fixtures/matrix/lts.json ]]; then 49 | out=$(jq -c '. + ($matrix | .[])' --slurpfile matrix spec/fixtures/matrix/lts.json <<<'${{ steps.latest_release.outputs.latest }}') 50 | echo "matrix=$out" >> $GITHUB_OUTPUT 51 | else 52 | echo "matrix={}" >> $GITHUB_OUTPUT 53 | fi 54 | 55 | 56 | Acceptance: 57 | name: "${{matrix.platforms.label}}, ${{matrix.collection}}" 58 | needs: 59 | - setup_matrix 60 | if: ${{ needs.setup_matrix.outputs.matrix != '{}' }} 61 | 62 | runs-on: ubuntu-24.04 63 | strategy: 64 | fail-fast: false 65 | matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} 66 | 67 | env: 68 | PUPPET_GEM_VERSION: '~> 8.0.1' 69 | 70 | steps: 71 | - name: Checkout Source 72 | uses: actions/checkout@v3 73 | 74 | - name: Activate Ruby 3.2 75 | uses: ruby/setup-ruby@v1 76 | with: 77 | ruby-version: "3.2" 78 | bundler-cache: true 79 | 80 | - name: Print bundle environment 81 | run: | 82 | echo ::group::bundler environment 83 | bundle env 84 | echo ::endgroup:: 85 | 86 | - name: Provision test environment 87 | run: | 88 | bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]' 89 | echo ::group::=== REQUEST === 90 | cat request.json || true 91 | echo 92 | echo ::endgroup:: 93 | echo ::group::=== INVENTORY === 94 | if [ -f 'spec/fixtures/litmus_inventory.yaml' ]; 95 | then 96 | FILE='spec/fixtures/litmus_inventory.yaml' 97 | elif [ -f 'inventory.yaml' ]; 98 | then 99 | FILE='inventory.yaml' 100 | fi 101 | sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true 102 | echo ::endgroup:: 103 | echo INVENTORY_PATH=$FILE >> $GITHUB_ENV 104 | - name: Install PE 105 | run: | 106 | 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":"Puppetlabs1!", "configure_tuning": false}}' --targets all --stream 107 | 108 | - name: Install module 109 | run: | 110 | bundle exec rake 'litmus:install_module' 111 | 112 | - name: Run acceptance tests 113 | run: | 114 | bundle exec rake 'litmus:acceptance:parallel' 115 | - name: Remove test environment 116 | if: ${{ always() }} 117 | continue-on-error: true 118 | run: | 119 | if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then 120 | bundle exec rake 'litmus:tear_down' 121 | echo ::group::=== REQUEST === 122 | cat request.json || true 123 | echo 124 | echo ::endgroup:: 125 | fi 126 | 127 | -------------------------------------------------------------------------------- /.github/workflows/pe_nightly_testing.yml: -------------------------------------------------------------------------------- 1 | name: "PE Nightly Acceptance Testing" 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | jobs: 8 | setup_matrix: 9 | name: "Setup Test Matrix" 10 | runs-on: ubuntu-24.04 11 | 12 | steps: 13 | - name: Checkout Source 14 | uses: actions/checkout@v3 15 | if: ${{ github.repository_owner == 'puppetlabs' }} 16 | 17 | - name: Activate Ruby 2.7 18 | uses: ruby/setup-ruby@v1 19 | if: ${{ github.repository_owner == 'puppetlabs' }} 20 | with: 21 | ruby-version: "2.7" 22 | bundler-cache: true 23 | 24 | - name: Print bundle environment 25 | if: ${{ github.repository_owner == 'puppetlabs' }} 26 | run: | 27 | echo ::group::bundler environment 28 | bundle env 29 | echo ::endgroup:: 30 | 31 | Acceptance: 32 | name: "Nightly" 33 | runs-on: ubuntu-24.04 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | architecture: [standard] 38 | image: [centos-7, sles-12, sles-15, rhel-7, rhel-8] 39 | include: 40 | - image: centos-7 41 | os: el-7-x86_64 42 | - image: sles-12 43 | os: sles-12-x86_64 44 | - image: rhel-7 45 | os: el-7-x86_64 46 | - image: sles-15 47 | os: sles-15-x86_64 48 | - image: rhel-8 49 | os: el-8-x86_64 50 | 51 | steps: 52 | - name: Checkout Source 53 | uses: actions/checkout@v3 54 | 55 | - name: Activate Ruby 2.7 56 | uses: ruby/setup-ruby@v1 57 | with: 58 | ruby-version: "2.7" 59 | bundler-cache: true 60 | - name: Print bundle environment 61 | run: | 62 | echo ::group::bundler environment 63 | bundle env 64 | echo ::endgroup:: 65 | 66 | - name: 'Provision test environment' 67 | timeout-minutes: 15 68 | run: | 69 | echo ::group::prepare 70 | mkdir -p $HOME/.ssh 71 | echo 'Host *' > $HOME/.ssh/config 72 | echo ' ServerAliveInterval 150' >> $HOME/.ssh/config 73 | echo ' ServerAliveCountMax 2' >> $HOME/.ssh/config 74 | bundle exec rake spec_prep 75 | echo ::endgroup:: 76 | echo ::group::provision 77 | bundle exec bolt plan run peadm_spec::provision_test_cluster \ 78 | --modulepath spec/fixtures/modules \ 79 | provider=provision_service \ 80 | image=${{ matrix.image }} \ 81 | architecture=${{ matrix.architecture }} 82 | echo ::endgroup:: 83 | echo ::group::info:request 84 | cat request.json || true; echo 85 | echo ::endgroup:: 86 | echo ::group::info:inventory 87 | sed -e 's/password: .*/password: "[redacted]"/' < spec/fixtures/litmus_inventory.yaml || true 88 | echo ::endgroup:: 89 | 90 | - name: 'Activate' 91 | uses: twingate/github-action@v1 92 | with: 93 | service-key: ${{ secrets.TWINGATE_PUBLIC_REPO_KEY }} 94 | 95 | - name: 'Get latest build name' 96 | id: latest 97 | run: | 98 | echo "ver=$(curl -q https://artifactory.delivery.puppetlabs.net/artifactory/generic_enterprise__local/main/ci-ready/LATEST)" >> $GITHUB_OUTPUT 99 | 100 | - name: 'Install PE on test cluster' 101 | timeout-minutes: 120 102 | run: | 103 | bundle exec bolt plan run peadm_spec::install_test_cluster \ 104 | --inventoryfile spec/fixtures/litmus_inventory.yaml \ 105 | --modulepath spec/fixtures/modules \ 106 | permit_unsafe_versions=true \ 107 | code_manager_auto_configure=false \ 108 | download_mode="bolthost" \ 109 | --log-level debug \ 110 | architecture=${{ matrix.architecture }} \ 111 | pe_installer_source="https://artifactory.delivery.puppetlabs.net/artifactory/generic_enterprise__local/main/ci-ready/puppet-enterprise-${{ steps.latest.outputs.ver }}-${{ matrix.os }}.tar" 112 | - name: Install module 113 | run: | 114 | bundle exec rake 'litmus:install_module' 115 | - name: Run acceptance tests 116 | run: | 117 | bundle exec rake 'litmus:acceptance:parallel' 118 | - name: Remove test environment 119 | if: ${{ always() }} 120 | continue-on-error: true 121 | run: | 122 | if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then 123 | bundle exec rake 'litmus:tear_down' 124 | echo ::group::=== REQUEST === 125 | cat request.json || true 126 | echo 127 | echo ::endgroup:: 128 | fi 129 | 130 | - name: Notify slack fail 131 | if: failure() 132 | env: 133 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }} 134 | uses: voxmedia/github-action-slack-notify-build@v1 135 | with: 136 | channel_id: C049PL6EF9S 137 | status: FAILED 138 | color: danger 139 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | 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). 4 | 5 | ## [v3.5.0](https://github.com/puppetlabs/ca_extend/tree/v3.5.0) (2023-10-03) 6 | 7 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v3.4.0...v3.5.0) 8 | 9 | ### Added 10 | 11 | - \(SUP-3564\) Support more types of infra nodes [\#89](https://github.com/puppetlabs/ca_extend/pull/89) ([m0dular](https://github.com/m0dular)) 12 | 13 | ## [v3.4.0](https://github.com/puppetlabs/ca_extend/tree/v3.4.0) (2023-07-19) 14 | 15 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v3.3.1...v3.4.0) 16 | 17 | ### Added 18 | 19 | - Bump stdlib dependency [\#86](https://github.com/puppetlabs/ca_extend/pull/86) ([m0dular](https://github.com/m0dular)) 20 | - \(SUP-4347\) Allow extend.sh to operate on Puppet 6 CAs [\#85](https://github.com/puppetlabs/ca_extend/pull/85) ([Sharpie](https://github.com/Sharpie)) 21 | - Puppet 8 Compatibility [\#82](https://github.com/puppetlabs/ca_extend/pull/82) ([MartyEwings](https://github.com/MartyEwings)) 22 | 23 | ## [v3.3.1](https://github.com/puppetlabs/ca_extend/tree/v3.3.1) (2023-01-26) 24 | 25 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v3.3.0...v3.3.1) 26 | 27 | ### Fixed 28 | 29 | - \(SUP-3365\) Fix lookup of crl\_results [\#77](https://github.com/puppetlabs/ca_extend/pull/77) ([m0dular](https://github.com/m0dular)) 30 | 31 | ## [v3.3.0](https://github.com/puppetlabs/ca_extend/tree/v3.3.0) (2023-01-26) 32 | 33 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v3.2.0...v3.3.0) 34 | 35 | ### Added 36 | 37 | - \(SUP-3365\) Add crl\_truncate [\#72](https://github.com/puppetlabs/ca_extend/pull/72) ([elainemccloskey](https://github.com/elainemccloskey)) 38 | 39 | ### Fixed 40 | 41 | - \(SUP-3048\) Do not print cert contents [\#71](https://github.com/puppetlabs/ca_extend/pull/71) ([m0dular](https://github.com/m0dular)) 42 | 43 | ## [v3.2.0](https://github.com/puppetlabs/ca_extend/tree/v3.2.0) (2022-06-29) 44 | 45 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v3.1.0...v3.2.0) 46 | 47 | ### Added 48 | 49 | - \(SUP-3016\) Ensure valid json formatting [\#64](https://github.com/puppetlabs/ca_extend/pull/64) ([m0dular](https://github.com/m0dular)) 50 | - \(SUP-3016\) Add printing dates to agent expiry task [\#63](https://github.com/puppetlabs/ca_extend/pull/63) ([elainemccloskey](https://github.com/elainemccloskey)) 51 | 52 | ### Fixed 53 | 54 | - \(SUP-3433\) Incorrect Error handling ref for upload\_ca\_cert.pp [\#67](https://github.com/puppetlabs/ca_extend/pull/67) ([MartyEwings](https://github.com/MartyEwings)) 55 | 56 | ## [v3.1.0](https://github.com/puppetlabs/ca_extend/tree/v3.1.0) (2022-03-28) 57 | 58 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v3.0.0...v3.1.0) 59 | 60 | ### Added 61 | 62 | - \(SUP-2742\) adds examples of the usage of ca\_extend::upload\_ca\_cert [\#61](https://github.com/puppetlabs/ca_extend/pull/61) ([taikaa](https://github.com/taikaa)) 63 | 64 | ### Fixed 65 | 66 | - \(SUP-2655\) Update Minimum Supported Bolt Version to 1.38.0 [\#49](https://github.com/puppetlabs/ca_extend/pull/49) ([henrywangpuppet](https://github.com/henrywangpuppet)) 67 | 68 | ## [v3.0.0](https://github.com/puppetlabs/ca_extend/tree/v3.0.0) (2021-08-18) 69 | 70 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v2.1.0...v3.0.0) 71 | 72 | ### Changed 73 | 74 | - Remove harmful terms from ca\_extend [\#40](https://github.com/puppetlabs/ca_extend/pull/40) ([gavindidrichsen](https://github.com/gavindidrichsen)) 75 | - \(SUP-2497\) Remove EOL platforms and versions [\#39](https://github.com/puppetlabs/ca_extend/pull/39) ([m0dular](https://github.com/m0dular)) 76 | 77 | ## [v2.1.0](https://github.com/puppetlabs/ca_extend/tree/v2.1.0) (2021-08-02) 78 | 79 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v1.3.0...v2.1.0) 80 | 81 | ### Added 82 | 83 | - 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)) 84 | - Add option to regenerate the primary agent cert. [\#10](https://github.com/puppetlabs/ca_extend/pull/10) ([m0dular](https://github.com/m0dular)) 85 | 86 | ### Fixed 87 | 88 | - Remove hard-coded paths from scripts [\#30](https://github.com/puppetlabs/ca_extend/pull/30) ([m0dular](https://github.com/m0dular)) 89 | - Check for cadir during primary cert regen [\#28](https://github.com/puppetlabs/ca_extend/pull/28) ([m0dular](https://github.com/m0dular)) 90 | 91 | ## [v1.3.0](https://github.com/puppetlabs/ca_extend/tree/v1.3.0) (2021-03-02) 92 | 93 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v1.2.1...v1.3.0) 94 | 95 | ## [v1.2.1](https://github.com/puppetlabs/ca_extend/tree/v1.2.1) (2021-02-05) 96 | 97 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v1.2.0...v1.2.1) 98 | 99 | ## [v1.2.0](https://github.com/puppetlabs/ca_extend/tree/v1.2.0) (2020-11-24) 100 | 101 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v1.1.1...v1.2.0) 102 | 103 | ## [v1.1.1](https://github.com/puppetlabs/ca_extend/tree/v1.1.1) (2020-04-13) 104 | 105 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v1.0.1...v1.1.1) 106 | 107 | ## [v1.0.1](https://github.com/puppetlabs/ca_extend/tree/v1.0.1) (2019-12-03) 108 | 109 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v1.0.0...v1.0.1) 110 | 111 | ## [v1.0.0](https://github.com/puppetlabs/ca_extend/tree/v1.0.0) (2019-04-30) 112 | 113 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/v0.1.0...v1.0.0) 114 | 115 | ## [v0.1.0](https://github.com/puppetlabs/ca_extend/tree/v0.1.0) (2019-03-22) 116 | 117 | [Full Changelog](https://github.com/puppetlabs/ca_extend/compare/fd9f05b17f65770910b3146688ed702011b62802...v0.1.0) 118 | 119 | 120 | 121 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 122 | -------------------------------------------------------------------------------- /tasks/crl_truncate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For compatibility wither older versions of tasks, we just shove everything in this file 4 | # PT_ variables can be referenced but not assigned 5 | # shellcheck disable=SC2154 6 | # Exit with an error message and error code, defaulting to 1 7 | fail() { 8 | # Print a stderr: entry if there were anything printed to stderr 9 | if [[ -s $_tmp ]]; then 10 | # Hack to try and output valid json by replacing newlines with spaces. 11 | echo "{ \"status\": \"error\", \"message\": \"$1\", \"stderr\": \"$(tr '\n' ' ' <"$_tmp")\" }" 12 | else 13 | echo "{ \"status\": \"error\", \"message\": \"$1\" }" 14 | fi 15 | 16 | exit "${2:-1}" 17 | } 18 | 19 | success() { 20 | echo "$1" 21 | exit 0 22 | } 23 | 24 | _tmp="$(mktemp)" 25 | exec 2>>"$_tmp" 26 | 27 | # Use indirection to munge PT_ environment variables 28 | # e.g. "$PT_version" becomes "$version" 29 | for v in ${!PT_*}; do 30 | declare "${v#*PT_}"="${!v}" 31 | done 32 | 33 | trap fail ERR 34 | 35 | (( EUID == 0 )) || fail 'This script must be run as root' 36 | 37 | cat >/tmp/openssl.cnf <>"${certs[cert_num]}" 107 | done <"$ssldir"/ca/ca_crl.pem 108 | 109 | for cert in "${certs[@]}"; do 110 | issuer="$("$PUPPET_BIN"/openssl crl -issuer -noout -in "$cert")" 111 | [[ $issuer =~ Puppet\ Root\ CA ]] && root_crl="$cert" 112 | done 113 | 114 | # Assume that a single length crl chain is the root. 115 | # This was the default prior to PE 2019 116 | if (( cert_num == 1 )); then 117 | root_crl="${certs[cert_num]}" 118 | elif ! [[ $root_crl ]]; then 119 | printf '%s\n' 'Puppet root CA not found' >"$_tmp" 120 | fail 121 | fi 122 | 123 | # Create an empty index 124 | :>/tmp/index.txt 125 | 126 | # openssl requires that the crlnumber be hex with an even number of digits 127 | # %02 in the format string with pad it to two characters, otherwise we have to check for evenness and add a 0 if needed 128 | crl_number="$("$PUPPET_BIN"/openssl crl -crlnumber -noout -in "$ssldir"/ca/ca_crl.pem)" 129 | # Strip everything before the '=' character and increment by one, as the docs say this should be the next crl number 130 | crl_number="$(printf '%02x\n' $((0x${crl_number##*=} +1 )))" 131 | 132 | # Add a leading 0 if we have an odd number of digits 133 | (( ${#crl_number} % 2 == 0 )) || crl_number="0${crl_number}" 134 | echo "$crl_number" >/tmp/crlnumber 135 | 136 | "$PUPPET_BIN"/openssl ca -config /tmp/openssl.cnf -gencrl -out /tmp/intermediate_crl.pem 137 | 138 | # For a multi-chain crl, cat the new crl with the root. Otherwise, use only the new crl. 139 | if (( cert_num > 1 )); then 140 | cat /tmp/intermediate_crl.pem "$root_crl" >/tmp/new_crl.pem 141 | 142 | cp /tmp/new_crl.pem "$ssldir"/ca/ca_crl.pem 143 | cp /tmp/new_crl.pem "$ssldir"/crl.pem 144 | else 145 | cp /tmp/intermediate_crl.pem "$ssldir"/ca/ca_crl.pem 146 | cp /tmp/intermediate_crl.pem "$ssldir"/crl.pem 147 | fi 148 | 149 | # Send errors to our temp file 150 | if $run_puppet_agent; then 151 | "$PUPPET_BIN"/puppet agent --onetime --no-daemonize --no-usecacheonfailure --logdest "$_tmp" --log_level err 152 | success '{ "status": "success", "message": "CRL truncated and Puppet agent run completed"}' 153 | else 154 | success '{ "status": "success", "message": "CRL truncated. Puppet agent run was skipped"}' 155 | fi 156 | -------------------------------------------------------------------------------- /REFERENCE.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | 4 | 5 | ## Table of Contents 6 | 7 | ### Tasks 8 | 9 | * [`check_agent_expiry`](#check_agent_expiry): Check the expiration date of all agent certificates 10 | * [`check_ca_expiry`](#check_ca_expiry): Check the expiration date of a CA certificate 11 | * [`check_crl_cert`](#check_crl_cert): Check the expiration date of the primary server crl 12 | * [`check_primary_cert`](#check_primary_cert): Check the expiration date of the primary server cert 13 | * [`configure_primary`](#configure_primary): Backup ssldir and copy newly generated CA certificate 14 | * [`crl_truncate`](#crl_truncate): Truncate the CRL issued by the Puppet CA 15 | * [`extend_ca_cert`](#extend_ca_cert): Extend CA certificate expiry date 16 | 17 | ### Plans 18 | 19 | * [`ca_extend::extend_ca_cert`](#ca_extend--extend_ca_cert): Plan that extends the Puppet CA certificate and configures the primary Puppet server 20 | and Compilers to use the extended certificate. 21 | * [`ca_extend::get_agent_facts`](#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. 22 | * [`ca_extend::upload_ca_cert`](#ca_extend--upload_ca_cert): A plan to upload a given CA certificate to a number of Puppet agent nodes 23 | 24 | ## Tasks 25 | 26 | ### `check_agent_expiry` 27 | 28 | Check the expiration date of all agent certificates 29 | 30 | **Supports noop?** false 31 | 32 | #### Parameters 33 | 34 | ##### `date` 35 | 36 | Data type: `Optional[String[1]]` 37 | 38 | YYYY-MM-DD date to test whether the certificates will expire by. Defaults to three months from today 39 | 40 | ### `check_ca_expiry` 41 | 42 | Check the expiration date of a CA certificate 43 | 44 | **Supports noop?** false 45 | 46 | #### Parameters 47 | 48 | ##### `cert` 49 | 50 | Data type: `Optional[String[1]]` 51 | 52 | Location of the CA certificate to check. Defaults to Puppet's default location 53 | 54 | ##### `date` 55 | 56 | Data type: `Optional[String[1]]` 57 | 58 | YYYY-MM-DD date to test whether the certificate will expire by. Defaults to three months from today 59 | 60 | ### `check_crl_cert` 61 | 62 | Check the expiration date of the primary server crl 63 | 64 | **Supports noop?** false 65 | 66 | ### `check_primary_cert` 67 | 68 | Check the expiration date of the primary server cert 69 | 70 | **Supports noop?** false 71 | 72 | ### `configure_primary` 73 | 74 | Backup ssldir and copy newly generated CA certificate 75 | 76 | **Supports noop?** false 77 | 78 | #### Parameters 79 | 80 | ##### `new_cert` 81 | 82 | Data type: `String` 83 | 84 | Location of the newly generated CA certificate 85 | 86 | ##### `regen_primary_cert` 87 | 88 | Data type: `Boolean` 89 | 90 | Flag to regerate the primary server's certificate. Set to true to perform the regeneration 91 | 92 | ### `crl_truncate` 93 | 94 | Truncate the CRL issued by the Puppet CA 95 | 96 | **Supports noop?** false 97 | 98 | #### Parameters 99 | 100 | ##### `ssldir` 101 | 102 | Data type: `Optional[String[1]]` 103 | 104 | The location of the Puppet ssl dir 105 | 106 | ##### `crl_expiration_days` 107 | 108 | Data type: `Integer[1]` 109 | 110 | The number of days until the new CRL expires. Defaults to 15 years (5475 days) 111 | 112 | ##### `run_puppet_agent` 113 | 114 | Data type: `Boolean` 115 | 116 | Whether to run the Puppet agent after creating the CRL. Defaults to true 117 | 118 | ### `extend_ca_cert` 119 | 120 | Extend CA certificate expiry date 121 | 122 | **Supports noop?** false 123 | 124 | ## Plans 125 | 126 | ### `ca_extend::extend_ca_cert` 127 | 128 | Plan that extends the Puppet CA certificate and configures the primary Puppet server 129 | and Compilers to use the extended certificate. 130 | 131 | #### Examples 132 | 133 | ##### Extend the CA cert and regenerate the primary agent cert locally on the primary Puppet server 134 | 135 | ```puppet 136 | bolt plan run ca_extend::extend_ca_cert regen_primary_cert=true --targets local://$(hostname -f) --run-as root 137 | ``` 138 | 139 | ##### Extend the CA cert by running the plan remotely 140 | 141 | ```puppet 142 | bolt plan run ca_extend::extend_ca_cert --targets --run-as root 143 | ``` 144 | 145 | #### Parameters 146 | 147 | The following parameters are available in the `ca_extend::extend_ca_cert` plan: 148 | 149 | * [`targets`](#-ca_extend--extend_ca_cert--targets) 150 | * [`compilers`](#-ca_extend--extend_ca_cert--compilers) 151 | * [`replica`](#-ca_extend--extend_ca_cert--replica) 152 | * [`psql_nodes`](#-ca_extend--extend_ca_cert--psql_nodes) 153 | * [`ssldir`](#-ca_extend--extend_ca_cert--ssldir) 154 | * [`regen_primary_cert`](#-ca_extend--extend_ca_cert--regen_primary_cert) 155 | 156 | ##### `targets` 157 | 158 | Data type: `TargetSpec` 159 | 160 | The target node on which to run the plan. Should be the primary Puppet server 161 | 162 | ##### `compilers` 163 | 164 | Data type: `Optional[TargetSpec]` 165 | 166 | Optional comma separated list of compilers to configure to use the extended CA 167 | 168 | Default value: `undef` 169 | 170 | ##### `replica` 171 | 172 | Data type: `Optional[TargetSpec]` 173 | 174 | Optional replica to configure to use the extended CA 175 | 176 | Default value: `undef` 177 | 178 | ##### `psql_nodes` 179 | 180 | Data type: `Optional[TargetSpec]` 181 | 182 | Optional comma separated list of psql nodes to configure to use the extended CA 183 | 184 | Default value: `undef` 185 | 186 | ##### `ssldir` 187 | 188 | Data type: `Any` 189 | 190 | Location of the ssldir on disk 191 | 192 | Default value: `'/etc/puppetlabs/puppet/ssl'` 193 | 194 | ##### `regen_primary_cert` 195 | 196 | Data type: `Any` 197 | 198 | Whether to also regenerate the agent certificate of the primary Puppet server 199 | 200 | Default value: `false` 201 | 202 | ### `ca_extend::get_agent_facts` 203 | 204 | A plan to work around BOLT-1168 so that one agent failing in apply_prep won't cause the whole plan to fail. 205 | 206 | #### Parameters 207 | 208 | The following parameters are available in the `ca_extend::get_agent_facts` plan: 209 | 210 | * [`nodes`](#-ca_extend--get_agent_facts--nodes) 211 | 212 | ##### `nodes` 213 | 214 | Data type: `TargetSpec` 215 | 216 | The targets to run apply_prep on 217 | 218 | ### `ca_extend::upload_ca_cert` 219 | 220 | A plan to upload a given CA certificate to a number of Puppet agent nodes 221 | 222 | #### Parameters 223 | 224 | The following parameters are available in the `ca_extend::upload_ca_cert` plan: 225 | 226 | * [`nodes`](#-ca_extend--upload_ca_cert--nodes) 227 | * [`cert`](#-ca_extend--upload_ca_cert--cert) 228 | 229 | ##### `nodes` 230 | 231 | Data type: `TargetSpec` 232 | 233 | The targets to upload the certificate to 234 | 235 | ##### `cert` 236 | 237 | Data type: `String` 238 | 239 | The location of the CA certificate on disk of the local machine 240 | 241 | -------------------------------------------------------------------------------- /plans/extend_ca_cert.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Plan that extends the Puppet CA certificate and configures the primary Puppet server 3 | # and Compilers to use the extended certificate. 4 | # @param targets The target node on which to run the plan. Should be the primary Puppet server 5 | # @param compilers Optional comma separated list of compilers to configure to use the extended CA 6 | # @param replica Optional replica to configure to use the extended CA 7 | # @param psql_nodes Optional comma separated list of psql nodes to configure to use the extended CA 8 | # @param ssldir Location of the ssldir on disk 9 | # @param regen_primary_cert Whether to also regenerate the agent certificate of the primary Puppet server 10 | # @example Extend the CA cert and regenerate the primary agent cert locally on the primary Puppet server 11 | # bolt plan run ca_extend::extend_ca_cert regen_primary_cert=true --targets local://$(hostname -f) --run-as root 12 | # @example Extend the CA cert by running the plan remotely 13 | # bolt plan run ca_extend::extend_ca_cert --targets --run-as root 14 | plan ca_extend::extend_ca_cert( 15 | TargetSpec $targets, 16 | Optional[TargetSpec] $compilers = undef, 17 | Optional[TargetSpec] $replica = undef, 18 | Optional[TargetSpec] $psql_nodes = undef, 19 | $ssldir = '/etc/puppetlabs/puppet/ssl', 20 | $regen_primary_cert = false, 21 | ) { 22 | $targets.apply_prep 23 | $primary_facts = run_task('facts', $targets, '_catch_errors' => true).first 24 | 25 | if $primary_facts['pe_build'] { 26 | $is_pe = true 27 | 28 | $primary_services = [ 29 | 'puppet', 30 | 'pe-puppetserver', 31 | 'pe-postgresql', 32 | 'pe-puppetdb', 33 | 'pe-ace-server', 34 | 'pe-bolt-server', 35 | 'pe-console-services', 36 | 'pe-orchestration-services', 37 | ] 38 | $replica_services = ['pe-puppetserver', 'pe-postgresql', 'pe-puppetdb', 'pe-console-services'] 39 | } 40 | elsif $primary_facts['puppetversion'] { 41 | $is_pe = false 42 | $primary_services = ['puppet', 'puppetserver'] 43 | } 44 | else { 45 | fail_plan("Puppet not detected on ${targets}") 46 | } 47 | 48 | if $is_pe and ! $regen_primary_cert { 49 | $out = run_task('ca_extend::check_primary_cert', $targets, '_catch_errors' => true).first 50 | unless $out.ok { 51 | fail_plan($out.value['message']) 52 | } 53 | if $out.value['status'] == 'warn' { 54 | warning($out.value['message']) 55 | } 56 | } 57 | 58 | if $is_pe { 59 | $crl_results = run_task('ca_extend::check_crl_cert', $targets).first 60 | if $crl_results['status'] == 'expired' { 61 | out::message('INFO: CRL expired, truncating to regenerate') 62 | run_task('ca_extend::crl_truncate', $targets) 63 | } 64 | } 65 | 66 | out::message("INFO: Stopping Puppet services on ${targets}") 67 | $primary_services.each |$service| { 68 | run_task('service::linux', $targets, 'action' => 'stop', 'name' => $service) 69 | } 70 | 71 | out::message("INFO: Extending CA certificate on ${targets}") 72 | $regen_results = run_task('ca_extend::extend_ca_cert', $targets) 73 | $new_cert = $regen_results.first.value 74 | $cert_contents = base64('decode', $new_cert['contents']) 75 | 76 | out::message("INFO: Configuring ${targets} to use the extended CA certificate") 77 | if $is_pe { 78 | run_task('ca_extend::configure_primary', $targets, 79 | 'new_cert' => $new_cert['new_cert'], 'regen_primary_cert' => $regen_primary_cert 80 | ) 81 | } 82 | else { 83 | run_command("/bin/cp ${new_cert['new_cert']} ${ssldir}/certs/ca.pem", $targets) 84 | run_command("/bin/cp ${new_cert['new_cert']} ${ssldir}/ca/ca_crt.pem", $targets) 85 | run_task('service::linux', $targets, 'action' => 'start', 'name' => 'puppetserver') 86 | } 87 | run_task('service::linux', $targets, 'action' => 'start', 'name' => 'puppet') 88 | 89 | $tmp = run_command('mktemp', 'localhost', '_run_as' => system::env('USER')) 90 | $tmp_file = $tmp.first.value['stdout'].chomp 91 | file::write($tmp_file, $cert_contents) 92 | 93 | run_command('/opt/puppetlabs/bin/puppet agent --no-daemonize --no-noop --onetime', $targets) 94 | 95 | if $is_pe and $replica { 96 | out::message("INFO: Stopping Puppet services on ${replica}") 97 | # Stop and start the puppet service manually on replicas 98 | run_task('service::linux', $replica, 'action' => 'stop', 'name' => 'puppet') 99 | 100 | $replica_services.each |$service| { 101 | run_task('service::linux', $replica, 'action' => 'stop', 'name' => $service) 102 | } 103 | 104 | out::message("INFO: Configuring the replica (${replica}) to use the extended CA certificate") 105 | upload_file($tmp_file, '/etc/puppetlabs/puppet/ssl/certs/ca.pem', $replica) 106 | 107 | # Run the agent to restart the appropriate services 108 | out::message("INFO: running Puppet agent on ${replica}") 109 | run_command('/opt/puppetlabs/bin/puppet agent --no-daemonize --no-noop --onetime', $replica) 110 | 111 | # Re-enable the Puppet service 112 | run_task('service::linux', $compilers, 'action' => 'start', 'name' => 'puppet') 113 | } 114 | 115 | if $compilers { 116 | out::message("INFO: Stopping Puppet services on compilers (${compilers})") 117 | run_task('service::linux', $compilers, 'action' => 'stop', 'name' => 'puppet') 118 | 119 | out::message("INFO: Configuring compilers (${compilers}) to use the extended CA certificate") 120 | upload_file($tmp_file, '/etc/puppetlabs/puppet/ssl/certs/ca.pem', $compilers) 121 | 122 | if $is_pe { 123 | # Use the service::linux task to check if PDB is running on compilers and restart it if so 124 | $pdb_compilers = run_task('service::linux', $compilers, 'action' => 'status', 'name' => 'pe-puppetdb').filter_set |$compiler| { 125 | $compiler['enabled'] !~ /^Failed to get unit file state/ }.map |$result| { 126 | $result.target 127 | } 128 | $legacy_compilers = get_targets($compilers) - $pdb_compilers 129 | 130 | unless $pdb_compilers.empty { 131 | out::message('INFO: stopping services on PDB compilers') 132 | ['pe-puppetserver', 'pe-puppetdb'].each |$service| { 133 | run_task('service::linux', $pdb_compilers, 'action' => 'stop', 'name' => $service) 134 | } 135 | } 136 | 137 | unless $legacy_compilers.empty { 138 | out::message('INFO: stopping services on legacy compilers') 139 | run_task('service::linux', $legacy_compilers, 'action' => 'stop', 'name' => 'pe-puppetserver') 140 | } 141 | } 142 | else { 143 | out::message('INFO: stopping services on compilers') 144 | run_task('service::linux', $compilers, 'action' => 'stop', 'name' => 'pe-puppetserver') 145 | } 146 | 147 | # Run the agent to restart the appropriate services 148 | out::message("INFO: running Puppet agent on ${compilers}") 149 | run_command('/opt/puppetlabs/bin/puppet agent --no-daemonize --no-noop --onetime', $compilers) 150 | 151 | # Re-enable the Puppet service 152 | run_task('service::linux', $compilers, 'action' => 'start', 'name' => 'puppet') 153 | } 154 | 155 | if $psql_nodes { 156 | out::message("INFO: Stopping Puppet services on psql nodes (${psql_nodes})") 157 | ['puppet', 'pe-postgresql'].each |$service| { 158 | run_task('service::linux', $psql_nodes, 'action' => 'stop', 'name' => $service) 159 | } 160 | 161 | out::message("INFO: Configuring psql nodes (${psql_nodes}) to use the extended CA certificate") 162 | upload_file($tmp_file, '/etc/puppetlabs/puppet/ssl/certs/ca.pem', $psql_nodes) 163 | 164 | # Run the agent to restart the appropriate services 165 | out::message("INFO: running Puppet agent on ${psql_nodes}") 166 | run_command('/opt/puppetlabs/bin/puppet agent --no-daemonize --no-noop --onetime', $psql_nodes) 167 | 168 | # Re-enable the Puppet service 169 | run_task('service::linux', $psql_nodes, 'action' => 'start', 'name' => 'puppet') 170 | } 171 | 172 | out::message("INFO: Extended CA certificate decoded and stored at ${tmp_file}") 173 | out::message("INFO: Run the 'ca_extend::upload_ca_cert' plan to distribute the extended CA certificate to agents") 174 | } 175 | -------------------------------------------------------------------------------- /files/extend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Puppet CA extension script 4 | # 5 | # This script uses the Puppet CA certificates and private 6 | # keys to generate new CA certificates with extended 15 year 7 | # lifespans. 8 | # 9 | # This script operates on 2-certificate CA bundles used by 10 | # Puppet 6 and later in addition to single-certificate 11 | # bundles used by Puppet 5 and earlier. 12 | # 13 | # This script requires that the default locations for 14 | # the Puppet cadir and files underneath it are in use. 15 | # 16 | # Externally issued CA certificates are not supported. 17 | 18 | set -e 19 | 20 | PUPPET_BIN='/opt/puppetlabs/puppet/bin' 21 | 22 | ca_bundle=$("${PUPPET_BIN}/puppet" config print --section master cacert) 23 | ca_dir=$(dirname "${ca_bundle}") 24 | 25 | printf 'CA bundle file: %s\n' "${ca_bundle}" >&2 26 | 27 | printf '\n Checking CA bundle length...\n' >&2 28 | chain_length=$(grep -cF 'BEGIN CERTIFICATE' "${ca_bundle}") 29 | 30 | if (( chain_length > 2 )); then 31 | printf '%s certificates were found in: %s\n' "${chain_length}" "${ca_bundle}" >&2 32 | printf 'This script only works on CA bundles that contain one or two certificates.\n' >&2 33 | exit 1 34 | elif (( chain_length == 2 )); then 35 | printf '2 entry Puppet CA detected in: %s\n' "${ca_bundle}" >&2 36 | root_key="${ca_dir}/root_key.pem" 37 | intermediate_key="${ca_dir}/ca_key.pem" 38 | 39 | [[ -r "${root_key}" ]] || { 40 | printf 'ERROR: The Root CA key file is not readable: %s\n' "${root_key}" >&2 41 | printf 'This script must be run as root and does not support externally issued CA certs.\n' >&2 42 | exit 1 43 | } 44 | 45 | [[ -r "${intermediate_key}" ]] || { 46 | printf 'ERROR: The Intermediate CA key file is not readable: %s\n' "${root_key}" >&2 47 | exit 1 48 | } 49 | elif (( chain_length == 1 )); then 50 | printf '1 entry Puppet CA detected in: %s\n' "${ca_bundle}" >&2 51 | root_key="${ca_dir}/ca_key.pem" 52 | 53 | [[ -r "${root_key}" ]] || { 54 | printf 'ERROR: The Root CA key file is not readable: %s\n' "${root_key}" >&2 55 | printf 'This script must be run as root and does not support externally issued CA certs.\n' >&2 56 | exit 1 57 | } 58 | else 59 | printf 'ERROR: No certificates detected in: %s\n' "${ca_bundle}" >&2 60 | exit 1 61 | fi 62 | 63 | 64 | # Build a temporary directory with files required to renew the CA cert. 65 | 66 | workdir=$(mktemp -d -t puppet_ca_extend.XXX) 67 | printf 'Using working directory: %s\n' "${workdir}" >&2 68 | 69 | touch "${workdir}/inventory" 70 | touch "${workdir}/inventory.attr" 71 | cat < "${workdir}/openssl.cnf" 72 | [ca] 73 | default_ca=ca_settings 74 | 75 | [ca_settings] 76 | serial=${workdir}/serial 77 | new_certs_dir=${workdir} 78 | database=${workdir}/inventory 79 | default_md=sha256 80 | policy=ca_policy 81 | x509_extensions=cert_extensions 82 | 83 | [ca_policy] 84 | commonName=supplied 85 | 86 | [cert_extensions] 87 | basicConstraints=critical,CA:TRUE 88 | keyUsage=keyCertSign,cRLSign 89 | subjectKeyIdentifier=hash 90 | authorityKeyIdentifier=keyid:always 91 | EOT 92 | 93 | # Separate CA bundle out into individual certificates 94 | csplit -szf "${workdir}/puppet-ca-cert-" "${ca_bundle}" '/-----BEGIN CERTIFICATE-----/' '{*}' 95 | ca_certs=("${workdir}"/puppet-ca-cert-*) 96 | 97 | 98 | # Match keys up with certificates 99 | root_cert='' 100 | 101 | root_fingerprint=$("${PUPPET_BIN}/openssl" rsa -in "${root_key}" -noout -modulus|cut -d= -f2-) 102 | for ca_cert in "${ca_certs[@]}"; do 103 | ca_fingerprint=$("${PUPPET_BIN}/openssl" x509 -in "${ca_cert}" -noout -modulus|cut -d= -f2-) 104 | if [[ "${ca_fingerprint}" == "${root_fingerprint}" ]]; then 105 | root_cert="${ca_cert}" 106 | break 107 | fi 108 | done 109 | 110 | [[ -n "${root_cert}" ]] || { 111 | printf 'ERROR: Could not find a certificate matching key %s\n' "${root_key}" >&2 112 | printf 'Checked: %s\n\t%s\n' "${ca_certs[@]}" >&2 113 | 114 | exit 1 115 | } 116 | 117 | if (( chain_length == 2 )); then 118 | intermediate_cert='' 119 | 120 | intermediate_fingerprint=$("${PUPPET_BIN}/openssl" rsa -in "${intermediate_key}" -noout -modulus|cut -d= -f2-) 121 | for ca_cert in "${ca_certs[@]}"; do 122 | ca_fingerprint=$("${PUPPET_BIN}/openssl" x509 -in "${ca_cert}" -noout -modulus|cut -d= -f2-) 123 | if [[ "${ca_fingerprint}" == "${intermediate_fingerprint}" ]]; then 124 | intermediate_cert="${ca_cert}" 125 | break 126 | fi 127 | done 128 | 129 | [[ -n "${intermediate_cert}" ]] || { 130 | printf 'ERROR: Could not find a certificate matching key %s\n' "${intermediate_key}" >&2 131 | printf 'Checked: %s\n\t%s\n' "${ca_certs[@]}" >&2 132 | 133 | exit 1 134 | } 135 | fi 136 | 137 | 138 | # Extend CA certs 139 | 140 | # Compute start and end dates for new certificates. 141 | # Formats the year as YY instead of YYYY because the latter isn't supported 142 | # until OpenSSL 1.1.1. 143 | start_date=$(date -u --date='-24 hours' '+%y%m%d%H%M%SZ') 144 | end_date=$(date -u --date='+15 years' '+%y%m%d%H%M%SZ') 145 | 146 | root_subject=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -subject|cut -d= -f2-) 147 | root_issuer=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -issuer|cut -d= -f2-) 148 | root_enddate=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -enddate|cut -d= -f2-) 149 | root_serial_num=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -serial|cut -d= -f2-) 150 | 151 | [[ "${root_subject}" = "${root_issuer}" ]] || { 152 | printf 'ERROR: Root CA cert is not self-signed: %s\n' "${root_cert}" >&2 153 | printf 'Subject: %s\n' "${root_subject}" >&2 154 | printf 'Issuer: %s\n' "${root_issuer}" >&2 155 | printf 'This script does not support externally-issued CAs.' >&2 156 | 157 | exit 1 158 | } 159 | 160 | printf '\nExtending: %s\n' "${root_cert}" >&2 161 | printf 'Subject: %s\n' "${root_subject}" >&2 162 | printf 'Issuer: %s\n' "${root_issuer}" >&2 163 | printf 'Serial: %s\n' "${root_serial_num}" >&2 164 | printf 'End-Date: %s\n' "${root_enddate}" >&2 165 | 166 | # Generate a signing request from the existing certificate 167 | "${PUPPET_BIN}/openssl" x509 -x509toreq \ 168 | -in "${root_cert}" \ 169 | -signkey "${root_key}" \ 170 | -out "${workdir}/root_ca.csr.pem" 171 | 172 | printf '%s' "${root_serial_num}" > "${workdir}/serial" 173 | 174 | yes | "${PUPPET_BIN}/openssl" ca \ 175 | -notext \ 176 | -in "${workdir}/root_ca.csr.pem" \ 177 | -keyfile "${root_key}" \ 178 | -config "${workdir}/openssl.cnf" \ 179 | -selfsign \ 180 | -startdate "${start_date}" \ 181 | -enddate "${end_date}" \ 182 | -out "${workdir}/root_ca.renewed.pem" >&2 183 | 184 | if (( chain_length == 2 )); then 185 | intermediate_subject=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -subject|cut -d= -f2-) 186 | intermediate_issuer=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -issuer|cut -d= -f2-) 187 | intermediate_enddate=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -enddate|cut -d= -f2-) 188 | intermediate_serial_num=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -serial|cut -d= -f2-) 189 | 190 | [[ "${intermediate_issuer}" == "${root_issuer}" ]] || { 191 | printf 'ERROR: Intermediate CA cert is not issued by Root CA: %s\n' "${intermediate_cert}" >&2 192 | printf 'Subject: %s\n' "${intermediate_subject}" >&2 193 | printf 'Issuer: %s\n' "${intermediate_issuer}" >&2 194 | printf 'This script does not support externally-issued CAs.' >&2 195 | 196 | exit 1 197 | } 198 | 199 | printf '\nExtending: %s\n' "${intermediate_cert}" >&2 200 | printf 'Subject: %s\n' "${intermediate_subject}" >&2 201 | printf 'Issuer: %s\n' "${intermediate_issuer}" >&2 202 | printf 'Serial: %s\n' "${intermediate_serial_num}" >&2 203 | printf 'End-Date: %s\n' "${intermediate_enddate}" >&2 204 | 205 | # Generate a signing request from the existing certificate 206 | "${PUPPET_BIN}/openssl" x509 -x509toreq \ 207 | -in "${intermediate_cert}" \ 208 | -signkey "${intermediate_key}" \ 209 | -out "${workdir}/intermediate_ca.csr.pem" 210 | 211 | printf '%s' "${intermediate_serial_num}" > "${workdir}/serial" 212 | 213 | yes | "${PUPPET_BIN}/openssl" ca \ 214 | -notext \ 215 | -in "${workdir}/intermediate_ca.csr.pem" \ 216 | -cert "${workdir}/root_ca.renewed.pem" \ 217 | -keyfile "${root_key}" \ 218 | -config "${workdir}/openssl.cnf" \ 219 | -startdate "${start_date}" \ 220 | -enddate "${end_date}" \ 221 | -out "${workdir}/intermediate_ca.renewed.pem" >&2 222 | fi 223 | 224 | 225 | # Generate output bundle 226 | new_ca_bundle="${ca_dir}/ca_crt-expires-${end_date}.pem" 227 | 228 | if (( chain_length == 2 )); then 229 | cat "${workdir}/intermediate_ca.renewed.pem" \ 230 | "${workdir}/root_ca.renewed.pem" > "${new_ca_bundle}" 231 | else 232 | cat "${workdir}/root_ca.renewed.pem" > "${new_ca_bundle}" 233 | fi 234 | 235 | printf '\nRenewed CA certificates.\n' >&2 236 | printf '%s\n' "${new_ca_bundle}" 237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ca_extend 2 | 3 | #### Table of Contents 4 | 5 | 1. [Overview](#overview) 6 | 1. [Description - What the module does and why it is useful](#description) 7 | 1. [Setup - The basics of getting started with this module](#setup) 8 | 1. [Usage - Configuration options and additional functionality](#usage) 9 | 1. [Reference - An under-the-hood peek at what the module is doing](#reference) 10 | 1. [Development - Guide for contributing to the module](#How-to-Report-an-issue-or-contribute-to-the-module) 11 | 12 | ## Overview 13 | 14 | This module can extend a certificate authority (CA) that's about to expire or has already expired. 15 | 16 | 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. 17 | 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. 18 | 19 | If your CA certificate is expiring soon (or it's already expired), you need to: 20 | 21 | * Generate a new CA certificate using the existing CA keypair. 22 | * Distribute the new CA certificate to agents. 23 | 24 | This module can automate those tasks. 25 | 26 | ## Description 27 | 28 | 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. 29 | 30 | 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. 31 | 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. 32 | If the CA certificate is stored in any keystores, those will also need to be updated. 33 | 34 | The functionality of this module is composed into two Plans: 35 | 36 | * `ca_extend::extend_ca_cert` 37 | * Extend the CA certificate and configure the primary Puppet server, Replica, Compilers, and Postgres nodes to use that extended certificate. 38 | * `ca_extend::upload_ca_cert` 39 | * Distribute the CA certificate to agents using transport supported by Puppet Bolt, such as `ssh` and `winrm`. 40 | 41 | 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. 42 | 43 | After the CA certificate has been extended, there are three methods for distributing it to agents: 44 | 45 | 1. Using the `ca_extend::upload_ca_cert` plan or another method to copy the CA certificate to agents. 46 | 1. 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. 47 | 1. Using a Puppet file resource to manage `ca.pem`. _Note: This method is only possible if the CA certificate has not yet expired because Puppet communications depend upon a valid CA certificate._ 48 | 49 | There are also complementary tasks to check the expiration date of the CA certificate, agent certificates, and the CA CRL. 50 | 51 | * `ca_extend::check_ca_expiry` 52 | * Checks if the CA certificate expires by a certain date. Defaults to three months from today. 53 | * `ca_extend::check_agent_expiry` 54 | * Checks if any agent certificate expires by a certain date. Defaults to three months from today. 55 | * `ca_extend::check_crl_expiry` 56 | * Checks if the CA crl on the primary server has expired 57 | * `ca_extend::crl_truncate` 58 | * Will truncate and regenerate the CA CRL, this should only be run if the CRL is expired 59 | 60 | ** If the CA certificate is expiring or expired, you must extend it as soon as possible. ** 61 | 62 | ## Setup 63 | 64 | This module requires [Puppet Bolt](https://puppet.com/docs/bolt/latest/bolt_installing.html) >= 1.38.0 on either on the primary Puppet server or a workstation with connectivity to the primary. 65 | 66 | 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. 67 | 68 | ```bash 69 | sudo rpm -Uvh https://yum.puppet.com/puppet-tools-release-el-7.noarch.rpm 70 | sudo yum install puppet-bolt 71 | ``` 72 | 73 | The following two sections show how to install the module dependencies depending on the installed version of Bolt. 74 | 75 | ### Bolt >= 1.38.0 < 3.0.0 76 | 77 | 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). 78 | 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: 79 | 80 | ```bash 81 | mkdir -p ~/Boltdir 82 | cd ~/Boltdir 83 | 84 | cat >>Puppetfile <= 3.0.0 94 | 95 | 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: 96 | 97 | ```bash 98 | sudo rpm -Uvh https://yum.puppet.com/puppet-tools-release-el-7.noarch.rpm 99 | sudo yum install puppet-bolt 100 | ``` 101 | 102 | If your primary Puppet server or workstation has internet access, the project can be initialized with the needed dependencies with the following: 103 | 104 | ```bash 105 | mkdir ca_extend 106 | cd ca_extend 107 | 108 | bolt project init expiry --modules puppetlabs-stdlib,puppetlabs-ca_extend 109 | ``` 110 | 111 | Otherwise, if your primary Puppet server or workstation operates behind a proxy, initialize the project without the `--modules` option: 112 | 113 | ```bash 114 | mkdir ca_extend 115 | cd ca_extend 116 | 117 | bolt project init expiry 118 | ``` 119 | 120 | 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`: 121 | 122 | ```yaml 123 | --- 124 | name: expiry 125 | modules: 126 | - name: puppetlabs-stdlib 127 | - name: puppetlabs-ca_extend 128 | 129 | ``` 130 | 131 | Finally, install the modules. 132 | 133 | ```bash 134 | bolt module install 135 | ``` 136 | 137 | See the "Usage" section for how to run the tasks and plans remotely or locally on the primary Puppet server. 138 | 139 | ### Dependencies 140 | 141 | * A [Puppet Bolt](https://puppet.com/docs/bolt/latest/bolt_installing.html) >= 1.38.0 142 | * [puppetlabs-stdlib](https://puppet.com/docs/bolt/latest/bolt_installing.html) 143 | * A `base64` binary on the primary Puppet server which supports the `-w` flag 144 | * `bash` >= 4.0 on the primary Puppet server 145 | 146 | ## Usage 147 | 148 | ### Extend the CA using the ca_extend::extend_ca_cert plan 149 | 150 | First, check the expiration of the Puppet agent certificate by running the following command as root on the primary Puppet server: 151 | 152 | ```bash 153 | /opt/puppetlabs/puppet/bin/openssl x509 -in "$(/opt/puppetlabs/bin/puppet config print hostcert)" -enddate -noout 154 | ``` 155 | 156 | 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. 157 | 158 | > Note: This plan will also run the `ca_extend::check_crl_cert` task and if the crl is expired, will automatically resolve the issue by running the `ca_extend::crl_truncate` task. 159 | 160 | ```bash 161 | bolt plan run ca_extend::extend_ca_cert regen_primary_cert=true --targets replica= compilers= --run-as root 162 | ``` 163 | 164 | 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`, e.g. 165 | 166 | ```bash 167 | bolt plan run ca_extend::extend_ca_cert --targets local://hostname --run-as root 168 | ``` 169 | 170 | ### Distribute `ca.pem` to agents 171 | 172 | Next, distribute `ca.pem` to agents using one of the three methods: 173 | 174 | #### 1. Using the ca_extend::upload_ca_cert Plan 175 | 176 | Using the `ca_extend::upload_ca_cert` plan relies on using `ssh` and/or `winrm` transport methods. Use the `cert` parameter to specify the location of the updated CA cert on the primary server. For example, you may use `cert=$(puppet config print localcacert)`. Distribute the CA certificate to agent nodes specified in the `targets` parameter. Bolt defaults to using `ssh` transport, which in turn will use `~/.ssh/config` for options such as `username` and `private-key`. However, the `ca_extend::upload_ca_cert` plan works best with a Bolt [inventory file](https://puppet.com/docs/bolt/latest/inventory_file.html) to specify `targets`; this allows for simultaneous uploads to \*nix and Windows agents. See the Bolt documentation for more information on configuring an inventory file and the `targets` parameter. 177 | 178 | ```bash 179 | bolt plan run ca_extend::upload_ca_cert cert= --targets 180 | ``` 181 | 182 | As an alternative to using the `targets` parameter, you may specify targets for the `ca_extend::upload_ca_cert` plan by connecting Bolt to [PuppetDB](https://puppet.com/docs/bolt/latest/bolt_connect_puppetdb.html), after which the [--query](https://puppet.com/docs/bolt/latest/bolt_command_reference.html#command-options) parameter can be used. 183 | 184 | Example query for all agent nodes excluding puppetserver nodes because the `ca_extend::extend_ca_cert` plan already updates the primary's and compilers' copies of the CA certificate: 185 | 186 | ```bash 187 | bolt plan run ca_extend::upload_ca_cert cert= --query "nodes[certname]{! certname in ['primaryfqdn', 'compiler1fqdn', 'compiler2fqdn']}" 188 | ``` 189 | 190 | #### 2. Manually deleting `ca.pem` on agents and letting them download that file as part of the next Puppet agent run 191 | 192 | The agent will download `ca.pem` only if it is absent, so it must be deleted to use this method. 193 | 194 | For example, on an \*nix agent node delete `ca.pem` by running: 195 | 196 | ```bash 197 | rm $(puppet config print localcacert) 198 | ``` 199 | 200 | Next, run puppet so the agent will retreive `ca.pem`: 201 | 202 | ```bash 203 | puppet agent -t 204 | ``` 205 | 206 | **Note:** If you are depending on agent nodes downloading `ca.pem` during a scheduled Puppet run rather than manually initiating a Puppet run with `puppet agent -t`, you may need to restart the `puppet` service on \*nix nodes. This is because the Puppet agent daemon on \*nix nodes could have previous CA content loaded into memory. 207 | 208 | #### 3. Using a Puppet file resource to manage `ca.pem` 209 | 210 | 211 | You may add this code to the catalog received by your agent nodes; the code manages `ca.pem` on Windows and \*nix nodes with the contents of `ca.pem` on the compiling server (primary server or compiler). The code will not work with a serverless approach such as `puppet apply`. _Note: This method is only possible if the CA certificate has not yet expired because Puppet communications depend upon a valid CA certificate._ 212 | 213 | ``` 214 | $localcacert = $facts['os']['family'] ? { 215 | 'windows' => 'C:\ProgramData\PuppetLabs\puppet\etc\ssl\certs\ca.pem', 216 | default => '/etc/puppetlabs/puppet/ssl/certs/ca.pem' 217 | } 218 | file {$localcacert: 219 | ensure => file, 220 | content => file($settings::localcacert), 221 | } 222 | ``` 223 | 224 | ### ca_extend::check_ca_expiry Task 225 | 226 | You can use this task to check the CA cert expiry on the `primary` mainly but you can also use it to check that a remote \*nix node's CA cert has been updated after using any means to distribute the new CA certificate. 227 | 228 | ```bash 229 | bolt task run ca_extend::check_ca_expiry --targets 230 | ``` 231 | 232 | ### ca_extend::check_agent_expiry Task 233 | 234 | You can use this task to categorize all PE certs in a PE environment as part of a valid or expiring section based on a customizable date in the future (default 3 months from now). This task runs against a `primary` server and checks all certs under `/etc/puppetlabs/puppet/ssl/ca/signed` as the single source of truth for the PE environment and splits the certs between a valid section or expiring section. 235 | 236 | ```bash 237 | bolt task run ca_extend::check_agent_expiry --targets local://hostname 238 | ``` 239 | 240 | As such, the following output illustrates that all available certs in `/etc/puppetlabs/puppet/ssl/ca/signed` are valid and nothing is expiring in the next 3 months. 241 | 242 | ```bash 243 | [root@pe-server-7a5b76-0 ca_extend]# bolt task run ca_extend::check_agent_expiry --targets local://hostname 244 | Started on local://pe-server-7a5b76-0.us-west1-c.internal... 245 | Finished on local://pe-server-7a5b76-0.us-west1-c.internal: 246 | { 247 | "valid": [ 248 | { 249 | "console-cert.pem": "Jan 14 19:55:34 2024 GMT" 250 | }, 251 | { 252 | "critical-boom.delivery.puppetlabs.net.pem": "Apr 21 17:57:20 2027 GMT" 253 | }, 254 | { 255 | "irate-maple.delivery.puppetlabs.net.pem": "Apr 21 19:25:35 2027 GMT" 256 | } 257 | ], 258 | "expired": [ 259 | 260 | ] 261 | } 262 | 263 | Successful on 1 target: local://pe-server-7a5b76-0.us-west1-c.internal 264 | Ran on 1 target in 1.32 sec 265 | ``` 266 | 267 | See `REFERENCE.md` for more detailed examples. 268 | 269 | ## Reference 270 | 271 | Puppet's security is based on a PKI using X.509 certificates. 272 | 273 | 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: 274 | 275 | * Keypair. 276 | * Subject. 277 | * Issuer. 278 | * X509v3 Subject Key Identifier (the fingerprint of the public key). 279 | 280 | The new CA has a different: 281 | 282 | * Authority Key Identifier (just the serial number, since it's self-signed). 283 | * Validity period (the point of the whole exercise). 284 | * Signature (since we changed the serial number and validity period). 285 | 286 | 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, 287 | 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. 288 | Once you've done that, it's just a matter of delivering the new CA certificate to every participant in the PKI. 289 | 290 | ## How to Report an issue or contribute to the module 291 | 292 | 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). 293 | 294 | 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) 295 | 296 | 297 | --- 298 | 299 | # Supporting Content 300 | 301 | ### Articles 302 | 303 | The [Support Knowledge base](https://support.puppet.com/hc/en-us) is a searchable repository for technical information and how-to guides for all Puppet products. 304 | 305 | This Module has the following specific Article(s) available: 306 | 307 | 1. [Check and fix the expiry date for your CA certificate in Puppet Enterprise](https://support.puppet.com/hc/en-us/articles/360022508353) 308 | 309 | 310 | ### Videos 311 | 312 | The [Support Video Playlist](https://youtube.com/playlist?list=PLV86BgbREluWKzzvVulR74HZzMl6SCh3S) is a resource of content generated by the support team 313 | 314 | 315 | --- 316 | 317 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | require: 3 | - rubocop-performance 4 | - rubocop-rspec 5 | AllCops: 6 | DisplayCopNames: true 7 | TargetRubyVersion: '2.6' 8 | Include: 9 | - "**/*.rb" 10 | Exclude: 11 | - bin/* 12 | - ".vendor/**/*" 13 | - "**/Gemfile" 14 | - "**/Rakefile" 15 | - pkg/**/* 16 | - spec/fixtures/**/* 17 | - vendor/**/* 18 | - "**/Puppetfile" 19 | - "**/Vagrantfile" 20 | - "**/Guardfile" 21 | Layout/LineLength: 22 | Description: People have wide screens, use them. 23 | Max: 200 24 | RSpec/BeforeAfterAll: 25 | Description: Beware of using after(:all) as it may cause state to leak between tests. 26 | A necessary evil in acceptance testing. 27 | Exclude: 28 | - spec/acceptance/**/*.rb 29 | RSpec/HookArgument: 30 | Description: Prefer explicit :each argument, matching existing module's style 31 | EnforcedStyle: each 32 | RSpec/DescribeSymbol: 33 | Exclude: 34 | - spec/unit/facter/**/*.rb 35 | Style/BlockDelimiters: 36 | Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to 37 | be consistent then. 38 | EnforcedStyle: braces_for_chaining 39 | Style/ClassAndModuleChildren: 40 | Description: Compact style reduces the required amount of indentation. 41 | EnforcedStyle: compact 42 | Style/EmptyElse: 43 | Description: Enforce against empty else clauses, but allow `nil` for clarity. 44 | EnforcedStyle: empty 45 | Style/FormatString: 46 | Description: Following the main puppet project's style, prefer the % format format. 47 | EnforcedStyle: percent 48 | Style/FormatStringToken: 49 | Description: Following the main puppet project's style, prefer the simpler template 50 | tokens over annotated ones. 51 | EnforcedStyle: template 52 | Style/Lambda: 53 | Description: Prefer the keyword for easier discoverability. 54 | EnforcedStyle: literal 55 | Style/RegexpLiteral: 56 | Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 57 | EnforcedStyle: percent_r 58 | Style/TernaryParentheses: 59 | Description: Checks for use of parentheses around ternary conditions. Enforce parentheses 60 | on complex expressions for better readability, but seriously consider breaking 61 | it up. 62 | EnforcedStyle: require_parentheses_when_complex 63 | Style/TrailingCommaInArguments: 64 | Description: Prefer always trailing comma on multiline argument lists. This makes 65 | diffs, and re-ordering nicer. 66 | EnforcedStyleForMultiline: comma 67 | Style/TrailingCommaInArrayLiteral: 68 | Description: Prefer always trailing comma on multiline literals. This makes diffs, 69 | and re-ordering nicer. 70 | EnforcedStyleForMultiline: comma 71 | Style/SymbolArray: 72 | Description: Using percent style obscures symbolic intent of array's contents. 73 | EnforcedStyle: brackets 74 | RSpec/MessageSpies: 75 | EnforcedStyle: receive 76 | Style/Documentation: 77 | Exclude: 78 | - lib/puppet/parser/functions/**/* 79 | - spec/**/* 80 | Style/WordArray: 81 | EnforcedStyle: brackets 82 | Performance/AncestorsInclude: 83 | Enabled: true 84 | Performance/BigDecimalWithNumericArgument: 85 | Enabled: true 86 | Performance/BlockGivenWithExplicitBlock: 87 | Enabled: true 88 | Performance/CaseWhenSplat: 89 | Enabled: true 90 | Performance/ConstantRegexp: 91 | Enabled: true 92 | Performance/MethodObjectAsBlock: 93 | Enabled: true 94 | Performance/RedundantSortBlock: 95 | Enabled: true 96 | Performance/RedundantStringChars: 97 | Enabled: true 98 | Performance/ReverseFirst: 99 | Enabled: true 100 | Performance/SortReverse: 101 | Enabled: true 102 | Performance/Squeeze: 103 | Enabled: true 104 | Performance/StringInclude: 105 | Enabled: true 106 | Performance/Sum: 107 | Enabled: true 108 | Style/CollectionMethods: 109 | Enabled: true 110 | Style/MethodCalledOnDoEndBlock: 111 | Enabled: true 112 | Style/StringMethods: 113 | Enabled: true 114 | Bundler/GemFilename: 115 | Enabled: false 116 | Bundler/InsecureProtocolSource: 117 | Enabled: false 118 | Capybara/CurrentPathExpectation: 119 | Enabled: false 120 | Capybara/VisibilityMatcher: 121 | Enabled: false 122 | Gemspec/DuplicatedAssignment: 123 | Enabled: false 124 | Gemspec/OrderedDependencies: 125 | Enabled: false 126 | Gemspec/RequiredRubyVersion: 127 | Enabled: false 128 | Gemspec/RubyVersionGlobalsUsage: 129 | Enabled: false 130 | Layout/ArgumentAlignment: 131 | Enabled: false 132 | Layout/BeginEndAlignment: 133 | Enabled: false 134 | Layout/ClosingHeredocIndentation: 135 | Enabled: false 136 | Layout/EmptyComment: 137 | Enabled: false 138 | Layout/EmptyLineAfterGuardClause: 139 | Enabled: false 140 | Layout/EmptyLinesAroundArguments: 141 | Enabled: false 142 | Layout/EmptyLinesAroundAttributeAccessor: 143 | Enabled: false 144 | Layout/EndOfLine: 145 | Enabled: false 146 | Layout/FirstArgumentIndentation: 147 | Enabled: false 148 | Layout/HashAlignment: 149 | Enabled: false 150 | Layout/HeredocIndentation: 151 | Enabled: false 152 | Layout/LeadingEmptyLines: 153 | Enabled: false 154 | Layout/SpaceAroundMethodCallOperator: 155 | Enabled: false 156 | Layout/SpaceInsideArrayLiteralBrackets: 157 | Enabled: false 158 | Layout/SpaceInsideReferenceBrackets: 159 | Enabled: false 160 | Lint/BigDecimalNew: 161 | Enabled: false 162 | Lint/BooleanSymbol: 163 | Enabled: false 164 | Lint/ConstantDefinitionInBlock: 165 | Enabled: false 166 | Lint/DeprecatedOpenSSLConstant: 167 | Enabled: false 168 | Lint/DisjunctiveAssignmentInConstructor: 169 | Enabled: false 170 | Lint/DuplicateElsifCondition: 171 | Enabled: false 172 | Lint/DuplicateRequire: 173 | Enabled: false 174 | Lint/DuplicateRescueException: 175 | Enabled: false 176 | Lint/EmptyConditionalBody: 177 | Enabled: false 178 | Lint/EmptyFile: 179 | Enabled: false 180 | Lint/ErbNewArguments: 181 | Enabled: false 182 | Lint/FloatComparison: 183 | Enabled: false 184 | Lint/HashCompareByIdentity: 185 | Enabled: false 186 | Lint/IdentityComparison: 187 | Enabled: false 188 | Lint/InterpolationCheck: 189 | Enabled: false 190 | Lint/MissingCopEnableDirective: 191 | Enabled: false 192 | Lint/MixedRegexpCaptureTypes: 193 | Enabled: false 194 | Lint/NestedPercentLiteral: 195 | Enabled: false 196 | Lint/NonDeterministicRequireOrder: 197 | Enabled: false 198 | Lint/OrderedMagicComments: 199 | Enabled: false 200 | Lint/OutOfRangeRegexpRef: 201 | Enabled: false 202 | Lint/RaiseException: 203 | Enabled: false 204 | Lint/RedundantCopEnableDirective: 205 | Enabled: false 206 | Lint/RedundantRequireStatement: 207 | Enabled: false 208 | Lint/RedundantSafeNavigation: 209 | Enabled: false 210 | Lint/RedundantWithIndex: 211 | Enabled: false 212 | Lint/RedundantWithObject: 213 | Enabled: false 214 | Lint/RegexpAsCondition: 215 | Enabled: false 216 | Lint/ReturnInVoidContext: 217 | Enabled: false 218 | Lint/SafeNavigationConsistency: 219 | Enabled: false 220 | Lint/SafeNavigationWithEmpty: 221 | Enabled: false 222 | Lint/SelfAssignment: 223 | Enabled: false 224 | Lint/SendWithMixinArgument: 225 | Enabled: false 226 | Lint/ShadowedArgument: 227 | Enabled: false 228 | Lint/StructNewOverride: 229 | Enabled: false 230 | Lint/ToJSON: 231 | Enabled: false 232 | Lint/TopLevelReturnWithArgument: 233 | Enabled: false 234 | Lint/TrailingCommaInAttributeDeclaration: 235 | Enabled: false 236 | Lint/UnreachableLoop: 237 | Enabled: false 238 | Lint/UriEscapeUnescape: 239 | Enabled: false 240 | Lint/UriRegexp: 241 | Enabled: false 242 | Lint/UselessMethodDefinition: 243 | Enabled: false 244 | Lint/UselessTimes: 245 | Enabled: false 246 | Metrics/AbcSize: 247 | Enabled: false 248 | Metrics/BlockLength: 249 | Enabled: false 250 | Metrics/BlockNesting: 251 | Enabled: false 252 | Metrics/ClassLength: 253 | Enabled: false 254 | Metrics/CyclomaticComplexity: 255 | Enabled: false 256 | Metrics/MethodLength: 257 | Enabled: false 258 | Metrics/ModuleLength: 259 | Enabled: false 260 | Metrics/ParameterLists: 261 | Enabled: false 262 | Metrics/PerceivedComplexity: 263 | Enabled: false 264 | Migration/DepartmentName: 265 | Enabled: false 266 | Naming/AccessorMethodName: 267 | Enabled: false 268 | Naming/BlockParameterName: 269 | Enabled: false 270 | Naming/HeredocDelimiterCase: 271 | Enabled: false 272 | Naming/HeredocDelimiterNaming: 273 | Enabled: false 274 | Naming/MemoizedInstanceVariableName: 275 | Enabled: false 276 | Naming/MethodParameterName: 277 | Enabled: false 278 | Naming/RescuedExceptionsVariableName: 279 | Enabled: false 280 | Naming/VariableNumber: 281 | Enabled: false 282 | Performance/BindCall: 283 | Enabled: false 284 | Performance/DeletePrefix: 285 | Enabled: false 286 | Performance/DeleteSuffix: 287 | Enabled: false 288 | Performance/InefficientHashSearch: 289 | Enabled: false 290 | Performance/UnfreezeString: 291 | Enabled: false 292 | Performance/UriDefaultParser: 293 | Enabled: false 294 | RSpec/Be: 295 | Enabled: false 296 | RSpec/Capybara/FeatureMethods: 297 | Enabled: false 298 | RSpec/ContainExactly: 299 | Enabled: false 300 | RSpec/ContextMethod: 301 | Enabled: false 302 | RSpec/ContextWording: 303 | Enabled: false 304 | RSpec/DescribeClass: 305 | Enabled: false 306 | RSpec/EmptyHook: 307 | Enabled: false 308 | RSpec/EmptyLineAfterExample: 309 | Enabled: false 310 | RSpec/EmptyLineAfterExampleGroup: 311 | Enabled: false 312 | RSpec/EmptyLineAfterHook: 313 | Enabled: false 314 | RSpec/ExampleLength: 315 | Enabled: false 316 | RSpec/ExampleWithoutDescription: 317 | Enabled: false 318 | RSpec/ExpectChange: 319 | Enabled: false 320 | RSpec/ExpectInHook: 321 | Enabled: false 322 | RSpec/FactoryBot/AttributeDefinedStatically: 323 | Enabled: false 324 | RSpec/FactoryBot/CreateList: 325 | Enabled: false 326 | RSpec/FactoryBot/FactoryClassName: 327 | Enabled: false 328 | RSpec/HooksBeforeExamples: 329 | Enabled: false 330 | RSpec/ImplicitBlockExpectation: 331 | Enabled: false 332 | RSpec/ImplicitSubject: 333 | Enabled: false 334 | RSpec/LeakyConstantDeclaration: 335 | Enabled: false 336 | RSpec/LetBeforeExamples: 337 | Enabled: false 338 | RSpec/MatchArray: 339 | Enabled: false 340 | RSpec/MissingExampleGroupArgument: 341 | Enabled: false 342 | RSpec/MultipleExpectations: 343 | Enabled: false 344 | RSpec/MultipleMemoizedHelpers: 345 | Enabled: false 346 | RSpec/MultipleSubjects: 347 | Enabled: false 348 | RSpec/NestedGroups: 349 | Enabled: false 350 | RSpec/PredicateMatcher: 351 | Enabled: false 352 | RSpec/ReceiveCounts: 353 | Enabled: false 354 | RSpec/ReceiveNever: 355 | Enabled: false 356 | RSpec/RepeatedExampleGroupBody: 357 | Enabled: false 358 | RSpec/RepeatedExampleGroupDescription: 359 | Enabled: false 360 | RSpec/RepeatedIncludeExample: 361 | Enabled: false 362 | RSpec/ReturnFromStub: 363 | Enabled: false 364 | RSpec/SharedExamples: 365 | Enabled: false 366 | RSpec/StubbedMock: 367 | Enabled: false 368 | RSpec/UnspecifiedException: 369 | Enabled: false 370 | RSpec/VariableDefinition: 371 | Enabled: false 372 | RSpec/VoidExpect: 373 | Enabled: false 374 | RSpec/Yield: 375 | Enabled: false 376 | Security/Open: 377 | Enabled: false 378 | Style/AccessModifierDeclarations: 379 | Enabled: false 380 | Style/AccessorGrouping: 381 | Enabled: false 382 | Style/BisectedAttrAccessor: 383 | Enabled: false 384 | Style/CaseLikeIf: 385 | Enabled: false 386 | Style/ClassEqualityComparison: 387 | Enabled: false 388 | Style/ColonMethodDefinition: 389 | Enabled: false 390 | Style/CombinableLoops: 391 | Enabled: false 392 | Style/CommentedKeyword: 393 | Enabled: false 394 | Style/Dir: 395 | Enabled: false 396 | Style/DoubleCopDisableDirective: 397 | Enabled: false 398 | Style/EmptyBlockParameter: 399 | Enabled: false 400 | Style/EmptyLambdaParameter: 401 | Enabled: false 402 | Style/Encoding: 403 | Enabled: false 404 | Style/EvalWithLocation: 405 | Enabled: false 406 | Style/ExpandPathArguments: 407 | Enabled: false 408 | Style/ExplicitBlockArgument: 409 | Enabled: false 410 | Style/ExponentialNotation: 411 | Enabled: false 412 | Style/FloatDivision: 413 | Enabled: false 414 | Style/FrozenStringLiteralComment: 415 | Enabled: false 416 | Style/GlobalStdStream: 417 | Enabled: false 418 | Style/HashAsLastArrayItem: 419 | Enabled: false 420 | Style/HashLikeCase: 421 | Enabled: false 422 | Style/HashTransformKeys: 423 | Enabled: false 424 | Style/HashTransformValues: 425 | Enabled: false 426 | Style/IfUnlessModifier: 427 | Enabled: false 428 | Style/KeywordParametersOrder: 429 | Enabled: false 430 | Style/MinMax: 431 | Enabled: false 432 | Style/MixinUsage: 433 | Enabled: false 434 | Style/MultilineWhenThen: 435 | Enabled: false 436 | Style/NegatedUnless: 437 | Enabled: false 438 | Style/NumericPredicate: 439 | Enabled: false 440 | Style/OptionalBooleanParameter: 441 | Enabled: false 442 | Style/OrAssignment: 443 | Enabled: false 444 | Style/RandomWithOffset: 445 | Enabled: false 446 | Style/RedundantAssignment: 447 | Enabled: false 448 | Style/RedundantCondition: 449 | Enabled: false 450 | Style/RedundantConditional: 451 | Enabled: false 452 | Style/RedundantFetchBlock: 453 | Enabled: false 454 | Style/RedundantFileExtensionInRequire: 455 | Enabled: false 456 | Style/RedundantRegexpCharacterClass: 457 | Enabled: false 458 | Style/RedundantRegexpEscape: 459 | Enabled: false 460 | Style/RedundantSelfAssignment: 461 | Enabled: false 462 | Style/RedundantSort: 463 | Enabled: false 464 | Style/RescueStandardError: 465 | Enabled: false 466 | Style/SingleArgumentDig: 467 | Enabled: false 468 | Style/SlicingWithRange: 469 | Enabled: false 470 | Style/SoleNestedConditional: 471 | Enabled: false 472 | Style/StderrPuts: 473 | Enabled: false 474 | Style/StringConcatenation: 475 | Enabled: false 476 | Style/Strip: 477 | Enabled: false 478 | Style/SymbolProc: 479 | Enabled: false 480 | Style/TrailingBodyOnClass: 481 | Enabled: false 482 | Style/TrailingBodyOnMethodDefinition: 483 | Enabled: false 484 | Style/TrailingBodyOnModule: 485 | Enabled: false 486 | Style/TrailingCommaInHashLiteral: 487 | Enabled: false 488 | Style/TrailingMethodEndStatement: 489 | Enabled: false 490 | Style/UnpackFirst: 491 | Enabled: false 492 | Capybara/MatchStyle: 493 | Enabled: false 494 | Capybara/NegationMatcher: 495 | Enabled: false 496 | Capybara/SpecificActions: 497 | Enabled: false 498 | Capybara/SpecificFinders: 499 | Enabled: false 500 | Capybara/SpecificMatcher: 501 | Enabled: false 502 | Gemspec/DeprecatedAttributeAssignment: 503 | Enabled: false 504 | Gemspec/DevelopmentDependencies: 505 | Enabled: false 506 | Gemspec/RequireMFA: 507 | Enabled: false 508 | Layout/LineContinuationLeadingSpace: 509 | Enabled: false 510 | Layout/LineContinuationSpacing: 511 | Enabled: false 512 | Layout/LineEndStringConcatenationIndentation: 513 | Enabled: false 514 | Layout/SpaceBeforeBrackets: 515 | Enabled: false 516 | Lint/AmbiguousAssignment: 517 | Enabled: false 518 | Lint/AmbiguousOperatorPrecedence: 519 | Enabled: false 520 | Lint/AmbiguousRange: 521 | Enabled: false 522 | Lint/ConstantOverwrittenInRescue: 523 | Enabled: false 524 | Lint/DeprecatedConstants: 525 | Enabled: false 526 | Lint/DuplicateBranch: 527 | Enabled: false 528 | Lint/DuplicateMagicComment: 529 | Enabled: false 530 | Lint/DuplicateRegexpCharacterClassElement: 531 | Enabled: false 532 | Lint/EmptyBlock: 533 | Enabled: false 534 | Lint/EmptyClass: 535 | Enabled: false 536 | Lint/EmptyInPattern: 537 | Enabled: false 538 | Lint/IncompatibleIoSelectWithFiberScheduler: 539 | Enabled: false 540 | Lint/LambdaWithoutLiteralBlock: 541 | Enabled: false 542 | Lint/NoReturnInBeginEndBlocks: 543 | Enabled: false 544 | Lint/NonAtomicFileOperation: 545 | Enabled: false 546 | Lint/NumberedParameterAssignment: 547 | Enabled: false 548 | Lint/OrAssignmentToConstant: 549 | Enabled: false 550 | Lint/RedundantDirGlobSort: 551 | Enabled: false 552 | Lint/RefinementImportMethods: 553 | Enabled: false 554 | Lint/RequireRangeParentheses: 555 | Enabled: false 556 | Lint/RequireRelativeSelfPath: 557 | Enabled: false 558 | Lint/SymbolConversion: 559 | Enabled: false 560 | Lint/ToEnumArguments: 561 | Enabled: false 562 | Lint/TripleQuotes: 563 | Enabled: false 564 | Lint/UnexpectedBlockArity: 565 | Enabled: false 566 | Lint/UnmodifiedReduceAccumulator: 567 | Enabled: false 568 | Lint/UselessRescue: 569 | Enabled: false 570 | Lint/UselessRuby2Keywords: 571 | Enabled: false 572 | Metrics/CollectionLiteralLength: 573 | Enabled: false 574 | Naming/BlockForwarding: 575 | Enabled: false 576 | Performance/CollectionLiteralInLoop: 577 | Enabled: false 578 | Performance/ConcurrentMonotonicTime: 579 | Enabled: false 580 | Performance/MapCompact: 581 | Enabled: false 582 | Performance/RedundantEqualityComparisonBlock: 583 | Enabled: false 584 | Performance/RedundantSplitRegexpArgument: 585 | Enabled: false 586 | Performance/StringIdentifierArgument: 587 | Enabled: false 588 | RSpec/BeEq: 589 | Enabled: false 590 | RSpec/BeNil: 591 | Enabled: false 592 | RSpec/ChangeByZero: 593 | Enabled: false 594 | RSpec/ClassCheck: 595 | Enabled: false 596 | RSpec/DuplicatedMetadata: 597 | Enabled: false 598 | RSpec/ExcessiveDocstringSpacing: 599 | Enabled: false 600 | RSpec/FactoryBot/ConsistentParenthesesStyle: 601 | Enabled: false 602 | RSpec/FactoryBot/FactoryNameStyle: 603 | Enabled: false 604 | RSpec/FactoryBot/SyntaxMethods: 605 | Enabled: false 606 | RSpec/IdenticalEqualityAssertion: 607 | Enabled: false 608 | RSpec/NoExpectationExample: 609 | Enabled: false 610 | RSpec/PendingWithoutReason: 611 | Enabled: false 612 | RSpec/Rails/AvoidSetupHook: 613 | Enabled: false 614 | RSpec/Rails/HaveHttpStatus: 615 | Enabled: false 616 | RSpec/Rails/InferredSpecType: 617 | Enabled: false 618 | RSpec/Rails/MinitestAssertions: 619 | Enabled: false 620 | RSpec/Rails/TravelAround: 621 | Enabled: false 622 | RSpec/RedundantAround: 623 | Enabled: false 624 | RSpec/SkipBlockInsideExample: 625 | Enabled: false 626 | RSpec/SortMetadata: 627 | Enabled: false 628 | RSpec/SubjectDeclaration: 629 | Enabled: false 630 | RSpec/VerifiedDoubleReference: 631 | Enabled: false 632 | Security/CompoundHash: 633 | Enabled: false 634 | Security/IoMethods: 635 | Enabled: false 636 | Style/ArgumentsForwarding: 637 | Enabled: false 638 | Style/ArrayIntersect: 639 | Enabled: false 640 | Style/CollectionCompact: 641 | Enabled: false 642 | Style/ComparableClamp: 643 | Enabled: false 644 | Style/ConcatArrayLiterals: 645 | Enabled: false 646 | Style/DirEmpty: 647 | Enabled: false 648 | Style/DocumentDynamicEvalDefinition: 649 | Enabled: false 650 | Style/EmptyHeredoc: 651 | Enabled: false 652 | Style/EndlessMethod: 653 | Enabled: false 654 | Style/EnvHome: 655 | Enabled: false 656 | Style/FetchEnvVar: 657 | Enabled: false 658 | Style/FileEmpty: 659 | Enabled: false 660 | Style/FileRead: 661 | Enabled: false 662 | Style/FileWrite: 663 | Enabled: false 664 | Style/HashConversion: 665 | Enabled: false 666 | Style/HashExcept: 667 | Enabled: false 668 | Style/IfWithBooleanLiteralBranches: 669 | Enabled: false 670 | Style/InPatternThen: 671 | Enabled: false 672 | Style/MagicCommentFormat: 673 | Enabled: false 674 | Style/MapCompactWithConditionalBlock: 675 | Enabled: false 676 | Style/MapToHash: 677 | Enabled: false 678 | Style/MapToSet: 679 | Enabled: false 680 | Style/MinMaxComparison: 681 | Enabled: false 682 | Style/MultilineInPatternThen: 683 | Enabled: false 684 | Style/NegatedIfElseCondition: 685 | Enabled: false 686 | Style/NestedFileDirname: 687 | Enabled: false 688 | Style/NilLambda: 689 | Enabled: false 690 | Style/NumberedParameters: 691 | Enabled: false 692 | Style/NumberedParametersLimit: 693 | Enabled: false 694 | Style/ObjectThen: 695 | Enabled: false 696 | Style/OpenStructUse: 697 | Enabled: false 698 | Style/OperatorMethodCall: 699 | Enabled: false 700 | Style/QuotedSymbols: 701 | Enabled: false 702 | Style/RedundantArgument: 703 | Enabled: false 704 | Style/RedundantConstantBase: 705 | Enabled: false 706 | Style/RedundantDoubleSplatHashBraces: 707 | Enabled: false 708 | Style/RedundantEach: 709 | Enabled: false 710 | Style/RedundantHeredocDelimiterQuotes: 711 | Enabled: false 712 | Style/RedundantInitialize: 713 | Enabled: false 714 | Style/RedundantSelfAssignmentBranch: 715 | Enabled: false 716 | Style/RedundantStringEscape: 717 | Enabled: false 718 | Style/SelectByRegexp: 719 | Enabled: false 720 | Style/StringChars: 721 | Enabled: false 722 | Style/SwapValues: 723 | Enabled: false 724 | --------------------------------------------------------------------------------