dns: add ovh backend

This commit is contained in:
Girish Ramakrishnan
2023-11-05 18:38:30 +01:00
parent 8e468788a9
commit b88afbac4e
14 changed files with 349 additions and 11 deletions

View File

@@ -2704,4 +2704,5 @@
* Show disk consumption of docker volumes for /run and /tmp of apps separately
* dns: add dnsimple automation
* roles: admin role can access branding and networking
* dns: add ovh backend

View File

@@ -226,6 +226,16 @@ const REGIONS_OVH = [
{ name: 'Warsaw (WAW)', value: 'https://s3.waw.cloud.ovh.net', region: 'waw' },
];
const ENDPOINTS_OVH = [
{ name: 'OVH Europe', value: 'ovh-eu' },
{ name: 'OVH US', value: 'ovh-us' },
{ name: 'OVH North-America', value: 'ovh-ca' },
{ name: 'SoYouStart Europe', value: 'soyoustart-eu' },
{ name: 'SoYouStart North-America', value: 'soyoustart-ca' },
{ name: 'Kimsufi Europe', value: 'kimsufi-eu' },
{ name: 'Kimsufi North-America', value: 'kimsufi-ca' },
];
// https://docs.ionos.com/cloud/managed-services/s3-object-storage/endpoints
const REGIONS_IONOS = [
{ name: 'Frankfurt (DE)', value: 'https://s3-de-central.profitbricks.com', region: 's3-de-central' }, // default

View File

@@ -1,6 +1,6 @@
'use strict';
/* global $, tld, angular, Clipboard */
/* global $, tld, angular, Clipboard, ENDPOINTS_OVH */
// create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
@@ -55,6 +55,8 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
}
};
$scope.ovhEndpoints = ENDPOINTS_OVH;
$scope.needsPort80 = function (dnsProvider, tlsProvider) {
return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
(tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
@@ -91,6 +93,7 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
{ name: 'Name.com', value: 'namecom' },
{ name: 'Namecheap', value: 'namecheap' },
{ name: 'Netcup', value: 'netcup' },
{ name: 'OVH', value: 'ovh' },
{ name: 'Porkbun', value: 'porkbun' },
{ name: 'Vultr', value: 'vultr' },
{ name: 'Wildcard', value: 'wildcard' },
@@ -123,6 +126,10 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
netcupCustomerNumber: '',
netcupApiKey: '',
netcupApiPassword: '',
ovhEndpoint: 'ovh-eu',
ovhConsumerKey: '',
ovhAppKey: '',
ovhAppSecret: '',
porkbunSecretapikey: '',
porkbunApikey: '',
@@ -222,6 +229,11 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
config.customerNumber = $scope.dnsCredentials.netcupCustomerNumber;
config.apiKey = $scope.dnsCredentials.netcupApiKey;
config.apiPassword = $scope.dnsCredentials.netcupApiPassword;
} else if (provider === 'ovh') {
config.endpoint = $scope.dnsCredentials.ovhEndpoint;
config.consumerKey = $scope.dnsCredentials.ovhConsumerKey;
config.appKey = $scope.dnsCredentials.ovhAppKey;
config.appSecret = $scope.dnsCredentials.ovhAppSecret;
} else if (provider === 'porkbun') {
config.apikey = $scope.dnsCredentials.porkbunApikey;
config.secretapikey = $scope.dnsCredentials.porkbunSecretapikey;

View File

@@ -232,15 +232,33 @@
<input type="text" class="form-control" ng-model="dnsCredentials.dnsimpleAccessToken" name="dnsimpleAccessToken" ng-required="dnsCredentials.provider === 'dnsimple'" ng-disabled="dnsCredentials.busy">
</p>
<!-- OVH -->
<p class="form-group" ng-show="dnsCredentials.provider === 'ovh'">
<label class="control-label" for="inputConfigureOvhEndpoint">Endpoint</label>
<select class="form-control" name="endpoint" id="inputConfigureOvhEndpoint" ng-model="dnsCredentials.ovhEndpoint" ng-options="a.value as a.name for a in ovhEndpoints" ng-disabled="dnsCredentials.busy" ng-required="dnsCredentials.provider === 'ovh'"></select>
</p>
<p class="form-group" ng-show="dnsCredentials.provider === 'ovh'">
<label class="control-label">Consumer Key</label>
<input type="text" class="form-control" ng-model="dnsCredentials.ovhConsumerKey" name="ovhConsumerKey" ng-disabled="dnsCredentials.busy" ng-required="dnsCredentials.provider === 'ovh'">
</p>
<p class="form-group" ng-show="dnsCredentials.provider === 'ovh'">
<label class="control-label">Application Key</label>
<input type="text" class="form-control" ng-model="dnsCredentials.ovhAppKey" name="ovhAppKey" ng-disabled="dnsCredentials.busy" ng-minlength="1" ng-required="dnsCredentials.provider === 'ovh'">
</p>
<p class="form-group" ng-show="dnsCredentials.provider === 'ovh'">
<label class="control-label">Application Secret</label>
<input type="text" class="form-control" ng-model="dnsCredentials.ovhAppSecret" name="ovhAppSecret" ng-disabled="dnsCredentials.busy" ng-required="dnsCredentials.provider === 'ovh'">
</p>
<!-- Porkbun -->
<div class="form-group" ng-class="{ 'has-error': dnsCredentialsForm.porkbunApikey.$dirty && dnsCredentialsForm.porkbunApikey.$invalid }" ng-show="dnsCredentials.provider === 'porkbun'">
<p class="form-group" ng-class="{ 'has-error': dnsCredentialsForm.porkbunApikey.$dirty && dnsCredentialsForm.porkbunApikey.$invalid }" ng-show="dnsCredentials.provider === 'porkbun'">
<label class="control-label">API Key</label>
<input type="text" class="form-control" ng-model="dnsCredentials.porkbunApikey" name="porkbunApikey" placeholder="API Key" ng-minlength="1" ng-required="dnsCredentials.provider === 'porkbun'" ng-disabled="dnsCredentials.busy">
</div>
<div class="form-group" ng-class="{ 'has-error': dnsCredentialsForm.porkbunSecretapikey.$dirty && dnsCredentialsForm.porkbunSecretapikey.$invalid }" ng-show="dnsCredentials.provider === 'porkbun'">
<label class="control-label">API Secret</label>
<input type="text" class="form-control" ng-model="dnsCredentials.porkbunSecretapikey" name="porkbunSecretapikey" placeholder="API Secret" ng-required="dnsCredentials.provider === 'porkbun'" ng-disabled="dnsCredentials.busy">
</div>
</p>
<p class="form-group" ng-class="{ 'has-error': dnsCredentialsForm.porkbunSecretapikey.$dirty && dnsCredentialsForm.porkbunSecretapikey.$invalid }" ng-show="dnsCredentials.provider === 'porkbun'">
<label class="control-label">API Secret</label>
<input type="text" class="form-control" ng-model="dnsCredentials.porkbunSecretapikey" name="porkbunSecretapikey" placeholder="API Secret" ng-required="dnsCredentials.provider === 'porkbun'" ng-disabled="dnsCredentials.busy">
</p>
<!-- Hetzner -->
<p class="form-group" ng-show="dnsCredentials.provider === 'hetzner'">

View File

@@ -1056,7 +1056,11 @@
"porkbunApikey": "API Key",
"porkbunSecretapikey": "Secret API Key",
"bunnyAccessKey": "Bunny Access Key",
"dnsimpleAccessToken": "Access Token"
"dnsimpleAccessToken": "Access Token",
"ovhEndpoint": "Endpoint",
"ovhConsumerKey": "Consumer Key",
"ovhAppKey": "Application Key",
"ovhAppSecret": "Application Secret"
},
"removeDialog": {
"title": "Really remove {{ domain }}?",

View File

@@ -824,7 +824,8 @@
"cloudflareDefaultProxyStatus": "Inschakelen proxy voor nieuwe DNS regels",
"porkbunApikey": "API sleutel",
"porkbunSecretapikey": "Geheime API sleutel",
"bunnyAccessKey": "Bunny toegangssleutel"
"bunnyAccessKey": "Bunny toegangssleutel",
"dnsimpleAccessToken": "Toegangstoken"
},
"title": "Domeinen & Certificaten",
"addDomain": "Domein toevoegen",

View File

@@ -98,6 +98,25 @@
<input type="text" class="form-control" ng-model="domainConfigure.netcupApiPassword" name="netcupApiPassword" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'netcup'">
</div>
<!-- OVH -->
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
<label class="control-label" for="inputConfigureOvhEndpoint">{{ 'domains.domainDialog.ovhEndpoint' | tr }}</label>
<select class="form-control" name="endpoint" id="inputConfigureOvhEndpoint" ng-model="domainConfigure.ovhEndpoint" ng-options="a.value as a.name for a in ovhEndpoints" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'ovh'"></select>
</div>
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
<label class="control-label">{{ 'domains.domainDialog.ovhConsumerKey' | tr }}</label>
<input type="text" class="form-control" ng-model="domainConfigure.ovhConsumerKey" name="ovhConsumerKey" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'ovh'">
</div>
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
<label class="control-label">{{ 'domains.domainDialog.ovhAppKey' | tr }}</label>
<input type="text" class="form-control" ng-model="domainConfigure.ovhAppKey" name="ovhAppKey" ng-disabled="domainConfigure.busy" ng-minlength="1" ng-required="domainConfigure.provider === 'ovh'">
</div>
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'ovh'">
<label class="control-label">{{ 'domains.domainDialog.ovhAppSecret' | tr }}</label>
<input type="text" class="form-control" ng-model="domainConfigure.ovhAppSecret" name="ovhAppSecret" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'ovh'">
</div>
<!-- Porkbun -->
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="domainConfigure.provider === 'porkbun'">
<label class="control-label">{{ 'domains.domainDialog.porkbunApikey' | tr }}</label>

View File

@@ -2,7 +2,7 @@
/* global async */
/* global angular */
/* global $, TASK_TYPES */
/* global $, TASK_TYPES, ENDPOINTS_OVH */
angular.module('Application').controller('DomainsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
@@ -56,6 +56,7 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
{ name: 'Name.com', value: 'namecom' },
{ name: 'Namecheap', value: 'namecheap' },
{ name: 'Netcup', value: 'netcup' },
{ name: 'OVH', value: 'ovh' },
{ name: 'Porkbun', value: 'porkbun' },
{ name: 'Vultr', value: 'vultr' },
{ name: 'Wildcard', value: 'wildcard' },
@@ -76,6 +77,7 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
case 'namecom': return 'Name.com';
case 'namecheap': return 'Namecheap';
case 'netcup': return 'Netcup';
case 'ovh': return 'OVH';
case 'gcdns': return 'Google Cloud';
case 'godaddy': return 'GoDaddy';
case 'vultr': return 'Vultr';
@@ -87,6 +89,8 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
}
};
$scope.ovhEndpoints = ENDPOINTS_OVH;
$scope.needsPort80 = function (dnsProvider, tlsProvider) {
return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
(tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
@@ -261,6 +265,10 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
netcupCustomerNumber: '',
netcupApiKey: '',
netcupApiPassword: '',
ovhEndpoint: 'ovh-eu',
ovhConsumerKey: '',
ovhAppKey: '',
ovhAppSecret: '',
porkbunSecretapikey: '',
porkbunApikey: '',
@@ -332,6 +340,11 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
$scope.domainConfigure.netcupApiKey = domain.provider === 'netcup' ? domain.config.apiKey : '';
$scope.domainConfigure.netcupApiPassword = domain.provider === 'netcup' ? domain.config.apiPassword : '';
$scope.domainConfigure.ovhEndpoint = domain.provider === 'ovh' ? domain.config.endpoint : '';
$scope.domainConfigure.ovhConsumerKey = domain.provider === 'ovh' ? domain.config.consumerKey : '';
$scope.domainConfigure.ovhAppKey = domain.provider === 'ovh' ? domain.config.appKey : '';
$scope.domainConfigure.ovhAppSecret = domain.provider === 'ovh' ? domain.config.appSecret : '';
$scope.domainConfigure.porkbunApikey = domain.provider === 'porkbun' ? domain.config.apikey : '';
$scope.domainConfigure.porkbunSecretapikey = domain.provider === 'porkbun' ? domain.config.secretapikey : '';
@@ -409,6 +422,11 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
data.customerNumber = $scope.domainConfigure.netcupCustomerNumber;
data.apiKey = $scope.domainConfigure.netcupApiKey;
data.apiPassword = $scope.domainConfigure.netcupApiPassword;
} else if (provider === 'ovh') {
data.endpoint = $scope.domainConfigure.ovhEndpoint;
data.consumerKey = $scope.domainConfigure.ovhConsumerKey;
data.appKey = $scope.domainConfigure.ovhAppKey;
data.appSecret = $scope.domainConfigure.ovhAppSecret;
} else if (provider === 'porkbun') {
data.apikey = $scope.domainConfigure.porkbunApikey;
data.secretapikey = $scope.domainConfigure.porkbunSecretapikey;
@@ -478,6 +496,10 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
$scope.domainConfigure.netcupCustomerNumber = '';
$scope.domainConfigure.netcupApiKey = '';
$scope.domainConfigure.netcupApiPassword = '';
$scope.domainConfigure.ovhEndpoint = '';
$scope.domainConfigure.ovhConsumerKey = '';
$scope.domainConfigure.ovhAppKey = '';
$scope.domainConfigure.ovhAppSecret = '';
$scope.domainConfigure.porkbunApikey = '';
$scope.domainConfigure.porkbunSecretapikey = '';
$scope.domainConfigure.vultrToken = '';

15
package-lock.json generated
View File

@@ -40,6 +40,7 @@
"nodemailer": "^6.9.4",
"nsyslog-parser": "^0.10.1",
"oidc-provider": "^8.2.2",
"ovh": "^2.0.3",
"qrcode": "^1.5.3",
"readdirp": "^3.6.0",
"safetydance": "^2.2.0",
@@ -4219,6 +4220,20 @@
"node": ">= 0.8.0"
}
},
"node_modules/ovh": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/ovh/-/ovh-2.0.3.tgz",
"integrity": "sha512-K2XpfSYza7PHVDqAP6BNk92X3f+BvaNd6eSgMqguLTaUz/DYzOyHsbGFu7lyHTkIUUb9qamvJ9FC6OTzk7tj3Q==",
"dependencies": {
"async": "0.9.x",
"bluebird": "^3.4.0"
}
},
"node_modules/ovh/node_modules/async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw=="
},
"node_modules/p-cancelable": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",

