Skip to content

Commit f5c8ee1

Browse files
committed
fix(ssl-migration): make the checks for migrating ssl more robust
closes #552 - skip ssl migration in a couple more cases - fix tests
1 parent 462a58f commit f5c8ee1

File tree

4 files changed

+138
-13
lines changed

4 files changed

+138
-13
lines changed

extensions/nginx/migrations.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,26 @@ function migrateSSL(ctx, migrateTask) {
1414
const confFile = path.join(ctx.instance.dir, 'system', 'files', `${parsedUrl.hostname}-ssl.conf`);
1515
const rootPath = path.resolve(ctx.instance.dir, 'system', 'nginx-root');
1616

17+
// Case to skip 1: SSL config for nginx does not exist
1718
if (!fs.existsSync(confFile)) {
1819
return migrateTask.skip('SSL config has not been set up for this domain');
1920
}
2021

2122
const originalAcmePath = path.join(os.homedir(), '.acme.sh');
23+
const originalCertFolder = path.join(originalAcmePath, parsedUrl.hostname);
24+
25+
// Case to skip 2: SSL cert doesn't exist in the original location for this domain
26+
if (!fs.existsSync(originalCertFolder)) {
27+
return migrateTask.skip('SSL cert does not exist for this domain');
28+
}
29+
30+
const confFileContents = fs.readFileSync(confFile, {encoding: 'utf8'});
31+
const certCheck = new RegExp(`ssl_certificate ${originalCertFolder}/fullchain.cer;`)
32+
33+
// Case to skip 3: SSL conf does not contain a cert config using the old LE cert
34+
if (!certCheck.test(confFileContents)) {
35+
return migrateTask.skip('LetsEncrypt SSL cert is not being used for this domain');
36+
}
2237

2338
// 1. parse ~/.acme.sh/account.conf to get the email
2439
const accountConf = fs.readFileSync(path.join(originalAcmePath, 'account.conf'), {encoding: 'utf8'});
@@ -45,8 +60,10 @@ function migrateSSL(ctx, migrateTask) {
4560
return replace({
4661
files: confFile,
4762
from: [
48-
/ssl_certificate .*/,
49-
/ssl_certificate_key .*/
63+
// Ensure here that we ONLY replace instances of the LetsEncrypt cert in the file,
64+
// that way we don't overwrite the cert config of other certs.
65+
certCheck,
66+
new RegExp(`ssl_certificate_key ${originalCertFolder}/${parsedUrl.hostname}.key;`)
5067
],
5168
to: [
5269
`ssl_certificate ${path.join(acmeFolder, 'fullchain.cer')};`,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
server {
2+
listen 443 ssl http2;
3+
listen [::]:443 ssl http2;
4+
5+
server_name ghost.org;
6+
root /var/www/ghost/system/nginx-root;
7+
8+
ssl_certificate /home/ghost/.acme.sh/ghost.org/fullchain.cer;
9+
ssl_certificate_key /home/ghost/.acme.sh/ghost.org/ghost.org.key;
10+
include /var/www/ghost/system/files/ssl-params.conf;
11+
12+
location / {
13+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
14+
proxy_set_header X-Forwarded-Proto $scheme;
15+
proxy_set_header X-Real-IP $remote_addr;
16+
proxy_set_header Host $http_host;
17+
proxy_pass http://127.0.0.1:2368;
18+
}
19+
20+
location ~ /.well-known {
21+
allow all;
22+
}
23+
24+
client_max_body_size 50m;
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
server {
2+
listen 443 ssl http2;
3+
listen [::]:443 ssl http2;
4+
5+
server_name ghost.org;
6+
root /var/www/ghost/system/nginx-root;
7+
8+
ssl_certificate /etc/ssl/comodo/ghost.org.cer;
9+
ssl_certificate_key /etc/ssl/comodo/ghost.org.key;
10+
include /var/www/ghost/system/files/ssl-params.conf;
11+
12+
location / {
13+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
14+
proxy_set_header X-Forwarded-Proto $scheme;
15+
proxy_set_header X-Real-IP $remote_addr;
16+
proxy_set_header Host $http_host;
17+
proxy_pass http://127.0.0.1:2368;
18+
}
19+
20+
location ~ /.well-known {
21+
allow all;
22+
}
23+
24+
client_max_body_size 50m;
25+
}

extensions/nginx/test/migrations-spec.js

+69-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
2-
2+
const fs = require('fs');
3+
const path = require('path');
34
const expect = require('chai').expect;
45
const sinon = require('sinon');
56
const proxyquire = require('proxyquire').noCallThru();
@@ -17,6 +18,9 @@ const context = {
1718
}
1819
};
1920

21+
const sslWithoutLe = fs.readFileSync(path.join(__dirname, './fixtures/ssl-without-le.txt'), {encoding: 'utf8'});
22+
const oldSslWithLe = fs.readFileSync(path.join(__dirname, './fixtures/old-ssl-with-le.txt'), {encoding: 'utf8'});
23+
2024
describe('Unit: Extensions > Nginx > Migrations', function () {
2125
describe('migrateSSL', function () {
2226
it('skips if ssl is not set up', function () {
@@ -34,12 +38,61 @@ describe('Unit: Extensions > Nginx > Migrations', function () {
3438
expect(skipStub.calledOnce).to.be.true;
3539
});
3640

41+
it('skips if cert has not been generated using the old method', function () {
42+
const skip = sinon.stub();
43+
const existsSync = sinon.stub();
44+
45+
existsSync.withArgs('/var/www/ghost/system/files/ghost.org-ssl.conf').returns(true);
46+
existsSync.withArgs('/home/ghost/.acme.sh/ghost.org').returns(false);
47+
48+
const migrate = proxyquire(modulePath, {
49+
'fs-extra': {existsSync: existsSync},
50+
os: {homedir: () => '/home/ghost'}
51+
});
52+
53+
migrate.migrateSSL(context, {skip: skip});
54+
55+
expect(existsSync.calledTwice).to.be.true;
56+
expect(existsSync.calledWithExactly('/var/www/ghost/system/files/ghost.org-ssl.conf')).to.be.true;
57+
expect(existsSync.calledWithExactly('/home/ghost/.acme.sh/ghost.org')).to.be.true;
58+
expect(skip.calledOnce).to.be.true;
59+
});
60+
61+
it('skips if ssl conf isn\'t using an LE cert', function () {
62+
const skip = sinon.stub();
63+
const existsSync = sinon.stub();
64+
const readFileSync = sinon.stub();
65+
66+
const confFile = '/var/www/ghost/system/files/ghost.org-ssl.conf';
67+
68+
existsSync.withArgs(confFile).returns(true);
69+
existsSync.withArgs('/home/ghost/.acme.sh/ghost.org').returns(true);
70+
readFileSync.withArgs(confFile).returns(sslWithoutLe);
71+
72+
const migrate = proxyquire(modulePath, {
73+
'fs-extra': {existsSync: existsSync, readFileSync: readFileSync},
74+
os: {homedir: () => '/home/ghost'}
75+
});
76+
77+
migrate.migrateSSL(context, {skip: skip});
78+
79+
expect(existsSync.calledTwice).to.be.true;
80+
expect(existsSync.calledWithExactly('/var/www/ghost/system/files/ghost.org-ssl.conf')).to.be.true;
81+
expect(existsSync.calledWithExactly('/home/ghost/.acme.sh/ghost.org')).to.be.true;
82+
expect(readFileSync.calledOnce).to.be.true;
83+
expect(readFileSync.calledWithExactly(confFile, {encoding: 'utf8'})).to.be.true;
84+
expect(skip.calledOnce).to.be.true;
85+
});
86+
3787
it('throws an error if it can\'t parse the letsencrypt account email', function () {
38-
const existsStub = sinon.stub().returns(true);
39-
const rfsStub = sinon.stub().returns('');
88+
const existsSync = sinon.stub().returns(true);
89+
const readFileSync = sinon.stub();
90+
91+
readFileSync.onFirstCall().returns(oldSslWithLe);
92+
readFileSync.onSecondCall().returns('');
4093

4194
const migrate = proxyquire(modulePath, {
42-
'fs-extra': {existsSync: existsStub, readFileSync: rfsStub},
95+
'fs-extra': {existsSync: existsSync, readFileSync: readFileSync},
4396
os: {homedir: () => '/home/ghost'}
4497
});
4598

@@ -50,13 +103,18 @@ describe('Unit: Extensions > Nginx > Migrations', function () {
50103
expect(e).to.be.an.instanceof(cli.errors.SystemError);
51104
expect(e.message).to.equal('Unable to parse letsencrypt account email');
52105

53-
expect(rfsStub.calledWithExactly('/home/ghost/.acme.sh/account.conf'));
106+
expect(readFileSync.calledTwice).to.be.true;
107+
expect(readFileSync.calledWithExactly('/home/ghost/.acme.sh/account.conf', {encoding: 'utf8'})).to.be.true;
54108
}
55109
});
56110

57111
it('runs tasks correctly', function () {
58-
const existsStub = sinon.stub().returns(true);
59-
const rfsStub = sinon.stub().returns('ACCOUNT_EMAIL=\'[email protected]\'\n');
112+
const existsSync = sinon.stub().returns(true);
113+
const readFileSync = sinon.stub();
114+
115+
readFileSync.onFirstCall().returns(oldSslWithLe);
116+
readFileSync.onSecondCall().returns('ACCOUNT_EMAIL=\'[email protected]\'\n');
117+
60118
const restartStub = sinon.stub().resolves();
61119
const replaceStub = sinon.stub().resolves();
62120

@@ -70,7 +128,7 @@ describe('Unit: Extensions > Nginx > Migrations', function () {
70128
};
71129

72130
const migrate = proxyquire(modulePath, {
73-
'fs-extra': {existsSync: existsStub, readFileSync: rfsStub},
131+
'fs-extra': {existsSync: existsSync, readFileSync: readFileSync},
74132
'replace-in-file': replaceStub,
75133
'./acme': acme,
76134
os: {homedir: () => '/home/ghost'}
@@ -80,8 +138,8 @@ describe('Unit: Extensions > Nginx > Migrations', function () {
80138

81139
fn(context);
82140

83-
expect(existsStub.calledOnce).to.be.true;
84-
expect(rfsStub.calledOnce).to.be.true;
141+
expect(existsSync.calledTwice).to.be.true;
142+
expect(readFileSync.calledTwice).to.be.true;
85143
expect(ui.listr.calledOnce).to.be.true;
86144

87145
const tasks = ui.listr.getCall(0).args[0];
@@ -112,7 +170,7 @@ describe('Unit: Extensions > Nginx > Migrations', function () {
112170
return tasks[4].task();
113171
}).then(() => {
114172
expect(acme.remove.calledOnce).to.be.true;
115-
expect(acme.remove.calledWithExactly('ghost.org', ui, '/home/ghost/.acme.sh'));
173+
expect(acme.remove.calledWithExactly('ghost.org', ui, '/home/ghost/.acme.sh')).to.be.true;
116174
});
117175
});
118176
});

0 commit comments

Comments
 (0)