Free up port 53

It's all very complicated.

Approach 1: Simple move unbound to not listen on 0.0.0.0 and only the internal
ones. However, docker has no way to bind only to the "public" interface.

Approach 2: Move the internal unbound to some other port. This required a PR
for haraka - https://github.com/haraka/Haraka/pull/2863 . This works and we use
systemd-resolved by default. However, it turns out systemd-resolved with hog the
lo and thus docker cannot bind again to port 53.

Approach 3: Get rid of systemd-resolved and try to put the dns server list in
/etc/resolv.conf. This is surprisingly hard because the DNS listing can come from
DHCP or netplan or wherever. We can hardcode some public DNS servers but this seems
not a good idea for privacy.

Approach 4: So maybe we don't move the unbound away to different port after all.
However, all the work for approach 2 is done and it's quite nice that the default
resolver is used with the default dns server of the network (probably a caching
server + also maybe has some home network firewalled dns).

So, the final solution is to bind to the make docker bind to the IP explicity.
It's unclear what will happen if the IP changes, maybe it needs a restart.
This commit is contained in:
Girish Ramakrishnan
2020-11-18 11:43:28 -08:00
parent ae94ff1432
commit bd9c664b1a
16 changed files with 37 additions and 47 deletions

View File

@@ -2137,4 +2137,5 @@
* Apps can optionally request an authwall to be installed in front of them
* mailbox can now owned by a group
* linode: enable dns provider in setup view
* dns: apps can now use the dns port

View File

@@ -28,7 +28,6 @@ debconf-set-selections <<< 'mysql-server mysql-server/root_password password pas
debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password password'
# this enables automatic security upgrades (https://help.ubuntu.com/community/AutomaticSecurityUpdates)
# resolvconf is needed for unbound to work property after disabling systemd-resolved in 18.04
gpg_package=$([[ "${ubuntu_version}" == "16.04" ]] && echo "gnupg" || echo "gpg")
mysql_package=$([[ "${ubuntu_version}" == "20.04" ]] && echo "mysql-server-8.0" || echo "mysql-server-5.7")
apt-get -y install \
@@ -48,7 +47,6 @@ apt-get -y install \
$mysql_package \
openssh-server \
pwgen \
resolvconf \
swaks \
tzdata \
unattended-upgrades \
@@ -145,21 +143,7 @@ fi
systemctl stop bind9 || true
systemctl disable bind9 || true
# on ovh images dnsmasq seems to run by default
systemctl stop dnsmasq || true
systemctl disable dnsmasq || true
# on ssdnodes postfix seems to run by default
systemctl stop postfix || true
systemctl disable postfix || true
# on ubuntu 18.04, this is the default. this requires resolvconf for DNS to work further after the disable
systemctl stop systemd-resolved || true
systemctl disable systemd-resolved || true
# ubuntu's default config for unbound does not work if ipv6 is disabled. this config is overwritten in start.sh
# we need unbound to work as this is required for installer.sh to do any DNS requests
ip6=$([[ -s /proc/net/if_inet6 ]] && echo "yes" || echo "no")
echo -e "server:\n\tinterface: 127.0.0.1\n\tdo-ip6: ${ip6}" > /etc/unbound/unbound.conf.d/cloudron-network.conf
systemctl restart unbound

6
package-lock.json generated
View File

