Validate VirtualBox hostonly network range
VirtualBox introduced a restriction on the valid range for hostonly
networks. When using a version of VirtualBox which includes this
restriction a check is performed on the defined IP address to validate
it is within either the default range (as defined in the VirtualBox
documentation) or the values defined in the network configuration
file.
This commit is contained in:
parent
c45de775e8
commit
ae7639ec23
@ -1024,6 +1024,10 @@ module Vagrant
|
||||
error_key(:virtualbox_version_empty)
|
||||
end
|
||||
|
||||
class VirtualBoxInvalidHostSubnet < VagrantError
|
||||
error_key(:virtualbox_invalid_host_subnet)
|
||||
end
|
||||
|
||||
class VMBaseMacNotSpecified < VagrantError
|
||||
error_key(:no_base_mac, "vagrant.actions.vm.match_mac")
|
||||
end
|
||||
|
||||
@ -16,6 +16,14 @@ module VagrantPlugins
|
||||
#
|
||||
# This handles all the `config.vm.network` configurations.
|
||||
class Network
|
||||
|
||||
# Location of the VirtualBox networks configuration file
|
||||
VBOX_NET_CONF = "/etc/vbox/networks.conf".freeze
|
||||
# Version of VirtualBox that introduced hostonly network range restrictions
|
||||
HOSTONLY_VALIDATE_VERSION = Gem::Version.new("6.1.28").freeze
|
||||
# Default valid range for hostonly networks
|
||||
HOSTONLY_DEFAULT_RANGE = [IPAddr.new("192.68.56.0/21").freeze].freeze
|
||||
|
||||
include Vagrant::Util::NetworkIP
|
||||
include Vagrant::Util::ScopedHashOverride
|
||||
|
||||
@ -255,7 +263,7 @@ module VagrantPlugins
|
||||
|
||||
# Make sure the type is a symbol
|
||||
options[:type] = options[:type].to_sym
|
||||
|
||||
|
||||
if options[:type] == :dhcp && !options[:ip]
|
||||
# Try to find a matching device to set the config ip to
|
||||
matching_device = hostonly_find_matching_network(options)
|
||||
@ -263,7 +271,7 @@ module VagrantPlugins
|
||||
options[:ip] = matching_device[:ip]
|
||||
else
|
||||
# Default IP is in the 20-bit private network block for DHCP based networks
|
||||
options[:ip] = "172.28.128.1"
|
||||
options[:ip] = "192.68.56.1"
|
||||
end
|
||||
end
|
||||
|
||||
@ -288,6 +296,8 @@ module VagrantPlugins
|
||||
error: e.message
|
||||
end
|
||||
|
||||
validate_hostonly_ip!(options[:ip])
|
||||
|
||||
if ip.ipv4?
|
||||
# Verify that a host-only network subnet would not collide
|
||||
# with a bridged networking interface.
|
||||
@ -501,6 +511,33 @@ module VagrantPlugins
|
||||
nil
|
||||
end
|
||||
|
||||
# Validates the IP used to configure the network is within the allowed
|
||||
# ranges. It only validates if the network configuration file exists.
|
||||
# This was introduced in 6.1.28 so previous version won't have restrictions
|
||||
# placed on the valid ranges
|
||||
def validate_hostonly_ip!(ip)
|
||||
return if Gem::Version.new(Driver::Meta.version) < HOSTONLY_VALIDATE_VERSION ||
|
||||
Vagrant::Util::Platform.windows?
|
||||
|
||||
ip = IPAddr.new(ip.to_s) if !ip.is_a?(IPAddr)
|
||||
valid_ranges = load_net_conf
|
||||
return if valid_ranges.any?{ |range| range.include?(ip) }
|
||||
raise Vagrant::Errors::VirtualBoxInvalidHostSubnet,
|
||||
address: ip,
|
||||
ranges: valid_ranges.map{ |r| "#{r}/#{r.prefix}" }.join(", ")
|
||||
end
|
||||
|
||||
def load_net_conf
|
||||
return HOSTONLY_DEFAULT_RANGE if !File.exist?(VBOX_NET_CONF)
|
||||
File.readlines(VBOX_NET_CONF).map do |line|
|
||||
line = line.strip
|
||||
next if !line.start_with?("*")
|
||||
line[1,line.length].strip.split(" ").map do |entry|
|
||||
IPAddr.new(entry)
|
||||
end
|
||||
end.flatten.compact
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
# DHCP Server Helper Functions
|
||||
#-----------------------------------------------------------------
|
||||
|
||||
@ -1842,6 +1842,18 @@ en:
|
||||
outputted:
|
||||
|
||||
%{vboxmanage} --version
|
||||
virtualbox_invalid_host_subnet: |-
|
||||
The IP address configured for the host-only network is not within the
|
||||
allowed ranges. Please update the address used to be within the allowed
|
||||
ranges and run the command again.
|
||||
|
||||
Address: %{address}
|
||||
Ranges: %{ranges}
|
||||
|
||||
Valid ranges can be modified in the /etc/vbox/networks.conf file. For
|
||||
more information including valid format see:
|
||||
|
||||
https://www.virtualbox.org/manual/ch06.html#network_hostonly
|
||||
vm_creation_required: |-
|
||||
VM must be created before running this command. Run `vagrant up` first.
|
||||
vm_inaccessible: |-
|
||||
|
||||
@ -30,6 +30,187 @@ describe VagrantPlugins::ProviderVirtualBox::Action::Network do
|
||||
before do
|
||||
allow(driver).to receive(:enable_adapters)
|
||||
allow(driver).to receive(:read_network_interfaces) { nics }
|
||||
allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:version).
|
||||
and_return("6.1.0")
|
||||
end
|
||||
|
||||
describe "#hostonly_config" do
|
||||
before do
|
||||
allow(subject).to receive(:hostonly_find_matching_network)
|
||||
allow(driver).to receive(:read_bridged_interfaces).and_return([])
|
||||
subject.instance_eval do
|
||||
def env=(e)
|
||||
@env = e
|
||||
end
|
||||
end
|
||||
|
||||
subject.env = env
|
||||
end
|
||||
|
||||
let(:options) {
|
||||
{
|
||||
type: type,
|
||||
ip: address,
|
||||
}
|
||||
}
|
||||
let(:type) { :dhcp }
|
||||
let(:address) { nil }
|
||||
|
||||
it "should validate the IP" do
|
||||
expect(subject).to receive(:validate_hostonly_ip!)
|
||||
subject.hostonly_config(options)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#validate_hostonly_ip!" do
|
||||
let(:address) { "192.168.1.2" }
|
||||
let(:net_conf) { [IPAddr.new(address + "/24")]}
|
||||
|
||||
before do
|
||||
allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:version).
|
||||
and_return("6.1.28")
|
||||
|
||||
allow(subject).to receive(:load_net_conf).and_return(net_conf)
|
||||
expect(subject).to receive(:validate_hostonly_ip!).and_call_original
|
||||
end
|
||||
|
||||
it "should load net configuration" do
|
||||
expect(subject).to receive(:load_net_conf).and_return(net_conf)
|
||||
subject.validate_hostonly_ip!(address)
|
||||
end
|
||||
|
||||
context "when address is within ranges" do
|
||||
it "should not error" do
|
||||
subject.validate_hostonly_ip!(address)
|
||||
end
|
||||
end
|
||||
|
||||
context "when address is not found within ranges" do
|
||||
let(:net_conf) { [IPAddr.new("127.0.0.1/20")] }
|
||||
|
||||
it "should raise an error" do
|
||||
expect {
|
||||
subject.validate_hostonly_ip!(address)
|
||||
}.to raise_error(Vagrant::Errors::VirtualBoxInvalidHostSubnet)
|
||||
end
|
||||
end
|
||||
|
||||
context "when virtualbox version does not restrict range" do
|
||||
before do
|
||||
allow(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:version).
|
||||
and_return("6.1.20")
|
||||
end
|
||||
|
||||
it "should not error" do
|
||||
subject.validate_hostonly_ip!(address)
|
||||
end
|
||||
|
||||
it "should not attempt to load network configuration" do
|
||||
expect(subject).not_to receive(:load_net_conf)
|
||||
subject.validate_hostonly_ip!(address)
|
||||
end
|
||||
end
|
||||
|
||||
context "when platform is windows" do
|
||||
before do
|
||||
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
|
||||
end
|
||||
|
||||
it "should not error" do
|
||||
subject.validate_hostonly_ip!(address)
|
||||
end
|
||||
|
||||
it "should not attempt to load network configuration" do
|
||||
expect(subject).not_to receive(:load_net_conf)
|
||||
subject.validate_hostonly_ip!(address)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#load_net_conf" do
|
||||
let(:file_contents) { [""] }
|
||||
|
||||
before do
|
||||
allow(File).to receive(:exist?).
|
||||
with(described_class.const_get(:VBOX_NET_CONF)).
|
||||
and_return(true)
|
||||
allow(File).to receive(:readlines).
|
||||
with(described_class.const_get(:VBOX_NET_CONF)).
|
||||
and_return(file_contents)
|
||||
end
|
||||
|
||||
it "should read the configuration file" do
|
||||
expect(File).to receive(:readlines).
|
||||
with(described_class.const_get(:VBOX_NET_CONF)).
|
||||
and_return(file_contents)
|
||||
|
||||
subject.load_net_conf
|
||||
end
|
||||
|
||||
context "when file has comments only" do
|
||||
let(:file_contents) {
|
||||
[
|
||||
"# A comment",
|
||||
"# Another comment",
|
||||
]
|
||||
}
|
||||
|
||||
it "should return an empty array" do
|
||||
expect(subject.load_net_conf).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context "when file has valid range entries" do
|
||||
let(:file_contents) {
|
||||
[
|
||||
"* 127.0.0.1/24",
|
||||
"* 192.168.1.1/24",
|
||||
]
|
||||
}
|
||||
|
||||
it "should return an array with content" do
|
||||
expect(subject.load_net_conf).not_to be_empty
|
||||
end
|
||||
|
||||
it "should include IPAddr instances" do
|
||||
subject.load_net_conf.each do |entry|
|
||||
expect(entry).to be_a(IPAddr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when file has valid range entries and comments" do
|
||||
let(:file_contents) {
|
||||
[
|
||||
"# Comment in file",
|
||||
"* 127.0.0.0/8",
|
||||
"random text",
|
||||
" * 192.168.2.0/28",
|
||||
]
|
||||
}
|
||||
|
||||
it "should contain two entries" do
|
||||
expect(subject.load_net_conf.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context "when file has multiple entries on single line" do
|
||||
let(:file_contents) {
|
||||
[
|
||||
"* 0.0.0.0/0 ::/0"
|
||||
]
|
||||
}
|
||||
|
||||
it "should contain two entries" do
|
||||
expect(subject.load_net_conf.size).to eq(2)
|
||||
end
|
||||
|
||||
it "should contain an ipv4 and ipv6 range" do
|
||||
result = subject.load_net_conf
|
||||
expect(result.first).to be_ipv4
|
||||
expect(result.last).to be_ipv6
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "calls the next action in the chain" do
|
||||
@ -133,29 +314,29 @@ describe VagrantPlugins::ProviderVirtualBox::Action::Network do
|
||||
subject.call(env)
|
||||
|
||||
expect(driver).to have_received(:create_host_only_network).with({
|
||||
adapter_ip: '172.28.128.1',
|
||||
adapter_ip: '192.68.56.1',
|
||||
netmask: '255.255.255.0',
|
||||
})
|
||||
|
||||
expect(driver).to have_received(:create_dhcp_server).with('vboxnet0', {
|
||||
adapter_ip: "172.28.128.1",
|
||||
adapter_ip: "192.68.56.1",
|
||||
auto_config: true,
|
||||
ip: "172.28.128.1",
|
||||
ip: "192.68.56.1",
|
||||
mac: nil,
|
||||
name: nil,
|
||||
netmask: "255.255.255.0",
|
||||
nic_type: nil,
|
||||
type: :dhcp,
|
||||
dhcp_ip: "172.28.128.2",
|
||||
dhcp_lower: "172.28.128.3",
|
||||
dhcp_upper: "172.28.128.254",
|
||||
dhcp_ip: "192.68.56.2",
|
||||
dhcp_lower: "192.68.56.3",
|
||||
dhcp_upper: "192.68.56.254",
|
||||
adapter: 2
|
||||
})
|
||||
|
||||
expect(guest).to have_received(:capability).with(:configure_networks, [{
|
||||
type: :dhcp,
|
||||
adapter_ip: "172.28.128.1",
|
||||
ip: "172.28.128.1",
|
||||
adapter_ip: "192.68.56.1",
|
||||
ip: "192.68.56.1",
|
||||
netmask: "255.255.255.0",
|
||||
auto_config: true,
|
||||
interface: nil
|
||||
@ -213,8 +394,8 @@ describe VagrantPlugins::ProviderVirtualBox::Action::Network do
|
||||
{ ip: 'foo'},
|
||||
{ ip: '1.2.3'},
|
||||
{ ip: 'dead::beef::'},
|
||||
{ ip: '172.28.128.3', netmask: 64},
|
||||
{ ip: '172.28.128.3', netmask: 'ffff:ffff::'},
|
||||
{ ip: '192.68.56.3', netmask: 64},
|
||||
{ ip: '192.68.56.3', netmask: 'ffff:ffff::'},
|
||||
{ ip: 'dead:beef::', netmask: 'foo:bar::'},
|
||||
{ ip: 'dead:beef::', netmask: '255.255.255.0'}
|
||||
].each do |args|
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user