diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index b307e6449..75a919159 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -59,7 +59,6 @@ jobs: --has-mfa=0 \ --has-mfa-password=1 \ --has-pamtester=1 \ - --skip-consistency-check \ --remote-etc-bastion=/usr/local/etc/bastion \ --slowness-factor=2 \ --post-run="sudo tar xzf /opt/bastion/ssh_config.tar.gz -C / ; sudo /etc/rc.d/sshd restart" \ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5defc3d5c..4cf276511 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ on: jobs: tests_short: - name: Short (deb12 only, no cc) + name: Short (deb12 only, w/o cc) runs-on: ubuntu-latest if: contains(github.event.pull_request.labels.*.name, 'tests:short') steps: @@ -14,12 +14,12 @@ jobs: with: persist-credentials: false - name: run tests inside a debian12 docker - run: tests/functional/docker/docker_build_and_run_tests.sh debian12 --skip-consistency-check --no-pause-on-fail + run: tests/functional/docker/docker_build_and_run_tests.sh debian12 --no-pause-on-fail env: DOCKER_TTY: false tests_long: - name: Long + name: Long (multi-distros, w/o cc) strategy: matrix: platform: [rockylinux9, debian12, 'opensuse15@opensuse/leap:15.6', ubuntu2404] @@ -30,12 +30,12 @@ jobs: with: persist-credentials: false - name: run tests inside a ${{ matrix.platform }} docker - run: tests/functional/docker/docker_build_and_run_tests.sh ${{ matrix.platform }} --skip-consistency-check --no-pause-on-fail + run: tests/functional/docker/docker_build_and_run_tests.sh ${{ matrix.platform }} --no-pause-on-fail env: DOCKER_TTY: false tests_full: - name: Full + name: Full (multi-distros all versions, w/ cc) strategy: matrix: platform: [rockylinux8, rockylinux9, debian10, debian11, debian12, 'opensuse15@opensuse/leap:15.6', ubuntu1804, ubuntu2004, ubuntu2204, ubuntu2404] @@ -46,6 +46,6 @@ jobs: with: persist-credentials: false - name: run tests inside a ${{ matrix.platform }} docker - run: tests/functional/docker/docker_build_and_run_tests.sh ${{ matrix.platform }} --no-pause-on-fail + run: tests/functional/docker/docker_build_and_run_tests.sh ${{ matrix.platform }} --consistency-check --no-pause-on-fail env: DOCKER_TTY: false diff --git a/bin/helper/osh-accountModifyPersonalAccess b/bin/helper/osh-accountModifyPersonalAccess index 9945b0738..5ec257494 100755 --- a/bin/helper/osh-accountModifyPersonalAccess +++ b/bin/helper/osh-accountModifyPersonalAccess @@ -81,9 +81,7 @@ if (not grep { $action eq $_ } qw{ add del }) { #CODE -my $machine = $ip; -$port and $machine .= ":$port"; -$user and $machine = $user . '@' . $machine; +my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value; my $plugin = ($target eq 'self' ? 'self' : 'account') . 'AddPersonalAccess'; diff --git a/bin/helper/osh-groupAddServer b/bin/helper/osh-groupAddServer index 5c25d4e8b..c8e0526e1 100755 --- a/bin/helper/osh-groupAddServer +++ b/bin/helper/osh-groupAddServer @@ -86,9 +86,7 @@ $fnret = OVH::Bastion::Helper::acquire_lock($lock_fh); $fnret or HEXIT($fnret); #>CODE -my $machine = $ip; -$port and $machine .= ":$port"; -$user and $machine = $user . '@' . $machine; +my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value; # access_modify validates all its parameters, don't do it ourselves here for clarity $fnret = OVH::Bastion::access_modify( diff --git a/bin/plugin/open/groupInfo b/bin/plugin/open/groupInfo index 6182eac28..d0d39dae1 100755 --- a/bin/plugin/open/groupInfo +++ b/bin/plugin/open/groupInfo @@ -172,11 +172,6 @@ foreach my $groupData (@groups) { push @filtered_guests, $guest; } - # deprecated in v2.18.00+ - $ret{'full_members'} = \@members; - $ret{'partial_members'} = \@filtered_guests; - # /deprecated - $ret{'members'} = \@members; $ret{'guests'} = \@filtered_guests; $ret{'guests_accesses'} = \%guest_nb_accesses; @@ -253,14 +248,15 @@ sub print_group_info { if $ret{'members'}; # show guest info, along with the number of accesses each guest has - my @guest_text; - foreach my $guest (@{$ret{'guests'}}) { - my $nb = $ret{'guests_accesses'}{$guest}; - push @guest_text, sprintf("%s[%s]", $guest, $nb // '?'); + if ($ret{'guests'}) { + my @guest_text; + foreach my $guest (@{$ret{'guests'}}) { + my $nb = $ret{'guests_accesses'}{$guest}; + push @guest_text, sprintf("%s[%s]", $guest, $nb // '?'); + } + osh_info("Group ${groupName}'s Guests (with access to SOME of the group servers) are: " + . colored(@{$ret{'guests'}} ? join(" ", sort @guest_text) : '-', 'red')); } - osh_info("Group ${groupName}'s Guests (with access to SOME of the group servers) are: " - . colored(@{$ret{'guests'}} ? join(" ", sort @guest_text) : '-', 'red')) - if $ret{'guests'}; # current user doesn't have enough rights to get this info, tell them that if (!$ret{'members'}) { diff --git a/bin/plugin/restricted/accountInfo b/bin/plugin/restricted/accountInfo index 2a12bd0d8..b94ea53e3 100755 --- a/bin/plugin/restricted/accountInfo +++ b/bin/plugin/restricted/accountInfo @@ -353,6 +353,11 @@ foreach my $accHash (@accounts) { $ret{'max_inactive_days'} = OVH::Bastion::account_config(account => $account, %{OVH::Bastion::OPT_ACCOUNT_MAX_INACTIVE_DAYS()})->value; + $ret{'osh_only'} = !!OVH::Bastion::account_config( + account => $account, + key => OVH::Bastion::OPT_ACCOUNT_OSH_ONLY + ) + 0; + if ($withPasswordInfo) { my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T }; push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountGetPasswordInfo'; @@ -478,7 +483,7 @@ sub print_account_info { } } - if ($ret{'creation_information'}) { + if (defined $ret{'creation_information'}) { if ($ret{'creation_information'}{'datetime_utc'}) { my $createdOnStr = $ret{'creation_information'}{'datetime_utc'}; if ( $ret{'creation_information'}{'datetime_local'} @@ -507,87 +512,106 @@ sub print_account_info { } } - osh_info "\nAccount egress SSH config:"; - if ($ret{'account_egress_ssh_config'}{'type'} eq 'default') { - osh_info "- (default)"; + if (defined $ret{'account_egress_ssh_config'}) { + osh_info "\nAccount egress SSH config:"; + if ($ret{'account_egress_ssh_config'}{'type'} eq 'default') { + osh_info "- (default)"; + } + elsif ($ret{'account_egress_ssh_config'}{'type'} eq 'locally_modified') { + osh_info "- (locally modified!)"; + } + elsif ($ret{'account_egress_ssh_config'}{'type'} eq 'custom') { + foreach my $key (sort keys %{$ret{'account_egress_ssh_config'}{'items'} || {}}) { + osh_info "- $key " . $ret{'account_egress_ssh_config'}{'items'}{$key}; + } + } + else { + osh_info "- (unknown)"; + } } - elsif ($ret{'account_egress_ssh_config'}{'type'} eq 'locally_modified') { - osh_info "- (locally modified!)"; + + if (defined $ret{'osh_only'}) { + osh_info "\nThis account can only run commands (\"osh-only\"): " + . ($ret{'osh_only'} ? colored('yes', 'red') : colored('no', 'blue')); } - elsif ($ret{'account_egress_ssh_config'}{'type'} eq 'custom') { - foreach my $key (sort keys %{$ret{'account_egress_ssh_config'}{'items'} || {}}) { - osh_info "- $key " . $ret{'account_egress_ssh_config'}{'items'}{$key}; + + if (exists $ret{'ingress_piv_policy'} && exists $ret{'ingress_piv_grace'}) { + osh_info "\nAccount PIV-only policy status:"; + my $ingress_piv_policy_print = $ret{'ingress_piv_policy'} || 'default'; + osh_info "- PIV policy for ingress keys on this account is set to " + . colored($ingress_piv_policy_print, $ingress_piv_policy_print eq 'default' ? 'blue' : 'green'); + + if ($ret{'ingress_piv_grace'} && $ret{'ingress_piv_grace'}{'seconds_remaining'}) { + $fnret = OVH::Bastion::duration2human(seconds => $ret{'ingress_piv_grace'}{'seconds_remaining'})->value; + osh_info("- PIV grace period for this account is " + . colored('set', 'green') + . " and expires in " + . $fnret->value->{'human'}); + } + else { + osh_info "- PIV grace period for this account is " . colored('inactive', 'blue'); } - } - else { - osh_info "- (unknown)"; } - osh_info "\nAccount PIV-only policy status:"; - my $ingress_piv_policy_print = $ret{'ingress_piv_policy'} || 'default'; - osh_info "- PIV policy for ingress keys on this account is set to " - . colored($ingress_piv_policy_print, $ingress_piv_policy_print eq 'default' ? 'blue' : 'green'); - - if ($ret{'ingress_piv_grace'} && $ret{'ingress_piv_grace'}{'seconds_remaining'}) { - $fnret = OVH::Bastion::duration2human(seconds => $ret{'ingress_piv_grace'}{'seconds_remaining'})->value; - osh_info("- PIV grace period for this account is " - . colored('set', 'green') - . " and expires in " - . $fnret->value->{'human'}); + if (defined $ret{'global_ingress_policy'}) { + osh_info "- Global PIV policy status is " + . ($ret{'global_ingress_policy'} ? colored('enabled', 'red') : colored('disabled', 'blue')); } - else { - osh_info "- PIV grace period for this account is " . colored('inactive', 'blue'); + + if (defined $ret{'effective_ingress_piv_policy'}) { + osh_info "- As a consequence, PIV policy is " + . ($ret{'effective_ingress_piv_policy'} ? colored('enforced', 'red') : colored('inactive', 'blue')) + . " for this account"; } - osh_info "- Global PIV policy status is " - . ($ret{'global_ingress_policy'} ? colored('enabled', 'red') : colored('disabled', 'blue')); - - osh_info "- As a consequence, PIV policy is " - . ($ret{'effective_ingress_piv_policy'} ? colored('enforced', 'red') : colored('inactive', 'blue')) - . " for this account"; - - osh_info "\nAccount Multi-Factor Authentication status:"; - osh_info "- Additional password authentication is " - . ($ret{'mfa_password_required'} ? colored('required', 'green') : colored('not required', 'blue')) - . " for this account"; - osh_info "- Additional password authentication bypass is " - . ($ret{'mfa_password_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')) - . " for this account"; - osh_info "- Additional password authentication is " - . ($ret{'mfa_password_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue')); - - osh_info "- Additional TOTP authentication is " - . ($ret{'mfa_totp_required'} ? colored('required', 'green') : colored('not required', 'blue')) - . " for this account"; - osh_info "- Additional TOTP authentication bypass is " - . ($ret{'mfa_totp_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')) - . " for this account"; - osh_info "- Additional TOTP authentication is " - . ($ret{'mfa_totp_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue')); - - osh_info "- PAM authentication bypass is " - . ($ret{'pam_auth_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')); - - osh_info "- Optional public key authentication is " - . ($ret{'pubkey_auth_optional'} ? colored('enabled', 'green') : colored('disabled', 'blue')); - - osh_info "- MFA policy on personal accesses (using personal keys) on egress side is: " - . $ret{'personal_egress_mfa_required'}; - - osh_info "\n- Account is immune to idle counter-measures: " - . ($ret{'idle_ignore'} ? colored('yes', 'green') : colored('no', 'blue')); - - if (!defined $ret{'max_inactive_days'}) { - osh_info "- Maximum number of days of inactivity before account is disabled: (default)"; + if (exists $ret{'mfa_password_required'} && exists $ret{'mfa_totp_required'} && exists $ret{'pam_auth_bypass'}) { + osh_info "\nAccount Multi-Factor Authentication status:"; + osh_info "- Additional password authentication is " + . ($ret{'mfa_password_required'} ? colored('required', 'green') : colored('not required', 'blue')) + . " for this account"; + osh_info "- Additional password authentication bypass is " + . ($ret{'mfa_password_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')) + . " for this account"; + osh_info "- Additional password authentication is " + . ($ret{'mfa_password_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue')); + + osh_info "- Additional TOTP authentication is " + . ($ret{'mfa_totp_required'} ? colored('required', 'green') : colored('not required', 'blue')) + . " for this account"; + osh_info "- Additional TOTP authentication bypass is " + . ($ret{'mfa_totp_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')) + . " for this account"; + osh_info "- Additional TOTP authentication is " + . ($ret{'mfa_totp_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue')); + + osh_info "- PAM authentication bypass is " + . ($ret{'pam_auth_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')); + + osh_info "- Optional public key authentication is " + . ($ret{'pubkey_auth_optional'} ? colored('enabled', 'green') : colored('disabled', 'blue')); + + osh_info "- MFA policy on personal accesses (using personal keys) on egress side is: " + . $ret{'personal_egress_mfa_required'}; } - elsif ($ret{'max_inactive_days'} == 0) { - osh_info "- Maximum number of days of inactivity before account is disabled: never"; + + if (exists $ret{'idle_ignore'}) { + osh_info "\n- Account is immune to idle counter-measures: " + . ($ret{'idle_ignore'} ? colored('yes', 'green') : colored('no', 'blue')); } - else { - osh_info "- Maximum number of days of inactivity before account is disabled: " . $ret{'max_inactive_days'}; + + if (exists $ret{'max_inactive_days'}) { + if (!defined $ret{'max_inactive_days'}) { + osh_info "- Maximum number of days of inactivity before account is disabled: (default)"; + } + elsif ($ret{'max_inactive_days'} == 0) { + osh_info "- Maximum number of days of inactivity before account is disabled: never"; + } + else { + osh_info "- Maximum number of days of inactivity before account is disabled: " . $ret{'max_inactive_days'}; + } } - if ($ret{'password'}) { + if (defined $ret{'password'}) { osh_info "Account PAM UNIX password information (used for password MFA):"; if ($ret{'password'}{'password'} eq 'locked') { osh_info "- No valid password is set"; diff --git a/bin/shell/osh.pl b/bin/shell/osh.pl index 8e969caba..34f5764bb 100755 --- a/bin/shell/osh.pl +++ b/bin/shell/osh.pl @@ -600,8 +600,7 @@ sub main_exit { if ($user && !OVH::Bastion::is_valid_remote_user(user => $user, allowWildcards => ($osh_command ? 1 : 0))) { main_exit OVH::Bastion::EXIT_INVALID_REMOTE_USER, 'invalid_remote_user', "Remote user name '$user' seems invalid"; } -if ($host && $host !~ m{^[a-zA-Z0-9._/:-]+$}) { - +if ($host && $host !~ m{^\[?[a-zA-Z0-9._/:-]+\]?$}) { # can be an IP (v4 or v6), hostname, or prefix (with a /) main_exit OVH::Bastion::EXIT_INVALID_REMOTE_HOST, 'invalid_remote_host', "Remote host name '$host' seems invalid"; } @@ -612,7 +611,6 @@ sub main_exit { # if: avoid loading Net::IP and BigInt if there's no host specified if ($host) { - # probably this "host" is in fact an option, but we didn't parse it because it's an unknown one, # so we call the long_help() for the user, before exiting if ($host =~ m{^--}) { @@ -624,14 +622,17 @@ sub main_exit { $fnret = OVH::Bastion::get_ip(host => $host); } if (!$fnret) { - - # exit error when not osh ... + # exit error when not a plugin call if (!$osh_command) { - main_exit OVH::Bastion::EXIT_HOST_NOT_FOUND, 'host_not_found', "Unable to resolve host '$host' ($fnret)"; + main_exit OVH::Bastion::EXIT_HOST_NOT_FOUND, 'host_not_found', $fnret->msg; } elsif ($host && $host !~ m{^[0-9.:]+/\d+$}) # in some osh plugins, ip/mask is accepted, don't yell. { - osh_warn("I was unable to resolve host '$host'. Something shitty might happen."); + osh_warn($fnret->msg); + osh_warn("Trying to proceed with $osh_command anyway, but things might go wrong."); + if (index($host, ':') >= 0 && !OVH::Bastion::config('IPv6Allowed')->value) { + osh_warn("Note that '$host' looks like an IPv6 but IPv6 support has not been enabled."); + } } } else { @@ -1131,7 +1132,11 @@ sub main_exit { # log request osh_debug("final request : " . "$user\@$ip -p $port -- $command'\n"); -my $displayLine = "$hostfrom:$portfrom => $self\@$bastionhost:$bastionport => $user\@$hostto:$port"; +my $displayLine = sprintf("%s => %s => %s", + OVH::Bastion::machine_display(ip => $hostfrom, port => $portfrom)->value, + OVH::Bastion::machine_display(ip => $bastionhost, port => $bastionport, user => $self)->value, + OVH::Bastion::machine_display(ip => $hostto, port => $port, user => $user)->value, +); if (!$quiet) { osh_print("$displayLine ..."); diff --git a/doc/sphinx/administration/configuration/bastion_conf.rst b/doc/sphinx/administration/configuration/bastion_conf.rst index c1bc1550d..bc341c2af 100644 --- a/doc/sphinx/administration/configuration/bastion_conf.rst +++ b/doc/sphinx/administration/configuration/bastion_conf.rst @@ -51,6 +51,8 @@ Those options can set a few global network policies to be applied bastion-wide. - `allowedNetworks`_ - `forbiddenNetworks`_ - `ingressToEgressRules`_ +- `IPv4Allowed`_ +- `IPv6Allowed`_ Logging options --------------- @@ -282,7 +284,7 @@ defaultAccountEgressKeyAlgorithm :Type: ``string`` -:Default: ``"rsa"`` +:Default: ``"ecdsa"`` The default algorithm to use to create the egress key of a newly created account @@ -293,7 +295,7 @@ defaultAccountEgressKeySize :Type: ``int > 0`` -:Default: ``4096`` +:Default: ``521`` The default size to use to create the egress key of a newly created account (also see ``defaultAccountEgressKeyAlgorithm``) @@ -426,6 +428,28 @@ For example, take the following configuration: In any case, all the personal and group accesses still apply in addition to these global rules. +.. _IPv4Allowed: + +IPv4Allowed +*********** + +:Type: ``boolean`` + +:Default: ``true`` + +If enabled, IPv4 egress connections will be allowed, and IPv4 will be enabled in the DNS queries. This is the default. Do NOT disable this unless you enable IPv6Allowed, if you need to have an IPv6-only bastion. + +.. _IPv6Allowed: + +IPv6Allowed +*********** + +:Type: ``boolean`` + +:Default: ``false`` + +If enabled, IPv6 egress connections will be allowed, and IPv6 will be enabled in the DNS queries. By default, only IPv4 is allowed. + Logging ------- diff --git a/doc/sphinx/development/setup.rst b/doc/sphinx/development/setup.rst index e00926564..cd573ba99 100644 --- a/doc/sphinx/development/setup.rst +++ b/doc/sphinx/development/setup.rst @@ -145,7 +145,7 @@ to get up-to-date information): Usage: /home/user/bastion/tests/functional/launch_tests_on_instance.sh [OPTIONS] Test Options: - --skip-consistency-check Speed up tests by skipping the consistency check between every test + --consistency-check Check system consistency between every test --no-pause-on-fail Don't pause when a test fails --log-prefix=X Prefix all logs by this name --module=X Only test this module (specify a filename found in `functional/tests.d/`), can be specified multiple times diff --git a/etc/bastion/bastion.conf.dist b/etc/bastion/bastion.conf.dist index b16f7af13..ac74a7618 100644 --- a/etc/bastion/bastion.conf.dist +++ b/etc/bastion/bastion.conf.dist @@ -86,13 +86,13 @@ # # defaultAccountEgressKeyAlgorithm (string) # DESC: The default algorithm to use to create the egress key of a newly created account -# DEFAULT: "rsa" -"defaultAccountEgressKeyAlgorithm": "rsa", +# DEFAULT: "ecdsa" +"defaultAccountEgressKeyAlgorithm": "ecdsa", # # defaultAccountEgressKeySize (int > 0) # DESC: The default size to use to create the egress key of a newly created account (also see ``defaultAccountEgressKeyAlgorithm``) -# DEFAULT: 4096 -"defaultAccountEgressKeySize": 4096, +# DEFAULT: 521 +"defaultAccountEgressKeySize": 521, # # moshAllowed (boolean) # DESC: If set to ``true``, mosh usage is allowed (mosh needs to be installed on serverside, obviously). Otherwise, this feature is disabled. @@ -173,6 +173,16 @@ # DEFAULT: [] "ingressToEgressRules": [], # +# IPv4Allowed (boolean) +# DESC: If enabled, IPv4 egress connections will be allowed, and IPv4 will be enabled in the DNS queries. This is the default. Do NOT disable this unless you enable IPv6Allowed, if you need to have an IPv6-only bastion. +# DEFAULT: true +"IPv4Allowed": true, +# +# IPv6Allowed (boolean) +# DESC: If enabled, IPv6 egress connections will be allowed, and IPv6 will be enabled in the DNS queries. By default, only IPv4 is allowed. +# DEFAULT: false +"IPv6Allowed": false, +# ########### # > Logging # >> Options to customize how logs should be produced. diff --git a/lib/perl/OVH/Bastion.pm b/lib/perl/OVH/Bastion.pm index 1a0b495cf..769891963 100644 --- a/lib/perl/OVH/Bastion.pm +++ b/lib/perl/OVH/Bastion.pm @@ -650,45 +650,30 @@ sub is_valid_ip { my $allowPrefixes = $params{'allowPrefixes'}; # if not, a /24 or /32 notation is rejected my $fast = $params{'fast'}; # fast mode: avoid instantiating Net::IP... except if ipv6 - if ($fast and $ip !~ m{:}) { - # fast asked and it's not an IPv6, regex ftw - ## no critic (ProhibitUnusedCapture) - if ( - $ip =~ m{^(? - (?[0-9]{1,3}) - \. - (?[0-9]{1,3}) - \. - (?[0-9]{1,3}) - \. - (?[0-9]{1,3}) - ) - ( - (?/) - (?\d+) - )? - $}x - ) - { - if (defined $+{'prefix'} and not $allowPrefixes) { + if ($fast and index($ip, ':') == -1) { + # We're being asked to be fast, and it's not an IPv6, just use a regex + # and don't instanciate a Net::IP. Also don't use named captures, as they're slower + if ($ip =~ m{^(([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3}))(/([0-9]{1,2}))?$}) { + if (defined $7 and not $allowPrefixes) { return R('KO_INVALID_IP', msg => "Invalid IP address ($ip), as prefixes are not allowed"); } - foreach my $key (qw{ x1 x2 x3 x4 }) { - return R('KO_INVALID_IP', msg => "Invalid IP address ($ip)") - if (not defined $+{$key} or $+{$key} > 255); - } - if (defined $+{'prefix'} and $+{'prefix'} > 32) { - return R('KO_INVALID_IP', msg => "Invalid IP address ($ip)"); - } - if (defined $+{'slash'} and not defined $+{'prefix'}) { - # got a / in $ip but it's not followed by \d+ + if ($2 > 255 || $3 > 255 || $4 > 255 || $5 > 255) { return R('KO_INVALID_IP', msg => "Invalid IP address ($ip)"); } - - if (defined $+{'prefix'} && $+{'prefix'} != 32) { - return R('OK', value => {ip => $ip, prefix => $+{'prefix'}}); + # int() to remove useless zeroes such as 01.002.003.04 => 1.2.3.4 + my $dottedquad = int($2) . '.' . int($3) . '.' . int($4) . '.' . int($5); + if (defined $7) { + if ($7 > 32) { + return R('KO_INVALID_IP', msg => "Invalid IP address ($ip)"); + } + # valid prefix? + if (((2**24 * $2 + 2**16 * $3 + 2**8 * $4 + $5) & (2**(32 - $7) - 1)) != 0) { + return R('KO_INVALID_IP', msg => "Invalid prefix ($7) for this IP address ($ip)"); + } + return R('OK', value => {ip => "$dottedquad/$7", prefix => $7}) if ($7 != 32); + # if prefix == 32, fallthrough and return the $dottedquad as a single IP below } - return R('OK', value => {ip => $+{'shortip'}}); + return R('OK', value => {ip => $dottedquad}); } return R('KO_INVALID_IP', msg => "Invalid IP address ($ip)"); } @@ -700,15 +685,26 @@ sub is_valid_ip { return R('KO_INVALID_IP', msg => "Invalid IP address ($ip)"); } - my $shortip = $IpObject->prefix; - - # if /32 or /128, omit the /prefixlen on $shortip - my $type = 'prefix'; - if ( ($IpObject->version == 4 and $IpObject->prefixlen == 32) - or ($IpObject->version == 6 and $IpObject->prefixlen == 128)) - { - $shortip =~ s'/\d+$''; - $type = 'single'; + my ($shortip, $type); + if ($IpObject->version == 4) { + if ($IpObject->prefixlen == 32) { + $shortip = $IpObject->ip; + $type = 'single'; + } + else { + $shortip = $IpObject->prefix; + $type = 'prefix'; + } + } + elsif ($IpObject->version == 6) { + if ($IpObject->prefixlen == 128) { + $shortip = $IpObject->short; + $type = 'single'; + } + else { + $shortip = $IpObject->short . '/' . $IpObject->prefixlen; + $type = 'prefix'; + } } if (not $allowPrefixes and $type eq 'prefix') { @@ -750,6 +746,19 @@ sub is_valid_remote_user { return R('ERR_INVALID_PARAMETER', msg => "Specified user doesn't seem to be valid"); } +sub machine_display { + my %params = @_; + my $ip = $params{'ip'}; + my $port = $params{'port'}; + my $user = $params{'user'}; + + my $machine = (index($ip, ':') >= 0 ? "[$ip]" : $ip); + $machine .= ":$port" if $port; + $machine = $user . '@' . $machine if $user; + + return R('OK', value => $machine); +} + sub touch_file { my $file = shift; my $perms = shift; @@ -1112,6 +1121,12 @@ sub build_ttyrec_cmdline_part1of2 { return R('ERR_MISSING_PARAMETER', msg => "Missing ip parameter"); } + # if ip is an IPv6, replace :'s by .'s and surround by v6[]'s (which is allowed on all filesystems) + if ($params{'ip'} && index($params{'ip'}, ':') >= 0) { + $params{'ip'} =~ tr/:/./; + $params{'ip'} = 'v6[' . $params{'ip'} . ']'; + } + # build ttyrec filename format my $bastionName = OVH::Bastion::config('bastionName')->value; my $ttyrecFilenameFormat = OVH::Bastion::config('ttyrecFilenameFormat')->value; diff --git a/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm b/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm index 018733b2f..ecd2baaa2 100644 --- a/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm +++ b/lib/perl/OVH/Bastion/Plugin/groupSetRole.pm @@ -206,10 +206,12 @@ sub act { # foreach guest access, delete foreach my $access (@acl) { - my $machine = $access->{'ip'}; - $machine .= ':' . $access->{'port'} if defined $access->{'port'}; - $machine = $access->{'user'} . '@' . $machine if defined $access->{'user'}; - $fnret = OVH::Bastion::Plugin::groupSetRole::act( + my $machine = OVH::Bastion::machine_display( + ip => $access->{'ip'}, + port => $access->{'port'}, + user => $access->{'user'} + )->value; + $fnret = OVH::Bastion::Plugin::groupSetRole::act( account => $account, group => $shortGroup, action => 'del', @@ -251,9 +253,7 @@ sub act { # in that case, we need to handle the add/del of the guest access to $user@$host:$port # check if group has access to $user@$ip:$port - my $machine = $host; - $port and $machine .= ":$port"; - $user and $machine = $user . '@' . $machine; + my $machine = OVH::Bastion::machine_display(ip => $host, port => $port, user => $user)->value; osh_debug( "groupSetRole::act, checking if group $group has access to $machine to $action $type access to $account"); diff --git a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm index bd8923746..206a452d4 100644 --- a/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm +++ b/lib/perl/OVH/Bastion/Plugin/otherProtocol.pm @@ -27,7 +27,7 @@ sub has_protocol_access { return R('ERR_MISSING_PARAMETERS', msg => "Missing mandatory parameters for has_protocol_access"); } - my $machine = "$user\@$ip:$port"; + my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value; my %keys; osh_debug("Checking access 1/2 of $account to $machine..."); diff --git a/lib/perl/OVH/Bastion/allowdeny.inc b/lib/perl/OVH/Bastion/allowdeny.inc index 8fc7f7adb..cbcfaa31b 100644 --- a/lib/perl/OVH/Bastion/allowdeny.inc +++ b/lib/perl/OVH/Bastion/allowdeny.inc @@ -280,19 +280,19 @@ sub is_access_way_granted { sub get_ip { my %params = @_; my $host = $params{'host'}; - my $v4 = $params{'v4'}; # allow ipv4 ? - my $v6 = $params{'v6'}; # allow ipv6 ? + my $v4 = $params{'v4'} // OVH::Bastion::config('IPv4Allowed')->value; + my $v6 = $params{'v6'} // OVH::Bastion::config('IPv6Allowed')->value; if (!$host) { return R('ERR_MISSING_PARAMETER', msg => "Missing parameter 'host'"); } - # by default, only v4 unless specified otherwise - $v4 = 1 if not defined $v4; - $v6 = 0 if not defined $v6; + # if v4 or v6 are disabled in config, force-disable them here too + $v4 = 0 if !OVH::Bastion::config('IPv4Allowed')->value; + $v6 = 0 if !OVH::Bastion::config('IPv6Allowed')->value; # try to see if it's already an IP - osh_debug("checking if '$host' is already an IP"); + osh_debug("checking if '$host' is already an IP (v4=$v4 v6=$v6)"); my $fnret = OVH::Bastion::is_valid_ip(ip => $host, allowPrefixes => 0); if ($fnret) { osh_debug("Host $host is already an IP"); @@ -301,7 +301,7 @@ sub get_ip { { return R('OK', value => {ip => $fnret->value->{'ip'}, iplist => [$fnret->value->{'ip'}]}); } - return R('ERR_INVALID_IP', msg => "IP $host version is not allowed"); + return R('ERR_INVALID_IP', msg => "IP $host version (IPv" . $fnret->value->{'version'} . ") is not allowed"); } if (OVH::Bastion::config('dnsSupportLevel')->value < 1) { @@ -309,26 +309,33 @@ sub get_ip { } osh_debug("Trying to resolve '$host' because is_valid_ip() says it's not an IP"); - my ($err, @res); - eval { + my ($err, @res) = eval { # dns resolving, v4/v6 compatible # can croak - ($err, @res) = getaddrinfo($host, undef, {socktype => SOCK_STREAM}); + getaddrinfo($host, undef, {socktype => SOCK_STREAM}); }; - return R('ERR_HOST_NOT_FOUND', msg => $@) if $@; - return R('ERR_HOST_NOT_FOUND', msg => $err) if $err; + return R('ERR_HOST_NOT_FOUND', msg => "Unable to resolve '$host' ($@)") if $@; + return R('ERR_HOST_NOT_FOUND', msg => "Unable to resolve '$host' ($err)") if $err; my %iplist; my $lastip; + my $skippedcount = 0; foreach my $item (@res) { if ($item->{'family'} == AF_INET) { - next if not $v4; + if (!$v4) { + $skippedcount++; + next; + } } elsif ($item->{'family'} == AF_INET6) { - next if not $v6; + if (!$v6) { + $skippedcount++; + next; + } } else { # unknown weird family ? + $skippedcount++; next; } my $as_text; @@ -347,6 +354,9 @@ sub get_ip { } # %iplist empty, not resolved (?) + return R('ERR_HOST_NOT_FOUND', + msg => "Unable to resolve '$host' (some IPv4 and/or IPv6 were skipped due to policy)") + if $skippedcount; return R('ERR_HOST_NOT_FOUND', msg => "Unable to resolve '$host'"); } @@ -918,9 +928,7 @@ sub is_access_granted { return R('OK', value => \@grants) if @grants; - my $machine = $ip; - $machine .= ":$port" if $port; - $machine = $user . '@' . $machine if $user; + my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value; return R('KO_ACCESS_DENIED', msg => "Access denied for $account to $machine"); } diff --git a/lib/perl/OVH/Bastion/allowkeeper.inc b/lib/perl/OVH/Bastion/allowkeeper.inc index 68339877a..1c24bb072 100644 --- a/lib/perl/OVH/Bastion/allowkeeper.inc +++ b/lib/perl/OVH/Bastion/allowkeeper.inc @@ -582,10 +582,8 @@ sub access_modify { } # build the line we're either adding or looking for (to delete it) - my $entry = $ip; - $entry = $user . "@" . $entry if defined $user; - $entry = $entry . ":" . $port if defined $port; - my $machine = $entry; + my $machine = OVH::Bastion::machine_display(ip => $ip, port => $port, user => $user)->value; + my $entry = $machine; my $t = localtime(time); my $fmt = "%Y-%m-%d %H:%M:%S"; @@ -594,11 +592,9 @@ sub access_modify { # if we're adding it, append other parameters as comments if ($action eq 'add') { - $entry .= " $entryComment"; if ($forceKey) { - # hash is case-sensitive only for new SHA256 format $forceKey = lc($forceKey) if ($forceKey !~ /^sha256:/i); $entry .= " # FORCEKEY=" . $forceKey; diff --git a/lib/perl/OVH/Bastion/configuration.inc b/lib/perl/OVH/Bastion/configuration.inc index 1382c9441..0009f9739 100644 --- a/lib/perl/OVH/Bastion/configuration.inc +++ b/lib/perl/OVH/Bastion/configuration.inc @@ -265,7 +265,7 @@ sub load_configuration { options => [ qw{ enableSyslog enableGlobalAccessLog enableAccountAccessLog enableGlobalSqlLog enableAccountSqlLog displayLastLogin - interactiveModeByDefault interactiveModeProactiveMFAenabled + interactiveModeByDefault interactiveModeProactiveMFAenabled IPv4Allowed } ], }, @@ -275,7 +275,7 @@ sub load_configuration { qw{ interactiveModeAllowed readOnlySlaveMode sshClientHasOptionE ingressKeysFromAllowOverride moshAllowed debug keyboardInteractiveAllowed passwordAllowed telnetAllowed remoteCommandEscapeByDefault - accountExternalValidationDenyOnFailure ingressRequirePIV + accountExternalValidationDenyOnFailure ingressRequirePIV IPv6Allowed } ], } @@ -325,7 +325,7 @@ sub load_configuration { # 4/6) Strings that must be one item of a specific enum. foreach my $o ( - {name => 'defaultAccountEgressKeyAlgorithm', default => 'rsa', valid => [qw{ rsa ecdsa ed25519 }]}, + {name => 'defaultAccountEgressKeyAlgorithm', default => 'ecdsa', valid => [qw{ rsa ecdsa ed25519 }]}, { name => 'accountMFAPolicy', default => 'enabled', diff --git a/tests/functional/docker/docker_build_and_run_tests.sh b/tests/functional/docker/docker_build_and_run_tests.sh index 8602d7a8e..815ed83d9 100755 --- a/tests/functional/docker/docker_build_and_run_tests.sh +++ b/tests/functional/docker/docker_build_and_run_tests.sh @@ -123,7 +123,13 @@ docker rm -f "bastion_${target}_tester" 2>/dev/null || true if docker inspect "bastion-$target" >/dev/null 2>&1; then docker network rm "bastion-$target" >/dev/null fi -docker network create "bastion-$target" >/dev/null + +# trying with IPv6 +if ! docker network create --ipv6 --subnet fd42:cafe:efac:"$(printf "%x" $RANDOM)"::/64 "bastion-$target" >/dev/null; then + # didn't work... retry without IPv6 + echo "... IPv6 is not enabled in docker daemon, falling back to IPv4-only network" + docker network create "bastion-$target" >/dev/null +fi # run target but force entrypoint to test one, and add some keys in env (will be shared with tester) echo "Starting target instance" diff --git a/tests/functional/docker/target_role.sh b/tests/functional/docker/target_role.sh index c1e68ead1..c1daf9f1b 100755 --- a/tests/functional/docker/target_role.sh +++ b/tests/functional/docker/target_role.sh @@ -74,8 +74,7 @@ if [ -x "$bashlocation" ]; then chsh -s "$bashlocation" "$UID0" fi -HOME="$UID0HOME" USER="$UID0" "$basedir"/bin/plugin/restricted/accountCreate '' '' '' '' --uid 5000 --account "$TARGET_USER" --public-key "$user_pubkey FOR_TESTS_ONLY" -HOME="$UID0HOME" USER="$UID0" "$basedir"/bin/plugin/restricted/accountGrantCommand '' '' '' '' --account "$TARGET_USER" --command accountGrantCommand +"$basedir"/bin/admin/setup-first-admin-account.sh "$TARGET_USER" 5000 <<< "$user_pubkey FOR_TESTS_ONLY" # add an account with local shell access (to mimic a remote server) useradd_compat test-shell_ "" "" /bin/sh @@ -126,7 +125,7 @@ if [ "$OS_FAMILY" = Linux ] ; then elif [ "$OS_FAMILY" = OpenBSD ] || [ "$OS_FAMILY" = FreeBSD ] || [ "$OS_FAMILY" = NetBSD ] ; then # setup some 127.0.0.x IPs (needed for our tests) - # this automatically works under Linux on lo + # this is not required under Linux where all IPs of 127.0.0.0/8 implicitely work nic=$(ifconfig | perl -ne 'm{^([a-z._0-9]+): flags}i and $nic=$1; m{inet 127\.0\.0\.1} and print $nic and exit') : "${nic:=lo0}" i=2 diff --git a/tests/functional/launch_tests_on_instance.sh b/tests/functional/launch_tests_on_instance.sh index 8b9dee8ab..64a412411 100755 --- a/tests/functional/launch_tests_on_instance.sh +++ b/tests/functional/launch_tests_on_instance.sh @@ -14,7 +14,7 @@ basedir=$(readlink -f "$(dirname "$0")"/../..) opt_remote_etc_bastion=/etc/bastion opt_remote_basedir=$basedir -opt_skip_consistency_check=0 +opt_consistency_check=0 opt_no_pause_on_fail=0 opt_slowness_factor=1 opt_log_prefix= @@ -25,7 +25,7 @@ declare -A capabilities=( [ed25519]=1 [mfa]=1 [mfa-password]=0 [pamtester]=1 [pi # set the helptext now to get the proper default values help_text=$(cat <"$mytmpdir/ssh_config" < "$tmp_b" - success module_postrun test "$module_ret" = 0 - + # ensure the module exited successfully + success module_postrun_exit_status test "$module_ret" = 0 # put the backed up configuration back after each module, just in case the module modified it - modulename=main - success configrestore $r0 "dd if=$opt_remote_etc_bastion/bastion.conf.bak.$now of=$opt_remote_etc_bastion/bastion.conf" + success module_postrun_config_restore $r0 "dd if=$opt_remote_etc_bastion/bastion.conf.bak.$now of=$opt_remote_etc_bastion/bastion.conf" # verify that the env hasn't been modified - success check_env_after_module diff -u "$tmp_a" "$tmp_b" + success module_postrun_check_env diff -u "$tmp_a" "$tmp_b" done # if the check_env_after_module of the last module fails, we wouldn't get the verbose error, diff --git a/tests/functional/tests.d/200-scripts.sh b/tests/functional/tests.d/200-scripts.sh index 6c7d59b77..eb72c2c1e 100644 --- a/tests/functional/tests.d/200-scripts.sh +++ b/tests/functional/tests.d/200-scripts.sh @@ -126,19 +126,12 @@ testsuite_scripts() # create and account and connect one to have a ttyrec file - grant accountCreate - success a0_create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate .value null - revoke accountCreate - grant accountAddPersonalAccess - success a0_allow_a1 $a0 --osh accountAddPersonalAccess --account $account1 --host 127.0.0.1 --user none --port 22 json .error_code OK .command accountAddPersonalAccess - revoke accountAddPersonalAccess - run a1_connect $a1 none@127.0.0.1 # encrypt rsync (one file to encrypt) @@ -155,11 +148,7 @@ testsuite_scripts() # cleanup account - grant accountDelete - success a0_delete_a1 $a0 --osh accountDelete --account $account1 --no-confirm - - revoke accountDelete } testsuite_scripts diff --git a/tests/functional/tests.d/300-activeness.sh b/tests/functional/tests.d/300-activeness.sh index eef886fe0..cf129b97f 100644 --- a/tests/functional/tests.d/300-activeness.sh +++ b/tests/functional/tests.d/300-activeness.sh @@ -7,7 +7,6 @@ testsuite_activeness() { - grant accountCreate # create account1 on local bastion success create_account1 $a0 --osh accountCreate --account $account1 --uid $uid1 --public-key \""$(cat $account1key1file.pub)"\" json .error_code OK .command accountCreate .value null @@ -18,8 +17,6 @@ testsuite_activeness() success create_account3 $a0 --osh accountCreate --account $account3 --uid $uid3 --always-active --public-key \""$(cat $account3key1file.pub)"\" json .error_code OK .command accountCreate .value null - revoke accountCreate - configchg 's=^\\\\x22accountExternalValidationProgram\\\\x22.+=\\\\x22accountExternalValidationProgram\\\\x22:\\\\x22/opt/bastion/bin/other/doesnotexist.pl\\\\x22,=' success test_invalid_config_but_always_active $a3 --osh info @@ -42,13 +39,9 @@ testsuite_activeness() # SSH-AS - grant accountAddPersonalAccess - # allow account1 to localhost, just so that ssh-as calls connect.pl (even if the connection doesn't make it through in the end) success add_access_to_a1 $a0 --osh accountAddPersonalAccess --account $account2 --host 127.0.0.1 --user sshas --port 22 - revoke accountAddPersonalAccess - # now, test ssh-as run ssh_as_denied $a1 --ssh-as $account2 sshas@127.0.0.1 retvalshouldbe 106 @@ -71,8 +64,6 @@ testsuite_activeness() # /SSH-AS - grant accountDelete - # delete account1 success account1_cleanup $a0 --osh accountDelete --account $account1 --no-confirm @@ -82,8 +73,6 @@ testsuite_activeness() # delete account3 success account3_cleanup $a0 --osh accountDelete --account $account3 --no-confirm - - revoke accountDelete } testsuite_activeness diff --git a/tests/functional/tests.d/305-admin-superowner.sh b/tests/functional/tests.d/305-admin-superowner.sh index 3e26e39a3..0a1b71863 100644 --- a/tests/functional/tests.d/305-admin-superowner.sh +++ b/tests/functional/tests.d/305-admin-superowner.sh @@ -7,9 +7,6 @@ testsuite_admin_superowner() { - grant accountCreate - grant groupCreate - # create account1 success create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate .value null @@ -71,11 +68,6 @@ testsuite_admin_superowner() retvalshouldbe 106 json .error_code KO_RESTRICTED_COMMAND .command null - revoke accountCreate - revoke groupCreate - grant accountDelete - grant groupDelete - script delete_a1 $a0 --osh accountDelete --account $account1 "<<< \"Yes, do as I say and delete $account1, kthxbye\"" retvalshouldbe 0 json .command accountDelete .error_code OK @@ -83,9 +75,6 @@ testsuite_admin_superowner() script delete_g1 "$a0 --osh groupDelete --group $group1 <<< $group1" retvalshouldbe 0 json .command groupDelete .error_code OK - - revoke accountDelete - revoke groupDelete } testsuite_admin_superowner diff --git a/tests/functional/tests.d/310-realm.sh b/tests/functional/tests.d/310-realm.sh index 6853b2e9a..7b79b6e36 100644 --- a/tests/functional/tests.d/310-realm.sh +++ b/tests/functional/tests.d/310-realm.sh @@ -10,9 +10,6 @@ testsuite_realm() local realm_egress_group=realm local realm_shared_account=UniVerse - grant accountCreate - grant accountModify - # create account1 on local bastion success create_account1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key \""$(cat $account1key1file.pub)"\" json .error_code OK .command accountCreate .value null @@ -25,9 +22,6 @@ testsuite_realm() success modify_account1 $a0 --osh accountModify --pam-auth-bypass yes --account $account2 json .error_code OK .command accountModify - revoke accountModify - grant groupCreate - # create realm-egress group on local bastion success create_support_group $a0 --osh groupCreate --group $realm_egress_group --owner $account0 --algo rsa --size 4096 local realm_group_key @@ -41,8 +35,6 @@ testsuite_realm() # add account1 to this group on local bastion success add_account2_to_support_group $a0 --osh groupAddMember --group $realm_egress_group --account $account2 - grant realmCreate - # fail to create a realm with forbidden name plgfail realm_forbidden_name $a0 --osh realmCreate --realm realm --from 0.0.0.0/0 --public-key \"$realm_group_key\" @@ -52,9 +44,6 @@ testsuite_realm() # create shared realm-account on remote bastion success create_shared_account $a0 --osh realmCreate --realm $realm_shared_account --public-key \"$realm_group_key\" --from 0.0.0.0/0 - revoke accountCreate - revoke realmCreate - # add remote bastion ip on group of local bastion success add_remote_bastion_to_group $a0 --osh groupAddServer --host 127.0.0.1 --user realm_$realm_shared_account --port 22 --group $realm_egress_group --kbd-interactive @@ -75,8 +64,6 @@ testsuite_realm() done unset plugin - grant accountAddPersonalAccess - # add an access to account1 from realm on remote bastion success add_access_to_remote $a0 --osh accountAddPersonalAccess --account $realm_shared_account/$account1 --user-any --port-any --host 127.0.0.5 json .error_code OK @@ -151,7 +138,6 @@ testsuite_realm() success removemyselffromaclk $a0 --osh groupDelAclkeeper --group $group1 --account $account0 success a0_delowner_group1 $a0 --osh groupDelOwner --group $group1 --account $account0 - grant accountListAccesses # check access list success access_list_account1 $a0 --osh accountListAccesses --account $realm_shared_account/$account1 @@ -238,8 +224,6 @@ testsuite_realm() # check max account length success add_guest_account3 $a0 --osh groupAddGuestAccess --account $realm_shared_account/verylongaccountnam --group $group1 --host 172.16.4.4 --user nobody --port 12345 - grant accountDelete - # delete account1 success account1_cleanup $a0 --osh accountDelete --account $account1 --no-confirm @@ -247,35 +231,21 @@ testsuite_realm() script account2_cleanup "$a0 --osh accountDelete --account $account2 <<< \"Yes, do as I say and delete $account2, kthxbye\"" retvalshouldbe 0 - revoke accountDelete - grant groupDelete - # delete realm-egress group run cleanup_realm_support_group $a0 --osh groupDelete --group $realm_egress_group --no-confirm retvalshouldbe 0 - revoke groupDelete - grant accountDelete - # delete shared realm-account script cleanup_shared_realm_account_fail "$a0 --osh accountDelete --account realm_$realm_shared_account <<< \"Yes, do as I say and delete realm_$realm_shared_account, kthxbye\"" retvalshouldbe 100 json .error_code KO_FORBIDDEN_PREFIX - grant realmDelete - script cleanup_shared_realm_account "$a0 --osh realmDelete --realm $realm_shared_account <<< \"Yes, do as I say and delete $realm_shared_account, kthxbye\"" retvalshouldbe 0 - revoke realmDelete - revoke accountDelete - grant groupDelete - # delete group1 script group_cleanup "$a0 --osh groupDelete --group $group1 <<< \"$group1\"" retvalshouldbe 0 - - revoke groupDelete } testsuite_realm diff --git a/tests/functional/tests.d/320-base.sh b/tests/functional/tests.d/320-base.sh index 5d7e149f3..be3fc6fe6 100644 --- a/tests/functional/tests.d/320-base.sh +++ b/tests/functional/tests.d/320-base.sh @@ -7,11 +7,9 @@ testsuite_base() { - grant accountCreate # create regular account to compare info access between auditor and non auditor success a0_create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate .value null - revoke accountCreate # basic stuff and help run nocmd $a0 @@ -36,30 +34,22 @@ testsuite_base() contain "Unknown command" json .error_code KO_UNKNOWN_COMMAND .command null .value null - # grant account0 as admin - success set_a0_as_admin $r0 "\". $opt_remote_basedir/lib/shell/functions.inc; add_user_to_group_compat $account0 osh-admin\"" - configchg 's=^\\\\x22adminAccounts\\\\x22.+=\\\\x22adminAccounts\\\\x22:[\\\\x22'"$account0"'\\\\x22],=' - # grant account1 as auditor - success accountGrantAuditor $a0 --osh accountGrantCommand --command auditor --account $account1 + # a1 is not auditor, won't seem the admins/superowners success info $a1 --osh info contain "Your alias to connect" - contain "My admins are: " - contain "My super owners are: " - json .error_code OK .command info .value.account $account1 .value.adminAccounts '["'"$account0"'"]' - + nocontain "My admins are: " + nocontain "My super owners are: " + json .error_code OK .command info .value.account $account1 - # now check that regular user do not see admins list + # now check that an admin can see the admins/superowners success info $a0 -osh info contain "Your alias to connect" - nocontain "My admins are: " - nocontain "My super owners are: " - json .error_code OK .command info .value.account $account0 + contain "My admins are: " + contain "My super owners are: " + json .error_code OK .command info .value.account $account0 .value.adminAccounts '["'"$account0"'"]' # delete account1 - grant accountDelete success delete_a1 $a0 --osh accountDelete --account $account1 --no-confirm - revoke accountDelete - } testsuite_base diff --git a/tests/functional/tests.d/325-accountinfo.sh b/tests/functional/tests.d/325-accountinfo.sh index 942671fb8..e1d77dffe 100644 --- a/tests/functional/tests.d/325-accountinfo.sh +++ b/tests/functional/tests.d/325-accountinfo.sh @@ -7,7 +7,6 @@ testsuite_accountinfo() { - grant accountCreate # create regular account to compare info access between auditor and non auditor success a0_create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate .value null @@ -17,48 +16,56 @@ testsuite_accountinfo() json .error_code OK .command accountCreate .value null # create a third account with a ttl - local ttl_account_created_at - ttl_account_created_at=$(date +%s) local ttl_account_seconds ttl_account_seconds=55 success a0_create_a3 $a0 --osh accountCreate --always-active --account $account3 --uid $uid3 --public-key "\"$(cat $account3key1file.pub)\"" --ttl ${ttl_account_seconds}s json .error_code OK .command accountCreate .value null - - revoke accountCreate + local ttl_account_created_at + ttl_account_created_at=$(date +%s) # check that account3 can connect during their TTL success a3_ttl_connect $a3 --osh info json .error_code OK - # grant account0 as admin - success set_a0_as_admin $r0 "\". $opt_remote_basedir/lib/shell/functions.inc; add_user_to_group_compat $account0 osh-admin\"" - configchg 's=^\\\\x22adminAccounts\\\\x22.+=\\\\x22adminAccounts\\\\x22:[\\\\x22'"$account0"'\\\\x22],=' - - # grant account1 as auditor - success a0_grant_a1_as_auditor $a0 --osh accountGrantCommand --command auditor --account $account1 - - # grant accountInfo to a0 and a1 - success a0_grant_a0_accountinfo $a0 --osh accountGrantCommand --command accountInfo --account $account0 + # grant accountInfo to a1 success a0_grant_a1_accountinfo $a0 --osh accountGrantCommand --command accountInfo --account $account1 # check that account3 info has the ttl in it - success a1_info_a3_ttl $a1 --osh accountInfo --account $account3 + success a0_info_a3_ttl $a0 --osh accountInfo --account $account3 json .error_code OK .value.is_ttl_expired 0 - # a0 should see basic info about a2 - success a0_accountinfo_a2_basic $a0 --osh accountInfo --account $account2 + # a1 should see basic info about a2 + success a1_accountinfo_a2_basic $a1 --osh accountInfo --account $account2 json_document '{"error_message":"OK","command":"accountInfo","error_code":"OK","value":{"account":"'"$account2"'","always_active":1,"always_active_reason":"account local configuration","is_active":1,"allowed_commands":[],"groups":{}}}' - # a1 should see detailed info about a2 - success a1_accountinfo_a2_detailed $a1 --osh accountInfo --account $account2 --with-mfa-password-info - json .error_code OK .command accountInfo .value.always_active 1 .value.is_active 1 .value.allowed_commands "[]" - json .value.ingress_piv_policy null .value.personal_egress_mfa_required none .value.pam_auth_bypass 0 - json .value.password.min_days 0 .value.password.user "$account2" .value.password.password locked - json .value.password.inactive_days -1 .value.password.date_disabled null .value.password.date_disabled_timestamp 0 - json .value.ingress_piv_enforced 0 .value.always_active 1 .value.creation_information.by "$account0" + # a0 should see detailed info about a2 + success a0_accountinfo_a2_detailed $a0 --osh accountInfo --account $account2 --with-mfa-password-info + json $(cat < - grant accountInfo - grant auditor - configchg 's=^\\\\x22minimumIngressRsaKeySize\\\\x22.+=\\\\x22minimumIngressRsaKeySize\\\\x22:4096,=' success info0 $a0 --osh accountInfo --account $account1 @@ -121,16 +112,11 @@ testsuite_selfkeys() json .error_code OK .command accountInfo json .value.account_egress_ssh_config.type default - revoke auditor - revoke accountInfo # success modify_account1 $a0 --osh accountModify --pam-auth-bypass yes --account $account1 json .error_code OK .command accountModify - revoke accountModify - grant accountListEgressKeys - success accountListEgressKeys $a0 --osh accountListEgressKeys --account $account1 contain "keyline" json .error_code OK .command accountListEgressKeys @@ -139,10 +125,10 @@ testsuite_selfkeys() tmpfp=$(get_json | $jq '.value|keys[0]') set -e json $(cat < $opt_remote_etc_bastion/plugin.accountAddPersonalAccess.conf \; chmod o+r $opt_remote_etc_bastion/plugin.accountAddPersonalAccess.conf" plgfail accountAddPersonalAccess_self_remote_user_only $a0 --osh accountAddPersonalAccess --host 127.0.0.9 --user notme --port-any --account $account1 @@ -154,7 +136,6 @@ testsuite_selfaccesses() success accountAddPersonalAccess_delconfig $r0 "rm -f $opt_remote_etc_bastion/plugin.accountAddPersonalAccess.conf" - revoke accountAddPersonalAccess # /test (self|account)AddPersonalAccess config items success withttl $a0 -osh selfAddPersonalAccess -h 127.0.0.4 -u $shellaccount -p 22 --force --ttl 0d0h0m3s @@ -523,8 +504,6 @@ testsuite_selfaccesses() success dupe $a0 --osh selfForgetHostKey --host 127.0.0.2 json .command selfForgetHostKey .error_code OK '.value."127.0.0.2".action' OK_NO_MATCH - grant accountUnexpire - success nochange $a0 --osh accountUnexpire --account $account1 json .command accountUnexpire .error_code OK_NO_CHANGE @@ -550,13 +529,9 @@ testsuite_selfaccesses() success worksnochange $a0 --osh accountUnexpire --account $account1 json .command accountUnexpire .error_code OK_NO_CHANGE - revoke accountUnexpire - # delete account1 - grant accountDelete script cleanup $a0 --osh accountDelete --account $account1 "<<< \"Yes, do as I say and delete $account1, kthxbye\"" retvalshouldbe 0 - revoke accountDelete } testsuite_selfaccesses diff --git a/tests/functional/tests.d/341-selfaccesses-force-password.sh b/tests/functional/tests.d/341-selfaccesses-force-password.sh index fdf3bab83..228efca40 100644 --- a/tests/functional/tests.d/341-selfaccesses-force-password.sh +++ b/tests/functional/tests.d/341-selfaccesses-force-password.sh @@ -8,16 +8,12 @@ testsuite_selfaccesses_force_password() { # create account4, it will be used as an egress target to test password connections - grant accountCreate success a4_create $a0 --osh accountCreate --always-active --account $account4 --uid $uid4 --public-key "\"$(cat $account4key1file.pub)\"" json .error_code OK .command accountCreate - revoke accountCreate # set account4 to require a password - grant accountModify success a4_setup_passreq $a0 --osh accountModify --account $account4 --mfa-password-required yes json .error_code OK .command accountModify .value.mfa_password_required.error_code OK - revoke accountModify # set a4's ingress password a4_password="276r8q76ZF5Y3" @@ -41,10 +37,8 @@ testsuite_selfaccesses_force_password() json .error_code OK .command selfMFASetupPassword # disable account4's pubkey requirement because autologin doesn't handle that - grant accountModify success a4_unset_pubkey $a0 --osh accountModify --account $account4 --pubkey-auth-optional yes json .error_code OK .command accountModify .value.pubkey_auth_optional.error_code OK - revoke accountModify # on non-mfa systems, temporarily update sshd's config to accept passwords for account4 if [ "${capabilities[mfa]}" = 0 ] && [ "${capabilities[mfa-password]}" = 0 ] @@ -62,10 +56,8 @@ testsuite_selfaccesses_force_password() for mode in personal group-member do # create account1, it will be used to connect to account4 - grant accountCreate success a1_create $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate - revoke accountCreate local target gen_pass_plugin list_pass_plugin add_access_plugin del_access_plugin password_switch password_base_path if [ $mode = "personal" ] @@ -78,12 +70,6 @@ testsuite_selfaccesses_force_password() del_access_plugin="accountDelPersonalAccess" password_switch="-P" password_base_path="/home/${account1}/pass/${account1}" - - # a few rights are needed - grant accountGeneratePassword - grant accountListPasswords - grant accountAddPersonalAccess - grant accountDelPersonalAccess else # group-member # in group-member mode, account1 is a member of group1 and we manipulate group1's accesses to connect to account4 target="--group ${group1}" @@ -95,10 +81,8 @@ testsuite_selfaccesses_force_password() password_base_path="/home/key${group1}/pass/${group1}" # create group1 - grant groupCreate success g1_create $a0 --osh groupCreate --group $group1 --owner $account0 --no-key json .error_code OK .command groupCreate - revoke groupCreate # add account1 as member success g1_member_a1 $a0 --osh groupAddMember --group $group1 --account $account1 @@ -122,12 +106,10 @@ testsuite_selfaccesses_force_password() success ${mode}_add_a4_fp_fake $a0 --osh $add_access_plugin $target --host $remote_ip --user $account4 --port $remote_port --force-password "'${fake_hash}'" json .error_code OK .command $add_access_plugin - grant accountListAccesses success ${mode}_listaccess $a0 --osh accountListAccesses --account $account1 json .error_code OK .command accountListAccesses contain "FORCED-PASSWORD" json .value[0].acl[0].forcePassword $fake_hash - revoke accountListAccesses success ${mode}_del_a4_fp_fake $a0 --osh $del_access_plugin $target --host $remote_ip --user $account4 --port $remote_port json .error_code OK .command $del_access_plugin @@ -206,31 +188,19 @@ testsuite_selfaccesses_force_password() json .error_code OK .command $del_access_plugin # cleanup - if test $mode = "personal" - then - revoke accountGeneratePassword - revoke accountListPasswords - revoke accountAddPersonalAccess - revoke accountDelPersonalAccess - else - grant groupDelete + if [ $mode = "group-member" ]; then success ${mode}_delete_g1 $a0 --osh groupDelete --group $group1 --no-confirm json .error_code OK .command groupDelete - revoke groupDelete fi # cleanup account1 - grant accountDelete success ${mode}_delete_a1 $a0 --osh accountDelete --account $account1 --no-confirm json .error_code OK .command accountDelete - revoke accountDelete done # final cleanup - grant accountDelete success a4_delete $a0 --osh accountDelete --account $account4 --no-confirm json .error_code OK .command accountDelete - revoke accountDelete # restore sshd_config on non-mfa systems if [ "${capabilities[mfa]}" = 0 ] && [ "${capabilities[mfa-password]}" = 0 ] diff --git a/tests/functional/tests.d/345-assetforgethostkey.sh b/tests/functional/tests.d/345-assetforgethostkey.sh index 3aaca3484..1559e20d6 100644 --- a/tests/functional/tests.d/345-assetforgethostkey.sh +++ b/tests/functional/tests.d/345-assetforgethostkey.sh @@ -8,7 +8,6 @@ testsuite_assetforgethostkey() { # create a1 and a2 - grant accountCreate success create_account1 $a0 --osh accountCreate --account $account1 --uid $uid1 --public-key \""$(cat $account1key1file.pub)"\" json .error_code OK .command accountCreate .value null @@ -16,10 +15,7 @@ testsuite_assetforgethostkey() success create_account2 $a0 --osh accountCreate --account $account2 --uid $uid2 --public-key \""$(cat $account2key1file.pub)"\" json .error_code OK .command accountCreate .value null - revoke accountCreate - # grant personal accesses to these accounts - grant accountAddPersonalAccess success a0_allow_a1_localhost $a0 --osh accountAddPersonalAccess --account $account1 --host 127.0.0.0/24 --port '*' --user '*' json .error_code OK .command accountAddPersonalAccess @@ -27,8 +23,6 @@ testsuite_assetforgethostkey() success a0_allow_a2_localhost $a0 --osh accountAddPersonalAccess --account $account2 --host 127.0.0.0/24 --port '*' --user '*' json .error_code OK .command accountAddPersonalAccess - revoke accountAddPersonalAccess - # connect to localhost from these accounts (it won't work in the end but their known_hosts files will be updated and that's what we need) run a1_connect_localhost1 $a1 user1@127.0.0.1 contain "Connecting..." @@ -42,8 +36,6 @@ testsuite_assetforgethostkey() run a2_connect_localhost2 $a2 user1@127.0.0.2 contain "Connecting..." - grant assetForgetHostKey - # now, delete the host keys for 127.0.0.1 success a0_asset_forgethostkey $a0 --osh assetForgetHostKey --host 127.0.0.1 json .error_code OK .command assetForgetHostKey .value.changed_files 2 @@ -58,15 +50,10 @@ testsuite_assetforgethostkey() success a0_asset_forgethostkey_226_dupe $a0 --osh assetForgetHostKey --host 127.0.0.1 --port 226 json .error_code OK_NO_CHANGE .command assetForgetHostKey .value.changed_files 0 - revoke assetForgetHostKey - # delete those accounts - grant accountDelete success account1_cleanup $a0 --osh accountDelete --account $account1 --no-confirm success account2_cleanup $a0 --osh accountDelete --account $account2 --no-confirm - - revoke accountDelete } testsuite_assetforgethostkey diff --git a/tests/functional/tests.d/350-groups.sh b/tests/functional/tests.d/350-groups.sh index 1a0f3e6d7..40ade74d3 100644 --- a/tests/functional/tests.d/350-groups.sh +++ b/tests/functional/tests.d/350-groups.sh @@ -7,14 +7,10 @@ testsuite_groups() { - grant accountCreate - # first we need to create account1, account2 and account3 success a0_create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate .value null - #grant accountModify - #success realm modify_account1 $a0 --osh accountModify --pam-auth-bypass yes --account $account1 #json .error_code OK .command accountModify @@ -33,18 +29,12 @@ testsuite_groups() contain "info" json .command accountCreate .error_code OK .value null - grant accountListIngressKeys - success a0_check_a2_ingress_keys $a0 --osh accountListIngressKeys --account $account2 json .command accountListIngressKeys .error_code OK .value.account "$account2" .value.keys '[]' - revoke accountListIngressKeys - - grant accountDelete script a0_delete_a2 $a0 --osh accountDelete --account $account2 "<<< \"Yes, do as I say and delete $account2, kthxbye\"" retvalshouldbe 0 json .command accountDelete .error_code OK - revoke accountDelete # /account with no key script a0_create_a2 $a0 --osh accountCreate --always-active --account $account2 --uid $uid2 \< $account2key1file.pub @@ -77,12 +67,8 @@ testsuite_groups() contain "Your alias to connect" json .command info .error_code OK .value.account $account3 - revoke accountCreate - # now create g1 - grant groupCreate - run a2_fail_to_create_g1_with_a1_as_owner_because_not_granted $a2 --osh groupCreate --group $group1 --algo rsa --size 2048 --owner $account1 retvalshouldbe 106 contain "you to be specifically granted" @@ -119,7 +105,7 @@ EOS success a0_create_g3_with_a3_as_owner $a0 --osh groupCreate --group $group3 --algo ed25519 --owner $account3 # test egress key generation as an owner - run a0_generate_key_g1_fail $a0 --osh groupGenerateEgressKey --group $group1 --algo ed25519 + run a2_generate_key_g1_fail $a2 --osh groupGenerateEgressKey --group $group1 --algo ed25519 retvalshouldbe 106 json .command null .error_code KO_RESTRICTED_COMMAND .value null @@ -176,7 +162,7 @@ EOS # test --alive - run a0_del_key_g1 $a0 --osh groupDelEgressKey --group $group1 --id $key1id + run a2_del_key_g1 $a2 --osh groupDelEgressKey --group $group1 --id $key1id retvalshouldbe 106 json .command null .error_code KO_RESTRICTED_COMMAND .value null @@ -188,10 +174,8 @@ EOS unset key1id unset key1fp - grant groupDelete script a0_delete_g3 $a0 --osh groupDelete --group $group3 '<<<' "$group3" retvalshouldbe 0 - revoke groupDelete # /egress key generation # now test all group-* commands from a2 to grant a3 on g1 => should get an early deny @@ -219,8 +203,6 @@ EOS #g3_pubkey=$(get_json | $jq .value.public_key.line) #g3_fp=$( get_json | $jq .value.public_key.fingerprint) - revoke groupCreate - success a0_info_on_g3_after_create $a0 --osh groupInfo --group $group3 json .error_code OK .command groupInfo .value.group $group3 json --arg want "$account0" '.value.owners|sort == ($want|split(" ")|sort)' true @@ -380,8 +362,6 @@ EOS # ... for accounts - grant accountGeneratePassword - success works1 $a0 --osh accountGeneratePassword --account $account1 --do-it json .command accountGeneratePassword .error_code OK .value.account $account1 md5a=$(get_json | $jq '.value.hashes.md5crypt') @@ -390,9 +370,6 @@ EOS type8a=$(get_json | $jq '.value.hashes.type8') type9a=$(get_json | $jq '.value.hashes.type9') - revoke accountGeneratePassword - grant accountListPasswords - success works $a0 --osh accountListPasswords --account $account1 json $(cat <\> ~$shellaccount/.ssh/authorized_keys" @@ -313,7 +306,6 @@ EOF ## set --personal-egress-mfa-required on this account, and add matching ssh/proto personal access: scp/sftp must request MFA, rsync must be denied - grant accountModify success personal_egress_mfa $a0 --osh accountModify --account $account0 --personal-egress-mfa-required password success personal_access_add_scpup $a0 --osh selfAddPersonalAccess --host 127.0.0.2 --port 22 --protocol scpupload @@ -346,7 +338,6 @@ EOF # reset --personal-egress-mfa-required on this account and remove protocol personal accesses success personal_egress_nomfa $a0 --osh accountModify --account $account0 --personal-egress-mfa-required none - revoke accountModify success personal_access_del_scpup $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --port 22 --protocol scpupload success personal_access_del_sftp $a0 --osh selfDelPersonalAccess --host 127.0.0.2 --port 22 --protocol sftp @@ -484,13 +475,11 @@ EOF retvalshouldbe 0 json .error_code OK .command selfMFAResetPassword else - grant accountMFAResetPassword success personal_mfa_reset_password $a0 --osh accountMFAResetPassword --account $account0 fi ## set account as exempt from MFA, and see whether scp/sftp/rsync (that still require MFA as per the group) do work - grant accountModify success personal_mfa_set_exempt $a0 --osh accountModify --account $account0 --mfa-password-required bypass success scp_upload_mfa_exempt_oldhelper_ok /tmp/scpwrapper -i $account0key1file /etc/passwd $shellaccount@127.0.0.2: @@ -525,13 +514,9 @@ EOF # reset account setup success personal_mfa_reset_policy $a0 --osh accountModify --account $account0 --mfa-password-required no - revoke accountModify # delete group1 success groupDestroy $a0 --osh groupDestroy --group $group1 --no-confirm - - revoke selfAddPersonalAccess - revoke selfDelPersonalAccess } testsuite_mfa_scp_sftp diff --git a/tests/functional/tests.d/400-piv.sh b/tests/functional/tests.d/400-piv.sh index 27692f783..05791e2ba 100644 --- a/tests/functional/tests.d/400-piv.sh +++ b/tests/functional/tests.d/400-piv.sh @@ -55,16 +55,9 @@ CzYH/zLEaIsLjRg+tnmKJu2E4IdodScri7oGVhVyhUW5DrcX+/8CPqnoBpd7zQ== -----END CERTIFICATE----- EOF - grant accountCreate - success accountCreate $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key \""$(cat $account1key1file.pub)"\" json .error_code OK .command accountCreate .value null - revoke accountCreate - grant accountModify - grant accountPIV - grant accountListIngressKeys - script piv_nopivspecified $a1 --osh selfAddIngressKey --piv "< $account2key1file.pub" retvalshouldbe 100 json .command selfAddIngressKey .error_code ERR_NO_PEM_START_MARKER @@ -110,10 +103,6 @@ EOF json .command accountListIngressKeys .error_code OK .value.keys[1] null .value.keys[0].isPiv 1 .value.keys[0].pivInfo.Yubikey.SerialNumber 10595103 # account0 sudo account1 to try to add a non-piv key. this must not work. - # for this trick, a0 needs to use adminSudo hence needs to be an admin - configchg 's=^\\\\x22adminAccounts\\\\x22.+=\\\\x22adminAccounts\\\\x22:[\\\\x22'"$account0"'\\\\x22],=' - - success set_a0_as_admin $r0 "\". $opt_remote_basedir/lib/shell/functions.inc; add_user_to_group_compat $account0 osh-admin\"" script a0_sudo_a1_selfaddnonpiv $a0 --osh adminSudo -- --sudo-as $account1 --sudo-cmd selfAddIngressKey -- $js "< $account2key1file.pub" retvalshouldbe 0 @@ -168,20 +157,10 @@ EOF success a1_delkey_test $a1 --osh selfDelIngressKey --fingerprint-to-delete $other_fp json .command selfDelIngressKey .error_code OK - # remove a0 from admins - success del_a0_as_admin $r0 "\". $opt_remote_basedir/lib/shell/functions.inc; del_user_from_group_compat $account0 osh-admin\"" - - revoke accountListIngressKeys - revoke accountPIV - revoke accountModify - # delete account1 - grant accountDelete script cleanup $a0 --osh accountDelete --account $account1 "<<< \"Yes, do as I say and delete $account1, kthxbye\"" retvalshouldbe 0 - revoke accountDelete - # restore default config success configrestore $r0 "dd if=$opt_remote_etc_bastion/bastion.conf.bak.$now of=$opt_remote_etc_bastion/bastion.conf" } diff --git a/tests/functional/tests.d/500-http-proxy.sh b/tests/functional/tests.d/500-http-proxy.sh index baf935fd8..56817b40d 100644 --- a/tests/functional/tests.d/500-http-proxy.sh +++ b/tests/functional/tests.d/500-http-proxy.sh @@ -117,13 +117,9 @@ testsuite_proxy() contain "This account doesn't have access to this user@host tuple (Access denied for $account0 to test@127.0.0.1:9443)" # add ourselves access - grant selfAddPersonalAccess - success add_personal_access $a0 --osh selfAddPersonalAccess --host 127.0.0.1 --port 9443 --user test --force json .command selfAddPersonalAccess .error_code OK - revoke selfAddPersonalAccess - script missing_egress_pwd "curl -ski -u '$account0@test@127.0.0.1%9443:$proxy_password' https://$remote_ip:$remote_proxy_port/test | cat; exit \${PIPESTATUS[0]}" retvalshouldbe 0 contain 'HTTP/1.0 412 Precondition Failed (egress password missing)' diff --git a/tests/functional/tests.d/900-strict-checking.sh b/tests/functional/tests.d/900-strict-checking.sh index 4b2421801..cb705b0b2 100644 --- a/tests/functional/tests.d/900-strict-checking.sh +++ b/tests/functional/tests.d/900-strict-checking.sh @@ -9,21 +9,14 @@ testsuite_strict_checking() { # test that strict host key checking with hostkey change is detected by the bastion and prints a help message - grant accountCreate - # first we need to create account1 success a0_create_a1 $a0 --osh accountCreate --always-active --account $account1 --uid $uid1 --public-key "\"$(cat $account1key1file.pub)\"" json .error_code OK .command accountCreate .value null - revoke accountCreate - grant accountAddPersonalAccess - # add access to root@127.0.0.1 (there are no keys deployed, but we don't care, connection should fail early due to the hostkey change) success add_local_access $a0 --osh accountAddPersonalAccess --account $account1 --host 127.0.0.1 --port 22 --user root json .command accountAddPersonalAccess .error_code OK - revoke accountAddPersonalAccess - # try to connect a first time, so that our bastion known_hosts is populated run connect_before $a1 root@127.0.0.1 retvalshouldbe 255 @@ -52,11 +45,9 @@ testsuite_strict_checking() contain selfForgetHostKey # delete account1 - grant accountDelete script a0_delete_a1 $a0 --osh accountDelete --account $account1 "<<< \"Yes, do as I say and delete $account1, kthxbye\"" retvalshouldbe 0 json .command accountDelete .error_code OK - revoke accountDelete } testsuite_strict_checking diff --git a/tests/unit/run.pl b/tests/unit/run.pl index 0483d123c..658d5a8f2 100755 --- a/tests/unit/run.pl +++ b/tests/unit/run.pl @@ -424,4 +424,861 @@ "build_re_from_wildcards() 2" ); +# is_valid_ip + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "1.2.0.0/32", "version" => "4", "prefixlen" => "32", "ip" => "1.2.0.0", "type" => "single",} + ), + "is_valid_ip(1.2.0.0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/0", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/16", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/16,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/24", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/24,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/32", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "1.2.0.0/32", "version" => "4", "prefixlen" => "32", "ip" => "1.2.0.0", "type" => "single",} + ), + "is_valid_ip(1.2.0.0/32,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/33", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/33,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "255.255.255.255/32", + "version" => "4", + "prefixlen" => "32", + "ip" => "255.255.255.255", + "type" => "single", + } + ), + "is_valid_ip(255.255.255.255,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/0", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/16", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/16,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/24", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/24,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/32", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "255.255.255.255/32", + "version" => "4", + "prefixlen" => "32", + "ip" => "255.255.255.255", + "type" => "single", + } + ), + "is_valid_ip(255.255.255.255/32,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/33", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/33,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/0", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/16", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/16,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/24", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/24,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/32", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/32,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/33", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/33,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "4.5.6.0/32", "version" => "4", "prefixlen" => "32", "ip" => "4.5.6.0", "type" => "single",} + ), + "is_valid_ip(4.5.6.0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/0", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/16", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/16,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/24", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/24,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/32", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "4.5.6.0/32", "version" => "4", "prefixlen" => "32", "ip" => "4.5.6.0", "type" => "single",} + ), + "is_valid_ip(4.5.6.0/32,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/33", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/33,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0", allowPrefixes => 0, fast => 0), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "0000:0000:0000:0000:0000:feef:0000:0000/128", + "version" => "6", + "prefixlen" => "128", + "ip" => "0000:0000:0000:0000:0000:feef:0000:0000", + "type" => "single", + } + ), + "is_valid_ip(::feef:0:0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/0", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/0,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/16", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/16,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/24", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/24,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/32", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/32,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/33", allowPrefixes => 0, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/33,fast=0,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "1.2.0.0/32", "version" => "4", "prefixlen" => "32", "ip" => "1.2.0.0", "type" => "single",} + ), + "is_valid_ip(1.2.0.0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/0", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/16", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "1.2.0.0/16", "version" => "4", "prefixlen" => "16", "ip" => "1.2.0.0/16", "type" => "prefix",} + ), + "is_valid_ip(1.2.0.0/16,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/24", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "1.2.0.0/24", "version" => "4", "prefixlen" => "24", "ip" => "1.2.0.0/24", "type" => "prefix",} + ), + "is_valid_ip(1.2.0.0/24,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/32", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "1.2.0.0/32", "version" => "4", "prefixlen" => "32", "ip" => "1.2.0.0", "type" => "single",} + ), + "is_valid_ip(1.2.0.0/32,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/33", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/33,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "255.255.255.255/32", + "version" => "4", + "prefixlen" => "32", + "ip" => "255.255.255.255", + "type" => "single", + } + ), + "is_valid_ip(255.255.255.255,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/0", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/16", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/16,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/24", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/24,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/32", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "255.255.255.255/32", + "version" => "4", + "prefixlen" => "32", + "ip" => "255.255.255.255", + "type" => "single", + } + ), + "is_valid_ip(255.255.255.255/32,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/33", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/33,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/0", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/16", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/16,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/24", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/24,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/32", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/32,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/33", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/33,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "4.5.6.0/32", "version" => "4", "prefixlen" => "32", "ip" => "4.5.6.0", "type" => "single",} + ), + "is_valid_ip(4.5.6.0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/0", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/16", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/16,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/24", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "4.5.6.0/24", "version" => "4", "prefixlen" => "24", "ip" => "4.5.6.0/24", "type" => "prefix",} + ), + "is_valid_ip(4.5.6.0/24,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/32", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => + {"prefix" => "4.5.6.0/32", "version" => "4", "prefixlen" => "32", "ip" => "4.5.6.0", "type" => "single",} + ), + "is_valid_ip(4.5.6.0/32,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/33", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/33,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0", allowPrefixes => 1, fast => 0), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "0000:0000:0000:0000:0000:feef:0000:0000/128", + "version" => "6", + "prefixlen" => "128", + "ip" => "0000:0000:0000:0000:0000:feef:0000:0000", + "type" => "single", + } + ), + "is_valid_ip(::feef:0:0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/0", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/0,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/16", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/16,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/24", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/24,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/32", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/32,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/33", allowPrefixes => 1, fast => 0), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/33,fast=0,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0", allowPrefixes => 0, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "1.2.0.0",}), + "is_valid_ip(1.2.0.0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/0", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/16", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/16,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/24", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/24,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/32", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/32,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/33", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/33,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255", allowPrefixes => 0, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "255.255.255.255",}), + "is_valid_ip(255.255.255.255,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/0", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/16", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/16,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/24", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/24,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/32", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/32,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/33", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/33,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/0", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/16", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/16,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/24", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/24,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/32", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/32,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/33", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/33,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0", allowPrefixes => 0, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "4.5.6.0",}), + "is_valid_ip(4.5.6.0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/0", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/16", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/16,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/24", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/24,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/32", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/32,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/33", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/33,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0", allowPrefixes => 0, fast => 1), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "0000:0000:0000:0000:0000:feef:0000:0000/128", + "version" => "6", + "prefixlen" => "128", + "ip" => "0000:0000:0000:0000:0000:feef:0000:0000", + "type" => "single", + } + ), + "is_valid_ip(::feef:0:0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/0", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/0,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/16", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/16,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/24", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/24,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/32", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/32,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/33", allowPrefixes => 0, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/33,fast=1,pfx=0)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "1.2.0.0",}), + "is_valid_ip(1.2.0.0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/0", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/16", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"prefix" => "16", "ip" => "1.2.0.0/16",}), + "is_valid_ip(1.2.0.0/16,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/24", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"prefix" => "24", "ip" => "1.2.0.0/24",}), + "is_valid_ip(1.2.0.0/24,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/32", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "1.2.0.0"}), + "is_valid_ip(1.2.0.0/32,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "1.2.0.0/33", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(1.2.0.0/33,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "255.255.255.255",}), + "is_valid_ip(255.255.255.255,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/0", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/16", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/16,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/24", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/24,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/32", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "255.255.255.255",}), + "is_valid_ip(255.255.255.255/32,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "255.255.255.255/33", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(255.255.255.255/33,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/0", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/16", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/16,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/24", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/24,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/32", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/32,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "256.0.0.0/33", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(256.0.0.0/33,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "4.5.6.0",}), + "is_valid_ip(4.5.6.0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/0", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/16", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/16,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/24", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"prefix" => "24", "ip" => "4.5.6.0/24",}), + "is_valid_ip(4.5.6.0/24,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0", allowPrefixes => 1, fast => 1), + R("OK", msg => ignore(), value => {"ip" => "4.5.6.0",}), + "is_valid_ip(4.5.6.0/32,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "4.5.6.0/33", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(4.5.6.0/33,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0", allowPrefixes => 1, fast => 1), + R( + "OK", + msg => ignore(), + value => { + "prefix" => "0000:0000:0000:0000:0000:feef:0000:0000/128", + "version" => "6", + "prefixlen" => "128", + "ip" => "0000:0000:0000:0000:0000:feef:0000:0000", + "type" => "single", + } + ), + "is_valid_ip(::feef:0:0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/0", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/0,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/16", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/16,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/24", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/24,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/32", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/32,fast=1,pfx=1)" +); + +cmp_deeply( + OVH::Bastion::is_valid_ip(ip => "::feef:0:0/33", allowPrefixes => 1, fast => 1), + R("KO_INVALID_IP", msg => ignore(), value => undef), + "is_valid_ip(::feef:0:0/33,fast=1,pfx=1)" +); + done_testing();