Compare commits

...

3 Commits

Author SHA1 Message Date
e46d3a0410
Align server package description
Unify capitalization with client library and align both setup
configurations.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
2024-09-29 02:34:49 +02:00
49a39e7970
Add client library
Initial code for a client library.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
2024-09-29 02:33:01 +02:00
1e84b2e57d
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>
2024-09-29 02:23:30 +02:00
29 changed files with 242 additions and 82 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
__pycache__
dist
nftables_api.egg-info
nftables_api*.egg-info
venv

View File

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

1
nft_http_api_client/LICENSE Symbolic link
View File

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

View File

@ -0,0 +1 @@
# RESTful HTTP API for nftables - Client

View File

@ -0,0 +1,11 @@
"""
A RESTful HTTP API client 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 .__version__ import __version__ as __version__

View File

@ -0,0 +1,11 @@
"""
A RESTful HTTP API client 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.
"""
__version__ = '0.0.1'

View File

@ -0,0 +1,46 @@
"""
A RESTful HTTP API client 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 os import getenv
import urllib3
class NftablesRemote:
def __init__(self, endpoint, token=None):
if token is None:
token = getenv('NFT-API-TOKEN')
if token is None:
raise ValueError('Missing token, pass one as an argument or via NFT-API-TOKEN.')
self.endpoint = endpoint
self.auth_headers = {
'X-NFT-API-Token': token,
}
def get(self, path):
retmap = {
'status': -1,
'data': {},
}
response = urllib3.request(
'GET',
f'{self.endpoint}/{path}',
headers=self.auth_headers,
)
retmap['status'] = response.status
retmap['data'] = response.json()
return retmap

View File

@ -0,0 +1,49 @@
[build-system]
requires = ['setuptools', 'wheel']
build-backend = 'setuptools.build_meta'
[project]
name = 'nftables_api-client'
description = 'RESTful HTTP API for nftables (client)'
dynamic = ['license', 'readme', 'version']
authors = [
{ name='Georg Pfuetzenreuter', email='georg+python@lysergic.dev' },
]
classifiers = [
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Topic :: Software Development',
'Typing :: Typed',
'Operating System :: POSIX :: Linux',
]
requires-python = '>=3.6'
dependencies = [
'urllib3',
]
[project.optional-dependencies]
dev = [
'pytest',
'ruff',
]
[tool.setuptools]
include-package-data = false
[tool.setuptools.dynamic]
version = {attr = 'nftables_api_client.__version__'}
readme = {file = ['README.md']}
[tool.setuptools.packages.find]
where = ['.']

View File

@ -0,0 +1,17 @@
[metadata]
name = nftables_api-client
version = attr: nftables_api_client.__version__
author = Georg Pfuetzenreuter
author_email = georg+python@lysergic.dev
description = RESTful HTTP API for nftables (Client)
license = EUPL-1.2
long_description = file: README.md, LICENSE
[options]
package_dir=
=.
packages=find:
python_requires = >=3.6
[options.packages.find]
where=.

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.
"""
from waitress import serve
from nftables_api.app import app
from waitress import serve
if __name__ == '__main__':
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]
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']

View File

@ -1,9 +1,9 @@
[metadata]
name = nftables_api
name = nftables_api-server
version = attr: nftables_api.__version__
author = Georg Pfuetzenreuter
author_email = georg+python@lysergic.dev
description = RESTful HTTP API for nftables
description = RESTful HTTP API for nftables (Server)
license = EUPL-1.2
long_description = file: README.md, LICENSE
@ -14,8 +14,4 @@ packages=find:
python_requires = >=3.6
[options.packages.find]
exclude=
scripts*
tests*
*.toml
where=.

View File

@ -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

View File

@ -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

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 nftables import Nftables
from pytest import exit, fixture
from nftables_api.app import app
from pytest import exit, fixture
def run_nft(nft, cmd):