Compare commits
5 Commits
docker_tests
...
v3.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
| fd723cf7eb | |||
| f2c8102a6a | |||
| 6589ba0988 | |||
| 2d7f0c3ebe | |||
| a66bc7192d |
@@ -1474,3 +1474,34 @@
|
|||||||
* Flexible mailbox management
|
* Flexible mailbox management
|
||||||
* Automatic updates can be toggled per app
|
* Automatic updates can be toggled per app
|
||||||
|
|
||||||
|
[3.4.1]
|
||||||
|
* Improve error page
|
||||||
|
* Add system view to manage addons and view their status
|
||||||
|
* Fix iconset regression for account and Cloudron name edits
|
||||||
|
* Add server reboot button and warn if reboot is required for security updates
|
||||||
|
* Backup and update tasks are now cancelable
|
||||||
|
* Move graphite away from port 3000 (reserved by ESXi)
|
||||||
|
* Flexible mailbox management
|
||||||
|
* Automatic updates can be toggled per app
|
||||||
|
|
||||||
|
[3.4.2]
|
||||||
|
* Improve error page
|
||||||
|
* Add system view to manage addons and view their status
|
||||||
|
* Fix iconset regression for account and Cloudron name edits
|
||||||
|
* Add server reboot button and warn if reboot is required for security updates
|
||||||
|
* Backup and update tasks are now cancelable
|
||||||
|
* Move graphite away from port 3000 (reserved by ESXi)
|
||||||
|
* Flexible mailbox management
|
||||||
|
* Automatic updates can be toggled per app
|
||||||
|
|
||||||
|
[3.4.3]
|
||||||
|
* Improve error page
|
||||||
|
* Add system view to manage addons and view their status
|
||||||
|
* Fix iconset regression for account and Cloudron name edits
|
||||||
|
* Add server reboot button and warn if reboot is required for security updates
|
||||||
|
* Backup and update tasks are now cancelable
|
||||||
|
* Move graphite away from port 3000 (reserved by ESXi)
|
||||||
|
* Flexible mailbox management
|
||||||
|
* Automatic updates can be toggled per app
|
||||||
|
* Fix issue where OOM mails are sent out without a rate limit
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -815,7 +815,8 @@ function setupEmail(app, options, callback) {
|
|||||||
{ name: 'MAIL_SIEVE_SERVER', value: 'mail' },
|
{ name: 'MAIL_SIEVE_SERVER', value: 'mail' },
|
||||||
{ name: 'MAIL_SIEVE_PORT', value: '4190' },
|
{ name: 'MAIL_SIEVE_PORT', value: '4190' },
|
||||||
{ name: 'MAIL_DOMAIN', value: app.domain },
|
{ name: 'MAIL_DOMAIN', value: app.domain },
|
||||||
{ name: 'MAIL_DOMAINS', value: mailInDomains }
|
{ name: 'MAIL_DOMAINS', value: mailInDomains },
|
||||||
|
{ name: 'LDAP_MAILBOXES_BASE_DN', value: 'ou=mailboxes,dc=cloudron' }
|
||||||
];
|
];
|
||||||
|
|
||||||
debugApp(app, 'Setting up Email');
|
debugApp(app, 'Setting up Email');
|
||||||
|
|||||||
+19
-19
@@ -15,11 +15,12 @@ exports = module.exports = {
|
|||||||
run: run
|
run: run
|
||||||
};
|
};
|
||||||
|
|
||||||
var HEALTHCHECK_INTERVAL = 10 * 1000; // every 10 seconds. this needs to be small since the UI makes only healthy apps clickable
|
const HEALTHCHECK_INTERVAL = 10 * 1000; // every 10 seconds. this needs to be small since the UI makes only healthy apps clickable
|
||||||
var UNHEALTHY_THRESHOLD = 10 * 60 * 1000; // 10 minutes
|
const UNHEALTHY_THRESHOLD = 10 * 60 * 1000; // 10 minutes
|
||||||
var gHealthInfo = { }; // { time, emailSent }
|
let gHealthInfo = { }; // { time, emailSent }
|
||||||
|
|
||||||
const NOOP_CALLBACK = function (error) { if (error) console.error(error); };
|
const OOM_MAIL_LIMIT = 60 * 60 * 1000; // 60 minutes
|
||||||
|
let gLastOomMailTime = Date.now() - (5 * 60 * 1000); // pretend we sent email 5 minutes ago
|
||||||
|
|
||||||
function debugApp(app) {
|
function debugApp(app) {
|
||||||
assert(typeof app === 'object');
|
assert(typeof app === 'object');
|
||||||
@@ -118,13 +119,11 @@ function checkAppHealth(app, callback) {
|
|||||||
apt-get update && apt-get install stress
|
apt-get update && apt-get install stress
|
||||||
stress --vm 1 --vm-bytes 200M --vm-hang 0
|
stress --vm 1 --vm-bytes 200M --vm-hang 0
|
||||||
*/
|
*/
|
||||||
function processDockerEvents(interval, callback) {
|
function processDockerEvents(intervalSecs, callback) {
|
||||||
assert.strictEqual(typeof interval, 'number');
|
assert.strictEqual(typeof intervalSecs, 'number');
|
||||||
assert.strictEqual(typeof callback, 'function');
|
assert.strictEqual(typeof callback, 'function');
|
||||||
|
|
||||||
const OOM_MAIL_LIMIT = 60 * 60 * 1000; // 60 minutes
|
const since = ((new Date().getTime() / 1000) - intervalSecs).toFixed(0);
|
||||||
let lastOomMailTime = new Date(new Date() - OOM_MAIL_LIMIT);
|
|
||||||
const since = ((new Date().getTime() / 1000) - interval).toFixed(0);
|
|
||||||
const until = ((new Date().getTime() / 1000) - 1).toFixed(0);
|
const until = ((new Date().getTime() / 1000) - 1).toFixed(0);
|
||||||
|
|
||||||
docker.getEvents({ since: since, until: until, filters: JSON.stringify({ event: [ 'oom' ] }) }, function (error, stream) {
|
docker.getEvents({ since: since, until: until, filters: JSON.stringify({ event: [ 'oom' ] }) }, function (error, stream) {
|
||||||
@@ -136,15 +135,17 @@ function processDockerEvents(interval, callback) {
|
|||||||
appdb.getByContainerId(ev.id, function (error, app) { // this can error for addons
|
appdb.getByContainerId(ev.id, function (error, app) { // this can error for addons
|
||||||
var program = error || !app.appStoreId ? ev.id : app.appStoreId;
|
var program = error || !app.appStoreId ? ev.id : app.appStoreId;
|
||||||
var context = JSON.stringify(ev);
|
var context = JSON.stringify(ev);
|
||||||
var now = new Date();
|
var now = Date.now();
|
||||||
if (app) context = context + '\n\n' + JSON.stringify(app, null, 4) + '\n';
|
if (app) context = context + '\n\n' + JSON.stringify(app, null, 4) + '\n';
|
||||||
|
|
||||||
debug('OOM Context: %s', context);
|
const notifyUser = (!app || !app.debugMode) && (now - gLastOomMailTime > OOM_MAIL_LIMIT);
|
||||||
|
|
||||||
|
debug('OOM Context: %s. notifyUser: %s. lastOomTime: %s (now: %s)', context, notifyUser, gLastOomMailTime, now);
|
||||||
|
|
||||||
// do not send mails for dev apps
|
// do not send mails for dev apps
|
||||||
if ((!app || !app.debugMode) && (now - lastOomMailTime > OOM_MAIL_LIMIT)) {
|
if (notifyUser) {
|
||||||
mailer.oomEvent(program, context); // app can be null if it's an addon crash
|
mailer.oomEvent(program, context); // app can be null if it's an addon crash
|
||||||
lastOomMailTime = now;
|
gLastOomMailTime = now;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -181,14 +182,13 @@ function processApp(callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(interval, callback) {
|
function run(intervalSecs, callback) {
|
||||||
assert.strictEqual(typeof interval, 'number');
|
assert.strictEqual(typeof intervalSecs, 'number');
|
||||||
|
assert.strictEqual(typeof callback, 'function');
|
||||||
callback = callback || NOOP_CALLBACK;
|
|
||||||
|
|
||||||
async.series([
|
async.series([
|
||||||
processDockerEvents.bind(null, interval),
|
processApp, // this is first because docker.getEvents seems to get 'stuck' sometimes
|
||||||
processApp
|
processDockerEvents.bind(null, intervalSecs)
|
||||||
], function (error) {
|
], function (error) {
|
||||||
if (error) debug(error);
|
if (error) debug(error);
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -22,7 +22,6 @@ var appHealthMonitor = require('./apphealthmonitor.js'),
|
|||||||
dyndns = require('./dyndns.js'),
|
dyndns = require('./dyndns.js'),
|
||||||
eventlog = require('./eventlog.js'),
|
eventlog = require('./eventlog.js'),
|
||||||
janitor = require('./janitor.js'),
|
janitor = require('./janitor.js'),
|
||||||
reverseProxy = require('./reverseproxy.js'),
|
|
||||||
scheduler = require('./scheduler.js'),
|
scheduler = require('./scheduler.js'),
|
||||||
settings = require('./settings.js'),
|
settings = require('./settings.js'),
|
||||||
updater = require('./updater.js'),
|
updater = require('./updater.js'),
|
||||||
@@ -48,7 +47,7 @@ var gJobs = {
|
|||||||
appHealthMonitor: null
|
appHealthMonitor: null
|
||||||
};
|
};
|
||||||
|
|
||||||
var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
|
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||||
var AUDIT_SOURCE = { userId: null, username: 'cron' };
|
var AUDIT_SOURCE = { userId: null, username: 'cron' };
|
||||||
|
|
||||||
// cron format
|
// cron format
|
||||||
@@ -202,7 +201,7 @@ function recreateJobs(tz) {
|
|||||||
if (gJobs.appHealthMonitor) gJobs.appHealthMonitor.stop();
|
if (gJobs.appHealthMonitor) gJobs.appHealthMonitor.stop();
|
||||||
gJobs.appHealthMonitor = new CronJob({
|
gJobs.appHealthMonitor = new CronJob({
|
||||||
cronTime: '*/10 * * * * *', // every 10 seconds
|
cronTime: '*/10 * * * * *', // every 10 seconds
|
||||||
onTick: appHealthMonitor.run.bind(null, 10),
|
onTick: appHealthMonitor.run.bind(null, 10, NOOP_CALLBACK),
|
||||||
start: true,
|
start: true,
|
||||||
timeZone: tz
|
timeZone: tz
|
||||||
});
|
});
|
||||||
|
|||||||
+39
-7
@@ -450,8 +450,39 @@ function authorizeUserForApp(req, res, next) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function authenticateMailbox(req, res, next) {
|
function authenticateUserMailbox(req, res, next) {
|
||||||
debug('mailbox auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
debug('user mailbox auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
||||||
|
|
||||||
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
|
||||||
|
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||||
|
var parts = email.split('@');
|
||||||
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
|
||||||
|
mail.getDomain(parts[1], function (error, domain) {
|
||||||
|
if (error && error.reason === MailError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
if (error) return next(new ldap.OperationsError(error.message));
|
||||||
|
|
||||||
|
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
|
||||||
|
mailboxdb.getMailbox(parts[0], parts[1], function (error, mailbox) {
|
||||||
|
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
if (error) return next(new ldap.OperationsError(error.message));
|
||||||
|
|
||||||
|
users.verify(mailbox.ownerId, req.credentials || '', function (error, result) {
|
||||||
|
if (error && error.reason === UsersError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
if (error && error.reason === UsersError.WRONG_PASSWORD) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||||
|
if (error) return next(new ldap.OperationsError(error.message));
|
||||||
|
|
||||||
|
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function authenticateMailAddon(req, res, next) {
|
||||||
|
debug('mail addon auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
||||||
|
|
||||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
|
||||||
@@ -516,12 +547,13 @@ function start(callback) {
|
|||||||
gServer.bind('ou=users,dc=cloudron', authenticateApp, authenticateUser, authorizeUserForApp);
|
gServer.bind('ou=users,dc=cloudron', authenticateApp, authenticateUser, authorizeUserForApp);
|
||||||
|
|
||||||
// http://www.ietf.org/proceedings/43/I-D/draft-srivastava-ldap-mail-00.txt
|
// http://www.ietf.org/proceedings/43/I-D/draft-srivastava-ldap-mail-00.txt
|
||||||
gServer.search('ou=mailboxes,dc=cloudron', mailboxSearch);
|
gServer.search('ou=mailboxes,dc=cloudron', mailboxSearch); // haraka, dovecot
|
||||||
gServer.search('ou=mailaliases,dc=cloudron', mailAliasSearch);
|
gServer.bind('ou=mailboxes,dc=cloudron', authenticateUserMailbox); // apps like sogo can use domain=${domain} to authenticate a mailbox
|
||||||
gServer.search('ou=mailinglists,dc=cloudron', mailingListSearch);
|
gServer.search('ou=mailaliases,dc=cloudron', mailAliasSearch); // haraka
|
||||||
|
gServer.search('ou=mailinglists,dc=cloudron', mailingListSearch); // haraka
|
||||||
|
|
||||||
gServer.bind('ou=recvmail,dc=cloudron', authenticateMailbox); // dovecot
|
gServer.bind('ou=recvmail,dc=cloudron', authenticateMailAddon); // dovecot
|
||||||
gServer.bind('ou=sendmail,dc=cloudron', authenticateMailbox); // haraka
|
gServer.bind('ou=sendmail,dc=cloudron', authenticateMailAddon); // haraka
|
||||||
|
|
||||||
gServer.compare('cn=users,ou=groups,dc=cloudron', authenticateApp, groupUsersCompare);
|
gServer.compare('cn=users,ou=groups,dc=cloudron', authenticateApp, groupUsersCompare);
|
||||||
gServer.compare('cn=admins,ou=groups,dc=cloudron', authenticateApp, groupAdminsCompare);
|
gServer.compare('cn=admins,ou=groups,dc=cloudron', authenticateApp, groupAdminsCompare);
|
||||||
|
|||||||
+64
-8
@@ -110,6 +110,8 @@ function setup(done) {
|
|||||||
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0, callback);
|
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0, callback);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
(done) => mailboxdb.addMailbox(USER_0.username.toLowerCase(), DOMAIN_0.domain, USER_0.id, done),
|
||||||
|
(done) => mailboxdb.setAliasesForName(USER_0.username.toLowerCase(), DOMAIN_0.domain, [ USER_0_ALIAS.toLocaleLowerCase() ], done),
|
||||||
appdb.update.bind(null, APP_0.id, { containerId: APP_0.containerId }),
|
appdb.update.bind(null, APP_0.id, { containerId: APP_0.containerId }),
|
||||||
appdb.setAddonConfig.bind(null, APP_0.id, 'sendmail', [{ name: 'MAIL_SMTP_PASSWORD', value : 'sendmailpassword' }]),
|
appdb.setAddonConfig.bind(null, APP_0.id, 'sendmail', [{ name: 'MAIL_SMTP_PASSWORD', value : 'sendmailpassword' }]),
|
||||||
appdb.setAddonConfig.bind(null, APP_0.id, 'recvmail', [{ name: 'MAIL_IMAP_PASSWORD', value : 'recvmailpassword' }]),
|
appdb.setAddonConfig.bind(null, APP_0.id, 'recvmail', [{ name: 'MAIL_IMAP_PASSWORD', value : 'recvmailpassword' }]),
|
||||||
@@ -812,10 +814,6 @@ describe('Ldap', function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('search mailbox', function () {
|
describe('search mailbox', function () {
|
||||||
before(function (done) {
|
|
||||||
mailboxdb.addMailbox(USER_0.username.toLowerCase(), DOMAIN_0.domain, USER_0.id, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('get specific mailbox by email', function (done) {
|
it('get specific mailbox by email', function (done) {
|
||||||
ldapSearch('cn=' + USER_0.username + '@example.com,ou=mailboxes,dc=cloudron', 'objectclass=mailbox', function (error, entries) {
|
ldapSearch('cn=' + USER_0.username + '@example.com,ou=mailboxes,dc=cloudron', 'objectclass=mailbox', function (error, entries) {
|
||||||
if (error) return done(error);
|
if (error) return done(error);
|
||||||
@@ -848,10 +846,6 @@ describe('Ldap', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('search aliases', function () {
|
describe('search aliases', function () {
|
||||||
before(function (done) {
|
|
||||||
mailboxdb.setAliasesForName(USER_0.username.toLowerCase(), DOMAIN_0.domain, [ USER_0_ALIAS.toLocaleLowerCase() ], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('get specific alias', function (done) {
|
it('get specific alias', function (done) {
|
||||||
ldapSearch('cn=' + USER_0_ALIAS + '@example.com,ou=mailaliases,dc=cloudron', 'objectclass=nismailalias', function (error, entries) {
|
ldapSearch('cn=' + USER_0_ALIAS + '@example.com,ou=mailaliases,dc=cloudron', 'objectclass=nismailalias', function (error, entries) {
|
||||||
if (error) return done(error);
|
if (error) return done(error);
|
||||||
@@ -919,6 +913,68 @@ describe('Ldap', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('user mailbox bind', function () {
|
||||||
|
it('email disabled - cannot find domain email', function (done) {
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=' + USER_0.username + '@example.com,domain=example.com,ou=mailboxes,dc=cloudron', USER_0.password + 'nope', function (error) {
|
||||||
|
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||||
|
client.unbind(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('email enabled - does not allow with invalid password', function (done) {
|
||||||
|
// use maildb to not trigger further events
|
||||||
|
maildb.update(DOMAIN_0.domain, { enabled: true }, function (error) {
|
||||||
|
expect(error).not.to.be.ok();
|
||||||
|
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=' + USER_0.username + '@example.com,domain=example.com,ou=mailboxes,dc=cloudron', USER_0.password + 'nope', function (error) {
|
||||||
|
expect(error).to.be.a(ldap.InvalidCredentialsError);
|
||||||
|
|
||||||
|
client.unbind();
|
||||||
|
|
||||||
|
maildb.update(DOMAIN_0.domain, { enabled: false }, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('email enabled - allows with valid password', function (done) {
|
||||||
|
// use maildb to not trigger further events
|
||||||
|
maildb.update(DOMAIN_0.domain, { enabled: true }, function (error) {
|
||||||
|
expect(error).not.to.be.ok();
|
||||||
|
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=' + USER_0.username + '@example.com,domain=example.com,ou=mailboxes,dc=cloudron', USER_0.password, function (error) {
|
||||||
|
expect(error).not.to.be.ok();
|
||||||
|
|
||||||
|
client.unbind();
|
||||||
|
|
||||||
|
maildb.update(DOMAIN_0.domain, { enabled: false }, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('email enabled - cannot auth with alias', function (done) {
|
||||||
|
// use maildb to not trigger further events
|
||||||
|
maildb.update(DOMAIN_0.domain, { enabled: true }, function (error) {
|
||||||
|
expect(error).not.to.be.ok();
|
||||||
|
|
||||||
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|
||||||
|
client.bind('cn=' + USER_0_ALIAS + '@example.com,domain=example.com,ou=mailboxes,dc=cloudron', USER_0.password, function (error) {
|
||||||
|
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||||
|
|
||||||
|
client.unbind();
|
||||||
|
|
||||||
|
maildb.update(DOMAIN_0.domain, { enabled: false }, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('user sendmail bind', function () {
|
describe('user sendmail bind', function () {
|
||||||
it('email disabled - cannot find domain email', function (done) {
|
it('email disabled - cannot find domain email', function (done) {
|
||||||
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
|
||||||
|
|||||||
Reference in New Issue
Block a user