Merge pull request #10496 from chrisroberts/e-ssh-config-file

Add support for config and remote_user to SSH options
This commit is contained in:
Chris Roberts 2018-12-13 09:16:49 -08:00 committed by GitHub
commit 480bb47e1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 292 additions and 65 deletions

View File

@ -452,9 +452,11 @@ module Vagrant
info[:keys_only] ||= @config.ssh.default.keys_only info[:keys_only] ||= @config.ssh.default.keys_only
info[:verify_host_key] ||= @config.ssh.default.verify_host_key info[:verify_host_key] ||= @config.ssh.default.verify_host_key
info[:username] ||= @config.ssh.default.username info[:username] ||= @config.ssh.default.username
info[:remote_user] ||= @config.ssh.default.remote_user
info[:compression] ||= @config.ssh.default.compression info[:compression] ||= @config.ssh.default.compression
info[:dsa_authentication] ||= @config.ssh.default.dsa_authentication info[:dsa_authentication] ||= @config.ssh.default.dsa_authentication
info[:extra_args] ||= @config.ssh.default.extra_args 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 # We set overrides if they are set. These take precedence over
# provider-returned data. # provider-returned data.
@ -466,7 +468,9 @@ module Vagrant
info[:dsa_authentication] = @config.ssh.dsa_authentication info[:dsa_authentication] = @config.ssh.dsa_authentication
info[:username] = @config.ssh.username if @config.ssh.username info[:username] = @config.ssh.username if @config.ssh.username
info[:password] = @config.ssh.password if @config.ssh.password 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[: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 # We also set some fields that are purely controlled by Vagrant
info[:forward_agent] = @config.ssh.forward_agent info[:forward_agent] = @config.ssh.forward_agent

View File

@ -178,6 +178,10 @@ module Vagrant
"-o", "ForwardX11Trusted=yes"] "-o", "ForwardX11Trusted=yes"]
end end
if ssh_info[:config]
command_options += ["-F", ssh_info[:config]]
end
if ssh_info[:proxy_command] if ssh_info[:proxy_command]
command_options += ["-o", "ProxyCommand=#{ssh_info[:proxy_command]}"] command_options += ["-o", "ProxyCommand=#{ssh_info[:proxy_command]}"]
end end

View File

@ -55,6 +55,7 @@ module VagrantPlugins
proxy_command: ssh_info[:proxy_command], proxy_command: ssh_info[:proxy_command],
ssh_command: ssh_info[:ssh_command], ssh_command: ssh_info[:ssh_command],
forward_env: ssh_info[:forward_env], forward_env: ssh_info[:forward_env],
config: ssh_info[:config],
} }
# Render the template and output directly to STDOUT # Render the template and output directly to STDOUT

View File

@ -414,6 +414,14 @@ module VagrantPlugins
connect_opts[:proxy] = Net::SSH::Proxy::Command.new(ssh_info[:proxy_command]) connect_opts[:proxy] = Net::SSH::Proxy::Command.new(ssh_info[:proxy_command])
end 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("Attempting to connect to SSH...")
@logger.info(" - Host: #{ssh_info[:host]}") @logger.info(" - Host: #{ssh_info[:host]}")
@logger.info(" - Port: #{ssh_info[:port]}") @logger.info(" - Port: #{ssh_info[:port]}")

View File

