Move security keys into the SQLite database, and auto-generate them.

This avoids packagers having to deal with the issue, which can be
difficult if they need to cater for both server and desktop modes.

Fixes #1849
This commit is contained in:
Dave Page 2016-10-19 09:22:38 +01:00
parent c4f1b8eb11
commit bc5cc964a3
5 changed files with 80 additions and 46 deletions

View File

@ -140,21 +140,13 @@ DEFAULT_SERVER_PORT = 5050
# Enable CSRF protection? # Enable CSRF protection?
CSRF_ENABLED = True CSRF_ENABLED = True
# Secret key for signing CSRF data. Override this in config_local.py if
# running on a web server
CSRF_SESSION_KEY = 'SuperSecret1'
# Secret key for signing cookies. Override this in config_local.py if
# running on a web server
SECRET_KEY = 'SuperSecret2'
# Salt used when hashing passwords. Override this in config_local.py if
# running on a web server
SECURITY_PASSWORD_SALT = 'SuperSecret3'
# Hashing algorithm used for password storage # Hashing algorithm used for password storage
SECURITY_PASSWORD_HASH = 'pbkdf2_sha512' SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'
# NOTE: CSRF_SESSION_KEY, SECRET_KEY and SECURITY_PASSWORD_SALT are no
# longer part of the main configuration, but are stored in the
# configuration databases 'keys' table and are auto-generated.
# Should HTML be minified on the fly when not in debug mode? # Should HTML be minified on the fly when not in debug mode?
# Note: This is disabled by default as it will error when processing the # Note: This is disabled by default as it will error when processing the
# docs. If the serving of docs is handled by an Apache HTTPD # docs. If the serving of docs is handled by an Apache HTTPD

View File

@ -32,18 +32,6 @@ config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION
# Sanity checks # Sanity checks
########################################################################## ##########################################################################
# Check for local settings if running in server mode
if config.SERVER_MODE is True:
local_config = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'config_local.py')
if not os.path.isfile(local_config):
print("The configuration file %s does not exist.\n" % local_config)
print("Before running this application, ensure that config_local.py has been created")
print("and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY")
print("at bare minimum. See config.py for more information and a complete list of")
print("settings. Exiting...")
sys.exit(1)
# Check if the database exists. If it does not, create it. # Check if the database exists. If it does not, create it.
if not os.path.isfile(config.SQLITE_PATH): if not os.path.isfile(config.SQLITE_PATH):
setupfile = os.path.join(os.path.dirname(os.path.realpath(__file__)), setupfile = os.path.join(os.path.dirname(os.path.realpath(__file__)),

View File

@ -26,7 +26,7 @@ from pgadmin.utils.session import create_session_interface
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from werkzeug.utils import find_modules from werkzeug.utils import find_modules
from pgadmin.model import db, Role, Server, ServerGroup, User, Version from pgadmin.model import db, Role, Server, ServerGroup, User, Version, Keys
# Configuration settings # Configuration settings
import config import config
@ -126,11 +126,6 @@ def create_app(app_name=config.APP_NAME):
app.config.from_object(config) app.config.from_object(config)
app.config.update(dict(PROPAGATE_EXCEPTIONS=True)) app.config.update(dict(PROPAGATE_EXCEPTIONS=True))
##########################################################################
# Setup session management
##########################################################################
app.session_interface = create_session_interface(app)
########################################################################## ##########################################################################
# Setup logging and log the application startup # Setup logging and log the application startup
########################################################################## ##########################################################################
@ -206,7 +201,7 @@ def create_app(app_name=config.APP_NAME):
# Setup Flask-Security # Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role) user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore) security = Security(None, user_datastore)
# Upgrade the schema (if required) # Upgrade the schema (if required)
with app.app_context(): with app.app_context():
@ -220,9 +215,29 @@ def create_app(app_name=config.APP_NAME):
) )
) )
from setup import do_upgrade from setup import do_upgrade
do_upgrade(app, user_datastore, security, version) do_upgrade(app, user_datastore, version)
##########################################################################
# Setup security
##########################################################################
with app.app_context():
config.CSRF_SESSION_KEY = Keys.query.filter_by(name = 'CSRF_SESSION_KEY').first().value
config.SECRET_KEY = Keys.query.filter_by(name = 'SECRET_KEY').first().value
config.SECURITY_PASSWORD_SALT = Keys.query.filter_by(name = 'SECURITY_PASSWORD_SALT').first().value
# Update the app.config with proper security keyes for signing CSRF data,
# signing cookies, and the SALT for hashing the passwords.
app.config.update(dict(CSRF_SESSION_KEY=config.CSRF_SESSION_KEY))
app.config.update(dict(SECRET_KEY=config.SECRET_KEY))
app.config.update(dict(SECURITY_PASSWORD_SALT=config.SECURITY_PASSWORD_SALT))
security.init_app(app)
app.session_interface = create_session_interface(app)
##########################################################################
# Load all available server drivers # Load all available server drivers
##########################################################################
driver.init_app(app) driver.init_app(app)
########################################################################## ##########################################################################

