From 79aa3994d78860c0b7a623a46ce44dffff988fd9 Mon Sep 17 00:00:00 2001 From: Janne Koschinski Date: Tue, 8 May 2018 16:49:11 -0500 Subject: [PATCH] core: Allow configuring DB/auth from environment Add support for environment variables to configure database and authenticator backends. This improves using Quassel in a container -based system as no configuration file needs to be written. Toggle use of environment variables via a new CLI switch, '--config-from-environment', which will disregard the core settings file (if it exists) and use environment variables instead. New environment variables: > Backend selection DB_BACKEND = ["SQLite", "PostgreSQL"] AUTH_AUTHENTICATOR = ["Database", "LDAP"] > PostgreSQL connection properties DB_PGSQL_USERNAME DB_PGSQL_PASSWORD DB_PGSQL_HOSTNAME DB_PGSQL_PORT DB_PGSQL_DATABASE > LDAP connection properties AUTH_LDAP_HOSTNAME AUTH_LDAP_PORT AUTH_LDAP_BIND_DN AUTH_LDAP_BIND_PASSWORD AUTH_LDAP_BASE_DN AUTH_LDAP_FILTER AUTH_LDAP_UID_ATTRIBUTE Closes GH-341. --- src/common/main.cpp | 1 + src/core/abstractsqlstorage.cpp | 11 ++-- src/core/abstractsqlstorage.h | 12 +++-- src/core/authenticator.h | 9 +++- src/core/core.cpp | 92 +++++++++++++++++++++++---------- src/core/core.h | 8 ++- src/core/ldapauthenticator.cpp | 40 +++++++++----- src/core/ldapauthenticator.h | 9 ++-- src/core/postgresqlstorage.cpp | 24 ++++++--- src/core/postgresqlstorage.h | 4 +- src/core/sqlauthenticator.cpp | 11 +++- src/core/sqlauthenticator.h | 6 ++- src/core/sqlitestorage.h | 9 +++- src/core/storage.h | 8 ++- 14 files changed, 176 insertions(+), 68 deletions(-) diff --git a/src/common/main.cpp b/src/common/main.cpp index a2cb5745..79a8cfec 100644 --- a/src/common/main.cpp +++ b/src/common/main.cpp @@ -163,6 +163,7 @@ int main(int argc, char **argv) cliParser->addSwitch("syslog", 0, "Log to syslog"); #endif cliParser->addOption("logfile", 'l', "Log to a file", "path"); + cliParser->addSwitch("config-from-environment", 0, "Load configuration from environment variables"); cliParser->addOption("select-backend", 0, "Switch storage backend (migrating data if possible)", "backendidentifier"); cliParser->addOption("select-authenticator", 0, "Select authentication backend", "authidentifier"); cliParser->addSwitch("add-user", 0, "Starts an interactive session to add a new core user"); diff --git a/src/core/abstractsqlstorage.cpp b/src/core/abstractsqlstorage.cpp index cb8fbd11..9fe95b9d 100644 --- a/src/core/abstractsqlstorage.cpp +++ b/src/core/abstractsqlstorage.cpp @@ -116,9 +116,11 @@ void AbstractSqlStorage::dbConnect(QSqlDatabase &db) } -Storage::State AbstractSqlStorage::init(const QVariantMap &settings) +Storage::State AbstractSqlStorage::init(const QVariantMap &settings, + const QProcessEnvironment &environment, + bool loadFromEnvironment) { - setConnectionProperties(settings); + setConnectionProperties(settings, environment, loadFromEnvironment); _debug = Quassel::isOptionSet("debug"); @@ -192,9 +194,10 @@ QStringList AbstractSqlStorage::setupQueries() } -bool AbstractSqlStorage::setup(const QVariantMap &settings) +bool AbstractSqlStorage::setup(const QVariantMap &settings, const QProcessEnvironment &environment, + bool loadFromEnvironment) { - setConnectionProperties(settings); + setConnectionProperties(settings, environment, loadFromEnvironment); QSqlDatabase db = logDb(); if (!db.isOpen()) { qCritical() << "Unable to setup Logging Backend!"; diff --git a/src/core/abstractsqlstorage.h b/src/core/abstractsqlstorage.h index e8945551..5e4c360d 100644 --- a/src/core/abstractsqlstorage.h +++ b/src/core/abstractsqlstorage.h @@ -43,8 +43,12 @@ public: virtual std::unique_ptr createMigrationWriter() { return {}; } public slots: - virtual State init(const QVariantMap &settings = QVariantMap()); - virtual bool setup(const QVariantMap &settings = QVariantMap()); + virtual State init(const QVariantMap &settings = QVariantMap(), + const QProcessEnvironment &environment = {}, + bool loadFromEnvironment = false); + virtual bool setup(const QVariantMap &settings = QVariantMap(), + const QProcessEnvironment &environment = {}, + bool loadFromEnvironment = false); protected: inline virtual void sync() {}; @@ -82,7 +86,9 @@ protected: virtual bool updateSchemaVersion(int newVersion) = 0; virtual bool setupSchemaVersion(int version) = 0; - virtual void setConnectionProperties(const QVariantMap &properties) = 0; + virtual void setConnectionProperties(const QVariantMap &properties, + const QProcessEnvironment &environment, + bool loadFromEnvironment) = 0; virtual QString driverName() = 0; inline virtual QString hostName() { return QString(); } inline virtual int port() { return -1; } diff --git a/src/core/authenticator.h b/src/core/authenticator.h index df5d198c..ebea4c94 100644 --- a/src/core/authenticator.h +++ b/src/core/authenticator.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include #include @@ -81,13 +82,17 @@ public slots: * \param settings Hostname, port, username, password, ... * \return True if and only if the authenticator provider was initialized successfully. */ - virtual bool setup(const QVariantMap &settings = QVariantMap()) = 0; + virtual bool setup(const QVariantMap &settings = QVariantMap(), + const QProcessEnvironment &environment = {}, + bool loadFromEnvironment = false) = 0; //! Initialize the authenticator provider /** \param settings Hostname, port, username, password, ... * \return the State the authenticator backend is now in (see authenticator::State) */ - virtual State init(const QVariantMap &settings = QVariantMap()) = 0; + virtual State init(const QVariantMap &settings = QVariantMap(), + const QProcessEnvironment &environment = {}, + bool loadFromEnvironment = false) = 0; //! Validate a username with a given password. /** \param user The username to validate diff --git a/src/core/core.cpp b/src/core/core.cpp index 7fbd4e71..37dfe788 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -184,15 +184,40 @@ Core::Core() void Core::init() { - CoreSettings cs; + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + bool config_from_environment = Quassel::isOptionSet("config-from-environment"); + + QString db_backend; + QVariantMap db_connectionProperties; + + QString auth_authenticator; + QVariantMap auth_properties; + + bool writeError = false; + + if (config_from_environment) { + db_backend = environment.value("DB_BACKEND"); + auth_authenticator = environment.value("AUTH_AUTHENTICATOR"); + } else { + CoreSettings cs; + + QVariantMap dbsettings = cs.storageSettings().toMap(); + db_backend = dbsettings.value("Backend").toString(); + db_connectionProperties = dbsettings.value("ConnectionProperties").toMap(); + + QVariantMap authSettings = cs.authSettings().toMap(); + auth_authenticator = authSettings.value("Authenticator", "Database").toString(); + auth_properties = authSettings.value("AuthProperties").toMap(); + + writeError = !cs.isWritable(); + } + // legacy - QVariantMap dbsettings = cs.storageSettings().toMap(); - _configured = initStorage(dbsettings.value("Backend").toString(), dbsettings.value("ConnectionProperties").toMap()); + _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment); // Not entirely sure what is 'legacy' about the above, but it seems to be the way things work! if (_configured) { - QVariantMap authSettings = cs.authSettings().toMap(); - initAuthenticator(authSettings.value("Authenticator", "Database").toString(), authSettings.value("AuthProperties").toMap()); + initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment); } if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) { @@ -206,20 +231,30 @@ void Core::init() } if (!_configured) { - if (_registeredStorageBackends.size() == 0) { - quWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting...")); - quWarning() << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n" - "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n" - "to work.")); - exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup) - } - quWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup."; + if (config_from_environment) { + _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true); + initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true); - if (!cs.isWritable()) { - qWarning() << "Cannot write quasselcore configuration; probably a permission problem."; - exit(EXIT_FAILURE); - } + if (!_configured) { + qWarning() << "Cannot configure from environment"; + exit(EXIT_FAILURE); + } + } else { + if (_registeredStorageBackends.empty()) { + quWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting...")); + quWarning() + << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n" + "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n" + "to work.")); + exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup) + } + quWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup."; + if (writeError) { + qWarning() << "Cannot write quasselcore configuration; probably a permission problem."; + exit(EXIT_FAILURE); + } + } } if (Quassel::isOptionSet("add-user")) { @@ -314,12 +349,12 @@ QString Core::setupCore(const QString &adminUser, const QString &adminPassword, if (adminUser.isEmpty() || adminPassword.isEmpty()) { return tr("Admin user or password not set."); } - if (!(_configured = initStorage(backend, setupData, true))) { + if (!(_configured = initStorage(backend, setupData, {}, false, true))) { return tr("Could not setup storage!"); } quInfo() << "Selected authenticator:" << authenticator; - if (!(_configured = initAuthenticator(authenticator, authSetupData, true))) + if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true))) { return tr("Could not setup authenticator!"); } @@ -386,7 +421,8 @@ DeferredSharedPtr Core::storageBackend(const QString &backendId) const // old db settings: // "Type" => "sqlite" -bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup) +bool Core::initStorage(const QString &backend, const QVariantMap &settings, + const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup) { if (backend.isEmpty()) { quWarning() << "No storage backend selected!"; @@ -399,13 +435,13 @@ bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool return false; } - Storage::State storageState = storage->init(settings); + Storage::State storageState = storage->init(settings, environment, loadFromEnvironment); switch (storageState) { case Storage::NeedsSetup: if (!setup) return false; // trigger setup process - if (storage->setup(settings)) - return initStorage(backend, settings, false); + if (storage->setup(settings, environment, loadFromEnvironment)) + return initStorage(backend, settings, environment, loadFromEnvironment, false); return false; // if initialization wasn't successful, we quit to keep from coming up unconfigured @@ -483,7 +519,9 @@ DeferredSharedPtr Core::authenticator(const QString &backendId) c // FIXME: Apparently, this is the legacy way of initting storage backends? // If there's a not-legacy way, it should be used here -bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings, bool setup) +bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings, + const QProcessEnvironment &environment, bool loadFromEnvironment, + bool setup) { if (backend.isEmpty()) { quWarning() << "No authenticator selected!"; @@ -496,13 +534,13 @@ bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings return false; } - Authenticator::State authState = auth->init(settings); + Authenticator::State authState = auth->init(settings, environment, loadFromEnvironment); switch (authState) { case Authenticator::NeedsSetup: if (!setup) return false; // trigger setup process - if (auth->setup(settings)) - return initAuthenticator(backend, settings, false); + if (auth->setup(settings, environment, loadFromEnvironment)) + return initAuthenticator(backend, settings, environment, loadFromEnvironment, false); return false; // if initialization wasn't successful, we quit to keep from coming up unconfigured diff --git a/src/core/core.h b/src/core/core.h index 0814e7c2..45e15966 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -718,8 +718,12 @@ private slots: void incomingConnection(); void clientDisconnected(); - bool initStorage(const QString &backend, const QVariantMap &settings, bool setup = false); - bool initAuthenticator(const QString &backend, const QVariantMap &settings, bool setup = false); + bool initStorage(const QString &backend, const QVariantMap &settings, + const QProcessEnvironment &environment, bool loadFromEnvironment, + bool setup = false); + bool initAuthenticator(const QString &backend, const QVariantMap &settings, + const QProcessEnvironment &environment, bool loadFromEnvironment, + bool setup = false); void socketError(QAbstractSocket::SocketError err, const QString &errorString); void setupClientSession(RemotePeer *, UserId); diff --git a/src/core/ldapauthenticator.cpp b/src/core/ldapauthenticator.cpp index 772b767e..620ae511 100644 --- a/src/core/ldapauthenticator.cpp +++ b/src/core/ldapauthenticator.cpp @@ -100,15 +100,27 @@ QVariantList LdapAuthenticator::setupData() const } -void LdapAuthenticator::setAuthProperties(const QVariantMap &properties) +void LdapAuthenticator::setAuthProperties(const QVariantMap &properties, + const QProcessEnvironment &environment, + bool loadFromEnvironment) { - _hostName = properties["Hostname"].toString(); - _port = properties["Port"].toInt(); - _bindDN = properties["BindDN"].toString(); - _bindPassword = properties["BindPassword"].toString(); - _baseDN = properties["BaseDN"].toString(); - _filter = properties["Filter"].toString(); - _uidAttribute = properties["UidAttribute"].toString(); + if (loadFromEnvironment) { + _hostName = environment.value("AUTH_LDAP_HOSTNAME"); + _port = environment.value("AUTH_LDAP_PORT").toInt(); + _bindDN = environment.value("AUTH_LDAP_BIND_DN"); + _bindPassword = environment.value("AUTH_LDAP_BIND_PASSWORD"); + _baseDN = environment.value("AUTH_LDAP_BASE_DN"); + _filter = environment.value("AUTH_LDAP_FILTER"); + _uidAttribute = environment.value("AUTH_LDAP_UID_ATTRIBUTE"); + } else { + _hostName = properties["Hostname"].toString(); + _port = properties["Port"].toInt(); + _bindDN = properties["BindDN"].toString(); + _bindPassword = properties["BindPassword"].toString(); + _baseDN = properties["BaseDN"].toString(); + _filter = properties["Filter"].toString(); + _uidAttribute = properties["UidAttribute"].toString(); + } } // TODO: this code is sufficiently general that in the future, perhaps an abstract @@ -142,17 +154,21 @@ UserId LdapAuthenticator::validateUser(const QString &username, const QString &p } -bool LdapAuthenticator::setup(const QVariantMap &settings) +bool LdapAuthenticator::setup(const QVariantMap &settings, + const QProcessEnvironment &environment, + bool loadFromEnvironment) { - setAuthProperties(settings); + setAuthProperties(settings, environment, loadFromEnvironment); bool status = ldapConnect(); return status; } -Authenticator::State LdapAuthenticator::init(const QVariantMap &settings) +Authenticator::State LdapAuthenticator::init(const QVariantMap &settings, + const QProcessEnvironment &environment, + bool loadFromEnvironment) { - setAuthProperties(settings); + setAuthProperties(settings, environment, loadFromEnvironment); bool status = ldapConnect(); if (!status) { diff --git a/src/core/ldapauthenticator.h b/src/core/ldapauthenticator.h index 1bff10fa..1b0b6394 100644 --- a/src/core/ldapauthenticator.h +++ b/src/core/ldapauthenticator.h @@ -63,12 +63,15 @@ public slots: bool canChangePassword() const override { return false; } - bool setup(const QVariantMap &settings = {}) override; - State init(const QVariantMap &settings = {}) override; + bool setup(const QVariantMap &settings, const QProcessEnvironment &environment, + bool loadFromEnvironment) override; + State init(const QVariantMap &settings, const QProcessEnvironment &environment, + bool loadFromEnvironment) override; UserId validateUser(const QString &user, const QString &password) override; protected: - void setAuthProperties(const QVariantMap &properties); + void setAuthProperties(const QVariantMap &properties, const QProcessEnvironment &environment, + bool loadFromEnvironment); bool ldapConnect(); void ldapDisconnect(); bool ldapAuth(const QString &username, const QString &password); diff --git a/src/core/postgresqlstorage.cpp b/src/core/postgresqlstorage.cpp index a6f2a72c..5bad7588 100644 --- a/src/core/postgresqlstorage.cpp +++ b/src/core/postgresqlstorage.cpp @@ -47,7 +47,7 @@ std::unique_ptr PostgreSqlStorage::createMigrationWr properties["Hostname"] = _hostName; properties["Port"] = _port; properties["Database"] = _databaseName; - writer->setConnectionProperties(properties); + writer->setConnectionProperties(properties, {}, false); return std::unique_ptr{writer}; } @@ -147,13 +147,23 @@ bool PostgreSqlStorage::initDbSession(QSqlDatabase &db) } -void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties) +void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties, + const QProcessEnvironment &environment, + bool loadFromEnvironment) { - _userName = properties["Username"].toString(); - _password = properties["Password"].toString(); - _hostName = properties["Hostname"].toString(); - _port = properties["Port"].toInt(); - _databaseName = properties["Database"].toString(); + if (loadFromEnvironment) { + _userName = environment.value("DB_PGSQL_USERNAME"); + _password = environment.value("DB_PGSQL_PASSWORD"); + _hostName = environment.value("DB_PGSQL_HOSTNAME"); + _port = environment.value("DB_PGSQL_PORT").toInt(); + _databaseName = environment.value("DB_PGSQL_DATABASE"); + } else { + _userName = properties["Username"].toString(); + _password = properties["Password"].toString(); + _hostName = properties["Hostname"].toString(); + _port = properties["Port"].toInt(); + _databaseName = properties["Database"].toString(); + } } diff --git a/src/core/postgresqlstorage.h b/src/core/postgresqlstorage.h index 1c6afbac..f27675d3 100644 --- a/src/core/postgresqlstorage.h +++ b/src/core/postgresqlstorage.h @@ -124,7 +124,9 @@ public slots: protected: bool initDbSession(QSqlDatabase &db) override; - void setConnectionProperties(const QVariantMap &properties) override; + void setConnectionProperties(const QVariantMap &properties, + const QProcessEnvironment &environment, + bool loadFromEnvironment) override; QString driverName() override { return "QPSQL"; } QString hostName() override { return _hostName; } int port() override { return _port; } diff --git a/src/core/sqlauthenticator.cpp b/src/core/sqlauthenticator.cpp index 92bc5698..57a6aa1c 100644 --- a/src/core/sqlauthenticator.cpp +++ b/src/core/sqlauthenticator.cpp @@ -72,16 +72,23 @@ UserId SqlAuthenticator::validateUser(const QString &user, const QString &passwo } -bool SqlAuthenticator::setup(const QVariantMap &settings) +bool SqlAuthenticator::setup(const QVariantMap &settings, const QProcessEnvironment &environment, + bool loadFromEnvironment) { Q_UNUSED(settings) + Q_UNUSED(environment) + Q_UNUSED(loadFromEnvironment) return true; } -Authenticator::State SqlAuthenticator::init(const QVariantMap &settings) +Authenticator::State SqlAuthenticator::init(const QVariantMap &settings, + const QProcessEnvironment &environment, + bool loadFromEnvironment) { Q_UNUSED(settings) + Q_UNUSED(environment) + Q_UNUSED(loadFromEnvironment) // TODO: FIXME: this should check if the storage provider is ready, but I don't // know if there's an exposed way to do that at the moment. diff --git a/src/core/sqlauthenticator.h b/src/core/sqlauthenticator.h index eb333601..79b204f2 100644 --- a/src/core/sqlauthenticator.h +++ b/src/core/sqlauthenticator.h @@ -40,8 +40,10 @@ public slots: virtual inline bool canChangePassword() const { return true; } - bool setup(const QVariantMap &settings = QVariantMap()); - State init(const QVariantMap &settings = QVariantMap()); + bool setup(const QVariantMap &settings, const QProcessEnvironment &environment, + bool loadFromEnvironment); + State init(const QVariantMap &settings, const QProcessEnvironment &environment, + bool loadFromEnvironment); UserId validateUser(const QString &user, const QString &password); /* User handling */ diff --git a/src/core/sqlitestorage.h b/src/core/sqlitestorage.h index 5d396ad5..9a37160a 100644 --- a/src/core/sqlitestorage.h +++ b/src/core/sqlitestorage.h @@ -124,7 +124,14 @@ public slots: QString getAuthUserName(UserId user) override; protected: - void setConnectionProperties(const QVariantMap & /* properties */) override {} + void setConnectionProperties(const QVariantMap &properties, + const QProcessEnvironment &environment, + bool loadFromEnvironment) override { + Q_UNUSED(properties); + Q_UNUSED(environment); + Q_UNUSED(loadFromEnvironment); + } + // SQLite does not have any connection properties to set QString driverName() override { return "QSQLITE"; } QString databaseName() override { return backlogFile(); } int installedSchemaVersion() override; diff --git a/src/core/storage.h b/src/core/storage.h index 5a0a6405..f912c8b6 100644 --- a/src/core/storage.h +++ b/src/core/storage.h @@ -85,13 +85,17 @@ public slots: * \param settings Hostname, port, username, password, ... * \return True if and only if the storage provider was initialized successfully. */ - virtual bool setup(const QVariantMap &settings = QVariantMap()) = 0; + virtual bool setup(const QVariantMap &settings = QVariantMap(), + const QProcessEnvironment &environment = {}, + bool loadFromEnvironment = false) = 0; //! Initialize the storage provider /** \param settings Hostname, port, username, password, ... * \return the State the storage backend is now in (see Storage::State) */ - virtual State init(const QVariantMap &settings = QVariantMap()) = 0; + virtual State init(const QVariantMap &settings = QVariantMap(), + const QProcessEnvironment &environment = {}, + bool loadFromEnvironment = false) = 0; //! Makes temp data persistent /** This Method is periodically called by the Quassel Core to make temporary -- 2.20.1