@ -3,6 +3,7 @@ module VagrantPlugins
class SSHConnectConfig < Vagrant.plugin("2", :config) class SSHConnectConfig < Vagrant.plugin("2", :config)
attr_accessor :host attr_accessor :host
attr_accessor :port attr_accessor :port
attr_accessor :config
attr_accessor :private_key_path attr_accessor :private_key_path
attr_accessor :username attr_accessor :username
attr_accessor :password attr_accessor :password
@ -13,10 +14,12 @@ module VagrantPlugins
attr_accessor :compression attr_accessor :compression
attr_accessor :dsa_authentication attr_accessor :dsa_authentication
attr_accessor :extra_args attr_accessor :extra_args
attr_accessor :remote_user
def initialize def initialize
@host = UNSET_VALUE @host = UNSET_VALUE
@port = UNSET_VALUE @port = UNSET_VALUE
@config = UNSET_VALUE
@private_key_path = UNSET_VALUE @private_key_path = UNSET_VALUE
@username = UNSET_VALUE @username = UNSET_VALUE
@password = UNSET_VALUE @password = UNSET_VALUE
@ -27,6 +30,7 @@ module VagrantPlugins
@compression = UNSET_VALUE @compression = UNSET_VALUE
@dsa_authentication = UNSET_VALUE @dsa_authentication = UNSET_VALUE
@extra_args = UNSET_VALUE @extra_args = UNSET_VALUE
@remote_user = UNSET_VALUE
end end
def finalize! def finalize!
@ -42,11 +46,20 @@ module VagrantPlugins
@compression = true if @compression == UNSET_VALUE @compression = true if @compression == UNSET_VALUE
@dsa_authentication = true if @dsa_authentication == UNSET_VALUE @dsa_authentication = true if @dsa_authentication == UNSET_VALUE
@extra_args = nil if @extra_args == 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) if @private_key_path && !@private_key_path.is_a?(Array)
@private_key_path = [@private_key_path] @private_key_path = [@private_key_path]
end end
if @remote_user == UNSET_VALUE
if @username
@remote_user = @username
else
@remote_user = nil
end
end
if @paranoid if @paranoid
@verify_host_key = @paranoid @verify_host_key = @paranoid
end end
@ -84,6 +97,15 @@ module VagrantPlugins
end end
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 if @paranoid
machine.env.ui.warn(I18n.t("vagrant.config.ssh.paranoid_deprecated")) machine.env.ui.warn(I18n.t("vagrant.config.ssh.paranoid_deprecated"))
end end

View File

@ -77,6 +77,11 @@ module VagrantPlugins
proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' " proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' "
end 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 # 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 # in the machine data dir but this can result in paths that are
# too long for unix domain sockets. # too long for unix domain sockets.
@ -91,6 +96,7 @@ module VagrantPlugins
"ssh", "-p", "#{ssh_info[:port]}", "ssh", "-p", "#{ssh_info[:port]}",
"-o", "LogLevel=#{log_level}", "-o", "LogLevel=#{log_level}",
proxy_command, proxy_command,
ssh_config_file,
control_options, control_options,
] ]

View File

@ -1,4 +1,7 @@
Host <%= host_key %> Host <%= host_key %>
<% if config -%>
Include <%= config %>
<% end -%>
HostName <%= ssh_host %> HostName <%= ssh_host %>
User <%= ssh_user %> User <%= ssh_user %>
Port <%= ssh_port %> Port <%= ssh_port %>

View File

@ -1810,6 +1810,7 @@ en:
paranoid_deprecated: |- paranoid_deprecated: |-
The key `paranoid` is deprecated. Please use `verify_host_key`. Supported The key `paranoid` is deprecated. Please use `verify_host_key`. Supported
values are exactly the same, only the name of the option has changed. values are exactly the same, only the name of the option has changed.
ssh_config_missing: "`config` file must exist: %{path}"
triggers: triggers:
bad_command_warning: |- bad_command_warning: |-
The command '%{cmd}' was not found for this trigger. The command '%{cmd}' was not found for this trigger.

View File

@ -165,5 +165,18 @@ Host #{machine.name}
expect(output).to include('StrictHostKeyChecking ') expect(output).to include('StrictHostKeyChecking ')
expect(output).to include('UserKnownHostsFile ') expect(output).to include('UserKnownHostsFile ')
end 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
end end

View File

@ -748,6 +748,48 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do
communicator.send(:connect) communicator.send(:connect)
end end
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 end
describe ".generate_environment_export" do describe ".generate_environment_export" do

View File

@ -3,6 +3,17 @@ require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/kernel_v2/config/ssh_connect") require Vagrant.source_root.join("plugins/kernel_v2/config/ssh_connect")
describe VagrantPlugins::Kernel_V2::SSHConnectConfig do 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 } subject { described_class.new }
describe "#verify_host_key" do describe "#verify_host_key" do
@ -29,4 +40,67 @@ describe VagrantPlugins::Kernel_V2::SSHConnectConfig do
expect(subject.verify_host_key).to eq(:always) expect(subject.verify_host_key).to eq(:always)
end end
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 end

View File

@ -8,6 +8,7 @@ describe VagrantPlugins::Kernel_V2::SSHConfig do
describe "#default" do describe "#default" do
it "defaults to vagrant username" do it "defaults to vagrant username" do
subject.finalize! subject.finalize!
expect(subject.default.port).to eq(22)
expect(subject.default.username).to eq("vagrant") expect(subject.default.username).to eq("vagrant")
end end
end end

