Code style, license headers

Apply my common Python code and header style as also found in other
projects.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
This commit is contained in:
Georg Pfuetzenreuter 2024-09-28 16:57:21 +02:00
parent 75c2411dd2
commit a47ee638f1
Signed by: Georg
GPG Key ID: 1ED2F138E7E6FF57
14 changed files with 220 additions and 37 deletions

View File

@ -1,4 +1,15 @@
"""
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
if __name__ == '__main__':

View File

@ -0,0 +1,9 @@
"""
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.
"""

View File

@ -1,14 +1,25 @@
"""
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 falcon import App
from .resources import nft_set
from .config import config
from nftables_api.middlewares.authentication import AuthMiddleWare
from .resources import nft_set
app = App(
middleware=[
AuthMiddleWare(),
]
],
)
rSet = nft_set.SetResource()
rset = nft_set.SetResource()
app.add_route('/set/{xfamily}/{xtable}/{xset}', rSet)
app.add_route('/set/{xfamily}/{xtable}/{xset}', rset)

View File

@ -1,6 +1,17 @@
from yaml import safe_load
"""
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 os import getenv
from yaml import safe_load
configpath = getenv('NFT-API-CONFIG')
if not configpath:
raise RuntimeError('NFT-API-CONFIG is not set')

View File

@ -0,0 +1,9 @@
"""
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.
"""

View File

@ -1,7 +1,19 @@
"""
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 bcrypt import checkpw
from falcon import HTTPUnauthorized
from nftables_api.config import config
class AuthMiddleWare:
def _match(self, token_plain, token_hashed):
"""
@ -23,7 +35,7 @@ class AuthMiddleWare:
return False
def process_request(self, req, resp):
def process_request(self, req, resp): # noqa ARG002, resp is not used but needs to be passed by Falcon
"""
Rudimentary token validation - check if it is worth walking further down the authorization chain
"""
@ -40,7 +52,7 @@ class AuthMiddleWare:
)
def process_resource(self, req, resp, resource, params):
def process_resource(self, req, resp, resource, params): # noqa ARG002, resp is not used but needs to be passed by Falcon
"""
Fully validate whether a token is authorized to perform the request
"""
@ -51,13 +63,15 @@ class AuthMiddleWare:
if not self._match(token, config_token):
continue
for config_path, methods in config_paths.items():
for got_config_path, methods in config_paths.items():
if not isinstance(methods, list):
raise RuntimeError(f'Invalid method configured for path {config_path}')
raise RuntimeError(f'Invalid method configured for path {got_config_path}')
# a leading slash causes an empty first list entry in the split
if config_path.startswith('/'):
config_path = config_path[1:]
if got_config_path.startswith('/'):
config_path = got_config_path[1:]
else:
config_path = got_config_path
path_elements = config_path.split('/')
@ -76,13 +90,13 @@ class AuthMiddleWare:
break
else:
if req.method in methods:
return
else:
if req.method not in methods:
raise HTTPUnauthorized(
title='Unauthorized method for path',
)
return
raise HTTPUnauthorized(
title='Unauthorized',
)

View File

@ -0,0 +1,9 @@
"""
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.
"""

View File

@ -1,8 +1,20 @@
import falcon
"""
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.
"""
import json
import falcon
from nftables import Nftables
from nftables_api.utils.parse import parse_nft_response
from nftables_api.utils.output import output_post
from nftables_api.utils.parse import parse_nft_response
nft = Nftables()
nft.set_json_output(True)
@ -47,7 +59,7 @@ class SetResource:
response.text = json.dumps({'status': status, 'error': err_parsed})
def on_post(self, request, response, xfamily, xtable, xset):
def on_post(self, request, response, xfamily, xtable, xset): # noqa PLR0912, todo: consider moving some of the logic to smaller functions
raw = request.get_param_as_bool('raw', default=False)
data = request.get_media()
@ -73,7 +85,7 @@ class SetResource:
'prefix': {
'addr': addrsplit[0],
'len': int(addrsplit[1]),
}
},
})
else:
@ -88,19 +100,19 @@ class SetResource:
'family': xfamily,
'name': xset,
'table': xtable,
}
}
}
]
},
},
},
],
}
if not nft.json_validate(nft_payload):
response.status = falcon.HTTP_BAD_REQUEST
response.text = output_post(False, 'Payload did not validate.')
response.text = output_post(status=False, message='Payload did not validate.')
return
rc, out, err = nft.json_cmd(nft_payload)
out_parsed, status, err_parsed = parse_nft_response(rc, out, err, raw)
_, status, err_parsed = parse_nft_response(rc, out, err, raw)
if status is True:
response.status = falcon.HTTP_CREATED

View File

@ -0,0 +1,9 @@
"""
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.
"""

View File

@ -1,15 +1,26 @@
"""
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.
"""
import json
def output_post(status, message=None):
output = {
'status': status
'status': status,
}
if message:
output.update(
{
'message': message
}
'message': message,
},
)
return json.dumps(output)

View File

@ -1,3 +1,13 @@
"""
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.
"""
import json

50
ruff.toml Normal file
View File

@ -0,0 +1,50 @@
[lint]
# https://docs.astral.sh/ruff/rules/
extend-select = [
"A", # flake8-builtins
"ARG", # flake8-unused-arguments
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"COM", # flake8-commas
"CPY001",# flake8-copyright
"E", # pycodestyle
"E261", # spaces before inline comments
"ERA", # eradicate
"EXE", # flake8-executable
"FBT", # flake8-boolean-trap
"I", # isort
"INP", # flake8-no-pep420
"ISC", # flake8-implicit-str-concat
"N", # pep8-naming
"PL", # Pylint
"RET", # flake8-return
"RSE", # flake8-raise
"RUF", # Ruff-specific rules
"S", # flake8-bandit
"SIM", # flake8-simplify
"T20", # flake8-print
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
]
ignore = [
"E501", # line lengths
"FBT002", # booleans as function arguments
"S603", # https://github.com/astral-sh/ruff/issues/4045
"S607", # makes subprocess calls in test suite more portable
]
preview = true
explicit-preview-rules = true
[lint.per-file-ignores]
"tests/*.py" = [
"INP001", # tests do not need to be part of a package
"S101", # allow "assert" in test suites
"T201", # lazy printing is ok in tests
]
[lint.pydocstyle]
convention = "pep257"
[lint.isort]
force-wrap-aliases = true

View File

@ -1,9 +1,20 @@
"""
Tests for the 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 falcon import testing
from pytest import exit, fixture
from nftables import Nftables
from pytest import exit, fixture
from nftables_api.app import app
def run_nft(nft, cmd):
rc, out, err = nft.cmd(cmd)
if rc != 0:

View File

@ -1,11 +1,22 @@
from pytest import mark
from falcon import HTTP_CREATED, HTTP_OK
"""
Tests for the 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 json import dumps, loads
from falcon import HTTP_CREATED, HTTP_OK
from pytest import mark
vs = [4, 6]
@mark.parametrize('v', vs)
def test_get_set(client, nft_ruleset_populated_sets, v):
def test_get_set(client, nft_ruleset_populated_sets, v): # noqa ARG001, nft is not needed here
want_out = {
4: ["192.168.0.0/24", "127.0.0.1"],
6: ["fd80::/64", "fe80::1"],
@ -22,11 +33,6 @@ def test_append_to_set(client, nft_ruleset_populated_sets, v, plvariant, plforma
nft = nft_ruleset_populated_sets
# all the matrixes could be moved to parameters
want_out = {
4: ["192.168.0.0/24", "127.0.0.1", "192.168.5.0/26"],
6: ["fd80::/64", "fe80::1", "fd10:f00::/128"],
}
if plformat == 'string':
if plvariant == 'address':
to_add = {