mirror of
https://github.com/xiaoxinpro/nginx-proxy-manager-zh.git
synced 2025-01-22 21:08:13 -05:00
Custom SSL Validation endpoint
This commit is contained in:
parent
1b68869e6b
commit
c8592503e3
@ -43,6 +43,6 @@ function appStart () {
|
|||||||
try {
|
try {
|
||||||
appStart();
|
appStart();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err.message);
|
logger.error(err.message, err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const certificateModel = require('../models/certificate');
|
const certificateModel = require('../models/certificate');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
|
const tempWrite = require('temp-write');
|
||||||
|
const utils = require('../lib/utils');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -200,7 +203,8 @@ const internalCertificate = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that the certs provided are good
|
* Validates that the certs provided are good.
|
||||||
|
* This is probably a horrible way to do this.
|
||||||
*
|
*
|
||||||
* @param {Access} access
|
* @param {Access} access
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
@ -208,7 +212,8 @@ const internalCertificate = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
validate: (access, data) => {
|
validate: (access, data) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(resolve => {
|
||||||
|
// Put file contents into an object
|
||||||
let files = {};
|
let files = {};
|
||||||
_.map(data.files, (file, name) => {
|
_.map(data.files, (file, name) => {
|
||||||
if (internalHost.allowed_ssl_files.indexOf(name) !== -1) {
|
if (internalHost.allowed_ssl_files.indexOf(name) !== -1) {
|
||||||
@ -219,12 +224,62 @@ const internalCertificate = {
|
|||||||
resolve(files);
|
resolve(files);
|
||||||
})
|
})
|
||||||
.then(files => {
|
.then(files => {
|
||||||
|
// For each file, create a temp file and write the contents to it
|
||||||
|
// Then test it depending on the file type
|
||||||
|
let promises = [];
|
||||||
|
_.map(files, (content, type) => {
|
||||||
|
promises.push(tempWrite(content, '/tmp')
|
||||||
|
.then(filepath => {
|
||||||
|
if (type === 'certificate_key') {
|
||||||
|
return utils.exec('openssl rsa -in ' + filepath + ' -check')
|
||||||
|
.then(result => {
|
||||||
|
return {tmp: filepath, result: result.split("\n").shift()};
|
||||||
|
}).catch(err => {
|
||||||
|
return {tmp: filepath, result: false, err: new error.ValidationError('Certificate Key is not valid')};
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: validate using openssl
|
} else if (type === 'certificate') {
|
||||||
// files.certificate
|
return utils.exec('openssl x509 -in ' + filepath + ' -text -noout')
|
||||||
// files.certificate_key
|
.then(result => {
|
||||||
|
return {tmp: filepath, result: result};
|
||||||
|
}).catch(err => {
|
||||||
|
return {tmp: filepath, result: false, err: new error.ValidationError('Certificate is not valid')};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return {tmp: filepath, result: false};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(file_result => {
|
||||||
|
// Remove temp files
|
||||||
|
fs.unlinkSync(file_result.tmp);
|
||||||
|
delete file_result.tmp;
|
||||||
|
|
||||||
return true;
|
return {[type]: file_result};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// With the results, delete the temp files for security mainly.
|
||||||
|
// If there was an error with any of them, wait until we've done the deleting
|
||||||
|
// before throwing it.
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(files => {
|
||||||
|
let data = {};
|
||||||
|
let err = null;
|
||||||
|
|
||||||
|
_.each(files, file => {
|
||||||
|
data = _.assign({}, data, file);
|
||||||
|
if (typeof file.err !== 'undefined' && file.err) {
|
||||||
|
err = file.err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ const deadHostModel = require('../models/dead_host');
|
|||||||
|
|
||||||
const internalHost = {
|
const internalHost = {
|
||||||
|
|
||||||
allowed_ssl_files: ['certificate', 'certificate_key'],
|
allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal use only, checks to see if the domain is already taken by any other record
|
* Internal use only, checks to see if the domain is already taken by any other record
|
||||||
|
@ -163,7 +163,7 @@ router
|
|||||||
* POST /api/nginx/certificates/123/upload
|
* POST /api/nginx/certificates/123/upload
|
||||||
*
|
*
|
||||||
* Upload certificates
|
* Upload certificates
|
||||||
*/validate
|
*/
|
||||||
.post((req, res, next) => {
|
.post((req, res, next) => {
|
||||||
if (!req.files) {
|
if (!req.files) {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
|
@ -535,6 +535,14 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
upload: function (id, form_data) {
|
upload: function (id, form_data) {
|
||||||
return FileUpload('nginx/certificates/' + id + '/upload', form_data);
|
return FileUpload('nginx/certificates/' + id + '/upload', form_data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormData} form_data
|
||||||
|
* @params {Promise}
|
||||||
|
*/
|
||||||
|
validate: function (form_data) {
|
||||||
|
return FileUpload('nginx/certificates/validate', form_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -39,22 +39,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-12 other-ssl">
|
<div class="col-sm-12 col-md-12 other-ssl">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-label"><%- i18n('all-hosts', 'other-certificate') %></div>
|
<div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div>
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" class="custom-file-input" name="meta[other_ssl_certificate]" id="other_ssl_certificate" required>
|
<input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required>
|
||||||
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-12 other-ssl">
|
<div class="col-sm-12 col-md-12 other-ssl">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-label"><%- i18n('all-hosts', 'other-certificate-key') %></div>
|
<div class="form-label"><%- i18n('certificates', 'other-certificate') %><span class="form-required">*</span></div>
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" class="custom-file-input" name="meta[other_ssl_certificate_key]" id="other_ssl_certificate_key" required>
|
<input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_certificate">
|
||||||
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12 other-ssl">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-label"><%- i18n('certificates', 'other-intermediate-certificate') %></div>
|
||||||
|
<div class="custom-file">
|
||||||
|
<input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate">
|
||||||
|
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -15,13 +15,14 @@ module.exports = Mn.View.extend({
|
|||||||
max_file_size: 5120,
|
max_file_size: 5120,
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
form: 'form',
|
form: 'form',
|
||||||
domain_names: 'input[name="domain_names"]',
|
domain_names: 'input[name="domain_names"]',
|
||||||
buttons: '.modal-footer button',
|
buttons: '.modal-footer button',
|
||||||
cancel: 'button.cancel',
|
cancel: 'button.cancel',
|
||||||
save: 'button.save',
|
save: 'button.save',
|
||||||
other_ssl_certificate: '#other_ssl_certificate',
|
other_certificate: '#other_certificate',
|
||||||
other_ssl_certificate_key: '#other_ssl_certificate_key'
|
other_certificate_key: '#other_certificate_key',
|
||||||
|
other_intermediate_certificate: '#other_intermediate_certificate'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@ -33,8 +34,8 @@ module.exports = Mn.View.extend({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = this;
|
let view = this;
|
||||||
let data = this.ui.form.serializeJSON();
|
let data = this.ui.form.serializeJSON();
|
||||||
data.provider = this.model.get('provider');
|
data.provider = this.model.get('provider');
|
||||||
|
|
||||||
// Manipulate
|
// Manipulate
|
||||||
@ -46,55 +47,66 @@ module.exports = Mn.View.extend({
|
|||||||
data.domain_names = data.domain_names.split(',');
|
data.domain_names = data.domain_names.split(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
let method = App.Api.Nginx.Certificates.create;
|
|
||||||
let is_new = true;
|
|
||||||
let ssl_files = [];
|
let ssl_files = [];
|
||||||
|
|
||||||
if (this.model.get('id')) {
|
|
||||||
// edit
|
|
||||||
is_new = false;
|
|
||||||
method = App.Api.Nginx.Certificates.update;
|
|
||||||
data.id = this.model.get('id');
|
|
||||||
}
|
|
||||||
|
|
||||||
// check files are attached
|
// check files are attached
|
||||||
if (this.model.get('provider') === 'other' && !this.model.hasSslFiles()) {
|
if (this.model.get('provider') === 'other' && !this.model.hasSslFiles()) {
|
||||||
if (!this.ui.other_ssl_certificate[0].files.length || !this.ui.other_ssl_certificate[0].files[0].size) {
|
if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) {
|
||||||
alert('certificate file is not attached');
|
alert('Certificate file is not attached');
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (this.ui.other_ssl_certificate[0].files[0].size > this.max_file_size) {
|
if (this.ui.other_certificate[0].files[0].size > this.max_file_size) {
|
||||||
alert('certificate file is too large (> 5kb)');
|
alert('Certificate file is too large (> 5kb)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ssl_files.push({name: 'certificate', file: this.ui.other_ssl_certificate[0].files[0]});
|
ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.ui.other_ssl_certificate_key[0].files.length || !this.ui.other_ssl_certificate_key[0].files[0].size) {
|
if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) {
|
||||||
alert('certificate key file is not attached');
|
alert('Certificate key file is not attached');
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (this.ui.other_ssl_certificate_key[0].files[0].size > this.max_file_size) {
|
if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) {
|
||||||
alert('certificate key file is too large (> 5kb)');
|
alert('Certificate key file is too large (> 5kb)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ssl_files.push({name: 'certificate_key', file: this.ui.other_ssl_certificate_key[0].files[0]});
|
ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) {
|
||||||
|
if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) {
|
||||||
|
alert('Intermediate Certificate file is too large (> 5kb)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||||
method(data)
|
|
||||||
|
// compile file data
|
||||||
|
let form_data = new FormData();
|
||||||
|
if (view.model.get('provider') && ssl_files.length) {
|
||||||
|
ssl_files.map(function (file) {
|
||||||
|
form_data.append(file.name, file.file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
new Promise(resolve => {
|
||||||
|
if (view.model.get('provider') === 'other') {
|
||||||
|
resolve(App.Api.Nginx.Certificates.validate(form_data));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return App.Api.Nginx.Certificates.create(data);
|
||||||
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
view.model.set(result);
|
view.model.set(result);
|
||||||
|
|
||||||
// Now upload the certs if we need to
|
// Now upload the certs if we need to
|
||||||
if (ssl_files.length) {
|
if (view.model.get('provider') === 'other') {
|
||||||
let form_data = new FormData();
|
|
||||||
|
|
||||||
ssl_files.map(function (file) {
|
|
||||||
form_data.append(file.name, file.file);
|
|
||||||
});
|
|
||||||
|
|
||||||
return App.Api.Nginx.Certificates.upload(view.model.get('id'), form_data)
|
return App.Api.Nginx.Certificates.upload(view.model.get('id'), form_data)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
view.model.set('meta', _.assign({}, view.model.get('meta'), result));
|
view.model.set('meta', _.assign({}, view.model.get('meta'), result));
|
||||||
@ -103,9 +115,7 @@ module.exports = Mn.View.extend({
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
App.UI.closeModal(function () {
|
App.UI.closeModal(function () {
|
||||||
if (is_new) {
|
App.Controller.showNginxCertificates();
|
||||||
App.Controller.showNginxCertificates();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -66,8 +66,6 @@
|
|||||||
"force-ssl": "Force SSL",
|
"force-ssl": "Force SSL",
|
||||||
"domain-names": "Domain Names",
|
"domain-names": "Domain Names",
|
||||||
"cert-provider": "Certificate Provider",
|
"cert-provider": "Certificate Provider",
|
||||||
"other-certificate": "Certificate",
|
|
||||||
"other-certificate-key": "Certificate Key",
|
|
||||||
"block-exploits": "Block Common Exploits",
|
"block-exploits": "Block Common Exploits",
|
||||||
"caching-enabled": "Cache Assets"
|
"caching-enabled": "Cache Assets"
|
||||||
},
|
},
|
||||||
@ -141,7 +139,10 @@
|
|||||||
"delete": "Delete SSL Certificate",
|
"delete": "Delete SSL Certificate",
|
||||||
"delete-confirm": "Are you sure you want to delete this SSL Certificate? Any hosts using it will need to be updated later.",
|
"delete-confirm": "Are you sure you want to delete this SSL Certificate? Any hosts using it will need to be updated later.",
|
||||||
"help-title": "SSL Certificates",
|
"help-title": "SSL Certificates",
|
||||||
"help-content": "TODO"
|
"help-content": "TODO",
|
||||||
|
"other-certificate": "Certificate",
|
||||||
|
"other-certificate-key": "Certificate Key",
|
||||||
|
"other-intermediate-certificate": "Intermediate Certificate"
|
||||||
},
|
},
|
||||||
"access-lists": {
|
"access-lists": {
|
||||||
"title": "Access Lists",
|
"title": "Access Lists",
|
||||||
|
Loading…
Reference in New Issue
Block a user