Skip to content

Commit 18537b9

Browse files
committed
404 hosts add update complete, fix certbot renewals
and remove the need for email and agreement on cert requests
1 parent d85e515 commit 18537b9

32 files changed

+449
-446
lines changed

backend/internal/certificate.js

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import utils from "../lib/utils.js";
1313
import { ssl as logger } from "../logger.js";
1414
import certificateModel from "../models/certificate.js";
1515
import tokenModel from "../models/token.js";
16+
import userModel from "../models/user.js";
1617
import internalAuditLog from "./audit-log.js";
1718
import internalHost from "./host.js";
1819
import internalNginx from "./nginx.js";
@@ -81,7 +82,7 @@ const internalCertificate = {
8182
Promise.resolve({
8283
permission_visibility: "all",
8384
}),
84-
token: new tokenModel(),
85+
token: tokenModel(),
8586
},
8687
{ id: certificate.id },
8788
)
@@ -118,10 +119,7 @@ const internalCertificate = {
118119
data.nice_name = data.domain_names.join(", ");
119120
}
120121

121-
const certificate = await certificateModel
122-
.query()
123-
.insertAndFetch(data)
124-
.then(utils.omitRow(omissions()));
122+
const certificate = await certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
125123

126124
if (certificate.provider === "letsencrypt") {
127125
// Request a new Cert from LE. Let the fun begin.
@@ -139,12 +137,19 @@ const internalCertificate = {
139137
// 2. Disable them in nginx temporarily
140138
await internalCertificate.disableInUseHosts(inUseResult);
141139

140+
const user = await userModel.query().where("is_deleted", 0).andWhere("id", data.owner_user_id).first();
141+
if (!user || !user.email) {
142+
throw new error.ValidationError(
143+
"A valid email address must be set on your user account to use Let's Encrypt",
144+
);
145+
}
146+
142147
// With DNS challenge no config is needed, so skip 3 and 5.
143148
if (certificate.meta?.dns_challenge) {
144149
try {
145150
await internalNginx.reload();
146151
// 4. Request cert
147-
await internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate);
152+
await internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate, user.email);
148153
await internalNginx.reload();
149154
// 6. Re-instate previously disabled hosts
150155
await internalCertificate.enableInUseHosts(inUseResult);
@@ -159,9 +164,9 @@ const internalCertificate = {
159164
try {
160165
await internalNginx.generateLetsEncryptRequestConfig(certificate);
161166
await internalNginx.reload();
162-
setTimeout(() => {}, 5000)
167+
setTimeout(() => {}, 5000);
163168
// 4. Request cert
164-
await internalCertificate.requestLetsEncryptSsl(certificate);
169+
await internalCertificate.requestLetsEncryptSsl(certificate, user.email);
165170
// 5. Remove LE config
166171
await internalNginx.deleteLetsEncryptRequestConfig(certificate);
167172
await internalNginx.reload();
@@ -204,13 +209,12 @@ const internalCertificate = {
204209
data.meta = _.assign({}, data.meta || {}, certificate.meta);
205210

206211
// Add to audit log
207-
await internalAuditLog
208-
.add(access, {
209-
action: "created",
210-
object_type: "certificate",
211-
object_id: certificate.id,
212-
meta: data,
213-
});
212+
await internalAuditLog.add(access, {
213+
action: "created",
214+
object_type: "certificate",
215+
object_id: certificate.id,
216+
meta: data,
217+
});
214218

215219
return certificate;
216220
},
@@ -248,13 +252,12 @@ const internalCertificate = {
248252
}
249253

250254
// Add to audit log
251-
await internalAuditLog
252-
.add(access, {
253-
action: "updated",
254-
object_type: "certificate",
255-
object_id: row.id,
256-
meta: _.omit(data, ["expires_on"]), // this prevents json circular reference because expires_on might be raw
257-
});
255+
await internalAuditLog.add(access, {
256+
action: "updated",
257+
object_type: "certificate",
258+
object_id: row.id,
259+
meta: _.omit(data, ["expires_on"]), // this prevents json circular reference because expires_on might be raw
260+
});
258261

259262
return savedRow;
260263
},
@@ -268,7 +271,7 @@ const internalCertificate = {
268271
* @return {Promise}
269272
*/
270273
get: async (access, data) => {
271-
const accessData = await access.can("certificates:get", data.id)
274+
const accessData = await access.can("certificates:get", data.id);
272275
const query = certificateModel
273276
.query()
274277
.where("is_deleted", 0)
@@ -367,12 +370,9 @@ const internalCertificate = {
367370
throw new error.ItemNotFoundError(data.id);
368371
}
369372

370-
await certificateModel
371-
.query()
372-
.where("id", row.id)
373-
.patch({
374-
is_deleted: 1,
375-
});
373+
await certificateModel.query().where("id", row.id).patch({
374+
is_deleted: 1,
375+
});
376376

377377
// Add to audit log
378378
row.meta = internalCertificate.cleanMeta(row.meta);
@@ -435,10 +435,7 @@ const internalCertificate = {
435435
* @returns {Promise}
436436
*/
437437
getCount: async (userId, visibility) => {
438-
const query = certificateModel
439-
.query()
440-
.count("id as count")
441-
.where("is_deleted", 0);
438+
const query = certificateModel.query().count("id as count").where("is_deleted", 0);
442439

443440
if (visibility !== "all") {
444441
query.andWhere("owner_user_id", userId);
@@ -501,12 +498,10 @@ const internalCertificate = {
501498
* @param {Access} access
502499
* @param {Object} data
503500
* @param {Array} data.domain_names
504-
* @param {String} data.meta.letsencrypt_email
505-
* @param {Boolean} data.meta.letsencrypt_agree
506501
* @returns {Promise}
507502
*/
508503
createQuickCertificate: async (access, data) => {
509-
return internalCertificate.create(access, {
504+
return await internalCertificate.create(access, {
510505
provider: "letsencrypt",
511506
domain_names: data.domain_names,
512507
meta: data.meta,
@@ -652,7 +647,7 @@ const internalCertificate = {
652647
const certData = {};
653648

654649
try {
655-
const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"])
650+
const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]);
656651
// Examples:
657652
// subject=CN = *.jc21.com
658653
// subject=CN = something.example.com
@@ -739,9 +734,10 @@ const internalCertificate = {
739734
/**
740735
* Request a certificate using the http challenge
741736
* @param {Object} certificate the certificate row
737+
* @param {String} email the email address to use for registration
742738
* @returns {Promise}
743739
*/
744-
requestLetsEncryptSsl: async (certificate) => {
740+
requestLetsEncryptSsl: async (certificate, email) => {
745741
logger.info(
746742
`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`,
747743
);
@@ -760,7 +756,7 @@ const internalCertificate = {
760756
"--authenticator",
761757
"webroot",
762758
"--email",
763-
certificate.meta.letsencrypt_email,
759+
email,
764760
"--preferred-challenges",
765761
"dns,http",
766762
"--domains",
@@ -779,9 +775,10 @@ const internalCertificate = {
779775

780776
/**
781777
* @param {Object} certificate the certificate row
778+
* @param {String} email the email address to use for registration
782779
* @returns {Promise}
783780
*/
784-
requestLetsEncryptSslWithDnsChallenge: async (certificate) => {
781+
requestLetsEncryptSslWithDnsChallenge: async (certificate, email) => {
785782
await installPlugin(certificate.meta.dns_provider);
786783
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
787784
logger.info(
@@ -807,7 +804,7 @@ const internalCertificate = {
807804
`npm-${certificate.id}`,
808805
"--agree-tos",
809806
"--email",
810-
certificate.meta.letsencrypt_email,
807+
email,
811808
"--domains",
812809
certificate.domain_names.join(","),
813810
"--authenticator",
@@ -847,7 +844,7 @@ const internalCertificate = {
847844
* @returns {Promise}
848845
*/
849846
renew: async (access, data) => {
850-
await access.can("certificates:update", data)
847+
await access.can("certificates:update", data);
851848
const certificate = await internalCertificate.get(access, data);
852849

853850
if (certificate.provider === "letsencrypt") {
@@ -860,11 +857,9 @@ const internalCertificate = {
860857
`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`,
861858
);
862859

863-
const updatedCertificate = await certificateModel
864-
.query()
865-
.patchAndFetchById(certificate.id, {
866-
expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"),
867-
});
860+
const updatedCertificate = await certificateModel.query().patchAndFetchById(certificate.id, {
861+
expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"),
862+
});
868863

869864
// Add to audit log
870865
await internalAuditLog.add(access, {
@@ -1159,7 +1154,9 @@ const internalCertificate = {
11591154
return "no-host";
11601155
}
11611156
// Other errors
1162-
logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`);
1157+
logger.info(
1158+
`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`,
1159+
);
11631160
return `other:${result.responsecode}`;
11641161
}
11651162

@@ -1201,7 +1198,7 @@ const internalCertificate = {
12011198

12021199
getLiveCertPath: (certificateId) => {
12031200
return `/etc/letsencrypt/live/npm-${certificateId}`;
1204-
}
1201+
},
12051202
};
12061203

12071204
export default internalCertificate;

backend/internal/dead-host.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,21 @@ const internalDeadHost = {
5454
thisData.advanced_config = "";
5555
}
5656

57-
const row = await deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
57+
const row = await deadHostModel.query()
58+
.insertAndFetch(thisData)
59+
.then(utils.omitRow(omissions()));
60+
61+
// Add to audit log
62+
await internalAuditLog.add(access, {
63+
action: "created",
64+
object_type: "dead-host",
65+
object_id: row.id,
66+
meta: _.assign({}, data.meta || {}, row.meta),
67+
});
5868

5969
if (createCertificate) {
6070
const cert = await internalCertificate.createQuickCertificate(access, data);
71+
6172
// update host with cert id
6273
await internalDeadHost.update(access, {
6374
id: row.id,
@@ -71,17 +82,13 @@ const internalDeadHost = {
7182
expand: ["certificate", "owner"],
7283
});
7384

85+
// Sanity check
86+
if (createCertificate && !freshRow.certificate_id) {
87+
throw new errs.InternalValidationError("The host was created but the Certificate creation failed.");
88+
}
89+
7490
// Configure nginx
7591
await internalNginx.configure(deadHostModel, "dead_host", freshRow);
76-
data.meta = _.assign({}, data.meta || {}, freshRow.meta);
77-
78-
// Add to audit log
79-
await internalAuditLog.add(access, {
80-
action: "created",
81-
object_type: "dead-host",
82-
object_id: freshRow.id,
83-
meta: data,
84-
});
8592

8693
return freshRow;
8794
},
@@ -94,7 +101,6 @@ const internalDeadHost = {
94101
*/
95102
update: async (access, data) => {
96103
const createCertificate = data.certificate_id === "new";
97-
98104
if (createCertificate) {
99105
delete data.certificate_id;
100106
}
@@ -147,6 +153,13 @@ const internalDeadHost = {
147153

148154
thisData = internalHost.cleanSslHstsData(thisData, row);
149155

156+
157+
// do the row update
158+
await deadHostModel
159+
.query()
160+
.where({id: data.id})
161+
.patch(data);
162+
150163
// Add to audit log
151164
await internalAuditLog.add(access, {
152165
action: "updated",

0 commit comments

Comments
 (0)