mirror of
https://github.com/vmario89/fabaccess-users-toml-validator.git
synced 2025-03-11 14:31:42 +01:00
209 lines
8.4 KiB
Python
Executable File
209 lines
8.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
'''
|
|
This script validates users.toml for several aspects
|
|
The script requires at least Python 3.11
|
|
|
|
Written by Mario Voigt (vmario89) - Stadtfabrikanten e.V. - 2024
|
|
|
|
ToDos
|
|
- enter bffh.dhall path to check roles against users.toml. If our toml contains roles, which bffh does not know, we should also warn!
|
|
'''
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import tomllib
|
|
import uuid
|
|
|
|
'''
|
|
cardkeys for FabAccess use Uuid format in Version v4 (see https://docs.rs/uuid/latest/uuid/struct.Uuid.html)
|
|
allowed formattings:
|
|
- simple: a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8
|
|
- hyphenated: a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8
|
|
- urn: urn:uuid:A1A2A3A4-B1B2-C1C2-D1D2-D3D4D5D6D7D8
|
|
- braced: {a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8}
|
|
'''
|
|
def is_valid_uuid(val):
|
|
try:
|
|
_uuid = uuid.UUID(val, version=4)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--db", type=str, help="path of users.toml user database file")
|
|
args = parser.parse_args()
|
|
|
|
if args.db is None:
|
|
usertoml = args.db
|
|
print("No custom users.toml given. You may add it with '--db </path/to/users.toml>'")
|
|
default_usertoml = "/etc/bffh/users.toml"
|
|
if os.path.isfile(default_usertoml):
|
|
print("Found default file: {}. Using this ...".format(default_usertoml))
|
|
usertoml = default_usertoml
|
|
else:
|
|
print("Error: no (default) users.toml file given or found. Cannot continue!".format(default_usertoml))
|
|
sys.exit(1)
|
|
|
|
countUsers = 0
|
|
countUsersWithoutCardkeyOrPassword = 0
|
|
uniqueRoles = []
|
|
countUserWithoutRoles = 0
|
|
countUserWithDuplicateRoles = 0
|
|
countPassword = 0
|
|
countPasswordUnencrypted = 0
|
|
countPasswordEncrypted = 0
|
|
countPasswordDuplicates = 0
|
|
countCardkey = 0
|
|
countCardkeyInvalid = 0
|
|
countCardkeyDuplicates = 0
|
|
countUnknownKeys = 0
|
|
|
|
countWarnings = 0
|
|
|
|
#a definition of valid keys within a user section of FabAccess
|
|
knownKeys = ['roles', 'passwd', 'cardkey']
|
|
|
|
print("{} Checking database {}\n".format("*"*25, "*"*25))
|
|
|
|
file_stats = os.stat(usertoml)
|
|
#print(file_stats)
|
|
print("Database size: {} Bytes ({:0.5f} MB)".format(file_stats.st_size, file_stats.st_size / (1024 * 1024)))
|
|
if file_stats.st_size == 0:
|
|
print("Error: File size is zero! Database is corrupted!")
|
|
sys.exit(1)
|
|
|
|
print("\n")
|
|
|
|
with open(usertoml, "rb") as f:
|
|
try:
|
|
data = tomllib.load(f)
|
|
except Exception as e:
|
|
if "Cannot declare" in str(e) and "twice" in str(e):
|
|
print("Error: found at least one duplicate user. Cannot parse database. Please fix and try again. Message: {}".format(str(e)))
|
|
elif "Invalid value" in str(e):
|
|
print("Error: Some user contains a key without value (e.g. 'passwd = '). Cannot parse database. Please fix and try again. Message: {}".format(str(e)))
|
|
elif "Expected '=' after a key" in str(e):
|
|
print("Error: Found an incorrect key/value mapping. Cannot parse database. Please fix and try again. Message: {}".format(str(e)))
|
|
else:
|
|
print(str(e))
|
|
sys.exit(1)
|
|
|
|
passwds = []
|
|
cardkeys = []
|
|
|
|
for user in data:
|
|
print("--- {}".format(user))
|
|
|
|
for key in data[user].keys():
|
|
if key not in knownKeys:
|
|
print("Warning: User '{}' contains unknown key '{}' (will be ignored by BFFH server)".format(user, key))
|
|
countWarnings += 1
|
|
countUnknownKeys += 1
|
|
|
|
if "roles" in data[user]:
|
|
roles = data[user]["roles"]
|
|
if type(roles) != list:
|
|
print("Warning: roles for user '{}' are not defined as array! BFFH will fail to load".format(user))
|
|
countWarnings += 1
|
|
userRoles = []
|
|
for role in roles:
|
|
# check if a role is duplicate in the array for the user
|
|
if role not in userRoles:
|
|
userRoles.append(role)
|
|
else:
|
|
print("Warning: duplicate role '{}' for user '{}'".format(role, user))
|
|
countUserWithDuplicateRoles += 1
|
|
countWarnings += 1
|
|
# collect all unique roles for the toml file
|
|
if role not in uniqueRoles:
|
|
uniqueRoles.append(role)
|
|
if roles is None: #if role key is defined but empty
|
|
countUserWithoutRoles += 1
|
|
else: #if role key is not existent
|
|
countUserWithoutRoles += 1
|
|
|
|
if "passwd" in data[user]:
|
|
passwd = data[user]["passwd"]
|
|
countPassword += 1
|
|
if type(passwd) != str:
|
|
print("Warning: password for user '{}' is not defined as string! BFFH will fail to load".format(user))
|
|
countWarnings += 1
|
|
elif passwd.startswith("$argon2") is False:
|
|
print("Warning: Password for user '{}' is not encrypted!".format(user))
|
|
countWarnings += 1
|
|
countPasswordUnencrypted += 1
|
|
else:
|
|
countPasswordEncrypted += 1
|
|
if passwd in passwds:
|
|
print("Warning: password for user '{}' is already in use by other user(s). That might be insecure".format(user))
|
|
countPasswordDuplicates += 1
|
|
countWarnings += 1
|
|
passwds.append(passwd)
|
|
|
|
if "cardkey" in data[user]:
|
|
cardkey = data[user]["cardkey"]
|
|
if type(passwd) != str:
|
|
print("Warning: cardkey for user '{}' is not defined as string! BFFH will fail to load".format(user))
|
|
countWarnings += 1
|
|
elif is_valid_uuid(cardkey) is False:
|
|
print("Warning: cardkey for user '{}' contains invalid cardkey (no UUID v4)".format(user))
|
|
countCardkeyInvalid += 1
|
|
countWarnings += 1
|
|
if cardkey in cardkeys:
|
|
print("Warning: cardkey for user '{}' is already in use by other user(s). That might be insecure".format(user))
|
|
countCardkeyDuplicates += 1
|
|
countWarnings += 1
|
|
|
|
cardkeys.append(cardkey)
|
|
|
|
countCardkey += 1
|
|
|
|
if "passwd" not in data[user] and "cardkey" not in data[user]:
|
|
countUsersWithoutCardkeyOrPassword += 1
|
|
|
|
countUsers += 1
|
|
print("\n")
|
|
|
|
print("\n")
|
|
|
|
if countUsers == 0:
|
|
print("Error: Database does not contain any users!")
|
|
sys.exit(1)
|
|
|
|
print("{} Database statistics {}\n".format("*"*25, "*"*25))
|
|
print("- Total users: {}".format(countUsers))
|
|
print("- Total unique roles: {}".format(len(uniqueRoles)))
|
|
print("- Total passwords: {} (encrypted: {}, unencrypted: {}, duplicates: {})".format(countPassword, countPasswordEncrypted, countPasswordUnencrypted, countPasswordDuplicates))
|
|
print("- Total cardkeys: {} (duplicates: {})".format(countCardkey, countCardkeyDuplicates))
|
|
|
|
print("\n")
|
|
|
|
print("{} Important information {}\n".format("*"*25, "*"*25))
|
|
if countUnknownKeys > 0:
|
|
print("- {} unknown keys (will be ignored by BFFH server)".format(countUnknownKeys))
|
|
|
|
if countUserWithoutRoles > 0:
|
|
print("- {} users without any roles. They won't be able to do something as client!".format(countUserWithoutRoles))
|
|
|
|
if countUserWithDuplicateRoles > 0:
|
|
print("- {} users with duplicate roles. Please clean up!".format(countUserWithDuplicateRoles))
|
|
|
|
if len(uniqueRoles) == 0:
|
|
print("- Globally, there are no roles assigned for any user. They won't be able to do something as client!")
|
|
|
|
if countCardkeyInvalid > 0:
|
|
print("- {} invalid cardkeys in your database. They won't be able to authenticate at BFFH server by keycard!".format(countCardkeyInvalid))
|
|
|
|
if countUsersWithoutCardkeyOrPassword > 0:
|
|
print("- {} users without both: password and cardkey. They won't be able to login anyhow!".format(countUsersWithoutCardkeyOrPassword))
|
|
|
|
if countWarnings > 0:
|
|
print("- {} warnings in total. You might need to optimize your user database!".format(countWarnings))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|