mirror of
https://github.com/certd/certd.git
synced 2026-04-24 12:27:25 +08:00
🔱: [acme] sync upgrade with 6 commits [trident-sync]
Bump v5.4.0 Bump dependencies Retry HTTP requests on server errors or when rate limited Forgot to refresh directory timestamp after successful get Add utility method tests
This commit is contained in:
@@ -12,33 +12,12 @@ const pkg = require('./../package.json');
|
||||
describe('http', () => {
|
||||
let testClient;
|
||||
|
||||
const endpoint = `http://${uuid()}.example.com`;
|
||||
const defaultUserAgent = `node-${pkg.name}/${pkg.version}`;
|
||||
const customUserAgent = 'custom-ua-123';
|
||||
|
||||
const primaryEndpoint = `http://${uuid()}.example.com`;
|
||||
const defaultUaEndpoint = `http://${uuid()}.example.com`;
|
||||
const customUaEndpoint = `http://${uuid()}.example.com`;
|
||||
|
||||
/**
|
||||
* HTTP mocking
|
||||
*/
|
||||
|
||||
before(() => {
|
||||
const defaultUaOpts = { reqheaders: { 'User-Agent': defaultUserAgent } };
|
||||
const customUaOpts = { reqheaders: { 'User-Agent': customUserAgent } };
|
||||
|
||||
nock(primaryEndpoint)
|
||||
.persist().get('/').reply(200, 'ok');
|
||||
|
||||
nock(defaultUaEndpoint, defaultUaOpts)
|
||||
.persist().get('/').reply(200, 'ok');
|
||||
|
||||
nock(customUaEndpoint, customUaOpts)
|
||||
.persist().get('/').reply(200, 'ok');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -54,7 +33,8 @@ describe('http', () => {
|
||||
*/
|
||||
|
||||
it('should http get', async () => {
|
||||
const resp = await testClient.request(primaryEndpoint, 'get');
|
||||
nock(endpoint).get('/').reply(200, 'ok');
|
||||
const resp = await testClient.request(endpoint, 'get');
|
||||
|
||||
assert.isObject(resp);
|
||||
assert.strictEqual(resp.status, 200);
|
||||
@@ -66,28 +46,76 @@ describe('http', () => {
|
||||
*/
|
||||
|
||||
it('should request using default user-agent', async () => {
|
||||
const resp = await testClient.request(defaultUaEndpoint, 'get');
|
||||
nock(endpoint).matchHeader('user-agent', defaultUserAgent).get('/').reply(200, 'ok');
|
||||
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
||||
const resp = await testClient.request(endpoint, 'get');
|
||||
|
||||
assert.isObject(resp);
|
||||
assert.strictEqual(resp.status, 200);
|
||||
assert.strictEqual(resp.data, 'ok');
|
||||
});
|
||||
|
||||
it('should not request using custom user-agent', async () => {
|
||||
await assert.isRejected(testClient.request(customUaEndpoint, 'get'));
|
||||
it('should reject using custom user-agent', async () => {
|
||||
nock(endpoint).matchHeader('user-agent', defaultUserAgent).get('/').reply(200, 'ok');
|
||||
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
||||
await assert.isRejected(testClient.request(endpoint, 'get'));
|
||||
});
|
||||
|
||||
it('should request using custom user-agent', async () => {
|
||||
nock(endpoint).matchHeader('user-agent', customUserAgent).get('/').reply(200, 'ok');
|
||||
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
||||
const resp = await testClient.request(customUaEndpoint, 'get');
|
||||
const resp = await testClient.request(endpoint, 'get');
|
||||
|
||||
assert.isObject(resp);
|
||||
assert.strictEqual(resp.status, 200);
|
||||
assert.strictEqual(resp.data, 'ok');
|
||||
});
|
||||
|
||||
it('should not request using default user-agent', async () => {
|
||||
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
||||
await assert.isRejected(testClient.request(defaultUaEndpoint, 'get'));
|
||||
it('should reject using default user-agent', async () => {
|
||||
nock(endpoint).matchHeader('user-agent', customUserAgent).get('/').reply(200, 'ok');
|
||||
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
||||
await assert.isRejected(testClient.request(endpoint, 'get'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Retry on HTTP errors
|
||||
*/
|
||||
|
||||
it('should retry on 429 rate limit', async () => {
|
||||
let rateLimitCount = 0;
|
||||
|
||||
nock(endpoint).persist().get('/').reply(() => {
|
||||
rateLimitCount += 1;
|
||||
|
||||
if (rateLimitCount < 3) {
|
||||
return [429, 'Rate Limit Exceeded', { 'Retry-After': 1 }];
|
||||
}
|
||||
|
||||
return [200, 'ok'];
|
||||
});
|
||||
|
||||
assert.strictEqual(rateLimitCount, 0);
|
||||
const resp = await testClient.request(endpoint, 'get');
|
||||
|
||||
assert.isObject(resp);
|
||||
assert.strictEqual(resp.status, 200);
|
||||
assert.strictEqual(resp.data, 'ok');
|
||||
assert.strictEqual(rateLimitCount, 3);
|
||||
});
|
||||
|
||||
it('should retry on 5xx server error', async () => {
|
||||
let serverErrorCount = 0;
|
||||
|
||||
nock(endpoint).persist().get('/').reply(() => {
|
||||
serverErrorCount += 1;
|
||||
return [500, 'Internal Server Error', { 'Retry-After': 1 }];
|
||||
});
|
||||
|
||||
assert.strictEqual(serverErrorCount, 0);
|
||||
const resp = await testClient.request(endpoint, 'get');
|
||||
|
||||
assert.isObject(resp);
|
||||
assert.strictEqual(resp.status, 500);
|
||||
assert.strictEqual(serverErrorCount, 4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Utility method tests
|
||||
*/
|
||||
|
||||
const dns = require('dns').promises;
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const { assert } = require('chai');
|
||||
const util = require('./../src/util');
|
||||
const { readCertificateInfo } = require('./../src/crypto');
|
||||
|
||||
describe('util', () => {
|
||||
const testCertPath1 = path.join(__dirname, 'fixtures', 'certificate.crt');
|
||||
const testCertPath2 = path.join(__dirname, 'fixtures', 'letsencrypt.crt');
|
||||
|
||||
it('retry()', async () => {
|
||||
let attempts = 0;
|
||||
const backoffOpts = {
|
||||
min: 100,
|
||||
max: 500,
|
||||
};
|
||||
|
||||
await assert.isRejected(util.retry(() => {
|
||||
throw new Error('oops');
|
||||
}, backoffOpts));
|
||||
|
||||
const r = await util.retry(() => {
|
||||
attempts += 1;
|
||||
|
||||
if (attempts < 3) {
|
||||
throw new Error('oops');
|
||||
}
|
||||
|
||||
return 'abc';
|
||||
}, backoffOpts);
|
||||
|
||||
assert.strictEqual(r, 'abc');
|
||||
assert.strictEqual(attempts, 3);
|
||||
});
|
||||
|
||||
it('parseLinkHeader()', () => {
|
||||
const r1 = util.parseLinkHeader('<https://example.com/a>;rel="alternate"');
|
||||
assert.isArray(r1);
|
||||
assert.strictEqual(r1.length, 1);
|
||||
assert.strictEqual(r1[0], 'https://example.com/a');
|
||||
|
||||
const r2 = util.parseLinkHeader('<https://example.com/b>;rel="test"');
|
||||
assert.isArray(r2);
|
||||
assert.strictEqual(r2.length, 0);
|
||||
|
||||
const r3 = util.parseLinkHeader('<http://example.com/c>; rel="test"', 'test');
|
||||
assert.isArray(r3);
|
||||
assert.strictEqual(r3.length, 1);
|
||||
assert.strictEqual(r3[0], 'http://example.com/c');
|
||||
|
||||
const r4 = util.parseLinkHeader(`<https://example.com/a>; rel="alternate",
|
||||
<https://example.com/x>; rel="nope",
|
||||
<https://example.com/b>;rel="alternate",
|
||||
<https://example.com/c>; rel="alternate"`);
|
||||
assert.isArray(r4);
|
||||
assert.strictEqual(r4.length, 3);
|
||||
assert.strictEqual(r4[0], 'https://example.com/a');
|
||||
assert.strictEqual(r4[1], 'https://example.com/b');
|
||||
assert.strictEqual(r4[2], 'https://example.com/c');
|
||||
});
|
||||
|
||||
it('parseRetryAfterHeader()', () => {
|
||||
const r1 = util.parseRetryAfterHeader('');
|
||||
assert.strictEqual(r1, 0);
|
||||
|
||||
const r2 = util.parseRetryAfterHeader('abcdef');
|
||||
assert.strictEqual(r2, 0);
|
||||
|
||||
const r3 = util.parseRetryAfterHeader('123');
|
||||
assert.strictEqual(r3, 123);
|
||||
|
||||
const r4 = util.parseRetryAfterHeader('123.456');
|
||||
assert.strictEqual(r4, 123);
|
||||
|
||||
const r5 = util.parseRetryAfterHeader('-555');
|
||||
assert.strictEqual(r5, 0);
|
||||
|
||||
const r6 = util.parseRetryAfterHeader('Wed, 21 Oct 2015 07:28:00 GMT');
|
||||
assert.strictEqual(r6, 0);
|
||||
|
||||
const now = new Date();
|
||||
const future = new Date(now.getTime() + 123000);
|
||||
const r7 = util.parseRetryAfterHeader(future.toUTCString());
|
||||
assert.isTrue(r7 > 100);
|
||||
});
|
||||
|
||||
it('findCertificateChainForIssuer()', async () => {
|
||||
const certs = [
|
||||
(await fs.readFile(testCertPath1)).toString(),
|
||||
(await fs.readFile(testCertPath2)).toString(),
|
||||
];
|
||||
|
||||
const r1 = util.findCertificateChainForIssuer(certs, 'abc123');
|
||||
const r2 = util.findCertificateChainForIssuer(certs, 'example.com');
|
||||
const r3 = util.findCertificateChainForIssuer(certs, 'E6');
|
||||
|
||||
[r1, r2, r3].forEach((r) => {
|
||||
assert.isString(r);
|
||||
assert.isNotEmpty(r);
|
||||
});
|
||||
|
||||
assert.strictEqual(readCertificateInfo(r1).issuer.commonName, 'example.com');
|
||||
assert.strictEqual(readCertificateInfo(r2).issuer.commonName, 'example.com');
|
||||
assert.strictEqual(readCertificateInfo(r3).issuer.commonName, 'E6');
|
||||
});
|
||||
|
||||
it('formatResponseError()', () => {
|
||||
const e1 = util.formatResponseError({ data: { error: 'aaa' } });
|
||||
assert.strictEqual(e1, 'aaa');
|
||||
|
||||
const e2 = util.formatResponseError({ data: { error: { detail: 'bbb' } } });
|
||||
assert.strictEqual(e2, 'bbb');
|
||||
|
||||
const e3 = util.formatResponseError({ data: { detail: 'ccc' } });
|
||||
assert.strictEqual(e3, 'ccc');
|
||||
|
||||
const e4 = util.formatResponseError({ data: { a: 123 } });
|
||||
assert.strictEqual(e4, '{"a":123}');
|
||||
|
||||
const e5 = util.formatResponseError({});
|
||||
assert.isString(e5);
|
||||
assert.isEmpty(e5);
|
||||
});
|
||||
|
||||
it('getAuthoritativeDnsResolver()', async () => {
|
||||
/* valid domain - should not use global default */
|
||||
const r1 = await util.getAuthoritativeDnsResolver('example.com');
|
||||
assert.instanceOf(r1, dns.Resolver);
|
||||
assert.isNotEmpty(r1.getServers());
|
||||
assert.notDeepEqual(r1.getServers(), dns.getServers());
|
||||
|
||||
/* invalid domain - fallback to global default */
|
||||
const r2 = await util.getAuthoritativeDnsResolver('invalid.xtldx');
|
||||
assert.instanceOf(r2, dns.Resolver);
|
||||
assert.deepStrictEqual(r2.getServers(), dns.getServers());
|
||||
});
|
||||
|
||||
/* TODO: Figure out how to test this */
|
||||
it('retrieveTlsAlpnCertificate()');
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDzzCCA1WgAwIBAgISA0ghDoSv5DpT3Pd3lqwjbVDDMAoGCCqGSM49BAMDMDIx
|
||||
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
|
||||
NjAeFw0yNDA2MTAxNzEyMjZaFw0yNDA5MDgxNzEyMjVaMBQxEjAQBgNVBAMTCWxl
|
||||
bmNyLm9yZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEHJ3DjN7pYV3mftHzaP
|
||||
V/WI0RhOJnSI5AIFEPFHDi8UowOINRGIfm9FHGIDqrb4Rmyvr9JrrqBdFGDen8BW
|
||||
6OGjggJnMIICYzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
|
||||
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIdCTnxqmpOELDyzPaEM
|
||||
seB36lUOMB8GA1UdIwQYMBaAFJMnRpgDqVFojpjWxEJI2yO/WJTSMFUGCCsGAQUF
|
||||
BwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U2Lm8ubGVuY3Iub3JnMCIGCCsG
|
||||
AQUFBzAChhZodHRwOi8vZTYuaS5sZW5jci5vcmcvMG8GA1UdEQRoMGaCCWxlbmNy
|
||||
Lm9yZ4IPbGV0c2VuY3J5cHQuY29tgg9sZXRzZW5jcnlwdC5vcmeCDXd3dy5sZW5j
|
||||
ci5vcmeCE3d3dy5sZXRzZW5jcnlwdC5jb22CE3d3dy5sZXRzZW5jcnlwdC5vcmcw
|
||||
EwYDVR0gBAwwCjAIBgZngQwBAgEwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgA/
|
||||
F0tP1yJHWJQdZRyEvg0S7ZA3fx+FauvBvyiF7PhkbgAAAZADWfneAAAEAwBHMEUC
|
||||
IGlp+dPU2hLT2suTMYkYMlt/xbzSnKLZDA/wYSsPACP7AiEAxbAzx6mkzn0cs0hh
|
||||
ti6sLf0pcbmDhxHdlJRjuo6SQZEAdwDf4VbrqgWvtZwPhnGNqMAyTq5W2W6n9aVq
|
||||
AdHBO75SXAAAAZADWfqrAAAEAwBIMEYCIQCrAmDUrlX3oGhri1qCIb65Cuf8h2GR
|
||||
LC1VfXBenX7dCAIhALXwbhCQ1vO1WLv4CqyihMHOwFaICYqN/N6ylaBlVAM4MAoG
|
||||
CCqGSM49BAMDA2gAMGUCMFdgjOXGl+hE2ABDsAeuNq8wi34yTMUHk0KMTOjRAfy9
|
||||
rOCGQqvP0myoYlyzXOH9uQIxAMdkG1ZWBZS1dHavbPf1I/MjYpzX6gy0jVHIXXu5
|
||||
aYWylBi/Uf2RPj0LWFZh8tNa1Q==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -29,6 +29,13 @@ if (process.env.ACME_TLSALPN_PORT) {
|
||||
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Greatly reduce retry duration while testing
|
||||
*/
|
||||
|
||||
axios.defaults.acmeSettings.retryMaxAttempts = 3;
|
||||
axios.defaults.acmeSettings.retryDefaultDelay = 1;
|
||||
|
||||
/**
|
||||
* External account binding
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user