diff --git a/src/addons.js b/src/addons.js index c9610dfe6..127bc201e 100644 --- a/src/addons.js +++ b/src/addons.js @@ -114,7 +114,7 @@ var RMAPPDIR_CMD = path.join(__dirname, 'scripts/rmappdir.sh'); function debugApp(app, args) { assert(!app || typeof app === 'object'); - var prefix = app ? config.appFqdn(app) : '(no app)'; + var prefix = app ? app.intrinsicFqdn : '(no app)'; debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); } @@ -250,7 +250,7 @@ function setupOauth(app, options, callback) { if (!app.sso) return callback(null); var appId = app.id; - var redirectURI = 'https://' + (app.altDomain || config.appFqdn(app)); + var redirectURI = 'https://' + (app.altDomain || app.intrinsicFqdn); var scope = 'profile'; clients.delByAppIdAndType(appId, clients.TYPE_OAUTH, function (error) { // remove existing creds @@ -647,7 +647,7 @@ function setupRedis(app, options, callback) { } const tag = infra.images.redis.tag, redisName = 'redis-' + app.id; - const label = config.appFqdn(app); + const label = app.intrinsicFqdn; // note that we do not add appId label because this interferes with the stop/start app logic const cmd = `docker run --restart=always -d --name=${redisName} \ --label=location=${label} \ diff --git a/src/apphealthmonitor.js b/src/apphealthmonitor.js index 7cb038035..0c047dd97 100644 --- a/src/apphealthmonitor.js +++ b/src/apphealthmonitor.js @@ -26,7 +26,7 @@ var gDockerEventStream = null; function debugApp(app) { assert(!app || typeof app === 'object'); - var prefix = app ? config.appFqdn(app) : '(no app)'; + var prefix = app ? app.intrinsicFqdn : '(no app)'; var manifestAppId = app ? app.manifest.id : ''; var id = app ? app.id : ''; diff --git a/src/apps.js b/src/apps.js index 45698a8c4..69d8e13cc 100644 --- a/src/apps.js +++ b/src/apps.js @@ -60,6 +60,9 @@ var addons = require('./addons.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:apps'), docker = require('./docker.js'), + domaindb = require('./domaindb.js'), + domains = require('./domains.js'), + DomainError = require('./domains.js').DomainError, eventlog = require('./eventlog.js'), fs = require('fs'), groups = require('./groups.js'), @@ -119,22 +122,18 @@ AppsError.BAD_CERTIFICATE = 'Invalid certificate'; // Domain name validation comes from RFC 2181 (Name syntax) // https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names // We are validating the validity of the location-fqdn as host name (and not dns name) -function validateHostname(location, domain) { +function validateHostname(location, domain, hostname) { assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof domain, 'string'); - var hostname = config.appFqdn({ location: location, domain: domain }); - if (!hostname) return new AppsError(AppsError.BAD_FIELD, 'hostname cannot be empty'); - const RESERVED_LOCATIONS = [ - config.adminFqdn(), - config.appFqdn({ location: constants.API_LOCATION, domain: config.fqdn() }), - config.appFqdn({ location: constants.SMTP_LOCATION, domain: config.fqdn() }), - config.appFqdn({ location: constants.IMAP_LOCATION, domain: config.fqdn() }), - config.mailFqdn(), - config.appFqdn({ location: constants.POSTMAN_LOCATION, domain: config.fqdn() }) + config.adminLocation(), // FIXME: this shouldn't be here + constants.API_LOCATION, + constants.SMTP_LOCATION, + constants.IMAP_LOCATION, + constants.POSTMAN_LOCATION ]; - if (RESERVED_LOCATIONS.indexOf(hostname) !== -1) return new AppsError(AppsError.BAD_FIELD, hostname + ' is reserved'); + if (RESERVED_LOCATIONS.indexOf(location) !== -1) return new AppsError(AppsError.BAD_FIELD, location + ' is reserved'); // workaround https://github.com/oncletom/tld.js/issues/73 var tmp = hostname.replace('_', '-'); @@ -311,6 +310,7 @@ function getAppConfig(app) { manifest: app.manifest, location: app.location, domain: app.domain, + intrinsicFqdn: app.intrinsicFqdn, accessRestriction: app.accessRestriction, portBindings: app.portBindings, memoryLimit: app.memoryLimit, @@ -358,11 +358,16 @@ function get(appId, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app')); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - app.iconUrl = getIconUrlSync(app); - app.fqdn = app.altDomain || config.appFqdn(app); - app.cnameTarget = app.altDomain ? config.appFqdn(app) : null; + domaindb.get(app.domain, function (error, result) { + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - callback(null, app); + app.intrinsicFqdn = app.location + (result.provider === 'caas' ? '-' : '.') + app.domain; + app.iconUrl = getIconUrlSync(app); + app.fqdn = app.altDomain || app.intrinsicFqdn; + app.cnameTarget = app.altDomain ? app.intrinsicFqdn : null; + + callback(null, app); + }); }); } @@ -377,11 +382,16 @@ function getByIpAddress(ip, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app')); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - app.iconUrl = getIconUrlSync(app); - app.fqdn = app.altDomain || config.appFqdn(app); - app.cnameTarget = app.altDomain ? config.appFqdn(app) : null; + domaindb.get(app.domain, function (error, result) { + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - callback(null, app); + app.intrinsicFqdn = app.location + (result.provider === 'caas' ? '-' : '.') + app.domain; + app.iconUrl = getIconUrlSync(app); + app.fqdn = app.altDomain || app.intrinsicFqdn; + app.cnameTarget = app.altDomain ? app.intrinsicFqdn : null; + + callback(null, app); + }); }); }); } @@ -392,13 +402,22 @@ function getAll(callback) { appdb.getAll(function (error, apps) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - apps.forEach(function (app) { - app.iconUrl = getIconUrlSync(app); - app.fqdn = app.altDomain || config.appFqdn(app); - app.cnameTarget = app.altDomain ? config.appFqdn(app) : null; - }); + async.eachSeries(apps, function (app, iteratorDone) { + domaindb.get(app.domain, function (error, result) { + if (error) return iteratorDone(new AppsError(AppsError.INTERNAL_ERROR, error)); - callback(null, apps); + app.intrinsicFqdn = app.location + (result.provider === 'caas' ? '-' : '.') + app.domain; + app.iconUrl = getIconUrlSync(app); + app.fqdn = app.altDomain || app.intrinsicFqdn; + app.cnameTarget = app.altDomain ? app.intrinsicFqdn : null; + + iteratorDone(); + }); + }, function (error) { + if (error) return callback(error); + + callback(null, apps); + }); }); } @@ -468,9 +487,6 @@ function install(data, auditSource, callback) { error = checkManifestConstraints(manifest); if (error) return callback(error); - error = validateHostname(location, domain); - if (error) return callback(error); - error = validatePortBindings(portBindings, manifest.tcpPorts); if (error) return callback(error); @@ -508,45 +524,56 @@ function install(data, auditSource, callback) { } } - error = certificates.validateCertificate(cert, key, config.appFqdn({ domain: domain, location: location })); - if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); + domains.get(domain, function (error, domainObject) { + if (error && error.reason === DomainError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain')); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message)); - debug('Will install app with id : ' + appId); + var intrinsicFqdn = location + (domainObject.provider === 'caas' ? '-' : '.') + domain; - appstore.purchase(appId, appStoreId, function (error) { - if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND)); - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); - if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); - if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + error = validateHostname(location, domain, intrinsicFqdn); + if (error) return callback(error); - var data = { - accessRestriction: accessRestriction, - memoryLimit: memoryLimit, - altDomain: altDomain, - xFrameOptions: xFrameOptions, - sso: sso, - debugMode: debugMode, - mailboxName: (location ? location : manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app', - restoreConfig: backupId ? { backupId: backupId, backupFormat: backupFormat } : null, - enableBackup: enableBackup, - robotsTxt: robotsTxt - }; + error = certificates.validateCertificate(cert, key, intrinsicFqdn); + if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); - appdb.add(appId, appStoreId, manifest, location, domain, portBindings, data, function (error) { - if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); + debug('Will install app with id : ' + appId); + + appstore.purchase(appId, appStoreId, function (error) { + if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND)); + if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - // save cert to boxdata/certs - if (cert && key) { - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn({ domain: domain, location: location }) + '.user.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn({ domain: domain, location: location }) + '.user.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); - } + var data = { + accessRestriction: accessRestriction, + memoryLimit: memoryLimit, + altDomain: altDomain, + xFrameOptions: xFrameOptions, + sso: sso, + debugMode: debugMode, + mailboxName: (location ? location : manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app', + restoreConfig: backupId ? { backupId: backupId, backupFormat: backupFormat } : null, + enableBackup: enableBackup, + robotsTxt: robotsTxt, + intrinsicFqdn: intrinsicFqdn + }; - taskmanager.restartAppTask(appId); + appdb.add(appId, appStoreId, manifest, location, domain, portBindings, data, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, location: location, domain: domain, manifest: manifest, backupId: backupId }); + // save cert to boxdata/certs + if (cert && key) { + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); + } - callback(null, { id : appId }); + taskmanager.restartAppTask(appId); + + eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, location: location, domain: domain, manifest: manifest, backupId: backupId }); + + callback(null, { id : appId }); + }); }); }); }); @@ -569,9 +596,6 @@ function configure(appId, data, auditSource, callback) { if ('domain' in data) domain = values.domain = data.domain.toLowerCase(); else domain = app.domain; - error = validateHostname(location, domain); - if (error) return callback(error); - if ('accessRestriction' in data) { values.accessRestriction = data.accessRestriction; error = validateAccessRestriction(values.accessRestriction); @@ -615,43 +639,55 @@ function configure(appId, data, auditSource, callback) { if (error) return callback(error); } - // save cert to boxdata/certs. TODO: move this to apptask when we have a real task queue - if ('cert' in data && 'key' in data) { - if (data.cert && data.key) { - error = certificates.validateCertificate(data.cert, data.key, config.appFqdn({ domain: domain, location: location })); - if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); + domains.get(domain, function (error, domainObject) { + if (error && error.reason === DomainError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain')); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn({ domain: domain, location: location }) + '.user.cert'), data.cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn({ domain: domain, location: location }) + '.user.key'), data.key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); - } else { // remove existing cert/key - if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, config.appFqdn({ domain: domain, location: location }) + '.user.cert'))) debug('Error removing cert: ' + safe.error.message); - if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, config.appFqdn({ domain: domain, location: location }) + '.user.key'))) debug('Error removing key: ' + safe.error.message); + var intrinsicFqdn = location + (domainObject.provider === 'caas' ? '-' : '.') + domain; + + values.intrinsicFqdn = intrinsicFqdn; + + error = validateHostname(location, domain, intrinsicFqdn); + if (error) return callback(error); + + // save cert to boxdata/certs. TODO: move this to apptask when we have a real task queue + if ('cert' in data && 'key' in data) { + if (data.cert && data.key) { + error = certificates.validateCertificate(data.cert, data.key, intrinsicFqdn); + if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); + + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.cert'), data.cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.key'), data.key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); + } else { // remove existing cert/key + if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.cert'))) debug('Error removing cert: ' + safe.error.message); + if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, intrinsicFqdn + '.user.key'))) debug('Error removing key: ' + safe.error.message); + } } - } - if ('enableBackup' in data) values.enableBackup = data.enableBackup; + if ('enableBackup' in data) values.enableBackup = data.enableBackup; - values.oldConfig = getAppConfig(app); + values.oldConfig = getAppConfig(app); - debug('Will configure app with id:%s values:%j', appId, values); + debug('Will configure app with id:%s values:%j', appId, values); - var oldName = (app.location ? app.location : app.manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app'; - var newName = (location ? location : app.manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app'; - mailboxdb.updateName(oldName, values.oldConfig.domain, newName, domain, function (error) { - if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new AppsError(AppsError.ALREADY_EXISTS, 'This mailbox is already taken')); - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); - if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - - appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_CONFIGURE, values, function (error) { - if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); + var oldName = (app.location ? app.location : app.manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app'; + var newName = (location ? location : app.manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app'; + mailboxdb.updateName(oldName, values.oldConfig.domain, newName, domain, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new AppsError(AppsError.ALREADY_EXISTS, 'This mailbox is already taken')); if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - taskmanager.restartAppTask(appId); + appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_CONFIGURE, values, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId }); + taskmanager.restartAppTask(appId); - callback(null); + eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId }); + + callback(null); + }); }); }); }); @@ -1130,13 +1166,13 @@ function restoreInstalledApps(callback) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); async.map(apps, function (app, iteratorDone) { - debug('marking %s for restore', config.appFqdn(app)); + debug('marking %s for restore', app.intrinsicFqdn); backups.getByAppIdPaged(1, 1, app.id, function (error, results) { var restoreConfig = !error && results.length ? { backupId: results[0].id, backupFormat: results[0].format } : null; appdb.setInstallationCommand(app.id, appdb.ISTATE_PENDING_RESTORE, { restoreConfig: restoreConfig, oldConfig: null }, function (error) { - if (error) debug('did not mark %s for restore', config.appFqdn(app), error); + if (error) debug('did not mark %s for restore', app.intrinsicFqdn, error); iteratorDone(); // always succeed }); @@ -1152,10 +1188,10 @@ function configureInstalledApps(callback) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); async.map(apps, function (app, iteratorDone) { - debug('marking %s for reconfigure', config.appFqdn(app)); + debug('marking %s for reconfigure', app.intrinsicFqdn); appdb.setInstallationCommand(app.id, appdb.ISTATE_PENDING_CONFIGURE, { oldConfig: null }, function (error) { - if (error) debug('did not mark %s for reconfigure', config.appFqdn(app), error); + if (error) debug('did not mark %s for reconfigure', app.intrinsicFqdn, error); iteratorDone(); // always succeed }); diff --git a/src/apptask.js b/src/apptask.js index 9e2b28580..a4b5e53bb 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -72,7 +72,7 @@ function initialize(callback) { function debugApp(app) { assert.strictEqual(typeof app, 'object'); - var prefix = app ? (config.appFqdn(app) || '(bare)') : '(no app)'; + var prefix = app ? (app.intrinsicFqdn || '(bare)') : '(no app)'; debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); } @@ -271,7 +271,7 @@ function registerSubdomain(app, overwrite, callback) { if (error) return callback(error); async.retry({ times: 200, interval: 5000 }, function (retryCallback) { - debugApp(app, 'Registering subdomain location [%s] overwrite: %s', config.appFqdn(app), overwrite); + debugApp(app, 'Registering subdomain location [%s] overwrite: %s', app.intrinsicFqdn, overwrite); // get the current record before updating it domains.getDNSRecords(app.location, app.domain, 'A', function (error, values) { @@ -320,7 +320,7 @@ function unregisterSubdomain(app, location, domain, callback) { if (error) return callback(error); async.retry({ times: 30, interval: 5000 }, function (retryCallback) { - debugApp(app, 'Unregistering subdomain: %s', config.appFqdn({ domain: domain, location: location })); + debugApp(app, 'Unregistering subdomain: %s', app.intrinsicFqdn); domains.removeDNSRecords(location, domain, 'A', [ ip ], function (error) { if (error && (error.reason === DomainError.STILL_BUSY || error.reason === DomainError.EXTERNAL_ERROR)) return retryCallback(error); // try again @@ -357,7 +357,7 @@ function waitForDnsPropagation(app, callback) { sysinfo.getPublicIp(function (error, ip) { if (error) return callback(error); - domains.waitForDNSRecord(config.appFqdn(app), app.domain, ip, 'A', { interval: 5000, times: 120 }, callback); + domains.waitForDNSRecord(app.intrinsicFqdn, app.domain, ip, 'A', { interval: 5000, times: 120 }, callback); }); } @@ -374,7 +374,7 @@ function waitForAltDomainDnsPropagation(app, callback) { domains.waitForDNSRecord(app.altDomain, tld.getDomain(app.altDomain), ip, 'A', { interval: 10000, times: 60 }, callback); }); } else { - domains.waitForDNSRecord(app.altDomain, tld.getDomain(app.altDomain), config.appFqdn(app) + '.', 'CNAME', { interval: 10000, times: 60 }, callback); + domains.waitForDNSRecord(app.altDomain, tld.getDomain(app.altDomain), app.intrinsicFqdn + '.', 'CNAME', { interval: 10000, times: 60 }, callback); } } @@ -508,7 +508,7 @@ function configure(app, callback) { assert.strictEqual(typeof callback, 'function'); // oldConfig can be null during an infra update - var locationChanged = app.oldConfig && (config.appFqdn(app.oldConfig) !== config.appFqdn(app)); + var locationChanged = app.oldConfig && (app.oldConfig.intrinsicFqdn !== app.intrinsicFqdn); async.series([ updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }), @@ -776,7 +776,7 @@ function startTask(appId, callback) { assert.strictEqual(typeof callback, 'function'); // determine what to do - appdb.get(appId, function (error, app) { + apps.get(appId, function (error, app) { if (error) return callback(error); debugApp(app, 'startTask installationState: %s runState: %s', app.installationState, app.runState); diff --git a/src/backups.js b/src/backups.js index 5e0b7f423..fab3895ee 100644 --- a/src/backups.js +++ b/src/backups.js @@ -70,7 +70,7 @@ var BACKUPTASK_CMD = path.join(__dirname, 'backuptask.js'); function debugApp(app) { assert(!app || typeof app === 'object'); - var prefix = app ? config.appFqdn(app) : '(no app)'; + var prefix = app ? app.intrinsicFqdn : '(no app)'; debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); } @@ -720,7 +720,7 @@ function backupApp(app, callback) { const timestamp = (new Date()).toISOString().replace(/[T.]/g, '-').replace(/[:Z]/g,''); safe.fs.unlinkSync(paths.BACKUP_LOG_FILE); // start fresh log file - progress.set(progress.BACKUP, 10, 'Backing up ' + (app.altDomain || config.appFqdn(app))); + progress.set(progress.BACKUP, 10, 'Backing up ' + (app.altDomain || app.intrinsicFqdn)); backupAppWithTimestamp(app, timestamp, function (error) { progress.set(progress.BACKUP, 100, error ? error.message : ''); @@ -747,12 +747,12 @@ function backupBoxAndApps(auditSource, callback) { var step = 100/(allApps.length+2); async.mapSeries(allApps, function iterator(app, iteratorCallback) { - progress.set(progress.BACKUP, step * processed, 'Backing up ' + (app.altDomain || config.appFqdn(app))); + progress.set(progress.BACKUP, step * processed, 'Backing up ' + (app.altDomain || app.intrinsicFqdn)); ++processed; if (!app.enableBackup) { - progress.set(progress.BACKUP, step * processed, 'Skipped backup ' + (app.altDomain || config.appFqdn(app))); + progress.set(progress.BACKUP, step * processed, 'Skipped backup ' + (app.altDomain || app.intrinsicFqdn)); return iteratorCallback(null, null); // nothing to backup } @@ -762,7 +762,7 @@ function backupBoxAndApps(auditSource, callback) { return iteratorCallback(error); } - progress.set(progress.BACKUP, step * processed, 'Backed up ' + (app.altDomain || config.appFqdn(app))); + progress.set(progress.BACKUP, step * processed, 'Backed up ' + (app.altDomain || app.intrinsicFqdn)); iteratorCallback(null, backupId || null); // clear backupId if is in BAD_STATE and never backed up }); diff --git a/src/certificates.js b/src/certificates.js index afc9a1da5..f9718b4f9 100644 --- a/src/certificates.js +++ b/src/certificates.js @@ -174,11 +174,11 @@ function renewAll(auditSource, callback) { apps.getAll(function (error, allApps) { if (error) return callback(error); - allApps.push({ location: config.adminLocation(), domain: config.fqdn() }); // inject fake webadmin app + allApps.push({ intrinsicFqdn: config.adminFqdn() }); // inject fake webadmin app var expiringApps = [ ]; for (var i = 0; i < allApps.length; i++) { - var appDomain = allApps[i].altDomain || config.appFqdn(allApps[i]); + var appDomain = allApps[i].altDomain || allApps[i].instrincFqdn; var certFilePath = path.join(paths.APP_CERTS_DIR, appDomain + '.user.cert'); var keyFilePath = path.join(paths.APP_CERTS_DIR, appDomain + '.user.key'); @@ -202,10 +202,10 @@ function renewAll(auditSource, callback) { } } - debug('renewAll: %j needs to be renewed', expiringApps.map(function (app) { return app.altDomain || config.appFqdn(app); })); + debug('renewAll: %j needs to be renewed', expiringApps.map(function (app) { return app.altDomain || app.intrinsicFqdn; })); async.eachSeries(expiringApps, function iterator(app, iteratorCallback) { - var domain = app.altDomain || config.appFqdn(app); + var domain = app.altDomain || app.intrinsicFqdn; getApi(app, function (error, api, apiOptions) { if (error) return callback(error); @@ -240,7 +240,7 @@ function renewAll(auditSource, callback) { } // reconfigure and reload nginx. this is required for the case where we got a renewed cert after fallback - var configureFunc = config.appFqdn(app) === config.adminFqdn() ? + var configureFunc = app.intrinsicFqdn === config.adminFqdn() ? nginx.configureAdmin.bind(null, certFilePath, keyFilePath, constants.NGINX_ADMIN_CONFIG_FILE_NAME, config.adminFqdn()) : nginx.configureApp.bind(null, app, certFilePath, keyFilePath); @@ -402,7 +402,7 @@ function ensureCertificate(app, callback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof callback, 'function'); - var domain = app.altDomain || config.appFqdn(app); + var domain = app.altDomain || app.intrinsicFqdn; var certFilePath = path.join(paths.APP_CERTS_DIR, domain + '.user.cert'); var keyFilePath = path.join(paths.APP_CERTS_DIR, domain + '.user.key'); diff --git a/src/cloudron.js b/src/cloudron.js index fb8049e93..39801614b 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -279,7 +279,7 @@ function configureWebadmin(callback) { function configureNginx(error) { debug('configureNginx: dns update: %j', error || {}); - certificates.ensureCertificate({ domain: config.fqdn(), location: config.adminLocation() }, function (error, certFilePath, keyFilePath) { + certificates.ensureCertificate({ domain: config.fqdn(), location: config.adminLocation(), intrinsicFqdn: config.adminFqdn() }, function (error, certFilePath, keyFilePath) { if (error) return done(error); gWebadminStatus.tls = true; diff --git a/src/config.js b/src/config.js index 5c9117fb7..5233af5e1 100644 --- a/src/config.js +++ b/src/config.js @@ -33,7 +33,6 @@ exports = module.exports = { adminFqdn: adminFqdn, mailLocation: mailLocation, mailFqdn: mailFqdn, - appFqdn: appFqdn, setZoneName: setZoneName, hasIPv6: hasIPv6, dkimSelector: dkimSelector, diff --git a/src/docker.js b/src/docker.js index c8d566f13..b698d7b35 100644 --- a/src/docker.js +++ b/src/docker.js @@ -51,7 +51,7 @@ var addons = require('./addons.js'), function debugApp(app, args) { assert(!app || typeof app === 'object'); - var prefix = app ? config.appFqdn(app) : '(no app)'; + var prefix = app ? app.intrinsicFqdn : '(no app)'; debug(prefix + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); } @@ -129,7 +129,7 @@ function createSubcontainer(app, name, cmd, options, callback) { var manifest = app.manifest; var exposedPorts = {}, dockerPortBindings = { }; - var domain = app.altDomain || config.appFqdn(app); + var domain = app.altDomain || app.intrinsicFqdn; var stdEnv = [ 'CLOUDRON=1', 'WEBADMIN_ORIGIN=' + config.adminOrigin(), @@ -186,7 +186,7 @@ function createSubcontainer(app, name, cmd, options, callback) { '/run': {} }, Labels: { - 'fqdn': config.appFqdn(app), + 'fqdn': app.intrinsicFqdn, 'appId': app.id, 'isSubcontainer': String(!isAppContainer) }, diff --git a/src/nginx.js b/src/nginx.js index 878c6d178..99b5a9116 100644 --- a/src/nginx.js +++ b/src/nginx.js @@ -55,7 +55,7 @@ function configureApp(app, certFilePath, keyFilePath, callback) { var sourceDir = path.resolve(__dirname, '..'); var endpoint = 'app'; - var vhost = app.altDomain || config.appFqdn(app); + var vhost = app.altDomain || app.intrinsicFqdn; var data = { sourceDir: sourceDir, @@ -86,7 +86,7 @@ function unconfigureApp(app, callback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof callback, 'function'); - var vhost = app.altDomain || config.appFqdn(app); + var vhost = app.altDomain || app.intrinsicFqdn; var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf'); if (!safe.fs.unlinkSync(nginxConfigFilename)) { diff --git a/src/routes/test/apps-test.js b/src/routes/test/apps-test.js index 612f721aa..31178ba83 100644 --- a/src/routes/test/apps-test.js +++ b/src/routes/test/apps-test.js @@ -47,7 +47,9 @@ var TEST_IMAGE = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG; var APP_STORE_ID = 'test', APP_ID; var APP_LOCATION = 'appslocation'; +var APP_DOMAIN = 'example-apps-test.com'; var APP_LOCATION_2 = 'appslocationtwo'; +var APP_DOMAIN_2 = 'example-apps-test.com'; var APP_LOCATION_NEW = 'appslocationnew'; var APP_MANIFEST = JSON.parse(fs.readFileSync(__dirname + '/../../../../test-app/CloudronManifest.json', 'utf8')); @@ -149,7 +151,7 @@ function startBox(done) { safe.fs.unlinkSync(paths.INFRA_VERSION_FILE); child_process.execSync('docker ps -qa | xargs --no-run-if-empty docker rm -f'); - config.setFqdn('example-apps-test.com'); + config.setFqdn(APP_DOMAIN); config.setZoneName('foobar.com'); awsHostedZones = { @@ -722,8 +724,8 @@ describe('App installation', function () { expect(data.Config.Env).to.contain('WEBADMIN_ORIGIN=' + config.adminOrigin()); expect(data.Config.Env).to.contain('API_ORIGIN=' + config.adminOrigin()); expect(data.Config.Env).to.contain('CLOUDRON=1'); - expect(data.Config.Env).to.contain('APP_ORIGIN=https://' + config.appFqdn(APP_LOCATION)); - expect(data.Config.Env).to.contain('APP_DOMAIN=' + config.appFqdn(APP_LOCATION)); + expect(data.Config.Env).to.contain('APP_ORIGIN=https://' + APP_LOCATION + '.' + APP_DOMAIN); + expect(data.Config.Env).to.contain('APP_DOMAIN=' + APP_LOCATION + '.' + APP_DOMAIN); // Hostname must not be set of app fqdn or app location! expect(data.Config.Hostname).to.not.contain(APP_LOCATION); expect(data.Config.Env).to.contain('ECHO_SERVER_PORT=7171'); diff --git a/src/taskmanager.js b/src/taskmanager.js index fea215812..c76ad85cd 100644 --- a/src/taskmanager.js +++ b/src/taskmanager.js @@ -48,7 +48,7 @@ function resumeTasks(callback) { if (app.installationState === appdb.ISTATE_ERROR) return; - debug('Creating process for %s (%s) with state %s', config.appFqdn(app), app.id, app.installationState); + debug('Creating process for %s (%s) with state %s', app.intrinsicFqdn, app.id, app.installationState); restartAppTask(app.id, NOOP_CALLBACK); // restart because the auto-installer could have queued up tasks already }); diff --git a/src/test/apps-test.js b/src/test/apps-test.js index 5595ab93c..148b065e7 100644 --- a/src/test/apps-test.js +++ b/src/test/apps-test.js @@ -161,35 +161,35 @@ describe('Apps', function () { describe('validateHostname', function () { it('does not allow admin subdomain', function () { - expect(apps._validateHostname('my', 'example.com')).to.be.an(Error); + expect(apps._validateHostname('my', 'example.com', 'my.example.com')).to.be.an(Error); }); it('cannot have >63 length subdomains', function () { var s = ''; for (var i = 0; i < 64; i++) s += 's'; - expect(apps._validateHostname(s, 'example.com')).to.be.an(Error); + expect(apps._validateHostname(s, 'example.com', s + '.example.com')).to.be.an(Error); }); it('allows only alphanumerics and hypen', function () { - expect(apps._validateHostname('#2r', 'example.com')).to.be.an(Error); - expect(apps._validateHostname('a%b', 'example.com')).to.be.an(Error); - expect(apps._validateHostname('ab_', 'example.com')).to.be.an(Error); - expect(apps._validateHostname('a.b', 'example.com')).to.be.an(Error); - expect(apps._validateHostname('-ab', 'example.com')).to.be.an(Error); - expect(apps._validateHostname('ab-', 'example.com')).to.be.an(Error); + expect(apps._validateHostname('#2r', 'example.com', '#2r.example.com')).to.be.an(Error); + expect(apps._validateHostname('a%b', 'example.com', 'a%b.example.com')).to.be.an(Error); + expect(apps._validateHostname('ab_', 'example.com', 'ab_.example.com')).to.be.an(Error); + expect(apps._validateHostname('a.b', 'example.com', 'a.b.example.com')).to.be.an(Error); + expect(apps._validateHostname('-ab', 'example.com', '-ab.example.com')).to.be.an(Error); + expect(apps._validateHostname('ab-', 'example.com', 'ab-.example.com')).to.be.an(Error); }); it('total length cannot exceed 255', function () { var s = ''; for (var i = 0; i < (255 - 'example.com'.length); i++) s += 's'; - expect(apps._validateHostname(s, 'example.com')).to.be.an(Error); + expect(apps._validateHostname(s, 'example.com', s + '.example.com')).to.be.an(Error); }); it('allow valid domains', function () { - expect(apps._validateHostname('a', 'example.com')).to.be(null); - expect(apps._validateHostname('a0-x', 'example.com')).to.be(null); - expect(apps._validateHostname('01', 'example.com')).to.be(null); + expect(apps._validateHostname('a', 'example.com', 'a.example.com')).to.be(null); + expect(apps._validateHostname('a0-x', 'example.com', 'a0-x.example.com')).to.be(null); + expect(apps._validateHostname('01', 'example.com', '01.example.com')).to.be(null); }); }); diff --git a/src/test/apptask-test.js b/src/test/apptask-test.js index 803a0f00f..3cc44f0e1 100644 --- a/src/test/apptask-test.js +++ b/src/test/apptask-test.js @@ -67,6 +67,8 @@ var APP = { runState: null, location: 'applocation', domain: DOMAIN_0.domain, + intrinsicFqdn: DOMAIN_0.domain + '.' + 'applocation', + fqdn: DOMAIN_0.domain + '.' + 'applocation', manifest: MANIFEST, containerId: null, httpPort: 4567, @@ -101,6 +103,7 @@ describe('apptask', function () { async.series([ database.initialize, + database._clear, domains.update.bind(null, DOMAIN_0.domain, DOMAIN_0.provider, DOMAIN_0.config, null), appdb.add.bind(null, APP.id, APP.appStoreId, APP.manifest, APP.location, APP.domain, APP.portBindings, APP), settings.initialize, diff --git a/src/test/config-test.js b/src/test/config-test.js index 370b27696..eb4e3bb2f 100644 --- a/src/test/config-test.js +++ b/src/test/config-test.js @@ -68,7 +68,6 @@ describe('config', function () { expect(config.isCustomDomain()).to.equal(true); expect(config.fqdn()).to.equal('example.com'); expect(config.adminOrigin()).to.equal('https://my.example.com'); - expect(config.appFqdn({ location: 'app', domain: config.fqdn() })).to.equal('app.example.com'); expect(config.zoneName()).to.equal('example.com'); }); @@ -79,7 +78,6 @@ describe('config', function () { expect(config.isCustomDomain()).to.equal(false); expect(config.fqdn()).to.equal('test.example.com'); expect(config.adminOrigin()).to.equal('https://my-test.example.com'); - expect(config.appFqdn({ location: 'app', domain: config.fqdn() })).to.equal('app-test.example.com'); expect(config.zoneName()).to.equal('example.com'); });