CI: packaging tests (#261)

This commit is contained in:
mmetc
2023-04-24 22:54:15 +02:00
committed by GitHub
parent 7f6ba97712
commit 6062710083
33 changed files with 545 additions and 197 deletions

View File

@@ -52,9 +52,12 @@ jobs:
- name: Install functional test dependencies
run: |
sudo apt install -y nftables iptables ipset
docker network create net-test
python3 -m pip install --upgrade pipenv wheel
pipenv install --deploy
# some tests need root, so we have to install pytest twice
sudo python3 -m pip install --upgrade pipenv wheel
sudo pipenv install --deploy
docker network create net-test
- name: Run functional tests
env:
@@ -62,8 +65,24 @@ jobs:
CROWDSEC_TEST_FLAVORS: full
CROWDSEC_TEST_NETWORK: net-test
CROWDSEC_TEST_TIMEOUT: 60
PYTEST_ADDOPTS: --durations=0 -vv --color=yes -m "not (deb or rpm)"
run: |
sudo pipenv run pytest --durations=0 --color=yes
# everything except for
# - install (requires root, ignored by default)
# - backends (requires root, ignored by default)
# - deb/rpm (on their own workflows)
pipenv run pytest
# these need root
sudo -E pipenv run pytest ./test/backends
sudo -E pipenv run pytest ./test/install/no_crowdsec
# these need a running crowdsec
docker run -d --name crowdsec -e CI_TESTING=true -e DISABLE_ONLINE_API=true -ti crowdsecurity/crowdsec
install -m 0755 /dev/stdin /usr/local/bin/cscli <<'EOT'
#!/bin/sh
docker exec crowdsec cscli "$@"
EOT
sleep 5
sudo -E pipenv run pytest ./test/install/with_crowdsec
- name: golangci-lint
uses: golangci/golangci-lint-action@v3

55
.github/workflows/tests_deb.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Test .deb packaging
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
build:
name: "Test .deb packages"
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.3
- name: Cache virtualenvs
id: cache-pipenv
uses: actions/cache@v3
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: Install functional test dependencies
run: |
sudo apt install -y build-essential debhelper devscripts fakeroot lintian
docker network create net-test
python3 -m pip install --upgrade pipenv wheel
pipenv install --deploy
sudo python3 -m pip install --upgrade pipenv wheel
sudo pipenv install --deploy
- name: Run functional tests
env:
CROWDSEC_TEST_VERSION: dev
CROWDSEC_TEST_FLAVORS: full
CROWDSEC_TEST_NETWORK: net-test
CROWDSEC_TEST_TIMEOUT: 60
PYTEST_ADDOPTS: --durations=0 -vv --color=yes
run: |
sudo apt install -y nftables iptables ipset
pipenv run pytest test/pkg/test_build_deb.py
sudo -E pipenv run pytest -m deb ./test/install/no_crowdsec

7
.gitignore vendored
View File

@@ -25,3 +25,10 @@ venv/
/debian/*.substvars
/debian/*.debhelper
/debian/*-stamp
# built by rpmbuild
/rpm/BUILD
/rpm/BUILDROOT
/rpm/RPMS
/rpm/SOURCES/*.tar.gz
/rpm/SRPMS

View File

@@ -20,12 +20,15 @@ LD_OPTS_VARS=\
-X '$(GO_MODULE_NAME)/pkg/version.BuildDate=$(BUILD_TIMESTAMP)' \
-X '$(GO_MODULE_NAME)/pkg/version.Tag=$(BUILD_TAG)'
export CGO_ENABLED=0
export LD_OPTS=-ldflags "-a -s -w -extldflags '-static' $(LD_OPTS_VARS)" \
-trimpath -tags netgo
.PHONY: all
all: build test
# same as "$(MAKE) -f debian/rules clean" but without the dependency on debhelper
.PHONY: clean-debian
clean-debian:
@$(RM) -r debian/crowdsec-firewall-bouncer-iptables
@$(RM) -r debian/crowdsec-firewall-bouncer-nftables
@@ -34,9 +37,17 @@ clean-debian:
@$(RM) -r debian/*.substvars
@$(RM) -r debian/*-stamp
.PHONY: clean-rpm
clean-rpm:
@$(RM) -r rpm/BUILD
@$(RM) -r rpm/BUILDROOT
@$(RM) -r rpm/RPMS
@$(RM) -r rpm/SOURCES/*.tar.gz
@$(RM) -r rpm/SRPMS
# Remove everything including all platform binaries and tarballs
.PHONY: clean
clean: clean-release-dir clean-debian
clean: clean-release-dir clean-debian clean-rpm
@$(RM) $(BINARY_NAME)
@$(RM) $(TARBALL_NAME)
@$(RM) -r $(BINARY_NAME)-* # platform binary name and leftover release dir

16
Pipfile
View File

@@ -1,13 +1,15 @@
[packages]
pytest-dotenv = "*"
flask = "*"
pytimeparse = "*"
psutil = "*"
pytest-cs = {ref = "0.6.0", git = "https://github.com/crowdsecurity/pytest-cs.git"}
pytest-cs = {ref = "0.7.13", git = "https://github.com/crowdsecurity/pytest-cs.git"}
pytest-dotenv = "0.5.2"
pytest-dependency = "0.5.1"
pexpect = "4.8.0"
flask = "2.2.3"
pytimeparse = "1.1.8"
psutil = "5.9.5"
[dev-packages]
gnureadline = "*"
ipdb = "*"
gnureadline = "8.1.2"
ipdb = "0.13.13"
[requires]
python_version = "3.10"

126
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "25756c9b09d982d55816194b5fc5f13cbee6b22fd052bfb40fa7a3c13c4a62cf"
"sha256": "14be1a4d221d70c7a026430e5d5483ab0647c78fe980eea54f7e2eef80826050"
},
"pipfile-spec": 6,
"requires": {
@@ -16,14 +16,6 @@
]
},
"default": {
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"certifi": {
"hashes": [
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
@@ -192,28 +184,28 @@
},
"cryptography": {
"hashes": [
"sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405",
"sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356",
"sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472",
"sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41",
"sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a",
"sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88",
"sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554",
"sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db",
"sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778",
"sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c",
"sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917",
"sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797",
"sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160",
"sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c",
"sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c",
"sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2",
"sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4",
"sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122",
"sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94"
"sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440",
"sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288",
"sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b",
"sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958",
"sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b",
"sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d",
"sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a",
"sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404",
"sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b",
"sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e",
"sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2",
"sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c",
"sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b",
"sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9",
"sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b",
"sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636",
"sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99",
"sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e",
"sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"
],
"markers": "python_version >= '3.6'",
"version": "==40.0.1"
"version": "==40.0.2"
},
"docker": {
"hashes": [
@@ -329,11 +321,19 @@
},
"packaging": {
"hashes": [
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
"sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
"version": "==23.0"
"version": "==23.1"
},
"pexpect": {
"hashes": [
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"index": "pypi",
"version": "==4.8.0"
},
"pluggy": {
"hashes": [
@@ -345,23 +345,30 @@
},
"psutil": {
"hashes": [
"sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff",
"sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1",
"sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62",
"sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549",
"sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08",
"sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7",
"sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e",
"sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe",
"sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24",
"sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad",
"sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94",
"sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8",
"sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7",
"sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"
"sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d",
"sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217",
"sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4",
"sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c",
"sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f",
"sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da",
"sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4",
"sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42",
"sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5",
"sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4",
"sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9",
"sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f",
"sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30",
"sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"
],
"index": "pypi",
"version": "==5.9.4"
"version": "==5.9.5"
},
"ptyprocess": {
"hashes": [
"sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
"sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
],
"version": "==0.7.0"
},
"pycparser": {
"hashes": [
@@ -372,15 +379,15 @@
},
"pytest": {
"hashes": [
"sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e",
"sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"
"sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362",
"sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"
],
"markers": "python_version >= '3.7'",
"version": "==7.2.2"
"version": "==7.3.1"
},
"pytest-cs": {
"git": "https://github.com/crowdsecurity/pytest-cs.git",
"ref": "8eb63c808b4ebe1bd0de07bfd593c86b1758a0bc"
"ref": "bf6c63e4cdbc755fb5c7698be750671ec6864584"
},
"pytest-datadir": {
"hashes": [
@@ -390,6 +397,13 @@
"markers": "python_version >= '3.6'",
"version": "==1.4.1"
},
"pytest-dependency": {
"hashes": [
"sha256:c2a892906192663f85030a6ab91304e508e546cddfe557d692d61ec57a1d946b"
],
"index": "pypi",
"version": "==0.5.1"
},
"pytest-dotenv": {
"hashes": [
"sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732",
@@ -616,7 +630,7 @@
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"markers": "sys_platform != 'win32'",
"index": "pypi",
"version": "==4.8.0"
},
"pickleshare": {
@@ -650,11 +664,11 @@
},
"pygments": {
"hashes": [
"sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297",
"sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"
"sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c",
"sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"
],
"markers": "python_version >= '3.6'",
"version": "==2.14.0"
"markers": "python_version >= '3.7'",
"version": "==2.15.1"
},
"six": {
"hashes": [

View File

@@ -2,9 +2,6 @@
systemctl daemon-reload
BOUNCER="crowdsec-firewall-bouncer"
BOUNCER_PREFIX="FirewallBouncer"
#shellcheck source=./scripts/_bouncer.sh
. "/usr/lib/$DPKG_MAINTSCRIPT_PACKAGE/_bouncer.sh"
START=1

View File

@@ -2,9 +2,6 @@
systemctl daemon-reload
BOUNCER="crowdsec-firewall-bouncer"
BOUNCER_PREFIX="FirewallBouncer"
#shellcheck source=./scripts/_bouncer.sh
. "/usr/lib/$DPKG_MAINTSCRIPT_PACKAGE/_bouncer.sh"
START=1

6
debian/rules vendored
View File

@@ -12,9 +12,9 @@ override_dh_auto_clean:
override_dh_auto_test:
override_dh_auto_build:
override_dh_auto_install:
make
@make build
BOUNCER=crowdsec-firewall-bouncer; \
@BOUNCER=crowdsec-firewall-bouncer; \
for BACKEND in iptables nftables; do \
PKG="$$BOUNCER-$$BACKEND"; \
install -D $$BOUNCER -t "debian/$$PKG/usr/bin/"; \
@@ -24,7 +24,7 @@ override_dh_auto_install:
done
execute_after_dh_fixperms:
BOUNCER=crowdsec-firewall-bouncer; \
@BOUNCER=crowdsec-firewall-bouncer; \
for BACKEND in iptables nftables; do \
PKG="$$BOUNCER-$$BACKEND"; \
chmod 0755 "debian/$$PKG/usr/bin/$$BOUNCER"; \

View File

@@ -1,6 +0,0 @@
[pytest]
# drop to pdb on first failure
addopts = --pdb --pdbcls=IPython.terminal.debugger:Pdb
env_files =
.env
default.env

View File

@@ -1,5 +1,12 @@
[pytest]
addopts =
--pdbcls=IPython.terminal.debugger:Pdb
--ignore=test/install
--ignore=test/backends
--strict-markers
markers:
deb: mark tests related to deb packaging
rpm: mark tests related to rpm packaging
env_files =
.env
default.env

View File

@@ -23,7 +23,7 @@ Requires: gettext,iptables,ipset,ipset-libs
%global __mangle_shebangs_exclude_from /usr/bin/env
%prep
%setup -q -T -b 0 -n crowdsec-firewall-bouncer-%{version_number}
%setup -q -T -b 0 -n %{name}-%{version_number}
%build
BUILD_VERSION=%{local_version} make
@@ -31,20 +31,20 @@ BUILD_VERSION=%{local_version} make
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{_bindir}/
install -m 755 %{name} %{buildroot}%{_bindir}/${name}
mkdir -p %{buildroot}%{_bindir}
install -m 755 %{name} %{buildroot}%{_bindir}/%{name}
mkdir -p %{buildroot}/etc/crowdsec/bouncers/
mkdir -p %{buildroot}/etc/crowdsec/bouncers
install -m 600 config/%{name}.yaml %{buildroot}/etc/crowdsec/bouncers/%{name}.yaml
mkdir -p %{buildroot}/usr/lib/%{name}/
mkdir -p %{buildroot}/usr/lib/%{name}
install -m 600 scripts/_bouncer.sh %{buildroot}/usr/lib/%{name}/_bouncer.sh
mkdir -p %{buildroot}%{_unitdir}/
BIN=%{_bindir}/%{name} CFG=/etc/crowdsec/bouncers/ envsubst '$BIN $CFG' < config/%{name}.service | install -m 0644 /dev/stdin %{buildroot}%{_unitdir}/%{name}.service
mkdir -p %{buildroot}%{_unitdir}
BIN=%{_bindir}/%{name} CFG=/etc/crowdsec/bouncers envsubst '$BIN $CFG' < config/%{name}.service | install -m 0644 /dev/stdin %{buildroot}%{_unitdir}/%{name}.service
mkdir -p %{buildroot}%{_presetdir}/
install -D -m 644 %{SOURCE1} %{buildroot}%{_presetdir}/${SOURCE1}
mkdir -p %{buildroot}%{_presetdir}
install -D -m 644 %{SOURCE1} %{buildroot}%{_presetdir}/
%clean
rm -rf %{buildroot}
@@ -57,22 +57,19 @@ rm -rf %{buildroot}
# iptables
# ------------------------------------
%description -n crowdsec-firewall-bouncer-iptables
%description -n %{name}-iptables
%files -n crowdsec-firewall-bouncer-iptables
%files -n %{name}-iptables
%defattr(-,root,root,-)
/usr/bin/%{name}
%{_bindir}/%{name}
/usr/lib/%{name}/_bouncer.sh
%{_unitdir}/%{name}.service
%config(noreplace) /etc/crowdsec/bouncers/%{name}.yaml
%config(noreplace) %{_presetdir}/80-crowdsec-firewall-bouncer.preset
%post -p /usr/bin/sh -n crowdsec-firewall-bouncer-iptables
%post -n %{name}-iptables
systemctl daemon-reload
BOUNCER="crowdsec-firewall-bouncer"
BOUNCER_PREFIX="FirewallBouncer"
. /usr/lib/%{name}/_bouncer.sh
START=1
@@ -89,12 +86,12 @@ if [ "$1" = "1" ]; then
fi
fi
%systemd_post crowdsec-firewall-bouncer.service
set_local_port
%systemd_post %{name}.service
if [ "$START" -eq 0 ]; then
echo "no api key was generated, won't start the service" >&2
echo "no api key was generated, you can generate one on your LAPI Server by running 'cscli bouncers add <bouncer_name>' and add it to '$CONFIG'" >&2
else
%if 0%{?fc35}
systemctl enable "$SERVICE"
@@ -102,8 +99,9 @@ else
systemctl start "$SERVICE"
fi
%preun -p /usr/bin/sh -n crowdsec-firewall-bouncer-iptables
BOUNCER="crowdsec-firewall-bouncer"
echo "$BOUNCER has been successfully installed"
%preun -n %{name}-iptables
. /usr/lib/%{name}/_bouncer.sh
if [ "$1" = "0" ]; then
@@ -112,35 +110,32 @@ if [ "$1" = "0" ]; then
delete_bouncer
fi
%postun -p /usr/bin/sh -n crowdsec-firewall-bouncer-iptables
%postun -n %{name}-iptables
if [ "$1" = "1" ]; then
systemctl restart crowdsec-firewall-bouncer || echo "cannot restart service"
systemctl restart %{name} || echo "cannot restart service"
fi
# ------------------------------------
# nftables
# ------------------------------------
%package -n crowdsec-firewall-bouncer-nftables
%package -n %{name}-nftables
Summary: Firewall bouncer for Crowdsec (nftables configuration)
Requires: nftables,gettext
%description -n crowdsec-firewall-bouncer-nftables
%description -n %{name}-nftables
%files -n crowdsec-firewall-bouncer-nftables
%files -n %{name}-nftables
%defattr(-,root,root,-)
/usr/bin/%{name}
%{_bindir}/%{name}
/usr/lib/%{name}/_bouncer.sh
%{_unitdir}/%{name}.service
%config(noreplace) /etc/crowdsec/bouncers/%{name}.yaml
%config(noreplace) %{_presetdir}/80-crowdsec-firewall-bouncer.preset
%post -p /usr/bin/sh -n crowdsec-firewall-bouncer-nftables
%post -n %{name}-nftables
systemctl daemon-reload
BOUNCER="crowdsec-firewall-bouncer"
BOUNCER_PREFIX="FirewallBouncer"
. /usr/lib/%{name}/_bouncer.sh
START=1
@@ -157,12 +152,12 @@ if [ "$1" = "1" ]; then
fi
fi
%systemd_post crowdsec-firewall-bouncer.service
set_local_port
%systemd_post %{name}.service
if [ "$START" -eq 0 ]; then
echo "no api key was generated, you can generate one on your LAPI Server by running 'cscli bouncers add <bouncer_name>' and add it to '/etc/crowdsec/bouncers/$BOUNCER.yaml'" >&2
echo "no api key was generated, you can generate one on your LAPI Server by running 'cscli bouncers add <bouncer_name>' and add it to '$CONFIG'" >&2
else
%if 0%{?fc35}
systemctl enable "$SERVICE"
@@ -172,8 +167,7 @@ fi
echo "$BOUNCER has been successfully installed"
%preun -p /usr/bin/sh -n crowdsec-firewall-bouncer-nftables
BOUNCER="crowdsec-firewall-bouncer"
%preun -n %{name}-nftables
. /usr/lib/%{name}/_bouncer.sh
if [ "$1" = "0" ]; then
@@ -182,7 +176,7 @@ if [ "$1" = "0" ]; then
delete_bouncer
fi
%postun -p /usr/bin/sh -n crowdsec-firewall-bouncer-nftables
%postun -n %{name}-nftables
if [ "$1" = "1" ]; then
systemctl restart crowdsec-firewall-bouncer || echo "cannot restart service"
systemctl restart %{name} || echo "cannot restart service"
fi

View File

@@ -1,44 +1,44 @@
#!/bin/sh
#shellcheck disable=SC3043
set -eu
BOUNCER="crowdsec-firewall-bouncer"
BOUNCER_PREFIX=$(echo "$BOUNCER" | sed 's/crowdsec-/cs-/g')
# This is a library of functions that can be sourced by other scripts
# to install and configure bouncers.
#
# While not requiring bash, it is not strictly POSIX-compliant because
# it uses local variables, but it should woth with every modern shell.
# it uses local variables, but it should work with every modern shell.
#
# Since passing/parsing arguments in posix sh is tricky, we share
# some environment variables with the functions. It's a matter of
# readability balance between shorter vs cleaner code.
set -eu
set_colors() {
if [ ! -t 0 ]; then
# terminal is not interactive; no colors
FG_RED=""
FG_GREEN=""
FG_YELLOW=""
FG_CYAN=""
RESET=""
elif tput sgr0 >/dev/null; then
# terminfo
FG_RED=$(tput setaf 1)
FG_GREEN=$(tput setaf 2)
FG_YELLOW=$(tput setaf 3)
FG_CYAN=$(tput setaf 6)
RESET=$(tput sgr0)
else
FG_RED=$(printf '%b' '\033[31m')
FG_GREEN=$(printf '%b' '\033[32m')
FG_YELLOW=$(printf '%b' '\033[33m')
FG_CYAN=$(printf '%b' '\033[36m')
RESET=$(printf '%b' '\033[0m')
fi
}
if [ ! -t 0 ]; then
# terminal is not interactive; no colors
FG_RED=""
FG_GREEN=""
FG_YELLOW=""
FG_CYAN=""
RESET=""
elif tput sgr0 >/dev/null; then
# terminfo
FG_RED=$(tput setaf 1)
FG_GREEN=$(tput setaf 2)
FG_YELLOW=$(tput setaf 3)
FG_CYAN=$(tput setaf 6)
RESET=$(tput sgr0)
else
FG_RED=$(printf '%b' '\033[31m')
FG_GREEN=$(printf '%b' '\033[32m')
FG_YELLOW=$(printf '%b' '\033[33m')
FG_CYAN=$(printf '%b' '\033[36m')
RESET=$(printf '%b' '\033[0m')
fi
msg() {
set_colors
case "$1" in
info) echo "${FG_CYAN}$2${RESET}" >&2 ;;
warn) echo "${FG_YELLOW}WARN:${RESET} $2" >&2 ;;
@@ -56,7 +56,6 @@ require() {
# shellcheck disable=SC2034
{
require 'BOUNCER'
SERVICE="$BOUNCER.service"
BIN_PATH_INSTALLED="/usr/local/bin/$BOUNCER"
BIN_PATH="./$BOUNCER"
@@ -69,7 +68,7 @@ SYSTEMD_PATH_FILE="/etc/systemd/system/$SERVICE"
assert_root() {
#shellcheck disable=SC2312
if [ "$(id -u)" -ne 0 ]; then
msg warn "Please run $0 as root or with sudo"
msg err "This script must be run as root"
exit 1
fi
}

View File

@@ -2,9 +2,6 @@
set -eu
BOUNCER="crowdsec-firewall-bouncer"
BOUNCER_PREFIX="cs-firewall-bouncer"
. ./scripts/_bouncer.sh
assert_root
@@ -117,10 +114,10 @@ install_bouncer() {
exit 1
fi
if [ -e "$BIN_PATH_INSTALLED" ]; then
msg warn "$BIN_PATH_INSTALLED is already installed. Exiting"
msg err "$BIN_PATH_INSTALLED is already installed. Exiting"
exit 1
fi
msg info "Installing $BOUNCER"
msg "Installing $BOUNCER"
check_firewall
install -v -m 0755 -D "$BIN_PATH" "$BIN_PATH_INSTALLED"
install -D -m 0600 "./config/$CONFIG_FILE" "$CONFIG"
@@ -143,5 +140,5 @@ else
msg warn "service not started. You need to get an API key and configure it in $CONFIG"
fi
msg succ "The $BOUNCER service has been installed!"
msg succ "The $BOUNCER service has been installed."
exit 0

View File

@@ -2,8 +2,6 @@
set -eu
BOUNCER="crowdsec-firewall-bouncer"
. ./scripts/_bouncer.sh
assert_root
@@ -11,7 +9,7 @@ assert_root
# --------------------------------- #
uninstall() {
systemctl stop "$SERVICE"
systemctl stop "$SERVICE" || true
delete_bouncer
rm -f "$CONFIG"
rm -f "$SYSTEMD_PATH_FILE"

View File

@@ -2,8 +2,6 @@
set -eu
BOUNCER="crowdsec-firewall-bouncer"
. ./scripts/_bouncer.sh
assert_root

View File

@@ -0,0 +1 @@
# required to avoid import of module test from /usr/lib/..

View File

@@ -6,11 +6,12 @@ from ipaddress import ip_address
from pathlib import Path
from time import sleep
from test.mock_lapi import MockLAPI
from test.utils import generate_n_decisions, run_cmd
from test.backends.mock_lapi import MockLAPI
from test.backends.utils import generate_n_decisions, run_cmd
SCRIPT_DIR = Path(os.path.dirname(os.path.realpath(__file__)))
PROJECT_ROOT = SCRIPT_DIR.parent.parent
PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent
BINARY_PATH = PROJECT_ROOT.joinpath("crowdsec-firewall-bouncer")
CONFIG_PATH = SCRIPT_DIR.joinpath("crowdsec-firewall-bouncer.yaml")

View File

@@ -6,11 +6,12 @@ from ipaddress import ip_address
from pathlib import Path
from time import sleep
from test.mock_lapi import MockLAPI
from test.utils import generate_n_decisions, run_cmd
from test.backends.mock_lapi import MockLAPI
from test.backends.utils import generate_n_decisions, run_cmd
SCRIPT_DIR = Path(os.path.dirname(os.path.realpath(__file__)))
PROJECT_ROOT = SCRIPT_DIR.parent.parent
PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent
BINARY_PATH = PROJECT_ROOT.joinpath("crowdsec-firewall-bouncer")
CONFIG_PATH = SCRIPT_DIR.joinpath("crowdsec-firewall-bouncer.yaml")

View File

@@ -1,6 +1,5 @@
import subprocess
from ipaddress import ip_address
from typing import List
def run_cmd(*cmd, ignore_error=False):

View File

@@ -1,11 +1,8 @@
from .conftest import fw_binary
def test_partial_config(crowdsec, bouncer, fw_cfg_factory):
cfg = fw_cfg_factory()
with bouncer(fw_binary, cfg) as fw:
with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
# XXX: improve this message
"*unable to load configuration: config does not contain mode and log mode*",
@@ -15,7 +12,7 @@ def test_partial_config(crowdsec, bouncer, fw_cfg_factory):
cfg['mode'] = 'whatever'
with bouncer(fw_binary, cfg) as fw:
with bouncer(cfg) as fw:
fw.wait_for_lines_fnmatch([
"*firewall 'whatever' is not supported*",
])
@@ -24,4 +21,3 @@ def test_partial_config(crowdsec, bouncer, fw_cfg_factory):
# cfg['mode'] = 'pf'
cfg['api_key'] = ''

View File

@@ -1,32 +1,18 @@
"""
Full integration test with a real Crowdsec running in Docker
"""
import contextlib
import os
import pathlib
import pytest
SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
PROJECT_ROOT = SCRIPT_DIR.parent
fw_binary = PROJECT_ROOT.joinpath("crowdsec-firewall-bouncer")
bouncer_binary = fw_binary
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_sessionstart(session):
if not bouncer_binary.exists() or not os.access(bouncer_binary, os.X_OK):
raise RuntimeError(f"Bouncer binary not found at {bouncer_binary}. Did you build it?")
yield
# Create a lapi container, registers a bouncer
# and runs it with the updated config.
# - Returns context manager that yields a tuple of (bouncer, lapi)
# provide the name of the bouncer binary to test
@pytest.fixture(scope='session')
def bouncer_with_lapi(bouncer, crowdsec, fw_cfg_factory, api_key_factory, tmp_path_factory):
def bouncer_under_test():
return 'crowdsec-firewall-bouncer'
# Create a lapi container, register a bouncer and run it with the updated config.
# - Return context manager that yields a tuple of (bouncer, lapi)
@pytest.fixture(scope='session')
def bouncer_with_lapi(bouncer, crowdsec, fw_cfg_factory, api_key_factory, tmp_path_factory, bouncer_binary):
@contextlib.contextmanager
def closure(config_lapi=None, config_bouncer=None, api_key=None):
if config_bouncer is None:
@@ -44,11 +30,10 @@ def bouncer_with_lapi(bouncer, crowdsec, fw_cfg_factory, api_key_factory, tmp_pa
lapi.wait_for_http(8080, '/health')
port = lapi.probe.get_bound_port('8080')
cfg = fw_cfg_factory()
cfg.setdefault('crowdsec_config', {})
cfg['api_url'] = f'http://localhost:{port}/'
cfg['api_key'] = api_key
cfg.update(config_bouncer)
with bouncer(fw_binary, cfg) as cb:
with bouncer(cfg) as cb:
yield cb, lapi
finally:
pass

View File

@@ -0,0 +1,54 @@
import os
import subprocess
import pytest
pytestmark = pytest.mark.deb
def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root):
# test the full install-purge cycle, doing that in separate tests would
# be a bit too much
# TODO: remove and reinstall
# use the package built as non-root by test_deb_build()
assert deb_package_path.exists(), f'This test requires {deb_package_path}'
bouncer_exe = f"/usr/bin/{bouncer_under_test}"
assert not os.path.exists(bouncer_exe)
config = f"/etc/crowdsec/bouncers/{bouncer_under_test}.yaml"
assert not os.path.exists(config)
# install the package
p = subprocess.run(
['dpkg', '--install', deb_package_path.as_posix()],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='utf-8'
)
assert p.returncode == 0, f'Failed to install {deb_package_path}'
assert os.path.exists(bouncer_exe)
assert os.stat(bouncer_exe).st_mode & 0o777 == 0o755
assert os.path.exists(config)
assert os.stat(config).st_mode & 0o777 == 0o600
p = subprocess.check_output(
['dpkg-deb', '-f', deb_package_path.as_posix(), 'Package'],
encoding='utf-8'
)
package_name = p.strip()
p = subprocess.run(
['dpkg', '--purge', package_name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='utf-8'
)
assert p.returncode == 0, f'Failed to purge {package_name}'
assert not os.path.exists(bouncer_exe)
assert not os.path.exists(config)

View File

@@ -0,0 +1,81 @@
import os
import pexpect
import re
import yaml
import pytest
BOUNCER = "crowdsec-firewall-bouncer"
CONFIG = f"/etc/crowdsec/bouncers/{BOUNCER}.yaml"
@pytest.mark.dependency()
def test_install_no_crowdsec(project_repo, bouncer_binary, must_be_root):
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/install.sh'],
cwd=project_repo
)
c.expect(f"Installing {BOUNCER}")
c.expect("iptables found")
c.expect("nftables found")
c.expect(re.escape("Found nftables (default) and iptables, which firewall "
"do you want to use (nftables/iptables)"))
c.sendline('nftables')
c.expect("WARN.* cscli not found, you will need to generate an api key.")
c.expect(f"WARN.* service not started. You need to get an API key and configure it in {CONFIG}")
c.expect(f"The {BOUNCER} service has been installed.")
c.wait()
assert c.terminated
assert c.exitstatus == 0
with open(CONFIG) as f:
y = yaml.safe_load(f)
assert y['api_key'] == '<API_KEY>'
assert y['mode'] == 'nftables'
assert os.path.exists(CONFIG)
assert os.stat(CONFIG).st_mode & 0o777 == 0o600
assert os.path.exists(f'/usr/local/bin/{BOUNCER}')
assert os.stat(f'/usr/local/bin/{BOUNCER}').st_mode & 0o777 == 0o755
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/install.sh'],
cwd=project_repo
)
c.expect(f"ERR.* /usr/local/bin/{BOUNCER} is already installed. Exiting")
@pytest.mark.dependency(depends=['test_install_no_crowdsec'])
def test_upgrade_no_crowdsec(project_repo, must_be_root):
os.remove(f'/usr/local/bin/{BOUNCER}')
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/upgrade.sh'],
cwd=project_repo
)
c.expect(f"{BOUNCER} upgraded successfully")
c.wait()
assert c.terminated
assert c.exitstatus == 0
assert os.path.exists(f'/usr/local/bin/{BOUNCER}')
assert os.stat(f'/usr/local/bin/{BOUNCER}').st_mode & 0o777 == 0o755
@pytest.mark.dependency(depends=['test_upgrade_no_crowdsec'])
def test_uninstall_no_crowdsec(project_repo, must_be_root):
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/uninstall.sh'],
cwd=project_repo
)
c.expect(f"{BOUNCER} has been successfully uninstalled")
c.wait()
assert c.terminated
assert c.exitstatus == 0
assert not os.path.exists(CONFIG)
assert not os.path.exists(f'/usr/local/bin/{BOUNCER}')

View File

@@ -0,0 +1,104 @@
import os
import pexpect
import re
import yaml
import pytest
from pytest_cs.lib import cscli, text
BOUNCER = "crowdsec-firewall-bouncer"
CONFIG = f"/etc/crowdsec/bouncers/{BOUNCER}.yaml"
@pytest.mark.dependency()
def test_install_crowdsec(project_repo, bouncer_binary, must_be_root):
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/install.sh'],
encoding='utf-8',
cwd=project_repo
)
c.expect(f"Installing {BOUNCER}")
c.expect("iptables found")
c.expect("nftables found")
c.expect(re.escape("Found nftables (default) and iptables, which firewall "
"do you want to use (nftables/iptables)"))
c.sendline('fntables')
c.expect("cscli found, generating bouncer api key.")
c.expect("API Key: (.*)")
api_key = text.nocolor(c.match.group(1).strip())
# XXX: what do we expect here ?
c.wait()
assert c.terminated
# XXX: partial configuration, the service won't start
# assert c.exitstatus == 0
# installed files
assert os.path.exists(CONFIG)
assert os.stat(CONFIG).st_mode & 0o777 == 0o600
assert os.path.exists(f'/usr/local/bin/{BOUNCER}')
assert os.stat(f'/usr/local/bin/{BOUNCER}').st_mode & 0o777 == 0o755
# configuration check
with open(CONFIG) as f:
y = yaml.safe_load(f)
assert y['api_key'] == api_key
# the bouncer is registered
with open(f"{CONFIG}.id") as f:
bouncer_name = f.read().strip()
assert len(list(cscli.get_bouncers(name=bouncer_name))) == 1
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/install.sh'],
encoding='utf-8',
cwd=project_repo
)
c.expect(f"ERR:.* /usr/local/bin/{BOUNCER} is already installed. Exiting")
@pytest.mark.dependency(depends=['test_install_crowdsec'])
def test_upgrade_crowdsec(project_repo, must_be_root):
os.remove(f'/usr/local/bin/{BOUNCER}')
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/upgrade.sh'],
encoding='utf-8',
cwd=project_repo
)
c.expect(f"{BOUNCER} upgraded successfully")
c.wait()
assert c.terminated
assert c.exitstatus == 0
assert os.path.exists(f'/usr/local/bin/{BOUNCER}')
assert os.stat(f'/usr/local/bin/{BOUNCER}').st_mode & 0o777 == 0o755
@pytest.mark.dependency(depends=['test_upgrade_crowdsec'])
def test_uninstall_crowdsec(project_repo, must_be_root):
# the bouncer is registered
with open(f"{CONFIG}.id") as f:
bouncer_name = f.read().strip()
c = pexpect.spawn(
'/usr/bin/sh', ['scripts/uninstall.sh'],
encoding='utf-8',
cwd=project_repo
)
c.expect(f"{BOUNCER} has been successfully uninstalled")
c.wait()
assert c.terminated
assert c.exitstatus == 0
# installed files
assert not os.path.exists(CONFIG)
assert not os.path.exists(f'/usr/local/bin/{BOUNCER}')
# the bouncer is unregistered
assert len(list(cscli.get_bouncers(name=bouncer_name))) == 0

View File

@@ -0,0 +1,10 @@
import pytest
pytestmark = pytest.mark.deb
# This test has the side effect of building the package and leaving it in the
# project's parent directory.
def test_deb_build(deb_package, skip_unless_deb):
"""Test that the package can be built."""
assert deb_package.exists(), f'Package {deb_package} not found'

View File

@@ -0,0 +1,8 @@
import pytest
pytestmark = pytest.mark.rpm
def test_rpm_build(rpm_package_path, skip_unless_rpm):
"""Test that the package can be built."""
assert rpm_package_path.exists(), f'Package {rpm_package_path} not found'

View File

@@ -0,0 +1,19 @@
import os
import subprocess
def test_scripts_nonroot(project_repo, bouncer_binary, must_be_nonroot):
assert os.geteuid() != 0, "This test must be run as non-root"
for script in ['install.sh', 'upgrade.sh', 'uninstall.sh']:
c = subprocess.run(
['/usr/bin/sh', f'scripts/{script}'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=project_repo,
encoding='utf-8',
)
assert c.returncode == 1
assert c.stdout == ''
assert 'This script must be run as root' in c.stderr