diff --git a/doc/sphinx/administration/configuration/bastion_conf.rst b/doc/sphinx/administration/configuration/bastion_conf.rst index db9813b6a..08bd11c7d 100644 --- a/doc/sphinx/administration/configuration/bastion_conf.rst +++ b/doc/sphinx/administration/configuration/bastion_conf.rst @@ -47,6 +47,7 @@ Global network policies options Those options can set a few global network policies to be applied bastion-wide. +- `dnsSupportLevel`_ - `allowedNetworks`_ - `forbiddenNetworks`_ - `ingressToEgressRules`_ @@ -345,6 +346,19 @@ Additional parameters that will be passed as-is to mosh-server. See ``man mosh-s Global network policies ----------------------- +.. _dnsSupportLevel: + +dnsSupportLevel +*************** + +:Type: ``integer between 0 and 2`` + +:Default: ``2`` + +If set to 0, The Bastion will never attempt to do DNS or reverse-DNS resolutions, and return an error if you request connection to a hostname instead of an IP. Use this if you know there's no working DNS in your environment and only use IPs everywhere. + If set to 1, The Bastion will not attempt to do DNS or reverse-DNS resolutions unless you force it to (i.e. by requesting connection to a hostname instead of an IP). You may use this if for example you have well-known hostnames in /etc/hosts, but don't have a working DNS (which would imply that reverse-DNS resolutions will always fail). + If set to 2, The Bastion will make the assumption that you have a working DNS setup, and will do DNS and reverse-DNS resolutions normally. + .. _allowedNetworks: allowedNetworks diff --git a/etc/bastion/bastion.conf.dist b/etc/bastion/bastion.conf.dist index 0a5d84b35..ae7fd4bc7 100644 --- a/etc/bastion/bastion.conf.dist +++ b/etc/bastion/bastion.conf.dist @@ -119,6 +119,13 @@ # > Global network policies # >> Those options can set a few global network policies to be applied bastion-wide. # +# dnsSupportLevel (integer between 0 and 2) +# DESC: If set to 0, The Bastion will never attempt to do DNS or reverse-DNS resolutions, and return an error if you request connection to a hostname instead of an IP. Use this if you know there's no working DNS in your environment and only use IPs everywhere. +# If set to 1, The Bastion will not attempt to do DNS or reverse-DNS resolutions unless you force it to (i.e. by requesting connection to a hostname instead of an IP). You may use this if for example you have well-known hostnames in /etc/hosts, but don't have a working DNS (which would imply that reverse-DNS resolutions will always fail). +# If set to 2, The Bastion will make the assumption that you have a working DNS setup, and will do DNS and reverse-DNS resolutions normally. +# DEFAULT: 2 +"dnsSupportLevel": 2, +# # allowedNetworks (array of strings (IPs and/or prefixes)) # DESC: Restricts egress connection attempts to those listed networks only. This is enforced at all times and can NOT be overridden by users. If you are lucky enough to have you own IP blocks, it's probably a good idea to list them here. An empty array means no restriction is applied. # DEFAULT: [] diff --git a/lib/perl/OVH/Bastion/allowdeny.inc b/lib/perl/OVH/Bastion/allowdeny.inc index 509a71b5d..da25a0cc3 100644 --- a/lib/perl/OVH/Bastion/allowdeny.inc +++ b/lib/perl/OVH/Bastion/allowdeny.inc @@ -287,6 +287,10 @@ sub get_ip { return R('ERR_INVALID_IP', msg => "IP $host version is not allowed"); } + if (OVH::Bastion::config('dnsSupportLevel')->value < 1) { + return R('ERR_DNS_DISABLED', msg => "DNS resolving is disabled on this bastion"); + } + osh_debug("Trying to resolve '$host' because is_valid_ip() says it's not an IP"); my ($err, @res); eval { @@ -334,6 +338,10 @@ sub ip2host { my $ip = shift; my ($err, @sockaddr, $host); + if (OVH::Bastion::config('dnsSupportLevel')->value < 2) { + return R('ERR_DNS_DISABLED'); + } + eval { # ip => packedip. AI_PASSIVE: don't use dns, just build sockaddr # can croak diff --git a/lib/perl/OVH/Bastion/configuration.inc b/lib/perl/OVH/Bastion/configuration.inc index a8f0b0fa4..53a2d9dc2 100644 --- a/lib/perl/OVH/Bastion/configuration.inc +++ b/lib/perl/OVH/Bastion/configuration.inc @@ -211,47 +211,44 @@ sub load_configuration { {name => 'accountMaxInactiveDays', min => 0, max => 365 * 5, default => 0}, {name => 'interactiveModeTimeout', min => 0, max => 86400 * 365, default => 15}, {name => 'interactiveModeProactiveMFAexpiration', min => 0, max => 86400, default => 900}, + {name => 'dnsSupportLevel', min => 0, max => 2, default => 2}, ) { if (not defined $C->{$o->{'name'}}) { $C->{$o->{'name'}} = $o->{'default'}; - push @errors, "Configuration error: missing option '" . $o->{'name'} . "', defaulting to " . $o->{'default'} + push @errors, + sprintf("Configuration error: missing option '%s', defaulting to %s", $o->{'name'}, $o->{'default'}) if $test; } if ($C->{$o->{'name'}} =~ /^(-?\d+)$/) { - # untaint $C->{$o->{'name'}} = $1; } else { push @errors, - "Configuration error: value of option '" - . $o->{'name'} . "' ('" - . $C->{$o->{'name'}} - . "') is not a number, defaulting to " - . $o->{'default'}; + sprintf( + "Configuration error: value of option '%s' ('%s') is not a number, defaulting to %s", + $o->{'name'}, $C->{$o->{'name'}}, + $o->{'default'} + ); $C->{$o->{'name'}} = $o->{'default'}; } if ($C->{$o->{'name'}} > $o->{'max'}) { push @errors, - "Configuration error: value of option '" - . $o->{'name'} . "' (" - . $C->{$o->{'name'}} - . ") is higher than allowed value (" - . $o->{'max'} - . "), defaulting to " - . $o->{'default'}; + sprintf( + "Configuration error: value of option '%s' (%s) is higher than allowed value (%s), defaulting to %s", + $o->{'name'}, $C->{$o->{'name'}}, + $o->{'max'}, $o->{'default'} + ); $C->{$o->{'name'}} = $o->{'default'}; } elsif ($C->{$o->{'name'}} < $o->{'min'}) { push @errors, - "Configuration error: value of option '" - . $o->{'name'} . "' (" - . $C->{$o->{'name'}} - . ") is lower than allowed value (" - . $o->{'min'} - . "), defaulting to " - . $o->{'default'}; + sprintf( + "Configuration error: value of option '%s' (%s) is lower than allowed value (%s), defaulting to %s", + $o->{'name'}, $C->{$o->{'name'}}, + $o->{'min'}, $o->{'default'} + ); $C->{$o->{'name'}} = $o->{'default'}; } delete $unknownkeys{$o->{'name'}}; diff --git a/tests/functional/tests.d/380-config-options.sh b/tests/functional/tests.d/380-config-options.sh new file mode 100644 index 000000000..710743a49 --- /dev/null +++ b/tests/functional/tests.d/380-config-options.sh @@ -0,0 +1,19 @@ +# vim: set filetype=sh ts=4 sw=4 sts=4 et: +# shellcheck shell=bash +# shellcheck disable=SC2086,SC2016,SC2046 +# below: convoluted way that forces shellcheck to source our caller +# shellcheck source=tests/functional/launch_tests_on_instance.sh +. "$(dirname "${BASH_SOURCE[0]}")"/dummy + +testsuite_config_options() +{ + configchg 's=^\\\\x22dnsSupportLevel\\\\x22.+=\\\\x22dnsSupportLevel\\\\x22:0,=' + + run a1_connect_nodns $a0 localhost + retvalmustbe 102 + json .error_code KO_HOST_NOT_FOUND + contain 'DNS resolving is disabled' +} + +testsuite_config_options +unset -f testsuite_config_options