added option to read flask SECRET_KEY and SECURITY_PASSWORD_SALT from file

This commit is contained in:
Simon Pirkelmann 2021-04-10 13:46:56 +02:00
parent db8ee556df
commit 8339294277
2 changed files with 40 additions and 17 deletions

View File

@ -4,16 +4,17 @@ import argparse
from imaginaerraum_door_admin.webapp import create_application
parser = argparse.ArgumentParser()
parser.add_argument("--key_file", default='/root/flask_keys', help="Path to file with Flask SECRET_KEY and SECURITY_PASSWORD_SALT")
parser.add_argument("--token_file", default="/etc/door_tokens", help="path to the file with door tokens and users")
parser.add_argument("--nfc_socket", default="/tmp/nfc.sock", help="socket for handling NFC reader commands")
parser.add_argument("--template_folder", default="templates", help="path to Flask templates folder")
parser.add_argument("--static_folder", default="static", help="path to Flask static folder")
parser.add_argument("--admin_file", help="Path to file for creating initial admin users")
parser.add_argument("--admin_file", help="Path to file for creating super admin users")
parser.add_argument("--log_file", default="/var/log/webinterface.log", help="Path to log file")
parser.add_argument("--ldap_url", default="ldaps://ldap.imaginaerraum.de",
help="URL for LDAP server for alternative user authorization")
parser.add_argument("--mqtt_host", default="10.10.21.2", help="IP address of MQTT broker")
parser.add_argument("--port", default=80, help="Port for running the Flask server")
parser.add_argument("--flask_port", default=80, help="Port for running the Flask server")
parser.add_argument("--mail_server", default="smtp.googlemail.com", help="email server for sending security messages")
parser.add_argument("--mail_port", default=465, help="port for security email server")
parser.add_argument("--mail_use_tls", default=False, help="use TLS for security emails")
@ -23,4 +24,4 @@ parser.add_argument("--mail_password", default="password", help="Password for em
config = parser.parse_args()
app = create_application(config)
app.run(host='0.0.0.0', port=config.port)
app.run(host='0.0.0.0', port=config.flask_port)

View File

@ -1,4 +1,3 @@
import os
from flask import Flask, render_template, request, flash, redirect, session, send_file
from werkzeug.utils import secure_filename
from flask_wtf import FlaskForm
@ -10,9 +9,9 @@ from flask_security import Security, SQLAlchemyUserDatastore, auth_required, has
current_user, roles_required
from flask_security.models import fsqla_v2 as fsqla
from flask_security.forms import LoginForm, Required, PasswordField
from flask_security.utils import find_user, verify_password
from flask_security.utils import find_user
from flask_mail import Mail
from email_validator import validate_email, EmailNotValidError
from email_validator import validate_email
import json
import secrets
@ -83,12 +82,36 @@ def create_application(config):
logger.addHandler(ch)
# do some checks for file existence etc.
try:
with open(config.key_file) as f:
data = f.readlines()
if 'SECRET_KEY' in data[0]:
secret_key = data[0].split()[-1]
else:
raise Exception("Could not read SECURITY_PASSWORD_SALT")
if 'SECURITY_PASSWORD_SALT' in data[1]:
security_password_salt = data[1].split()[-1]
else:
raise Exception("Could not read SECURITY_PASSWORD_SALT")
except Exception as e:
logger.warning(f"Flask keys could not be read from file at {Path(config.key_file).absolute()}. Exception: {e}. Using default values instead.")
secret_key = 'Q7PJu2fg2jabYwP-Psop6c6f2G4'
security_password_salt = '10036796768252925167749545152988277953'
if Path(config.template_folder).is_absolute():
if not Path(config.template_folder).exists():
logger.error(f'Flask template folder not found at {config.template_folder}')
logger.error(f'Flask template folder not found at {Path(config.template_folder).absolute()}')
else:
if not (Path(__file__).parent / config.template_folder).exists():
logger.error(f'Flask template folder not found at {(Path(__file__).parent / config.template_folder).absolute()}')
if Path(config.static_folder).is_absolute():
if not Path(config.static_folder).exists():
logger.error(f'Flask static folder not found at {config.static_folder}')
logger.error(f'Flask static folder not found at {Path(config.static_folder).absolute()}')
else:
if not (Path(__file__).parent / config.static_folder).exists():
logger.error(f'Flask static folder not found at {(Path(__file__).parent / config.static_folder).absolute()}')
if not Path(config.token_file).exists():
logger.warning(f"Token file not found at {config.token_file}")
logger.warning(f"Token file not found at {Path(config.token_file).absolute()}")
# create door objects which provides access to the token file and current door state via MQTT
door = DoorHandle(token_file=config.token_file, mqtt_host=config.mqtt_host, nfc_socket=config.nfc_socket,
@ -97,11 +120,10 @@ def create_application(config):
app = Flask(__name__, template_folder=config.template_folder, static_folder=config.static_folder)
# Generate a nice key using secrets.token_urlsafe()
app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'Q7PJu2fg2jabYwP-Psop6c6f2G4')
app.config['SECRET_KEY'] = secret_key
# Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt
# Generate a good salt using: secrets.SystemRandom().getrandbits(128)
app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT",
'10036796768252925167749545152988277953')
app.config['SECURITY_PASSWORD_SALT'] = security_password_salt
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = [
{"email": {"mapper": uia_email_mapper, "case_insensitive": True}},
@ -233,14 +255,14 @@ def create_application(config):
user_datastore.commit()
self.user = find_user(self.email.data)
logger.info(f"Admin user with credentials '{self.email.data}' authorized through LDAP")
logger.info(f"User with credentials '{self.email.data}' authorized through LDAP")
if not authorized:
# try authorizing locally using Flask security user datastore
authorized = super(ExtendedLoginForm, self).validate()
if authorized:
logger.info(f"Admin user with credentials '{self.email.data}' authorized through local database")
logger.info(f"User with credentials '{self.email.data}' authorized through local database")
# if any of the authorization methods is successful we authorize the user
return authorized
@ -668,7 +690,7 @@ def create_application(config):
for d in new_admin_data:
if user_datastore.find_user(email=d['email'], username=d['username']) is None:
logger.info(f"New admin user created with username '{d['username']}' and email '{d['email']}'")
logger.info(f"New super admin user created with username '{d['username']}' and email '{d['email']}'")
# create new admin (only if admin does not already exist)
new_admin = user_datastore.create_user(email=d['email'], username=d['username'],
password=hash_password(d['password']),