make ipv4 and ipv6 settings separate

This commit is contained in:
Girish Ramakrishnan
2022-02-15 12:31:55 -08:00
parent 0dbe8ee8f2
commit c6da8c8167
14 changed files with 102 additions and 68 deletions
+3 -5
View File
@@ -243,13 +243,12 @@ async function waitForDnsPropagation(app) {
}
const ipv4 = await sysinfo.getServerIPv4();
const ipv6Enabled = await settings.getIPv6Config();
const ipv6 = ipv6Enabled ? await sysinfo.getServerIPv6() : null;
const ipv6 = await sysinfo.getServerIPv6();
let error;
[error] = await safe(dns.waitForDnsRecord(app.subdomain, app.domain, 'A', ipv4, { times: 240 }));
if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS A Record is not synced yet: ${error.message}`, { ipv4, subdomain: app.subdomain, domain: app.domain });
if (ipv6Enabled) {
if (ipv6) {
[error] = await safe(dns.waitForDnsRecord(app.subdomain, app.domain, 'AAAA', ipv6, { times: 240 }));
if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS AAAA Record is not synced yet: ${error.message}`, { ipv6, subdomain: app.subdomain, domain: app.domain });
}
@@ -259,11 +258,10 @@ async function waitForDnsPropagation(app) {
for (const domain of allDomains) {
[error] = await safe(dns.waitForDnsRecord(domain.subdomain, domain.domain, 'A', ipv4, { times: 240 }));
if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS A Record is not synced yet: ${error.message}`, { ipv4, subdomain: domain.subdomain, domain: domain.domain });
if (ipv6Enabled) {
if (ipv6) {
[error] = await safe(dns.waitForDnsRecord(domain.subdomain, domain.domain, 'AAAA', ipv6, { times: 240 }));
if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS AAAA Record is not synced yet: ${error.message}`, { ipv6, subdomain: domain.subdomain, domain: domain.domain });
}
}
}
+3 -4
View File
@@ -328,15 +328,14 @@ async function setupDnsAndCert(subdomain, domain, auditSource, progressCallback)
const dashboardFqdn = dns.fqdn(subdomain, domainObject);
const ipv4 = await sysinfo.getServerIPv4();
const ipv6Enabled = await settings.getIPv6Config();
const ipv6 = ipv6Enabled ? await sysinfo.getServerIPv6() : null;
const ipv6 = await sysinfo.getServerIPv6();
progressCallback({ percent: 20, message: `Updating DNS of ${dashboardFqdn}` });
await dns.upsertDnsRecords(subdomain, domain, 'A', [ ipv4 ]);
if (ipv6Enabled) await dns.upsertDnsRecords(subdomain, domain, 'AAAA', [ ipv6 ]);
if (ipv6) await dns.upsertDnsRecords(subdomain, domain, 'AAAA', [ ipv6 ]);
progressCallback({ percent: 40, message: `Waiting for DNS of ${dashboardFqdn}` });
await dns.waitForDnsRecord(subdomain, domain, 'A', ipv4, { interval: 30000, times: 50000 });
if (ipv6Enabled) await dns.waitForDnsRecord(subdomain, domain, 'AAAA', ipv6, { interval: 30000, times: 50000 });
if (ipv6) await dns.waitForDnsRecord(subdomain, domain, 'AAAA', ipv6, { interval: 30000, times: 50000 });
progressCallback({ percent: 60, message: `Getting certificate of ${dashboardFqdn}` });
await reverseProxy.ensureCertificate(dns.fqdn(subdomain, domainObject), domain, auditSource);
}
+6 -9
View File
@@ -128,10 +128,9 @@ async function checkDnsRecords(subdomain, domain) {
// if empty OR exactly one record with the ip, we don't need to overwrite
if (ipv4Records.length !== 0 && (ipv4Records.length !== 1 || ipv4Records[0] !== ipv4)) return { needsOverwrite: true };
const ipv6Enabled = await settings.getIPv6Config();
if (ipv6Enabled) {
const ipv6 = await sysinfo.getServerIPv6();
if (ipv6) {
const ipv6Records = await getDnsRecords(subdomain, domain, 'AAAA');
const ipv6 = await sysinfo.getServerIPv6();
// if empty OR exactly one record with the ip, we don't need to overwrite
if (ipv6Records.length !== 0 && (ipv6Records.length !== 1 || ipaddr.parse(ipv6Records[0]).toRFC5952String() !== ipv6)) return { needsOverwrite: true };
@@ -222,15 +221,14 @@ async function registerLocations(locations, options, progressCallback) {
debug(`registerLocations: Will register ${JSON.stringify(locations)} with options ${JSON.stringify(options)}`);
const ipv4 = await sysinfo.getServerIPv4();
const ipv6Enabled = await settings.getIPv6Config();
const ipv6 = ipv6Enabled ? await sysinfo.getServerIPv6() : null;
const ipv6 = await sysinfo.getServerIPv6();
for (const location of locations) {
progressCallback({ message: `Registering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` });
await promiseRetry({ times: 200, interval: 5000, debug, retry: (error) => error.retryable }, async function () {
await registerLocation(location, options, 'A', ipv4);
if (ipv6Enabled) await registerLocation(location, options, 'AAAA', ipv6);
if (ipv6) await registerLocation(location, options, 'AAAA', ipv6);
});
}
}
@@ -250,15 +248,14 @@ async function unregisterLocations(locations, progressCallback) {
assert.strictEqual(typeof progressCallback, 'function');
const ipv4 = await sysinfo.getServerIPv4();
const ipv6Enabled = await settings.getIPv6Config();
const ipv6 = ipv6Enabled ? await sysinfo.getServerIPv6() : null;
const ipv6 = await sysinfo.getServerIPv6();
for (const location of locations) {
progressCallback({ message: `Unregistering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` });
await promiseRetry({ times: 30, interval: 5000, debug, retry: (error) => error.retryable }, async function () {
await unregisterLocation(location, 'A', ipv4);
if (ipv6Enabled) await unregisterLocation(location, 'AAAA', ipv6);
if (ipv6) await unregisterLocation(location, 'AAAA', ipv6);
});
}
}
+2 -4
View File
@@ -16,7 +16,6 @@ const assert = require('assert'),
dig = require('../dig.js'),
dns = require('../dns.js'),
safe = require('safetydance'),
settings = require('../settings.js'),
sysinfo = require('../sysinfo.js'),
waitForDns = require('./waitfordns.js');
@@ -87,13 +86,12 @@ async function verifyDomainConfig(domainObject) {
const ipv4 = await sysinfo.getServerIPv4();
if (ipv4Result.length !== 1 || ipv4 !== ipv4Result[0]) throw new BoxError(BoxError.EXTERNAL_ERROR, `Domain resolves to ${JSON.stringify(ipv4Result)} instead of IPv4 ${ipv4}`);
const ipv6Enabled = await settings.getIPv6Config();
if (ipv6Enabled) {
const ipv6 = await sysinfo.getServerIPv6(); // both should be RFC 5952 format
if (ipv6) {
const [ipv6Error, ipv6Result] = await safe(dig.resolve(fqdn, 'AAAA', { server: '127.0.0.1', timeout: 5000 }));
if (ipv6Error && ipv6Error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, `Unable to resolve IPv6 of ${fqdn}`);
if (ipv6Error || !ipv6Result) throw new BoxError(BoxError.BAD_FIELD, ipv6Error ? ipv6Error.message : `Unable to resolve IPv6 of ${fqdn}`);
const ipv6 = await sysinfo.getServerIPv6(); // both should be RFC 5952 format
if (ipv6Result.length !== 1 || ipv6 !== ipv6Result[0]) throw new BoxError(BoxError.EXTERNAL_ERROR, `Domain resolves to ${JSON.stringify(ipv6Result)} instead of IPv6 ${ipv6}`);
}
+2 -3
View File
@@ -20,8 +20,7 @@ async function sync(auditSource) {
assert.strictEqual(typeof auditSource, 'object');
const ipv4 = await sysinfo.getServerIPv4();
const ipv6Enabled = await settings.getIPv6Config();
const ipv6 = ipv6Enabled ? await sysinfo.getServerIPv6() : null;
const ipv6 = await sysinfo.getServerIPv6();
const info = safe.JSON.parse(safe.fs.readFileSync(paths.DYNDNS_INFO_FILE, 'utf8')) || { ipv4: null, ipv6: null };
if (info.ip) { // legacy cache file
@@ -29,7 +28,7 @@ async function sync(auditSource) {
delete info.ip;
}
const ipv4Changed = info.ip !== ipv4;
const ipv6Changed = ipv6Enabled && info.ipv6 !== ipv6; // both should be RFC 5952 format
const ipv6Changed = ipv6 && info.ipv6 !== ipv6; // both should be RFC 5952 format
if (!ipv4Changed && !ipv6Changed) {
debug(`refreshDNS: no change in IP ipv4: ${ipv4} ipv6: ${ipv6}`);
+1 -1
View File
@@ -230,7 +230,7 @@ async function restore(backupConfig, backupId, version, sysinfoConfig, options,
backupConfig.encryption = null;
}
error = await sysinfo.testConfig(sysinfoConfig);
error = await sysinfo.testIPv4Config(sysinfoConfig);
if (error) throw error;
safe(restoreTask(backupConfig, backupId, sysinfoConfig, options, auditSource), { debug }); // now that args are validated run the task in the background
+6 -6
View File
@@ -176,18 +176,18 @@ async function setDynamicDnsConfig(req, res, next) {
}
async function getIPv6Config(req, res, next) {
const [error, enabled] = await safe(settings.getIPv6Config());
const [error, ipv6Config] = await safe(settings.getSysinfoConfig());
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { enabled }));
next(new HttpSuccess(200, ipv6Config));
}
async function setIPv6Config(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled boolean is required'));
if (!req.body.provider || typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider is required'));
const [error] = await safe(settings.setIPv6Config(req.body.enabled));
const [error] = await safe(settings.setIPv6Config(req.body));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -295,7 +295,7 @@ function get(req, res, next) {
switch (req.params.setting) {
case settings.DYNAMIC_DNS_KEY: return getDynamicDnsConfig(req, res, next);
case settings.IPV6_KEY: return getIPv6Config(req, res, next);
case settings.IPV6_CONFIG_KEY: return getIPv6Config(req, res, next);
case settings.BACKUP_CONFIG_KEY: return getBackupConfig(req, res, next);
case settings.EXTERNAL_LDAP_KEY: return getExternalLdapConfig(req, res, next);
case settings.USER_DIRECTORY_KEY: return getUserDirectoryConfig(req, res, next);
@@ -319,7 +319,7 @@ function set(req, res, next) {
switch (req.params.setting) {
case settings.DYNAMIC_DNS_KEY: return setDynamicDnsConfig(req, res, next);
case settings.IPV6_KEY: return setIPv6Config(req, res, next);
case settings.IPV6_CONFIG_KEY: return setIPv6Config(req, res, next);
case settings.EXTERNAL_LDAP_KEY: return setExternalLdapConfig(req, res, next);
case settings.USER_DIRECTORY_KEY: return setUserDirectoryConfig(req, res, next);
case settings.UNSTABLE_APPS_KEY: return setUnstableAppsConfig(req, res, next);
+19 -15
View File
@@ -93,7 +93,6 @@ exports = module.exports = {
// booleans. if you add an entry here, be sure to fix list()
DYNAMIC_DNS_KEY: 'dynamic_dns',
UNSTABLE_APPS_KEY: 'unstable_apps',
IPV6_KEY: 'ipv6',
DEMO_KEY: 'demo',
// json. if you add an entry here, be sure to fix list()
@@ -102,12 +101,13 @@ exports = module.exports = {
EXTERNAL_LDAP_KEY: 'external_ldap_config',
USER_DIRECTORY_KEY: 'user_directory_config',
REGISTRY_CONFIG_KEY: 'registry_config',
SYSINFO_CONFIG_KEY: 'sysinfo_config',
SYSINFO_CONFIG_KEY: 'sysinfo_config', // misnomer: ipv4 config
APPSTORE_LISTING_CONFIG_KEY: 'appstore_listing_config',
SUPPORT_CONFIG_KEY: 'support_config',
PROFILE_CONFIG_KEY: 'profile_config',
GHOSTS_CONFIG_KEY: 'ghosts_config',
REVERSE_PROXY_CONFIG_KEY: 'reverseproxy_config',
IPV6_CONFIG_KEY: 'ipv6',
// strings
AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern',
@@ -171,7 +171,9 @@ const gDefaults = (function () {
result[exports.TIME_ZONE_KEY] = 'America/Los_Angeles';
result[exports.CLOUDRON_NAME_KEY] = 'Cloudron';
result[exports.DYNAMIC_DNS_KEY] = false;
result[exports.IPV6_KEY] = false;
result[exports.IPV6_CONFIG_KEY] = {
provider: 'noop'
};
result[exports.UNSTABLE_APPS_KEY] = true;
result[exports.LICENSE_KEY] = '';
result[exports.LANGUAGE_KEY] = 'en';
@@ -370,18 +372,21 @@ async function setDynamicDnsConfig(enabled) {
}
async function getIPv6Config() {
const enabled = await get(exports.IPV6_KEY);
if (enabled === null) return gDefaults[exports.IPV6_KEY];
return !!enabled; // db holds string values only
const value = await get(exports.IPV6_CONFIG_KEY);
if (value === null) return gDefaults[exports.IPV6_CONFIG_KEY];
return JSON.parse(value);
}
async function setIPv6Config(enabled) {
assert.strictEqual(typeof enabled, 'boolean');
async function setIPv6Config(ipv6Config) {
assert.strictEqual(typeof ipv6Config, 'object');
// we don't validate if server has IPv6 intentionally. our api server could be down, maybe user assigns
// ipv6 later, fixed/static address is not defined yet etc
await set(exports.IPV6_KEY, enabled ? 'enabled' : ''); // db holds string values only
notifyChange(exports.IPV6_KEY, enabled);
if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode');
const error = await sysinfo.testIPv6Config(ipv6Config);
if (error) throw error;
await set(exports.IPV6_CONFIG_KEY, JSON.stringify(ipv6Config));
notifyChange(exports.IPV6_CONFIG_KEY, ipv6Config);
}
async function getUnstableAppsConfig() {
@@ -603,7 +608,7 @@ async function setSysinfoConfig(sysinfoConfig) {
if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode');
const error = await sysinfo.testConfig(sysinfoConfig);
const error = await sysinfo.testIPv4Config(sysinfoConfig);
if (error) throw error;
await set(exports.SYSINFO_CONFIG_KEY, JSON.stringify(sysinfoConfig));
@@ -756,12 +761,11 @@ async function list() {
// convert booleans
result[exports.DYNAMIC_DNS_KEY] = !!result[exports.DYNAMIC_DNS_KEY];
result[exports.IPV6_KEY] = !!result[exports.IPV6_KEY];
result[exports.UNSTABLE_APPS_KEY] = !!result[exports.UNSTABLE_APPS_KEY];
result[exports.DEMO_KEY] = !!result[exports.DEMO_KEY];
// convert JSON objects
[exports.BACKUP_CONFIG_KEY, exports.PROFILE_CONFIG_KEY, exports.SERVICES_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY, exports.REVERSE_PROXY_CONFIG_KEY ].forEach(function (key) {
[exports.BACKUP_CONFIG_KEY, exports.IPV6_CONFIG_KEY, exports.PROFILE_CONFIG_KEY, exports.SERVICES_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY, exports.REVERSE_PROXY_CONFIG_KEY ].forEach(function (key) {
result[key] = typeof result[key] === 'object' ? result[key] : safe.JSON.parse(result[key]);
});
+12 -3
View File
@@ -3,7 +3,8 @@
exports = module.exports = {
getServerIPv4,
getServerIPv6,
testConfig,
testIPv4Config,
testIPv6Config,
hasIPv6
};
@@ -33,6 +34,8 @@ async function getServerIPv4() {
async function getServerIPv6() {
const config = await settings.getSysinfoConfig();
if (config.provider === 'noop') return null;
const result = await api(config.provider).getServerIPv6(config);
return ipaddr.parse(result).toRFC5952String();
}
@@ -43,8 +46,14 @@ function hasIPv6() {
return fs.existsSync(IPV6_PROC_FILE) && fs.readFileSync(IPV6_PROC_FILE, 'utf8').trim().length !== 0;
}
async function testConfig(config) {
async function testIPv4Config(config) {
assert.strictEqual(typeof config, 'object');
return await api(config.provider).testConfig(config);
return await api(config.provider).testIPv4Config(config);
}
async function testIPv6Config(config) {
assert.strictEqual(typeof config, 'object');
return await api(config.provider).testIPv6Config(config);
}
+11 -6
View File
@@ -3,7 +3,8 @@
exports = module.exports = {
getServerIPv4,
getServerIPv6,
testConfig
testIPv4Config,
testIPv6Config
};
const assert = require('assert'),
@@ -24,16 +25,20 @@ async function getServerIPv6(config) {
throw new BoxError(BoxError.NETWORK_ERROR, 'No IPv6 configured');
}
async function testConfig(config) {
async function testIPv4Config(config) {
assert.strictEqual(typeof config, 'object');
if (typeof config.ipv4 !== 'string') return new BoxError(BoxError.BAD_FIELD, 'ipv4 must be a string');
if (!net.isIPv4(config.ipv4)) return new BoxError(BoxError.BAD_FIELD, 'invalid IPv4');
if ('ipv6' in config) {
if (typeof config.ipv6 !== 'string') return new BoxError(BoxError.BAD_FIELD, 'ipv6 must be a string');
if (!net.isIPv6(config.ipv6)) return new BoxError(BoxError.BAD_FIELD, 'invalid IPv6');
}
return null;
}
async function testIPv6Config(config) {
assert.strictEqual(typeof config, 'object');
if (typeof config.ipv6 !== 'string') return new BoxError(BoxError.BAD_FIELD, 'ipv6 must be a string');
if (!net.isIPv6(config.ipv6)) return new BoxError(BoxError.BAD_FIELD, 'invalid IPv6');
return null;
}
+9 -2
View File
@@ -3,7 +3,8 @@
exports = module.exports = {
getServerIPv4,
getServerIPv6,
testConfig
testIPv4Config,
testIPv6Config
};
const assert = require('assert'),
@@ -62,7 +63,13 @@ async function getServerIPv6(config) {
return response.body.ip;
}
async function testConfig(config) {
async function testIPv4Config(config) {
assert.strictEqual(typeof config, 'object');
return null;
}
async function testIPv6Config(config) {
assert.strictEqual(typeof config, 'object');
return null;
+9 -2
View File
@@ -9,7 +9,8 @@
exports = module.exports = {
getServerIPv4,
getServerIPv6,
testConfig
testIPv4Config,
testIPv6Config
};
const assert = require('assert'),
@@ -27,7 +28,13 @@ async function getServerIPv6(config) {
throw new BoxError(BoxError.NOT_IMPLEMENTED, 'getServerIPv6 is not implemented');
}
async function testConfig(config) {
async function testIPv4Config(config) {
assert.strictEqual(typeof config, 'object');
return null;
}
async function testIPv6Config(config) {
assert.strictEqual(typeof config, 'object');
return null;
+13 -2
View File
@@ -3,7 +3,8 @@
exports = module.exports = {
getServerIPv4,
getServerIPv6,
testConfig
testIPv4Config,
testIPv6Config
};
const assert = require('assert'),
@@ -40,7 +41,7 @@ async function getServerIPv6(config) {
return addresses[0];
}
async function testConfig(config) {
async function testIPv4Config(config) {
assert.strictEqual(typeof config, 'object');
assert.strictEqual(typeof callback, 'function');
@@ -49,3 +50,13 @@ async function testConfig(config) {
const [error] = await safe(getServerIPv4(config));
return error || null;
}
async function testIPv6Config(config) {
assert.strictEqual(typeof config, 'object');
assert.strictEqual(typeof callback, 'function');
if (typeof config.ifname !== 'string') return new BoxError(BoxError.BAD_FIELD, 'ifname is not a string');
const [error] = await safe(getServerIPv6(config));
return error || null;
}
+6 -6
View File
@@ -55,15 +55,15 @@ describe('Settings', function () {
});
it('can get default IPv6 setting', async function () {
const enabled = await settings.getIPv6Config();
expect(enabled).to.be(false);
const config = await settings.getIPv6Config();
expect(config.provider).to.be('noop');
});
it('can set IPv6 setting', async function () {
await settings.setIPv6Config(true);
await settings.setIPv6Config({ provider: 'generic' });
const enabled = await settings.getIPv6Config();
expect(enabled).to.be(true);
const config = await settings.getIPv6Config();
expect(config.provider).to.be('generic');
});
it('can get default profile config', async function () {
@@ -88,6 +88,6 @@ describe('Settings', function () {
expect(allSettings[settings.AUTOUPDATE_PATTERN_KEY]).to.be.a('string');
expect(allSettings[settings.CLOUDRON_NAME_KEY]).to.be.a('string');
expect(allSettings[settings.UNSTABLE_APPS_KEY]).to.be.a('boolean');
expect(allSettings[settings.IPV6_KEY]).to.be.a('boolean');
expect(allSettings[settings.IPV6_CONFIG_KEY]).to.be.an('object');
});
});