Refactor to server subdirectory

A client library will be included in this repository as well, prepare by
moving all server components to a subdirectory. As part of this, the
server script is refactored to a generated entrypoint script.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
This commit is contained in:
Georg Pfuetzenreuter 2024-09-29 02:23:30 +02:00
parent 792149bae6
commit 1e84b2e57d
Signed by: Georg
GPG Key ID: 1ED2F138E7E6FF57
21 changed files with 104 additions and 80 deletions

View File

@ -1,67 +1,3 @@
# RESTful HTTP API for nftables # RESTful HTTP API for nftables
Early work in progress. 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}⏎
```

1
nft_http_api_server/LICENSE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE

View File

@ -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}⏎
```

View File

@ -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. 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 nftables_api.app import app
from waitress import serve
if __name__ == '__main__': if __name__ == '__main__':
serve(app, host='*', port=9090) serve(app, host='*', port=9090)

View File

@ -0,0 +1,20 @@
"""
A RESTful HTTP API for nftables
Copyright 2024, Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
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.')

View File

@ -4,8 +4,8 @@ build-backend = 'setuptools.build_meta'
[project] [project]
name = 'nftables_api' name = 'nftables_api-server'
description = 'RESTful HTTP API for nftables' description = 'RESTful HTTP API for nftables (server)'
dynamic = ['license', 'readme', 'version'] dynamic = ['license', 'readme', 'version']
authors = [ authors = [
{ name='Georg Pfuetzenreuter', email='georg+python@lysergic.dev' }, { name='Georg Pfuetzenreuter', email='georg+python@lysergic.dev' },
@ -30,6 +30,8 @@ requires-python = '>=3.6'
dependencies = [ dependencies = [
'PyYAML', 'PyYAML',
'bcrypt',
'falcon',
# 'nftables', # cannot by managed by pip # 'nftables', # cannot by managed by pip
] ]
@ -39,6 +41,9 @@ dev = [
'ruff', 'ruff',
] ]
[project.scripts]
nftables_api = 'nftables_api.server:server'
[tool.setuptools] [tool.setuptools]
include-package-data = false include-package-data = false
@ -48,4 +53,3 @@ readme = {file = ['README.md']}
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ['.'] where = ['.']
exclude = ['scripts.*', 'tests', 'tests.*', 'ruff.toml']

View File

@ -1,5 +1,5 @@
[metadata] [metadata]
name = nftables_api name = nftables_api-server
version = attr: nftables_api.__version__ version = attr: nftables_api.__version__
author = Georg Pfuetzenreuter author = Georg Pfuetzenreuter
author_email = georg+python@lysergic.dev author_email = georg+python@lysergic.dev
@ -14,8 +14,4 @@ packages=find:
python_requires = >=3.6 python_requires = >=3.6
[options.packages.find] [options.packages.find]
exclude=
scripts*
tests*
*.toml
where=. where=.

View File

@ -37,7 +37,11 @@ preview = true
explicit-preview-rules = true explicit-preview-rules = true
[lint.per-file-ignores] [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" = [ "tests/*.py" = [
"INP001", # tests do not need to be part of a package "INP001", # tests do not need to be part of a package
"S101", # allow "assert" in test suites "S101", # allow "assert" in test suites
@ -47,5 +51,5 @@ explicit-preview-rules = true
[lint.pydocstyle] [lint.pydocstyle]
convention = "pep257" convention = "pep257"
[lint.isort] #[lint.isort]
force-wrap-aliases = true #force-wrap-aliases = true

View File

@ -8,5 +8,5 @@ podman run \
-it \ -it \
-v .:"$wd" \ -v .:"$wd" \
registry.opensuse.org/home/crameleon/containers/containers/crameleon/pytest-nftables:latest \ 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

View File

@ -10,9 +10,8 @@ You may obtain copies of the Licence in any of the official languages at https:/
from falcon import testing from falcon import testing
from nftables import Nftables from nftables import Nftables
from pytest import exit, fixture
from nftables_api.app import app from nftables_api.app import app
from pytest import exit, fixture
def run_nft(nft, cmd): def run_nft(nft, cmd):