Make LE work with hyphenated domains
This commit is contained in:
@@ -21,7 +21,8 @@ exports = module.exports = {
|
||||
getCertificate: getCertificate,
|
||||
|
||||
// testing
|
||||
_name: 'acme'
|
||||
_name: 'acme',
|
||||
_getChallengeSubdomain: getChallengeSubdomain
|
||||
};
|
||||
|
||||
function Acme2Error(reason, errorOrMessage) {
|
||||
@@ -435,11 +436,14 @@ function getChallengeSubdomain(hostname, domain) {
|
||||
if (hostname === domain) {
|
||||
challengeSubdomain = '_acme-challenge';
|
||||
} else if (hostname.includes('*')) { // wildcard
|
||||
challengeSubdomain = hostname.replace('*', '_acme-challenge').slice(0, -domain.length - 1);
|
||||
let subdomain = hostname.slice(0, -domain.length - 1);
|
||||
challengeSubdomain = subdomain ? subdomain.replace('*', '_acme-challenge') : '_acme-challenge';
|
||||
} else {
|
||||
challengeSubdomain = '_acme-challenge.' + hostname.slice(0, -domain.length - 1);
|
||||
}
|
||||
|
||||
debug(`getChallengeSubdomain: challenge subdomain for hostname ${hostname} at domain ${domain} is ${challengeSubdomain}`);
|
||||
|
||||
return challengeSubdomain;
|
||||
}
|
||||
|
||||
@@ -466,7 +470,7 @@ Acme2.prototype.prepareDnsChallenge = function (hostname, domain, authorization,
|
||||
domains.upsertDnsRecords(challengeSubdomain, domain, 'TXT', [ `"${txtValue}"` ], function (error) {
|
||||
if (error) return callback(new Acme2Error(Acme2Error.EXTERNAL_ERROR, error.message));
|
||||
|
||||
domains.waitForDnsRecord(`${challengeSubdomain}`, domain, 'TXT', txtValue, { interval: 5000, times: 200 }, function (error) {
|
||||
domains.waitForDnsRecord(challengeSubdomain, domain, 'TXT', txtValue, { interval: 5000, times: 200 }, function (error) {
|
||||
if (error) return callback(new Acme2Error(Acme2Error.EXTERNAL_ERROR, error.message));
|
||||
|
||||
callback(null, challenge);
|
||||
|
||||
@@ -26,7 +26,10 @@ module.exports = exports = {
|
||||
|
||||
makeWildcard: makeWildcard,
|
||||
|
||||
DomainsError: DomainsError
|
||||
DomainsError: DomainsError,
|
||||
|
||||
// exported for testing
|
||||
_getName: getName
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
@@ -347,19 +350,25 @@ function del(domain, callback) {
|
||||
|
||||
// returns the 'name' that needs to be inserted into zone
|
||||
function getName(domain, subdomain, type) {
|
||||
// support special caas domains
|
||||
// hack for supporting special caas domains. if we want to remove this, we have to fix the appstore domain API first
|
||||
if (domain.provider === 'caas') return subdomain;
|
||||
|
||||
if (domain.domain === domain.zoneName) return subdomain;
|
||||
const part = domain.domain.slice(0, -domain.zoneName.length - 1);
|
||||
|
||||
var part = domain.domain.slice(0, -domain.zoneName.length - 1);
|
||||
if (subdomain === '') return part;
|
||||
|
||||
if (subdomain === '') {
|
||||
return part;
|
||||
} else if (type === 'TXT') {
|
||||
return `${subdomain}.${part}`;
|
||||
if (!domain.config.hyphenatedSubdomains) return part ? `${subdomain}.${part}` : subdomain;
|
||||
|
||||
// hyphenatedSubdomains
|
||||
if (type !== 'TXT') return `${subdomain}-${part}`;
|
||||
|
||||
if (subdomain.startsWith('_acme-challenge.')) {
|
||||
return `${subdomain}-${part}`;
|
||||
} else if (subdomain === '_acme-challenge') {
|
||||
const up = part.replace(/^[^.]*\.?/, ''); // this gets the domain one level up
|
||||
return up ? `${subdomain}.${up}` : subdomain;
|
||||
} else {
|
||||
return subdomain + (domain.config.hyphenatedSubdomains ? '-' : '.') + part;
|
||||
return `${subdomain}.${part}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +441,8 @@ function waitForDnsRecord(subdomain, domain, type, value, options, callback) {
|
||||
get(domain, function (error, domainObject) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const hostname = fqdn(subdomain, domainObject);
|
||||
const name = getName(domainObject, subdomain, type);
|
||||
const hostname = `${name}.${domainObject.zoneName}`;
|
||||
|
||||
api(domainObject.provider).waitForDns(hostname, domainObject.zoneName, type, value, options, callback);
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ var acme2 = require('./cert/acme2.js'),
|
||||
config = require('./config.js'),
|
||||
constants = require('./constants.js'),
|
||||
crypto = require('crypto'),
|
||||
debug = require('debug')('box:certificates'),
|
||||
debug = require('debug')('box:reverseproxy'),
|
||||
domains = require('./domains.js'),
|
||||
ejs = require('ejs'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
|
||||
44
src/test/acme2-test.js
Normal file
44
src/test/acme2-test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* global it:false */
|
||||
/* global describe:false */
|
||||
/* global before:false */
|
||||
/* global after:false */
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
config = require('../config.js'),
|
||||
database = require('../database.js'),
|
||||
acme2 = require('../cert/acme2.js'),
|
||||
expect = require('expect.js'),
|
||||
_ = require('underscore');
|
||||
|
||||
describe('Acme2', function () {
|
||||
before(function (done) {
|
||||
config._reset();
|
||||
|
||||
async.series([
|
||||
database.initialize,
|
||||
database._clear
|
||||
], done);
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
async.series([
|
||||
database._clear,
|
||||
database.uninitialize
|
||||
], done);
|
||||
});
|
||||
|
||||
describe('getChallengeSubdomain', function () {
|
||||
it('non-wildcard', function () {
|
||||
expect(acme2._getChallengeSubdomain('example.com', 'example.com')).to.be('_acme-challenge');
|
||||
expect(acme2._getChallengeSubdomain('git.example.com', 'example.com')).to.be('_acme-challenge.git');
|
||||
});
|
||||
|
||||
it('wildcard', function () {
|
||||
expect(acme2._getChallengeSubdomain('*.example.com', 'example.com')).to.be('_acme-challenge');
|
||||
expect(acme2._getChallengeSubdomain('*.git.example.com', 'example.com')).to.be('_acme-challenge.git');
|
||||
expect(acme2._getChallengeSubdomain('*.example.com', 'customer.example.com')).to.be('_acme-challenge'); // for hyphenatedSubdomains
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -29,13 +29,13 @@ describe('Domains', function () {
|
||||
], done);
|
||||
});
|
||||
|
||||
const domain = {
|
||||
domain: 'example.com',
|
||||
zoneName: 'example.com',
|
||||
config: {}
|
||||
};
|
||||
|
||||
describe('validateHostname', function () {
|
||||
const domain = {
|
||||
domain: 'example.com',
|
||||
zoneName: 'example.com',
|
||||
config: {}
|
||||
};
|
||||
|
||||
it('does not allow admin subdomain', function () {
|
||||
config.setFqdn('example.com');
|
||||
config.setAdminFqdn('my.example.com');
|
||||
@@ -85,4 +85,109 @@ describe('Domains', function () {
|
||||
expect(domains.validateHostname('a0.x', domain)).to.be.an(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getName', function () {
|
||||
it('works with zoneName==domain (not hyphenated)', function () {
|
||||
const domain = {
|
||||
domain: 'example.com',
|
||||
zoneName: 'example.com',
|
||||
config: {}
|
||||
};
|
||||
|
||||
expect(domains._getName(domain, '', 'A')).to.be('');
|
||||
expect(domains._getName(domain, 'www', 'A')).to.be('www');
|
||||
expect(domains._getName(domain, 'www.dev', 'A')).to.be('www.dev');
|
||||
|
||||
expect(domains._getName(domain, '', 'MX')).to.be('');
|
||||
|
||||
expect(domains._getName(domain, '', 'TXT')).to.be('');
|
||||
expect(domains._getName(domain, 'www', 'TXT')).to.be('www');
|
||||
expect(domains._getName(domain, 'www.dev', 'TXT')).to.be('www.dev');
|
||||
});
|
||||
|
||||
it('works when zoneName!=domain (not hyphenated)', function () {
|
||||
const domain = {
|
||||
domain: 'dev.example.com',
|
||||
zoneName: 'example.com',
|
||||
config: {}
|
||||
};
|
||||
|
||||
expect(domains._getName(domain, '', 'A')).to.be('dev');
|
||||
expect(domains._getName(domain, 'www', 'A')).to.be('www.dev');
|
||||
expect(domains._getName(domain, 'www.dev', 'A')).to.be('www.dev.dev');
|
||||
|
||||
expect(domains._getName(domain, '', 'MX')).to.be('dev');
|
||||
|
||||
expect(domains._getName(domain, '', 'TXT')).to.be('dev');
|
||||
expect(domains._getName(domain, 'www', 'TXT')).to.be('www.dev');
|
||||
expect(domains._getName(domain, 'www.dev', 'TXT')).to.be('www.dev.dev');
|
||||
});
|
||||
|
||||
it('works when hyphenated - level1', function () {
|
||||
const domain = {
|
||||
domain: 'customer.example.com',
|
||||
zoneName: 'example.com',
|
||||
config: {
|
||||
hyphenatedSubdomains: true
|
||||
}
|
||||
};
|
||||
|
||||
expect(domains._getName(domain, '', 'A')).to.be('customer');
|
||||
expect(domains._getName(domain, 'www', 'A')).to.be('www-customer');
|
||||
expect(domains._getName(domain, 'www.dev', 'A')).to.be('www.dev-customer');
|
||||
|
||||
expect(domains._getName(domain, '', 'MX')).to.be('customer');
|
||||
|
||||
expect(domains._getName(domain, '', 'TXT')).to.be('customer');
|
||||
expect(domains._getName(domain, '_dmarc', 'TXT')).to.be('_dmarc.customer');
|
||||
expect(domains._getName(domain, 'cloudron._domainkey', 'TXT')).to.be('cloudron._domainkey.customer');
|
||||
expect(domains._getName(domain, '_acme-challenge.my', 'TXT')).to.be('_acme-challenge.my-customer');
|
||||
expect(domains._getName(domain, '_acme-challenge', 'TXT')).to.be('_acme-challenge');
|
||||
});
|
||||
|
||||
it('works when hyphenated - level2', function () {
|
||||
const domain = {
|
||||
domain: 'customer.dev.example.com',
|
||||
zoneName: 'example.com',
|
||||
config: {
|
||||
hyphenatedSubdomains: true
|
||||
}
|
||||
};
|
||||
|
||||
expect(domains._getName(domain, '', 'A')).to.be('customer.dev');
|
||||
expect(domains._getName(domain, 'www', 'A')).to.be('www-customer.dev');
|
||||
expect(domains._getName(domain, 'www.dev', 'A')).to.be('www.dev-customer.dev');
|
||||
|
||||
expect(domains._getName(domain, '', 'MX')).to.be('customer.dev');
|
||||
|
||||
expect(domains._getName(domain, '', 'TXT')).to.be('customer.dev');
|
||||
expect(domains._getName(domain, '_dmarc', 'TXT')).to.be('_dmarc.customer.dev');
|
||||
expect(domains._getName(domain, 'cloudron._domainkey', 'TXT')).to.be('cloudron._domainkey.customer.dev');
|
||||
expect(domains._getName(domain, '_acme-challenge.my', 'TXT')).to.be('_acme-challenge.my-customer.dev');
|
||||
expect(domains._getName(domain, '_acme-challenge', 'TXT')).to.be('_acme-challenge.dev');
|
||||
});
|
||||
|
||||
it('works with caas', function () {
|
||||
const domain = {
|
||||
domain: 'customer.example.com',
|
||||
provider: 'caas',
|
||||
zoneName: 'example.com',
|
||||
config: {
|
||||
hyphenatedSubdomains: true
|
||||
}
|
||||
};
|
||||
|
||||
expect(domains._getName(domain, '', 'A')).to.be('');
|
||||
expect(domains._getName(domain, 'www', 'A')).to.be('www');
|
||||
expect(domains._getName(domain, 'www.dev', 'A')).to.be('www.dev');
|
||||
|
||||
expect(domains._getName(domain, '', 'MX')).to.be('');
|
||||
|
||||
expect(domains._getName(domain, '', 'TXT')).to.be('');
|
||||
expect(domains._getName(domain, '_dmarc', 'TXT')).to.be('_dmarc');
|
||||
expect(domains._getName(domain, 'cloudron._domainkey', 'TXT')).to.be('cloudron._domainkey');
|
||||
expect(domains._getName(domain, '_acme-challenge.my', 'TXT')).to.be('_acme-challenge.my');
|
||||
expect(domains._getName(domain, '_acme-challenge', 'TXT')).to.be('_acme-challenge');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user