diff --git a/README.md b/README.md index 4a76009..fa791d6 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,3 @@ # RESTful HTTP API for nftables Early work in progress. - -## Configuration - -A configuration file must be passed using the environment variable `NFT-API-CONFIG`. The file contains a mapping of bcrypt hashed tokens to API paths and methods. Requests to the API are only authorized if the token passed using the header `X-NFT-API-Token` and the requested path and method match an entry in the configuration. - -### Sample configuration: - -``` -nft-api: - tokens: - $2y$05$ZifkrfFg2XZU2ds7Lrcl9usJVyxHro9Ezjo84OMpsBSau4pEu42eS: - /set/inet/filter/foo4: - - GET - - POST - $2y$05$ZifkrfFg2XZU2ds7Lrcl9usJVyxHro9Ezjo84OMpsBSau4pEu42eS: - /set/inet/filter/*: - - GET -``` - -Any elements can contain a wildcard in the form of a `*` character. - -Generate token hashes using any bcrypt hashing tool, such as `htpasswd` from the `apache-utils` suite: - -``` -$ htpasswd -Bn x -``` - -The username (here `x`) is not used, ignore the relevant part before and including the colon in the output. - -## Deployment - -The application can be served using any Python WSGI server. Easiest is to use `waitress` using the script found in the repository root: - -``` -./nftables-api.py -``` - -It will bind on `*:9090`. - -## Usage - -Currently `GET` and `POST` methods are implemented, allowing to query or append data respectively. - -All requests must contain a header `X-NFT-API-Token` containing a token authorized for the requested path and method. All `POST` requests must contain a JSON payload. - -By default, the response body will be output deemed most "useful" - in case of `GET` requests, that is only the data found for the given query, in case of `POST` requests the result of the change. Optionally, a query parameter `raw` can be passed to retrieve a mapping containing all status information, including the raw `libnftables-json(5)` output, instead. - -Below are examples using `curl`. - -### nftables sets - -#### Get a set - -``` -$ curl -H 'X-NFT-API-Token: foo' localhost:9090/set/inet/filter/foo4 -["192.168.0.0/24", "192.168.1.1", "192.168.3.0/24", "192.168.4.0/24", "192.168.5.0/26"]⏎ -``` - -#### Append to a set - -``` -$ curl -H 'X-NFT-API-Token: foo' --json '{"addresses": "fe80::/64"}' localhost:9090/set/inet/filter/foo -{"status": true}⏎ -``` diff --git a/nft_http_api_server/LICENSE b/nft_http_api_server/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/nft_http_api_server/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/nft_http_api_server/README.md b/nft_http_api_server/README.md new file mode 100644 index 0000000..c06a289 --- /dev/null +++ b/nft_http_api_server/README.md @@ -0,0 +1,65 @@ +# RESTful HTTP API for nftables - Server + +## Configuration + +A configuration file must be passed using the environment variable `NFT-API-CONFIG`. The file contains a mapping of bcrypt hashed tokens to API paths and methods. Requests to the API are only authorized if the token passed using the header `X-NFT-API-Token` and the requested path and method match an entry in the configuration. + +### Sample configuration: + +``` +nft-api: + tokens: + $2y$05$ZifkrfFg2XZU2ds7Lrcl9usJVyxHro9Ezjo84OMpsBSau4pEu42eS: + /set/inet/filter/foo4: + - GET + - POST + $2y$05$ZifkrfFg2XZU2ds7Lrcl9usJVyxHro9Ezjo84OMpsBSau4pEu42eS: + /set/inet/filter/*: + - GET +``` + +Any elements can contain a wildcard in the form of a `*` character. + +Generate token hashes using any bcrypt hashing tool, such as `htpasswd` from the `apache-utils` suite: + +``` +$ htpasswd -Bn x +``` + +The username (here `x`) is not used, ignore the relevant part before and including the colon in the output. + +## Deployment + +The application can be served using any Python WSGI server. Easiest is to use `waitress` using the script found in the repository root: + +``` +./nftables-api.py +``` + +It will bind on `*:9090`. + +## Usage + +Currently `GET` and `POST` methods are implemented, allowing to query or append data respectively. + +All requests must contain a header `X-NFT-API-Token` containing a token authorized for the requested path and method. All `POST` requests must contain a JSON payload. + +By default, the response body will be output deemed most "useful" - in case of `GET` requests, that is only the data found for the given query, in case of `POST` requests the result of the change. Optionally, a query parameter `raw` can be passed to retrieve a mapping containing all status information, including the raw `libnftables-json(5)` output, instead. + +Below are examples using `curl`. + +### nftables sets + +#### Get a set + +``` +$ curl -H 'X-NFT-API-Token: foo' localhost:9090/set/inet/filter/foo4 +["192.168.0.0/24", "192.168.1.1", "192.168.3.0/24", "192.168.4.0/24", "192.168.5.0/26"]⏎ +``` + +#### Append to a set + +``` +$ curl -H 'X-NFT-API-Token: foo' --json '{"addresses": "fe80::/64"}' localhost:9090/set/inet/filter/foo +{"status": true}⏎ +``` diff --git a/nftables-api.py b/nft_http_api_server/nftables-http-api.py similarity index 99% rename from nftables-api.py rename to nft_http_api_server/nftables-http-api.py index 5e69c6f..60267d5 100644 --- a/nftables-api.py +++ b/nft_http_api_server/nftables-http-api.py @@ -8,9 +8,8 @@ An English copy of the Licence is shipped in a file called LICENSE along with th You may obtain copies of the Licence in any of the official languages at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12. """ -from waitress import serve - from nftables_api.app import app +from waitress import serve if __name__ == '__main__': serve(app, host='*', port=9090) diff --git a/nftables_api/__init__.py b/nft_http_api_server/nftables_api/__init__.py similarity index 100% rename from nftables_api/__init__.py rename to nft_http_api_server/nftables_api/__init__.py diff --git a/nftables_api/__version__.py b/nft_http_api_server/nftables_api/__version__.py similarity index 100% rename from nftables_api/__version__.py rename to nft_http_api_server/nftables_api/__version__.py diff --git a/nftables_api/app.py b/nft_http_api_server/nftables_api/app.py similarity index 100% rename from nftables_api/app.py rename to nft_http_api_server/nftables_api/app.py diff --git a/nftables_api/config.py b/nft_http_api_server/nftables_api/config.py similarity index 100% rename from nftables_api/config.py rename to nft_http_api_server/nftables_api/config.py diff --git a/nftables_api/middlewares/__init__.py b/nft_http_api_server/nftables_api/middlewares/__init__.py similarity index 100% rename from nftables_api/middlewares/__init__.py rename to nft_http_api_server/nftables_api/middlewares/__init__.py diff --git a/nftables_api/middlewares/authentication.py b/nft_http_api_server/nftables_api/middlewares/authentication.py similarity index 100% rename from nftables_api/middlewares/authentication.py rename to nft_http_api_server/nftables_api/middlewares/authentication.py diff --git a/nftables_api/resources/__init__.py b/nft_http_api_server/nftables_api/resources/__init__.py similarity index 100% rename from nftables_api/resources/__init__.py rename to nft_http_api_server/nftables_api/resources/__init__.py diff --git a/nftables_api/resources/nft_set.py b/nft_http_api_server/nftables_api/resources/nft_set.py similarity index 100% rename from nftables_api/resources/nft_set.py rename to nft_http_api_server/nftables_api/resources/nft_set.py diff --git a/nft_http_api_server/nftables_api/server.py b/nft_http_api_server/nftables_api/server.py new file mode 100644 index 0000000..92fde2e --- /dev/null +++ b/nft_http_api_server/nftables_api/server.py @@ -0,0 +1,20 @@ +""" +A RESTful HTTP API for nftables +Copyright 2024, Georg Pfuetzenreuter + +Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence"). +You may not use this work except in compliance with the Licence. +An English copy of the Licence is shipped in a file called LICENSE along with this applications source code. +You may obtain copies of the Licence in any of the official languages at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12. +""" + +from waitress import serve + +from nftables_api.app import app + + +def server(): + if __name__ == '__main__': + serve(app, host='*', port=9090) + else: + raise RuntimeError('serve() is intended to be used as an entrypoint.') diff --git a/nftables_api/utils/__init__.py b/nft_http_api_server/nftables_api/utils/__init__.py similarity index 100% rename from nftables_api/utils/__init__.py rename to nft_http_api_server/nftables_api/utils/__init__.py diff --git a/nftables_api/utils/output.py b/nft_http_api_server/nftables_api/utils/output.py similarity index 100% rename from nftables_api/utils/output.py rename to nft_http_api_server/nftables_api/utils/output.py diff --git a/nftables_api/utils/parse.py b/nft_http_api_server/nftables_api/utils/parse.py similarity index 100% rename from nftables_api/utils/parse.py rename to nft_http_api_server/nftables_api/utils/parse.py diff --git a/pyproject.toml b/nft_http_api_server/pyproject.toml similarity index 87% rename from pyproject.toml rename to nft_http_api_server/pyproject.toml index 165dba8..fe05111 100644 --- a/pyproject.toml +++ b/nft_http_api_server/pyproject.toml @@ -4,8 +4,8 @@ build-backend = 'setuptools.build_meta' [project] -name = 'nftables_api' -description = 'RESTful HTTP API for nftables' +name = 'nftables_api-server' +description = 'RESTful HTTP API for nftables (server)' dynamic = ['license', 'readme', 'version'] authors = [ { name='Georg Pfuetzenreuter', email='georg+python@lysergic.dev' }, @@ -30,6 +30,8 @@ requires-python = '>=3.6' dependencies = [ 'PyYAML', + 'bcrypt', + 'falcon', # 'nftables', # cannot by managed by pip ] @@ -39,6 +41,9 @@ dev = [ 'ruff', ] +[project.scripts] +nftables_api = 'nftables_api.server:server' + [tool.setuptools] include-package-data = false @@ -48,4 +53,3 @@ readme = {file = ['README.md']} [tool.setuptools.packages.find] where = ['.'] -exclude = ['scripts.*', 'tests', 'tests.*', 'ruff.toml'] diff --git a/setup.cfg b/nft_http_api_server/setup.cfg similarity index 83% rename from setup.cfg rename to nft_http_api_server/setup.cfg index 8d66965..1d92bf7 100644 --- a/setup.cfg +++ b/nft_http_api_server/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = nftables_api +name = nftables_api-server version = attr: nftables_api.__version__ author = Georg Pfuetzenreuter author_email = georg+python@lysergic.dev @@ -14,8 +14,4 @@ packages=find: python_requires = >=3.6 [options.packages.find] -exclude= - scripts* - tests* - *.toml where=. diff --git a/ruff.toml b/ruff.toml index b6b6cf4..0462c27 100644 --- a/ruff.toml +++ b/ruff.toml @@ -37,7 +37,11 @@ preview = true explicit-preview-rules = true [lint.per-file-ignores] -"nftables_api/__init__.py" = ["PLC0414"] # allow explicit re-exports / avoid conflict with F401 +"nft_http_api_client/nftables_api_client/__init__.py" = ["PLC0414"] # allow explicit re-exports / avoid conflict with F401 +"nft_http_api_server/nftables_api/__init__.py" = ["PLC0414"] # ^ +"nft_http_api_server/nftables-http-api.py" = [ + "INP001", # entrypoint script +] "tests/*.py" = [ "INP001", # tests do not need to be part of a package "S101", # allow "assert" in test suites @@ -47,5 +51,5 @@ explicit-preview-rules = true [lint.pydocstyle] convention = "pep257" -[lint.isort] -force-wrap-aliases = true +#[lint.isort] +#force-wrap-aliases = true diff --git a/scripts/test.sh b/scripts/test.sh index 0544aa6..b870d9f 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -8,5 +8,5 @@ podman run \ -it \ -v .:"$wd" \ registry.opensuse.org/home/crameleon/containers/containers/crameleon/pytest-nftables:latest \ - env NFT-API-CONFIG="$wd"/tests/config.yaml PYTHONPATH="$wd" pytest --pdb --pdbcls=IPython.terminal.debugger:Pdb -rA -s -v -x "$wd"/tests + env NFT-API-CONFIG="$wd"/tests/config.yaml PYTHONPATH="$wd/nft_http_api_client:$wd/nft_http_api_server" pytest --pdb --pdbcls=IPython.terminal.debugger:Pdb -rA -s -v -x "$wd"/tests diff --git a/tests/conftest.py b/tests/conftest.py index b792108..37d4552 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,9 +10,8 @@ You may obtain copies of the Licence in any of the official languages at https:/ from falcon import testing from nftables import Nftables -from pytest import exit, fixture - from nftables_api.app import app +from pytest import exit, fixture def run_nft(nft, cmd):