From b9170ef68d45b97f581fdf9ac34718e6c1e25e74 Mon Sep 17 00:00:00 2001 From: Jacob Buchanan Date: Thu, 10 Oct 2024 11:09:05 +0100 Subject: [PATCH 1/5] (PE-39411) Rewrite infra upgrade error to be more useful. Add validate_rbac_token task to check if a given token file is valid --- tasks/puppet_infra_upgrade.rb | 3 +- tasks/validate_rbac_token.json | 13 ++++++ tasks/validate_rbac_token.rb | 78 ++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tasks/validate_rbac_token.json create mode 100755 tasks/validate_rbac_token.rb 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..87e62a1d --- /dev/null +++ b/tasks/validate_rbac_token.json @@ -0,0 +1,13 @@ +{ + "description": "Get and save an rbac token for the root user, admin rbac user", + "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..ccac57ad --- /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}. " + + "An alternate token file can be specified using the token_file param. \n\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 From b208aff4384e638e2eec219b64ddb3016c02b7ca Mon Sep 17 00:00:00 2001 From: Jacob Buchanan Date: Thu, 10 Oct 2024 15:58:55 +0100 Subject: [PATCH 2/5] (PE-39411) Validate RBAC token on upgrade if required for compiler upgrades --- plans/upgrade.pp | 7 +++++++ tasks/validate_rbac_token.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) 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/tasks/validate_rbac_token.rb b/tasks/validate_rbac_token.rb index ccac57ad..85b3c4f9 100755 --- a/tasks/validate_rbac_token.rb +++ b/tasks/validate_rbac_token.rb @@ -29,7 +29,7 @@ def execute! body = JSON.parse(resp.body) case resp.code when '401', '403' - puts "#{resp.code} #{body['kind']}: Check your API token at #{token_file}. " + + puts "#{resp.code} #{body['kind']}: \nCheck your API token at #{token_file}. " + "An alternate token file can be specified using the token_file param. \n\n" + "See https://www.puppet.com/docs/pe/latest/rbac_token_auth_intro for more details. \n" else From 8db17e418a09745301da352ea89d5ed6da76fc40 Mon Sep 17 00:00:00 2001 From: Jacob Buchanan Date: Thu, 10 Oct 2024 18:00:30 +0100 Subject: [PATCH 3/5] (maint) Fix lint issues --- plans/util/retrieve_and_upload.pp | 58 +++++++++++++++---------------- tasks/validate_rbac_token.rb | 7 ++-- 2 files changed, 32 insertions(+), 33 deletions(-) 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/validate_rbac_token.rb b/tasks/validate_rbac_token.rb index 85b3c4f9..153fb878 100755 --- a/tasks/validate_rbac_token.rb +++ b/tasks/validate_rbac_token.rb @@ -29,9 +29,9 @@ def execute! body = JSON.parse(resp.body) case resp.code when '401', '403' - puts "#{resp.code} #{body['kind']}: \nCheck your API token at #{token_file}. " + - "An alternate token file can be specified using the token_file param. \n\n" + - "See https://www.puppet.com/docs/pe/latest/rbac_token_auth_intro for more details. \n" + puts "#{resp.code} #{body['kind']}: \nCheck your API token at #{token_file}. \ + An alternate token file can be specified using the token_file param. \n\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'] @@ -65,7 +65,6 @@ def https_object(uri:) https end - end # Run the task unless an environment flag has been set, signaling not to. The From c13c9006692b811a5dfe8e318c8b0941e94af78f Mon Sep 17 00:00:00 2001 From: Jacob Buchanan Date: Fri, 11 Oct 2024 09:24:33 +0100 Subject: [PATCH 4/5] (maint) Regenerate references.md --- REFERENCE.md | 15 +++++++++++++++ tasks/validate_rbac_token.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) 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/tasks/validate_rbac_token.json b/tasks/validate_rbac_token.json index 87e62a1d..6c526643 100644 --- a/tasks/validate_rbac_token.json +++ b/tasks/validate_rbac_token.json @@ -1,5 +1,5 @@ { - "description": "Get and save an rbac token for the root user, admin rbac user", + "description": "Check an RBAC token stored in a file is valid", "parameters": { "token_file": { "type": "Optional[String]", From 43bd9abe064e759f28961fdabc63bb8532e4ee1f Mon Sep 17 00:00:00 2001 From: Jacob Buchanan Date: Mon, 14 Oct 2024 12:56:02 +0100 Subject: [PATCH 5/5] Update tasks/validate_rbac_token.rb Co-authored-by: Neil Anderson --- tasks/validate_rbac_token.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tasks/validate_rbac_token.rb b/tasks/validate_rbac_token.rb index 153fb878..eed23384 100755 --- a/tasks/validate_rbac_token.rb +++ b/tasks/validate_rbac_token.rb @@ -29,9 +29,10 @@ def execute! body = JSON.parse(resp.body) case resp.code when '401', '403' - puts "#{resp.code} #{body['kind']}: \nCheck your API token at #{token_file}. \ - An alternate token file can be specified using the token_file param. \n\n\ - See https://www.puppet.com/docs/pe/latest/rbac_token_auth_intro for more details. \n" + 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']