upgrade_server: Implement basic permission and multi user support

This commit is contained in:
deajan 2025-01-17 00:24:53 +01:00
parent 321fafa8af
commit 76f591d943
2 changed files with 80 additions and 16 deletions

View file

@ -3,18 +3,23 @@
http_server:
listen: 0.0.0.0
port: 8080
username: upgrade_client
password: super_secret_password
users:
- upgrade_client:
password: super_secret_password
permissions:
- audience:
- private
- public
upgrades:
# Build dir should contain the following structure
# /VERSION
# VERSION is a file containing a single line with the currently built NPBackup version, example: 2.2.0
# /{platform}/{arch}/{binary}
# /{platform}/{arch}/{binary}/{audience}
# Current platforms are 'windows', 'linux'
# Current arches are 'x64', 'x86', 'arm' and 'arm64'
# In each folder there should be a npbackup or npbackup.exe binary depending on the platform
data_root: /var/www/upgrade_server/dist
data_root: /var/npbackup_upgrade_server/dist
# We'll store a CSV containing backup clients that upgrade here
statistics_file: /var/www/upgrade_server/stats.csv
statistics_file: /var/npbackup_upgrade_server/stats.csv

View file

@ -60,17 +60,20 @@ security = HTTPBasic()
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = config_dict["http_server"]["username"].encode("utf-8")
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = config_dict["http_server"]["password"].encode("utf-8")
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
authenticated_user = None
for user in config_dict["http_server"]["users"]:
try:
if secrets.compare_digsest(credentials.username.encode("utf-8"), user.encode("utf-8")):
if secrets.compare_digest(credentials.password.encode("utf-8"), config_dict["http_server"]["users"]["user"]["password"].encode("utf-8")):
authenticated_user = user
break
except Exception as exc:
logger.info(f"Failed to check user: {exc}")
if authenticated_user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
@ -79,6 +82,17 @@ def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
return credentials.username
def get_user_permissions(username: str):
"""
Returns a list of permissions
"""
try:
return config_dict["http_server"]["users"][username]["permissions"]
except Exception as exc:
logger.error(f"Failed to get user permissions: {exc}")
return []
@app.get("/")
async def api_root(auth=Depends(get_current_username)):
if crud.is_enabled():
@ -139,9 +153,18 @@ async def current_version(
client_ip = x_forwarded_for
else:
client_ip = request.client.host
try:
has_permission = True if audience.value in get_user_permissions(auth)["audience"] else False
except Exception as exc:
logger.error(f"Failed to get user permissions: {exc}")
has_permission = False
data = {
"action": "check_version",
"ip": client_ip,
"user": auth,
"has_permission": has_permission,
"auto_upgrade_host_identity": auto_upgrade_host_identity,
"installed_version": installed_version,
"group": group,
@ -156,6 +179,12 @@ async def current_version(
except KeyError:
logger.error("No statistics file set.")
if not has_permission:
raise HTTPException(
status_code=403,
detail="User does not have permission to access this resource",
)
if not crud.is_enabled():
return CurrentVersion(version="0.00")
@ -222,9 +251,18 @@ async def upgrades(
client_ip = x_forwarded_for
else:
client_ip = request.client.host
try:
has_permission = True if audience.value in get_user_permissions(auth)["audience"] else False
except Exception as exc:
logger.error(f"Failed to get user permissions: {exc}")
has_permission = False
data = {
"action": "get_file_info",
"ip": client_ip,
"user": auth,
"has_permission": has_permission,
"auto_upgrade_host_identity": auto_upgrade_host_identity,
"installed_version": installed_version,
"group": group,
@ -239,6 +277,12 @@ async def upgrades(
except KeyError:
logger.error("No statistics file set.")
if not has_permission:
raise HTTPException(
status_code=403,
detail="User does not have permission to access this resource",
)
if not crud.is_enabled():
raise HTTPException(
status_code=503, detail="Service is currently disabled for maintenance"
@ -307,9 +351,18 @@ async def download(
client_ip = x_forwarded_for
else:
client_ip = request.client.host
try:
has_permission = True if audience.value in get_user_permissions(auth)["audience"] else False
except Exception as exc:
logger.error(f"Failed to get user permissions: {exc}")
has_permission = False
data = {
"action": "download_upgrade",
"ip": client_ip,
"user": auth,
"has_permission": has_permission,
"auto_upgrade_host_identity": auto_upgrade_host_identity,
"installed_version": installed_version,
"group": group,
@ -324,6 +377,12 @@ async def download(
except KeyError:
logger.error("No statistics file set.")
if not has_permission:
raise HTTPException(
status_code=403,
detail="User does not have permission to access this resource",
)
if not crud.is_enabled():
raise HTTPException(
status_code=503, detail="Service is currently disabled for maintenance"