diff --git a/run-tests b/run-tests index c6954371b..dad56ffa4 100755 --- a/run-tests +++ b/run-tests @@ -24,7 +24,9 @@ cd ${DATA_DIR} mkdir -p appsdata mkdir -p boxdata/box boxdata/mail boxdata/certs boxdata/mail/dkim/localhost boxdata/mail/dkim/foobar.com mkdir -p platformdata/addons/mail/banner platformdata/nginx/cert platformdata/nginx/applications/dashboard platformdata/collectd/collectd.conf.d platformdata/addons platformdata/logrotate.d platformdata/backup platformdata/logs/tasks platformdata/sftp/ssh platformdata/firewall platformdata/update platformdata/diskusage -sudo mkdir -p /mnt/cloudron-test-music /media/cloudron-test-music # volume test +sudo mkdir -p /mnt/cloudron-test-music /media/cloudron-test-music /mnt/cloudron-test-music2 # volume test +sudo ln -sf /root /media/cloudron-test-music/root +sudo touch /media/cloudron-test-music/file # put cert echo "=> Generating a localhost selfsigned cert" diff --git a/src/test/branding-test.js b/src/test/branding-test.js index 783b2438d..25c71d9f1 100644 --- a/src/test/branding-test.js +++ b/src/test/branding-test.js @@ -49,6 +49,6 @@ describe('Branding', function () { it('can render footer with YEAR', async function () { await branding.setFooter('BigFoot Inc %YEAR%', auditSource); - expect(await branding.renderFooter()).to.be('BigFoot Inc 2024'); + expect(await branding.renderFooter()).to.be('BigFoot Inc 2025'); }); }); diff --git a/src/test/volumes-test.js b/src/test/volumes-test.js index f4c103c06..56f26a218 100644 --- a/src/test/volumes-test.js +++ b/src/test/volumes-test.js @@ -9,6 +9,7 @@ const BoxError = require('../boxerror.js'), common = require('./common.js'), expect = require('expect.js'), + paths = require('../paths.js'), safe = require('safetydance'), volumes = require('../volumes.js'); @@ -73,4 +74,26 @@ describe('Volumes', function () { it('can del volume', async function () { await volumes.del(volume, auditSource); }); + + const badPaths = [ + '/opt/data/../bar', // not normalized + 'opt/data/bar', // not absolute + '/', // root + '/mnt', // top level itself is reserved + '/usr/bin', // reserved + paths.VOLUMES_MOUNT_DIR, // reserved + paths.VOLUMES_MOUNT_DIR + '/', // also reserved + paths.VOLUMES_MOUNT_DIR + '/something', // also reserved + '/media/cloudron-test-music/root', // realpath won't match + '/media/cloudron-test-music/root/', // realpath won't match + '/media/cloudron-test-music/file', // need directory + '/media/cloudron-test-music/randompath', // need directory + ]; + + for (const p of badPaths) { + it(`validateHostPath - ${p}`, function () { + const error = volumes._validateHostPath(p); + expect(error.reason).to.be(BoxError.BAD_FIELD); + }); + } }); diff --git a/src/volumes.js b/src/volumes.js index 12f75d3a4..b7d76a06c 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -10,7 +10,10 @@ exports = module.exports = { getStatus, removePrivateFields, - mountAll + mountAll, + + // exported for testing + _validateHostPath: validateHostPath }; const assert = require('assert'), @@ -45,9 +48,8 @@ function validateName(name) { return null; } -function validateHostPath(hostPath, mountType) { +function validateHostPath(hostPath) { assert.strictEqual(typeof hostPath, 'string'); - assert.strictEqual(typeof mountType, 'string'); if (path.normalize(hostPath) !== hostPath) return new BoxError(BoxError.BAD_FIELD, 'hostPath must contain a normalized path'); if (!path.isAbsolute(hostPath)) return new BoxError(BoxError.BAD_FIELD, 'hostPath must be an absolute path'); @@ -57,16 +59,14 @@ function validateHostPath(hostPath, mountType) { if (hostPath === '/') return new BoxError(BoxError.BAD_FIELD, 'hostPath cannot be /'); const allowedPaths = [ '/mnt', '/media', '/srv', '/opt' ]; - if (!allowedPaths.some(p => hostPath === p || hostPath.startsWith(p + '/'))) return new BoxError(BoxError.BAD_FIELD, 'hostPath must be under /mnt, /media, /opt or /srv'); + if (!allowedPaths.some(p => hostPath.startsWith(p + '/'))) return new BoxError(BoxError.BAD_FIELD, 'hostPath must be under /mnt, /media, /opt or /srv'); const reservedPaths = [ `${paths.VOLUMES_MOUNT_DIR}` ]; if (reservedPaths.some(p => hostPath === p || hostPath.startsWith(p + '/'))) return new BoxError(BoxError.BAD_FIELD, 'hostPath is reserved'); - if (!constants.TEST) { // we expect user to have already mounted this - const stat = safe.fs.lstatSync(hostPath); - if (!stat) return new BoxError(BoxError.BAD_FIELD, 'hostPath does not exist. Please create it on the server first'); - if (!stat.isDirectory()) return new BoxError(BoxError.BAD_FIELD, 'hostPath is not a directory'); - } + const stat = safe.fs.lstatSync(hostPath); + if (!stat) return new BoxError(BoxError.BAD_FIELD, 'hostPath does not exist. Please create it on the server first'); + if (!stat.isDirectory()) return new BoxError(BoxError.BAD_FIELD, 'hostPath is not a directory'); return null; } @@ -87,7 +87,7 @@ async function add(volume, auditSource) { let hostPath; if (mountType === 'mountpoint' || mountType === 'filesystem') { - error = validateHostPath(mountOptions.hostPath, mountType); + error = validateHostPath(mountOptions.hostPath); if (error) throw error; hostPath = mountOptions.hostPath; } else {