diff --git a/.gitignore b/.gitignore index eb626d5..c5c5775 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,4 @@ cython_debug/ inventory_export.csv config.py +.flask_secret_key diff --git a/app.py b/app.py index 8a86369..e0226b7 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,7 @@ # Validate configuration before starting the app from functions.validate_config import validate_config -from config import item_attributes, sql_conf, app_secret_key +from functions.secret_key import get_secret_key +from config import item_attributes, sql_conf config_status = validate_config(item_attributes) if config_status != "Ok": @@ -22,7 +23,7 @@ from routes.upload import upload_bp from routes.confirm_save import confirm_save_bp app = Flask(__name__) -app.secret_key = app_secret_key # Required for flashing messages and session +app.secret_key = get_secret_key() # Required for flashing messages and session app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql://{sql_conf.SQL_USER}:{sql_conf.SQL_PASSWORD}@{sql_conf.SQL_HOST}/{sql_conf.SQL_DB}' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app) diff --git a/config.sample.py b/config.sample.py index e40f03a..fcbcf6f 100644 --- a/config.sample.py +++ b/config.sample.py @@ -1,6 +1,5 @@ from definitions.attributes import * -app_secret_key = "test_phase_secret_key" # MySQL information class sql_conf: diff --git a/functions/secret_key.py b/functions/secret_key.py new file mode 100644 index 0000000..773ebac --- /dev/null +++ b/functions/secret_key.py @@ -0,0 +1,55 @@ +import os +import sys +from typing import Optional +import secrets +from warnings import warn + +def get_secret_key() -> str: + """ + Secure secret key loader with fail-safes for different environments. + Priority: + 1. Explicit environment variable (FLASK_SECRET_KEY) + 2. Secret file (./.flask_secret_key) + 3. Generated temporary key (development only) + + Returns: + str: The secret key + + Raises: + RuntimeError: In production if no key is configured + """ + # 1. Check environment variable first + if 'FLASK_SECRET_KEY' in os.environ: + if len(os.environ['FLASK_SECRET_KEY']) < 32: + warn('Weak secret key (min 32 chars recommended)') + return os.environ['FLASK_SECRET_KEY'] + + # 2. Check for secret file (Docker/K8s compatible) + secret_file = '.flask_secret_key' + if os.path.exists(secret_file): + with open(secret_file) as f: + key = f.read().strip() + if len(key) >= 32: + return key + warn(f'Weak key in {secret_file} (min 32 chars)') + + # 3. Development fallback + if os.getenv('FLASK_ENV') == 'development': + key = secrets.token_hex(32) + warn(f'Using temporary development key: {key[:8]}...') + return key + + # 4. Production failure + raise RuntimeError( + "No valid secret key configured.\n" + "Set FLASK_SECRET_KEY environment variable or " + f"create {secret_file} with 32+ random characters." + ) + +def generate_key() -> str: + """Generate a strong secret key for configuration""" + return secrets.token_urlsafe(64) # URL-safe for easier handling + +if __name__ == '__main__': + # CLI for key generation + print(f"New secret key: {generate_key()}") \ No newline at end of file