replace 'timeout' helper with async python script; allow hub preload in func tests; improve item removal (#2591)
* replace 'timeout' helper with async python script; allow hub preload in func tests; improve item removal * func tests: cscli hub update/upgrade * docker test update * Update docker entrypoint to disable items with --force The --force flag was not transmitted to cscli, but is required after the hub refact to disable items inside installed collections
This commit is contained in:
parent
f8c91d20b0
commit
4a6fd338e0
25 changed files with 511 additions and 166 deletions
|
@ -101,19 +101,23 @@ register_bouncer() {
|
|||
# $2 can be install, remove, upgrade
|
||||
# $3 is a list of object names separated by space
|
||||
cscli_if_clean() {
|
||||
local itemtype="$1"
|
||||
local action="$2"
|
||||
local objs=$3
|
||||
shift 3
|
||||
# loop over all objects
|
||||
for obj in $3; do
|
||||
if cscli "$1" inspect "$obj" -o json | yq -e '.tainted // false' >/dev/null 2>&1; then
|
||||
echo "Object $1/$obj is tainted, skipping"
|
||||
for obj in $objs; do
|
||||
if cscli "$itemtype" inspect "$obj" -o json | yq -e '.tainted // false' >/dev/null 2>&1; then
|
||||
echo "Object $itemtype/$obj is tainted, skipping"
|
||||
else
|
||||
# # Too verbose? Only show errors if not in debug mode
|
||||
# if [ "$DEBUG" != "true" ]; then
|
||||
# error_only=--error
|
||||
# fi
|
||||
error_only=""
|
||||
echo "Running: cscli $error_only $1 $2 \"$obj\""
|
||||
echo "Running: cscli $error_only $itemtype $action \"$obj\" $*"
|
||||
# shellcheck disable=SC2086
|
||||
cscli $error_only "$1" "$2" "$obj"
|
||||
cscli $error_only "$itemtype" "$action" "$obj" "$@"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ def test_install_two_collections(crowdsec, flavor):
|
|||
cs.wait_for_log([
|
||||
# f'*collections install "{it1}"*'
|
||||
# f'*collections install "{it2}"*'
|
||||
f'*Enabled collections : {it1}*',
|
||||
f'*Enabled collections : {it2}*',
|
||||
f'*Enabled collections: {it1}*',
|
||||
f'*Enabled collections: {it2}*',
|
||||
])
|
||||
|
||||
|
||||
|
@ -72,7 +72,7 @@ def test_install_and_disable_collection(crowdsec, flavor):
|
|||
assert it not in items
|
||||
logs = cs.log_lines()
|
||||
# check that there was no attempt to install
|
||||
assert not any(f'Enabled collections : {it}' in line for line in logs)
|
||||
assert not any(f'Enabled collections: {it}' in line for line in logs)
|
||||
|
||||
|
||||
# already done in bats, prividing here as example of a somewhat complex test
|
||||
|
@ -91,7 +91,7 @@ def test_taint_bubble_up(crowdsec, tmp_path_factory, flavor):
|
|||
# implicit check for tainted=False
|
||||
assert items[coll]['status'] == 'enabled'
|
||||
cs.wait_for_log([
|
||||
f'*Enabled collections : {coll}*',
|
||||
f'*Enabled collections: {coll}*',
|
||||
])
|
||||
|
||||
scenario = 'crowdsecurity/http-crawl-non_statics'
|
||||
|
|
|
@ -21,8 +21,8 @@ def test_install_two_scenarios(crowdsec, flavor):
|
|||
}
|
||||
with crowdsec(flavor=flavor, environment=env) as cs:
|
||||
cs.wait_for_log([
|
||||
f'*scenarios install "{it1}*"',
|
||||
f'*scenarios install "{it2}*"',
|
||||
f'*scenarios install "{it1}"*',
|
||||
f'*scenarios install "{it2}"*',
|
||||
"*Starting processing data*"
|
||||
])
|
||||
cs.wait_for_http(8080, '/health', want_status=HTTPStatus.OK)
|
||||
|
|
|
@ -76,10 +76,19 @@ func (i *Item) enable() error {
|
|||
|
||||
// purge removes the actual config file that was downloaded
|
||||
func (i *Item) purge() error {
|
||||
if !i.Downloaded {
|
||||
log.Infof("removing %s: not downloaded -- no need to remove", i.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
itempath := i.hub.local.HubDir + "/" + i.RemotePath
|
||||
|
||||
// disable hub file
|
||||
if err := os.Remove(itempath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Debugf("%s doesn't exist, no need to remove", itempath)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("while removing file: %w", err)
|
||||
}
|
||||
|
||||
|
@ -94,17 +103,6 @@ func (i *Item) disable(purge bool, force bool) error {
|
|||
// XXX: should return the number of disabled/purged items to inform the upper layer whether to reload or not
|
||||
var err error
|
||||
|
||||
// already disabled, noop unless purge
|
||||
if !i.Installed {
|
||||
if purge {
|
||||
if err = i.purge(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.IsLocal() {
|
||||
return fmt.Errorf("%s isn't managed by hub. Please delete manually", i.Name)
|
||||
}
|
||||
|
@ -134,6 +132,11 @@ func (i *Item) disable(purge bool, force bool) error {
|
|||
}
|
||||
}
|
||||
|
||||
if !i.Installed && !purge {
|
||||
log.Infof("removing %s: not installed -- no need to remove", i.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
syml, err := filepath.Abs(i.hub.local.InstallDir + "/" + i.Type + "/" + i.Stage + "/" + i.FileName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -168,6 +171,10 @@ func (i *Item) disable(purge bool, force bool) error {
|
|||
}
|
||||
|
||||
if err = os.Remove(syml); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Debugf("%s doesn't exist, no need to remove", syml)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("while removing symlink: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -55,16 +55,6 @@ func (i *Item) Install(force bool, downloadOnly bool) error {
|
|||
func (i *Item) Remove(purge bool, forceAction bool) (bool, error) {
|
||||
removed := false
|
||||
|
||||
if !i.Downloaded {
|
||||
log.Infof("removing %s: not downloaded -- no removal required", i.Name)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !i.Installed && !purge {
|
||||
log.Infof("removing %s: already uninstalled", i.Name)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := i.disable(purge, forceAction); err != nil {
|
||||
return false, fmt.Errorf("unable to disable %s: %w", i.Name, err)
|
||||
}
|
||||
|
|
|
@ -298,6 +298,7 @@ func (h *Hub) GetAllItems(itemType string) ([]*Item, error) {
|
|||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetInstalledItems returns the list of installed items
|
||||
func (h *Hub) GetInstalledItems(itemType string) ([]*Item, error) {
|
||||
items, ok := h.Items[itemType]
|
||||
|
|
71
test/bats/00_wait_for.bats
Normal file
71
test/bats/00_wait_for.bats
Normal file
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env bats
|
||||
# vim: ft=bats:list:ts=8:sts=4:sw=4:et:ai:si:
|
||||
|
||||
set -u
|
||||
|
||||
setup_file() {
|
||||
load "../lib/setup_file.sh"
|
||||
}
|
||||
|
||||
setup() {
|
||||
load "../lib/setup.sh"
|
||||
}
|
||||
|
||||
@test "run a command and capture its stdout" {
|
||||
run -0 wait-for seq 1 3
|
||||
assert_output - <<-EOT
|
||||
1
|
||||
2
|
||||
3
|
||||
EOT
|
||||
}
|
||||
|
||||
@test "run a command and capture its stderr" {
|
||||
rune -0 wait-for sh -c 'seq 1 3 >&2'
|
||||
assert_stderr - <<-EOT
|
||||
1
|
||||
2
|
||||
3
|
||||
EOT
|
||||
}
|
||||
|
||||
@test "run a command until a pattern is found in stdout" {
|
||||
run -0 wait-for --out "1[12]0" seq 1 200
|
||||
assert_line --index 0 "1"
|
||||
assert_line --index -1 "110"
|
||||
refute_line "111"
|
||||
}
|
||||
|
||||
@test "run a command until a pattern is found in stderr" {
|
||||
rune -0 wait-for --err "10" sh -c 'seq 1 20 >&2'
|
||||
assert_stderr - <<-EOT
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
EOT
|
||||
}
|
||||
|
||||
@test "run a command with timeout (no match)" {
|
||||
# when the process is terminated without a match, it returns
|
||||
# 256 - 15 (SIGTERM) = 241
|
||||
rune -241 wait-for --timeout 0.1 --out "10" sh -c 'echo 1; sleep 3; echo 2'
|
||||
assert_line 1
|
||||
# there may be more, but we don't care
|
||||
}
|
||||
|
||||
@test "run a command with timeout (match)" {
|
||||
# when the process is terminated with a match, return code is 128
|
||||
rune -128 wait-for --timeout .4 --out "2" sh -c 'echo 1; sleep .1; echo 2; echo 3; echo 4; sleep 10'
|
||||
assert_output - <<-EOT
|
||||
1
|
||||
2
|
||||
EOT
|
||||
}
|
||||
|
|
@ -24,28 +24,22 @@ teardown() {
|
|||
#----------
|
||||
|
||||
@test "crowdsec (usage)" {
|
||||
rune -0 timeout 2s "${CROWDSEC}" -h
|
||||
assert_stderr_line --regexp "Usage of .*:"
|
||||
|
||||
rune -0 timeout 2s "${CROWDSEC}" --help
|
||||
assert_stderr_line --regexp "Usage of .*:"
|
||||
rune -0 wait-for --out "Usage of " "${CROWDSEC}" -h
|
||||
rune -0 wait-for --out "Usage of " "${CROWDSEC}" --help
|
||||
}
|
||||
|
||||
@test "crowdsec (unknown flag)" {
|
||||
rune -2 timeout 2s "${CROWDSEC}" --foobar
|
||||
assert_stderr_line "flag provided but not defined: -foobar"
|
||||
assert_stderr_line --regexp "Usage of .*"
|
||||
rune -0 wait-for --err "flag provided but not defined: -foobar" "$CROWDSEC" --foobar
|
||||
}
|
||||
|
||||
@test "crowdsec (unknown argument)" {
|
||||
rune -2 timeout 2s "${CROWDSEC}" trololo
|
||||
assert_stderr_line "argument provided but not defined: trololo"
|
||||
assert_stderr_line --regexp "Usage of .*"
|
||||
rune -0 wait-for --err "argument provided but not defined: trololo" "${CROWDSEC}" trololo
|
||||
}
|
||||
|
||||
@test "crowdsec (no api and no agent)" {
|
||||
rune -1 timeout 2s "${CROWDSEC}" -no-api -no-cs
|
||||
assert_stderr_line --partial "You must run at least the API Server or crowdsec"
|
||||
rune -0 wait-for \
|
||||
--err "You must run at least the API Server or crowdsec" \
|
||||
"${CROWDSEC}" -no-api -no-cs
|
||||
}
|
||||
|
||||
@test "crowdsec - print error on exit" {
|
||||
|
@ -57,18 +51,20 @@ teardown() {
|
|||
|
||||
@test "crowdsec - default logging configuration (empty/missing common section)" {
|
||||
config_set '.common={}'
|
||||
rune -124 timeout 2s "${CROWDSEC}"
|
||||
rune -0 wait-for \
|
||||
--err "Starting processing data" \
|
||||
"${CROWDSEC}"
|
||||
refute_output
|
||||
assert_stderr --partial "Starting processing data"
|
||||
|
||||
config_set 'del(.common)'
|
||||
rune -124 timeout 2s "${CROWDSEC}"
|
||||
rune -0 wait-for \
|
||||
--err "Starting processing data" \
|
||||
"${CROWDSEC}"
|
||||
refute_output
|
||||
assert_stderr --partial "Starting processing data"
|
||||
}
|
||||
|
||||
@test "CS_LAPI_SECRET not strong enough" {
|
||||
CS_LAPI_SECRET=foo rune -1 timeout 2s "${CROWDSEC}"
|
||||
CS_LAPI_SECRET=foo rune -1 wait-for "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run local API: controller init: CS_LAPI_SECRET not strong enough"
|
||||
}
|
||||
|
||||
|
@ -138,8 +134,8 @@ teardown() {
|
|||
ACQUIS_YAML=$(config_get '.crowdsec_service.acquisition_path')
|
||||
rm -f "$ACQUIS_YAML"
|
||||
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr_line --partial "acquis.yaml: no such file or directory"
|
||||
rune -1 wait-for "${CROWDSEC}"
|
||||
assert_stderr --partial "acquis.yaml: no such file or directory"
|
||||
}
|
||||
|
||||
@test "crowdsec (error if acquisition_path is not defined and acquisition_dir is empty)" {
|
||||
|
@ -151,7 +147,7 @@ teardown() {
|
|||
rm -f "$ACQUIS_DIR"
|
||||
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
rune -1 wait-for "${CROWDSEC}"
|
||||
# check warning
|
||||
assert_stderr --partial "no acquisition file found"
|
||||
assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled"
|
||||
|
@ -167,13 +163,15 @@ teardown() {
|
|||
config_set '.crowdsec_service.acquisition_dir=""'
|
||||
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
rune -1 wait-for "${CROWDSEC}"
|
||||
# check warning
|
||||
assert_stderr --partial "no acquisition_path or acquisition_dir specified"
|
||||
assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled"
|
||||
}
|
||||
|
||||
@test "crowdsec (no error if acquisition_path is empty string but acquisition_dir is not empty)" {
|
||||
config_set '.common.log_media="stdout"'
|
||||
|
||||
ACQUIS_YAML=$(config_get '.crowdsec_service.acquisition_path')
|
||||
config_set '.crowdsec_service.acquisition_path=""'
|
||||
|
||||
|
@ -181,13 +179,15 @@ teardown() {
|
|||
mkdir -p "$ACQUIS_DIR"
|
||||
mv "$ACQUIS_YAML" "$ACQUIS_DIR"/foo.yaml
|
||||
|
||||
rune -124 timeout 2s "${CROWDSEC}"
|
||||
rune -0 wait-for \
|
||||
--err "Starting processing data" \
|
||||
"${CROWDSEC}"
|
||||
|
||||
# now, if foo.yaml is empty instead, there won't be valid datasources.
|
||||
|
||||
cat /dev/null >"$ACQUIS_DIR"/foo.yaml
|
||||
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
rune -1 wait-for "${CROWDSEC}"
|
||||
assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled"
|
||||
}
|
||||
|
||||
|
@ -212,9 +212,10 @@ teardown() {
|
|||
type: syslog
|
||||
EOT
|
||||
|
||||
rune -124 timeout 2s env PATH='' "${CROWDSEC}"
|
||||
#shellcheck disable=SC2016
|
||||
assert_stderr --partial 'datasource '\''journalctl'\'' is not available: exec: "journalctl": executable file not found in $PATH'
|
||||
rune -0 wait-for \
|
||||
--err 'datasource '\''journalctl'\'' is not available: exec: "journalctl": executable file not found in ' \
|
||||
env PATH='' "${CROWDSEC}"
|
||||
|
||||
# if all datasources are disabled, crowdsec should exit
|
||||
|
||||
|
@ -222,7 +223,7 @@ teardown() {
|
|||
rm -f "$ACQUIS_YAML"
|
||||
config_set '.crowdsec_service.acquisition_path=""'
|
||||
|
||||
rune -1 timeout 2s env PATH='' "${CROWDSEC}"
|
||||
rune -1 wait-for env PATH='' "${CROWDSEC}"
|
||||
assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled"
|
||||
}
|
||||
|
||||
|
|
|
@ -24,21 +24,23 @@ teardown() {
|
|||
#----------
|
||||
|
||||
@test "test without -no-api flag" {
|
||||
rune -124 timeout 2s "${CROWDSEC}"
|
||||
# from `man timeout`: If the command times out, and --preserve-status is not set, then exit with status 124.
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -0 wait-for \
|
||||
--err "CrowdSec Local API listening" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "crowdsec should not run without LAPI (-no-api flag)" {
|
||||
# really needs 4 secs on slow boxes
|
||||
rune -1 timeout 4s "${CROWDSEC}" -no-api
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -1 wait-for "${CROWDSEC}" -no-api
|
||||
}
|
||||
|
||||
@test "crowdsec should not run without LAPI (no api.server in configuration file)" {
|
||||
config_disable_lapi
|
||||
config_log_stderr
|
||||
# really needs 4 secs on slow boxes
|
||||
rune -1 timeout 4s "${CROWDSEC}"
|
||||
assert_stderr --partial "crowdsec local API is disabled"
|
||||
rune -0 wait-for \
|
||||
--err "crowdsec local API is disabled" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "capi status shouldn't be ok without api.server" {
|
||||
|
|
|
@ -23,20 +23,25 @@ teardown() {
|
|||
#----------
|
||||
|
||||
@test "with agent: test without -no-cs flag" {
|
||||
rune -124 timeout 2s "${CROWDSEC}"
|
||||
# from `man timeout`: If the command times out, and --preserve-status is not set, then exit with status 124.
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -0 wait-for \
|
||||
--err "Starting processing data" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "no agent: crowdsec LAPI should run (-no-cs flag)" {
|
||||
rune -124 timeout 2s "${CROWDSEC}" -no-cs
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -0 wait-for \
|
||||
--err "CrowdSec Local API listening" \
|
||||
"${CROWDSEC}" -no-cs
|
||||
}
|
||||
|
||||
@test "no agent: crowdsec LAPI should run (no crowdsec_service in configuration file)" {
|
||||
config_disable_agent
|
||||
config_log_stderr
|
||||
rune -124 timeout 2s "${CROWDSEC}"
|
||||
|
||||
assert_stderr --partial "crowdsec agent is disabled"
|
||||
rune -0 wait-for \
|
||||
--err "crowdsec agent is disabled" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "no agent: cscli config show" {
|
||||
|
|
|
@ -25,16 +25,17 @@ teardown() {
|
|||
@test "without capi: crowdsec LAPI should run without capi (-no-capi flag)" {
|
||||
config_set '.common.log_media="stdout"'
|
||||
|
||||
rune -124 timeout 1s "${CROWDSEC}" -no-capi
|
||||
assert_stderr --partial "Communication with CrowdSec Central API disabled from args"
|
||||
rune -0 wait-for \
|
||||
--err "Communication with CrowdSec Central API disabled from args" \
|
||||
"${CROWDSEC}" -no-capi
|
||||
}
|
||||
|
||||
@test "without capi: crowdsec LAPI should still work" {
|
||||
config_disable_capi
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune -124 timeout 1s "${CROWDSEC}"
|
||||
# from `man timeout`: If the command times out, and --preserve-status is not set, then exit with status 124.
|
||||
assert_stderr --partial "push and pull to Central API disabled"
|
||||
rune -0 wait-for \
|
||||
--err "push and pull to Central API disabled" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "without capi: cscli capi status -> fail" {
|
||||
|
@ -47,10 +48,7 @@ teardown() {
|
|||
@test "no capi: cscli config show" {
|
||||
config_disable_capi
|
||||
rune -0 cscli config show -o human
|
||||
assert_output --partial "Global:"
|
||||
assert_output --partial "cscli:"
|
||||
assert_output --partial "Crowdsec:"
|
||||
assert_output --partial "Local API Server:"
|
||||
assert_output --regexp "Global:.*Crowdsec.*cscli:.*Local API Server:"
|
||||
}
|
||||
|
||||
@test "no agent: cscli config backup" {
|
||||
|
|
|
@ -56,28 +56,28 @@ teardown() {
|
|||
# disable the agent or we'll need to patch api client credentials too
|
||||
rune -0 config_disable_agent
|
||||
./instance-crowdsec start
|
||||
rune -0 ./bin/wait-for-port -q 8080
|
||||
rune -0 wait-for-port -q 8080
|
||||
./instance-crowdsec stop
|
||||
rune -1 ./bin/wait-for-port -q 8080
|
||||
rune -1 wait-for-port -q 8080
|
||||
|
||||
echo "{'api':{'server':{'listen_uri':127.0.0.1:8083}}}" >"${CONFIG_YAML}.local"
|
||||
|
||||
./instance-crowdsec start
|
||||
rune -0 ./bin/wait-for-port -q 8083
|
||||
rune -1 ./bin/wait-for-port -q 8080
|
||||
rune -0 wait-for-port -q 8083
|
||||
rune -1 wait-for-port -q 8080
|
||||
./instance-crowdsec stop
|
||||
|
||||
rm -f "${CONFIG_YAML}.local"
|
||||
./instance-crowdsec start
|
||||
rune -1 ./bin/wait-for-port -q 8083
|
||||
rune -0 ./bin/wait-for-port -q 8080
|
||||
rune -1 wait-for-port -q 8083
|
||||
rune -0 wait-for-port -q 8080
|
||||
}
|
||||
|
||||
@test "local_api_credentials.yaml.local" {
|
||||
rune -0 config_disable_agent
|
||||
echo "{'api':{'server':{'listen_uri':127.0.0.1:8083}}}" >"${CONFIG_YAML}.local"
|
||||
./instance-crowdsec start
|
||||
rune -0 ./bin/wait-for-port -q 8083
|
||||
rune -0 wait-for-port -q 8083
|
||||
|
||||
rune -1 cscli decisions list
|
||||
echo "{'url':'http://127.0.0.1:8083'}" >"${LOCAL_API_CREDENTIALS}.local"
|
||||
|
|
|
@ -18,6 +18,7 @@ setup() {
|
|||
load "../lib/setup.sh"
|
||||
load "../lib/bats-file/load.bash"
|
||||
./instance-data load
|
||||
config_set '.common.log_media="stdout"'
|
||||
config_set '.api.server.capi_whitelists_path=strenv(CAPI_WHITELISTS_YAML)'
|
||||
}
|
||||
|
||||
|
@ -28,38 +29,51 @@ teardown() {
|
|||
#----------
|
||||
|
||||
@test "capi_whitelists: file missing" {
|
||||
rune -1 timeout 1s "${CROWDSEC}"
|
||||
assert_stderr --partial "capi whitelist file '$CAPI_WHITELISTS_YAML' does not exist"
|
||||
rune -0 wait-for \
|
||||
--err "capi whitelist file '$CAPI_WHITELISTS_YAML' does not exist" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "capi_whitelists: error on open" {
|
||||
echo > "$CAPI_WHITELISTS_YAML"
|
||||
chmod 000 "$CAPI_WHITELISTS_YAML"
|
||||
rune -1 timeout 1s "${CROWDSEC}"
|
||||
assert_stderr --partial "while opening capi whitelist file: open $CAPI_WHITELISTS_YAML: permission denied"
|
||||
if is_package_testing; then
|
||||
rune -0 wait-for \
|
||||
--err "while parsing capi whitelist file .*: empty file" \
|
||||
"${CROWDSEC}"
|
||||
else
|
||||
rune -0 wait-for \
|
||||
--err "while opening capi whitelist file: open $CAPI_WHITELISTS_YAML: permission denied" \
|
||||
"${CROWDSEC}"
|
||||
fi
|
||||
}
|
||||
|
||||
@test "capi_whitelists: empty file" {
|
||||
echo > "$CAPI_WHITELISTS_YAML"
|
||||
rune -1 timeout 1s "${CROWDSEC}"
|
||||
assert_stderr --partial "while parsing capi whitelist file '$CAPI_WHITELISTS_YAML': empty file"
|
||||
rune -0 wait-for \
|
||||
--err "while parsing capi whitelist file '$CAPI_WHITELISTS_YAML': empty file" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "capi_whitelists: empty lists" {
|
||||
echo '{"ips": [], "cidrs": []}' > "$CAPI_WHITELISTS_YAML"
|
||||
rune -124 timeout 1s "${CROWDSEC}"
|
||||
rune -0 wait-for \
|
||||
--err "Starting processing data" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "capi_whitelists: bad ip" {
|
||||
echo '{"ips": ["blahblah"], "cidrs": []}' > "$CAPI_WHITELISTS_YAML"
|
||||
rune -1 timeout 1s "${CROWDSEC}"
|
||||
assert_stderr --partial "while parsing capi whitelist file '$CAPI_WHITELISTS_YAML': invalid IP address: blahblah"
|
||||
rune -0 wait-for \
|
||||
--err "while parsing capi whitelist file '$CAPI_WHITELISTS_YAML': invalid IP address: blahblah" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "capi_whitelists: bad cidr" {
|
||||
echo '{"ips": [], "cidrs": ["blahblah"]}' > "$CAPI_WHITELISTS_YAML"
|
||||
rune -1 timeout 1s "${CROWDSEC}"
|
||||
assert_stderr --partial "while parsing capi whitelist file '$CAPI_WHITELISTS_YAML': invalid CIDR address: blahblah"
|
||||
rune -0 wait-for \
|
||||
--err "while parsing capi whitelist file '$CAPI_WHITELISTS_YAML': invalid CIDR address: blahblah" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "capi_whitelists: file with ip and cidr values" {
|
||||
|
|
|
@ -72,16 +72,32 @@ teardown() {
|
|||
}
|
||||
|
||||
@test "cscli hub update" {
|
||||
#XXX: todo
|
||||
:
|
||||
rm -f "$INDEX_PATH"
|
||||
rune -0 cscli hub update
|
||||
assert_stderr --partial "Wrote index to $INDEX_PATH"
|
||||
rune -0 cscli hub update
|
||||
assert_stderr --partial "hub index is up to date"
|
||||
}
|
||||
|
||||
@test "cscli hub upgrade" {
|
||||
#XXX: todo
|
||||
:
|
||||
}
|
||||
rune -0 cscli hub upgrade
|
||||
assert_stderr --partial "Upgrading parsers"
|
||||
assert_stderr --partial "Upgraded 0 parsers"
|
||||
assert_stderr --partial "Upgrading postoverflows"
|
||||
assert_stderr --partial "Upgraded 0 postoverflows"
|
||||
assert_stderr --partial "Upgrading scenarios"
|
||||
assert_stderr --partial "Upgraded 0 scenarios"
|
||||
assert_stderr --partial "Upgrading collections"
|
||||
assert_stderr --partial "Upgraded 0 collections"
|
||||
|
||||
@test "cscli hub upgrade --force" {
|
||||
#XXX: todo
|
||||
:
|
||||
rune -0 cscli parsers install crowdsecurity/syslog-logs
|
||||
rune -0 cscli hub upgrade
|
||||
assert_stderr --partial "crowdsecurity/syslog-logs: up-to-date"
|
||||
|
||||
rune -0 cscli hub upgrade --force
|
||||
assert_stderr --partial "crowdsecurity/syslog-logs: overwrite"
|
||||
assert_stderr --partial "crowdsecurity/syslog-logs: updated"
|
||||
assert_stderr --partial "Upgraded 1 parsers"
|
||||
# this is used by the cron script to know if the hub was updated
|
||||
assert_output --partial "updated crowdsecurity/syslog-logs"
|
||||
}
|
||||
|
|
|
@ -187,7 +187,6 @@ teardown() {
|
|||
# one item, json
|
||||
rune -0 cscli collections inspect crowdsecurity/sshd -o json
|
||||
rune -0 jq -c '[.type, .name, .author, .path, .installed]' <(output)
|
||||
# XXX: .installed is missing -- not false
|
||||
assert_json '["collections","crowdsecurity/sshd","crowdsecurity","collections/crowdsecurity/sshd.yaml",false]'
|
||||
|
||||
# one item, raw
|
||||
|
@ -220,7 +219,7 @@ teardown() {
|
|||
rune -0 cscli collections inspect crowdsecurity/sshd crowdsecurity/smb -o raw
|
||||
assert_output --partial 'crowdsecurity/sshd'
|
||||
assert_output --partial 'crowdsecurity/smb'
|
||||
run -1 grep -c 'Current metrics:' <(output)
|
||||
rune -1 grep -c 'Current metrics:' <(output)
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
|
@ -230,16 +229,23 @@ teardown() {
|
|||
rune -1 cscli collections remove blahblah/blahblah
|
||||
assert_stderr --partial "can't find 'blahblah/blahblah' in collections"
|
||||
|
||||
rune -0 cscli collections remove crowdsecurity/sshd --purge
|
||||
rune -0 cscli collections remove crowdsecurity/sshd
|
||||
assert_stderr --partial 'removing crowdsecurity/sshd: not downloaded -- no removal required'
|
||||
|
||||
rune -0 cscli collections install crowdsecurity/sshd --download-only
|
||||
rune -0 cscli collections remove crowdsecurity/sshd
|
||||
assert_stderr --partial 'removing crowdsecurity/sshd: already uninstalled'
|
||||
assert_stderr --partial 'removing crowdsecurity/sshd: not installed -- no need to remove'
|
||||
|
||||
rune -0 cscli collections install crowdsecurity/sshd
|
||||
rune -0 cscli collections remove crowdsecurity/sshd
|
||||
assert_stderr --partial 'Removed crowdsecurity/sshd'
|
||||
|
||||
rune -0 cscli collections remove crowdsecurity/sshd --purge
|
||||
assert_stderr --partial 'Removed source file [crowdsecurity/sshd]'
|
||||
|
||||
rune -0 cscli collections remove crowdsecurity/sshd
|
||||
assert_stderr --partial 'removing crowdsecurity/sshd: not installed -- no need to remove'
|
||||
|
||||
rune -0 cscli collections remove crowdsecurity/sshd --purge
|
||||
assert_stderr --partial 'removing crowdsecurity/sshd: not downloaded -- no need to remove'
|
||||
|
||||
# install, then remove, check files
|
||||
rune -0 cscli collections install crowdsecurity/sshd
|
||||
assert_file_exists "$CONFIG_DIR/collections/sshd.yaml"
|
||||
|
|
|
@ -70,6 +70,27 @@ teardown() {
|
|||
rune -1 cscli collections inspect crowdsecurity/sshd --no-metrics -o json
|
||||
# XXX: we are on the verbose side here...
|
||||
rune -0 jq -r ".msg" <(stderr)
|
||||
assert_output "failed to read Hub index: failed to sync items: failed to scan $CONFIG_DIR: while syncing collections sshd.yaml: 1.2.3.4: Invalid Semantic Version. Run 'sudo cscli hub update' to download the index again"
|
||||
assert_output --regexp "failed to read Hub index: failed to sync items: failed to scan .*: while syncing collections sshd.yaml: 1.2.3.4: Invalid Semantic Version. Run 'sudo cscli hub update' to download the index again"
|
||||
}
|
||||
|
||||
@test "removing or purging an item already removed by hand" {
|
||||
rune -0 cscli parsers install crowdsecurity/syslog-logs
|
||||
rune -0 cscli parsers inspect crowdsecurity/syslog-logs -o json
|
||||
rune -0 jq -r '.local_path' <(output)
|
||||
rune -0 rm "$(output)"
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/syslog-logs --debug
|
||||
assert_stderr --partial "Removed crowdsecurity/syslog-logs"
|
||||
|
||||
rune -0 cscli parsers inspect crowdsecurity/syslog-logs -o json
|
||||
rune -0 jq -r '.path' <(output)
|
||||
rune -0 rm "$HUB_DIR/$(output)"
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/syslog-logs --purge
|
||||
assert_stderr --partial "removing crowdsecurity/syslog-logs: not downloaded -- no need to remove"
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/linux --all --error --purge --force
|
||||
rune -0 cscli collections remove crowdsecurity/linux --all --error --purge --force
|
||||
refute_output
|
||||
refute_stderr
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ teardown() {
|
|||
#----------
|
||||
|
||||
@test "cscli parsers list" {
|
||||
hub_purge_all
|
||||
|
||||
# no items
|
||||
rune -0 cscli parsers list
|
||||
assert_output --partial "PARSERS"
|
||||
|
@ -221,7 +223,7 @@ teardown() {
|
|||
rune -0 cscli parsers inspect crowdsecurity/sshd-logs crowdsecurity/whitelists -o raw
|
||||
assert_output --partial 'crowdsecurity/sshd-logs'
|
||||
assert_output --partial 'crowdsecurity/whitelists'
|
||||
run -1 grep -c 'Current metrics:' <(output)
|
||||
rune -1 grep -c 'Current metrics:' <(output)
|
||||
assert_output "0"
|
||||
}
|
||||
|
||||
|
@ -231,16 +233,23 @@ teardown() {
|
|||
rune -1 cscli parsers remove blahblah/blahblah
|
||||
assert_stderr --partial "can't find 'blahblah/blahblah' in parsers"
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists --purge
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists
|
||||
assert_stderr --partial 'removing crowdsecurity/whitelists: not downloaded -- no removal required'
|
||||
|
||||
rune -0 cscli parsers install crowdsecurity/whitelists --download-only
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists
|
||||
assert_stderr --partial 'removing crowdsecurity/whitelists: already uninstalled'
|
||||
assert_stderr --partial "removing crowdsecurity/whitelists: not installed -- no need to remove"
|
||||
|
||||
rune -0 cscli parsers install crowdsecurity/whitelists
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists
|
||||
assert_stderr --partial "Removed crowdsecurity/whitelists"
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists --purge
|
||||
assert_stderr --partial 'Removed source file [crowdsecurity/whitelists]'
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists
|
||||
assert_stderr --partial "removing crowdsecurity/whitelists: not installed -- no need to remove"
|
||||
|
||||
rune -0 cscli parsers remove crowdsecurity/whitelists --purge
|
||||
assert_stderr --partial 'removing crowdsecurity/whitelists: not downloaded -- no need to remove'
|
||||
|
||||
# install, then remove, check files
|
||||
rune -0 cscli parsers install crowdsecurity/whitelists
|
||||
assert_file_exists "$CONFIG_DIR/parsers/s02-enrich/whitelists.yaml"
|
||||
|
|
|
@ -191,7 +191,6 @@ teardown() {
|
|||
# one item, json
|
||||
rune -0 cscli postoverflows inspect crowdsecurity/rdns -o json
|
||||
rune -0 jq -c '[.type, .stage, .name, .author, .path, .installed]' <(output)
|
||||
# XXX: .installed is missing -- not false
|
||||
assert_json '["postoverflows","s00-enrich","crowdsecurity/rdns","crowdsecurity","postoverflows/s00-enrich/crowdsecurity/rdns.yaml",false]'
|
||||
|
||||
# one item, raw
|
||||
|
@ -235,16 +234,23 @@ teardown() {
|
|||
rune -1 cscli postoverflows remove blahblah/blahblah
|
||||
assert_stderr --partial "can't find 'blahblah/blahblah' in postoverflows"
|
||||
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns --purge
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns
|
||||
assert_stderr --partial 'removing crowdsecurity/rdns: not downloaded -- no removal required'
|
||||
|
||||
rune -0 cscli postoverflows install crowdsecurity/rdns --download-only
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns
|
||||
assert_stderr --partial 'removing crowdsecurity/rdns: already uninstalled'
|
||||
assert_stderr --partial "removing crowdsecurity/rdns: not installed -- no need to remove"
|
||||
|
||||
rune -0 cscli postoverflows install crowdsecurity/rdns
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns
|
||||
assert_stderr --partial 'Removed crowdsecurity/rdns'
|
||||
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns --purge
|
||||
assert_stderr --partial 'Removed source file [crowdsecurity/rdns]'
|
||||
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns
|
||||
assert_stderr --partial 'removing crowdsecurity/rdns: not installed -- no need to remove'
|
||||
|
||||
rune -0 cscli postoverflows remove crowdsecurity/rdns --purge
|
||||
assert_stderr --partial 'removing crowdsecurity/rdns: not downloaded -- no need to remove'
|
||||
|
||||
# install, then remove, check files
|
||||
rune -0 cscli postoverflows install crowdsecurity/rdns
|
||||
assert_file_exists "$CONFIG_DIR/postoverflows/s00-enrich/rdns.yaml"
|
||||
|
|
|
@ -232,16 +232,23 @@ teardown() {
|
|||
rune -1 cscli scenarios remove blahblah/blahblah
|
||||
assert_stderr --partial "can't find 'blahblah/blahblah' in scenarios"
|
||||
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf --purge
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf
|
||||
assert_stderr --partial 'removing crowdsecurity/ssh-bf: not downloaded -- no removal required'
|
||||
|
||||
rune -0 cscli scenarios install crowdsecurity/ssh-bf --download-only
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf
|
||||
assert_stderr --partial 'removing crowdsecurity/ssh-bf: already uninstalled'
|
||||
assert_stderr --partial "removing crowdsecurity/ssh-bf: not installed -- no need to remove"
|
||||
|
||||
rune -0 cscli scenarios install crowdsecurity/ssh-bf
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf
|
||||
assert_stderr --partial "Removed crowdsecurity/ssh-bf"
|
||||
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf --purge
|
||||
assert_stderr --partial 'Removed source file [crowdsecurity/ssh-bf]'
|
||||
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf
|
||||
assert_stderr --partial "removing crowdsecurity/ssh-bf: not installed -- no need to remove"
|
||||
|
||||
rune -0 cscli scenarios remove crowdsecurity/ssh-bf --purge
|
||||
assert_stderr --partial 'removing crowdsecurity/ssh-bf: not downloaded -- no need to remove'
|
||||
|
||||
# install, then remove, check files
|
||||
rune -0 cscli scenarios install crowdsecurity/ssh-bf
|
||||
assert_file_exists "$CONFIG_DIR/scenarios/ssh-bf.yaml"
|
||||
|
|
|
@ -78,15 +78,17 @@ teardown() {
|
|||
@test "missing key_file" {
|
||||
config_set '.api.server.tls.key_file=""'
|
||||
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "missing TLS key file"
|
||||
rune -0 wait-for \
|
||||
--err "missing TLS key file" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "missing cert_file" {
|
||||
config_set '.api.server.tls.cert_file=""'
|
||||
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "missing TLS cert file"
|
||||
rune -0 wait-for \
|
||||
--err "missing TLS cert file" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "invalid OU for agent" {
|
||||
|
|
|
@ -27,7 +27,7 @@ setup() {
|
|||
teardown() {
|
||||
./instance-crowdsec stop
|
||||
rm -f "${PLUGIN_DIR}"/badname
|
||||
chmod go-w "${PLUGIN_DIR}"/notification-http
|
||||
chmod go-w "${PLUGIN_DIR}"/notification-http || true
|
||||
}
|
||||
|
||||
#----------
|
||||
|
@ -35,36 +35,41 @@ teardown() {
|
|||
@test "misconfigured plugin, only user is empty" {
|
||||
config_set '.plugin_config.user="" | .plugin_config.group="nogroup"'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: both plugin user and group must be set"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: both plugin user and group must be set" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "misconfigured plugin, only group is empty" {
|
||||
config_set '(.plugin_config.user="nobody") | (.plugin_config.group="")'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: both plugin user and group must be set"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: both plugin user and group must be set" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "misconfigured plugin, user does not exist" {
|
||||
config_set '(.plugin_config.user="userdoesnotexist") | (.plugin_config.group="groupdoesnotexist")'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: user: unknown user userdoesnotexist"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: user: unknown user userdoesnotexist" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "misconfigured plugin, group does not exist" {
|
||||
config_set '(.plugin_config.user=strenv(USER)) | (.plugin_config.group="groupdoesnotexist")'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: group: unknown group groupdoesnotexist"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: while getting process attributes: group: unknown group groupdoesnotexist" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "bad plugin name" {
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
cp "${PLUGIN_DIR}"/notification-http "${PLUGIN_DIR}"/badname
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: plugin name ${PLUGIN_DIR}/badname is invalid. Name should be like {type-name}"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: plugin name ${PLUGIN_DIR}/badname is invalid. Name should be like {type-name}" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "duplicate notification config" {
|
||||
|
@ -75,48 +80,55 @@ teardown() {
|
|||
config_set "${PROFILES_PATH}" '.notifications=["slack_default"]'
|
||||
# the slack plugin may fail or not, but we just need the logs
|
||||
config_set '.common.log_media="stdout"'
|
||||
rune timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "notification 'email_default' is defined multiple times"
|
||||
rune wait-for \
|
||||
--err "notification 'email_default' is defined multiple times" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "bad plugin permission (group writable)" {
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
chmod g+w "${PLUGIN_DIR}"/notification-http
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: plugin at ${PLUGIN_DIR}/notification-http is group writable, group writable plugins are invalid"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: plugin at ${PLUGIN_DIR}/notification-http is group writable, group writable plugins are invalid" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "bad plugin permission (world writable)" {
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
chmod o+w "${PLUGIN_DIR}"/notification-http
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin: plugin at ${PLUGIN_DIR}/notification-http is world writable, world writable plugins are invalid"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin: plugin at ${PLUGIN_DIR}/notification-http is world writable, world writable plugins are invalid" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "config.yaml: missing .plugin_config section" {
|
||||
config_set 'del(.plugin_config)'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: plugins are enabled, but the plugin_config section is missing in the configuration"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: plugins are enabled, but the plugin_config section is missing in the configuration" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "config.yaml: missing config_paths.notification_dir" {
|
||||
config_set 'del(.config_paths.notification_dir)'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: plugins are enabled, but config_paths.notification_dir is not defined"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: plugins are enabled, but config_paths.notification_dir is not defined" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "config.yaml: missing config_paths.plugin_dir" {
|
||||
config_set 'del(.config_paths.plugin_dir)'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: plugins are enabled, but config_paths.plugin_dir is not defined"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: plugins are enabled, but config_paths.plugin_dir is not defined" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
||||
@test "unable to run plugin broker: while reading plugin config" {
|
||||
config_set '.config_paths.notification_dir="/this/path/does/not/exist"'
|
||||
config_set "${PROFILES_PATH}" '.notifications=["http_default"]'
|
||||
rune -1 timeout 2s "${CROWDSEC}"
|
||||
assert_stderr --partial "api server init: unable to run plugin broker: while loading plugin config: open /this/path/does/not/exist: no such file or directory"
|
||||
rune -0 wait-for \
|
||||
--err "api server init: unable to run plugin broker: while loading plugin config: open /this/path/does/not/exist: no such file or directory" \
|
||||
"${CROWDSEC}"
|
||||
}
|
||||
|
|
116
test/bin/wait-for
Executable file
116
test/bin/wait-for
Executable file
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import asyncio
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
# TODO: signal handler to terminate spawned process group when wait-for is killed
|
||||
# TODO: better return codes esp. when matches are found
|
||||
# TODO: multiple patterns (multiple out, err, both)
|
||||
# TODO: print unmatched patterns
|
||||
|
||||
|
||||
async def terminate(p):
|
||||
# Terminate the process group (shell, crowdsec plugins)
|
||||
try:
|
||||
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
|
||||
async def monitor(cmd, args, want_out, want_err, timeout):
|
||||
"""Monitor a process and terminate it if a pattern is matched in stdout or stderr.
|
||||
|
||||
Args:
|
||||
cmd: The command to run.
|
||||
args: A list of arguments to pass to the command.
|
||||
stdout: A regular expression pattern to search for in stdout.
|
||||
stderr: A regular expression pattern to search for in stderr.
|
||||
timeout: The maximum number of seconds to wait for the process to terminate.
|
||||
|
||||
Returns:
|
||||
The exit code of the process.
|
||||
"""
|
||||
|
||||
status = None
|
||||
|
||||
async def read_stream(p, stream, outstream, pattern):
|
||||
nonlocal status
|
||||
if stream is None:
|
||||
return
|
||||
while True:
|
||||
line = await stream.readline()
|
||||
if line:
|
||||
line = line.decode('utf-8')
|
||||
outstream.write(line)
|
||||
if pattern and pattern.search(line):
|
||||
await terminate(process)
|
||||
# this is nasty.
|
||||
# if we timeout, we want to return a different exit code
|
||||
# in case of a match, so that the caller can tell
|
||||
# if the application was still running.
|
||||
# XXX: still not good for match found, but return code != 0
|
||||
if timeout != DEFAULT_TIMEOUT:
|
||||
status = 128
|
||||
else:
|
||||
status = 0
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
cmd,
|
||||
*args,
|
||||
# capture stdout
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
# capture stderr
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
# disable buffering
|
||||
bufsize=0,
|
||||
# create a new process group
|
||||
# (required to kill child processes when cmd is a shell)
|
||||
preexec_fn=os.setsid)
|
||||
|
||||
out_regex = re.compile(want_out) if want_out else None
|
||||
err_regex = re.compile(want_err) if want_err else None
|
||||
|
||||
# Apply a timeout
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.wait([
|
||||
asyncio.create_task(process.wait()),
|
||||
asyncio.create_task(read_stream(process, process.stdout, sys.stdout, out_regex)),
|
||||
asyncio.create_task(read_stream(process, process.stderr, sys.stderr, err_regex))
|
||||
]), timeout)
|
||||
if status is None:
|
||||
status = process.returncode
|
||||
except asyncio.TimeoutError:
|
||||
await terminate(process)
|
||||
status = 241
|
||||
|
||||
# Return the same exit code, stdout and stderr as the spawned process
|
||||
return status
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Monitor a process and terminate it if a pattern is matched in stdout or stderr.')
|
||||
parser.add_argument('cmd', help='The command to run.')
|
||||
parser.add_argument('args', nargs=argparse.REMAINDER, help='A list of arguments to pass to the command.')
|
||||
parser.add_argument('--out', default='', help='A regular expression pattern to search for in stdout.')
|
||||
parser.add_argument('--err', default='', help='A regular expression pattern to search for in stderr.')
|
||||
parser.add_argument('--timeout', type=float, default=DEFAULT_TIMEOUT)
|
||||
args = parser.parse_args()
|
||||
|
||||
exit_code = await monitor(args.cmd, args.args, args.out, args.err, args.timeout)
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
|
@ -38,6 +38,8 @@ DATA_DIR="${LOCAL_DIR}/${REL_DATA_DIR}"
|
|||
export DATA_DIR
|
||||
CONFIG_DIR="${LOCAL_DIR}/${REL_CONFIG_DIR}"
|
||||
export CONFIG_DIR
|
||||
HUB_DIR="${CONFIG_DIR}/hub"
|
||||
export HUB_DIR
|
||||
|
||||
if [[ $(uname) == "OpenBSD" ]]; then
|
||||
TAR=gtar
|
||||
|
@ -52,6 +54,51 @@ remove_init_data() {
|
|||
|
||||
# we need a separate function for initializing config when testing package
|
||||
# because we want to test the configuration as well
|
||||
preload_hub_items() {
|
||||
# pre-download everything but don't install anything
|
||||
# each test can install what it needs
|
||||
|
||||
echo "Purging existing hub..."
|
||||
|
||||
"$CSCLI" parsers delete --all --error --purge --force
|
||||
"$CSCLI" scenarios delete --all --error --purge --force
|
||||
"$CSCLI" postoverflows delete --all --error --purge --force
|
||||
"$CSCLI" collections delete --all --error --purge --force
|
||||
|
||||
echo "Pre-downloading hub content..."
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" collections install \
|
||||
$("$CSCLI" collections list -a -o json | jq -r '.collections[].name') \
|
||||
--download-only \
|
||||
--error
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" parsers install \
|
||||
$("$CSCLI" parsers list -a -o json | jq -r '.parsers[].name') \
|
||||
--download-only \
|
||||
--error
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" scenarios install \
|
||||
$("$CSCLI" scenarios list -a -o json | jq -r '.scenarios[].name') \
|
||||
--download-only \
|
||||
--error
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" postoverflows install \
|
||||
$("$CSCLI" postoverflows list -a -o json | jq -r '.postoverflows[].name') \
|
||||
--download-only \
|
||||
--error
|
||||
|
||||
# XXX: download-only works only for collections, not for parsers, scenarios, postoverflows.
|
||||
# so we have to delete the links manually, and leave the downloaded files in place
|
||||
|
||||
"$CSCLI" parsers delete --all --error
|
||||
"$CSCLI" scenarios delete --all --error
|
||||
"$CSCLI" postoverflows delete --all --error
|
||||
}
|
||||
|
||||
make_init_data() {
|
||||
./bin/assert-crowdsec-not-running || die "Cannot create fixture data."
|
||||
|
||||
|
@ -61,6 +108,8 @@ make_init_data() {
|
|||
# when installed packages are always using sqlite, so no need to regenerate
|
||||
# local credz for sqlite
|
||||
|
||||
preload_hub_items
|
||||
|
||||
[[ "${DB_BACKEND}" == "sqlite" ]] || ${CSCLI} machines add --auto
|
||||
|
||||
mkdir -p "$LOCAL_INIT_DIR"
|
||||
|
|
|
@ -105,31 +105,38 @@ preload_hub_items() {
|
|||
# pre-download everything but don't install anything
|
||||
# each test can install what it needs
|
||||
|
||||
echo "Downloading collections..."
|
||||
echo "Purging existing hub..."
|
||||
|
||||
"$CSCLI" parsers delete --all --error --purge --force
|
||||
"$CSCLI" scenarios delete --all --error --purge --force
|
||||
"$CSCLI" postoverflows delete --all --error --purge --force
|
||||
"$CSCLI" collections delete --all --error --purge --force
|
||||
|
||||
echo "Pre-downloading hub content..."
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" collections install \
|
||||
$("$CSCLI" collections list -a -o json | jq -r '.collections[].name') \
|
||||
--download-only \
|
||||
--warning
|
||||
--error
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" parsers install \
|
||||
$("$CSCLI" parsers list -a -o json | jq -r '.parsers[].name') \
|
||||
--download-only \
|
||||
--warning
|
||||
--error
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" scenarios install \
|
||||
$("$CSCLI" scenarios list -a -o json | jq -r '.scenarios[].name') \
|
||||
--download-only \
|
||||
--warning
|
||||
--error
|
||||
|
||||
#shellcheck disable=SC2046
|
||||
"$CSCLI" postoverflows install \
|
||||
$("$CSCLI" postoverflows list -a -o json | jq -r '.postoverflows[].name') \
|
||||
--download-only \
|
||||
--warning
|
||||
--error
|
||||
|
||||
# XXX: download-only works only for collections, not for parsers, scenarios, postoverflows.
|
||||
# so we have to delete the links manually, and leave the downloaded files in place
|
||||
|
|
|
@ -20,6 +20,7 @@ eval "$(debug)"
|
|||
# Allow tests to use relative paths for helper scripts.
|
||||
# shellcheck disable=SC2164
|
||||
cd "${TEST_DIR}"
|
||||
export PATH="${TEST_DIR}/bin:${PATH}"
|
||||
|
||||
# complain if there's a crowdsec running system-wide or leftover from a previous test
|
||||
./bin/assert-crowdsec-not-running
|
||||
|
|
Loading…
Reference in a new issue