diff --git a/CHANGES b/CHANGES index e50aeffff..bf29d200e 100644 --- a/CHANGES +++ b/CHANGES @@ -2348,4 +2348,5 @@ * mail: open up port 465 for mail submission (TLS) * Implement operator role for apps * sftp: normal users do not have SFTP access anymore. Use operator role instead +* eventlog: add service rebuild/restart/configure events diff --git a/src/apps.js b/src/apps.js index af099b9b2..2569be4b0 100644 --- a/src/apps.js +++ b/src/apps.js @@ -1568,7 +1568,7 @@ async function setDataDir(app, dataDir, auditSource) { args: { newDataDir: dataDir }, values: { }, onFinished: async (error) => { - if (!error) await safe(services.rebuildService('sftp'), { debug }); + if (!error) await safe(services.rebuildService('sftp', auditSource), { debug }); } }; const taskId = await addTask(appId, exports.ISTATE_PENDING_DATA_DIR_MIGRATION, task); diff --git a/src/cloudron.js b/src/cloudron.js index 4b7fe3f6f..8b06e2c45 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -302,7 +302,7 @@ async function updateDashboardDomain(domain, auditSource) { await setDashboardDomain(domain, auditSource); - safe(services.rebuildService('turn'), { debug }); // to update the realm variable + safe(services.rebuildService('turn', auditSource), { debug }); // to update the realm variable } async function renewCerts(options, auditSource) { diff --git a/src/eventlog.js b/src/eventlog.js index 81ac4791e..902ef0550 100644 --- a/src/eventlog.js +++ b/src/eventlog.js @@ -54,6 +54,11 @@ exports = module.exports = { ACTION_PROVISION: 'cloudron.provision', ACTION_RESTORE: 'cloudron.restore', // unused ACTION_START: 'cloudron.start', + + ACTION_SERVICE_CONFIGURE: 'service.configure', + ACTION_SERVICE_REBUILD: 'service.rebuild', + ACTION_SERVICE_RESTART: 'service.restart', + ACTION_UPDATE: 'cloudron.update', ACTION_UPDATE_FINISH: 'cloudron.update.finish', @@ -78,7 +83,6 @@ exports = module.exports = { const assert = require('assert'), database = require('./database.js'), - debug = require('debug')('box:eventlog'), mysql = require('mysql'), notifications = require('./notifications.js'), safe = require('safetydance'), diff --git a/src/routes/services.js b/src/routes/services.js index f075d7a3c..120f24443 100644 --- a/src/routes/services.js +++ b/src/routes/services.js @@ -11,6 +11,7 @@ exports = module.exports = { }; const assert = require('assert'), + auditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, @@ -44,7 +45,7 @@ async function configure(req, res, next) { memoryLimit: req.body.memoryLimit }; - const [error] = await safe(services.configureService(req.params.service, data)); + const [error] = await safe(services.configureService(req.params.service, data, auditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, {})); @@ -114,7 +115,7 @@ async function getLogStream(req, res, next) { async function restart(req, res, next) { assert.strictEqual(typeof req.params.service, 'string'); - const [error] = await safe(services.restartService(req.params.service)); + const [error] = await safe(services.restartService(req.params.service, auditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, {})); @@ -123,7 +124,7 @@ async function restart(req, res, next) { async function rebuild(req, res, next) { assert.strictEqual(typeof req.params.service, 'string'); - const [error] = await safe(services.rebuildService(req.params.service)); + const [error] = await safe(services.rebuildService(req.params.service, auditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, {})); diff --git a/src/services.js b/src/services.js index 193645169..6155c7d08 100644 --- a/src/services.js +++ b/src/services.js @@ -40,6 +40,7 @@ const addonConfigs = require('./addonconfigs.js'), crypto = require('crypto'), debug = require('debug')('box:services'), docker = require('./docker.js'), + eventlog = require('./eventlog.js'), fs = require('fs'), hat = require('./hat.js'), infra = require('./infra_version.js'), @@ -384,9 +385,10 @@ async function getServiceStatus(id) { return tmp; } -async function configureService(id, data) { +async function configureService(id, data, auditSource) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof data, 'object'); + assert.strictEqual(typeof auditSource, 'object'); const [name, instance ] = id.split(':'); @@ -411,6 +413,8 @@ async function configureService(id, data) { } else { throw new BoxError(BoxError.NOT_FOUND, 'No such service'); } + + await eventlog.add(eventlog.ACTION_SERVICE_CONFIGURE, auditSource, { id, data }); } async function getServiceLogs(id, options) { @@ -490,27 +494,45 @@ async function getServiceLogs(id, options) { return transformStream; } -async function rebuildService(id) { +async function rebuildService(id, auditSource) { assert.strictEqual(typeof id, 'string'); + assert.strictEqual(typeof auditSource, 'object'); // this attempts to recreate the service docker container if they don't exist but platform infra version is unchanged - // passing an infra version of 'none' will not attempt to purge existing data, not sure if this is good or bad + // passing an infra version of 'none' will not attempt to purge existing data const serviceConfig = await getServiceConfig(id); - if (id === 'turn') return await startTurn({ version: 'none' }, serviceConfig); - if (id === 'mongodb') return await startMongodb({ version: 'none' }); - if (id === 'postgresql') return await startPostgresql({ version: 'none' }); - if (id === 'mysql') return await startMysql({ version: 'none' }); - if (id === 'sftp') return await sftp.rebuild(serviceConfig, { /* options */ }); - if (id === 'graphite') return await startGraphite({ version: 'none' }, serviceConfig); + switch (id) { + case 'turn': + await startTurn({ version: 'none' }, serviceConfig); + break; + case 'mongodb': + await startMongodb({ version: 'none' }); + break; + case 'postgresql': + await startPostgresql({ version: 'none' }); + break; + case 'mysql': + await startMysql({ version: 'none' }); + break; + case 'sftp': + await sftp.rebuild(serviceConfig, { /* options */ }); + break; + case 'graphite': + await startGraphite({ version: 'none' }, serviceConfig); + break; + default: + // nothing to rebuild for now. + } - // nothing to rebuild for now. - // TODO: mongo/postgresql/mysql need to be scaled down. // TODO: missing redis container is not created + + await eventlog.add(eventlog.ACTION_SERVICE_REBUILD, auditSource, { id }); } -async function restartService(id) { +async function restartService(id, auditSource) { assert.strictEqual(typeof id, 'string'); + assert.strictEqual(typeof auditSource, 'object'); const [name, instance ] = id.split(':'); @@ -523,6 +545,8 @@ async function restartService(id) { } else { throw new BoxError(BoxError.NOT_FOUND, 'Service not found'); } + + await eventlog.add(eventlog.ACTION_SERVICE_RESTART, auditSource, { id }); } // in the future, we can refcount and lazy start global services diff --git a/src/volumes.js b/src/volumes.js index 01b9cc2d6..59e9571a1 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -99,7 +99,7 @@ async function add(volume, auditSource) { eventlog.add(eventlog.ACTION_VOLUME_ADD, auditSource, { id, name, hostPath: volume.hostPath }); // in theory, we only need to do this mountpoint volumes. but for some reason a restart is required to detect new "mounts" - safe(services.rebuildService('sftp'), { debug }); + safe(services.rebuildService('sftp', auditSource), { debug }); const collectdConf = ejs.render(COLLECTD_CONFIG_EJS, { volumeId: id, hostPath: volume.hostPath }); await collectd.addProfile(id, collectdConf); @@ -152,7 +152,7 @@ async function del(volume, auditSource) { eventlog.add(eventlog.ACTION_VOLUME_REMOVE, auditSource, { volume }); if (volume.mountType === 'mountpoint' || volume.mountType === 'filesystem') { - safe(services.rebuildService('sftp'), { debug }); + safe(services.rebuildService('sftp', auditSource), { debug }); } else { await safe(mounts.removeMount(volume)); }