View File

@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
# #
########################################################################## ##########################################################################
SCHEMA_VERSION = 13 SCHEMA_VERSION = 14
########################################################################## ##########################################################################
# #
@ -207,3 +207,10 @@ class Process(db.Model):
end_time = db.Column(db.String(), nullable=True) end_time = db.Column(db.String(), nullable=True)
exit_code = db.Column(db.Integer(), nullable=True) exit_code = db.Column(db.Integer(), nullable=True)
acknowledge = db.Column(db.String(), nullable=True) acknowledge = db.Column(db.String(), nullable=True)
class Keys(db.Model):
"""Define the keys table."""
__tablename__ = 'keys'
name = db.Column(db.String(), nullable=False, primary_key=True)
value = db.Column(db.String(), nullable=False)

View File

@ -10,6 +10,7 @@
"""Perform the initial setup of the application, by creating the auth """Perform the initial setup of the application, by creating the auth
and settings database.""" and settings database."""
import base64
import getpass import getpass
import os import os
import random import random
@ -22,7 +23,7 @@ from flask_security import Security, SQLAlchemyUserDatastore
from flask_security.utils import encrypt_password from flask_security.utils import encrypt_password
from pgadmin.model import db, Role, User, Server, \ from pgadmin.model import db, Role, User, Server, \
ServerGroup, Version ServerGroup, Version, Keys
# Configuration settings # Configuration settings
import config import config
@ -40,6 +41,7 @@ if hasattr(__builtins__, 'raw_input'):
def do_setup(app): def do_setup(app):
"""Create a new settings database from scratch""" """Create a new settings database from scratch"""
if config.SERVER_MODE is False: if config.SERVER_MODE is False:
print("NOTE: Configuring authentication for DESKTOP mode.") print("NOTE: Configuring authentication for DESKTOP mode.")
email = config.DESKTOP_USER email = config.DESKTOP_USER
@ -116,6 +118,17 @@ def do_setup(app):
name='ConfigDB', value=config.SETTINGS_SCHEMA_VERSION name='ConfigDB', value=config.SETTINGS_SCHEMA_VERSION
) )
db.session.merge(version) db.session.merge(version)
db.session.commit()
# Create the keys
key = Keys(name='CSRF_SESSION_KEY', value=config.CSRF_SESSION_KEY)
db.session.merge(key)
key = Keys(name='SECRET_KEY', value=config.SECRET_KEY)
db.session.merge(key)
key = Keys(name='SECURITY_PASSWORD_SALT', value=config.SECURITY_PASSWORD_SALT)
db.session.merge(key)
db.session.commit() db.session.commit()
@ -128,7 +141,7 @@ def do_setup(app):
) )
def do_upgrade(app, datastore, security, version): def do_upgrade(app, datastore, version):
"""Upgrade an existing settings database""" """Upgrade an existing settings database"""
####################################################################### #######################################################################
# Run whatever is required to update the database schema to the current # Run whatever is required to update the database schema to the current
@ -329,6 +342,29 @@ ALTER TABLE SERVER
ADD COLUMN discovery_id TEXT ADD COLUMN discovery_id TEXT
""") """)
if int(version.value) < 14:
db.engine.execute("""
CREATE TABLE keys (
name TEST NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (name))
""")
sql = "INSERT INTO keys (name, value) VALUES ('CSRF_SESSION_KEY', '%s')" % base64.urlsafe_b64encode(os.urandom(32))
db.engine.execute(sql)
sql = "INSERT INTO keys (name, value) VALUES ('SECRET_KEY', '%s')" % base64.urlsafe_b64encode(os.urandom(32))
db.engine.execute(sql)
# If SECURITY_PASSWORD_SALT is not in the config, but we're upgrading, then it must (unless the
# user edited the main config - which they shouldn't have done) have been at it's default
# value, so we'll use that. Otherwise, use whatever we can find in the config.
if hasattr(config, 'SECURITY_PASSWORD_SALT'):
sql = "INSERT INTO keys (name, value) VALUES ('SECURITY_PASSWORD_SALT', '%s')" % config.SECURITY_PASSWORD_SALT
else:
sql = "INSERT INTO keys (name, value) VALUES ('SECURITY_PASSWORD_SALT', 'SuperSecret3')"
db.engine.execute(sql)
# Finally, update the schema version # Finally, update the schema version
version.value = config.SETTINGS_SCHEMA_VERSION version.value = config.SETTINGS_SCHEMA_VERSION
db.session.merge(version) db.session.merge(version)
@ -347,6 +383,7 @@ ALTER TABLE SERVER
############################################################################### ###############################################################################
if __name__ == '__main__': if __name__ == '__main__':
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config) app.config.from_object(config)
if config.TESTING_MODE: if config.TESTING_MODE:
@ -364,15 +401,6 @@ if __name__ == '__main__':
'config_local.py' 'config_local.py'
) )
if not os.path.isfile(local_config):
print("""
The configuration file - {0} does not exist.
Before running this application, ensure that config_local.py has been created
and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY
at bare minimum. See config.py for more information and a complete list of
settings. Exiting...""".format(local_config))
sys.exit(1)
# Check if the database exists. If it does, tell the user and exit. # Check if the database exists. If it does, tell the user and exit.
if os.path.isfile(config.SQLITE_PATH): if os.path.isfile(config.SQLITE_PATH):
print(""" print("""
@ -381,7 +409,6 @@ Entering upgrade mode...""" % config.SQLITE_PATH)
# Setup Flask-Security # Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role) user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Always use "< REQUIRED_VERSION" as the test for readability # Always use "< REQUIRED_VERSION" as the test for readability
with app.app_context(): with app.app_context():
@ -403,8 +430,13 @@ Exiting...""" % (version.value))
print("NOTE: Upgrading database schema from version %d to %d." % ( print("NOTE: Upgrading database schema from version %d to %d." % (
version.value, config.SETTINGS_SCHEMA_VERSION version.value, config.SETTINGS_SCHEMA_VERSION
)) ))
do_upgrade(app, user_datastore, security, version) do_upgrade(app, user_datastore, version)
else: else:
# Get some defaults for the various keys
config.CSRF_SESSION_KEY = base64.urlsafe_b64encode(os.urandom(32))
config.SECRET_KEY = base64.urlsafe_b64encode(os.urandom(32))
config.SECURITY_PASSWORD_SALT = base64.urlsafe_b64encode(os.urandom(32))
directory = os.path.dirname(config.SQLITE_PATH) directory = os.path.dirname(config.SQLITE_PATH)
if not os.path.exists(directory): if not os.path.exists(directory):
os.makedirs(directory, int('700', 8)) os.makedirs(directory, int('700', 8))