Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Keyboard interactive authentication for HTTP downloads of boxes #1147

Closed
wants to merge 3 commits into from

3 participants

@gabetax

Say I have a Vagrantfile with:

config.vm.box_url = "https://secure.example.com/private.box"

and https://secure.example.com/ requires basic HTTP authentication. Currenlty when you run vagrant up you will see:

$ vagrant up
[default] Box base was not found. Fetching box from specified URL...
[vagrant] Downloading with Vagrant::Downloaders::HTTP...
[vagrant] Downloading box: https://secure.example.com/private.box
Bad status code: 401

Please verify that the box exists and is accessible. Also verify that
this computer is properly connected to the internet.

You can currently embed a username and password into the URL (as of #521), e.g.

config.vm.box_url = "https://john:mypassword@secure.example.com/private.box"

However, I wouldn't want to commit a login into source control, especially if the logins were employee specific. You can have people work around this by editing the Vagrantfile to temporarily insert their login for running vagrant up, or you could have them separately download and vagrant add the box, but both of these methods are inconvenient. Ideally I would like to be prompted for my login information if Vagrant::Downloaders::HTTP encounters a 401 response.

@pbrisbin

The vagrant file is just ruby, so you could do something like get the credentials from the environment via the ENV hash or require a (git)ignored file which sets some variables used in the Vagrantfile.

At my work, we YAML::load a file that sets a lot of employee-specific options used in the Vagrantfile.

@gabetax

Loading environment-specific data from the Vagrantfile is also a great option I hadn't thought of, but prompting for a login seems like something that the vagrant UI should ideally do. If there's no objections or concerns with adding this feature, I can write it in. It seems the appropriate place to do this would be to rescue Errors::DownloaderHTTPStatusError in https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/action/builtin/box_add.rb and prompt for the username and password with env[:ui].

The only other issue is that it does not appear to be an easy cross platform/interpreter way to prompt for a password and not have it echo out. It seems that even thor doesn't have this. Most people recommend using HighLine, which supports password prompts for MRI, Windows, and JRuby, but I don't want to force a new dependency. We could also just accept the password being echoed out, or cover unix platforms on MRI with a `stty -echo` if $stdout.tty? call.

gabetax added some commits
@gabetax gabetax Use Errors::DownloaderHTTPUnauthorized for unauthorized HTTP requests
Error message will display the URL with the HTTP password replaced with "<secret>".
94b3af3
@gabetax gabetax Add ask_secret to Vagrant::UI::Basic
Ruby unfortunately have a standard method for prompting for passwords without displaying the output. The methodologies change between UNIX platforms with `stty` available, Windows, and jRuby, and handling all cases can get complicated. https://gist.github.com/2040373 gives an example of how to cope for other platforms. The 'highline' gem supports doing this, but is an extra dependency that may be undesired.

Due to these complications, I've stuck to just using the `stty` approach. If it fails, it will fallback to a standard `#ask`, but warning the user that their input will be displayed.

`#ask` already does not appear jRuby compatible, which fails the `$stdin.tty?` test.
c4f4d66
@gabetax gabetax `box add`: Keyboard interactive prompt for HTTP login if unauthorized e188de0
@gabetax

We could add a counter here to give up after N failed attempts.

@gabetax

This warning could be moved to the en.yml file, but I didn't see any good candidates for where in the hierarchy to put it. I could put it under "general", or make a new "ui" namespace is preferred.

@mitchellh
Owner

Loading in the Vagrantfile is the preferred way here. In the latest version, it uses curl under the covers and it'll give you a nice error if you don't set this up properly. :)

@mitchellh mitchellh closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 20, 2012
  1. @gabetax

    Use Errors::DownloaderHTTPUnauthorized for unauthorized HTTP requests

    gabetax authored
    Error message will display the URL with the HTTP password replaced with "<secret>".
  2. @gabetax

    Add ask_secret to Vagrant::UI::Basic

    gabetax authored
    Ruby unfortunately have a standard method for prompting for passwords without displaying the output. The methodologies change between UNIX platforms with `stty` available, Windows, and jRuby, and handling all cases can get complicated. https://gist.github.com/2040373 gives an example of how to cope for other platforms. The 'highline' gem supports doing this, but is an extra dependency that may be undesired.
    
    Due to these complications, I've stuck to just using the `stty` approach. If it fails, it will fallback to a standard `#ask`, but warning the user that their input will be displayed.
    
    `#ask` already does not appear jRuby compatible, which fails the `$stdin.tty?` test.
  3. @gabetax