View File

@@ -48,6 +48,7 @@
"nodemailer": "^6.9.4",
"nsyslog-parser": "^0.10.1",
"oidc-provider": "^8.2.2",
"ovh": "^2.0.3",
"qrcode": "^1.5.3",
"readdirp": "^3.6.0",
"safetydance": "^2.2.0",

View File

@@ -60,6 +60,7 @@ function api(provider) {
case 'hetzner': return require('./dns/hetzner.js');
case 'noop': return require('./dns/noop.js');
case 'manual': return require('./dns/manual.js');
case 'ovh': return require('./dns/ovh.js');
case 'porkbun': return require('./dns/porkbun.js');
case 'wildcard': return require('./dns/wildcard.js');
default: return null;

View File

@@ -209,7 +209,7 @@ async function del(domainObject, location, type, values) {
if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message);
if (response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response));
if (response.statusCode === 400) continue;
if (response.statusCode === 404) continue;
if (response.statusCode !== 204) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response));
}
}

233
src/dns/ovh.js Normal file
View File

@@ -0,0 +1,233 @@
'use strict';
exports = module.exports = {
removePrivateFields,
injectPrivateFields,
upsert,
get,
del,
wait,
verifyDomainConfig
};
const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/ovh'),
dig = require('../dig.js'),
dns = require('../dns.js'),
ovhClient = require('ovh'),
safe = require('safetydance'),
waitForDns = require('./waitfordns.js');
function formatError(error) {
return `OVH DNS error ${error.error} ${error.message}`; // error.error is the statusCode
}
function removePrivateFields(domainObject) {
domainObject.config.appSecret = constants.SECRET_PLACEHOLDER;
return domainObject;
}
function injectPrivateFields(newConfig, currentConfig) {
if (newConfig.appSecret === constants.SECRET_PLACEHOLDER) newConfig.appSecret = currentConfig.appSecret;
}
function createClient(domainConfig) {
return ovhClient({
endpoint: domainConfig.endpoint,
appKey: domainConfig.appKey,
appSecret: domainConfig.appSecret,
consumerKey: domainConfig.consumerKey,
});
}
async function getDnsRecordIds(domainConfig, zoneName, name, type) {
assert.strictEqual(typeof domainConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof type, 'string');
debug(`get: ${name} in zone ${zoneName} of type ${type}`);
const client = createClient(domainConfig);
const [error, data] = await safe(client.requestPromised('GET', `/domain/zone/${zoneName}/record`, { fieldType: type, subDomain: name }));
if (error) {
if (error.error === 401 || error.error === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(error));
throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(error));
}
return data || []; // array of numbers. data is undefined when no entries
}
async function refreshZone(domainConfig, zoneName) {
assert.strictEqual(typeof domainConfig, 'object');
assert.strictEqual(typeof zoneName, 'string');
debug(`refresh: zone ${zoneName}`);
const client = createClient(domainConfig);
const [error] = await safe(client.requestPromised('POST', `/domain/zone/${zoneName}/refresh`));
if (error) {
if (error.error === 401 || error.error === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(error));
throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(error));
}
}
async function upsert(domainObject, location, type, values) {
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof type, 'string');
assert(Array.isArray(values));
const domainConfig = domainObject.config,
zoneName = domainObject.zoneName,
name = dns.getName(domainObject, location, type) || '';
debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
const recordIds = await getDnsRecordIds(domainConfig, zoneName, name, type);
const client = createClient(domainConfig);
// used to track available records to update instead of create
let i = 0;
for (let value of values) {
const data = {
subDomain: name,
target: value,
ttl: 60
};
let error;
if (i >= recordIds.length) {
data.fieldType = type;
[error] = await safe(client.requestPromised('POST', `/domain/zone/${zoneName}/record`, data));
} else {
[error] = await safe(client.requestPromised('PUT', `/domain/zone/${zoneName}/record/${recordIds[i]}`, data));
++i;
}
if (error) {
if (error.error === 401 || error.error === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(error));
throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(error));
}
}
for (let j = values.length + 1; j < recordIds.length; j++) {
const [error] = await safe(client.requestPromised('DELETE', `/domain/zone/${zoneName}/record/${recordIds[j]}`));
if (error) {
if (error.error === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(error));
if (error.error === 404) continue; // not found
throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(error));
}
}
await refreshZone(domainConfig, zoneName);
debug('upsert: completed');
}
async function get(domainObject, location, type) {
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof type, 'string');
const domainConfig = domainObject.config,
zoneName = domainObject.zoneName,
name = dns.getName(domainObject, location, type) || '';
const recordIds = await getDnsRecordIds(domainConfig, zoneName, name, type);
const client = createClient(domainConfig);
const result = [];
for (const id of recordIds) {
const [error, data] = await safe(client.requestPromised('GET', `/domain/zone/${zoneName}/record/${id}`));
if (error) {
if (error.error === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(error));
throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(error));
}
result.push(data.target);
}
return result;
}
async function del(domainObject, location, type, values) {
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof type, 'string');
assert(Array.isArray(values));
const domainConfig = domainObject.config,
zoneName = domainObject.zoneName,
name = dns.getName(domainObject, location, type) || '';
debug(`del: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
const recordIds = await getDnsRecordIds(domainConfig, zoneName, name, type);
const client = createClient(domainConfig);
for (const id of recordIds) {
const [error] = await safe(client.requestPromised('DELETE', `/domain/zone/${zoneName}/record/${id}`));
if (error) {
if (error.error === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(error));
if (error.error === 404) continue; // not found
throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(error));
}
}
await refreshZone(domainConfig, zoneName);
}
async function wait(domainObject, subdomain, type, value, options) {
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof subdomain, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof value, 'string');
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
const fqdn = dns.fqdn(subdomain, domainObject.domain);
await waitForDns(fqdn, domainObject.zoneName, type, value, options);
}
async function verifyDomainConfig(domainObject) {
assert.strictEqual(typeof domainObject, 'object');
const domainConfig = domainObject.config,
zoneName = domainObject.zoneName;
if (!domainConfig.endpoint || typeof domainConfig.endpoint !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'endpoint must be a non-empty string');
if (!domainConfig.appKey || typeof domainConfig.appKey !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'appKey must be a non-empty string');
if (!domainConfig.appSecret || typeof domainConfig.appSecret !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'appSecret must be a non-empty string');
if (!domainConfig.consumerKey || typeof domainConfig.consumerKey !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'consumerKey must be a non-empty string');
const ip = '127.0.0.1';
const credentials = {
endpoint: domainConfig.endpoint, // https://github.com/ovh/node-ovh#2-authorize-your-application-to-access-to-a-customer-account
appKey: domainConfig.appKey,
appSecret: domainConfig.appSecret,
consumerKey: domainConfig.consumerKey,
};
if (constants.TEST) return credentials; // this shouldn't be here
const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 }));
if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain');
if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers');
if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('ovh.net') !== -1; })) { // SoYouStart and Kimsufi can also be accomdated
debug('verifyDomainConfig: %j does not contain OVH NS', nameservers);
throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to OVH');
}
const location = 'cloudrontestdns';
await upsert(domainObject, location, 'A', [ ip ]);
debug('verifyDomainConfig: Test A record added');
await del(domainObject, location, 'A', [ ip ]);
debug('verifyDomainConfig: Test A record removed again');
return credentials;
}

View File

@@ -67,6 +67,7 @@ function api(provider) {
case 'namecheap': return require('./dns/namecheap.js');
case 'netcup': return require('./dns/netcup.js');
case 'noop': return require('./dns/noop.js');
case 'ovh': return require('./dns/ovh.js');
case 'manual': return require('./dns/manual.js');
case 'porkbun': return require('./dns/porkbun.js');
case 'wildcard': return require('./dns/wildcard.js');