diff --git a/server/database.js b/server/database.js index 3b7646de8c..44005d6e9e 100644 --- a/server/database.js +++ b/server/database.js @@ -184,10 +184,18 @@ class Database { /** * @typedef {string|undefined} envString - * @param {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} dbConfig the database configuration that should be written + * @param {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString, caFilePath:envString}} dbConfig the database configuration that should be written * @returns {void} */ static writeDBConfig(dbConfig) { + // Move CA file to the data directory + if (dbConfig.caFilePath) { + const dataCaFilePath = path.join(Database.dataDir, "mariadb-ca.pem"); + fs.renameSync(dbConfig.caFilePath, dataCaFilePath); + dbConfig.caFilePath = dataCaFilePath; + dbConfig.ssl = undefined; + dbConfig.caFile = undefined; + } fs.writeFileSync(path.join(Database.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4)); } @@ -259,11 +267,22 @@ class Database { throw Error("Invalid database name. A database name can only consist of letters, numbers and underscores"); } + let sslConfig = null; + let serverCa = undefined; + if (dbConfig.caFilePath) { + serverCa = [ fs.readFileSync(dbConfig.caFilePath, "utf8") ]; + sslConfig = { + rejectUnauthorized: true, + ca: serverCa + }; + } + const connection = await mysql.createConnection({ host: dbConfig.hostname, port: dbConfig.port, user: dbConfig.username, password: dbConfig.password, + ssl: sslConfig }); await connection.execute("CREATE DATABASE IF NOT EXISTS " + dbConfig.dbName + " CHARACTER SET utf8mb4"); @@ -277,7 +296,9 @@ class Database { user: dbConfig.username, password: dbConfig.password, database: dbConfig.dbName, + ssl: sslConfig, timezone: "Z", + //ssl: sslConfig, typeCast: function (field, next) { if (field.type === "DATETIME") { // Do not perform timezone conversion diff --git a/server/setup-database.js b/server/setup-database.js index 483f2c9a49..ddf664a19f 100644 --- a/server/setup-database.js +++ b/server/setup-database.js @@ -77,6 +77,7 @@ class SetupDatabase { dbConfig.dbName = process.env.UPTIME_KUMA_DB_NAME; dbConfig.username = process.env.UPTIME_KUMA_DB_USERNAME; dbConfig.password = process.env.UPTIME_KUMA_DB_PASSWORD; + dbConfig.caFilePath = process.env.UPTIME_KUMA_DB_CA_CERT; Database.writeDBConfig(dbConfig); } @@ -206,13 +207,43 @@ class SetupDatabase { return; } + // Prevent someone from injecting a CA file path not generated by the code below + if (dbConfig.caFilePath) { + dbConfig.caFilePath = undefined; + } + + if (dbConfig.caFile) { + const base64Data = dbConfig.caFile.replace(/^data:application\/octet-stream;base64,/, ""); + const binaryData = Buffer.from(base64Data, "base64").toString("binary"); + const tempCaDirectory = fs.mkdtempSync("kuma-ca-"); + dbConfig.caFilePath = path.join(tempCaDirectory, "ca.pem"); + try { + fs.writeFileSync(dbConfig.caFilePath, binaryData, "binary"); + } catch (err) { + + response.status(400).json("Cannot write CA file: " + err.message); + this.runningSetup = false; + return; + } + dbConfig.ssl = { + rejectUnauthorized: true, + ca: [ fs.readFileSync(dbConfig.caFilePath) ] + }; + } + // Test connection try { + let sslConfig = null; + if (dbConfig.ssl) { + sslConfig = dbConfig.ssl; + } + const connection = await mysql.createConnection({ host: dbConfig.hostname, port: dbConfig.port, user: dbConfig.username, password: dbConfig.password, + ssl: sslConfig }); await connection.execute("SELECT 1"); connection.end(); diff --git a/src/lang/en.json b/src/lang/en.json index e215f1031f..1c92ebbb7d 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -4,6 +4,7 @@ "setupDatabaseEmbeddedMariaDB": "You don't need to set anything. This docker image has embedded and configured MariaDB for you automatically. Uptime Kuma will connect to this database via unix socket.", "setupDatabaseMariaDB": "Connect to an external MariaDB database. You need to set the database connection information.", "setupDatabaseSQLite": "A simple database file, recommended for small-scale deployments. Prior to v2.0.0, Uptime Kuma used SQLite as the default database.", + "configureMariaCaFile": "You will sometimes need to provide a CA certificate to connect to database with 'require-secure-transport' on. Such as when using Azure MySql flexible servers. You can upload the CA file that will be used to enable a secure connection.", "settingUpDatabaseMSG": "Setting up the database. It may take a while, please be patient.", "dbName": "Database Name", "Settings": "Settings", diff --git a/src/pages/SetupDatabase.vue b/src/pages/SetupDatabase.vue index 81738a98b5..c4906d6411 100644 --- a/src/pages/SetupDatabase.vue +++ b/src/pages/SetupDatabase.vue @@ -90,8 +90,12 @@ - +
+

{{ $t("configureMariaCaFile") }}

+ +
+ @@ -117,6 +121,7 @@ export default { username: "", password: "", dbName: "kuma", + caFile: "" }, info: { needSetup: false, @@ -178,6 +183,15 @@ export default { } }, + onCaFileChange(e) { + const fileReader = new FileReader(); + fileReader.onload = () => { + this.dbConfig.caFile = fileReader.result; + console.log(this.dbConfig.caFile); + }; + fileReader.readAsDataURL(e.target.files[0]); + }, + test() { this.$root.toastError("not implemented"); } @@ -186,6 +200,22 @@ export default {