nyaundi brian
5 years ago
commit
d64f91b74e
9 changed files with 362 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
.env |
|||
*secret* |
|||
node_modules |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"bracketSpacing": true, |
|||
"printWidth": 80, |
|||
"singleQuote": true, |
|||
"tabWidth": 2, |
|||
"trailingComma": "none", |
|||
"useTabs": true |
|||
} |
@ -0,0 +1,2 @@ |
|||
AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/) |
|||
Nyaundi Brian <danleyb2@gmail.com> (https://danleyb2.online/) |
@ -0,0 +1,83 @@ |
|||
# [acme-dns-01-ovh](https://git.rootprojects.org/root/acme-dns-01-ovh.js) | a [Root](https://rootprojects.org) project |
|||
|
|||
OVH DNS + Let's Encrypt for Node.js |
|||
|
|||
This handles ACME dns-01 challenges, compatible with ACME.js and Greenlock.js. |
|||
Passes [acme-dns-01-test](https://git.rootprojects.org/root/acme-dns-01-test.js). |
|||
|
|||
# Install |
|||
|
|||
```bash |
|||
npm install --save acme-dns-01-ovh@3.x |
|||
``` |
|||
|
|||
# Usage |
|||
|
|||
First you need to create API keys on the OVH portal : |
|||
|
|||
Go to the page https://api.ovh.com/createToken/index.cgi?GET=/domain/zone&PUT=/domain/zone&POST=/domain/zone&DELETE=/domain/zone |
|||
|
|||
Then you create an instance with your API keys |
|||
|
|||
```js |
|||
var dns01 = require('acme-dns-01-ovh').create({ |
|||
applicationKey: 'xxxx', // default |
|||
applicationSecret: 'xxxx', |
|||
consumerKey: 'xxxx', |
|||
region: 'ovh-eu', // one of ovh-eu, ovh-ca, kimsufi-eu, kimsufi-ca, soyoustart-eu, soyoustart-ca, runabove-ca |
|||
}); |
|||
``` |
|||
|
|||
Then you can use it with any compatible ACME module, |
|||
such as Greenlock.js or ACME.js. |
|||
|
|||
### Greenlock.js |
|||
|
|||
```js |
|||
var Greenlock = require('greenlock-express'); |
|||
var greenlock = Greenlock.create({ |
|||
challenges: { |
|||
'dns-01': dns01 |
|||
// ... |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
See [Greenlock™ Express](https://git.rootprojects.org/root/greenlock-express.js) |
|||
and/or [Greenlock.js](https://git.rootprojects.org/root/greenlock.js) documentation for more details. |
|||
|
|||
### ACME.js |
|||
|
|||
```js |
|||
// TODO |
|||
``` |
|||
|
|||
See the [ACME.js](https://git.rootprojects.org/root/acme-v2.js) for more details. |
|||
|
|||
### Build your own |
|||
|
|||
```js |
|||
dns01 |
|||
.set({ |
|||
identifier: { value: 'foo.example.com' }, |
|||
wildcard: false, |
|||
dnsHost: '_acme-challenge.foo.example.com', |
|||
dnsAuthorization: 'xxx_secret_xxx' |
|||
}) |
|||
.then(function() { |
|||
console.log('TXT record set'); |
|||
}) |
|||
.catch(function() { |
|||
console.log('Failed to set TXT record'); |
|||
}); |
|||
``` |
|||
|
|||
See [acme-dns-01-test](https://git.rootprojects.org/root/acme-dns-01-test.js) |
|||
for more implementation details. |
|||
|
|||
# Tests |
|||
|
|||
```bash |
|||
# node ./test.js domain-zone api-key |
|||
node ./test.js example.com xxxxxx |
|||
``` |
@ -0,0 +1,10 @@ |
|||
ZONE=example.co.uk |
|||
|
|||
|
|||
APPLICATION_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|||
APPLICATION_SECRET=xxxxxxx |
|||
CONSUMER_KEY=xxxxxxxxxx |
|||
|
|||
|
|||
# one of ovh-eu, ovh-ca, kimsufi-eu, kimsufi-ca, soyoustart-eu, soyoustart-ca, runabove-ca |
|||
REGION='ovh-eu' |
@ -0,0 +1,3 @@ |
|||
'use strict'; |
|||
|
|||
module.exports = require('./lib/index.js'); |
@ -0,0 +1,196 @@ |
|||
'use strict'; |
|||
var request = require('@root/request'); |
|||
request = require('util').promisify(request); |
|||
var crypto = require('crypto'); |
|||
var querystring = require('querystring'); |
|||
|
|||
var defaults = { |
|||
region:'ovh-eu', |
|||
basePath:'/1.0' |
|||
}; |
|||
|
|||
var recordsStore = {}; |
|||
|
|||
module.exports.create = function(config) { |
|||
var applicationKey = config.applicationKey||null; |
|||
var applicationSecret = config.applicationSecret||null; |
|||
var consumerKey = config.consumerKey||null; |
|||
// one of ovh-eu, ovh-ca, kimsufi-eu, kimsufi-ca, soyoustart-eu, soyoustart-ca, runabove-ca
|
|||
var region = config.region || defaults.region; |
|||
var apiTimeDiff = config.apiTimeDiff || null; |
|||
var basePath = config.basePath || defaults.basePath; |
|||
|
|||
if (typeof(applicationKey) !== 'string' || typeof(applicationSecret) !== 'string') { |
|||
throw new Error('[OVH] You should precise an application key / secret'); |
|||
} |
|||
|
|||
var endpoints = { |
|||
'ovh-eu': 'eu.api.ovh.com', |
|||
'ovh-ca': 'ca.api.ovh.com', |
|||
'kimsufi-eu': 'eu.api.kimsufi.com', |
|||
'kimsufi-ca': 'ca.api.kimsufi.com', |
|||
'soyoustart-eu': 'eu.api.soyoustart.com', |
|||
'soyoustart-ca': 'ca.api.soyoustart.com', |
|||
'runabove-ca': 'api.runabove.com' |
|||
}; |
|||
|
|||
var baseUrl = 'https://' + endpoints[region] + basePath; |
|||
|
|||
/** |
|||
* Signs an API request |
|||
* |
|||
* @param {String} httpMethod |
|||
* @param {String} url |
|||
* @param {String} body |
|||
* @param {Number|String} timestamp |
|||
* @return {String} The signature |
|||
*/ |
|||
function signRequest(httpMethod, url, body, timestamp) { |
|||
var s = [ |
|||
applicationSecret, |
|||
consumerKey, |
|||
httpMethod, |
|||
url, |
|||
body || '', |
|||
timestamp |
|||
]; |
|||
return '$1$' + crypto.createHash('sha1').update(s.join('+')).digest('hex'); |
|||
} |
|||
|
|||
function api(httpMethod, path, params) { |
|||
// Time drift
|
|||
if (apiTimeDiff === null && path !== '/auth/time') { |
|||
return api('GET', '/auth/time', {}) |
|||
.then(function(res) { |
|||
|
|||
apiTimeDiff = res.body - Math.round(Date.now() / 1000); |
|||
return api(httpMethod, path, params); |
|||
}).catch(function(err) { |
|||
// todo
|
|||
throw new Error('[OVH] Unable to fetch OVH API time'); |
|||
}); |
|||
} |
|||
|
|||
var headers = { |
|||
'X-Ovh-Application': applicationKey |
|||
}; |
|||
|
|||
if (httpMethod === 'GET') { |
|||
path += '?' + querystring.stringify(params); |
|||
} |
|||
|
|||
var url = baseUrl + path; |
|||
|
|||
if (path.indexOf('/auth') < 0) { |
|||
headers['X-Ovh-Timestamp'] = Math.round(Date.now() / 1000) + apiTimeDiff; |
|||
// Sign request
|
|||
if (typeof(consumerKey) === 'string') { |
|||
headers['X-Ovh-Consumer'] = consumerKey; |
|||
headers['X-Ovh-Signature'] = signRequest( |
|||
httpMethod, url, params, headers['X-Ovh-Timestamp'] |
|||
); |
|||
} |
|||
} |
|||
|
|||
console.debug( |
|||
'[OVH] API call:', |
|||
httpMethod, |
|||
path, |
|||
params || '' |
|||
); |
|||
|
|||
return request({ |
|||
method: httpMethod, |
|||
url: url, |
|||
headers: headers, |
|||
json: true, |
|||
form: params |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
init: function(deps) { |
|||
request = deps.request; |
|||
return null; |
|||
}, |
|||
zones: function(data) { |
|||
return api('GET', '/domain/zone') |
|||
.then(function(resp) { |
|||
if (200 !== resp.statusCode) { |
|||
console.error(resp.statusCode); |
|||
console.error(resp.body); |
|||
throw new Error('Could not get list of zones.'); |
|||
} |
|||
|
|||
// list of zones
|
|||
return resp.body; |
|||
|
|||
}).catch(function(err) { |
|||
|
|||
}); |
|||
}, |
|||
set: function(data) { |
|||
var ch = data.challenge; |
|||
var txt = ch.dnsAuthorization; |
|||
|
|||
// record name received as argument : www.qwerty.sampledomain.com
|
|||
// received zone id : sampledomain.com required record name by OVH : www.qwerty
|
|||
//console.debug('adding txt', data);
|
|||
return api('POST', '/domain/zone/' + ch.dnsZone + '/record', { |
|||
fieldType: 'TXT', |
|||
subDomain: ch.dnsPrefix, |
|||
target: txt, |
|||
ttl: 1 |
|||
}).then(function(resp) { |
|||
if (200 !== resp.statusCode) { |
|||
console.error(resp.statusCode); |
|||
console.error(resp.body); |
|||
throw new Error('record did not set.'); |
|||
} |
|||
// save id for remove
|
|||
recordsStore[ch.dnsPrefix] = resp.body['id'] |
|||
|
|||
}).then(function() { |
|||
// Apply zone modification on DNS servers
|
|||
return api('POST', '/domain/zone/' + ch.dnsZone + '/record', {}) |
|||
.then(function() { |
|||
return true; |
|||
}); |
|||
}); |
|||
}, |
|||
remove: function(data) { |
|||
var ch = data.challenge; |
|||
return api('DELETE', '/domain/zone/' + ch.dnsZone + '/record/'+recordsStore[ch.dnsPrefix]) |
|||
.then(function(resp) { |
|||
if (200 !== resp.statusCode) { |
|||
throw new Error('record did not remove.'); |
|||
} |
|||
}) |
|||
.then(function() { |
|||
return api('POST', '/domain/zone/' + ch.dnsZone + '/record', {}) |
|||
.then(function(resp) { |
|||
if (200 !== resp.statusCode) { |
|||
console.error(resp.statusCode); |
|||
console.error(resp.body); |
|||
throw new Error('record did not remove.'); |
|||
} |
|||
return true; |
|||
}); |
|||
}); |
|||
}, |
|||
get: function(data) { |
|||
var ch = data.challenge; |
|||
|
|||
return api('GET', '/domain/zone/' + ch.dnsZone + '/record/'+recordsStore[ch.dnsPrefix]) |
|||
.then(function(resp) { |
|||
if (200 !== resp.statusCode) { |
|||
throw new Error('record did not remove.'); |
|||
} |
|||
return { |
|||
dnsAuthorization:resp.body.target |
|||
}; |
|||
|
|||
}); |
|||
} |
|||
}; |
|||
}; |
@ -0,0 +1,30 @@ |
|||
{ |
|||
"name": "acme-dns-01-ovh", |
|||
"version": "3.0.2", |
|||
"description": "OVH DNS for Let's Encrypt / ACME dns-01 challenges with ACME.js and Greenlock.js", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"test": "node ./test.js" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://git.rootprojects.org/root/acme-dns-01-ovh.js.git" |
|||
}, |
|||
"keywords": [ |
|||
"OVH", |
|||
"dns", |
|||
"dns-01", |
|||
"letsencrypt", |
|||
"acme", |
|||
"greenlock" |
|||
], |
|||
"author": "Nyaundi Brian <danleyb2@gmail.com> (https://danleyb2.online/)", |
|||
"license": "MPL-2.0", |
|||
"dependencies": { |
|||
"@root/request": "^1.4.2" |
|||
}, |
|||
"devDependencies": { |
|||
"acme-dns-01-test": "^3.2.1", |
|||
"dotenv": "^8.0.0" |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
#!/usr/bin/env node
|
|||
'use strict'; |
|||
|
|||
// See https://git.coolaj86.com/coolaj86/acme-challenge-test.js
|
|||
var tester = require('acme-challenge-test'); |
|||
require('dotenv').config(); |
|||
|
|||
// Usage: node ./test.js example.com xxxxxxxxx
|
|||
var zone = process.argv[2] || process.env.ZONE; |
|||
var challenger = require('./index.js').create({ |
|||
applicationKey: process.argv[3] || process.env.APPLICATION_KEY, |
|||
applicationSecret: process.argv[4] || process.env.APPLICATION_SECRET, |
|||
consumerKey: process.argv[5] || process.env.CONSUMER_KEY, |
|||
region: process.env.REGION |
|||
}); |
|||
|
|||
// The dry-run tests can pass on, literally, 'example.com'
|
|||
// but the integration tests require that you have control over the domain
|
|||
tester |
|||
.testZone('dns-01', zone, challenger) |
|||
.then(function() { |
|||
console.info('PASS', zone); |
|||
}) |
|||
.catch(function(e) { |
|||
console.error(e.message); |
|||
console.error(e.stack); |
|||
}); |
Loading…
Reference in new issue