eventlog: always use AuditSource objects as source field
This commit is contained in:
@@ -38,14 +38,14 @@ async function setHealth(app, health) {
|
||||
debug(`setHealth: ${app.id} (${app.fqdn}) switched from ${lastHealth} to healthy`);
|
||||
|
||||
// do not send mails for dev apps
|
||||
if (!app.debugMode) await eventlog.add(eventlog.ACTION_APP_UP, AuditSource.HEALTH_MONITOR, { app: app });
|
||||
if (!app.debugMode) await eventlog.add(eventlog.ACTION_APP_UP, AuditSource.HEALTH_MONITOR, { app });
|
||||
}
|
||||
} else if (Math.abs(now - healthTime) > UNHEALTHY_THRESHOLD) {
|
||||
if (lastHealth === apps.HEALTH_HEALTHY) {
|
||||
debug(`setHealth: marking ${app.id} (${app.fqdn}) as unhealthy since not seen for more than ${UNHEALTHY_THRESHOLD/(60 * 1000)} minutes`);
|
||||
|
||||
// do not send mails for dev apps
|
||||
if (!app.debugMode) await eventlog.add(eventlog.ACTION_APP_DOWN, AuditSource.HEALTH_MONITOR, { app: app });
|
||||
if (!app.debugMode) await eventlog.add(eventlog.ACTION_APP_DOWN, AuditSource.HEALTH_MONITOR, { app });
|
||||
}
|
||||
} else {
|
||||
debug(`setHealth: ${app.id} (${app.fqdn}) waiting for ${(UNHEALTHY_THRESHOLD - Math.abs(now - healthTime))/1000} to update health`);
|
||||
|
||||
+15
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
class AuditSource {
|
||||
constructor(username, userId, ip) {
|
||||
this.username = username;
|
||||
this.username = username || null; // this can be a real user or a module like cron/apptask/platform
|
||||
this.userId = userId || null;
|
||||
this.ip = ip || null;
|
||||
}
|
||||
@@ -11,14 +11,26 @@ class AuditSource {
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
return new AuditSource(req.user?.username, req.user?.id, ip);
|
||||
}
|
||||
|
||||
static fromDirectoryServerRequest(req) {
|
||||
const ip = req.connection.ldap.id; // also contains the port
|
||||
return new AuditSource('directoryserver', null, ip);
|
||||
}
|
||||
|
||||
static fromOidcRequest(req) {
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
return new AuditSource('oidc', req.body?.username, ip);
|
||||
}
|
||||
}
|
||||
|
||||
// these can be static variables but see https://stackoverflow.com/questions/60046847/eslint-does-not-allow-static-class-properties#comment122122927_60464446
|
||||
AuditSource.APPTASK = new AuditSource('apptask');
|
||||
AuditSource.BOOT = new AuditSource('boot');
|
||||
AuditSource.CRON = new AuditSource('cron');
|
||||
AuditSource.EXTERNAL_LDAP_TASK = new AuditSource('externalldap');
|
||||
AuditSource.EXTERNAL_LDAP_AUTO_CREATE = new AuditSource('externalldap');
|
||||
AuditSource.EXTERNAL_LDAP = new AuditSource('externalldap');
|
||||
AuditSource.HEALTH_MONITOR = new AuditSource('healthmonitor');
|
||||
AuditSource.LDAP = new AuditSource('ldap');
|
||||
AuditSource.MAIL = new AuditSource('mail');
|
||||
AuditSource.PLATFORM = new AuditSource('platform');
|
||||
|
||||
exports = module.exports = AuditSource;
|
||||
|
||||
@@ -11,6 +11,7 @@ exports = module.exports = {
|
||||
};
|
||||
|
||||
const assert = require('assert'),
|
||||
AuditSource = require('./auditsource.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:directoryserver'),
|
||||
@@ -363,7 +364,7 @@ async function start() {
|
||||
gServer.bind('ou=users,dc=cloudron', userAuth, async function (req, res) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
await eventlog.upsertLoginEvent(req.user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, { authType: 'directoryserver', id: req.connection.ldap.id }, { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||
await eventlog.upsertLoginEvent(req.user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, AuditSource.fromDirectoryServerRequest(req), { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
+2
-2
@@ -111,7 +111,7 @@ function postProcess(record) {
|
||||
// never throws, only logs because previously code did not take a callback
|
||||
async function add(action, source, data) {
|
||||
assert.strictEqual(typeof action, 'string');
|
||||
assert.strictEqual(typeof source, 'object');
|
||||
assert.strictEqual(typeof source, 'object'); // an AuditSource
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
|
||||
const id = uuid.v4();
|
||||
@@ -123,7 +123,7 @@ async function add(action, source, data) {
|
||||
// never throws, only logs because previously code did not take a callback
|
||||
async function upsertLoginEvent(action, source, data) {
|
||||
assert.strictEqual(typeof action, 'string');
|
||||
assert.strictEqual(typeof source, 'object');
|
||||
assert.strictEqual(typeof source, 'object'); // an AuditSource
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
|
||||
// can't do a real sql upsert, for frequent eventlog entries we only have to do 2 queries once a day
|
||||
|
||||
+4
-4
@@ -275,7 +275,7 @@ async function maybeCreateUser(identifier) {
|
||||
const user = translateUser(config, ldapUsers[0]);
|
||||
if (!validUserRequirements(user)) throw new BoxError(BoxError.BAD_FIELD);
|
||||
|
||||
return await users.add(user.email, { username: user.username, password: null, displayName: user.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP_AUTO_CREATE);
|
||||
return await users.add(user.email, { username: user.username, password: null, displayName: user.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP);
|
||||
}
|
||||
|
||||
async function verifyPassword(user, password, totpToken) {
|
||||
@@ -346,17 +346,17 @@ async function syncUsers(config, progressCallback) {
|
||||
if (!user) {
|
||||
debug(`syncUsers: [adding user] username=${ldapUser.username} email=${ldapUser.email} displayName=${ldapUser.displayName}`);
|
||||
|
||||
const [userAddError] = await safe(users.add(ldapUser.email, { username: ldapUser.username, password: null, displayName: ldapUser.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP_TASK));
|
||||
const [userAddError] = await safe(users.add(ldapUser.email, { username: ldapUser.username, password: null, displayName: ldapUser.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP));
|
||||
if (userAddError) debug('syncUsers: Failed to create user. %j %o', ldapUser, userAddError);
|
||||
} else if (user.source !== 'ldap') {
|
||||
debug(`syncUsers: [mapping user] username=${ldapUser.username} email=${ldapUser.email} displayName=${ldapUser.displayName}`);
|
||||
|
||||
const [userMappingError] = await safe(users.update(user, { email: ldapUser.email, fallbackEmail: ldapUser.email, displayName: ldapUser.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP_TASK));
|
||||
const [userMappingError] = await safe(users.update(user, { email: ldapUser.email, fallbackEmail: ldapUser.email, displayName: ldapUser.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP));
|
||||
if (userMappingError) debug('Failed to map user. %j %o', ldapUser, userMappingError);
|
||||
} else if (user.email !== ldapUser.email || user.displayName !== ldapUser.displayName) {
|
||||
debug(`syncUsers: [updating user] username=${ldapUser.username} email=${ldapUser.email} displayName=${ldapUser.displayName}`);
|
||||
|
||||
const [userUpdateError] = await safe(users.update(user, { email: ldapUser.email, fallbackEmail: ldapUser.email, displayName: ldapUser.displayName }, AuditSource.EXTERNAL_LDAP_TASK));
|
||||
const [userUpdateError] = await safe(users.update(user, { email: ldapUser.email, fallbackEmail: ldapUser.email, displayName: ldapUser.displayName }, AuditSource.EXTERNAL_LDAP));
|
||||
if (userUpdateError) debug('Failed to update user. %j %o', ldapUser, userUpdateError);
|
||||
} else {
|
||||
// user known and up-to-date
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@ exports = module.exports = {
|
||||
const addonConfigs = require('./addonconfigs.js'),
|
||||
assert = require('assert'),
|
||||
apps = require('./apps.js'),
|
||||
AuditSource = require('./auditsource.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:ldap'),
|
||||
@@ -458,7 +459,7 @@ async function authorizeUserForApp(req, res, next) {
|
||||
// we return no such object, to avoid leakage of a users existence
|
||||
if (!canAccess) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
await eventlog.upsertLoginEvent(req.user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: req.app.id }, { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||
await eventlog.upsertLoginEvent(req.user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, AuditSource.LDAP, { appId: req.app.id, userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||
|
||||
res.end();
|
||||
}
|
||||
@@ -603,7 +604,7 @@ async function authenticateService(serviceId, dn, req, res, next) {
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(dn.toString()));
|
||||
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
||||
|
||||
eventlog.upsertLoginEvent(result.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
||||
eventlog.upsertLoginEvent(result.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, AuditSource.MAIL, { mailboxId: email, userId: result.id, user: users.removePrivateFields(result) });
|
||||
|
||||
res.end();
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@ exports = module.exports = {
|
||||
|
||||
const assert = require('assert'),
|
||||
apps = require('./apps.js'),
|
||||
AuditSource = require('./auditsource.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
blobs = require('./blobs.js'),
|
||||
branding = require('./branding.js'),
|
||||
@@ -510,7 +511,6 @@ function interactionLogin(provider) {
|
||||
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
const userAgent = req.headers['user-agent'] || '';
|
||||
const auditSource = { authType: 'oidc', ip };
|
||||
const clientId = details.params.client_id;
|
||||
|
||||
debug(`interactionLogin: for OpenID client ${clientId} from ${ip}`);
|
||||
@@ -539,6 +539,7 @@ function interactionLogin(provider) {
|
||||
const [interactionFinishError, redirectTo] = await safe(provider.interactionResult(req, res, result));
|
||||
if (interactionFinishError) return next(new HttpError(500, interactionFinishError));
|
||||
|
||||
const auditSource = AuditSource.fromOidcRequest(req);
|
||||
await eventlog.add(user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, auditSource, { userId: user.id, user: users.removePrivateFields(user), appId: clientId });
|
||||
if (!user.ghost) safe(users.notifyLoginLocation(user, ip, userAgent, auditSource), { debug });
|
||||
|
||||
|
||||
+2
-3
@@ -32,7 +32,6 @@ async function login(req, res, next) {
|
||||
const type = req.body.type || tokens.ID_WEBADMIN;
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
|
||||
const userAgent = req.headers['user-agent'] || '';
|
||||
const auditSource = { authType: 'basic', ip };
|
||||
|
||||
let error = tokens.validateTokenType(type);
|
||||
if (error) return next(new HttpError(400, error.message));
|
||||
@@ -41,8 +40,8 @@ async function login(req, res, next) {
|
||||
[error, token] = await safe(tokens.add({ clientId: type, identifier: req.user.id, expires: Date.now() + constants.DEFAULT_TOKEN_EXPIRATION_MSECS }));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
await eventlog.add(req.user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, auditSource, { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||
|
||||
const auditSource = AuditSource.fromRequest(req);
|
||||
await eventlog.add(req.user.ghost ? eventlog.ACTION_USER_LOGIN_GHOST : eventlog.ACTION_USER_LOGIN, auditSource, { userId: req.user.id, user: users.removePrivateFields(req.user), type, appId: tokens.ID_CLI });
|
||||
if (!req.user.ghost) safe(users.notifyLoginLocation(req.user, ip, userAgent, auditSource), { debug });
|
||||
|
||||
next(new HttpSuccess(200, token));
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ exports = module.exports = {
|
||||
};
|
||||
|
||||
const assert = require('assert'),
|
||||
AuditSource = require('./auditsource.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:server'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
@@ -463,7 +464,7 @@ async function start() {
|
||||
gHttpServer = await initializeExpressSync();
|
||||
|
||||
await util.promisify(gHttpServer.listen.bind(gHttpServer))(constants.PORT, '127.0.0.1');
|
||||
await safe(eventlog.add(eventlog.ACTION_START, { userId: null, username: 'boot' }, { version: constants.VERSION })); // can fail if db down
|
||||
await safe(eventlog.add(eventlog.ACTION_START, AuditSource.BOOT, { version: constants.VERSION })); // can fail if db down
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
|
||||
Reference in New Issue
Block a user