Skip to content

Commit 4d5adef

Browse files
author
Jamie Curnow
committed
Added ability to force renew a LE cert, and also fix revoking certs
1 parent feaa0e5 commit 4d5adef

File tree

7 files changed

+141
-59
lines changed

7 files changed

+141
-59
lines changed

src/backend/internal/certificate.js

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
const fs = require('fs');
42
const _ = require('lodash');
53
const logger = require('../logger').ssl;
@@ -9,7 +7,7 @@ const internalAuditLog = require('./audit-log');
97
const tempWrite = require('temp-write');
108
const utils = require('../lib/utils');
119
const moment = require('moment');
12-
const debug_mode = process.env.NODE_ENV !== 'production';
10+
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG ;
1311
const internalNginx = require('./nginx');
1412
const internalHost = require('./host');
1513
const certbot_command = '/usr/bin/certbot';
@@ -21,7 +19,7 @@ function omissions () {
2119
const internalCertificate = {
2220

2321
allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'],
24-
interval_timeout: 1000 * 60 * 60 * 12, // 12 hours
22+
interval_timeout: 1000 * 60 * 60, // 1 hour
2523
interval: null,
2624
interval_processing: false,
2725

@@ -205,7 +203,7 @@ const internalCertificate = {
205203
/**
206204
* @param {Access} access
207205
* @param {Object} data
208-
* @param {Integer} data.id
206+
* @param {Number} data.id
209207
* @param {String} [data.email]
210208
* @param {String} [data.name]
211209
* @return {Promise}
@@ -251,7 +249,7 @@ const internalCertificate = {
251249
/**
252250
* @param {Access} access
253251
* @param {Object} data
254-
* @param {Integer} data.id
252+
* @param {Number} data.id
255253
* @param {Array} [data.expand]
256254
* @param {Array} [data.omit]
257255
* @return {Promise}
@@ -297,7 +295,7 @@ const internalCertificate = {
297295
/**
298296
* @param {Access} access
299297
* @param {Object} data
300-
* @param {Integer} data.id
298+
* @param {Number} data.id
301299
* @param {String} [data.reason]
302300
* @returns {Promise}
303301
*/
@@ -381,7 +379,7 @@ const internalCertificate = {
381379
/**
382380
* Report use
383381
*
384-
* @param {Integer} user_id
382+
* @param {Number} user_id
385383
* @param {String} visibility
386384
* @returns {Promise}
387385
*/
@@ -522,7 +520,7 @@ const internalCertificate = {
522520
/**
523521
* @param {Access} access
524522
* @param {Object} data
525-
* @param {Integer} data.id
523+
* @param {Number} data.id
526524
* @param {Object} data.files
527525
* @returns {Promise}
528526
*/
@@ -734,6 +732,36 @@ const internalCertificate = {
734732
});
735733
},
736734

735+
/**
736+
* @param {Access} access
737+
* @param {Object} data
738+
* @param {Number} data.id
739+
* @returns {Promise}
740+
*/
741+
renew: (access, data) => {
742+
return access.can('certificates:update', data)
743+
.then(() => {
744+
return internalCertificate.get(access, data);
745+
})
746+
.then((certificate) => {
747+
if (certificate.provider === 'letsencrypt') {
748+
return internalCertificate.renewLetsEncryptSsl(certificate)
749+
.then(() => {
750+
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
751+
})
752+
.then(cert_info => {
753+
return certificateModel
754+
.query()
755+
.patchAndFetchById(certificate.id, {
756+
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
757+
});
758+
});
759+
} else {
760+
throw new error.ValidationError('Only Let\'sEncrypt certificates can be renewed');
761+
}
762+
})
763+
},
764+
737765
/**
738766
* @param {Object} certificate the certificate row
739767
* @returns {Promise}
@@ -762,17 +790,29 @@ const internalCertificate = {
762790
revokeLetsEncryptSsl: (certificate, throw_errors) => {
763791
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
764792

765-
let cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (debug_mode ? '--staging' : '');
793+
let revoke_cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (debug_mode ? '--staging' : '');
794+
let delete_cmd = certbot_command + ' delete --cert-name "npm-' + certificate.id + '" ' + (debug_mode ? '--staging' : '');
766795

767796
if (debug_mode) {
768-
logger.info('Command:', cmd);
797+
logger.info('Command:', revoke_cmd);
769798
}
770799

771-
return utils.exec(cmd)
772-
.then(result => {
800+
return utils.exec(revoke_cmd)
801+
.then((result) => {
773802
logger.info(result);
774803
return result;
775804
})
805+
.then(() => {
806+
if (debug_mode) {
807+
logger.info('Command:', delete_cmd);
808+
}
809+
810+
return utils.exec(delete_cmd)
811+
.then((result) => {
812+
logger.info(result);
813+
return result;
814+
})
815+
})
776816
.catch(err => {
777817
if (debug_mode) {
778818
logger.error(err.message);
@@ -796,7 +836,7 @@ const internalCertificate = {
796836

797837
/**
798838
* @param {Object} in_use_result
799-
* @param {Integer} in_use_result.total_count
839+
* @param {Number} in_use_result.total_count
800840
* @param {Array} in_use_result.proxy_hosts
801841
* @param {Array} in_use_result.redirection_hosts
802842
* @param {Array} in_use_result.dead_hosts
@@ -826,7 +866,7 @@ const internalCertificate = {
826866

827867
/**
828868
* @param {Object} in_use_result
829-
* @param {Integer} in_use_result.total_count
869+
* @param {Number} in_use_result.total_count
830870
* @param {Array} in_use_result.proxy_hosts
831871
* @param {Array} in_use_result.redirection_hosts
832872
* @param {Array} in_use_result.dead_hosts

src/backend/routes/api/nginx/certificates.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
const express = require('express');
42
const validator = require('../../../lib/validator');
53
const jwtdecode = require('../../../lib/express/jwt-decode');
@@ -94,13 +92,13 @@ router
9492
certificate_id: {
9593
$ref: 'definitions#/definitions/id'
9694
},
97-
expand: {
95+
expand: {
9896
$ref: 'definitions#/definitions/expand'
9997
}
10098
}
10199
}, {
102100
certificate_id: req.params.certificate_id,
103-
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
101+
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
104102
})
105103
.then(data => {
106104
return internalCertificate.get(res.locals.access, {
@@ -181,6 +179,34 @@ router
181179
}
182180
});
183181

182+
/**
183+
* Renew LE Certs
184+
*
185+
* /api/nginx/certificates/123/renew
186+
*/
187+
router
188+
.route('/:certificate_id/renew')
189+
.options((req, res) => {
190+
res.sendStatus(204);
191+
})
192+
.all(jwtdecode())
193+
194+
/**
195+
* POST /api/nginx/certificates/123/renew
196+
*
197+
* Renew certificate
198+
*/
199+
.post((req, res, next) => {
200+
internalCertificate.renew(res.locals.access, {
201+
id: parseInt(req.params.certificate_id, 10)
202+
})
203+
.then(result => {
204+
res.status(200)
205+
.send(result);
206+
})
207+
.catch(next);
208+
});
209+
184210
/**
185211
* Validate Certs before saving
186212
*

src/frontend/js/app/api.js

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
const $ = require('jquery');
42
const _ = require('underscore');
53
const Tokens = require('./tokens');
@@ -11,8 +9,8 @@ const Tokens = require('./tokens');
119
* @constructor
1210
*/
1311
const ApiError = function (message, debug, code) {
14-
let temp = Error.call(this, message);
15-
temp.name = this.name = 'ApiError';
12+
let temp = Error.call(this, message);
13+
temp.name = this.name = 'ApiError';
1614
this.stack = temp.stack;
1715
this.message = temp.message;
1816
this.debug = debug;
@@ -35,7 +33,7 @@ ApiError.prototype = Object.create(Error.prototype, {
3533
* @param {Object} [options]
3634
* @returns {Promise}
3735
*/
38-
function fetch (verb, path, data, options) {
36+
function fetch(verb, path, data, options) {
3937
options = options || {};
4038

4139
return new Promise(function (resolve, reject) {
@@ -55,7 +53,7 @@ function fetch (verb, path, data, options) {
5553
contentType: options.contentType || 'application/json; charset=UTF-8',
5654
processData: options.processData || true,
5755
crossDomain: true,
58-
timeout: options.timeout ? options.timeout : 15000,
56+
timeout: options.timeout ? options.timeout : 30000,
5957
xhrFields: {
6058
withCredentials: true
6159
},
@@ -99,7 +97,7 @@ function fetch (verb, path, data, options) {
9997
* @param {Array} expand
10098
* @returns {String}
10199
*/
102-
function makeExpansionString (expand) {
100+
function makeExpansionString(expand) {
103101
let items = [];
104102
_.forEach(expand, function (exp) {
105103
items.push(encodeURIComponent(exp));
@@ -114,7 +112,7 @@ function makeExpansionString (expand) {
114112
* @param {String} [query]
115113
* @returns {Promise}
116114
*/
117-
function getAllObjects (path, expand, query) {
115+
function getAllObjects(path, expand, query) {
118116
let params = [];
119117

120118
if (typeof expand === 'object' && expand !== null && expand.length) {
@@ -128,20 +126,7 @@ function getAllObjects (path, expand, query) {
128126
return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
129127
}
130128

131-
/**
132-
* @param {String} path
133-
* @param {FormData} form_data
134-
* @returns {Promise}
135-
*/
136-
function upload (path, form_data) {
137-
console.log('UPLOAD:', path, form_data);
138-
return fetch('post', path, form_data, {
139-
contentType: 'multipart/form-data',
140-
processData: false
141-
});
142-
}
143-
144-
function FileUpload (path, fd) {
129+
function FileUpload(path, fd) {
145130
return new Promise((resolve, reject) => {
146131
let xhr = new XMLHttpRequest();
147132
let token = Tokens.getTopToken();
@@ -214,7 +199,7 @@ module.exports = {
214199
Users: {
215200

216201
/**
217-
* @param {Integer|String} user_id
202+
* @param {Number|String} user_id
218203
* @param {Array} [expand]
219204
* @returns {Promise}
220205
*/
@@ -639,6 +624,14 @@ module.exports = {
639624
*/
640625
validate: function (form_data) {
641626
return FileUpload('nginx/certificates/validate', form_data);
627+
},
628+
629+
/**
630+
* @param {Number} id
631+
* @returns {Promise}
632+
*/
633+
renew: function (id) {
634+
return fetch('post', 'nginx/certificates/' + id + '/renew');
642635
}
643636
}
644637
},

src/frontend/js/app/nginx/certificates/list/item.ejs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,23 @@
55
</td>
66
<td>
77
<div>
8-
<% if (provider === 'letsencrypt') { %>
9-
<% domain_names.map(function(host) {
10-
%>
11-
<span class="tag"><%- host %></span>
12-
<%
8+
<%
9+
if (provider === 'letsencrypt') {
10+
domain_names.map(function(host) {
11+
if (host.indexOf('*') === -1) {
12+
%>
13+
<span class="tag host-link hover-pink" rel="https://<%- host %>"><%- host %></span>
14+
<%
15+
} else {
16+
%>
17+
<span class="tag"><%- host %></span>
18+
<%
19+
}
1320
});
14-
%>
15-
<% } else { %>
16-
<%- nice_name %>
17-
<% } %>
21+
} else {
22+
%><%- nice_name %><%
23+
}
24+
%>
1825
</div>
1926
<div class="small text-muted">
2027
<%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %>
@@ -31,6 +38,10 @@
3138
<div class="item-action dropdown">
3239
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
3340
<div class="dropdown-menu dropdown-menu-right">
41+
<% if (provider === 'letsencrypt') { %>
42+
<a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></a>
43+
<div class="dropdown-divider"></div>
44+
<% } %>
3445
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
3546
</div>
3647
</div>

src/frontend/js/app/nginx/certificates/list/item.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
const Mn = require('backbone.marionette');
42
const moment = require('moment');
53
const App = require('../../../main');
@@ -10,13 +8,26 @@ module.exports = Mn.View.extend({
108
tagName: 'tr',
119

1210
ui: {
13-
delete: 'a.delete'
11+
host_link: '.host-link',
12+
renew: 'a.renew',
13+
delete: 'a.delete'
1414
},
1515

1616
events: {
17+
'click @ui.renew': function (e) {
18+
e.preventDefault();
19+
App.Controller.showNginxCertificateRenew(this.model);
20+
},
21+
1722
'click @ui.delete': function (e) {
1823
e.preventDefault();
1924
App.Controller.showNginxCertificateDeleteConfirm(this.model);
25+
},
26+
27+
'click @ui.host_link': function (e) {
28+
e.preventDefault();
29+
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
30+
win.focus();
2031
}
2132
},
2233

src/frontend/js/app/nginx/certificates/list/main.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
const Mn = require('backbone.marionette');
42
const App = require('../../../main');
53
const ItemView = require('./item');

0 commit comments

Comments
 (0)