diff --git a/src/dns/gcdns.js b/src/dns/gcdns.js index 8604885ad..e5808eb2e 100644 --- a/src/dns/gcdns.js +++ b/src/dns/gcdns.js @@ -1,5 +1,7 @@ 'use strict'; +const safe = require('safetydance'); + exports = module.exports = { removePrivateFields, injectPrivateFields, @@ -14,6 +16,7 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), constants = require('../constants.js'), debug = require('debug')('box:dns/gcdns'), + dig = require('../dig.js'), dns = require('../dns.js'), GCDNS = require('@google-cloud/dns').DNS, waitForDns = require('./waitfordns.js'), @@ -40,39 +43,37 @@ function getDnsCredentials(domainConfig) { }; } -function getZoneByName(domainConfig, zoneName, callback) { +async function getZoneByName(domainConfig, zoneName) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof zoneName, 'string'); assert.strictEqual(typeof callback, 'function'); - var gcdns = new GCDNS(getDnsCredentials(domainConfig)); + const gcdns = new GCDNS(getDnsCredentials(domainConfig)); - gcdns.getZones(function (error, zones) { - if (error && error.message === 'invalid_grant') return callback(new BoxError(BoxError.ACCESS_DENIED, 'The key was probably revoked')); - if (error && error.reason === 'No such domain') return callback(new BoxError(BoxError.NOT_FOUND, error.message)); - if (error && error.code === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, error.message)); - if (error && error.code === 404) return callback(new BoxError(BoxError.NOT_FOUND, error.message)); - if (error) { - debug('gcdns.getZones', error); - return callback(new BoxError(BoxError.EXTERNAL_ERROR, error)); - } + const [error, zones] = await safe(gcdns.getZones()); + if (error && error.message === 'invalid_grant') throw new BoxError(BoxError.ACCESS_DENIED, 'The key was probably revoked'); + if (error && error.reason === 'No such domain') throw new BoxError(BoxError.NOT_FOUND, error.message); + if (error && error.code === 403) throw new BoxError(BoxError.ACCESS_DENIED, error.message); + if (error && error.code === 404) throw new BoxError(BoxError.NOT_FOUND, error.message); + if (error) { + debug('gcdns.getZones', error); + throw new BoxError(BoxError.EXTERNAL_ERROR, error); + } - var zone = zones.filter(function (zone) { - return zone.metadata.dnsName.slice(0, -1) === zoneName; // the zone name contains a '.' at the end - })[0]; + const zone = zones.filter(function (zone) { + return zone.metadata.dnsName.slice(0, -1) === zoneName; // the zone name contains a '.' at the end + })[0]; - if (!zone) return callback(new BoxError(BoxError.NOT_FOUND, 'no such zone')); + if (!zone) throw new BoxError(BoxError.NOT_FOUND, 'no such zone'); - callback(null, zone); //zone.metadata ~= {name="", dnsName="", nameServers:[]} - }); + return zone; //zone.metadata ~= {name="", dnsName="", nameServers:[]} } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -80,97 +81,68 @@ function upsert(domainObject, location, type, values, callback) { debug('add: %s for zone %s of type %s with values %j', fqdn, zoneName, type, values); - getZoneByName(getDnsCredentials(domainConfig), zoneName, function (error, zone) { - if (error) return callback(error); + const zone = await getZoneByName(getDnsCredentials(domainConfig), zoneName); - zone.getRecords({ type: type, name: fqdn + '.' }, function (error, oldRecords) { - if (error && error.code === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, error.message)); - if (error) { - debug('upsert->zone.getRecords', error); - return callback(new BoxError(BoxError.EXTERNAL_ERROR, error.message)); - } + const [error, oldRecords] = await safe(zone.getRecords({ type: type, name: fqdn + '.' })); + if (error && error.code === 403) throw new BoxError(BoxError.ACCESS_DENIED, error.message); + if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, error.message); - var newRecord = zone.record(type, { - name: fqdn + '.', - data: values, - ttl: 1 - }); - - zone.createChange({ delete: oldRecords, add: newRecord }, function(error /*, change */) { - if (error && error.code === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, error.message)); - if (error && error.code === 412) return callback(new BoxError(BoxError.BUSY, error.message)); - if (error) { - debug('upsert->zone.createChange', error); - return callback(new BoxError(BoxError.EXTERNAL_ERROR, error.message)); - } - - callback(null); - }); - }); + const newRecord = zone.record(type, { + name: fqdn + '.', + data: values, + ttl: 1 }); + + const [changeError] = await safe(zone.createChange({ delete: oldRecords, add: newRecord })); + if (changeError && changeError.code === 403) throw new BoxError(BoxError.ACCESS_DENIED, changeError.message); + if (changeError && changeError.code === 412) throw new BoxError(BoxError.BUSY, changeError.message); + if (changeError) throw new BoxError(BoxError.EXTERNAL_ERROR, changeError.message); } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, fqdn = dns.fqdn(location, domainObject); - getZoneByName(getDnsCredentials(domainConfig), zoneName, function (error, zone) { - if (error) return callback(error); + const zone = await getZoneByName(getDnsCredentials(domainConfig), zoneName); - var params = { - name: fqdn + '.', - type: type - }; + const params = { + name: fqdn + '.', + type: type + }; - zone.getRecords(params, function (error, records) { - if (error && error.code === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, error.message)); - if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error)); - if (records.length === 0) return callback(null, [ ]); + const [error, records] = await safe(zone.getRecords(params)); + if (error && error.code === 403) throw new BoxError(BoxError.ACCESS_DENIED, error.message); + if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, error.message); + if (records.length === 0) return []; - return callback(null, records[0].data); - }); - }); + return records[0].data; } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, fqdn = dns.fqdn(location, domainObject); - getZoneByName(getDnsCredentials(domainConfig), zoneName, function (error, zone) { - if (error) return callback(error); + const zone = await getZoneByName(getDnsCredentials(domainConfig), zoneName); - zone.getRecords({ type: type, name: fqdn + '.' }, function(error, oldRecords) { - if (error && error.code === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, error.message)); - if (error) { - debug('del->zone.getRecords', error); - return callback(new BoxError(BoxError.EXTERNAL_ERROR, error.message)); - } + const [error, oldRecords] = await safe(zone.getRecords({ type: type, name: fqdn + '.' })); + if (error && error.code === 403) throw new BoxError(BoxError.ACCESS_DENIED, error.message); + if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, error.message); - zone.deleteRecords(oldRecords, function (error, change) { - if (error && error.code === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, error.message)); - if (error && error.code === 412) return callback(new BoxError(BoxError.BUSY, error.message)); - if (error) { - debug('del->zone.createChange', error); - return callback(new BoxError(BoxError.EXTERNAL_ERROR, error.message)); - } - - callback(null, change.id); - }); - }); - }); + const [delError] = await safe(zone.deleteRecords(oldRecords)); + if (delError && delError.code === 403) throw new BoxError(BoxError.ACCESS_DENIED, delError.message); + if (delError && delError.code === 412) throw new BoxError(BoxError.BUSY, delError.message); + if (delError) throw new BoxError(BoxError.EXTERNAL_ERROR, delError.message); } async function wait(domainObject, subdomain, type, value, options) { @@ -185,52 +157,41 @@ async function wait(domainObject, subdomain, type, value, options) { await waitForDns(fqdn, domainObject.zoneName, type, value, options); } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName; - if (typeof domainConfig.projectId !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'projectId must be a string', { field: 'projectId' })); - if (!domainConfig.credentials || typeof domainConfig.credentials !== 'object') return callback(new BoxError(BoxError.BAD_FIELD, 'credentials must be an object', { field: 'credentials' })); - if (typeof domainConfig.credentials.client_email !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'credentials.client_email must be a string', { field: 'client_email' })); - if (typeof domainConfig.credentials.private_key !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'credentials.private_key must be a string', { field: 'private_key' })); + if (typeof domainConfig.projectId !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'projectId must be a string'); + if (!domainConfig.credentials || typeof domainConfig.credentials !== 'object') throw new BoxError(BoxError.BAD_FIELD, 'credentials must be an object'); + if (typeof domainConfig.credentials.client_email !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'credentials.client_email must be a string'); + if (typeof domainConfig.credentials.private_key !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'credentials.private_key must be a string'); - var credentials = getDnsCredentials(domainConfig); + const credentials = getDnsCredentials(domainConfig); const ip = '127.0.0.1'; - if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here + if (process.env.BOX_ENV === 'test') return credentials; // this shouldn't be here - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - getZoneByName(credentials, zoneName, function (error, zone) { - if (error) return callback(error); + const zone = await getZoneByName(credentials, zoneName); - var definedNS = zone.metadata.nameServers.sort().map(function(r) { return r.replace(/\.$/, ''); }); - if (!_.isEqual(definedNS, nameservers.sort())) { - debug('verifyDomainConfig: %j and %j do not match', nameservers, definedNS); - return callback(new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Google Cloud DNS', { field: 'nameservers' })); - } + const definedNS = zone.metadata.nameServers.sort().map(function(r) { return r.replace(/\.$/, ''); }); + if (!_.isEqual(definedNS, nameservers.sort())) { + debug('verifyDomainConfig: %j and %j do not match', nameservers, definedNS); + throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Google Cloud DNS'); + } - const location = 'cloudrontestdns'; + const location = 'cloudrontestdns'; + await upsert(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record added'); - upsert(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); + await del(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record removed again'); - debug('verifyDomainConfig: Test A record added'); - - del(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); - - debug('verifyDomainConfig: Test A record removed again'); - - callback(null, credentials); - }); - }); - }); - }); + return credentials; }