mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
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:
parent
c4f1b8eb11
commit
bc5cc964a3
@ -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
|
||||||
|
@ -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__)),
|
||||||
|
@ -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)
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -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)
|
58
web/setup.py
58
web/setup.py
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user