2017-09-05 22:23:24 +02:00
'use strict' ;
exports = module . exports = {
2019-02-08 11:11:49 +01:00
removePrivateFields : removePrivateFields ,
2019-02-09 19:08:15 +01:00
injectPrivateFields : injectPrivateFields ,
2017-09-05 22:23:24 +02:00
upsert : upsert ,
get : get ,
del : del ,
2019-01-04 18:44:54 -08:00
wait : wait ,
2017-09-05 22:23:24 +02:00
verifyDnsConfig : verifyDnsConfig
} ;
var assert = require ( 'assert' ) ,
2019-10-23 10:02:04 -07:00
BoxError = require ( '../boxerror.js' ) ,
2020-05-14 23:01:44 +02:00
constants = require ( '../constants.js' ) ,
2017-09-12 16:29:07 +02:00
debug = require ( 'debug' ) ( 'box:dns/gcdns' ) ,
2018-02-08 10:21:31 -08:00
dns = require ( '../native-dns.js' ) ,
2019-01-04 18:44:54 -08:00
domains = require ( '../domains.js' ) ,
2019-05-11 19:17:58 -07:00
GCDNS = require ( '@google-cloud/dns' ) . DNS ,
2017-09-05 22:23:24 +02:00
util = require ( 'util' ) ,
2019-01-04 18:44:54 -08:00
waitForDns = require ( './waitfordns.js' ) ,
2017-09-05 22:23:24 +02:00
_ = require ( 'underscore' ) ;
2019-02-08 11:11:49 +01:00
function removePrivateFields ( domainObject ) {
2020-05-14 23:01:44 +02:00
domainObject . config . credentials . private _key = constants . SECRET _PLACEHOLDER ;
2019-02-08 11:11:49 +01:00
return domainObject ;
}
2019-02-09 19:08:15 +01:00
function injectPrivateFields ( newConfig , currentConfig ) {
2020-05-14 23:01:44 +02:00
if ( newConfig . credentials . private _key === constants . SECRET _PLACEHOLDER && currentConfig . credentials ) newConfig . credentials . private _key = currentConfig . credentials . private _key ;
2019-02-09 19:08:15 +01:00
}
2017-09-05 22:23:24 +02:00
function getDnsCredentials ( dnsConfig ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2018-06-17 21:44:08 -07:00
return {
2017-09-05 22:23:24 +02:00
projectId : dnsConfig . projectId ,
2018-06-17 21:44:08 -07:00
credentials : {
2017-09-05 22:23:24 +02:00
client _email : dnsConfig . credentials . client _email ,
private _key : dnsConfig . credentials . private _key
2018-09-11 21:24:04 -07:00
}
2018-06-17 21:44:08 -07:00
} ;
2017-09-05 22:23:24 +02:00
}
function getZoneByName ( dnsConfig , zoneName , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-05-11 19:17:58 -07:00
var gcdns = new GCDNS ( getDnsCredentials ( dnsConfig ) ) ;
2017-09-05 22:23:24 +02:00
2017-09-14 23:11:17 -07:00
gcdns . getZones ( function ( error , zones ) {
2019-10-23 10:02:04 -07:00
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 ) ) ;
2017-09-14 18:12:07 -07:00
if ( error ) {
debug ( 'gcdns.getZones' , error ) ;
2019-10-23 10:02:04 -07:00
return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
2017-09-12 16:29:07 +02:00
}
2017-09-05 22:23:24 +02:00
var zone = zones . filter ( function ( zone ) {
return zone . metadata . dnsName . slice ( 0 , - 1 ) === zoneName ; // the zone name contains a '.' at the end
} ) [ 0 ] ;
2019-10-23 10:02:04 -07:00
if ( ! zone ) return callback ( new BoxError ( BoxError . NOT _FOUND , 'no such zone' ) ) ;
2017-09-05 22:23:24 +02:00
callback ( null , zone ) ; //zone.metadata ~= {name="", dnsName="", nameServers:[]}
} ) ;
}
2019-01-04 18:44:54 -08:00
function upsert ( domainObject , location , type , values , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
2017-09-05 22:23:24 +02:00
assert . strictEqual ( typeof type , 'string' ) ;
2021-05-02 11:26:08 -07:00
assert ( Array . isArray ( values ) ) ;
2017-09-05 22:23:24 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
const dnsConfig = domainObject . config ,
zoneName = domainObject . zoneName ,
fqdn = domains . fqdn ( location , domainObject ) ;
debug ( 'add: %s for zone %s of type %s with values %j' , fqdn , zoneName , type , values ) ;
2017-09-05 22:23:24 +02:00
getZoneByName ( getDnsCredentials ( dnsConfig ) , zoneName , function ( error , zone ) {
if ( error ) return callback ( error ) ;
2019-01-04 18:44:54 -08:00
zone . getRecords ( { type : type , name : fqdn + '.' } , function ( error , oldRecords ) {
2019-10-23 10:02:04 -07:00
if ( error && error . code === 403 ) return callback ( new BoxError ( BoxError . ACCESS _DENIED , error . message ) ) ;
2017-09-12 16:29:07 +02:00
if ( error ) {
2017-09-13 20:53:38 +02:00
debug ( 'upsert->zone.getRecords' , error ) ;
2019-10-23 10:02:04 -07:00
return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message ) ) ;
2017-09-12 16:29:07 +02:00
}
2017-09-05 22:23:24 +02:00
2017-09-13 20:53:38 +02:00
var newRecord = zone . record ( type , {
2019-01-04 18:44:54 -08:00
name : fqdn + '.' ,
2017-09-14 23:11:17 -07:00
data : values ,
2017-09-13 20:53:38 +02:00
ttl : 1
} ) ;
2019-01-04 18:44:54 -08:00
zone . createChange ( { delete : oldRecords , add : newRecord } , function ( error /*, change */ ) {
2019-10-23 10:02:04 -07:00
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 ) ) ;
2017-09-13 20:53:38 +02:00
if ( error ) {
debug ( 'upsert->zone.createChange' , error ) ;
2019-10-23 10:02:04 -07:00
return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message ) ) ;
2017-09-13 20:53:38 +02:00
}
2018-06-29 22:25:34 +02:00
callback ( null ) ;
2017-09-13 20:53:38 +02:00
} ) ;
2017-09-05 22:23:24 +02:00
} ) ;
} ) ;
}
2019-01-04 18:44:54 -08:00
function get ( domainObject , location , type , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
2017-09-05 22:23:24 +02:00
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
const dnsConfig = domainObject . config ,
zoneName = domainObject . zoneName ,
fqdn = domains . fqdn ( location , domainObject ) ;
2017-09-05 22:23:24 +02:00
getZoneByName ( getDnsCredentials ( dnsConfig ) , zoneName , function ( error , zone ) {
if ( error ) return callback ( error ) ;
var params = {
2019-01-04 18:44:54 -08:00
name : fqdn + '.' ,
2017-09-05 22:23:24 +02:00
type : type
} ;
2017-09-14 23:11:17 -07:00
zone . getRecords ( params , function ( error , records ) {
2019-10-23 10:02:04 -07:00
if ( error && error . code === 403 ) return callback ( new BoxError ( BoxError . ACCESS _DENIED , error . message ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
2017-09-14 23:11:17 -07:00
if ( records . length === 0 ) return callback ( null , [ ] ) ;
2017-09-18 23:45:21 +02:00
2017-09-14 23:11:17 -07:00
return callback ( null , records [ 0 ] . data ) ;
} ) ;
2017-09-05 22:23:24 +02:00
} ) ;
}
2019-01-04 18:44:54 -08:00
function del ( domainObject , location , type , values , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
2017-09-05 22:23:24 +02:00
assert . strictEqual ( typeof type , 'string' ) ;
2021-05-02 11:26:08 -07:00
assert ( Array . isArray ( values ) ) ;
2017-09-05 22:23:24 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
const dnsConfig = domainObject . config ,
zoneName = domainObject . zoneName ,
fqdn = domains . fqdn ( location , domainObject ) ;
2017-09-05 22:23:24 +02:00
getZoneByName ( getDnsCredentials ( dnsConfig ) , zoneName , function ( error , zone ) {
if ( error ) return callback ( error ) ;
2019-01-04 18:44:54 -08:00
zone . getRecords ( { type : type , name : fqdn + '.' } , function ( error , oldRecords ) {
2019-10-23 10:02:04 -07:00
if ( error && error . code === 403 ) return callback ( new BoxError ( BoxError . ACCESS _DENIED , error . message ) ) ;
2017-09-12 16:29:07 +02:00
if ( error ) {
2017-09-13 20:53:38 +02:00
debug ( 'del->zone.getRecords' , error ) ;
2019-10-23 10:02:04 -07:00
return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message ) ) ;
2017-09-12 16:29:07 +02:00
}
2017-09-13 20:53:38 +02:00
2017-09-14 23:11:17 -07:00
zone . deleteRecords ( oldRecords , function ( error , change ) {
2019-10-23 10:02:04 -07:00
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 ) ) ;
2017-09-13 20:53:38 +02:00
if ( error ) {
debug ( 'del->zone.createChange' , error ) ;
2019-10-23 10:02:04 -07:00
return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message ) ) ;
2017-09-13 20:53:38 +02:00
}
callback ( null , change . id ) ;
} ) ;
2017-09-05 22:23:24 +02:00
} ) ;
} ) ;
}
2019-01-04 18:44:54 -08:00
function wait ( domainObject , location , type , value , options , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof value , 'string' ) ;
assert ( options && typeof options === 'object' ) ; // { interval: 5000, times: 50000 }
2017-09-05 22:23:24 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-04 18:44:54 -08:00
const fqdn = domains . fqdn ( location , domainObject ) ;
waitForDns ( fqdn , domainObject . zoneName , type , value , options , callback ) ;
}
function verifyDnsConfig ( domainObject , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
const dnsConfig = domainObject . config ,
zoneName = domainObject . zoneName ;
2019-10-23 10:02:04 -07:00
if ( typeof dnsConfig . projectId !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'projectId must be a string' , { field : 'projectId' } ) ) ;
if ( ! dnsConfig . credentials || typeof dnsConfig . credentials !== 'object' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'credentials must be an object' , { field : 'credentials' } ) ) ;
if ( typeof dnsConfig . credentials . client _email !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'credentials.client_email must be a string' , { field : 'client_email' } ) ) ;
if ( typeof dnsConfig . credentials . private _key !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'credentials.private_key must be a string' , { field : 'private_key' } ) ) ;
2018-06-17 21:44:08 -07:00
2017-09-05 22:23:24 +02:00
var credentials = getDnsCredentials ( dnsConfig ) ;
2019-01-04 18:44:54 -08:00
const ip = '127.0.0.1' ;
2017-09-05 22:23:24 +02:00
if ( process . env . BOX _ENV === 'test' ) return callback ( null , credentials ) ; // this shouldn't be here
2018-02-08 14:39:35 -08:00
dns . resolve ( zoneName , 'NS' , { timeout : 5000 } , function ( error , nameservers ) {
2019-10-23 10:02:04 -07:00
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' } ) ) ;
2017-09-05 22:23:24 +02:00
2017-09-12 16:29:07 +02:00
getZoneByName ( credentials , zoneName , function ( error , zone ) {
2017-09-05 22:23:24 +02:00
if ( error ) return callback ( error ) ;
2017-09-14 18:12:07 -07:00
var definedNS = zone . metadata . nameServers . sort ( ) . map ( function ( r ) { return r . replace ( /\.$/ , '' ) ; } ) ;
2018-05-10 10:05:35 -07:00
if ( ! _ . isEqual ( definedNS , nameservers . sort ( ) ) ) {
debug ( 'verifyDnsConfig: %j and %j do not match' , nameservers , definedNS ) ;
2019-10-23 10:02:04 -07:00
return callback ( new BoxError ( BoxError . BAD _FIELD , 'Domain nameservers are not set to Google Cloud DNS' , { field : 'nameservers' } ) ) ;
2017-09-05 22:23:24 +02:00
}
2019-01-04 18:44:54 -08:00
const location = 'cloudrontestdns' ;
2017-09-05 22:23:24 +02:00
2019-01-04 18:44:54 -08:00
upsert ( domainObject , location , 'A' , [ ip ] , function ( error ) {
2017-09-05 22:23:24 +02:00
if ( error ) return callback ( error ) ;
2019-01-04 18:44:54 -08:00
debug ( 'verifyDnsConfig: Test A record added' ) ;
2017-09-05 22:23:24 +02:00
2019-01-04 18:44:54 -08:00
del ( domainObject , location , 'A' , [ ip ] , function ( error ) {
2017-11-07 23:13:58 +01:00
if ( error ) return callback ( error ) ;
debug ( 'verifyDnsConfig: Test A record removed again' ) ;
callback ( null , credentials ) ;
} ) ;
2017-09-05 22:23:24 +02:00
} ) ;
} ) ;
} ) ;
}