diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 53cb05ee8..1a53918a9 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -76,8 +76,18 @@ module Vagrant end end + # Call the hook to transform URLs into authenticated URLs. + # In the case we don't have a plugin that does this, then it + # will just return the same URLs. + hook_env = env[:hook].call( + :authenticate_box_url, box_urls: url.dup) + authed_urls = hook_env[:box_urls] + if !authed_urls || authed_urls.length != url.length + raise "Bad box authentication hook, did not generate proper results." + end + # Test if any of our URLs point to metadata - is_metadata_results = url.map do |u| + is_metadata_results = authed_urls.map do |u| begin metadata_url?(u, env) rescue Errors::DownloaderError => e @@ -105,7 +115,8 @@ module Vagrant end if is_metadata - add_from_metadata(url.first, env, expanded) + url = [url.first, authed_urls.first] + add_from_metadata(url, env, expanded) else add_direct(url, env) end @@ -147,7 +158,10 @@ module Vagrant # Adds a box given that the URL is a metadata document. # - # @param [String] url The URL of the metadata for the box to add. + # @param [String | Array] url The URL of the metadata for + # the box to add. If this is an array, then it must be a two-element + # array where the first element is the original URL and the second + # element is an authenticated URL. # @param [Hash] env # @param [Bool] expanded True if the metadata URL was expanded with # a Atlas server URL. @@ -156,6 +170,15 @@ module Vagrant provider = env[:box_provider] provider = Array(provider) if provider version = env[:box_version] + + authenticated_url = url + if url.is_a?(Array) + # We have both a normal URL and "authenticated" URL. Split + # them up. + authenticated_url = url[1] + url = url[0] + end + display_original_url = Util::CredentialScrubber.scrub(Array(original_url).first) display_url = Util::CredentialScrubber.scrub(url) @@ -170,7 +193,7 @@ module Vagrant metadata = nil begin metadata_path = download( - url, env, json: true, ui: false) + authenticated_url, env, json: true, ui: false) return if @download_interrupted File.open(metadata_path) do |f| @@ -248,6 +271,16 @@ module Vagrant end provider_url = metadata_provider.url + if provider_url != authenticated_url + # Authenticate the provider URL since we're using auth + hook_env = env[:hook].call(:authenticate_box_url, box_urls: [provider_url]) + authed_urls = hook_env[:box_urls] + if !authed_urls || authed_urls.length != 1 + raise "Bad box authentication hook, did not generate proper results." + end + provider_url = authed_urls[0] + end + box_add( [[provider_url, metadata_provider.url]], metadata.name, @@ -404,7 +437,7 @@ module Vagrant downloader_options[:box_extra_download_options] = env[:box_extra_download_options] d = Util::Downloader.new(url, temp_path, downloader_options) - env[:hook].call(:authenticate_box_url, downloader: d) + env[:hook].call(:authenticate_box_downloader, downloader: d) d end @@ -412,7 +445,7 @@ module Vagrant opts[:ui] = true if !opts.key?(:ui) d = downloader(url, env, **opts) - env[:hook].call(:authenticate_box_url, downloader: d) + env[:hook].call(:authenticate_box_downloader, downloader: d) # Download the box to a temporary path. We store the temporary # path as an instance variable so that the `#recover` method can @@ -456,7 +489,7 @@ module Vagrant # @return [Boolean] true if metadata def metadata_url?(url, env) d = downloader(url, env, json: true, ui: false) - env[:hook].call(:authenticate_box_url, downloader: d) + env[:hook].call(:authenticate_box_downloader, downloader: d) # If we're downloading a file, cURL just returns no # content-type (makes sense), so we just test if it is JSON diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index 0faf570e9..e00fac2d9 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -136,7 +136,7 @@ module Vagrant opts = { headers: ["Accept: application/json"] }.merge(download_options) d = Util::Downloader.new(url, tf.path, opts) if @hook - @hook.call(:authenticate_box_url, downloader: d) + @hook.call(:authenticate_box_downloader, downloader: d) end d.download! BoxMetadata.new(File.open(tf.path, "r")) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 7bf9f6f5b..9e0471f5b 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -317,6 +317,12 @@ module Vagrant metadata_url_file = box_directory.join("metadata_url") metadata_url = metadata_url_file.read if metadata_url_file.file? + if metadata_url && @hook + hook_env = @hook.call( + :authenticate_box_url, box_urls: [metadata_url]) + metadata_url = hook_env[:box_urls].first + end + return Box.new( name, provider, version_dir_map[v.to_s], provider_dir, metadata_url: metadata_url, hook: @hook diff --git a/plugins/commands/cloud/auth/middleware/add_authentication.rb b/plugins/commands/cloud/auth/middleware/add_authentication.rb index 09e6ac116..9d826617e 100644 --- a/plugins/commands/cloud/auth/middleware/add_authentication.rb +++ b/plugins/commands/cloud/auth/middleware/add_authentication.rb @@ -1,8 +1,6 @@ require "cgi" require "uri" -require "vagrant/util/credential_scrubber" - require Vagrant.source_root.join("plugins/commands/cloud/client/client") module VagrantPlugins @@ -35,34 +33,47 @@ module VagrantPlugins def call(env) client = Client.new(env[:env]) token = client.token - target_url = URI.parse(env[:downloader].source) - Vagrant::Util::CredentialScrubber.sensitive(token) - if target_url.host != TARGET_HOST && REPLACEMENT_HOSTS.include?(target_url.host) + env[:box_urls].map! do |url| begin - target_url.host = TARGET_HOST - target_url = target_url.to_s + u = URI.parse(url) + if u.host != TARGET_HOST && REPLACEMENT_HOSTS.include?(u.host) + u.host = TARGET_HOST + u.to_s + else + url + end rescue URI::Error - # if there is an error, use current target_url + url end end server_uri = URI.parse(Vagrant.server_url.to_s) + if token && !server_uri.host.to_s.empty? - if target_url.host == server_uri.host - if server_uri.host != TARGET_HOST && !self.class.custom_host_notified? - env[:ui].warn(I18n.t("cloud_command.middleware.authentication.different_target", - custom_host: server_uri.host, known_host: TARGET_HOST) + "\n") - sleep CUSTOM_HOST_NOTIFY_WAIT - self.class.custom_host_notified! + env[:box_urls].map! do |url| + u = URI.parse(url) + + if u.host == server_uri.host + if server_uri.host != TARGET_HOST && !self.class.custom_host_notified? + env[:ui].warn(I18n.t("cloud_command.middleware.authentication.different_target", + custom_host: server_uri.host, known_host: TARGET_HOST) + "\n") + sleep CUSTOM_HOST_NOTIFY_WAIT + self.class.custom_host_notified! + end + + q = CGI.parse(u.query || "") + + current = q["access_token"] + if current && current.empty? + q["access_token"] = token + end + + u.query = URI.encode_www_form(q) end - if env[:downloader].headers && !env[:downloader].headers.any? { |h| h.include?("Authorization") } - env[:downloader].headers << "Authorization: Bearer #{token}" - end + u.to_s end - - env[:downloader] end @app.call(env) diff --git a/plugins/commands/cloud/auth/middleware/add_downloader_authentication.rb b/plugins/commands/cloud/auth/middleware/add_downloader_authentication.rb new file mode 100644 index 000000000..b99a01916 --- /dev/null +++ b/plugins/commands/cloud/auth/middleware/add_downloader_authentication.rb @@ -0,0 +1,50 @@ +require "cgi" +require "uri" + +require "vagrant/util/credential_scrubber" +require_relative "./add_authentication" + +require Vagrant.source_root.join("plugins/commands/cloud/client/client") + +module VagrantPlugins + module CloudCommand + class AddDownloaderAuthentication < AddAuthentication + + def call(env) + client = Client.new(env[:env]) + token = client.token + target_url = URI.parse(env[:downloader].source) + Vagrant::Util::CredentialScrubber.sensitive(token) + + if target_url.host != TARGET_HOST && REPLACEMENT_HOSTS.include?(target_url.host) + begin + target_url.host = TARGET_HOST + target_url = target_url.to_s + rescue URI::Error + # if there is an error, use current target_url + end + end + + server_uri = URI.parse(Vagrant.server_url.to_s) + if token && !server_uri.host.to_s.empty? + if target_url.host == server_uri.host + if server_uri.host != TARGET_HOST && !self.class.custom_host_notified? + env[:ui].warn(I18n.t("cloud_command.middleware.authentication.different_target", + custom_host: server_uri.host, known_host: TARGET_HOST) + "\n") + sleep CUSTOM_HOST_NOTIFY_WAIT + self.class.custom_host_notified! + end + + if env[:downloader].headers && !env[:downloader].headers.any? { |h| h.include?("Authorization") } + env[:downloader].headers << "Authorization: Bearer #{token}" + end + end + + env[:downloader] + end + + @app.call(env) + end.freeze + end + end +end diff --git a/plugins/commands/cloud/plugin.rb b/plugins/commands/cloud/plugin.rb index b5fc8cb75..cce50a01c 100644 --- a/plugins/commands/cloud/plugin.rb +++ b/plugins/commands/cloud/plugin.rb @@ -22,6 +22,11 @@ module VagrantPlugins hook.prepend(AddAuthentication) end + action_hook(:cloud_authenticated_boxes, :authenticate_box_downloader) do |hook| + require_relative "auth/middleware/add_downloader_authentication" + hook.prepend(AddDownloaderAuthentication) + end + protected def self.init!