'use strict'; var v4 = require('aws-signature-v4'); var xml2js = require('xml2js'); var parseString = xml2js.parseString; parseString = require('util').promisify(parseString); var builder = new xml2js.Builder(); var request = require('@root/request'); //request.debug = false; var defaults = { baseUri: '/2013-04-01/' }; module.exports.create = function(config) { var baseUri = defaults.baseUri.replace(/\/$/, ''); var key = config.key; var secret = config.secret; function api(method, path, body, query) { var body = body || ''; var query = query || {}; var options = { key: key, secret: secret, query: query }; if(method === 'POST') { options.headers = { 'Content-Type': 'application/xml' } } var url = v4.createPresignedURL(method, 'route53.amazonaws.com', path, 'route53', body, options); //console.log('url: ' + url + '\n'); var parameters = { method: method, url: url }; if(method === 'POST') { parameters.headers = { 'Content-Type': 'application/xml' } } if(body) { parameters.body = body; } // console.log(method, path, query); return request(parameters).then(function(response) { if (response.statusCode < 200 || response.statusCode >= 300) { console.error(response.statusCode, options.url); console.error(); console.error('Request:'); console.error(options); console.error(); console.error('Response:'); console.error(response.body); console.error(); throw new Error( 'Error response. Check token, baseUri, domains, etc.' ); } return response; }); } /* api('GET', baseUri + '/' + 'hostedzone').then(function(response){ console.log('######## RESPONSE #########'); }); */ return { init: function(options) { request = options.request; return null; }, zones: function(data) { //console.info('List Zones', data); return api('GET', baseUri + '/' + 'hostedzone').then(function(response){ return parseString(response.body).then(function(body){ var zones = body.ListHostedZonesResponse.HostedZones[0].HostedZone.map(function(element) { return element.Name[0].replace(/\.$/, ''); // all route 53 domains terminate with a dot }); return zones; }); }); }, set: function(data) { // console.info('Add TXT', data); // console.log('data: ' + JSON.stringify(data, null, 2)); var challenge = data.challenge; if (!challenge.dnsZone) { throw new Error('No matching zone for ' + challenge.dnsHost); } return api('GET', baseUri + '/' + 'hostedzonesbyname', null, 'dnsname=' + challenge.dnsZone + '&maxitems=1') .then(function(response){ return parseString(response.body).then(function(body){ return body; }); }) .then(function(body){ var zoneID = body.ListHostedZonesByNameResponse.HostedZones[0].HostedZone[0].Id[0]; var xmlns = body.ListHostedZonesByNameResponse.$.xmlns; var parameters = { 'ChangeResourceRecordSetsRequest': { '$': { 'xmlns': xmlns }, 'ChangeBatch': [ { 'Changes': [ { 'Change': [ { 'Action': [ 'CREATE' ], 'ResourceRecordSet': [ { 'Type': [ 'TXT' ], 'Name': [ challenge.dnsHost // AWS requires FQDN ], 'ResourceRecords': [ { 'ResourceRecord': [ { 'Value': [ '"' + challenge.dnsAuthorization + '"' // value must be surrounded by double quotes ] } ] } ], 'TTL': [ 300 ] } ] } ] } ] } ] } }; // By default AWS creates record sets with simple routing policy, so duplicates are not allowed. // A workaround is put the record sets in different weighted policies. if(challenge.identifier.value.startsWith('foo')) { if(challenge.wildcard) { parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Weight = [ 1 ]; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [ 'wildcard' ]; } else { parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Weight = [ 1 ]; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [ 'subdomain' ]; } } var xml = builder.buildObject(parameters); //console.log('xml: ' + xml); return api('POST', baseUri + zoneID + '/rrset/', xml); }); }, get: function(data) { // console.info('List TXT', data); var challenge = data.challenge; return api('GET', baseUri + '/' + 'hostedzonesbyname', null, 'dnsname=' + challenge.dnsZone + '&maxitems=1') .then(function(response){ return parseString(response.body).then(function(body){ return body; }); }) .then(function(body){ var zoneID = body.ListHostedZonesByNameResponse.HostedZones[0].HostedZone[0].Id[0]; // console.log('zoneID: ' + zoneID); return zoneID; }) .then(function(zoneID){ // GET /2013-04-01/hostedzone/Id/rrset?identifier=StartRecordIdentifier&maxitems=MaxItems&name=StartRecordName&type=StartRecordType HTTP/1.1 return api('GET', baseUri + '/' + zoneID + '/rrset', null, 'name=' + challenge.dnsPrefix + '.' + challenge.dnsZone + '.' + '&type=TXT') .then(function(response){ return parseString(response.body).then(function(body){ if (body.ListResourceRecordSetsResponse.ResourceRecordSets[0] === '') return null; var record = body.ListResourceRecordSetsResponse.ResourceRecordSets[0].ResourceRecordSet.filter(function(element){ return ( element.ResourceRecords[0].ResourceRecord[0].Value[0].replace(/\"/g, '') === challenge.dnsAuthorization && element.Name[0].includes(challenge.dnsPrefix) ); })[0]; if (record) { return { dnsAuthorization: record.ResourceRecords[0].ResourceRecord[0].Value[0].replace(/\"/g, '') }; } return null; }); }); }); }, remove: function(data) { // console.info('Remove TXT', data); var challenge = data.challenge; return api('GET', baseUri + '/' + 'hostedzonesbyname', null, 'dnsname=' + challenge.dnsZone + '&maxitems=1') .then(function(response){ return parseString(response.body).then(function(body){ return body; }); }) .then(function(body){ var zoneID = body.ListHostedZonesByNameResponse.HostedZones[0].HostedZone[0].Id[0]; var xmlns = body.ListHostedZonesByNameResponse.$.xmlns; var parameters = { 'ChangeResourceRecordSetsRequest': { '$': { 'xmlns': xmlns }, 'ChangeBatch': [ { 'Changes': [ { 'Change': [ { 'Action': [ 'DELETE' ], 'ResourceRecordSet': [ { 'Type': [ 'TXT' ], 'Name': [ challenge.dnsHost // AWS requires FQDN ], 'ResourceRecords': [ { 'ResourceRecord': [ { 'Value': [ '"' + challenge.dnsAuthorization + '"' // value must be surrounded by double quotes ] } ] } ], 'TTL': [ 300 ] } ] } ] } ] } ] } }; if(challenge.identifier.value.startsWith('foo')) { if(challenge.wildcard) { parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Weight = [ 1 ]; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [ 'wildcard' ]; } else { parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Weight = [ 1 ]; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [ 'subdomain' ]; } } var xml = builder.buildObject(parameters); // console.log('\n', xml, '\n'); return api('POST', baseUri + zoneID + '/rrset/', xml); }); } } };