Skip to content

Commit 05c0813

Browse files
authored
deb, rpm: handle api key creation (skip/ignore) with .yaml.local or remote LAPI (#66)
1 parent 382186b commit 05c0813

File tree

6 files changed

+190
-34
lines changed

6 files changed

+190
-34
lines changed

Pipfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
[packages]
2+
exceptiongroup = "1.1.1"
3+
pexpect = "4.8.0"
24
pytest-cs = {ref = "0.7.15", git = "https://github.com/crowdsecurity/pytest-cs.git"}
3-
pytest-dotenv = "0.5.2"
45
pytest-dependency = "0.5.1"
5-
pexpect = "4.8.0"
6-
exceptiongroup = "1.1.1"
6+
pytest-dotenv = "0.5.2"
7+
zxcvbn = "4.4.28"
78

89
[dev-packages]
910
gnureadline = "8.1.2"

Pipfile.lock

Lines changed: 25 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func main() {
100100
verbose := flag.Bool("v", false, "set verbose mode")
101101
bouncerVersion := flag.Bool("version", false, "display version and exit")
102102
testConfig := flag.Bool("t", false, "test config and exit")
103+
showConfig := flag.Bool("T", false, "show full config (.yaml + .yaml.local) and exit")
103104

104105
flag.Parse()
105106

@@ -117,6 +118,11 @@ func main() {
117118
log.Fatalf("unable to read config file: %s", err)
118119
}
119120

121+
if *showConfig {
122+
fmt.Println(string(configBytes))
123+
return
124+
}
125+
120126
config, err := cfg.NewConfig(bytes.NewReader(configBytes))
121127
if err != nil {
122128
log.Fatalf("unable to load configuration: %s", err)

scripts/_bouncer.sh

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ config_not_set() {
8686
exit 1
8787
fi
8888

89-
before=$(cat "$CONFIG")
89+
before=$("$BOUNCER" -c "$CONFIG" -T)
9090
# shellcheck disable=SC2016
91-
after=$(envsubst "\$$varname" < "$CONFIG")
91+
after=$(echo "$before" | envsubst "\$$varname")
9292

9393
if [ "$before" = "$after" ]; then
9494
return 1
@@ -128,29 +128,31 @@ set_config_var_value() {
128128

129129
set_api_key() {
130130
require 'CONFIG' 'BOUNCER_PREFIX'
131-
local api_key ret unique bouncer_id before
131+
local api_key ret bouncer_id before
132132
# if we can't set the key, the user will take care of it
133-
api_key="<API_KEY>"
134133
ret=0
135134

136135
if command -v cscli >/dev/null; then
137136
echo "cscli/crowdsec is present, generating API key" >&2
138-
unique=$(date +%s)
139-
bouncer_id="$BOUNCER_PREFIX-$unique"
140-
api_key=$(cscli -oraw bouncers add "$bouncer_id")
141-
if [ $? -eq 1 ]; then
137+
bouncer_id="$BOUNCER_PREFIX-$(date +%s)"
138+
api_key=$(cscli -oraw bouncers add "$bouncer_id" || true)
139+
if [ "$api_key" = "" ]; then
142140
echo "failed to create API key" >&2
141+
api_key="<API_KEY>"
143142
ret=1
144143
else
145144
echo "API Key successfully created" >&2
146145
echo "$bouncer_id" > "$CONFIG.id"
147146
fi
148147
else
149148
echo "cscli/crowdsec is not present, please set the API key manually" >&2
149+
api_key="<API_KEY>"
150150
ret=1
151151
fi
152152

153-
set_config_var_value 'API_KEY' "$api_key"
153+
if [ "$api_key" != "" ]; then
154+
set_config_var_value 'API_KEY' "$api_key"
155+
fi
154156

155157
return "$ret"
156158
}
@@ -159,7 +161,8 @@ set_local_port() {
159161
require 'CONFIG'
160162
local port
161163
command -v cscli >/dev/null || return 0
162-
port=$(cscli config show --key "Config.API.Server.ListenURI" | cut -d ":" -f2)
164+
# the following will fail with a non-LAPI local crowdsec, leaving empty port
165+
port=$(cscli config show --key "Config.API.Server.ListenURI" 2>/dev/null | cut -d ":" -f2 || true)
163166
if [ "$port" != "" ]; then
164167
sed -i "s/localhost:8080/127.0.0.1:$port/g" "$CONFIG"
165168
sed -i "s/127.0.0.1:8080/127.0.0.1:$port/g" "$CONFIG"
@@ -180,7 +183,7 @@ set_local_lapi_url() {
180183
fi
181184
command -v cscli >/dev/null || return 0
182185

183-
port=$(cscli config show --key "Config.API.Server.ListenURI" | cut -d ":" -f2 || true)
186+
port=$(cscli config show --key "Config.API.Server.ListenURI" 2>/dev/null | cut -d ":" -f2 || true)
184187
if [ "$port" = "" ]; then
185188
port=8080
186189
fi

scripts/install.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ API_KEY="<API_KEY>"
1313
gen_apikey() {
1414
if command -v cscli >/dev/null; then
1515
msg succ "cscli found, generating bouncer api key."
16-
unique=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 8)
17-
bouncer_id="$BOUNCER_PREFIX-$unique"
16+
bouncer_id="$BOUNCER_PREFIX-$(date +%s)"
1817
API_KEY=$(cscli -oraw bouncers add "$bouncer_id")
1918
echo "$bouncer_id" > "$CONFIG.id"
2019
msg info "API Key: $API_KEY"
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import os
2+
import subprocess
3+
import yaml
4+
from pathlib import Path
5+
6+
import pytest
7+
from zxcvbn import zxcvbn
8+
9+
pytestmark = pytest.mark.deb
10+
11+
12+
# TODO: use fixtures to install/purge and register/unregister bouncers
13+
14+
15+
def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root):
16+
# test the full install-purge cycle, doing that in separate tests would
17+
# be a bit too much
18+
19+
# TODO: remove and reinstall
20+
21+
# use the package built as non-root by test_deb_build()
22+
assert deb_package_path.exists(), f'This test requires {deb_package_path}'
23+
24+
p = subprocess.check_output(
25+
['dpkg-deb', '-f', deb_package_path.as_posix(), 'Package'],
26+
encoding='utf-8'
27+
)
28+
package_name = p.strip()
29+
30+
subprocess.check_call(['dpkg', '--purge', package_name])
31+
32+
bouncer_exe = f"/usr/bin/{bouncer_under_test}"
33+
assert not os.path.exists(bouncer_exe)
34+
35+
config = f"/etc/crowdsec/bouncers/{bouncer_under_test}.yaml"
36+
assert not os.path.exists(config)
37+
38+
# install the package
39+
p = subprocess.run(
40+
['dpkg', '--install', deb_package_path.as_posix()],
41+
stdout=subprocess.PIPE,
42+
stderr=subprocess.PIPE,
43+
encoding='utf-8'
44+
)
45+
assert p.returncode == 0, f'Failed to install {deb_package_path}'
46+
47+
assert os.path.exists(bouncer_exe)
48+
assert os.stat(bouncer_exe).st_mode & 0o777 == 0o755
49+
50+
assert os.path.exists(config)
51+
assert os.stat(config).st_mode & 0o777 == 0o600
52+
53+
with open(config) as f:
54+
cfg = yaml.safe_load(f)
55+
api_key = cfg['api_key']
56+
# the api key has been set to a random value
57+
assert zxcvbn(api_key)['score'] == 4, f"weak api_key: '{api_key}'"
58+
59+
with open(config+'.id') as f:
60+
bouncer_name = f.read().strip()
61+
62+
p = subprocess.check_output(['cscli', 'bouncers', 'list', '-o', 'json'])
63+
bouncers = yaml.safe_load(p)
64+
assert len([b for b in bouncers if b['name'] == bouncer_name]) == 1
65+
66+
p = subprocess.run(
67+
['dpkg', '--purge', package_name],
68+
stdout=subprocess.PIPE,
69+
stderr=subprocess.PIPE,
70+
encoding='utf-8'
71+
)
72+
assert p.returncode == 0, f'Failed to purge {package_name}'
73+
74+
assert not os.path.exists(bouncer_exe)
75+
assert not os.path.exists(config)
76+
77+
78+
def test_deb_install_purge_yaml_local(deb_package_path, bouncer_under_test, must_be_root):
79+
"""
80+
Check .deb package installation with:
81+
82+
- a pre-existing .yaml.local file with an api key
83+
- a pre-registered bouncer
84+
85+
=> the configuration files are not touched (no new api key)
86+
"""
87+
88+
assert deb_package_path.exists(), f'This test requires {deb_package_path}'
89+
90+
p = subprocess.check_output(
91+
['dpkg-deb', '-f', deb_package_path.as_posix(), 'Package'],
92+
encoding='utf-8'
93+
)
94+
package_name = p.strip()
95+
96+
subprocess.check_call(['dpkg', '--purge', package_name])
97+
subprocess.run(['cscli', 'bouncers', 'delete', 'testbouncer'])
98+
99+
bouncer_exe = f"/usr/bin/{bouncer_under_test}"
100+
config = Path(f"/etc/crowdsec/bouncers/{bouncer_under_test}.yaml")
101+
config.parent.mkdir(parents=True, exist_ok=True)
102+
103+
subprocess.check_call(['cscli', 'bouncers', 'add', 'testbouncer', '-k', '123456'])
104+
105+
with open(config.with_suffix('.yaml.local'), 'w') as f:
106+
f.write('api_key: "123456"')
107+
108+
p = subprocess.run(
109+
['dpkg', '--install', deb_package_path.as_posix()],
110+
stdout=subprocess.PIPE,
111+
stderr=subprocess.PIPE,
112+
encoding='utf-8'
113+
)
114+
assert p.returncode == 0, f'Failed to install {deb_package_path}'
115+
116+
assert os.path.exists(bouncer_exe)
117+
assert os.path.exists(config)
118+
119+
with open(config) as f:
120+
cfg = yaml.safe_load(f)
121+
api_key = cfg['api_key']
122+
# the api key has not been set
123+
assert api_key == '${API_KEY}'
124+
125+
p = subprocess.check_output([bouncer_exe, '-c', config, '-T'])
126+
merged_config = yaml.safe_load(p)
127+
assert merged_config['api_key'] == '123456'
128+
129+
os.unlink(config.with_suffix('.yaml.local'))
130+
131+
p = subprocess.run(
132+
['dpkg', '--purge', package_name],
133+
stdout=subprocess.PIPE,
134+
stderr=subprocess.PIPE,
135+
encoding='utf-8'
136+
)
137+
assert p.returncode == 0, f'Failed to purge {package_name}'
138+
139+
assert not os.path.exists(bouncer_exe)
140+
assert not os.path.exists(config)

0 commit comments

Comments
 (0)