From 2fa539e4996bf17467e8f4c38d23cd6fc14ebfb8 Mon Sep 17 00:00:00 2001 From: Allison Larson Date: Wed, 17 May 2023 15:55:25 -0700 Subject: [PATCH] Salt: Download & verify shasum of default bootstrap-salt file --- plugins/provisioners/salt/bootstrap-salt.ps1 | 6 -- plugins/provisioners/salt/bootstrap-salt.sh | 17 ------ .../provisioners/salt/bootstrap_downloader.rb | 58 +++++++++++++++++++ plugins/provisioners/salt/errors.rb | 6 +- plugins/provisioners/salt/provisioner.rb | 9 ++- templates/locales/en.yml | 2 + .../salt/bootstrap_downloader_test.rb | 44 ++++++++++++++ .../provisioners/salt/provisioner_test.rb | 1 - 8 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 plugins/provisioners/salt/bootstrap_downloader.rb create mode 100644 test/unit/plugins/provisioners/salt/bootstrap_downloader_test.rb diff --git a/plugins/provisioners/salt/bootstrap-salt.ps1 b/plugins/provisioners/salt/bootstrap-salt.ps1 index f97ec6c6d..48715a49f 100644 --- a/plugins/provisioners/salt/bootstrap-salt.ps1 +++ b/plugins/provisioners/salt/bootstrap-salt.ps1 @@ -1,11 +1,5 @@ -# Powershell supports only TLS 1.0 by default. Add support for TLS 1.2 -[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls12' - # Define script root for PowerShell 2.0 $ScriptRoot = Split-Path $script:MyInvocation.MyCommand.Path -# Download the upstream bootstrap script -(New-Object System.Net.WebClient).DownloadFile('https://winbootstrap.saltproject.io', "${ScriptRoot}\bootstrap_salt_upstream.ps1") - # Run the upstream bootstrap script with passthrough arguments & "${ScriptRoot}\bootstrap_salt_upstream.ps1" @args diff --git a/plugins/provisioners/salt/bootstrap-salt.sh b/plugins/provisioners/salt/bootstrap-salt.sh index a95dd87b0..bdc763fa3 100755 --- a/plugins/provisioners/salt/bootstrap-salt.sh +++ b/plugins/provisioners/salt/bootstrap-salt.sh @@ -1,21 +1,4 @@ #!/bin/sh - - -cd `mktemp -d` - -# We just download the bootstrap script by default and execute that. -if [ -x /usr/bin/fetch ]; then - /usr/bin/fetch -o bootstrap-salt.sh https://bootstrap.saltproject.io -elif [ -x /usr/bin/curl ]; then - /usr/bin/curl --silent --show-error -L --output bootstrap-salt.sh https://bootstrap.saltproject.io -elif [ -x /usr/bin/wget ]; then - /usr/bin/wget -O bootstrap-salt.sh https://bootstrap.saltproject.io -elif [ "2" = `python -c 'import sys; sys.stdout.write(str(sys.version_info.major))'` ]; then - # TODO: remove after there is no supported distros with Python 2 - python -c 'import urllib; urllib.urlretrieve("https://bootstrap.saltproject.io", "bootstrap-salt.sh")' -else - python -c 'import urllib.request; urllib.request.urlretrieve("https://bootstrap.saltproject.io", "bootstrap-salt.sh")' -fi - if [ -e bootstrap-salt.sh ]; then sh bootstrap-salt.sh "$@" else diff --git a/plugins/provisioners/salt/bootstrap_downloader.rb b/plugins/provisioners/salt/bootstrap_downloader.rb new file mode 100644 index 000000000..7f4060e5e --- /dev/null +++ b/plugins/provisioners/salt/bootstrap_downloader.rb @@ -0,0 +1,58 @@ +require 'open-uri' +require 'digest' +require_relative "./errors" + +module VagrantPlugins + module Salt + class BootstrapDownloader + WINDOWS_URL = "https://winbootstrap.saltproject.io" + URL = "https://bootstrap.saltproject.io" + SHA256_SUFFIX = "sha256" + + def initialize(guest) + @guest = guest + @logger = Log4r::Logger.new("vagrant::salt::bootstrap_downloader") + end + + def source_url + @guest == :windows ? WINDOWS_URL : URL + end + + def get_bootstrap_script + @logger.debug "Downloading bootstrap script from #{source_url}" + script_file = download(source_url) + + verify_sha256(script_file) + + @logger.info "Downloaded and verified salt-bootstrap script" + script_file + end + + def verify_sha256(script) + @logger.debug "Downloading sha256 file from #{source_url}/#{SHA256_SUFFIX}" + sha256_file = download("#{source_url}/#{SHA256_SUFFIX}") + sha256 = extract_sha256(sha256_file.read) + sha256_file.close + + @logger.debug "Computing sha256 value from script file" + computed_sha256 = Digest::SHA256.hexdigest(script.read) + script.rewind + + @logger.debug "Comparing sha256 values" + if computed_sha256 != sha256 + @logger.debug "Mismatched sha256, expected #{sha256} but computed #{computed_sha256}" + raise Salt::Errors::InvalidShasumError, source: source_url, expected_sha: sha256, computed_sha: computed_sha256 + end + @logger.debug "Sha256 values match" + end + + def extract_sha256(text) + text.scan(/\b([a-f0-9]{64})\b/).last.first + end + + def download(url) + URI(url).open + end + end + end +end diff --git a/plugins/provisioners/salt/errors.rb b/plugins/provisioners/salt/errors.rb index 012d5df3d..f259f78b2 100644 --- a/plugins/provisioners/salt/errors.rb +++ b/plugins/provisioners/salt/errors.rb @@ -6,6 +6,10 @@ module VagrantPlugins class SaltError < Vagrant::Errors::VagrantError error_namespace("vagrant.provisioners.salt") end + + class InvalidShasumError < SaltError + error_key(:salt_invalid_shasum_error) + end end end -end \ No newline at end of file +end diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index befe38902..15b1bb6d0 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -1,4 +1,5 @@ require 'json' +require_relative "bootstrap_downloader" module VagrantPlugins module Salt @@ -274,11 +275,9 @@ module VagrantPlugins if @config.bootstrap_script bootstrap_abs_path = expanded_path(@config.bootstrap_script) else - if @machine.config.vm.communicator == :winrm - bootstrap_abs_path = Pathname.new("../bootstrap-salt.ps1").expand_path(__FILE__) - else - bootstrap_abs_path = Pathname.new("../bootstrap-salt.sh").expand_path(__FILE__) - end + bootstrap_downloader = BootstrapDownloader.new(@machine.config.vm.guest) + bootstrap_script = bootstrap_downloader.get_bootstrap_script + bootstrap_abs_path = expanded_path(bootstrap_script.path) end return bootstrap_abs_path diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4a18ff7f6..422d8bf6a 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -3077,6 +3077,8 @@ en: You must set `python_version` as an integer or string that represents an integer. version_type_missing: |- You must set the option `install_type` when specifying a `version`. + salt_invalid_shasum_error: |- + The bootstrap-salt script downloaded from '%{source}' couldn't be verified. Expected SHA256 '%{expected_sha}', but computed '%{computed_sha}' pushes: file: diff --git a/test/unit/plugins/provisioners/salt/bootstrap_downloader_test.rb b/test/unit/plugins/provisioners/salt/bootstrap_downloader_test.rb new file mode 100644 index 000000000..67e24fa84 --- /dev/null +++ b/test/unit/plugins/provisioners/salt/bootstrap_downloader_test.rb @@ -0,0 +1,44 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/salt/bootstrap_downloader") + +describe VagrantPlugins::Salt::BootstrapDownloader do + include_context "unit" + + subject { described_class.new(:computer) } + + describe "verify_sha256" do + let(:sha256) { "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } + let(:bad_sha256) { "ffffffffffffffff" } + let(:sha256_file) { StringIO.new("#{sha256} test_script_value") } + let(:test_script) { StringIO.new("test_script_value") } + + it "does not error if both shas match" do + allow(subject).to receive(:download).and_return(sha256_file) + allow(Digest::SHA256).to receive(:hexdigest).and_return(sha256) + + expect{subject.verify_sha256(test_script)}.to_not raise_error + end + + it "raises an exception if shas don't match" do + allow(subject).to receive(:download).and_return(sha256_file) + allow(Digest::SHA256).to receive(:hexdigest).and_return(bad_sha256) + + expect{subject.verify_sha256(test_script)}.to raise_error(VagrantPlugins::Salt::Errors::InvalidShasumError) { |err| + expect(err.message).to include("The bootstrap-salt script downloaded from '#{described_class::URL}' couldn't be verified.") + expect(err.message).to include("Expected SHA256 '#{sha256}', but computed '#{bad_sha256}'") + } + end + + it "raises the correct error message to a windows guest" do + subject = described_class.new(:windows) + allow(subject).to receive(:download).and_return(sha256_file) + allow(Digest::SHA256).to receive(:hexdigest).and_return(bad_sha256) + + expect{subject.verify_sha256(test_script)}.to raise_error(VagrantPlugins::Salt::Errors::InvalidShasumError) { |err| + expect(err.message).to include("The bootstrap-salt script downloaded from '#{described_class::WINDOWS_URL}' couldn't be verified.") + } + end + end +end + diff --git a/test/unit/plugins/provisioners/salt/provisioner_test.rb b/test/unit/plugins/provisioners/salt/provisioner_test.rb index 2da55d8a7..51bcaba7f 100644 --- a/test/unit/plugins/provisioners/salt/provisioner_test.rb +++ b/test/unit/plugins/provisioners/salt/provisioner_test.rb @@ -182,5 +182,4 @@ describe VagrantPlugins::Salt::Provisioner do end end end - end