From c321c9b0cf2f38a6102eb7d866d607415215f54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Thu, 19 Dec 2024 23:30:25 +0100 Subject: [PATCH] wip --- _allPlugins.jsonschema | 81 +++++++++++++++++++ accountAddPersonalAccess.jsonschema | 66 +++++++++++++++ accountGeneratePassword.jsonschema | 59 ++++++++++++++ bin/shell/osh.pl | 4 +- lib/perl/OVH/Bastion.pm | 2 +- lib/perl/OVH/Bastion/ssh.inc | 2 +- lib/perl/OVH/Result.pm | 1 + selfListPasswords.jsonschema | 65 +-------------- .../docker/docker_build_and_run_tests.sh | 6 +- tests/functional/launch_tests_on_instance.sh | 78 +++++++++++------- tests/functional/tests.d/325-accountinfo.sh | 8 +- tests/functional/tests.d/330-selfkeys.sh | 2 +- .../341-selfaccesses-force-password.sh | 2 +- tests/functional/tests.d/370-mfa.sh | 4 +- 14 files changed, 278 insertions(+), 102 deletions(-) create mode 100644 _allPlugins.jsonschema create mode 100644 accountAddPersonalAccess.jsonschema create mode 100644 accountGeneratePassword.jsonschema diff --git a/_allPlugins.jsonschema b/_allPlugins.jsonschema new file mode 100644 index 000000000..376e266ff --- /dev/null +++ b/_allPlugins.jsonschema @@ -0,0 +1,81 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.ovhcloud.com/the-bastion/schemas/plugins/_allPlugins", + "$defs": { + "command": { + "description": "Command name", + "type": "string", + "pattern": "^[a-z][a-zA-Z]+$", + "minLength": 4 + }, + "error_message": { + "type": "string", + "minLength": 2 + }, + "session_id": { + "type": "string", + "pattern": "^[a-f0-9]{12}$" + } + }, + "type": "object", + "additionalProperties": true, + "minProperties": 5, + "maxProperties": 5, + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "command": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/command" + } + ] + }, + "error_message": { + "$ref": "#/$defs/error_message" + }, + "error_code": { + "type": "string", + "pattern": "^(ERR|KO)(_[A-Z_]+)?$" + }, + "value": { + "type": ["array","object","null"] + }, + "session_id": { + "$ref": "#/$defs/session_id" + } + } + }, + { + "additionalProperties": false, + "properties": { + "command": { + "$ref": "#/$defs/command" + }, + "error_message": { + "$ref": "#/$defs/error_message" + }, + "error_code": { + "type": "string", + "pattern": "^OK(_[A-Z_]+)?$" + }, + "value": { + "type": ["array","object","null"] + }, + "session_id": { + "$ref": "#/$defs/session_id" + } + } + } + ], + "required": [ + "command", + "error_code", + "error_message", + "value" + ] +} diff --git a/accountAddPersonalAccess.jsonschema b/accountAddPersonalAccess.jsonschema new file mode 100644 index 000000000..ed7662cb3 --- /dev/null +++ b/accountAddPersonalAccess.jsonschema @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.ovhcloud.com/the-bastion/schemas/plugins/accountAddPersonalAccess", + "type": "object", + "additionalProperties": false, + "minProperties": 7, + "maxProperties": 7, + "required": [ + "comment", + "port", + "ip", + "user", + "account", + "ttl", + "action" + ], + "properties": { + "comment": { + "type": [ + "null", + "string" + ] + }, + "port": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + ] + }, + "ip": { + "type": "string", + "pattern": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(/[0-9]+)?$|^\\[[:a-f0-9]+]\\](/[0-9]+)?$" + }, + "user": { + "type": [ + "null", + "string" + ] + }, + "account": { + "type": [ + "null", + "string" + ] + }, + "ttl": { + "type": [ + "null", + "integer" + ] + }, + "action": { + "type": "string", + "enum": [ + "add", + "del" + ] + } + } +} diff --git a/accountGeneratePassword.jsonschema b/accountGeneratePassword.jsonschema new file mode 100644 index 000000000..6ed25905d --- /dev/null +++ b/accountGeneratePassword.jsonschema @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.ovhcloud.com/the-bastion/schemas/plugins/accountGeneratePassword", + "$defs": { + "value_hashes_object": { + "type": "object", + "additionalProperties": false, + "minProperties": 3, + "maxProperties": 5, + "required": [ + "sha512crypt", + "sha256crypt", + "md5crypt" + ], + "properties": { + "sha512crypt": { + "type": "string", + "pattern": "^\\$6\\$.+\\$.+$" + }, + "sha256crypt": { + "type": "string", + "pattern": "^\\$5\\$.+\\$.+$" + }, + "md5crypt": { + "type": "string", + "pattern": "^\\$1\\$.+\\$.+$" + }, + "type8": { + "type": "string", + "pattern": "^\\$8\\$.+\\$.+$" + }, + "type9": { + "type": "string", + "pattern": "^\\$9\\$.+\\$.+$" + } + } + } + }, + "type": "object", + "additionalProperties": false, + "minProperties": 4, + "maxProperties": 4, + "properties": { + "hashes": { + "$ref": "#/$defs/value_hashes_object" + }, + "context": { + "const": "account" + }, + "group": { + "type": "null" + }, + "account": { + "type": "string", + "minLength": 2, + "maxLength": 28 + } + } +} diff --git a/bin/shell/osh.pl b/bin/shell/osh.pl index 3280c03b9..50c9a480c 100755 --- a/bin/shell/osh.pl +++ b/bin/shell/osh.pl @@ -73,7 +73,9 @@ sub main_exit { uniqid => $log_uniq_id ) if (not defined $log_db_name or not defined $log_insert_id); - my $R = R($retcode eq OVH::Bastion::EXIT_OK ? 'OK' : 'KO_' . uc($comment), msg => $msg); + my $exitcode = uc($comment); + $exitcode =~ tr/-/_/; + my $R = R($retcode eq OVH::Bastion::EXIT_OK ? 'OK' : "KO_$exitcode", msg => $msg); OVH::Bastion::osh_crit($R->msg) if not $R; OVH::Bastion::json_output($R) if $ENV{'PLUGIN_JSON'}; diff --git a/lib/perl/OVH/Bastion.pm b/lib/perl/OVH/Bastion.pm index cbddfd6ba..a37437177 100644 --- a/lib/perl/OVH/Bastion.pm +++ b/lib/perl/OVH/Bastion.pm @@ -446,7 +446,7 @@ sub json_output { ## no critic (ArgUnpacking) $JsonObject->pretty(1); } my $encoded_json = - $JsonObject->encode({error_code => $R->err, error_message => $R->msg, command => $command, value => $R->value}); + $JsonObject->encode({error_code => $R->err, error_message => $R->msg, command => $command, value => $R->value, session_id => $ENV{'UNIQID'}}); # rename forbidden strings $encoded_json =~ s/JSON_(START|OUTPUT|END)/JSON__$1/g; diff --git a/lib/perl/OVH/Bastion/ssh.inc b/lib/perl/OVH/Bastion/ssh.inc index 9fa33f1c3..8432c7b69 100644 --- a/lib/perl/OVH/Bastion/ssh.inc +++ b/lib/perl/OVH/Bastion/ssh.inc @@ -308,7 +308,7 @@ sub get_ssh_pub_key_info { ($prefix, $typecode, $base64, $comment) = ($2, $3, $4, $6); } else { - return R('KO_NOT_A_KEY', value => {line => $pubKey}); + return R('KO_NOT_A_KEY'); } my $line = "$typecode $base64"; $prefix = '' if not defined $prefix; diff --git a/lib/perl/OVH/Result.pm b/lib/perl/OVH/Result.pm index 39c8df472..4bf68f91d 100644 --- a/lib/perl/OVH/Result.pm +++ b/lib/perl/OVH/Result.pm @@ -74,6 +74,7 @@ sub TO_JSON { error_code => $self->err, value => $self->value, error_message => $self->msg, + session_id => $ENV{'UNIQID'}, } if (ref $self eq 'OVH::Result'); return {}; diff --git a/selfListPasswords.jsonschema b/selfListPasswords.jsonschema index e4d5a4b76..be0e6ab86 100644 --- a/selfListPasswords.jsonschema +++ b/selfListPasswords.jsonschema @@ -2,16 +2,6 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://www.ovhcloud.com/the-bastion/schemas/plugins/selfListPasswords", "$defs": { - "command": { - "description": "Command name", - "type": "string", - "pattern": "^[a-z][a-zA-Z]+$", - "minLength": 4 - }, - "error_message": { - "type": "string", - "minLength": 2 - }, "value_arrayitem_object": { "type": "object", "additionalProperties": false, @@ -90,55 +80,8 @@ } } }, - "type": "object", - "additionalProperties": true, - "minProperties": 4, - "maxProperties": 4, - "oneOf": [ - { - "additionalProperties": false, - "properties": { - "command": { - "$ref": "#/$defs/command" - }, - "error_message": { - "$ref": "#/$defs/error_message" - }, - "error_code": { - "type": "string", - "pattern": "^(KO|ERR)(_[A-Z_]+)?$" - }, - "value": { - "type": "null" - } - } - }, - { - "additionalProperties": false, - "properties": { - "command": { - "$ref": "#/$defs/command" - }, - "error_message": { - "$ref": "#/$defs/error_message" - }, - "error_code": { - "type": "string", - "pattern": "^OK(_[A-Z_]+)?$" - }, - "value": { - "type": "array", - "items": { - "$ref": "#/$defs/value_arrayitem_object" - } - } - } - } - ], - "required": [ - "command", - "error_code", - "error_message", - "value" - ] + "type": "array", + "items": { + "$ref": "#/$defs/value_arrayitem_object" + } } diff --git a/tests/functional/docker/docker_build_and_run_tests.sh b/tests/functional/docker/docker_build_and_run_tests.sh index 815ed83d9..610f1c38c 100755 --- a/tests/functional/docker/docker_build_and_run_tests.sh +++ b/tests/functional/docker/docker_build_and_run_tests.sh @@ -125,11 +125,11 @@ if docker inspect "bastion-$target" >/dev/null 2>&1; then fi # trying with IPv6 -if ! docker network create --ipv6 --subnet fd42:cafe:efac:"$(printf "%x" $RANDOM)"::/64 "bastion-$target" >/dev/null; then +#if true || ! 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" +# echo "... IPv6 is not enabled in docker daemon, falling back to IPv4-only network" docker network create "bastion-$target" >/dev/null -fi +#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/launch_tests_on_instance.sh b/tests/functional/launch_tests_on_instance.sh index ee23ceb7a..500ca0989 100755 --- a/tests/functional/launch_tests_on_instance.sh +++ b/tests/functional/launch_tests_on_instance.sh @@ -394,8 +394,10 @@ run() cat "$outdir/$basename.log" printf "%b%b%b\\n" "$WHITE_ON_BLUE" "[INFO] returned json follows" "$NOC" grep "^JSON_OUTPUT=" -- $outdir/$basename.log | cut -d= -f2- | jq --sort-keys . - printf "%b%b%b\\n" "$WHITE_ON_BLUE" "[INFO] json validation information follows" "$NOC" - cat "$outdir/$basename.jv" + if [ -s "$outdir/$basename.jv" ]; then + printf "%b%b%b\\n" "$WHITE_ON_BLUE" "[INFO] json validation information follows" "$NOC" + cat "$outdir/$basename.jv" + fi if [ "$opt_consistency_check" = 1 ]; then printf "%b%b%b\\n" "$WHITE_ON_BLUE" "[INFO] consistency check follows" "$NOC" cat "$outdir/$basename.cc" @@ -445,30 +447,46 @@ run() fi # do we have a json output and if we do, do we have a jsonschema for the command? + returned_json=$(grep "^JSON_OUTPUT=" -- $outdir/$basename.log | tail -n1 | cut -d= -f2-) local _osh - _osh=$(get_json | $jq '.command') - if [ -n "$_osh" ] && [ "$_osh" != "null" ] && [ -e "$basedir/$_osh.jsonschema" ]; then - get_json | perl -e ' + _osh=$(echo "$returned_json" | $jq '.command') + if [ -n "$_osh" ]; then + echo "$returned_json" | env basedir="$basedir" osh="$_osh" perl -e ' use strict; use warnings; use JSON; use JSON::Validator; - my $jv = JSON::Validator->new()->schema("file://'"$basedir/$_osh"'.jsonschema"); - my $schema = $jv->schema; # will die if file is invalid json, this is what we want - if ($schema->is_invalid) { - CORE::say $_->to_string() for @{ $schema->errors }; - exit 1; + + sub validate { + my ($filename, $data) = @_; + return 0 if ! defined $data; + my $file = $ENV{"basedir"}."/$filename.jsonschema"; + return 0 if ! -e $file; + my $jv = JSON::Validator->new()->schema("file://$file"); + my $schema = $jv->schema; # will die if file is invalid json, this is what we want + if ($schema->is_invalid) { + print "Schema is invalid:\n"; + CORE::say $_->to_string() for @{ $schema->errors }; + exit 1; + } + my @errors = $schema->validate($data); + CORE::say $_->to_string() for (@errors); + return 1; } - my $data = eval { local $/; <>; }; - my $json = decode_json($data); # will die if it fails, this is what we want - my @errors = $schema->validate($json); - CORE::say $_->to_string() for (@errors); + + my $datastr = eval { local $/; <>; }; + my $json = decode_json($datastr); # will die if it fails, this is what we want + + my $count = 0; + $count += validate("_allPlugins", $json); + $count += validate($ENV{"osh"}, $json->{"value"}); + die "Nothing to validate" if !$count; ' > "$outdir/$basename.jv" 2>&1 if [ -s "$outdir/$basename.jv" ]; then nbfailedgeneric=$(( nbfailedgeneric + 1 )) - fail "JSON SCHEMA" "(json validation failed against the schema of $_osh)" + fail "JSON SCHEMA" "(json validation failed against the schema)" elif [ -f "$outdir/$basename.jv" ]; then - ok "JSON SCHEMA" "(json data matches the schema of $_osh)" + ok "JSON SCHEMA" "(json data matches the schema)" else nbfailedgeneric=$(( nbfailedgeneric + 1 )) fail "JSON SCHEMA" "(json validation didn't produce an output at all)" @@ -508,6 +526,7 @@ script() { return fi + local tmpscript tmpscript=$(mktemp) echo "#! /usr/bin/env bash" > "$tmpscript" echo "$*" >> "$tmpscript" @@ -559,12 +578,6 @@ ignorecodewarn() code_warn_exclude="$*" } -get_json() -{ - [ "$COUNTONLY" = 1 ] && return - grep "^JSON_OUTPUT=" -- $outdir/$basename.log | tail -n1 | cut -d= -f2- -} - get_stdout() { [ "$COUNTONLY" = 1 ] && return @@ -574,7 +587,7 @@ get_stdout() json() { [ "$COUNTONLY" = 1 ] && return - local jq1="" jq2="" jq3="" + local jq1="" jq2="" jq3="" got="" expected="" local splitsort=0 while [ $# -ge 2 ] ; do if [ "$1" = "--splitsort" ]; then @@ -590,23 +603,22 @@ json() fi local filter="$1" expected="$2" shift 2 - json=$(get_json) set +e if [ -n "$jq3" ]; then - got=$($jq "$jq1" "$jq2" "$jq3" "$filter" <<< "$json") + got=$($jq "$jq1" "$jq2" "$jq3" "$filter" <<< "$returned_json") else - got=$($jq "$filter" <<< "$json") + got=$($jq "$filter" <<< "$returned_json") fi if [ "$splitsort" = 1 ]; then expected=$(echo "$expected" | tr " " "\\n" | sort) got=$($jq ".[]" <<< "$got" | sort) fi set -e - if [ -z "$json" ] ; then + if [ -z "$returned_json" ] ; then nbfailedgrep=$(( nbfailedgrep + 1 )) fail "JSON VALUE" "(no json found in output, couldn't look for key <$filter>)" elif [ "$expected" = "$got" ] ; then - ok "JSON VALUE" "($filter => $expected) [$jq1 $jq3 $jq3]" + ok "JSON VALUE" "($filter => $expected) [$jq1 $jq2 $jq3]" else nbfailedgrep=$(( nbfailedgrep + 1 )) fail "JSON VALUE" "(for key <$filter> wanted <$expected> but got <$got>, with optional params jq1='$jq1' jq2='$jq2' jq3='$jq3')" @@ -614,13 +626,18 @@ json() done } +get_json() +{ + echo "${returned_json:-}" +} + json_document() { [ "$COUNTONLY" = 1 ] && return local fulljson="$1" local tmpdiff; tmpdiff=$(mktemp) local diffret=0 - diff -u0 <(echo "$fulljson" | jq -S .) <(get_json | jq -S .) > "$tmpdiff"; diffret=$? + diff -u0 <(echo "$fulljson" | jq -S .) <(echo "$returned_json" | jq -S .) > "$tmpdiff"; diffret=$? if [ "$diffret" = 0 ]; then ok "JSON DOCUMENT" "(fully matched)" else @@ -662,6 +679,7 @@ contain() nocontain() { [ "$COUNTONLY" = 1 ] && return + local grepit grepit="$1" if grep -Eq "$grepit" "$outdir/$basename.log"; then nbfailedgrep=$(( nbfailedgrep + 1 )) @@ -696,7 +714,7 @@ dump_vars_and_funcs() { set | grep -v -E '^('\ 'testno|section|code_warn_exclude|COPROC_PID|LINES|COLUMNS|PIPESTATUS|_|'\ -'BASH_LINENO|basename|case|json|name|tmpscript|grepit|got|isbad|'\ +'BASH_LINENO|basename|case|json|name|isbad|returned_json|'\ 'nbfailedgrep|nbfailedcon|nbfailedgeneric|nbfailedlog|nbfailedret|shouldbe|modulename)=' } diff --git a/tests/functional/tests.d/325-accountinfo.sh b/tests/functional/tests.d/325-accountinfo.sh index e1d77dffe..9be45ccd7 100644 --- a/tests/functional/tests.d/325-accountinfo.sh +++ b/tests/functional/tests.d/325-accountinfo.sh @@ -36,7 +36,13 @@ testsuite_accountinfo() # 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":{}}}' + json .error_code OK .command accountInfo + json .value.account "$account2" + json .value.always_active 1 + json .value.always_active_reason "account local configuration" + json .value.is_active 1 + json .value.allowed_commands '[]' + json .value.groups '{}' # a0 should see detailed info about a2 success a0_accountinfo_a2_detailed $a0 --osh accountInfo --account $account2 --with-mfa-password-info diff --git a/tests/functional/tests.d/330-selfkeys.sh b/tests/functional/tests.d/330-selfkeys.sh index b211abaf4..171a32acf 100644 --- a/tests/functional/tests.d/330-selfkeys.sh +++ b/tests/functional/tests.d/330-selfkeys.sh @@ -186,7 +186,7 @@ EOS script bogus $a1 -osh selfAddIngressKey '<<<' "bogus" retvalshouldbe 100 contain "look like an SSH public key" - json .command selfAddIngressKey .error_code KO_NOT_A_KEY .value.key.line bogus + json .command selfAddIngressKey .error_code KO_NOT_A_KEY .value null script eof $a1 -osh selfAddIngressKey '