diff --git a/.github/workflows/build-binary-package.yml b/.github/workflows/build-binary-package.yml index 87add9d8..c8effc91 100644 --- a/.github/workflows/build-binary-package.yml +++ b/.github/workflows/build-binary-package.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version-file: go.mod - name: Build all platforms run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fbd6e86b..eb178b3e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,7 +25,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version-file: go.mod - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d9ca128a..dcfa8c3b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ permissions: jobs: build: name: "Build + tests" - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: @@ -24,7 +24,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version-file: go.mod - name: Build run: | @@ -38,19 +38,21 @@ jobs: env: RICHGO_FORCE_COLOR: 1 - - name: "Setup Python" + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.5.24 + enable-cache: true + cache-dependency-glob: "test/uv.lock" + + - name: "Set up Python" uses: actions/setup-python@v5 with: - python-version: "3.12" - cache: 'pipenv' + python-version-file: "test/.python-version" - - name: "Install python dependencies" - run: | - python -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 + - name: Install the project + working-directory: ./test + run: uv sync --all-extras --dev - name: Install functional test dependencies run: | @@ -65,15 +67,16 @@ jobs: CROWDSEC_TEST_NETWORK: net-test CROWDSEC_TEST_TIMEOUT: 60 PYTEST_ADDOPTS: --durations=0 -vv --color=yes -m "not (deb or rpm)" + working-directory: ./test run: | # everything except for # - install (requires root, ignored by default) # - backends (requires root, ignored by default) # - deb/rpm (on their own workflows) - pipenv run pytest + uv run pytest # these need root - sudo -E pipenv run pytest ./test/backends - sudo -E pipenv run pytest ./test/install/no_crowdsec + sudo -E $(which uv) run pytest ./tests/backends + sudo -E $(which uv) run pytest ./tests/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' @@ -81,4 +84,11 @@ jobs: docker exec crowdsec cscli "$@" EOT sleep 5 - sudo -E pipenv run pytest ./test/install/with_crowdsec + sudo -E $(which uv) run pytest ./tests/install/with_crowdsec + + - name: Lint + working-directory: ./test + run: | + uv run ruff check + uv run basedpyright + diff --git a/.github/workflows/tests_deb.yml b/.github/workflows/tests_deb.yml index 4fd1807c..790dcb5d 100644 --- a/.github/workflows/tests_deb.yml +++ b/.github/workflows/tests_deb.yml @@ -12,7 +12,7 @@ permissions: jobs: build: name: "Test .deb packages" - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: @@ -24,26 +24,28 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version-file: go.mod - - name: "Setup Python" + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.5.24 + enable-cache: true + cache-dependency-glob: "test/uv.lock" + + - name: "Set up Python" uses: actions/setup-python@v5 with: - python-version: "3.12" - cache: 'pipenv' + python-version-file: "test/.python-version" - - name: "Install python dependencies" - run: | - python -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 + - name: Install the project + run: uv sync --all-extras --dev + working-directory: ./test - name: Install functional test dependencies run: | sudo apt update - sudo apt install -y build-essential debhelper devscripts fakeroot lintian + sudo apt install -y nftables iptables ipset build-essential debhelper devscripts fakeroot lintian docker network create net-test - name: Run functional tests @@ -53,7 +55,7 @@ jobs: CROWDSEC_TEST_NETWORK: net-test CROWDSEC_TEST_TIMEOUT: 60 PYTEST_ADDOPTS: --durations=0 -vv --color=yes + working-directory: ./test 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 + uv run pytest ./tests/pkg/test_build_deb.py + sudo -E $(which uv) run pytest -m deb ./tests/install/no_crowdsec diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 2ab33c9a..00000000 --- a/Pipfile +++ /dev/null @@ -1,17 +0,0 @@ -[packages] -exceptiongroup = "1.1.1" -pexpect = "4.8.0" -pytest-cs = {ref = "0.7.18", git = "https://github.com/crowdsecurity/pytest-cs.git"} -pytest-dependency = "0.5.1" -pytest-dotenv = "0.5.2" -flask = "2.2.3" -pytimeparse = "1.1.8" -psutil = "5.9.5" -zxcvbn = "4.4.28" - -[dev-packages] -gnureadline = "8.1.2" -ipdb = "0.13.13" - -[requires] -python_version = "*" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index e9401afc..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,739 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "e6979d7a1cdcf9ab4874e2049b8a8af3ce99eb706f81334621a2eadd542e4baf" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "*" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "blinker": { - "hashes": [ - "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", - "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182" - ], - "markers": "python_version >= '3.8'", - "version": "==1.7.0" - }, - "certifi": { - "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" - ], - "markers": "python_version >= '3.6'", - "version": "==2024.2.2" - }, - "cffi": { - "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" - ], - "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.16.0" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "click": { - "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" - }, - "cryptography": { - "hashes": [ - "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380", - "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589", - "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea", - "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65", - "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a", - "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3", - "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008", - "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1", - "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2", - "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635", - "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2", - "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90", - "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee", - "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a", - "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242", - "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12", - "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2", - "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d", - "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be", - "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee", - "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6", - "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529", - "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929", - "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1", - "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6", - "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a", - "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446", - "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9", - "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888", - "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4", - "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33", - "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f" - ], - "markers": "python_version >= '3.7'", - "version": "==42.0.2" - }, - "docker": { - "hashes": [ - "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b", - "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3" - ], - "markers": "python_version >= '3.8'", - "version": "==7.0.0" - }, - "exceptiongroup": { - "hashes": [ - "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", - "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68" - ], - "index": "pypi", - "version": "==1.2.0" - }, - "flask": { - "hashes": [ - "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e", - "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d" - ], - "index": "pypi", - "version": "==3.0.2" - }, - "idna": { - "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" - ], - "markers": "python_version >= '3.5'", - "version": "==3.6" - }, - "iniconfig": { - "hashes": [ - "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "itsdangerous": { - "hashes": [ - "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" - }, - "jinja2": { - "hashes": [ - "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", - "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.3" - }, - "markupsafe": { - "hashes": [ - "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", - "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", - "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", - "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", - "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", - "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", - "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", - "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", - "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", - "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", - "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", - "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", - "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", - "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", - "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", - "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", - "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", - "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", - "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", - "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", - "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", - "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", - "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", - "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", - "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", - "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", - "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", - "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", - "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", - "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", - "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", - "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", - "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", - "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", - "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", - "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", - "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", - "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", - "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", - "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", - "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", - "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", - "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", - "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", - "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", - "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", - "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", - "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", - "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", - "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", - "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", - "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", - "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", - "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", - "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", - "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", - "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", - "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", - "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", - "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.5" - }, - "packaging": { - "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" - ], - "markers": "python_version >= '3.7'", - "version": "==23.2" - }, - "pexpect": { - "hashes": [ - "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", - "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" - ], - "index": "pypi", - "version": "==4.9.0" - }, - "pluggy": { - "hashes": [ - "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", - "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" - ], - "markers": "python_version >= '3.8'", - "version": "==1.4.0" - }, - "psutil": { - "hashes": [ - "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d", - "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73", - "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8", - "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2", - "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e", - "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36", - "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7", - "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c", - "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee", - "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421", - "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf", - "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81", - "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0", - "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631", - "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4", - "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8" - ], - "index": "pypi", - "version": "==5.9.8" - }, - "ptyprocess": { - "hashes": [ - "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", - "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" - ], - "version": "==0.7.0" - }, - "pycparser": { - "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" - ], - "version": "==2.21" - }, - "pytest": { - "hashes": [ - "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c", - "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6" - ], - "markers": "python_version >= '3.8'", - "version": "==8.0.0" - }, - "pytest-cs": { - "git": "https://github.com/crowdsecurity/pytest-cs.git", - "ref": "df835beabc539be7f7f627b21caa0d6ad333daae" - }, - "pytest-datadir": { - "hashes": [ - "sha256:1617ed92f9afda0c877e4eac91904b5f779d24ba8f5e438752e3ae39d8d2ee3f", - "sha256:34adf361bcc7b37961bbc1dfa8d25a4829e778bab461703c38a5c50ca9c36dc8" - ], - "markers": "python_version >= '3.8'", - "version": "==1.5.0" - }, - "pytest-dependency": { - "hashes": [ - "sha256:934b0e6a39d95995062c193f7eaeed8a8ffa06ff1bcef4b62b0dc74a708bacc1" - ], - "index": "pypi", - "version": "==0.6.0" - }, - "pytest-dotenv": { - "hashes": [ - "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", - "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f" - ], - "index": "pypi", - "version": "==0.5.2" - }, - "python-dotenv": { - "hashes": [ - "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", - "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" - ], - "markers": "python_version >= '3.8'", - "version": "==1.0.1" - }, - "pytimeparse": { - "hashes": [ - "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", - "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a" - ], - "index": "pypi", - "version": "==1.1.8" - }, - "pyyaml": { - "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" - ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "setuptools": { - "hashes": [ - "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401", - "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6" - ], - "markers": "python_version >= '3.8'", - "version": "==69.1.0" - }, - "trustme": { - "hashes": [ - "sha256:5375ad7fb427074bec956592e0d4ee2a4cf4da68934e1ba4bcf4217126bc45e6", - "sha256:ce105b68fb9f6d7ac7a9ee6e95bb2347a22ce4d3be78ef9a6494d5ef890e1e16" - ], - "markers": "python_version >= '3.8'", - "version": "==1.1.0" - }, - "urllib3": { - "hashes": [ - "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20", - "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224" - ], - "markers": "python_version >= '3.8'", - "version": "==2.2.0" - }, - "werkzeug": { - "hashes": [ - "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc", - "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10" - ], - "markers": "python_version >= '3.8'", - "version": "==3.0.1" - }, - "zxcvbn": { - "hashes": [ - "sha256:151bd816817e645e9064c354b13544f85137ea3320ca3be1fb6873ea75ef7dc1" - ], - "index": "pypi", - "version": "==4.4.28" - } - }, - "develop": { - "asttokens": { - "hashes": [ - "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", - "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0" - ], - "version": "==2.4.1" - }, - "decorator": { - "hashes": [ - "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", - "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" - ], - "markers": "python_version >= '3.11'", - "version": "==5.1.1" - }, - "executing": { - "hashes": [ - "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147", - "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc" - ], - "markers": "python_version >= '3.5'", - "version": "==2.0.1" - }, - "gnureadline": { - "hashes": [ - "sha256:17a651e0c49d4b44e8ccf8992edc5a544e33ed9695d3b940ef002858c2215744", - "sha256:194bafa818d0fc3d46f8d71a8811a297a493c1264d3e2d0a71b1b1ff05f8fc15", - "sha256:1e3a8aaf1d61d351c16ad2d3425caf5768603ff5d0e86ba61da9b8756bdd1b95", - "sha256:264f22e865975a3c2ac1183f431dddd8ff7de5a645b89a801c6a276d800f49f3", - "sha256:2753aa1e46b4260b38da424c6a7da7a3ddac161a0b4e6fb71c1093e9ef3d2e73", - "sha256:2816bac8be6bc0e3aa2301acac76e308137eeef1b618c9e0c95c1f89a139a4d8", - "sha256:2ce5c49ecc54e1df0193e90422806a5940f908553206689aeaa04bc959d3aa9a", - "sha256:33ea248385e0d87a3fada38c9164a5756861aa59d6ee010c8be30eeb41f41b49", - "sha256:3903cba2987d42340f1d85c38d3780e954c95e64bfe1839002c7818aa63f8ac3", - "sha256:4262a6aa356ab22ef642f43a7f94eb42a72d6f0c532edb4e8c6b933f573056d2", - "sha256:49df5a432e4ff39cee1b0632c6d0e5fb304757113e502d70b50e33d9ffa47372", - "sha256:4ad9b10409d969ba42acbf89e58352cf3043a5155c2ee677d061e292336b5479", - "sha256:5e1e2d34b0c4ad81c7b00019fafa6de2faf6969c55fa58229e26267cae34047e", - "sha256:5fde3e6417d9004381e8e9835e0a89d81d2d77eeace9364d2e3d9fb64054d449", - "sha256:72da8bac1eb24b6c8237a33d7019a3f004a3d5ba867337175ed764831d9a2c99", - "sha256:74f2538ac15ff4ef9534823abdef077bb34c7dd343e204a36d978f09e168462f", - "sha256:861936c9b362d96152af2d73ccb6f3e901e70f0e4a2e7e62f4e226e91d349edb", - "sha256:8c4690d6c89dbead0958b19263ae67ef995e6109d6bc880cb0e40720cb1ba301", - "sha256:aa29a18594277ea691f92b0c6627d594c0f3387a6685e2e42038ab3f718c794e", - "sha256:b422ff3a78e281ee2e19b0eff70efa48396284bbefa86b83438d668ea9d038a3", - "sha256:c1bcb32e3b63442570d6425055aa6d5c3b6e8b09b9c7d1f8333e70203166a5a3", - "sha256:c402bc6e107beb015ae18c3d2e11f28375f049e464423ead88b35affe80f9be0", - "sha256:c7971653083a48049abd52baa9c8c0188aee362e7b2dd236fe51ecd4e6bc9bbe", - "sha256:de3d8ea66f1b5d00ed843b8925fc07476b8c838c38e584af8639c6a976a43d08", - "sha256:deb921c2cbc14671bb81f3f33d9363a9d0720203b5d716baee32e51c399e914b", - "sha256:e84e903de1514043e6a22866a1973c2ad5f5717f78e9d54e4d6809c48fbd3d81", - "sha256:ecdc4368bd2f7ae9a22de31b024455222082cb49b98ee69ffd0a59734bf648e1" - ], - "index": "pypi", - "version": "==8.1.2" - }, - "ipdb": { - "hashes": [ - "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", - "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726" - ], - "index": "pypi", - "version": "==0.13.13" - }, - "ipython": { - "hashes": [ - "sha256:1050a3ab8473488d7eee163796b02e511d0735cf43a04ba2a8348bd0f2eaf8a5", - "sha256:48fbc236fbe0e138b88773fa0437751f14c3645fb483f1d4c5dee58b37e5ce73" - ], - "markers": "python_version >= '3.11'", - "version": "==8.21.0" - }, - "jedi": { - "hashes": [ - "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", - "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0" - ], - "markers": "python_version >= '3.6'", - "version": "==0.19.1" - }, - "matplotlib-inline": { - "hashes": [ - "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", - "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" - ], - "markers": "python_version >= '3.5'", - "version": "==0.1.6" - }, - "parso": { - "hashes": [ - "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", - "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" - ], - "markers": "python_version >= '3.6'", - "version": "==0.8.3" - }, - "pexpect": { - "hashes": [ - "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", - "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" - ], - "index": "pypi", - "version": "==4.9.0" - }, - "prompt-toolkit": { - "hashes": [ - "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d", - "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.0.43" - }, - "ptyprocess": { - "hashes": [ - "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", - "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" - ], - "version": "==0.7.0" - }, - "pure-eval": { - "hashes": [ - "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", - "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" - ], - "version": "==0.2.2" - }, - "pygments": { - "hashes": [ - "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", - "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" - ], - "markers": "python_version >= '3.7'", - "version": "==2.17.2" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "stack-data": { - "hashes": [ - "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", - "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" - ], - "version": "==0.6.3" - }, - "traitlets": { - "hashes": [ - "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74", - "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e" - ], - "markers": "python_version >= '3.8'", - "version": "==5.14.1" - }, - "wcwidth": { - "hashes": [ - "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", - "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" - ], - "version": "==0.2.13" - } - } -} diff --git a/test/.python-version b/test/.python-version new file mode 100644 index 00000000..e4fba218 --- /dev/null +++ b/test/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..e69de29b diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 85c7e88c..00000000 --- a/test/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# required to avoid import of module test from /usr/lib/.. diff --git a/test/bouncer/test_firewall_bouncer.py b/test/bouncer/test_firewall_bouncer.py deleted file mode 100644 index 4e7fbe98..00000000 --- a/test/bouncer/test_firewall_bouncer.py +++ /dev/null @@ -1,106 +0,0 @@ - -import json - - -def test_backend_mode(bouncer, fw_cfg_factory): - cfg = fw_cfg_factory() - - del cfg['mode'] - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*unable to load configuration: config does not contain 'mode'*", - ]) - fw.proc.wait(timeout=0.2) - assert not fw.proc.is_running() - - cfg['mode'] = 'whatever' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*firewall 'whatever' is not supported*", - ]) - fw.proc.wait(timeout=0.2) - assert not fw.proc.is_running() - - cfg['mode'] = 'dry-run' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*Starting crowdsec-firewall-bouncer*", - "*backend type: dry-run*", - "*backend.Init() called*", - "*unable to configure bouncer: config does not contain LAPI url*", - ]) - fw.proc.wait(timeout=0.2) - assert not fw.proc.is_running() - - -def test_api_url(crowdsec, bouncer, fw_cfg_factory): - cfg = fw_cfg_factory() - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*unable to configure bouncer: config does not contain LAPI url*", - ]) - fw.proc.wait() - assert not fw.proc.is_running() - - cfg['api_url'] = '' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*unable to configure bouncer: config does not contain LAPI url*", - ]) - fw.proc.wait() - assert not fw.proc.is_running() - - -def test_api_key(crowdsec, bouncer, fw_cfg_factory, api_key_factory, bouncer_under_test): - api_key = api_key_factory() - env = { - 'BOUNCER_KEY_bouncer': api_key - } - - with crowdsec(environment=env) as lapi: - lapi.wait_for_http(8080, '/health') - port = lapi.probe.get_bound_port('8080') - - cfg = fw_cfg_factory() - cfg['api_url'] = f'http://localhost:{port}' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*unable to configure bouncer: config does not contain LAPI key or certificate*", - ]) - fw.proc.wait() - assert not fw.proc.is_running() - - cfg['api_key'] = 'badkey' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*Using API key auth*", - "*API error: access forbidden*", - "*process terminated with error: bouncer stream halted*", - ]) - fw.proc.wait() - assert not fw.proc.is_running() - - cfg['api_key'] = api_key - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*Using API key auth*", - "*Processing new and deleted decisions*", - ]) - assert fw.proc.is_running() - - # check that the bouncer is registered - res = lapi.cont.exec_run('cscli bouncers list -o json') - assert res.exit_code == 0 - bouncers = json.loads(res.output) - assert len(bouncers) == 1 - assert bouncers[0]['name'] == 'bouncer' - assert bouncers[0]['auth_type'] == 'api-key' - assert bouncers[0]['type'] == bouncer_under_test diff --git a/test/bouncer/test_iptables_deny_action.py b/test/bouncer/test_iptables_deny_action.py deleted file mode 100644 index c905ebbb..00000000 --- a/test/bouncer/test_iptables_deny_action.py +++ /dev/null @@ -1,49 +0,0 @@ - -def test_iptables_deny_action(bouncer, fw_cfg_factory): - cfg = fw_cfg_factory() - - cfg['log_level'] = 'trace' - cfg['mode'] = 'iptables' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*using 'DROP' as deny_action*", - ]) - fw.proc.wait(timeout=5) - assert not fw.proc.is_running() - - cfg['deny_action'] = 'drop' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*using 'DROP' as deny_action*", - ]) - fw.proc.wait(timeout=5) - assert not fw.proc.is_running() - - cfg['deny_action'] = 'reject' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*using 'REJECT' as deny_action*", - ]) - fw.proc.wait(timeout=5) - assert not fw.proc.is_running() - - cfg['deny_action'] = 'tarpit' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*using 'TARPIT' as deny_action*", - ]) - fw.proc.wait(timeout=5) - assert not fw.proc.is_running() - - cfg['deny_action'] = 'somethingelse' - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*invalid deny_action 'somethingelse', must be one of DROP, REJECT, TARPIT*", - ]) - fw.proc.wait(timeout=5) - assert not fw.proc.is_running() diff --git a/test/bouncer/test_tls.py b/test/bouncer/test_tls.py deleted file mode 100644 index 8f459ca6..00000000 --- a/test/bouncer/test_tls.py +++ /dev/null @@ -1,153 +0,0 @@ -import json - - -def test_tls_server(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory): - """TLS with server-only certificate""" - - api_key = api_key_factory() - - lapi_env = { - 'CACERT_FILE': '/etc/ssl/crowdsec/ca.crt', - 'LAPI_CERT_FILE': '/etc/ssl/crowdsec/lapi.crt', - 'LAPI_KEY_FILE': '/etc/ssl/crowdsec/lapi.key', - 'USE_TLS': 'true', - 'LOCAL_API_URL': 'https://localhost:8080', - 'BOUNCER_KEY_bouncer': api_key, - } - - certs = certs_dir(lapi_hostname='lapi') - - volumes = { - certs: {'bind': '/etc/ssl/crowdsec', 'mode': 'ro'}, - } - - with crowdsec(environment=lapi_env, volumes=volumes) as cs: - cs.wait_for_log("*CrowdSec Local API listening*") - # TODO: wait_for_https - cs.wait_for_http(8080, '/health', want_status=None) - - port = cs.probe.get_bound_port('8080') - cfg = fw_cfg_factory() - cfg['api_url'] = f'https://localhost:{port}' - cfg['api_key'] = api_key - - with bouncer(cfg) as cb: - cb.wait_for_lines_fnmatch([ - "*backend type: dry-run*", - "*Using API key auth*", - "*auth-api: auth with api key failed*", - "*tls: failed to verify certificate: x509: certificate signed by unknown authority*", - ]) - - cfg['ca_cert_path'] = (certs / 'ca.crt').as_posix() - - with bouncer(cfg) as cb: - cb.wait_for_lines_fnmatch([ - "*backend type: dry-run*", - "*Using CA cert *ca.crt*", - "*Using API key auth*", - "*Processing new and deleted decisions*", - ]) - - -def test_tls_mutual(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory, bouncer_under_test): - """TLS with two-way bouncer/lapi authentication""" - - lapi_env = { - 'CACERT_FILE': '/etc/ssl/crowdsec/ca.crt', - 'LAPI_CERT_FILE': '/etc/ssl/crowdsec/lapi.crt', - 'LAPI_KEY_FILE': '/etc/ssl/crowdsec/lapi.key', - 'USE_TLS': 'true', - 'LOCAL_API_URL': 'https://localhost:8080', - } - - certs = certs_dir(lapi_hostname='lapi') - - volumes = { - certs: {'bind': '/etc/ssl/crowdsec', 'mode': 'ro'}, - } - - with crowdsec(environment=lapi_env, volumes=volumes) as cs: - cs.wait_for_log("*CrowdSec Local API listening*") - # TODO: wait_for_https - cs.wait_for_http(8080, '/health', want_status=None) - - port = cs.probe.get_bound_port('8080') - cfg = fw_cfg_factory() - cfg['api_url'] = f'https://localhost:{port}' - cfg['ca_cert_path'] = (certs / 'ca.crt').as_posix() - - cfg['cert_path'] = (certs / 'agent.crt').as_posix() - cfg['key_path'] = (certs / 'agent.key').as_posix() - - with bouncer(cfg) as cb: - cb.wait_for_lines_fnmatch([ - "*Starting crowdsec-firewall-bouncer*", - "*Using CA cert*", - "*Using cert auth with cert * and key *", - "*API error: access forbidden*", - ]) - - cs.wait_for_log("*client certificate OU ?agent-ou? doesn't match expected OU ?bouncer-ou?*") - - cfg['cert_path'] = (certs / 'bouncer.crt').as_posix() - cfg['key_path'] = (certs / 'bouncer.key').as_posix() - - with bouncer(cfg) as cb: - cb.wait_for_lines_fnmatch([ - "*backend type: dry-run*", - "*Using CA cert*", - "*Using cert auth with cert * and key *", - "*Processing new and deleted decisions . . .*", - ]) - - # check that the bouncer is registered - res = cs.cont.exec_run('cscli bouncers list -o json') - assert res.exit_code == 0 - bouncers = json.loads(res.output) - assert len(bouncers) == 1 - assert bouncers[0]['name'].startswith('@') - assert bouncers[0]['auth_type'] == 'tls' - assert bouncers[0]['type'] == bouncer_under_test - - -def test_api_key_and_cert(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory): - """Attempt to send an api key and a certificate too""" - - api_key = api_key_factory() - - lapi_env = { - 'CACERT_FILE': '/etc/ssl/crowdsec/ca.crt', - 'LAPI_CERT_FILE': '/etc/ssl/crowdsec/lapi.crt', - 'LAPI_KEY_FILE': '/etc/ssl/crowdsec/lapi.key', - 'USE_TLS': 'true', - 'LOCAL_API_URL': 'https://localhost:8080', - 'BOUNCER_KEY_bouncer': api_key, - } - - certs = certs_dir(lapi_hostname='lapi') - - volumes = { - certs: {'bind': '/etc/ssl/crowdsec', 'mode': 'ro'}, - } - - with crowdsec(environment=lapi_env, volumes=volumes) as cs: - cs.wait_for_log("*CrowdSec Local API listening*") - cs.wait_for_http(8080, '/health', want_status=None) - - port = cs.probe.get_bound_port('8080') - cfg = fw_cfg_factory() - cfg['api_url'] = f'https://localhost:{port}' - cfg['ca_cert_path'] = (certs / 'ca.crt').as_posix() - cfg['api_key'] = api_key - - cfg['cert_path'] = (certs / 'bouncer.crt').as_posix() - cfg['key_path'] = (certs / 'bouncer.key').as_posix() - - cs.wait_for_log("*Starting processing data*") - - with bouncer(cfg) as cb: - cb.wait_for_lines_fnmatch([ - "*Starting crowdsec-firewall-bouncer*", - "*unable to configure bouncer: api client init: cannot use both API key and certificate auth*", - ]) diff --git a/test/bouncer/test_yaml_local.py b/test/bouncer/test_yaml_local.py deleted file mode 100644 index 3c28b667..00000000 --- a/test/bouncer/test_yaml_local.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - - -def test_yaml_local(bouncer, fw_cfg_factory): - cfg = fw_cfg_factory() - - cfg.pop('mode') - - with bouncer(cfg) as fw: - fw.wait_for_lines_fnmatch([ - "*unable to load configuration: config does not contain 'mode'*", - ]) - fw.proc.wait(timeout=0.2) - assert not fw.proc.is_running() - - config_local = { - 'mode': 'whatever' - } - - with bouncer(cfg, config_local=config_local) as fw: - fw.wait_for_lines_fnmatch([ - "*firewall 'whatever' is not supported*", - ]) - fw.proc.wait(timeout=0.2) - assert not fw.proc.is_running() - - # variable expansion - - config_local = { - 'mode': '$BOUNCER_MODE' - } - - os.environ['BOUNCER_MODE'] = 'fromenv' - - with bouncer(cfg, config_local=config_local) as fw: - fw.wait_for_lines_fnmatch([ - "*firewall 'fromenv' is not supported*", - ]) - fw.proc.wait(timeout=0.2) - assert not fw.proc.is_running() diff --git a/default.env b/test/default.env similarity index 100% rename from default.env rename to test/default.env diff --git a/test/pyproject.toml b/test/pyproject.toml new file mode 100644 index 00000000..35aa8c0b --- /dev/null +++ b/test/pyproject.toml @@ -0,0 +1,107 @@ +[project] +name = "cs-firewall-bouncer-tests" +version = "0.1.0" +description = "Tests for cs-firewall-bouncer" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "flask>=3.1.0", + "pexpect>=4.9.0", + "psutil>=6.1.1", + "pytest>=8.3.4", + "pytest-cs", + "pytest-dependency>=0.6.0", + "pytest-dotenv>=0.5.2", + "pytimeparse>=1.1.8", + "zxcvbn>=4.4.28", +] + +[tool.uv.sources] +pytest-cs = { git = "https://github.com/crowdsecurity/pytest-cs" } + +[dependency-groups] +dev = [ + "basedpyright>=1.26.0", + "ipdb>=0.13.13", + "ruff>=0.9.4", +] + +#[tool.uv.sources] +#pytest-cs = { path = "../../../pytest-cs", editable = true } + + +[tool.ruff] + +line-length = 120 + +[tool.ruff.lint] +select = [ + "ALL" +] + +ignore = [ + "ANN", # Missing type annotations + "A002", # Function argument `id` is shadowing a Python builtin + "ARG001", # Unused function argument: `...` + "COM812", # Trailing comma missing + "D100", # Missing docstring in public module + "D101", # Missing docstring in public class + "D102", # Missing docstring in public method + "D103", # Missing docstring in public function + "D104", # Missing docstring in public package + "D107", # Missing docstring in __init__ + "D203", # incorrect-blank-line-before-class + "D212", # Multi-line docstring summary should start at the first line + "D212", # Multi-line docstring summary should start at the first line + "D400", # First line should end with a period + "D415", # First line should end with a period, question mark, or exclamation point + "DTZ005", # `datetime.datetime.now()` called without a `tz` argument + "EM102", # Exception must not use an f-string literal, assign to variable first + "ERA001", # Found commented-out code + "FBT002", # Boolean default positional argument in function definition + "FIX002", # Line contains TODO, consider resolving the issue + "FIX003", # Line contains XXX, consider resolving the issue + "N802", # Function name `testLogging` should be lowercase + "PLW1510", # `subprocess.run` without explicit `check` argument + "S101", # Use of 'assert' detected + "S104", # Possible binding to all interfaces + "S314", # Using `xml` to parse untrusted data is known to be vulnerable to XML attacks; use `defusedxml` equivalents + "S603", # `subprocess` call: check for execution of untrusted input + "S604", # Function call with `shell=True` parameter identified, security issue + "S607", # Starting a process with a partial executable path + "SIM108", # Use ternary operator `...` instead of `if`-`else`-block + "TD001", # Invalid TODO tag: `XXX` + "TD002", # Missing author in TODO + "TD003", # Missing issue link for this TODO + "TRY003", # Avoid specifying long messages outside the exception class + "PLR2004", # Magic value used in comparison, consider replacing `...` with a constant variable + "PLR0913", # Too many arguments in function definition (6 > 5) + "PTH107", # `os.remove()` should be replaced by `Path.unlink()` + "PTH108", # `os.unlink()` should be replaced by `Path.unlink()` + "PTH110", # `os.path.exists()` should be replaced by `Path.exists()` + "PTH116", # `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()` + "PTH120", # `os.path.dirname()` should be replaced by `Path.parent` + "PTH123", # `open()` should be replaced by `Path.open()` + "PT009", # Use a regular `assert` instead of unittest-style `assertEqual` + "PT022", # No teardown in fixture `fw_cfg_factory`, use `return` instead of `yield` + "TID252", # Prefer absolute imports over relative imports from parent modules + "UP022", # Prefer `capture_output` over sending `stdout` and `stderr` to `PIPE` +] + +[tool.basedpyright] +reportAny = "none" +reportArgumentType = "none" +reportAttributeAccessIssue = "none" +reportImplicitOverride = "none" +reportImplicitStringConcatenation = "none" +reportMissingParameterType = "none" +reportMissingTypeStubs = "none" +reportOptionalMemberAccess = "none" +reportUnannotatedClassAttribute = "none" +reportUninitializedInstanceVariable = "none" +reportUnknownArgumentType = "none" +reportUnknownMemberType = "none" +reportUnknownParameterType = "none" +reportUnknownVariableType = "none" +reportUnusedCallResult = "none" +reportUnusedParameter = "none" diff --git a/pytest.ini b/test/pytest.ini similarity index 83% rename from pytest.ini rename to test/pytest.ini index 276b17e4..44d80943 100644 --- a/pytest.ini +++ b/test/pytest.ini @@ -1,8 +1,8 @@ [pytest] addopts = --pdbcls=IPython.terminal.debugger:Pdb - --ignore=test/install - --ignore=test/backends + --ignore=tests/install + --ignore=tests/backends --strict-markers markers: deb: mark tests related to deb packaging diff --git a/test/tests/__init__.py b/test/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/tests/backends/__init__.py b/test/tests/backends/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/tests/backends/iptables/__init__.py b/test/tests/backends/iptables/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/backends/iptables/crowdsec-firewall-bouncer-logging.yaml b/test/tests/backends/iptables/crowdsec-firewall-bouncer-logging.yaml similarity index 100% rename from test/backends/iptables/crowdsec-firewall-bouncer-logging.yaml rename to test/tests/backends/iptables/crowdsec-firewall-bouncer-logging.yaml diff --git a/test/backends/iptables/crowdsec-firewall-bouncer.yaml b/test/tests/backends/iptables/crowdsec-firewall-bouncer.yaml similarity index 100% rename from test/backends/iptables/crowdsec-firewall-bouncer.yaml rename to test/tests/backends/iptables/crowdsec-firewall-bouncer.yaml diff --git a/test/backends/iptables/test_iptables.py b/test/tests/backends/iptables/test_iptables.py similarity index 86% rename from test/backends/iptables/test_iptables.py rename to test/tests/backends/iptables/test_iptables.py index 079ce8e0..b53c3a99 100644 --- a/test/backends/iptables/test_iptables.py +++ b/test/tests/backends/iptables/test_iptables.py @@ -6,12 +6,11 @@ from pathlib import Path from time import sleep -from test.backends.mock_lapi import MockLAPI -from test.backends.utils import generate_n_decisions, run_cmd, new_decision - +from ..mock_lapi import MockLAPI +from ..utils import generate_n_decisions, new_decision, run_cmd SCRIPT_DIR = Path(os.path.dirname(os.path.realpath(__file__))) -PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent +PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent.parent BINARY_PATH = PROJECT_ROOT.joinpath("crowdsec-firewall-bouncer") CONFIG_PATH = SCRIPT_DIR.joinpath("crowdsec-firewall-bouncer.yaml") CONFIG_PATH_LOGGING = SCRIPT_DIR.joinpath("crowdsec-firewall-bouncer-logging.yaml") @@ -23,6 +22,7 @@ LOGGING_CHAIN_NAME = "CROWDSEC_LOG" CHAIN_NAME = "INPUT" + class TestIPTables(unittest.TestCase): def setUp(self): self.fb = subprocess.Popen([BINARY_PATH, "-c", CONFIG_PATH]) @@ -136,11 +136,7 @@ def test_decision_insertion_deletion_ipv6(self): set_elements = set(map(ip_address, set_elements)) self.assertEqual(len(set_elements), total_decisions - duplicate_decisions - 1) self.assertEqual( - { - ip_address(i["value"]) - for i in decisions - if ip_address(i["value"]) != ip_address("::1:0:3") - }, + {ip_address(i["value"]) for i in decisions if ip_address(i["value"]) != ip_address("::1:0:3")}, set_elements, ) self.assertNotIn(ip_address("::1:0:3"), set_elements) @@ -192,40 +188,42 @@ def tearDown(self): self.lapi.stop() def testLogging(self): - #We use 1.1.1.1 because we want to see some dropped packets in the logs - #We know this IP responds to ping, and the response will be dropped by the firewall + # We use 1.1.1.1 because we want to see some dropped packets in the logs + # We know this IP responds to ping, and the response will be dropped by the firewall d = new_decision("1.1.1.1") self.lapi.ds.insert_decisions([d]) sleep(3) - - #Check if our logging chain is in place + + # Check if our logging chain is in place output = run_cmd("iptables", "-L", LOGGING_CHAIN_NAME) - rules = [line for line in output.split("\n") if 'anywhere' in line] + rules = [line for line in output.split("\n") if "anywhere" in line] - #2 rules: one logging, one generic drop + # 2 rules: one logging, one generic drop self.assertEqual(len(rules), 2) - #Check if the logging chain is called from the main chain + # Check if the logging chain is called from the main chain output = run_cmd("iptables", "-L", CHAIN_NAME) rules = [line for line in output.split("\n") if RULES_CHAIN_NAME in line] self.assertEqual(len(rules), 1) - #Check if logging/drop chain is called from the rules chain + # Check if logging/drop chain is called from the rules chain output = run_cmd("iptables", "-L", RULES_CHAIN_NAME) rules = [line for line in output.split("\n") if LOGGING_CHAIN_NAME in line] self.assertEqual(len(rules), 1) - #Now, try to ping the IP + # Now, try to ping the IP - output = run_cmd("curl", "--connect-timeout", "1", "1.1.1.1", ignore_error=True) #We don't care about the output, we just want to trigger the rule + output = run_cmd( + "curl", "--connect-timeout", "1", "1.1.1.1", ignore_error=True + ) # We don't care about the output, we just want to trigger the rule - #Check if the firewall has logged the dropped response + # Check if the firewall has logged the dropped response output = run_cmd("dmesg | tail -n 10", shell=True) - assert 'blocked by crowdsec' in output \ No newline at end of file + assert "blocked by crowdsec" in output diff --git a/test/backends/mock_lapi.py b/test/tests/backends/mock_lapi.py similarity index 98% rename from test/backends/mock_lapi.py rename to test/tests/backends/mock_lapi.py index d975dd38..687a44fb 100644 --- a/test/backends/mock_lapi.py +++ b/test/tests/backends/mock_lapi.py @@ -83,7 +83,7 @@ def decisions(self): api_key = request.headers.get("x-api-key") if not api_key: abort(404) - startup = True if request.args.get("startup") == "true" else False + startup = request.args.get("startup") == "true" active_decisions, expired_decisions = self.ds.get_decisions_for_bouncer(api_key, startup) return { "new": formatted_decisions(active_decisions), diff --git a/test/tests/backends/nftables/__init__.py b/test/tests/backends/nftables/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/backends/nftables/crowdsec-firewall-bouncer.yaml b/test/tests/backends/nftables/crowdsec-firewall-bouncer.yaml similarity index 100% rename from test/backends/nftables/crowdsec-firewall-bouncer.yaml rename to test/tests/backends/nftables/crowdsec-firewall-bouncer.yaml diff --git a/test/backends/nftables/test_nftables.py b/test/tests/backends/nftables/test_nftables.py similarity index 84% rename from test/backends/nftables/test_nftables.py rename to test/tests/backends/nftables/test_nftables.py index b7f00f7f..ff99fd45 100644 --- a/test/backends/nftables/test_nftables.py +++ b/test/tests/backends/nftables/test_nftables.py @@ -6,12 +6,11 @@ from pathlib import Path from time import sleep -from test.backends.mock_lapi import MockLAPI -from test.backends.utils import generate_n_decisions, run_cmd - +from ..mock_lapi import MockLAPI +from ..utils import generate_n_decisions, run_cmd SCRIPT_DIR = Path(os.path.dirname(os.path.realpath(__file__))) -PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent +PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent.parent BINARY_PATH = PROJECT_ROOT.joinpath("crowdsec-firewall-bouncer") CONFIG_PATH = SCRIPT_DIR.joinpath("crowdsec-firewall-bouncer.yaml") @@ -36,11 +35,7 @@ def test_table_rule_set_are_created(self): self.lapi.ds.insert_decisions(d1 + d2) sleep(1) output = json.loads(run_cmd("nft", "-j", "list", "tables")) - tables = { - (node["table"]["family"], node["table"]["name"]) - for node in output["nftables"] - if "table" in node - } + tables = {(node["table"]["family"], node["table"]["name"]) for node in output["nftables"] if "table" in node} assert ("ip6", "crowdsec6") in tables assert ("ip", "crowdsec") in tables @@ -52,9 +47,7 @@ def test_table_rule_set_are_created(self): if "set" in node } assert ("ip", "crowdsec-blacklists-script", "ipv4_addr") in sets - rules = { - node["rule"]["chain"] for node in output["nftables"] if "rule" in node - } # maybe stricter check ? + rules = {node["rule"]["chain"] for node in output["nftables"] if "rule" in node} # maybe stricter check ? assert "crowdsec-chain-forward" in rules assert "crowdsec-chain-input" in rules @@ -67,9 +60,7 @@ def test_table_rule_set_are_created(self): } assert ("ip6", "crowdsec6-blacklists-script", "ipv6_addr") in sets - rules = { - node["rule"]["chain"] for node in output["nftables"] if "rule" in node - } # maybe stricter check ? + rules = {node["rule"]["chain"] for node in output["nftables"] if "rule" in node} # maybe stricter check ? assert "crowdsec6-chain-input" in rules assert "crowdsec6-chain-forward" in rules @@ -77,13 +68,17 @@ def test_duplicate_decisions_across_decision_stream(self): d1, d2, d3 = generate_n_decisions(3, dup_count=1) self.lapi.ds.insert_decisions([d1]) sleep(1) - self.assertEqual(get_set_elements("ip", "crowdsec", "crowdsec-blacklists-script"), {"0.0.0.0"}) + self.assertEqual( + get_set_elements("ip", "crowdsec", "crowdsec-blacklists-script"), + {"0.0.0.0"}, + ) self.lapi.ds.insert_decisions([d2, d3]) sleep(1) assert self.fb.poll() is None self.assertEqual( - get_set_elements("ip", "crowdsec", "crowdsec-blacklists-script"), {"0.0.0.0", "0.0.0.1"} + get_set_elements("ip", "crowdsec", "crowdsec-blacklists-script"), + {"0.0.0.0", "0.0.0.1"}, ) self.lapi.ds.delete_decision_by_id(d1["id"]) @@ -135,11 +130,7 @@ def test_decision_insertion_deletion_ipv6(self): set_elements = set(map(ip_address, set_elements)) self.assertEqual(len(set_elements), total_decisions - duplicate_decisions - 1) assert ( - { - ip_address(i["value"]) - for i in decisions - if ip_address(i["value"]) != ip_address("::1:0:3") - } + {ip_address(i["value"]) for i in decisions if ip_address(i["value"]) != ip_address("::1:0:3")} ) == set_elements assert ip_address("::1:0:3") not in set_elements @@ -171,8 +162,8 @@ def get_set_elements(family, table_name, set_name, with_timeout=False): continue if not isinstance(node["set"]["elem"][0], dict): return set(node["set"]["elem"]) - else: - if not with_timeout: - return {elem["elem"]["val"] for elem in node["set"]["elem"]} - return {(elem["elem"]["val"], elem["elem"]["timeout"]) for elem in node["set"]["elem"]} + + if not with_timeout: + return {elem["elem"]["val"] for elem in node["set"]["elem"]} + return {(elem["elem"]["val"], elem["elem"]["timeout"]) for elem in node["set"]["elem"]} return set() diff --git a/test/backends/utils.py b/test/tests/backends/utils.py similarity index 96% rename from test/backends/utils.py rename to test/tests/backends/utils.py index 5335f9c5..153c8c25 100644 --- a/test/backends/utils.py +++ b/test/tests/backends/utils.py @@ -20,7 +20,7 @@ def generate_n_decisions(n: int, action="ban", dup_count=0, ipv4=True, duration= if ipv4: ip = ip_address(i) else: - ip = ip_address(2 ** 32 + i) + ip = ip_address(2**32 + i) decisions.append( { "value": ip.__str__(), @@ -35,6 +35,7 @@ def generate_n_decisions(n: int, action="ban", dup_count=0, ipv4=True, duration= decisions *= n // unique_decision_count return decisions + def new_decision(ip: str): return { "value": ip, @@ -43,4 +44,4 @@ def new_decision(ip: str): "origin": "script", "duration": "4h", "reason": "for testing", - } \ No newline at end of file + } diff --git a/test/tests/bouncer/__init__.py b/test/tests/bouncer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/tests/bouncer/test_firewall_bouncer.py b/test/tests/bouncer/test_firewall_bouncer.py new file mode 100644 index 00000000..0ab91fb5 --- /dev/null +++ b/test/tests/bouncer/test_firewall_bouncer.py @@ -0,0 +1,119 @@ +import json + + +def test_backend_mode(bouncer, fw_cfg_factory): + cfg = fw_cfg_factory() + + del cfg["mode"] + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*unable to load configuration: config does not contain 'mode'*", + ] + ) + fw.proc.wait(timeout=0.2) + assert not fw.proc.is_running() + + cfg["mode"] = "whatever" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*firewall 'whatever' is not supported*", + ] + ) + fw.proc.wait(timeout=0.2) + assert not fw.proc.is_running() + + cfg["mode"] = "dry-run" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*Starting crowdsec-firewall-bouncer*", + "*backend type: dry-run*", + "*backend.Init() called*", + "*unable to configure bouncer: config does not contain LAPI url*", + ] + ) + fw.proc.wait(timeout=0.2) + assert not fw.proc.is_running() + + +def test_api_url(crowdsec, bouncer, fw_cfg_factory): + cfg = fw_cfg_factory() + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*unable to configure bouncer: config does not contain LAPI url*", + ] + ) + fw.proc.wait() + assert not fw.proc.is_running() + + cfg["api_url"] = "" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*unable to configure bouncer: config does not contain LAPI url*", + ] + ) + fw.proc.wait() + assert not fw.proc.is_running() + + +def test_api_key(crowdsec, bouncer, fw_cfg_factory, api_key_factory, bouncer_under_test): + api_key = api_key_factory() + env = {"BOUNCER_KEY_bouncer": api_key} + + with crowdsec(environment=env) as lapi: + lapi.wait_for_http(8080, "/health") + port = lapi.probe.get_bound_port("8080") + + cfg = fw_cfg_factory() + cfg["api_url"] = f"http://localhost:{port}" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*unable to configure bouncer: config does not contain LAPI key or certificate*", + ] + ) + fw.proc.wait() + assert not fw.proc.is_running() + + cfg["api_key"] = "badkey" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*Using API key auth*", + "*API error: access forbidden*", + "*process terminated with error: bouncer stream halted*", + ] + ) + fw.proc.wait() + assert not fw.proc.is_running() + + cfg["api_key"] = api_key + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*Using API key auth*", + "*Processing new and deleted decisions*", + ] + ) + assert fw.proc.is_running() + + # check that the bouncer is registered + res = lapi.cont.exec_run("cscli bouncers list -o json") + assert res.exit_code == 0 + bouncers = json.loads(res.output) + assert len(bouncers) == 1 + assert bouncers[0]["name"] == "bouncer" + assert bouncers[0]["auth_type"] == "api-key" + assert bouncers[0]["type"] == bouncer_under_test diff --git a/test/tests/bouncer/test_iptables_deny_action.py b/test/tests/bouncer/test_iptables_deny_action.py new file mode 100644 index 00000000..56944e2c --- /dev/null +++ b/test/tests/bouncer/test_iptables_deny_action.py @@ -0,0 +1,58 @@ +def test_iptables_deny_action(bouncer, fw_cfg_factory): + cfg = fw_cfg_factory() + + cfg["log_level"] = "trace" + cfg["mode"] = "iptables" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*using 'DROP' as deny_action*", + ] + ) + fw.proc.wait(timeout=5) + assert not fw.proc.is_running() + + cfg["deny_action"] = "drop" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*using 'DROP' as deny_action*", + ] + ) + fw.proc.wait(timeout=5) + assert not fw.proc.is_running() + + cfg["deny_action"] = "reject" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*using 'REJECT' as deny_action*", + ] + ) + fw.proc.wait(timeout=5) + assert not fw.proc.is_running() + + cfg["deny_action"] = "tarpit" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*using 'TARPIT' as deny_action*", + ] + ) + fw.proc.wait(timeout=5) + assert not fw.proc.is_running() + + cfg["deny_action"] = "somethingelse" + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*invalid deny_action 'somethingelse', must be one of DROP, REJECT, TARPIT*", + ] + ) + fw.proc.wait(timeout=5) + assert not fw.proc.is_running() diff --git a/test/tests/bouncer/test_tls.py b/test/tests/bouncer/test_tls.py new file mode 100644 index 00000000..5effe6aa --- /dev/null +++ b/test/tests/bouncer/test_tls.py @@ -0,0 +1,160 @@ +import json + + +def test_tls_server(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory): + """TLS with server-only certificate""" + api_key = api_key_factory() + + lapi_env = { + "CACERT_FILE": "/etc/ssl/crowdsec/ca.crt", + "LAPI_CERT_FILE": "/etc/ssl/crowdsec/lapi.crt", + "LAPI_KEY_FILE": "/etc/ssl/crowdsec/lapi.key", + "USE_TLS": "true", + "LOCAL_API_URL": "https://localhost:8080", + "BOUNCER_KEY_bouncer": api_key, + } + + certs = certs_dir(lapi_hostname="lapi") + + volumes = { + certs: {"bind": "/etc/ssl/crowdsec", "mode": "ro"}, + } + + with crowdsec(environment=lapi_env, volumes=volumes) as cs: + cs.wait_for_log("*CrowdSec Local API listening*") + # TODO: wait_for_https + cs.wait_for_http(8080, "/health", want_status=None) + + port = cs.probe.get_bound_port("8080") + cfg = fw_cfg_factory() + cfg["api_url"] = f"https://localhost:{port}" + cfg["api_key"] = api_key + + with bouncer(cfg) as cb: + cb.wait_for_lines_fnmatch( + [ + "*backend type: dry-run*", + "*Using API key auth*", + "*auth-api: auth with api key failed*", + "*tls: failed to verify certificate: x509: certificate signed by unknown authority*", + ] + ) + + cfg["ca_cert_path"] = (certs / "ca.crt").as_posix() + + with bouncer(cfg) as cb: + cb.wait_for_lines_fnmatch( + [ + "*backend type: dry-run*", + "*Using CA cert *ca.crt*", + "*Using API key auth*", + "*Processing new and deleted decisions*", + ] + ) + + +def test_tls_mutual(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory, bouncer_under_test): + """TLS with two-way bouncer/lapi authentication""" + lapi_env = { + "CACERT_FILE": "/etc/ssl/crowdsec/ca.crt", + "LAPI_CERT_FILE": "/etc/ssl/crowdsec/lapi.crt", + "LAPI_KEY_FILE": "/etc/ssl/crowdsec/lapi.key", + "USE_TLS": "true", + "LOCAL_API_URL": "https://localhost:8080", + } + + certs = certs_dir(lapi_hostname="lapi") + + volumes = { + certs: {"bind": "/etc/ssl/crowdsec", "mode": "ro"}, + } + + with crowdsec(environment=lapi_env, volumes=volumes) as cs: + cs.wait_for_log("*CrowdSec Local API listening*") + # TODO: wait_for_https + cs.wait_for_http(8080, "/health", want_status=None) + + port = cs.probe.get_bound_port("8080") + cfg = fw_cfg_factory() + cfg["api_url"] = f"https://localhost:{port}" + cfg["ca_cert_path"] = (certs / "ca.crt").as_posix() + + cfg["cert_path"] = (certs / "agent.crt").as_posix() + cfg["key_path"] = (certs / "agent.key").as_posix() + + with bouncer(cfg) as cb: + cb.wait_for_lines_fnmatch( + [ + "*Starting crowdsec-firewall-bouncer*", + "*Using CA cert*", + "*Using cert auth with cert * and key *", + "*API error: access forbidden*", + ] + ) + + cs.wait_for_log("*client certificate OU ?agent-ou? doesn't match expected OU ?bouncer-ou?*") + + cfg["cert_path"] = (certs / "bouncer.crt").as_posix() + cfg["key_path"] = (certs / "bouncer.key").as_posix() + + with bouncer(cfg) as cb: + cb.wait_for_lines_fnmatch( + [ + "*backend type: dry-run*", + "*Using CA cert*", + "*Using cert auth with cert * and key *", + "*Processing new and deleted decisions . . .*", + ] + ) + + # check that the bouncer is registered + res = cs.cont.exec_run("cscli bouncers list -o json") + assert res.exit_code == 0 + bouncers = json.loads(res.output) + assert len(bouncers) == 1 + assert bouncers[0]["name"].startswith("@") + assert bouncers[0]["auth_type"] == "tls" + assert bouncers[0]["type"] == bouncer_under_test + + +def test_api_key_and_cert(crowdsec, certs_dir, api_key_factory, bouncer, fw_cfg_factory): + """Attempt to send an api key and a certificate too""" + api_key = api_key_factory() + + lapi_env = { + "CACERT_FILE": "/etc/ssl/crowdsec/ca.crt", + "LAPI_CERT_FILE": "/etc/ssl/crowdsec/lapi.crt", + "LAPI_KEY_FILE": "/etc/ssl/crowdsec/lapi.key", + "USE_TLS": "true", + "LOCAL_API_URL": "https://localhost:8080", + "BOUNCER_KEY_bouncer": api_key, + } + + certs = certs_dir(lapi_hostname="lapi") + + volumes = { + certs: {"bind": "/etc/ssl/crowdsec", "mode": "ro"}, + } + + with crowdsec(environment=lapi_env, volumes=volumes) as cs: + cs.wait_for_log("*CrowdSec Local API listening*") + cs.wait_for_http(8080, "/health", want_status=None) + + port = cs.probe.get_bound_port("8080") + cfg = fw_cfg_factory() + cfg["api_url"] = f"https://localhost:{port}" + cfg["ca_cert_path"] = (certs / "ca.crt").as_posix() + cfg["api_key"] = api_key + + cfg["cert_path"] = (certs / "bouncer.crt").as_posix() + cfg["key_path"] = (certs / "bouncer.key").as_posix() + + cs.wait_for_log("*Starting processing data*") + + with bouncer(cfg) as cb: + cb.wait_for_lines_fnmatch( + [ + "*Starting crowdsec-firewall-bouncer*", + "*unable to configure bouncer: api client init: cannot use both API key and certificate auth*", + ] + ) diff --git a/test/tests/bouncer/test_yaml_local.py b/test/tests/bouncer/test_yaml_local.py new file mode 100644 index 00000000..02f9d877 --- /dev/null +++ b/test/tests/bouncer/test_yaml_local.py @@ -0,0 +1,42 @@ +import os + + +def test_yaml_local(bouncer, fw_cfg_factory): + cfg = fw_cfg_factory() + + cfg.pop("mode") + + with bouncer(cfg) as fw: + fw.wait_for_lines_fnmatch( + [ + "*unable to load configuration: config does not contain 'mode'*", + ] + ) + fw.proc.wait(timeout=0.2) + assert not fw.proc.is_running() + + config_local = {"mode": "whatever"} + + with bouncer(cfg, config_local=config_local) as fw: + fw.wait_for_lines_fnmatch( + [ + "*firewall 'whatever' is not supported*", + ] + ) + fw.proc.wait(timeout=0.2) + assert not fw.proc.is_running() + + # variable expansion + + config_local = {"mode": "$BOUNCER_MODE"} + + os.environ["BOUNCER_MODE"] = "fromenv" + + with bouncer(cfg, config_local=config_local) as fw: + fw.wait_for_lines_fnmatch( + [ + "*firewall 'fromenv' is not supported*", + ] + ) + fw.proc.wait(timeout=0.2) + assert not fw.proc.is_running() diff --git a/test/conftest.py b/test/tests/conftest.py similarity index 68% rename from test/conftest.py rename to test/tests/conftest.py index 991ec494..345fc3a8 100644 --- a/test/conftest.py +++ b/test/tests/conftest.py @@ -1,22 +1,21 @@ import contextlib import pytest - from pytest_cs import plugin -pytest_exception_interact = plugin.pytest_exception_interact +# pytest_exception_interact = plugin.pytest_exception_interact # provide the name of the bouncer binary to test -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def bouncer_under_test(): - return 'crowdsec-firewall-bouncer' + 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): +@pytest.fixture(scope="session") +def bouncer_with_lapi(bouncer, crowdsec, fw_cfg_factory, api_key_factory: plugin.ApiKeyFactoryType): @contextlib.contextmanager def closure(config_lapi=None, config_bouncer=None, api_key=None): if config_bouncer is None: @@ -26,16 +25,16 @@ def closure(config_lapi=None, config_bouncer=None, api_key=None): # can be overridden by config_lapi + config_bouncer api_key = api_key_factory() env = { - 'BOUNCER_KEY_custom': api_key, + "BOUNCER_KEY_custom": api_key, } try: env.update(config_lapi) with crowdsec(environment=env) as lapi: - lapi.wait_for_http(8080, '/health') - port = lapi.probe.get_bound_port('8080') + lapi.wait_for_http(8080, "/health") + port = lapi.probe.get_bound_port("8080") cfg = fw_cfg_factory() - cfg['api_url'] = f'http://localhost:{port}/' - cfg['api_key'] = api_key + cfg["api_url"] = f"http://localhost:{port}/" + cfg["api_key"] = api_key cfg.update(config_bouncer) with bouncer(cfg) as cb: yield cb, lapi @@ -46,15 +45,16 @@ def closure(config_lapi=None, config_bouncer=None, api_key=None): _default_config = { - 'mode': 'dry-run', - 'log_level': 'info', + "mode": "dry-run", + "log_level": "info", } -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def fw_cfg_factory(): def closure(**kw): cfg = _default_config.copy() cfg |= kw return cfg | kw + yield closure diff --git a/test/tests/install/__init__.py b/test/tests/install/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/tests/install/no_crowdsec/__init__.py b/test/tests/install/no_crowdsec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/install/no_crowdsec/test_no_crowdsec_deb.py b/test/tests/install/no_crowdsec/test_no_crowdsec_deb.py similarity index 68% rename from test/install/no_crowdsec/test_no_crowdsec_deb.py rename to test/tests/install/no_crowdsec/test_no_crowdsec_deb.py index ac2638fc..63d0a3f0 100644 --- a/test/install/no_crowdsec/test_no_crowdsec_deb.py +++ b/test/tests/install/no_crowdsec/test_no_crowdsec_deb.py @@ -13,7 +13,7 @@ def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root): # 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}' + 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) @@ -23,12 +23,12 @@ def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root): # install the package p = subprocess.run( - ['dpkg', '--install', deb_package_path.as_posix()], + ["dpkg", "--install", deb_package_path.as_posix()], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='utf-8' + encoding="utf-8", ) - assert p.returncode == 0, f'Failed to install {deb_package_path}' + 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 @@ -36,19 +36,16 @@ def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root): 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' - ) + 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], + ["dpkg", "--purge", package_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='utf-8' + encoding="utf-8", ) - assert p.returncode == 0, f'Failed to purge {package_name}' + assert p.returncode == 0, f"Failed to purge {package_name}" assert not os.path.exists(bouncer_exe) assert not os.path.exists(config) diff --git a/test/install/no_crowdsec/test_no_crowdsec_scripts.py b/test/tests/install/no_crowdsec/test_no_crowdsec_scripts.py similarity index 55% rename from test/install/no_crowdsec/test_no_crowdsec_scripts.py rename to test/tests/install/no_crowdsec/test_no_crowdsec_scripts.py index 0d487f65..14c08826 100644 --- a/test/install/no_crowdsec/test_no_crowdsec_scripts.py +++ b/test/tests/install/no_crowdsec/test_no_crowdsec_scripts.py @@ -1,27 +1,23 @@ import os -import pexpect import re -import yaml +import pexpect import pytest +import yaml BOUNCER = "crowdsec-firewall-bouncer" CONFIG = f"/etc/crowdsec/bouncers/{BOUNCER}.yaml" -@pytest.mark.dependency() +@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 = 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(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.") @@ -31,46 +27,37 @@ def test_install_no_crowdsec(project_repo, bouncer_binary, must_be_root): with open(CONFIG) as f: y = yaml.safe_load(f) - assert y['api_key'] == '' - assert y['mode'] == 'nftables' + assert y["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 + 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 = 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']) +@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}') + os.remove(f"/usr/local/bin/{BOUNCER}") - c = pexpect.spawn( - '/usr/bin/sh', ['scripts/upgrade.sh'], - cwd=project_repo - ) + 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 + 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']) +@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 = pexpect.spawn("/usr/bin/sh", ["scripts/uninstall.sh"], cwd=project_repo) c.expect(f"{BOUNCER} has been successfully uninstalled") c.wait() @@ -78,4 +65,4 @@ def test_uninstall_no_crowdsec(project_repo, must_be_root): assert c.exitstatus == 0 assert not os.path.exists(CONFIG) - assert not os.path.exists(f'/usr/local/bin/{BOUNCER}') + assert not os.path.exists(f"/usr/local/bin/{BOUNCER}") diff --git a/test/tests/install/with_crowdsec/__init__.py b/test/tests/install/with_crowdsec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/install/with_crowdsec/test_crowdsec_deb.py b/test/tests/install/with_crowdsec/test_crowdsec_deb.py similarity index 57% rename from test/install/with_crowdsec/test_crowdsec_deb.py rename to test/tests/install/with_crowdsec/test_crowdsec_deb.py index 138b1aa7..327f544a 100644 --- a/test/install/with_crowdsec/test_crowdsec_deb.py +++ b/test/tests/install/with_crowdsec/test_crowdsec_deb.py @@ -1,9 +1,9 @@ import os import subprocess -import yaml from pathlib import Path import pytest +import yaml from zxcvbn import zxcvbn pytestmark = pytest.mark.deb @@ -19,15 +19,12 @@ def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root): # 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}' + assert deb_package_path.exists(), f"This test requires {deb_package_path}" - p = subprocess.check_output( - ['dpkg-deb', '-f', deb_package_path.as_posix(), 'Package'], - encoding='utf-8' - ) + p = subprocess.check_output(["dpkg-deb", "-f", deb_package_path.as_posix(), "Package"], encoding="utf-8") package_name = p.strip() - subprocess.check_call(['dpkg', '--purge', package_name]) + subprocess.check_call(["dpkg", "--purge", package_name]) bouncer_exe = f"/usr/bin/{bouncer_under_test}" assert not os.path.exists(bouncer_exe) @@ -37,12 +34,12 @@ def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root): # install the package p = subprocess.run( - ['dpkg', '--install', deb_package_path.as_posix()], + ["dpkg", "--install", deb_package_path.as_posix()], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='utf-8' + encoding="utf-8", ) - assert p.returncode == 0, f'Failed to install {deb_package_path}' + 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 @@ -52,24 +49,24 @@ def test_deb_install_purge(deb_package_path, bouncer_under_test, must_be_root): with open(config) as f: cfg = yaml.safe_load(f) - api_key = cfg['api_key'] + api_key = cfg["api_key"] # the api key has been set to a random value - assert zxcvbn(api_key)['score'] == 4, f"weak api_key: '{api_key}'" + assert zxcvbn(api_key)["score"] == 4, f"weak api_key: '{api_key}'" - with open(config+'.id') as f: + with open(config + ".id") as f: bouncer_name = f.read().strip() - p = subprocess.check_output(['cscli', 'bouncers', 'list', '-o', 'json']) + p = subprocess.check_output(["cscli", "bouncers", "list", "-o", "json"]) bouncers = yaml.safe_load(p) - assert len([b for b in bouncers if b['name'] == bouncer_name]) == 1 + assert len([b for b in bouncers if b["name"] == bouncer_name]) == 1 p = subprocess.run( - ['dpkg', '--purge', package_name], + ["dpkg", "--purge", package_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='utf-8' + encoding="utf-8", ) - assert p.returncode == 0, f'Failed to purge {package_name}' + assert p.returncode == 0, f"Failed to purge {package_name}" assert not os.path.exists(bouncer_exe) assert not os.path.exists(config) @@ -84,57 +81,53 @@ def test_deb_install_purge_yaml_local(deb_package_path, bouncer_under_test, must => the configuration files are not touched (no new api key) """ + assert deb_package_path.exists(), f"This test requires {deb_package_path}" - assert deb_package_path.exists(), f'This test requires {deb_package_path}' - - p = subprocess.check_output( - ['dpkg-deb', '-f', deb_package_path.as_posix(), 'Package'], - encoding='utf-8' - ) + p = subprocess.check_output(["dpkg-deb", "-f", deb_package_path.as_posix(), "Package"], encoding="utf-8") package_name = p.strip() - subprocess.check_call(['dpkg', '--purge', package_name]) - subprocess.run(['cscli', 'bouncers', 'delete', 'testbouncer']) + subprocess.check_call(["dpkg", "--purge", package_name]) + subprocess.run(["cscli", "bouncers", "delete", "testbouncer"]) bouncer_exe = f"/usr/bin/{bouncer_under_test}" config = Path(f"/etc/crowdsec/bouncers/{bouncer_under_test}.yaml") config.parent.mkdir(parents=True, exist_ok=True) - subprocess.check_call(['cscli', 'bouncers', 'add', 'testbouncer', '-k', '123456']) + subprocess.check_call(["cscli", "bouncers", "add", "testbouncer", "-k", "123456"]) - with open(config.with_suffix('.yaml.local'), 'w') as f: + with open(config.with_suffix(".yaml.local"), "w") as f: f.write('api_key: "123456"') p = subprocess.run( - ['dpkg', '--install', deb_package_path.as_posix()], + ["dpkg", "--install", deb_package_path.as_posix()], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='utf-8' + encoding="utf-8", ) - assert p.returncode == 0, f'Failed to install {deb_package_path}' + assert p.returncode == 0, f"Failed to install {deb_package_path}" assert os.path.exists(bouncer_exe) assert os.path.exists(config) with open(config) as f: cfg = yaml.safe_load(f) - api_key = cfg['api_key'] + api_key = cfg["api_key"] # the api key has not been set - assert api_key == '${API_KEY}' + assert api_key == "${API_KEY}" - p = subprocess.check_output([bouncer_exe, '-c', config, '-T']) + p = subprocess.check_output([bouncer_exe, "-c", config, "-T"]) merged_config = yaml.safe_load(p) - assert merged_config['api_key'] == '123456' + assert merged_config["api_key"] == "123456" - os.unlink(config.with_suffix('.yaml.local')) + os.unlink(config.with_suffix(".yaml.local")) p = subprocess.run( - ['dpkg', '--purge', package_name], + ["dpkg", "--purge", package_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='utf-8' + encoding="utf-8", ) - assert p.returncode == 0, f'Failed to purge {package_name}' + assert p.returncode == 0, f"Failed to purge {package_name}" assert not os.path.exists(bouncer_exe) assert not os.path.exists(config) diff --git a/test/install/with_crowdsec/test_crowdsec_scripts.py b/test/tests/install/with_crowdsec/test_crowdsec_scripts.py similarity index 62% rename from test/install/with_crowdsec/test_crowdsec_scripts.py rename to test/tests/install/with_crowdsec/test_crowdsec_scripts.py index 9805dcff..d8b07d81 100644 --- a/test/install/with_crowdsec/test_crowdsec_scripts.py +++ b/test/tests/install/with_crowdsec/test_crowdsec_scripts.py @@ -1,9 +1,9 @@ import os -import pexpect import re -import yaml +import pexpect import pytest +import yaml from pytest_cs.lib import cscli, text BOUNCER = "crowdsec-firewall-bouncer" @@ -11,20 +11,15 @@ @pytest.mark.systemd_debug(BOUNCER) -@pytest.mark.dependency() +@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 = 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(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()) @@ -37,13 +32,13 @@ def test_install_crowdsec(project_repo, bouncer_binary, must_be_root): # 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 + 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 + assert y["api_key"] == api_key # the bouncer is registered with open(f"{CONFIG}.id") as f: @@ -51,45 +46,33 @@ def test_install_crowdsec(project_repo, bouncer_binary, must_be_root): 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 = 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']) +@pytest.mark.dependency(depends=["test_install_crowdsec"]) def test_upgrade_crowdsec(project_repo, must_be_root): - os.remove(f'/usr/local/bin/{BOUNCER}') + os.remove(f"/usr/local/bin/{BOUNCER}") - c = pexpect.spawn( - '/usr/bin/sh', ['scripts/upgrade.sh'], - encoding='utf-8', - cwd=project_repo - ) + 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 + 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']) +@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 = pexpect.spawn("/usr/bin/sh", ["scripts/uninstall.sh"], encoding="utf-8", cwd=project_repo) c.expect(f"{BOUNCER} has been successfully uninstalled") c.wait() @@ -98,7 +81,7 @@ def test_uninstall_crowdsec(project_repo, must_be_root): # installed files assert not os.path.exists(CONFIG) - assert not os.path.exists(f'/usr/local/bin/{BOUNCER}') + assert not os.path.exists(f"/usr/local/bin/{BOUNCER}") # the bouncer is unregistered assert len(list(cscli.get_bouncers(name=bouncer_name))) == 0 diff --git a/test/tests/pkg/__init__.py b/test/tests/pkg/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/pkg/test_build_deb.py b/test/tests/pkg/test_build_deb.py similarity index 78% rename from test/pkg/test_build_deb.py rename to test/tests/pkg/test_build_deb.py index 05aedce2..640513a0 100644 --- a/test/pkg/test_build_deb.py +++ b/test/tests/pkg/test_build_deb.py @@ -7,4 +7,4 @@ # 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' + assert deb_package.exists(), f"Package {deb_package} not found" diff --git a/test/pkg/test_build_rpm.py b/test/tests/pkg/test_build_rpm.py similarity index 67% rename from test/pkg/test_build_rpm.py rename to test/tests/pkg/test_build_rpm.py index dbc971f8..568be2d2 100644 --- a/test/pkg/test_build_rpm.py +++ b/test/tests/pkg/test_build_rpm.py @@ -5,4 +5,4 @@ def test_rpm_build(rpm_package, skip_unless_rpm): """Test that the package can be built.""" - assert rpm_package.exists(), f'Package {rpm_package} not found' + assert rpm_package.exists(), f"Package {rpm_package} not found" diff --git a/test/pkg/test_scripts_nonroot.py b/test/tests/pkg/test_scripts_nonroot.py similarity index 59% rename from test/pkg/test_scripts_nonroot.py rename to test/tests/pkg/test_scripts_nonroot.py index acb2a881..155f9f1b 100644 --- a/test/pkg/test_scripts_nonroot.py +++ b/test/tests/pkg/test_scripts_nonroot.py @@ -5,15 +5,15 @@ 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']: + for script in ["install.sh", "upgrade.sh", "uninstall.sh"]: c = subprocess.run( - ['/usr/bin/sh', f'scripts/{script}'], + ["/usr/bin/sh", f"scripts/{script}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_repo, - encoding='utf-8', + encoding="utf-8", ) assert c.returncode == 1 - assert c.stdout == '' - assert 'This script must be run as root' in c.stderr + assert c.stdout == "" + assert "This script must be run as root" in c.stderr diff --git a/test/uv.lock b/test/uv.lock new file mode 100644 index 00000000..0f7f1a25 --- /dev/null +++ b/test/uv.lock @@ -0,0 +1,747 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + +[[package]] +name = "basedpyright" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodejs-wheel-binaries" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/c2/5685d040d4f2598788d42bfd2db5f808e9aa2eaee77fcae3c2fbe4ea0e7c/basedpyright-1.26.0.tar.gz", hash = "sha256:5e01f6eb9290a09ef39672106cf1a02924fdc8970e521838bc502ccf0676f32f", size = 24932771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/72/65308f45bb73efc93075426cac5f37eea937ae364aa675785521cb3512c7/basedpyright-1.26.0-py3-none-any.whl", hash = "sha256:5a6a17f2c389ec313dd2c3644f40e8221bc90252164802e626055341c0a37381", size = 11504579 }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "cryptography" +version = "44.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/4c/45dfa6829acffa344e3967d6006ee4ae8be57af746ae2eba1c431949b32c/cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", size = 710657 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/09/8cc67f9b84730ad330b3b72cf867150744bf07ff113cda21a15a1c6d2c7c/cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", size = 6541833 }, + { url = "https://files.pythonhosted.org/packages/7e/5b/3759e30a103144e29632e7cb72aec28cedc79e514b2ea8896bb17163c19b/cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", size = 3922710 }, + { url = "https://files.pythonhosted.org/packages/5f/58/3b14bf39f1a0cfd679e753e8647ada56cddbf5acebffe7db90e184c76168/cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", size = 4137546 }, + { url = "https://files.pythonhosted.org/packages/98/65/13d9e76ca19b0ba5603d71ac8424b5694415b348e719db277b5edc985ff5/cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", size = 3915420 }, + { url = "https://files.pythonhosted.org/packages/b1/07/40fe09ce96b91fc9276a9ad272832ead0fddedcba87f1190372af8e3039c/cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", size = 4154498 }, + { url = "https://files.pythonhosted.org/packages/75/ea/af65619c800ec0a7e4034207aec543acdf248d9bffba0533342d1bd435e1/cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", size = 3932569 }, + { url = "https://files.pythonhosted.org/packages/c7/af/d1deb0c04d59612e3d5e54203159e284d3e7a6921e565bb0eeb6269bdd8a/cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", size = 4016721 }, + { url = "https://files.pythonhosted.org/packages/bd/69/7ca326c55698d0688db867795134bdfac87136b80ef373aaa42b225d6dd5/cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", size = 4240915 }, + { url = "https://files.pythonhosted.org/packages/ef/d4/cae11bf68c0f981e0413906c6dd03ae7fa864347ed5fac40021df1ef467c/cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", size = 2757925 }, + { url = "https://files.pythonhosted.org/packages/64/b1/50d7739254d2002acae64eed4fc43b24ac0cc44bf0a0d388d1ca06ec5bb1/cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", size = 3202055 }, + { url = "https://files.pythonhosted.org/packages/11/18/61e52a3d28fc1514a43b0ac291177acd1b4de00e9301aaf7ef867076ff8a/cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", size = 6542801 }, + { url = "https://files.pythonhosted.org/packages/1a/07/5f165b6c65696ef75601b781a280fc3b33f1e0cd6aa5a92d9fb96c410e97/cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", size = 3922613 }, + { url = "https://files.pythonhosted.org/packages/28/34/6b3ac1d80fc174812486561cf25194338151780f27e438526f9c64e16869/cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", size = 4137925 }, + { url = "https://files.pythonhosted.org/packages/d0/c7/c656eb08fd22255d21bc3129625ed9cd5ee305f33752ef2278711b3fa98b/cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", size = 3915417 }, + { url = "https://files.pythonhosted.org/packages/ef/82/72403624f197af0db6bac4e58153bc9ac0e6020e57234115db9596eee85d/cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", size = 4155160 }, + { url = "https://files.pythonhosted.org/packages/a2/cd/2f3c440913d4329ade49b146d74f2e9766422e1732613f57097fea61f344/cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", size = 3932331 }, + { url = "https://files.pythonhosted.org/packages/7f/df/8be88797f0a1cca6e255189a57bb49237402b1880d6e8721690c5603ac23/cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", size = 4017372 }, + { url = "https://files.pythonhosted.org/packages/af/36/5ccc376f025a834e72b8e52e18746b927f34e4520487098e283a719c205e/cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", size = 4239657 }, + { url = "https://files.pythonhosted.org/packages/46/b0/f4f7d0d0bcfbc8dd6296c1449be326d04217c57afb8b2594f017eed95533/cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", size = 2758672 }, + { url = "https://files.pythonhosted.org/packages/97/9b/443270b9210f13f6ef240eff73fd32e02d381e7103969dc66ce8e89ee901/cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", size = 3202071 }, +] + +[[package]] +name = "cs-firewall-bouncer-tests" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask" }, + { name = "pexpect" }, + { name = "psutil" }, + { name = "pytest" }, + { name = "pytest-cs" }, + { name = "pytest-dependency" }, + { name = "pytest-dotenv" }, + { name = "pytimeparse" }, + { name = "zxcvbn" }, +] + +[package.dev-dependencies] +dev = [ + { name = "basedpyright" }, + { name = "ipdb" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask", specifier = ">=3.1.0" }, + { name = "pexpect", specifier = ">=4.9.0" }, + { name = "psutil", specifier = ">=6.1.1" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-cs", git = "https://github.com/crowdsecurity/pytest-cs" }, + { name = "pytest-dependency", specifier = ">=0.6.0" }, + { name = "pytest-dotenv", specifier = ">=0.5.2" }, + { name = "pytimeparse", specifier = ">=1.1.8" }, + { name = "zxcvbn", specifier = ">=4.4.28" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "basedpyright", specifier = ">=1.26.0" }, + { name = "ipdb", specifier = ">=0.13.13" }, + { name = "ruff", specifier = ">=0.9.4" }, +] + +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + +[[package]] +name = "flask" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "ipdb" +version = "0.13.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, + { name = "ipython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130 }, +] + +[[package]] +name = "ipython" +version = "8.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/80/4d2a072e0db7d250f134bc11676517299264ebe16d62a8619d49a78ced73/ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251", size = 5507441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e1/f4474a7ecdb7745a820f6f6039dc43c66add40f1bcc66485607d93571af6/ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa", size = 825524 }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + +[[package]] +name = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "nodejs-wheel-binaries" +version = "22.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/c5/1af2fc54fcc18f4a99426b46f18832a04f755ee340019e1be536187c1e1c/nodejs_wheel_binaries-22.13.1.tar.gz", hash = "sha256:a0c15213c9c3383541be4400a30959883868ce5da9cebb3d63ddc7fe61459308", size = 8053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e9/b0dd118e0fd4eabe1ec9c3d9a68df4d811282e8837b811d804f23742e117/nodejs_wheel_binaries-22.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:e4f64d0e26600d51cbdd98a6718a19c2d1b8c7538e9e353e95a634a06a8e1a58", size = 51015650 }, + { url = "https://files.pythonhosted.org/packages/cc/a6/9ba835f5d4f3f6b1f01191e7ac0874871f9743de5c42a5a9a54e67c2e2a6/nodejs_wheel_binaries-22.13.1-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:afcb40484bb02f23137f838014724604ae183fd767b30da95b0be1510a40c06d", size = 51814957 }, + { url = "https://files.pythonhosted.org/packages/0d/2e/a430207e5f22bd3dcffb81acbddf57ee4108b9e2b0f99a5578dc2c1ff7fc/nodejs_wheel_binaries-22.13.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fc88c98eebabfc36b5270a4ab974a2682746931567ca76a5ca49c54482bbb51", size = 57148437 }, + { url = "https://files.pythonhosted.org/packages/97/f4/5731b6f0c8af434619b4f1b8fd895bc33fca60168cd68133e52841872114/nodejs_wheel_binaries-22.13.1-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9f75ea8f5e3e5416256fcb00a98cbe14c8d3b6dcaf17da29c4ade5723026d8", size = 57634451 }, + { url = "https://files.pythonhosted.org/packages/49/28/83166f7e39812e9ef99cfa3e722c54e32dd9de6a1290f3216c2e5d1f4957/nodejs_wheel_binaries-22.13.1-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:94608702ef6c389d32e89ff3b7a925cb5dedaf55b5d98bd0c4fb3450a8b6d1c1", size = 58794510 }, + { url = "https://files.pythonhosted.org/packages/f7/64/4832ec26d0a7ca7a5574df265d85c6832f9a624024511fc34958227ad740/nodejs_wheel_binaries-22.13.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53a40d0269689aa2eaf2e261cbe5ec256644bc56aae0201ef344b7d8f40ccc79", size = 59738596 }, + { url = "https://files.pythonhosted.org/packages/18/cd/def29615dac250cda3d141e1c03b7153b9a027360bde0272a6768c5fae33/nodejs_wheel_binaries-22.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:549371a929a29fbce8d0ab8f1b5410549946d4f1b0376a5ce635b45f6d05298f", size = 40455444 }, + { url = "https://files.pythonhosted.org/packages/15/d7/6de2bc615203bf590ca437a5cac145b2f86d994ce329489125a0a90ba715/nodejs_wheel_binaries-22.13.1-py2.py3-none-win_arm64.whl", hash = "sha256:cf72d50d755f4e5c0709b0449de01768d96b3b1ec7aa531561415b88f179ad8b", size = 36200929 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, +] + +[[package]] +name = "psutil" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511 }, + { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985 }, + { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488 }, + { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477 }, + { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017 }, + { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602 }, + { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-cs" +version = "0.7.21" +source = { git = "https://github.com/crowdsecurity/pytest-cs#1eb949d7befa6fe172bf459616b267d4ffc01179" } +dependencies = [ + { name = "docker" }, + { name = "psutil" }, + { name = "pytest" }, + { name = "pytest-datadir" }, + { name = "pytest-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "trustme" }, +] + +[[package]] +name = "pytest-datadir" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/97/a93900d82635aa3f419c3cd2059b4de7d7fe44e415eaf00c298854582dcc/pytest-datadir-1.5.0.tar.gz", hash = "sha256:1617ed92f9afda0c877e4eac91904b5f779d24ba8f5e438752e3ae39d8d2ee3f", size = 8821 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/90/96b9474cddda5ef9e10e6f1871c0fadfa153b605e0e749ba30437bfb62a0/pytest_datadir-1.5.0-py3-none-any.whl", hash = "sha256:34adf361bcc7b37961bbc1dfa8d25a4829e778bab461703c38a5c50ca9c36dc8", size = 5095 }, +] + +[[package]] +name = "pytest-dependency" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/3b/317cc04e77d707d338540ca67b619df8f247f3f4c9f40e67bf5ea503ad94/pytest-dependency-0.6.0.tar.gz", hash = "sha256:934b0e6a39d95995062c193f7eaeed8a8ffa06ff1bcef4b62b0dc74a708bacc1", size = 19499 } + +[[package]] +name = "pytest-dotenv" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/b0/cafee9c627c1bae228eb07c9977f679b3a7cb111b488307ab9594ba9e4da/pytest-dotenv-0.5.2.tar.gz", hash = "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", size = 3782 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/da/9da67c67b3d0963160e3d2cbc7c38b6fae342670cc8e6d5936644b2cf944/pytest_dotenv-0.5.2-py3-none-any.whl", hash = "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f", size = 3993 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "pytimeparse" +version = "1.1.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/5d/231f5f33c81e09682708fb323f9e4041408d8223e2f0fb9742843328778f/pytimeparse-1.1.8.tar.gz", hash = "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a", size = 9403 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/b4/afd75551a3b910abd1d922dbd45e49e5deeb4d47dc50209ce489ba9844dd/pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", size = 9969 }, +] + +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "ruff" +version = "0.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/17/529e78f49fc6f8076f50d985edd9a2cf011d1dbadb1cdeacc1d12afc1d26/ruff-0.9.4.tar.gz", hash = "sha256:6907ee3529244bb0ed066683e075f09285b38dd5b4039370df6ff06041ca19e7", size = 3599458 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/f8/3fafb7804d82e0699a122101b5bee5f0d6e17c3a806dcbc527bb7d3f5b7a/ruff-0.9.4-py3-none-linux_armv6l.whl", hash = "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706", size = 11668400 }, + { url = "https://files.pythonhosted.org/packages/2e/a6/2efa772d335da48a70ab2c6bb41a096c8517ca43c086ea672d51079e3d1f/ruff-0.9.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf", size = 11628395 }, + { url = "https://files.pythonhosted.org/packages/dc/d7/cd822437561082f1c9d7225cc0d0fbb4bad117ad7ac3c41cd5d7f0fa948c/ruff-0.9.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:54499fb08408e32b57360f6f9de7157a5fec24ad79cb3f42ef2c3f3f728dfe2b", size = 11090052 }, + { url = "https://files.pythonhosted.org/packages/9e/67/3660d58e893d470abb9a13f679223368ff1684a4ef40f254a0157f51b448/ruff-0.9.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37c892540108314a6f01f105040b5106aeb829fa5fb0561d2dcaf71485021137", size = 11882221 }, + { url = "https://files.pythonhosted.org/packages/79/d1/757559995c8ba5f14dfec4459ef2dd3fcea82ac43bc4e7c7bf47484180c0/ruff-0.9.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de9edf2ce4b9ddf43fd93e20ef635a900e25f622f87ed6e3047a664d0e8f810e", size = 11424862 }, + { url = "https://files.pythonhosted.org/packages/c0/96/7915a7c6877bb734caa6a2af424045baf6419f685632469643dbd8eb2958/ruff-0.9.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87c90c32357c74f11deb7fbb065126d91771b207bf9bfaaee01277ca59b574ec", size = 12626735 }, + { url = "https://files.pythonhosted.org/packages/0e/cc/dadb9b35473d7cb17c7ffe4737b4377aeec519a446ee8514123ff4a26091/ruff-0.9.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56acd6c694da3695a7461cc55775f3a409c3815ac467279dfa126061d84b314b", size = 13255976 }, + { url = "https://files.pythonhosted.org/packages/5f/c3/ad2dd59d3cabbc12df308cced780f9c14367f0321e7800ca0fe52849da4c/ruff-0.9.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0c93e7d47ed951b9394cf352d6695b31498e68fd5782d6cbc282425655f687a", size = 12752262 }, + { url = "https://files.pythonhosted.org/packages/c7/17/5f1971e54bd71604da6788efd84d66d789362b1105e17e5ccc53bba0289b/ruff-0.9.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4c8772670aecf037d1bf7a07c39106574d143b26cfe5ed1787d2f31e800214", size = 14401648 }, + { url = "https://files.pythonhosted.org/packages/30/24/6200b13ea611b83260501b6955b764bb320e23b2b75884c60ee7d3f0b68e/ruff-0.9.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc5f1d7afeda8d5d37660eeca6d389b142d7f2b5a1ab659d9214ebd0e025231", size = 12414702 }, + { url = "https://files.pythonhosted.org/packages/34/cb/f5d50d0c4ecdcc7670e348bd0b11878154bc4617f3fdd1e8ad5297c0d0ba/ruff-0.9.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faa935fc00ae854d8b638c16a5f1ce881bc3f67446957dd6f2af440a5fc8526b", size = 11859608 }, + { url = "https://files.pythonhosted.org/packages/d6/f4/9c8499ae8426da48363bbb78d081b817b0f64a9305f9b7f87eab2a8fb2c1/ruff-0.9.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6c634fc6f5a0ceae1ab3e13c58183978185d131a29c425e4eaa9f40afe1e6d6", size = 11485702 }, + { url = "https://files.pythonhosted.org/packages/18/59/30490e483e804ccaa8147dd78c52e44ff96e1c30b5a95d69a63163cdb15b/ruff-0.9.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:433dedf6ddfdec7f1ac7575ec1eb9844fa60c4c8c2f8887a070672b8d353d34c", size = 12067782 }, + { url = "https://files.pythonhosted.org/packages/3d/8c/893fa9551760b2f8eb2a351b603e96f15af167ceaf27e27ad873570bc04c/ruff-0.9.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d612dbd0f3a919a8cc1d12037168bfa536862066808960e0cc901404b77968f0", size = 12483087 }, + { url = "https://files.pythonhosted.org/packages/23/15/f6751c07c21ca10e3f4a51ea495ca975ad936d780c347d9808bcedbd7182/ruff-0.9.4-py3-none-win32.whl", hash = "sha256:db1192ddda2200671f9ef61d9597fcef89d934f5d1705e571a93a67fb13a4402", size = 9852302 }, + { url = "https://files.pythonhosted.org/packages/12/41/2d2d2c6a72e62566f730e49254f602dfed23019c33b5b21ea8f8917315a1/ruff-0.9.4-py3-none-win_amd64.whl", hash = "sha256:05bebf4cdbe3ef75430d26c375773978950bbf4ee3c95ccb5448940dc092408e", size = 10850051 }, + { url = "https://files.pythonhosted.org/packages/c6/e6/3d6ec3bc3d254e7f005c543a661a41c3e788976d0e52a1ada195bd664344/ruff-0.9.4-py3-none-win_arm64.whl", hash = "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41", size = 10078251 }, +] + +[[package]] +name = "setuptools" +version = "75.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "trustme" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/931476f4cf1cd9e736f32651005078061a50dc164a2569fb874e00eb2786/trustme-1.2.1.tar.gz", hash = "sha256:6528ba2bbc7f2db41f33825c8dd13e3e3eb9d334ba0f909713c8c3139f4ae47f", size = 26844 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/f3/c34dbabf6da5eda56fe923226769d40e11806952cd7f46655dd06e10f018/trustme-1.2.1-py3-none-any.whl", hash = "sha256:d768e5fc57c86dfc5ec9365102e9b092541cd6954b35d8c1eea01a84f35a762a", size = 16530 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] + +[[package]] +name = "zxcvbn" +version = "4.4.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/67/c6712608c99e7720598e769b8fb09ebd202119785adad0bbce25d330243c/zxcvbn-4.4.28.tar.gz", hash = "sha256:151bd816817e645e9064c354b13544f85137ea3320ca3be1fb6873ea75ef7dc1", size = 407871 }