This page is out of date. Refresh to see the latest.
View
12 lib/vagrant/action/builtin/box_add.rb
@@ -21,7 +21,17 @@ def call(env)
# access it.
@temp_path = env[:tmp_path].join("box" + Time.now.to_i.to_s)
File.open(@temp_path, Vagrant::Util::Platform.tar_file_options) do |f|
- downloader.download!(env[:box_url], f)
+ begin
+ downloader.download!(env[:box_url], f)
+ rescue Errors::DownloaderHTTPUnauthorized => e
+ env[:ui].warn e
+
+ uri = URI.parse(env[:box_url])
+ uri.user = env[:ui].ask I18n.t("vagrant.actions.box.download.http_username")
+ uri.password = env[:ui].ask_secret I18n.t("vagrant.actions.box.download.http_password")
+ env[:box_url] = uri.to_s
+ retry
+ end
end
# Add the box
View
9 lib/vagrant/downloaders/http.rb
@@ -27,7 +27,7 @@ def download!(source_url, destination_file)
end
http.start do |h|
- @ui.info I18n.t("vagrant.downloaders.http.download", :url => source_url)
+ @ui.info I18n.t("vagrant.downloaders.http.download", :url => uri_without_userinfo(uri))
headers = nil
if uri.user && uri.password
@@ -40,6 +40,8 @@ def download!(source_url, destination_file)
# TODO: Error on some redirect limit
download!(response["Location"], destination_file)
return
+ elsif response.is_a?(Net::HTTPUnauthorized)
+ raise Errors::DownloaderHTTPUnauthorized, :url => uri_without_userinfo(uri)
elsif !response.is_a?(Net::HTTPOK)
raise Errors::DownloaderHTTPStatusError, :status => response.code
end
@@ -96,6 +98,11 @@ def resolve_proxy(source_uri)
URI.parse(proxy_string)
end
+
+ # Mask HTTP authentication information from URI's string output
+ def uri_without_userinfo (source_uri)
+ source_uri.to_s.gsub(":#{source_uri.password}@", ':<secret>@')
+ end
end
end
end
View
4 lib/vagrant/errors.rb
@@ -190,6 +190,10 @@ class DownloaderHTTPStatusError < VagrantError
error_key(:status_error, "vagrant.downloaders.http")
end
+ class DownloaderHTTPUnauthorized < VagrantError
+ error_key(:unauthorized, "vagrant.downloaders.http")
+ end
+
class EnvironmentNonExistentCWD < VagrantError
status_code(75)
error_key(:environment_non_existent_cwd)
View
18 lib/vagrant/ui.rb
@@ -40,6 +40,8 @@ def ask(*args)
# Silent can't do this, obviously.
raise Errors::UIExpectsTTY
end
+
+ alias_method :ask_secret, :ask
end
# This is a UI implementation that outputs the text as is. It
@@ -80,6 +82,22 @@ def ask(message, opts=nil)
input.chomp
end
+ # `ask` for input without input displayed to the screen.
+ #
+ # Input will be supressed for UNIX-like environments only. Un-supported
+ # environments will display a warning, but will still allow
+ # the user to enter input.
+ def ask_secret(message, opts = nil)
+ if $stdin.tty? && system('stty -echo -icanon')
+ response = ask(message, opts)
+ system('stty echo icanon')
+ puts
+ response
+ else
+ ask("(Warning: input will be displayed) #{message}" , opts)
+ end
+ end
+
# This is used to output progress reports to the UI.
# Send this method progress/total and it will output it
# to the UI. Send `clear_line` to clear the line to show
View
4 templates/locales/en.yml
@@ -616,6 +616,8 @@ en:
with: "Downloading with %{class}..."
cleaning: "Cleaning up downloaded box..."
unknown_type: "Unknown or unsupported URI type given for box download."
+ http_username: "HTTP Username: "
+ http_password: "HTTP Password: "
verify:
verifying: "Verifying box..."
failed: |-
@@ -663,6 +665,8 @@ en:
Please verify that the box exists and is accessible. Also verify that
this computer is properly connected to the internet.
+ unauthorized: |-
+ %{url} requires HTTP authentication to continue.
hosts:
bsd:
Something went wrong with that request. Please try again.