eventlog: always use AuditSource objects as source field

This commit is contained in:
Girish Ramakrishnan
2023-08-26 08:18:58 +05:30
parent 246c45c1bc
commit d2c702f890
9 changed files with 34 additions and 19 deletions
+2 -2
View File
@@ -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
View File
@@ -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;
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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() {