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:
parent
75c2411dd2
commit
a47ee638f1
@ -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 waitress import serve
|
||||||
|
|
||||||
from nftables_api.app import app
|
from nftables_api.app import app
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -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.
|
||||||
|
"""
|
@ -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 falcon import App
|
||||||
from .resources import nft_set
|
|
||||||
from .config import config
|
|
||||||
from nftables_api.middlewares.authentication import AuthMiddleWare
|
from nftables_api.middlewares.authentication import AuthMiddleWare
|
||||||
|
|
||||||
|
from .resources import nft_set
|
||||||
|
|
||||||
app = App(
|
app = App(
|
||||||
middleware=[
|
middleware=[
|
||||||
AuthMiddleWare(),
|
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)
|
||||||
|
@ -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 os import getenv
|
||||||
|
|
||||||
|
from yaml import safe_load
|
||||||
|
|
||||||
configpath = getenv('NFT-API-CONFIG')
|
configpath = getenv('NFT-API-CONFIG')
|
||||||
if not configpath:
|
if not configpath:
|
||||||
raise RuntimeError('NFT-API-CONFIG is not set')
|
raise RuntimeError('NFT-API-CONFIG is not set')
|
||||||
|
9
nftables_api/middlewares/__init__.py
Normal file
9
nftables_api/middlewares/__init__.py
Normal 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.
|
||||||
|
"""
|
@ -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 bcrypt import checkpw
|
||||||
from falcon import HTTPUnauthorized
|
from falcon import HTTPUnauthorized
|
||||||
|
|
||||||
from nftables_api.config import config
|
from nftables_api.config import config
|
||||||
|
|
||||||
|
|
||||||
class AuthMiddleWare:
|
class AuthMiddleWare:
|
||||||
def _match(self, token_plain, token_hashed):
|
def _match(self, token_plain, token_hashed):
|
||||||
"""
|
"""
|
||||||
@ -23,7 +35,7 @@ class AuthMiddleWare:
|
|||||||
return False
|
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
|
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
|
Fully validate whether a token is authorized to perform the request
|
||||||
"""
|
"""
|
||||||
@ -51,13 +63,15 @@ class AuthMiddleWare:
|
|||||||
if not self._match(token, config_token):
|
if not self._match(token, config_token):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for config_path, methods in config_paths.items():
|
for got_config_path, methods in config_paths.items():
|
||||||
if not isinstance(methods, list):
|
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
|
# a leading slash causes an empty first list entry in the split
|
||||||
if config_path.startswith('/'):
|
if got_config_path.startswith('/'):
|
||||||
config_path = config_path[1:]
|
config_path = got_config_path[1:]
|
||||||
|
else:
|
||||||
|
config_path = got_config_path
|
||||||
|
|
||||||
path_elements = config_path.split('/')
|
path_elements = config_path.split('/')
|
||||||
|
|
||||||
@ -76,13 +90,13 @@ class AuthMiddleWare:
|
|||||||
break
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if req.method in methods:
|
if req.method not in methods:
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise HTTPUnauthorized(
|
raise HTTPUnauthorized(
|
||||||
title='Unauthorized method for path',
|
title='Unauthorized method for path',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
raise HTTPUnauthorized(
|
raise HTTPUnauthorized(
|
||||||
title='Unauthorized',
|
title='Unauthorized',
|
||||||
)
|
)
|
||||||
|
9
nftables_api/resources/__init__.py
Normal file
9
nftables_api/resources/__init__.py
Normal 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.
|
||||||
|
"""
|
@ -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 json
|
||||||
|
|
||||||
|
import falcon
|
||||||
from nftables import Nftables
|
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.output import output_post
|
||||||
|
from nftables_api.utils.parse import parse_nft_response
|
||||||
|
|
||||||
nft = Nftables()
|
nft = Nftables()
|
||||||
nft.set_json_output(True)
|
nft.set_json_output(True)
|
||||||
@ -47,7 +59,7 @@ class SetResource:
|
|||||||
response.text = json.dumps({'status': status, 'error': err_parsed})
|
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)
|
raw = request.get_param_as_bool('raw', default=False)
|
||||||
data = request.get_media()
|
data = request.get_media()
|
||||||
|
|
||||||
@ -73,7 +85,7 @@ class SetResource:
|
|||||||
'prefix': {
|
'prefix': {
|
||||||
'addr': addrsplit[0],
|
'addr': addrsplit[0],
|
||||||
'len': int(addrsplit[1]),
|
'len': int(addrsplit[1]),
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -88,19 +100,19 @@ class SetResource:
|
|||||||
'family': xfamily,
|
'family': xfamily,
|
||||||
'name': xset,
|
'name': xset,
|
||||||
'table': xtable,
|
'table': xtable,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
if not nft.json_validate(nft_payload):
|
if not nft.json_validate(nft_payload):
|
||||||
response.status = falcon.HTTP_BAD_REQUEST
|
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
|
return
|
||||||
|
|
||||||
rc, out, err = nft.json_cmd(nft_payload)
|
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:
|
if status is True:
|
||||||
response.status = falcon.HTTP_CREATED
|
response.status = falcon.HTTP_CREATED
|
||||||
|
9
nftables_api/utils/__init__.py
Normal file
9
nftables_api/utils/__init__.py
Normal 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.
|
||||||
|
"""
|
@ -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
|
import json
|
||||||
|
|
||||||
|
|
||||||
def output_post(status, message=None):
|
def output_post(status, message=None):
|
||||||
output = {
|
output = {
|
||||||
'status': status
|
'status': status,
|
||||||
}
|
}
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
output.update(
|
output.update(
|
||||||
{
|
{
|
||||||
'message': message
|
'message': message,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return json.dumps(output)
|
return json.dumps(output)
|
||||||
|
@ -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
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
50
ruff.toml
Normal file
50
ruff.toml
Normal 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
|
@ -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 falcon import testing
|
||||||
from pytest import exit, fixture
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def run_nft(nft, cmd):
|
def run_nft(nft, cmd):
|
||||||
rc, out, err = nft.cmd(cmd)
|
rc, out, err = nft.cmd(cmd)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
|
@ -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 json import dumps, loads
|
||||||
|
|
||||||
|
from falcon import HTTP_CREATED, HTTP_OK
|
||||||
|
from pytest import mark
|
||||||
|
|
||||||
vs = [4, 6]
|
vs = [4, 6]
|
||||||
|
|
||||||
@mark.parametrize('v', vs)
|
@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 = {
|
want_out = {
|
||||||
4: ["192.168.0.0/24", "127.0.0.1"],
|
4: ["192.168.0.0/24", "127.0.0.1"],
|
||||||
6: ["fd80::/64", "fe80::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
|
nft = nft_ruleset_populated_sets
|
||||||
|
|
||||||
# all the matrixes could be moved to parameters
|
# 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 plformat == 'string':
|
||||||
if plvariant == 'address':
|
if plvariant == 'address':
|
||||||
to_add = {
|
to_add = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user