diff --git a/REFERENCE.md b/REFERENCE.md index 7aef694b..cb85d76e 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -86,6 +86,7 @@ * [`ssl_clean`](#ssl_clean): Clean an agent's certificate * [`submit_csr`](#submit_csr): Submit a certificate signing request * [`transform_classification_groups`](#transform_classification_groups): Transform the user groups from a source backup to a list of groups on the target server +* [`validate_rbac_token`](#validate_rbac_token): Check an RBAC token stored in a file is valid * [`wait_until_service_ready`](#wait_until_service_ready): Return when the orchestrator service is healthy, or timeout after 15 seconds ### Plans @@ -1572,6 +1573,20 @@ Data type: `String` Location of target node group yaml file and where to create the transformed file +### `validate_rbac_token` + +Check an RBAC token stored in a file is valid + +**Supports noop?** false + +#### Parameters + +##### `token_file` + +Data type: `Optional[String]` + +The path to the token file to use + ### `wait_until_service_ready` Return when the orchestrator service is healthy, or timeout after 15 seconds diff --git a/plans/upgrade.pp b/plans/upgrade.pp index 5d061e0d..d15f7e44 100644 --- a/plans/upgrade.pp +++ b/plans/upgrade.pp @@ -67,6 +67,8 @@ 'upgrade-replica-compilers', 'finalize']] $begin_at_step = undef, ) { + out::message('# Validating inputs') + # Ensure input valid for a supported architecture $arch = peadm::assert_supported_architecture( $primary_host, @@ -97,6 +99,11 @@ $replica_postgresql_target, ]) + # Validate the RBAC token used to upgrade compilers if compilers are present + if $compiler_targets and $compiler_targets.size > 0 { + run_task('peadm::validate_rbac_token', $primary_target, token_file => $token_file) + } + out::message('# Gathering information') # lint:ignore:strict_indent diff --git a/plans/util/retrieve_and_upload.pp b/plans/util/retrieve_and_upload.pp index 2bb40168..51002d28 100644 --- a/plans/util/retrieve_and_upload.pp +++ b/plans/util/retrieve_and_upload.pp @@ -29,39 +29,39 @@ |-HEREDOC # lint:endignore -$operating_system = run_task('peadm::os_identification', 'local://localhost') -$os_string =$operating_system.first.value['_output'] + $operating_system = run_task('peadm::os_identification', 'local://localhost') + $os_string =$operating_system.first.value['_output'] -if 'windows' in $os_string { - $exists = run_command("[System.IO.File]::Exists('${local_path}')", 'local://localhost') - if $exists.first['stdout'].chomp == 'false' { - run_task('peadm::download', 'local://localhost', - source => $source, - path => $local_path, - ) - } + if 'windows' in $os_string { + $exists = run_command("[System.IO.File]::Exists('${local_path}')", 'local://localhost') + if $exists.first['stdout'].chomp == 'false' { + run_task('peadm::download', 'local://localhost', + source => $source, + path => $local_path, + ) + } - $result_size = run_task('peadm::filesize', 'local://localhost', - path => $local_path, - ) - $local_size = $result_size.first.value['_output'] -} else { - $exists = without_default_logging() || { - run_command("test -e '${local_path}'", 'local://localhost', - _catch_errors => true, - ).ok() - } - unless $exists { - run_task('peadm::download', 'local://localhost', - source => $source, - path => $local_path, + $result_size = run_task('peadm::filesize', 'local://localhost', + path => $local_path, ) - } + $local_size = $result_size.first.value['_output'] + } else { + $exists = without_default_logging() || { + run_command("test -e '${local_path}'", 'local://localhost', + _catch_errors => true, + ).ok() + } + unless $exists { + run_task('peadm::download', 'local://localhost', + source => $source, + path => $local_path, + ) + } - $local_size = run_task('peadm::filesize', 'local://localhost', - path => $local_path, - ).first['size'] -} + $local_size = run_task('peadm::filesize', 'local://localhost', + path => $local_path, + ).first['size'] + } $targets_needing_file = run_task('peadm::filesize', $nodes, path => $upload_path, diff --git a/tasks/puppet_infra_upgrade.rb b/tasks/puppet_infra_upgrade.rb index daad14b8..11fea276 100755 --- a/tasks/puppet_infra_upgrade.rb +++ b/tasks/puppet_infra_upgrade.rb @@ -73,7 +73,8 @@ def wait_until_connected(nodes:, token_file:, timeout: 120) loop do response = https.request(request) unless response.is_a? Net::HTTPSuccess - raise "Unexpected result from orchestrator: #{response.class}\n#{response}" + body = JSON.parse(response.body) + raise "Unexpected result from orchestrator: #{response.code}#{body.kind}\n#{body.msg}" end inventory = JSON.parse(response.body) break if inventory['items'].all? { |item| item['connected'] } diff --git a/tasks/validate_rbac_token.json b/tasks/validate_rbac_token.json new file mode 100644 index 00000000..6c526643 --- /dev/null +++ b/tasks/validate_rbac_token.json @@ -0,0 +1,13 @@ +{ + "description": "Check an RBAC token stored in a file is valid", + "parameters": { + "token_file": { + "type": "Optional[String]", + "description": "The path to the token file to use" + } + }, + "input_method": "stdin", + "implementations": [ + {"name": "validate_rbac_token.rb"} + ] +} diff --git a/tasks/validate_rbac_token.rb b/tasks/validate_rbac_token.rb new file mode 100755 index 00000000..eed23384 --- /dev/null +++ b/tasks/validate_rbac_token.rb @@ -0,0 +1,78 @@ +#!/opt/puppetlabs/puppet/bin/ruby +# frozen_string_literal: true + +require 'uri' +require 'net/https' +require 'json' +require 'etc' +require 'puppet' + +# Class to check an rbac token is valid +class ValidateRbacToken + def initialize(params) + @token_file = params['token_file'] + end + + def execute! + token_file = @token_file || File.join(Etc.getpwuid.dir, '.puppetlabs', 'token') + + uri = URI("https://#{Puppet.settings[:certname]}:4433/rbac-api/v2/auth/token/authenticate") + https = https_object(uri: uri) + request = request_object(token_file: token_file) + + resp = https.request(request) + + if resp.code == '200' + puts 'RBAC token is valid' + exit 0 + else + body = JSON.parse(resp.body) + case resp.code + when '401', '403' + puts "#{resp.code} #{body['kind']}: " \ + "Check your API token at #{token_file}.\n" \ + "Please refresh your token or provide an alternate file.\n" \ + "See https://www.puppet.com/docs/pe/latest/rbac_token_auth_intro for more details.\n" + else + puts "Error validating token: #{resp.code} #{body['kind']}" + puts body['msg'] + end + + exit 1 + end + end + + def request_object(token_file:) + token = File.read(token_file) + body = { + 'token' => token.chomp, + 'update_last_activity?' => false, + }.to_json + + request = Net::HTTP::Post.new('/rbac-api/v2/auth/token/authenticate') + request['Content-Type'] = 'application/json' + request.body = body + + request + end + + def https_object(uri:) + https = Net::HTTP.new(uri.host, uri.port) + https.use_ssl = true + https.cert = OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert])) + https.key = OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey])) + https.verify_mode = OpenSSL::SSL::VERIFY_PEER + https.ca_file = Puppet.settings[:localcacert] + + https + end +end + +# Run the task unless an environment flag has been set, signaling not to. The +# environment flag is used to disable auto-execution and enable Ruby unit +# testing of this task. +unless ENV['RSPEC_UNIT_TEST_MODE'] + Puppet.initialize_settings + validate = ValidateRbacToken.new(JSON.parse(STDIN.read)) + validate.execute! +end