Propagate taints to top collections (fix #2064) (#2066)

This commit is contained in:
mmetc 2023-02-21 22:12:08 +01:00 committed by GitHub
parent 76ea3a063f
commit be18fea136
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 37 deletions

View file

@ -6,10 +6,14 @@ Test collection management
from http import HTTPStatus
import json
import os
import pwd
import time
from pytest_cs import wait_for_log, wait_for_http
import pytest
import yaml
pytestmark = pytest.mark.docker
@ -75,3 +79,59 @@ def test_install_and_disable_collection(crowdsec, flavor):
logs = cont.logs().decode().splitlines()
# check that there was no attempt to install
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
def test_taint_bubble_up(crowdsec, tmp_path_factory, flavor):
coll = 'crowdsecurity/nginx'
env = {
'COLLECTIONS': f'{coll}'
}
hub = tmp_path_factory.mktemp("hub")
volumes = {
hub: {'bind': '/etc/crowdsec/hub', 'mode': 'rw'}
}
with crowdsec(flavor=flavor, environment=env, volumes=volumes) as cont:
wait_for_http(cont, 8080, '/health', want_status=HTTPStatus.OK)
res = cont.exec_run('cscli collections list -o json')
assert res.exit_code == 0
j = json.loads(res.output)
items = {c['name']: c for c in j['collections']}
# implicit check for tainted=False
assert items[coll]['status'] == 'enabled'
wait_for_log(cont, [
f'*Enabled collections : {coll}*',
])
# change file permissions to allow edit
current_uid = pwd.getpwuid(os.getuid()).pw_uid
res = cont.exec_run(f'chown -R {current_uid} /etc/crowdsec/hub')
assert res.exit_code == 0
scenario = 'crowdsecurity/http-crawl-non_statics'
scenario_file = hub / f'scenarios/{scenario}.yaml'
with open(scenario_file) as f:
yml = yaml.safe_load(f)
yml['description'] += ' (tainted)'
# won't be able to read it back because description is taken from the index
with open(scenario_file, 'w') as f:
yaml.dump(yml, f)
with crowdsec(flavor=flavor, environment=env, volumes=volumes) as cont:
wait_for_http(cont, 8080, '/health', want_status=HTTPStatus.OK)
res = cont.exec_run(f'cscli scenarios inspect {scenario} -o json')
assert res.exit_code == 0
j = json.loads(res.output)
assert j['tainted'] is True
res = cont.exec_run('cscli collections list -o json')
assert res.exit_code == 0
j = json.loads(res.output)
items = {c['name']: c for c in j['collections']}
assert items['crowdsecurity/nginx']['status'] == 'enabled,tainted'
assert items['crowdsecurity/base-http-scenarios']['status'] == 'enabled,tainted'

View file

@ -267,6 +267,9 @@ func CollecDepsCheck(v *Item) error {
if val.Type == COLLECTIONS {
log.Tracef("collec, recurse.")
if err := CollecDepsCheck(&val); err != nil {
if val.Tainted {
v.Tainted = true
}
return fmt.Errorf("sub collection %s is broken : %s", val.Name, err)
}
hubIdx[ptrtype][p] = val

View file

@ -28,87 +28,118 @@ teardown() {
}
@test "there are 2 collections (linux and sshd)" {
run -0 --separate-stderr cscli collections list -o json
run -0 jq '.collections | length' <(output)
rune -0 cscli collections list -o json
rune -0 jq '.collections | length' <(output)
assert_output 2
}
@test "can install a collection (as a regular user) and remove it" {
# collection is not installed
run -0 --separate-stderr cscli collections list -o json
run -0 jq -r '.collections[].name' <(output)
rune -0 cscli collections list -o json
rune -0 jq -r '.collections[].name' <(output)
refute_line "crowdsecurity/mysql"
# we install it
run -0 --separate-stderr cscli collections install crowdsecurity/mysql -o human
rune -0 cscli collections install crowdsecurity/mysql -o human
assert_stderr --partial "Enabled crowdsecurity/mysql"
# it has been installed
run -0 --separate-stderr cscli collections list -o json
run -0 jq -r '.collections[].name' <(output)
rune -0 cscli collections list -o json
rune -0 jq -r '.collections[].name' <(output)
assert_line "crowdsecurity/mysql"
# we install it
run -0 cscli collections remove crowdsecurity/mysql -o human
assert_output --partial "Removed symlink [crowdsecurity/mysql]"
rune -0 cscli collections remove crowdsecurity/mysql -o human
assert_stderr --partial "Removed symlink [crowdsecurity/mysql]"
# it has been removed
run -0 --separate-stderr cscli collections list -o json
run -0 jq -r '.collections[].name' <(output)
rune -0 cscli collections list -o json
rune -0 jq -r '.collections[].name' <(output)
refute_line "crowdsecurity/mysql"
}
@test "must use --force to remove a collection that belongs to another, which becomes tainted" {
# we expect no error since we may have multiple collections, some removed and some not
run -0 --separate-stderr cscli collections remove crowdsecurity/sshd
rune -0 cscli collections remove crowdsecurity/sshd
assert_stderr --partial "crowdsecurity/sshd belongs to other collections"
assert_stderr --partial "[crowdsecurity/linux]"
run -0 --separate-stderr cscli collections remove crowdsecurity/sshd --force
rune -0 cscli collections remove crowdsecurity/sshd --force
assert_stderr --partial "Removed symlink [crowdsecurity/sshd]"
run -0 --separate-stderr cscli collections inspect crowdsecurity/linux -o json
run -0 jq -r '.tainted' <(output)
rune -0 cscli collections inspect crowdsecurity/linux -o json
rune -0 jq -r '.tainted' <(output)
assert_output "true"
}
@test "can remove a collection" {
run -0 cscli collections remove crowdsecurity/linux
assert_output --partial "Removed"
assert_output --regexp ".*for the new configuration to be effective."
run -0 cscli collections inspect crowdsecurity/linux -o human
rune -0 cscli collections remove crowdsecurity/linux
assert_stderr --partial "Removed"
assert_stderr --regexp ".*for the new configuration to be effective."
rune -0 cscli collections inspect crowdsecurity/linux -o human
assert_line 'installed: false'
}
@test "collections delete is an alias for collections remove" {
run -0 cscli collections delete crowdsecurity/linux
assert_output --partial "Removed"
assert_output --regexp ".*for the new configuration to be effective."
rune -0 cscli collections delete crowdsecurity/linux
assert_stderr --partial "Removed"
assert_stderr --regexp ".*for the new configuration to be effective."
}
@test "removing a collection that does not exist is noop" {
run -0 cscli collections remove crowdsecurity/apache2
refute_output --partial "Removed"
assert_output --regexp ".*for the new configuration to be effective."
rune -0 cscli collections remove crowdsecurity/apache2
refute_stderr --partial "Removed"
assert_stderr --regexp ".*for the new configuration to be effective."
}
@test "can remove a removed collection" {
run -0 cscli collections install crowdsecurity/mysql
run -0 cscli collections remove crowdsecurity/mysql
assert_output --partial "Removed"
run -0 cscli collections remove crowdsecurity/mysql
refute_output --partial "Removed"
rune -0 cscli collections install crowdsecurity/mysql
rune -0 cscli collections remove crowdsecurity/mysql
assert_stderr --partial "Removed"
rune -0 cscli collections remove crowdsecurity/mysql
refute_stderr --partial "Removed"
}
@test "can remove all collections" {
# we may have this too, from package installs
run cscli parsers delete crowdsecurity/whitelists
run -0 cscli collections remove --all
assert_output --partial "Removed symlink [crowdsecurity/sshd]"
assert_output --partial "Removed symlink [crowdsecurity/linux]"
run -0 --separate-stderr cscli hub list -o json
rune cscli parsers delete crowdsecurity/whitelists
rune -0 cscli collections remove --all
assert_stderr --partial "Removed symlink [crowdsecurity/sshd]"
assert_stderr --partial "Removed symlink [crowdsecurity/linux]"
rune -0 --separate-stderr cscli hub list -o json
assert_json '{collections:[],parsers:[],postoverflows:[],scenarios:[]}'
run -0 cscli collections remove --all
assert_output --partial 'Disabled 0 items'
rune -0 cscli collections remove --all
assert_stderr --partial 'Disabled 0 items'
}
@test "a taint bubbles up to the top collection" {
coll=crowdsecurity/nginx
subcoll=crowdsecurity/base-http-scenarios
scenario=crowdsecurity/http-crawl-non_statics
# install a collection with dependencies
rune -0 cscli collections install "$coll"
# the collection, subcollection and scenario are installed and not tainted
# we have to default to false because tainted is (as of 1.4.6) returned
# only when true
rune -0 cscli collections inspect "$coll" -o json
rune -0 jq -e '(.installed,.tainted|false)==(true,false)' <(output)
rune -0 cscli collections inspect "$subcoll" -o json
rune -0 jq -e '(.installed,.tainted|false)==(true,false)' <(output)
rune -0 cscli scenarios inspect "$scenario" -o json
rune -0 jq -e '(.installed,.tainted|false)==(true,false)' <(output)
# we taint the scenario
HUB_DIR=$(config_get '.config_paths.hub_dir')
yq e '.description="I am tainted"' -i "$HUB_DIR/scenarios/$scenario.yaml"
# the collection, subcollection and scenario are now tainted
rune -0 cscli scenarios inspect "$scenario" -o json
rune -0 jq -e '(.installed,.tainted)==(true,true)' <(output)
rune -0 cscli collections inspect "$subcoll" -o json
rune -0 jq -e '(.installed,.tainted)==(true,true)' <(output)
rune -0 cscli collections inspect "$coll" -o json
rune -0 jq -e '(.installed,.tainted)==(true,true)' <(output)
}
# TODO test download-only