diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 450e21fc1..c4fc253d1 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -452,9 +452,11 @@ module Vagrant info[:keys_only] ||= @config.ssh.default.keys_only info[:verify_host_key] ||= @config.ssh.default.verify_host_key info[:username] ||= @config.ssh.default.username + info[:remote_user] ||= @config.ssh.default.remote_user info[:compression] ||= @config.ssh.default.compression info[:dsa_authentication] ||= @config.ssh.default.dsa_authentication info[:extra_args] ||= @config.ssh.default.extra_args + info[:config] ||= @config.ssh.default.config # We set overrides if they are set. These take precedence over # provider-returned data. @@ -466,7 +468,9 @@ module Vagrant info[:dsa_authentication] = @config.ssh.dsa_authentication info[:username] = @config.ssh.username if @config.ssh.username info[:password] = @config.ssh.password if @config.ssh.password + info[:remote_user] = @config.ssh.remote_user if @config.ssh.remote_user info[:extra_args] = @config.ssh.extra_args if @config.ssh.extra_args + info[:config] = @config.ssh.config if @config.ssh.config # We also set some fields that are purely controlled by Vagrant info[:forward_agent] = @config.ssh.forward_agent diff --git a/lib/vagrant/util/ssh.rb b/lib/vagrant/util/ssh.rb index 43d1dcfbf..e2b0ffaba 100644 --- a/lib/vagrant/util/ssh.rb +++ b/lib/vagrant/util/ssh.rb @@ -178,6 +178,10 @@ module Vagrant "-o", "ForwardX11Trusted=yes"] end + if ssh_info[:config] + command_options += ["-F", ssh_info[:config]] + end + if ssh_info[:proxy_command] command_options += ["-o", "ProxyCommand=#{ssh_info[:proxy_command]}"] end diff --git a/plugins/commands/ssh_config/command.rb b/plugins/commands/ssh_config/command.rb index 492352f36..8b0d2830a 100644 --- a/plugins/commands/ssh_config/command.rb +++ b/plugins/commands/ssh_config/command.rb @@ -55,6 +55,7 @@ module VagrantPlugins proxy_command: ssh_info[:proxy_command], ssh_command: ssh_info[:ssh_command], forward_env: ssh_info[:forward_env], + config: ssh_info[:config], } # Render the template and output directly to STDOUT diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index e9b0cce8f..c92e39980 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -414,6 +414,14 @@ module VagrantPlugins connect_opts[:proxy] = Net::SSH::Proxy::Command.new(ssh_info[:proxy_command]) end + if ssh_info[:config] + connect_opts[:config] = ssh_info[:config] + end + + if ssh_info[:remote_user] + connect_opts[:remote_user] = ssh_info[:remote_user] + end + @logger.info("Attempting to connect to SSH...") @logger.info(" - Host: #{ssh_info[:host]}") @logger.info(" - Port: #{ssh_info[:port]}") diff --git a/plugins/kernel_v2/config/ssh_connect.rb b/plugins/kernel_v2/config/ssh_connect.rb index c4c6fec0e..897046bad 100644 --- a/plugins/kernel_v2/config/ssh_connect.rb +++ b/plugins/kernel_v2/config/ssh_connect.rb @@ -3,6 +3,7 @@ module VagrantPlugins class SSHConnectConfig < Vagrant.plugin("2", :config) attr_accessor :host attr_accessor :port + attr_accessor :config attr_accessor :private_key_path attr_accessor :username attr_accessor :password @@ -13,10 +14,12 @@ module VagrantPlugins attr_accessor :compression attr_accessor :dsa_authentication attr_accessor :extra_args + attr_accessor :remote_user def initialize @host = UNSET_VALUE @port = UNSET_VALUE + @config = UNSET_VALUE @private_key_path = UNSET_VALUE @username = UNSET_VALUE @password = UNSET_VALUE @@ -27,6 +30,7 @@ module VagrantPlugins @compression = UNSET_VALUE @dsa_authentication = UNSET_VALUE @extra_args = UNSET_VALUE + @remote_user = UNSET_VALUE end def finalize! @@ -42,11 +46,20 @@ module VagrantPlugins @compression = true if @compression == UNSET_VALUE @dsa_authentication = true if @dsa_authentication == UNSET_VALUE @extra_args = nil if @extra_args == UNSET_VALUE + @config = nil if @config == UNSET_VALUE if @private_key_path && !@private_key_path.is_a?(Array) @private_key_path = [@private_key_path] end + if @remote_user == UNSET_VALUE + if @username + @remote_user = @username + else + @remote_user = nil + end + end + if @paranoid @verify_host_key = @paranoid end @@ -84,6 +97,15 @@ module VagrantPlugins end end + if @config + config_path = File.expand_path(@config, machine.env.root_path) + if !File.file?(config_path) + errors << I18n.t( + "vagrant.config.ssh.ssh_config_missing", + path: @config) + end + end + if @paranoid machine.env.ui.warn(I18n.t("vagrant.config.ssh.paranoid_deprecated")) end diff --git a/plugins/synced_folders/rsync/helper.rb b/plugins/synced_folders/rsync/helper.rb index c190cf3c1..5604b83cf 100644 --- a/plugins/synced_folders/rsync/helper.rb +++ b/plugins/synced_folders/rsync/helper.rb @@ -77,6 +77,11 @@ module VagrantPlugins proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' " end + ssh_config_file = "" + if ssh_info[:config] + ssh_config_file = "-F #{ssh_info[:config]}" + end + # Create the path for the control sockets. We used to do this # in the machine data dir but this can result in paths that are # too long for unix domain sockets. @@ -91,6 +96,7 @@ module VagrantPlugins "ssh", "-p", "#{ssh_info[:port]}", "-o", "LogLevel=#{log_level}", proxy_command, + ssh_config_file, control_options, ] diff --git a/templates/commands/ssh_config/config.erb b/templates/commands/ssh_config/config.erb index 8aa244cf0..b0cdbeedc 100644 --- a/templates/commands/ssh_config/config.erb +++ b/templates/commands/ssh_config/config.erb @@ -1,4 +1,7 @@ Host <%= host_key %> +<% if config -%> + Include <%= config %> +<% end -%> HostName <%= ssh_host %> User <%= ssh_user %> Port <%= ssh_port %> diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 3890182b6..5af7bdb11 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1810,6 +1810,7 @@ en: paranoid_deprecated: |- The key `paranoid` is deprecated. Please use `verify_host_key`. Supported values are exactly the same, only the name of the option has changed. + ssh_config_missing: "`config` file must exist: %{path}" triggers: bad_command_warning: |- The command '%{cmd}' was not found for this trigger. diff --git a/test/unit/plugins/commands/ssh_config/command_test.rb b/test/unit/plugins/commands/ssh_config/command_test.rb index d88a39b66..84f593e54 100644 --- a/test/unit/plugins/commands/ssh_config/command_test.rb +++ b/test/unit/plugins/commands/ssh_config/command_test.rb @@ -165,5 +165,18 @@ Host #{machine.name} expect(output).to include('StrictHostKeyChecking ') expect(output).to include('UserKnownHostsFile ') end + + it "includes custom ssh_config path when provided" do + allow(machine).to receive(:ssh_info) { ssh_info.merge(config: "/custom/ssh/config") } + + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + + expect(output).to include("Include /custom/ssh/config") + end end end diff --git a/test/unit/plugins/communicators/ssh/communicator_test.rb b/test/unit/plugins/communicators/ssh/communicator_test.rb index 3b2cb9a32..6a979ecc3 100644 --- a/test/unit/plugins/communicators/ssh/communicator_test.rb +++ b/test/unit/plugins/communicators/ssh/communicator_test.rb @@ -748,6 +748,48 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do communicator.send(:connect) end end + + context "with config configured" do + before do + expect(machine).to receive(:ssh_info).and_return( + host: '127.0.0.1', + port: 2222, + config: './ssh_config', + keys_only: true, + verify_host_key: false + ) + end + + it "has config defined" do + expect(Net::SSH).to receive(:start).with( + anything, anything, hash_including( + config: './ssh_config' + ) + ).and_return(true) + communicator.send(:connect) + end + end + + context "with remote_user configured" do + let(:remote_user) { double("remote_user") } + + before do + expect(machine).to receive(:ssh_info).and_return( + host: '127.0.0.1', + port: 2222, + remote_user: remote_user + ) + end + + it "has remote_user defined" do + expect(Net::SSH).to receive(:start).with( + anything, anything, hash_including( + remote_user: remote_user + ) + ).and_return(true) + communicator.send(:connect) + end + end end describe ".generate_environment_export" do diff --git a/test/unit/plugins/kernel_v2/config/ssh_connect_test.rb b/test/unit/plugins/kernel_v2/config/ssh_connect_test.rb index 0c2772b66..e11386f31 100644 --- a/test/unit/plugins/kernel_v2/config/ssh_connect_test.rb +++ b/test/unit/plugins/kernel_v2/config/ssh_connect_test.rb @@ -3,6 +3,17 @@ require File.expand_path("../../../../base", __FILE__) require Vagrant.source_root.join("plugins/kernel_v2/config/ssh_connect") describe VagrantPlugins::Kernel_V2::SSHConnectConfig do + include_context "unit" + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + subject { described_class.new } describe "#verify_host_key" do @@ -29,4 +40,67 @@ describe VagrantPlugins::Kernel_V2::SSHConnectConfig do expect(subject.verify_host_key).to eq(:always) end end + + describe "#config" do + let(:config_file) { "/path/to/config" } + + before do + # NOTE: The machine instance must be initialized before + # any mocks on File are registered. Otherwise it + # will cause a failure attempting to create the + # instance + machine + allow(File).to receive(:file?). + with(/#{Regexp.escape(config_file)}/). + and_return(true) + end + + it "defaults to nil" do + subject.finalize! + expect(subject.config).to be_nil + end + + it "should return the set path" do + subject.config = config_file + subject.finalize! + expect(subject.config).to eq(config_file) + end + + it "should validate when path exists" do + subject.config = config_file + subject.finalize! + machine + expect(File).to receive(:file?). + with(/#{Regexp.escape(config_file)}/). + and_return(true) + expect(subject.validate(machine)).to be_empty + end + + it "should not validate when path does not exist" do + subject.config = config_file + subject.finalize! + expect(File).to receive(:file?). + with(/#{Regexp.escape(config_file)}/). + and_return(false) + expect(subject.validate(machine)).not_to be_empty + end + end + + describe "#remote_user" do + let(:username) { double("username") } + let(:remote_user) { double("remote_user") } + + it "should default to username value" do + subject.username = username + subject.finalize! + expect(subject.remote_user).to eq(subject.username) + end + + it "should be set to provided value" do + subject.username = username + subject.remote_user = remote_user + subject.finalize! + expect(subject.remote_user).to eq(remote_user) + end + end end diff --git a/test/unit/plugins/kernel_v2/config/ssh_test.rb b/test/unit/plugins/kernel_v2/config/ssh_test.rb index 15e23a7a0..d654da450 100644 --- a/test/unit/plugins/kernel_v2/config/ssh_test.rb +++ b/test/unit/plugins/kernel_v2/config/ssh_test.rb @@ -8,6 +8,7 @@ describe VagrantPlugins::Kernel_V2::SSHConfig do describe "#default" do it "defaults to vagrant username" do subject.finalize! + expect(subject.default.port).to eq(22) expect(subject.default.username).to eq("vagrant") end end diff --git a/test/unit/plugins/synced_folders/rsync/helper_test.rb b/test/unit/plugins/synced_folders/rsync/helper_test.rb index 3e6c1274a..61f83eed0 100644 --- a/test/unit/plugins/synced_folders/rsync/helper_test.rb +++ b/test/unit/plugins/synced_folders/rsync/helper_test.rb @@ -319,5 +319,15 @@ describe VagrantPlugins::SyncedFolderRSync::RsyncHelper do subject.rsync_single(machine, ssh_info, opts) end + + it "includes custom ssh config when set" do + ssh_info[:config] = "/path/to/ssh/config" + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + ssh_config_args = "-F /path/to/ssh/config" + expect(args.any?{|a| a.include?(ssh_config_args)}).to be_truthy + result + end + subject.rsync_single(machine, ssh_info, opts) + end end end diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 02646e353..e956636b6 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -847,6 +847,16 @@ describe Vagrant::Machine do expect(instance.ssh_info[:private_key_path]).to eql([path]) end + it "should return the remote_user when set" do + instance.config.ssh.remote_user = "remote-user" + expect(instance.ssh_info[:remote_user]).to eq("remote-user") + end + + it "should return the config when set" do + instance.config.ssh.config = "/path/to/ssh_config" + expect(instance.ssh_info[:config]).to eq("/path/to/ssh_config") + end + context "with no data dir" do let(:base) { true } let(:data_dir) { nil } diff --git a/test/unit/vagrant/util/ssh_test.rb b/test/unit/vagrant/util/ssh_test.rb index a8617c6bc..9d84f0225 100644 --- a/test/unit/vagrant/util/ssh_test.rb +++ b/test/unit/vagrant/util/ssh_test.rb @@ -236,6 +236,26 @@ describe Vagrant::Util::SSH do end end + context "when config is provided" do + let(:ssh_info) {{ + host: "localhost", + port: 2222, + username: "vagrant", + config: "/path/to/config" + }} + + it "enables ssh config loading" do + allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil) + expect(Vagrant::Util::SafeExec).to receive(:exec) do |exe_path, *args| + expect(exe_path).to eq(ssh_path) + config_options = ["-F", "/path/to/config"] + expect(args & config_options).to eq(config_options) + end + + expect(described_class.exec(ssh_info)).to eq(nil) + end + end + context "with subprocess enabled" do let(:ssh_info) {{ host: "localhost", diff --git a/website/source/docs/vagrantfile/ssh_settings.html.md b/website/source/docs/vagrantfile/ssh_settings.html.md index 12227d7bd..1e457f3b4 100644 --- a/website/source/docs/vagrantfile/ssh_settings.html.md +++ b/website/source/docs/vagrantfile/ssh_settings.html.md @@ -18,54 +18,38 @@ defaults are typically fine, but you can fine tune whatever you would like. ## Available Settings -* `config.ssh.username` (string) - This sets the username that Vagrant will SSH -as by default. Providers are free to override this if they detect a more -appropriate user. By default this is "vagrant", since that is what most -public boxes are made as. +* `config.ssh.compression` (boolean) - If `false`, this setting will not include the +compression setting when ssh'ing into a machine. If this is not set, it will +default to `true` and `Compression=yes` will be enabled with ssh. -* `config.ssh.password` (string) - This sets a password that Vagrant will use to -authenticate the SSH user. Note that Vagrant recommends you use key-based -authentication rather than a password (see `private_key_path`) below. If -you use a password, Vagrant will automatically insert a keypair if -`insert_key` is true. +* `config.ssh.config` (string) - Path to a custom ssh_config file to use for configuring +the SSH connections. -* `config.ssh.host` (string) - The hostname or IP to SSH into. By default this is -empty, because the provider usually figures this out for you. +* `config.ssh.dsa_authentication` (boolean) - If `false`, this setting will not include +`DSAAuthentication` when ssh'ing into a machine. If this is not set, it will +default to `true` and `DSAAuthentication=yes` will be used with ssh. -* `config.ssh.port` (integer) - The port to SSH into. By default this is port 22. +* `config.ssh.export_command_template` (string) - The template used to generate +exported environment variables in the active session. This can be useful +when using a Bourne incompatible shell like C shell. The template supports +two variables which are replaced with the desired environment variable key and +environment variable value: `%ENV_KEY%` and `%ENV_VALUE%`. The default template +is: -* `config.ssh.guest_port` (integer) - The port on the guest that SSH is running on. This -is used by some providers to detect forwarded ports for SSH. For example, if -this is set to 22 (the default), and Vagrant detects a forwarded port to -port 22 on the guest from port 4567 on the host, Vagrant will attempt -to use port 4567 to talk to the guest if there is no other option. + ```ruby + config.ssh.export_command_template = 'export %ENV_KEY%="%ENV_VALUE%"' + ``` -* `config.ssh.private_key_path` (string, array of strings) - The path to the private -key to use to SSH into the guest machine. By default this is the insecure private key -that ships with Vagrant, since that is what public boxes use. If you make -your own custom box with a custom SSH key, this should point to that -private key. You can also specify multiple private keys by setting this to be an array. -This is useful, for example, if you use the default private key to bootstrap -the machine, but replace it with perhaps a more secure key later. - -* `config.ssh.keys_only` (boolean) - Only use Vagrant-provided SSH private keys (do not use -any keys stored in ssh-agent). The default value is `true`. - -* `config.ssh.verify_host_key` (string, symbol) - Perform strict host-key verification. The -default value is `:never`. - -* `config.ssh.paranoid` (boolean) - Perform strict host-key verification. The default value is -`false`. - - __Deprecation:__ The `config.ssh.paranoid` option is deprecated and will be removed - in a future release. Please use the `config.ssh.verify_host_key` option instead. +* `config.ssh.extra_args` (array of strings) - This settings value is passed directly +into the ssh executable. This allows you to pass any arbitrary commands to do things such +as reverse tunneling down into the ssh program. These options can either be +single flags set as strings such as `"-6"` for IPV6 or an array of arguments +such as `["-L", "8008:localhost:80"]` for enabling a tunnel from host port 8008 +to port 80 on guest. * `config.ssh.forward_agent` (boolean) - If `true`, agent forwarding over SSH connections is enabled. Defaults to false. -* `config.ssh.forward_x11` (boolean) - If `true`, X11 forwarding over SSH connections -is enabled. Defaults to false. - * `config.ssh.forward_env` (array of strings) - An array of host environment variables to forward to the guest. If you are familiar with OpenSSH, this corresponds to the `SendEnv` parameter. @@ -74,6 +58,18 @@ parameter. config.ssh.forward_env = ["CUSTOM_VAR"] ``` +* `config.ssh.forward_x11` (boolean) - If `true`, X11 forwarding over SSH connections +is enabled. Defaults to false. + +* `config.ssh.guest_port` (integer) - The port on the guest that SSH is running on. This +is used by some providers to detect forwarded ports for SSH. For example, if +this is set to 22 (the default), and Vagrant detects a forwarded port to +port 22 on the guest from port 4567 on the host, Vagrant will attempt +to use port 4567 to talk to the guest if there is no other option. + +* `config.ssh.host` (string) - The hostname or IP to SSH into. By default this is +empty, because the provider usually figures this out for you. + * `config.ssh.insert_key` (boolean) - If `true`, Vagrant will automatically insert a keypair to use for SSH, replacing Vagrant's default insecure key inside the machine if detected. By default, this is true. @@ -83,6 +79,34 @@ if detected. By default, this is true. If you do not have to care about security in your project and want to keep using the default insecure key, set this to `false`. +* `config.ssh.keep_alive` (boolean) - If `true`, this setting SSH will send keep-alive packets +every 5 seconds by default to keep connections alive. + +* `config.ssh.keys_only` (boolean) - Only use Vagrant-provided SSH private keys (do not use +any keys stored in ssh-agent). The default value is `true`. + +* `config.ssh.paranoid` (boolean) - Perform strict host-key verification. The default value is +`false`. + + __Deprecation:__ The `config.ssh.paranoid` option is deprecated and will be removed + in a future release. Please use the `config.ssh.verify_host_key` option instead. + +* `config.ssh.password` (string) - This sets a password that Vagrant will use to +authenticate the SSH user. Note that Vagrant recommends you use key-based +authentication rather than a password (see `private_key_path`) below. If +you use a password, Vagrant will automatically insert a keypair if +`insert_key` is true. + +* `config.ssh.port` (integer) - The port to SSH into. By default this is port 22. + +* `config.ssh.private_key_path` (string, array of strings) - The path to the private +key to use to SSH into the guest machine. By default this is the insecure private key +that ships with Vagrant, since that is what public boxes use. If you make +your own custom box with a custom SSH key, this should point to that +private key. You can also specify multiple private keys by setting this to be an array. +This is useful, for example, if you use the default private key to bootstrap +the machine, but replace it with perhaps a more secure key later. + * `config.ssh.proxy_command` (string) - A command-line command to execute that receives the data to send to SSH on stdin. This can be used to proxy the SSH connection. `%h` in the command is replaced with the host and `%p` is replaced with @@ -99,40 +123,24 @@ the port. streamed to the UI. Instead, the output will be delivered in full to the UI once the command has completed. -* `config.ssh.keep_alive` (boolean) - If `true`, this setting SSH will send keep-alive packets -every 5 seconds by default to keep connections alive. +* `config.ssh.remote_user` (string) - The "remote user" value used to replace the `%r` +character(s) used within a configured `ProxyCommand`. This value is only used by the +net-ssh library (ignored by the `ssh` executable) and should not be used in general. +This defaults to the value of `config.ssh.username`. * `config.ssh.shell` (string) - The shell to use when executing SSH commands from Vagrant. By default this is `bash -l`. Note that this has no effect on the shell you get when you run `vagrant ssh`. This configuration option only affects the shell to use when executing commands internally in Vagrant. -* `config.ssh.export_command_template` (string) - The template used to generate -exported environment variables in the active session. This can be useful -when using a Bourne incompatible shell like C shell. The template supports -two variables which are replaced with the desired environment variable key and -environment variable value: `%ENV_KEY%` and `%ENV_VALUE%`. The default template -is: - - ```ruby - config.ssh.export_command_template = 'export %ENV_KEY%="%ENV_VALUE%"' - ``` - * `config.ssh.sudo_command` (string) - The command to use when executing a command with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by the command that is being executed. -* `config.ssh.compression` (boolean) - If `false`, this setting will not include the -compression setting when ssh'ing into a machine. If this is not set, it will -default to `true` and `Compression=yes` will be enabled with ssh. +* `config.ssh.username` (string) - This sets the username that Vagrant will SSH +as by default. Providers are free to override this if they detect a more +appropriate user. By default this is "vagrant", since that is what most +public boxes are made as. -* `config.ssh.dsa_authentication` (boolean) - If `false`, this setting will not include -`DSAAuthentication` when ssh'ing into a machine. If this is not set, it will -default to `true` and `DSAAuthentication=yes` will be used with ssh. - -* `config.ssh.extra_args` (array of strings) - This settings value is passed directly -into the ssh executable. This allows you to pass any arbitrary commands to do things such -as reverse tunneling down into the ssh program. These options can either be -single flags set as strings such as `"-6"` for IPV6 or an array of arguments -such as `["-L", "8008:localhost:80"]` for enabling a tunnel from host port 8008 -to port 80 on guest. +* `config.ssh.verify_host_key` (string, symbol) - Perform strict host-key verification. The +default value is `:never`.