@@ -743,9 +743,9 @@
}
},
"cloudron-manifestformat": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.8.1.tgz",
"integrity": "sha512-9UZQwh8ohOlY5t+PuUP10EQTD5iLJiKaYCuncjNh3xgqVlEIPRkmhwXdNZmx9WS2RbFft3sSaE7U89y6GYjeUQ==",
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.8.3.tgz",
"integrity": "sha512-8lrxUZlNUjReOOgPwFsARA2sVtHvmHFBUXFJ29Ss/sH89hMJneR2FihR6NTbi57UAE5AG+DH+ftHfaW+riWSgg==",
"requires": {
"cron": "^1.8.2",
"java-packagename-regex": "^1.0.0",

View File

@@ -21,7 +21,7 @@
"aws-sdk": "^2.759.0",
"basic-auth": "^2.0.1",
"body-parser": "^1.19.0",
"cloudron-manifestformat": "^5.8.1",
"cloudron-manifestformat": "^5.8.3",
"connect": "^3.7.0",
"connect-lastmile": "^2.0.0",
"connect-timeout": "^1.9.0",

View File

@@ -134,6 +134,11 @@ if ! id "${user}" 2>/dev/null; then
useradd "${user}" -m
fi
if which resolvconf; then
echo "==> installer: uninstall resolvconf"
apt remove -y resolvconf
fi
if [[ "${is_update}" == "yes" ]]; then
echo "==> installer: stop box service for update"
${box_src_dir}/setup/stop.sh

View File

@@ -106,8 +106,8 @@ systemctl disable cloudron.target || true
rm -f /etc/systemd/system/cloudron.target
[[ "${ubuntu_version}" == "16.04" ]] && sed -e 's/MemoryMax/MemoryLimit/g' -i /etc/systemd/system/box.service
systemctl daemon-reload
systemctl enable unbound
systemctl enable cloudron-syslog
systemctl enable --now unbound
systemctl enable --now cloudron-syslog
systemctl enable box
systemctl enable cloudron-firewall
@@ -117,8 +117,7 @@ systemctl restart cloudron-firewall
# For logrotate
systemctl enable --now cron
# ensure unbound runs
systemctl restart unbound
systemctl enable --now systemd-resolved
# ensure cloudron-syslog runs
systemctl restart cloudron-syslog

View File

@@ -1,5 +1,7 @@
server:
interface: 0.0.0.0
port: 533
interface: 127.0.0.1
interface: 172.18.0.1
do-ip6: no
access-control: 127.0.0.1 allow
access-control: 172.18.0.1/16 allow

View File

@@ -1200,8 +1200,6 @@ function startMysql(existingInfra, callback) {
--log-opt syslog-address=udp://127.0.0.1:2514 \
--log-opt syslog-format=rfc5424 \
--log-opt tag=mysql \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_MYSQL_TOKEN=${cloudronToken} \
-e CLOUDRON_MYSQL_ROOT_HOST=172.18.0.1 \
-e CLOUDRON_MYSQL_ROOT_PASSWORD=${rootPassword} \
@@ -1419,8 +1417,6 @@ function startPostgresql(existingInfra, callback) {
--log-opt syslog-address=udp://127.0.0.1:2514 \
--log-opt syslog-format=rfc5424 \
--log-opt tag=postgresql \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_POSTGRESQL_ROOT_PASSWORD="${rootPassword}" \
-e CLOUDRON_POSTGRESQL_TOKEN="${cloudronToken}" \
-v "${dataDir}/postgresql:/var/lib/postgresql" \
@@ -1599,8 +1595,6 @@ function startTurn(existingInfra, callback) {
--log-opt tag=turn \
-m ${memoryLimit}m \
--memory-swap ${memoryLimit * 2}m \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_TURN_SECRET="${turnSecret}" \
-e CLOUDRON_REALM="${realm}" \
--label isCloudronManaged=true \
@@ -1639,8 +1633,6 @@ function startMongodb(existingInfra, callback) {
--log-opt syslog-address=udp://127.0.0.1:2514 \
--log-opt syslog-format=rfc5424 \
--log-opt tag=mongodb \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_MONGODB_ROOT_PASSWORD="${rootPassword}" \
-e CLOUDRON_MONGODB_TOKEN="${cloudronToken}" \
-v "${dataDir}/mongodb:/var/lib/mongodb" \
@@ -1867,8 +1859,6 @@ function setupRedis(app, options, callback) {
--log-opt tag="${redisName}" \
-m ${memoryLimit/2} \
--memory-swap ${memoryLimit} \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_REDIS_PASSWORD="${redisPassword}" \
-e CLOUDRON_REDIS_TOKEN="${redisServiceToken}" \
-v "${paths.PLATFORM_DATA_DIR}/redis/${app.id}:/var/lib/redis" \

View File

@@ -155,7 +155,6 @@ function validatePortBindings(portBindings, manifest) {
const RESERVED_PORTS = [
22, /* ssh */
25, /* smtp */
53, /* dns */
80, /* http */
143, /* imap */
202, /* alternate ssh */
@@ -192,7 +191,7 @@ function validatePortBindings(portBindings, manifest) {
if (!Number.isInteger(hostPort)) return new BoxError(BoxError.BAD_FIELD, `${hostPort} is not an integer`, { field: 'portBindings', portName: portName });
if (RESERVED_PORTS.indexOf(hostPort) !== -1) return new BoxError(BoxError.BAD_FIELD, `Port ${hostPort} is reserved.`, { field: 'portBindings', portName: portName });
if (RESERVED_PORT_RANGES.find(range => (hostPort >= range[0] && hostPort <= range[1]))) return new BoxError(BoxError.BAD_FIELD, `Port ${hostPort} is reserved.`, { field: 'portBindings', portName: portName });
if (hostPort <= 1023 || hostPort > 65535) return new BoxError(BoxError.BAD_FIELD, `${hostPort} is not in permitted range`, { field: 'portBindings', portName: portName });
if (hostPort !== 53 && (hostPort <= 1023 || hostPort > 65535)) return new BoxError(BoxError.BAD_FIELD, `${hostPort} is not in permitted range`, { field: 'portBindings', portName: portName }); // dns 53 is special and adblocker apps can use them
}
// it is OK if there is no 1-1 mapping between values in manifest.tcpPorts and portBindings. missing values implies

View File

@@ -28,6 +28,7 @@ exports = module.exports = {
INTERNAL_SMTP_PORT: 2525, // this value comes from the mail container
AUTHWALL_PORT: 3001,
LDAP_PORT: 3002,
DNS_PORT: 533,
DOCKER_PROXY_PORT: 3003,
NGINX_DEFAULT_CONFIG_FILE_NAME: 'default.conf',

View File

@@ -39,6 +39,7 @@ var addons = require('./addons.js'),
constants = require('./constants.js'),
debug = require('debug')('box:docker'),
Docker = require('dockerode'),
os = require('os'),
path = require('path'),
settings = require('./settings.js'),
shell = require('./shell.js'),
@@ -208,6 +209,18 @@ function getBinds(app, callback) {
});
}
function getLowerUpIp() { // see getifaddrs and IFF_LOWER_UP and netdevice
const ni = os.networkInterfaces(); // { lo: [], eth0: [] }
for (const iname of Object.keys(ni)) {
if (iname === 'lo') continue;
for (const address of ni[iname]) {
if (!address.internal && address.family === 'IPv4') return address.address;
}
}
return null;
}
function createSubcontainer(app, name, cmd, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof name, 'string');
@@ -249,7 +262,8 @@ function createSubcontainer(app, name, cmd, options, callback) {
exposedPorts[`${containerPort}/${portType}`] = {};
portEnv.push(`${portName}=${hostPort}`);
dockerPortBindings[`${containerPort}/${portType}`] = [ { HostIp: '0.0.0.0', HostPort: hostPort + '' } ];
const hostIp = hostPort === 53 ? getLowerUpIp() : '0.0.0.0'; // port 53 is special because it is possibly taken by systemd-resolved
dockerPortBindings[`${containerPort}/${portType}`] = [ { HostIp: hostIp, HostPort: hostPort + '' } ];
}
let appEnv = [];

View File

@@ -29,8 +29,6 @@ function startGraphite(existingInfra, callback) {
--log-opt tag=graphite \
-m 150m \
--memory-swap 150m \
--dns 172.18.0.1 \
--dns-search=. \
-p 127.0.0.1:2003:2003 \
-p 127.0.0.1:2004:2004 \
-p 127.0.0.1:8417:8000 \

View File

@@ -6,7 +6,7 @@
exports = module.exports = {
// a version change recreates all containers with latest docker config
'version': '48.17.1',
'version': '48.18.0',
'baseImages': [
{ repo: 'cloudron/base', tag: 'cloudron/base:2.0.0@sha256:f9fea80513aa7c92fe2e7bf3978b54c8ac5222f47a9a32a7f8833edf0eb5a4f4' }
@@ -20,7 +20,7 @@ exports = module.exports = {
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:3.3.0@sha256:ad73bff9825c96260a8fd54d1e4b398c48b4b8787c5130e910693de4e96c584e' },
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:3.0.0@sha256:59e50b1f55e433ffdf6d678f8c658812b4119f631db8325572a52ee40d3bc562' },
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.3.0@sha256:0e31ec817e235b1814c04af97b1e7cf0053384aca2569570ce92bef0d95e94d2' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.10.0@sha256:3aff92bfc85d6ca3cc6fc381c8a89625d2af95cc55ed2db692ef4e483e600372' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:3.0.0@sha256:014b651f42384c915677acb8b323d1cd18fe5194fb0e6874020a1e1bbad0bbef' },
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.3.0@sha256:b7bc1ca4f4d0603a01369a689129aa273a938ce195fe43d00d42f4f2d5212f50' },
'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:3.0.0@sha256:5b249db20ee559de2e3b669526763538cf1ec554966b51accdb7056b9be8fc0f' }
}

View File

@@ -666,10 +666,9 @@ function configureMail(mailFqdn, mailDomain, callback) {
--log-opt tag=mail \
-m ${memoryLimit}m \
--memory-swap ${memoryLimit * 2}m \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \
-e CLOUDRON_RELAY_TOKEN="${relayToken}" \
-e CLOUDRON_FTS="true" \
-v "${paths.MAIL_DATA_DIR}:/app/data" \
-v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
${ports} \

View File

@@ -9,7 +9,7 @@ var assert = require('assert'),
dns = require('dns'),
_ = require('underscore');
const DEFAULT_OPTIONS = { server: '127.0.0.1', timeout: 5000 }; // unbound runs on 127.0.0.1
const DEFAULT_OPTIONS = { server: `127.0.0.1:${constants.DNS_PORT}`, timeout: 5000 }; // unbound runs on 127.0.0.1
// a note on TXT records. It doesn't have quotes ("") at the DNS level. Those quotes
// are added for DNS server software to enclose spaces. Such quotes may also be returned

View File

@@ -105,8 +105,6 @@ function rebuild(callback) {
--log-opt tag=sftp \
-m ${memoryLimit}m \
--memory-swap ${memoryLimit * 2}m \
--dns 172.18.0.1 \
--dns-search=. \
-p 222:22 \
${mounts} \
-e CLOUDRON_SFTP_TOKEN="${cloudronToken}" \