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

294 lines
7.6 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;
var defaults = {
baseUri: '/2013-04-01/'
};
function setWeightedPolicy(XmlObject, isWildcard) {
if (isWildcard) {
XmlObject.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [
'wildcard'
];
} else {
XmlObject.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [
'subdomain'
];
}
XmlObject.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Weight = [
1
];
}
// used to build XML payload
var XmlParameters = {
ChangeResourceRecordSetsRequest: {
$: {
xmlns: ''
},
ChangeBatch: [
{
Changes: [
{
Change: [
{
Action: [''],
ResourceRecordSet: [
{
Type: ['TXT'],
Name: [''],
ResourceRecords: [
{
ResourceRecord: [
{
Value: ['']
}
]
}
],
TTL: [300]
}
]
}
]
}
]
}
]
}
};
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
};
var parameters = {
method: method
};
if (method === 'POST') {
options.headers = { 'Content-Type': 'application/xml' };
parameters.headers = { 'Content-Type': 'application/xml' };
}
if (body) {
parameters.body = body;
}
var url = v4.createPresignedURL(
method,
'route53.amazonaws.com',
path,
'route53',
body,
options
);
parameters.url = url;
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;
});
}
return {
init: function(options) {
request = options.request;
return null;
},
zones: function(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) {
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 = JSON.parse(JSON.stringify(XmlParameters));
parameters.ChangeResourceRecordSetsRequest.$.xmlns = xmlns;
parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].Action[0] =
'CREATE';
parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Name[0] =
challenge.dnsHost; // AWS requires FQDN
parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].ResourceRecords[0].ResourceRecord[0].Value[0] =
'"' + challenge.dnsAuthorization + '"'; // value must be surrounded by double quotes
// 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')) {
setWeightedPolicy(parameters, challenge.wildcard);
}
var xml = builder.buildObject(parameters);
return api('POST', baseUri + zoneID + '/rrset/', xml);
});
},
get: function(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];
return zoneID;
})
.then(function(zoneID) {
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) {
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 = JSON.parse(JSON.stringify(XmlParameters));
parameters.ChangeResourceRecordSetsRequest.$.xmlns = xmlns;
parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].Action[0] =
'DELETE';
parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Name[0] =
challenge.dnsHost; // AWS requires FQDN
parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].ResourceRecords[0].ResourceRecord[0].Value[0] =
'"' + challenge.dnsAuthorization + '"'; // value must be surrounded by double quotes
// need to add weight and setidentifier to delete record set
if (challenge.identifier.value.startsWith('foo')) {
setWeightedPolicy(parameters, challenge.wildcard);
}
var xml = builder.buildObject(parameters);
return api('POST', baseUri + zoneID + '/rrset/', xml);
});
}
};
};