acme-dns-01-route53.js/lib/index.js

330 lines
9.2 KiB
JavaScript

'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);
});
}
}
};