View File

@ -319,5 +319,15 @@ describe VagrantPlugins::SyncedFolderRSync::RsyncHelper do
subject.rsync_single(machine, ssh_info, opts) subject.rsync_single(machine, ssh_info, opts)
end 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
end end

View File

@ -847,6 +847,16 @@ describe Vagrant::Machine do
expect(instance.ssh_info[:private_key_path]).to eql([path]) expect(instance.ssh_info[:private_key_path]).to eql([path])
end 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 context "with no data dir" do
let(:base) { true } let(:base) { true }
let(:data_dir) { nil } let(:data_dir) { nil }

View File

@ -236,6 +236,26 @@ describe Vagrant::Util::SSH do
end end
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 context "with subprocess enabled" do
let(:ssh_info) {{ let(:ssh_info) {{
host: "localhost", host: "localhost",

View File

@ -18,54 +18,38 @@ defaults are typically fine, but you can fine tune whatever you would like.
## Available Settings ## Available Settings
* `config.ssh.username` (string) - This sets the username that Vagrant will SSH * `config.ssh.compression` (boolean) - If `false`, this setting will not include the
as by default. Providers are free to override this if they detect a more compression setting when ssh'ing into a machine. If this is not set, it will
appropriate user. By default this is "vagrant", since that is what most default to `true` and `Compression=yes` will be enabled with ssh.
public boxes are made as.
* `config.ssh.password` (string) - This sets a password that Vagrant will use to * `config.ssh.config` (string) - Path to a custom ssh_config file to use for configuring
authenticate the SSH user. Note that Vagrant recommends you use key-based the SSH connections.
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.host` (string) - The hostname or IP to SSH into. By default this is * `config.ssh.dsa_authentication` (boolean) - If `false`, this setting will not include
empty, because the provider usually figures this out for you. `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 ```ruby
is used by some providers to detect forwarded ports for SSH. For example, if config.ssh.export_command_template = 'export %ENV_KEY%="%ENV_VALUE%"'
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.private_key_path` (string, array of strings) - The path to the private * `config.ssh.extra_args` (array of strings) - This settings value is passed directly
key to use to SSH into the guest machine. By default this is the insecure private key into the ssh executable. This allows you to pass any arbitrary commands to do things such
that ships with Vagrant, since that is what public boxes use. If you make as reverse tunneling down into the ssh program. These options can either be
your own custom box with a custom SSH key, this should point to that single flags set as strings such as `"-6"` for IPV6 or an array of arguments
private key. You can also specify multiple private keys by setting this to be an array. such as `["-L", "8008:localhost:80"]` for enabling a tunnel from host port 8008
This is useful, for example, if you use the default private key to bootstrap to port 80 on guest.
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.forward_agent` (boolean) - If `true`, agent forwarding over SSH * `config.ssh.forward_agent` (boolean) - If `true`, agent forwarding over SSH
connections is enabled. Defaults to false. 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 * `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` forward to the guest. If you are familiar with OpenSSH, this corresponds to the `SendEnv`
parameter. parameter.
@ -74,6 +58,18 @@ parameter.
config.ssh.forward_env = ["CUSTOM_VAR"] 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 * `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 a keypair to use for SSH, replacing Vagrant's default insecure key inside the machine
if detected. By default, this is true. 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 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`. 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 * `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. 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 `%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 streamed to the UI. Instead, the output will be delivered in full to the UI
once the command has completed. once the command has completed.
* `config.ssh.keep_alive` (boolean) - If `true`, this setting SSH will send keep-alive packets * `config.ssh.remote_user` (string) - The "remote user" value used to replace the `%r`
every 5 seconds by default to keep connections alive. 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 * `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 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 the shell you get when you run `vagrant ssh`. This configuration option
only affects the shell to use when executing commands internally in Vagrant. 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 * `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 with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by
the command that is being executed. the command that is being executed.
* `config.ssh.compression` (boolean) - If `false`, this setting will not include the * `config.ssh.username` (string) - This sets the username that Vagrant will SSH
compression setting when ssh'ing into a machine. If this is not set, it will as by default. Providers are free to override this if they detect a more
default to `true` and `Compression=yes` will be enabled with ssh. 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 * `config.ssh.verify_host_key` (string, symbol) - Perform strict host-key verification. The
`DSAAuthentication` when ssh'ing into a machine. If this is not set, it will default value is `:never`.
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.