Implement authorization
Read a configuration file mapping bcrypt hashed tokens to authorized paths and methods to decide whether a request should be pursued. Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
This commit is contained in:
parent
218c9122d1
commit
75c2411dd2
@ -1,7 +1,13 @@
|
||||
from falcon import App
|
||||
from .resources import nft_set
|
||||
from .config import config
|
||||
from nftables_api.middlewares.authentication import AuthMiddleWare
|
||||
|
||||
app = App()
|
||||
app = App(
|
||||
middleware=[
|
||||
AuthMiddleWare(),
|
||||
]
|
||||
)
|
||||
|
||||
rSet = nft_set.SetResource()
|
||||
|
||||
|
14
nftables_api/config.py
Normal file
14
nftables_api/config.py
Normal file
@ -0,0 +1,14 @@
|
||||
from yaml import safe_load
|
||||
from os import getenv
|
||||
|
||||
configpath = getenv('NFT-API-CONFIG')
|
||||
if not configpath:
|
||||
raise RuntimeError('NFT-API-CONFIG is not set')
|
||||
|
||||
with open(configpath) as fh:
|
||||
configdata = safe_load(fh)
|
||||
|
||||
config = configdata.get('nft-api', {})
|
||||
|
||||
if not config:
|
||||
raise RuntimeError('Invalid configuration data')
|
88
nftables_api/middlewares/authentication.py
Normal file
88
nftables_api/middlewares/authentication.py
Normal file
@ -0,0 +1,88 @@
|
||||
from bcrypt import checkpw
|
||||
from falcon import HTTPUnauthorized
|
||||
from nftables_api.config import config
|
||||
|
||||
class AuthMiddleWare:
|
||||
def _match(self, token_plain, token_hashed):
|
||||
"""
|
||||
Check plain token against bcrypt hash
|
||||
"""
|
||||
try:
|
||||
return checkpw(token_plain.encode(), token_hashed.encode())
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def _valid(self, token):
|
||||
"""
|
||||
Check if token is contained in the configuration
|
||||
"""
|
||||
for config_token, config_paths in config.get('tokens', {}).items():
|
||||
if self._match(token, config_token):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def process_request(self, req, resp):
|
||||
"""
|
||||
Rudimentary token validation - check if it is worth walking further down the authorization chain
|
||||
"""
|
||||
token = req.get_header('X-NFT-API-TOKEN')
|
||||
|
||||
if token is None:
|
||||
raise HTTPUnauthorized(
|
||||
title='Authentication required',
|
||||
)
|
||||
|
||||
if not self._valid(token):
|
||||
raise HTTPUnauthorized(
|
||||
title='Unauthorized',
|
||||
)
|
||||
|
||||
|
||||
def process_resource(self, req, resp, resource, params):
|
||||
"""
|
||||
Fully validate whether a token is authorized to perform the request
|
||||
"""
|
||||
token = req.get_header('X-NFT-API-TOKEN')
|
||||
resource_name = resource._name()
|
||||
|
||||
for config_token, config_paths in config.get('tokens', {}).items():
|
||||
if not self._match(token, config_token):
|
||||
continue
|
||||
|
||||
for config_path, methods in config_paths.items():
|
||||
if not isinstance(methods, list):
|
||||
raise RuntimeError(f'Invalid method configured for path {config_path}')
|
||||
|
||||
# a leading slash causes an empty first list entry in the split
|
||||
if config_path.startswith('/'):
|
||||
config_path = config_path[1:]
|
||||
|
||||
path_elements = config_path.split('/')
|
||||
|
||||
if path_elements[0] != resource_name:
|
||||
continue
|
||||
|
||||
path_elements = path_elements[1:]
|
||||
|
||||
if resource_name == 'set':
|
||||
need_elements = ['xfamily', 'xtable', 'xset']
|
||||
|
||||
for i, need_element in enumerate(need_elements):
|
||||
if path_elements[i] == '*':
|
||||
continue
|
||||
if path_elements[i] != params.get(need_element):
|
||||
break
|
||||
|
||||
else:
|
||||
if req.method in methods:
|
||||
return
|
||||
else:
|
||||
raise HTTPUnauthorized(
|
||||
title='Unauthorized method for path',
|
||||
)
|
||||
|
||||
raise HTTPUnauthorized(
|
||||
title='Unauthorized',
|
||||
)
|
@ -8,6 +8,9 @@ nft = Nftables()
|
||||
nft.set_json_output(True)
|
||||
|
||||
class SetResource:
|
||||
def _name(self):
|
||||
return 'set'
|
||||
|
||||
def on_get(self, request, response, xfamily, xtable, xset):
|
||||
raw = request.get_param_as_bool('raw', default=False)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user