Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e5cb750
Bump node-forge from 0.9.1 to 0.10.0 in /docs
dependabot[bot] Sep 17, 2020
3e10b7b
Fix for access list getAll when not granted all permissions
Philip-Mooney Sep 19, 2020
b81325d
Implements dns challenge provider selection in frontend
chaptergy Oct 4, 2020
2523424
Updates dockerfiles
chaptergy Oct 4, 2020
05f6a55
Adds frontend improvements and fixes
chaptergy Oct 6, 2020
093b48a
Implements backend changes to allow more dns challenges
chaptergy Oct 6, 2020
64de36c
Adds more DNS plugins
chaptergy Oct 6, 2020
4cbc1f5
Minor refactoring
chaptergy Oct 6, 2020
514b13f
Fixes build issues due to globally used file
chaptergy Oct 6, 2020
95208a5
Increases timeouts in front- and backend
chaptergy Oct 8, 2020
867fe13
Unifies directory structure in dev and prod containers
chaptergy Oct 8, 2020
3fec135
Fixes ESlint formatting errors
chaptergy Oct 8, 2020
07e78ae
Adds error stack information in prod environment for certificates
chaptergy Oct 8, 2020
049e424
Adds special case for Route53
chaptergy Oct 14, 2020
ac9f052
Fixes linting errors
chaptergy Oct 14, 2020
3c4ce83
Merge pull request #635 from chaptergy/allow-more-dns-challenges
jc21 Oct 14, 2020
5830bd7
Merge pull request #608 from Philip-Mooney/master
jc21 Oct 14, 2020
165bfc9
Merge pull request #607 from jc21/dependabot/npm_and_yarn/docs/node-f…
jc21 Oct 14, 2020
0df0545
Allows auth information from AccessList not to be passed to proxied h…
Oct 14, 2020
e3b680c
Merge pull request #653 from jmwebslave/dont-pass-auth-header
jc21 Oct 15, 2020
10f0eb1
Fix linting errors
jc21 Oct 15, 2020
dbf5dec
Bump version
jc21 Oct 15, 2020
c413b4a
Added contributors
jc21 Oct 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.5.0
2.6.0
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pipeline {
// See: https://github.com/yarnpkg/yarn/issues/3254
sh '''docker run --rm \\
-v "$(pwd)/backend:/app" \\
-v "$(pwd)/global:/app/global" \\
-w /app \\
node:latest \\
sh -c "yarn install && yarn eslint . && rm -rf node_modules"
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center">
<img src="https://nginxproxymanager.com/github.png">
<br><br>
<img src="https://img.shields.io/badge/version-2.5.0-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.6.0-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a>
Expand Down Expand Up @@ -185,6 +185,26 @@ Special thanks to the following contributors:
<br /><sub><b>Jaap-Jan de Wit</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jmwebslave">
<img src="https://avatars2.githubusercontent.com/u/6118262?s=460&u=7db409c47135b1e141c366bbb03ed9fae6ac2638&v=4" width="80px;" alt=""/>
<br /><sub><b>James Morgan</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/chaptergy">
<img src="https://avatars2.githubusercontent.com/u/26956711?s=460&u=7d9adebabb6b4e7af7cb05d98d751087a372304b&v=4" width="80px;" alt=""/>
<br /><sub><b>chaptergy</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Philip-Mooney">
<img src="https://avatars0.githubusercontent.com/u/48624631?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Philip Mooney</b></sub>
</a>
</td>
</tr>
</table>
<!-- markdownlint-enable -->
Expand Down
2 changes: 1 addition & 1 deletion backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ app.use(function (err, req, res, next) {
}
};

if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
payload.debug = {
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
previous: err.previous
Expand Down
2 changes: 1 addition & 1 deletion backend/config/sqlite-test-db.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/app/backend/config/mydb.sqlite"
"filename": "/app/config/mydb.sqlite"
},
"pool": {
"min": 0,
Expand Down
4 changes: 3 additions & 1 deletion backend/internal/access-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const internalAccessList = {
.insertAndFetch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1)
});
})
Expand Down Expand Up @@ -128,6 +129,7 @@ const internalAccessList = {
.patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
}
})
Expand Down Expand Up @@ -384,7 +386,7 @@ const internalAccessList = {
.orderBy('access_list.name', 'ASC');

if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
}

// Query is used for searching
Expand Down
121 changes: 91 additions & 30 deletions backend/internal/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const internalNginx = require('./nginx');
const internalHost = require('./host');
const certbot_command = '/usr/bin/certbot';
const le_config = '/etc/letsencrypt.ini';
const dns_plugins = require('../global/certbot-dns-plugins');

function omissions() {
return ['is_deleted'];
Expand Down Expand Up @@ -141,11 +142,11 @@ const internalCertificate = {
});
})
.then((in_use_result) => {
// Is CloudFlare, no config needed, so skip 3 and 5.
if (data.meta.cloudflare_use) {
// With DNS challenge no config is needed, so skip 3 and 5.
if (certificate.meta.dns_challenge) {
return internalNginx.reload().then(() => {
// 4. Request cert
return internalCertificate.requestLetsEncryptCloudFlareDnsSsl(certificate, data.meta.cloudflare_token);
return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate);
})
.then(internalNginx.reload)
.then(() => {
Expand Down Expand Up @@ -772,35 +773,70 @@ const internalCertificate = {
},

/**
* @param {Object} certificate the certificate row
* @param {String} apiToken the cloudflare api token
* @param {Object} certificate the certificate row
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
* @param {String | null} credentials the content of this providers credentials file
* @param {String} propagation_seconds the cloudflare api token
* @returns {Promise}
*/
requestLetsEncryptCloudFlareDnsSsl: (certificate, apiToken) => {
logger.info('Requesting Let\'sEncrypt certificates via Cloudflare DNS for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
requestLetsEncryptSslWithDnsChallenge: (certificate) => {
const dns_plugin = dns_plugins[certificate.meta.dns_provider];

if (!dns_plugin) {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
}

logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);

const credentials_loc = '/etc/letsencrypt/credentials-' + certificate.id;
const credentials_cmd = 'echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
const prepare_cmd = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version;

let tokenLoc = '~/cloudflare-token';
let storeKey = 'echo "dns_cloudflare_api_token = ' + apiToken + '" > ' + tokenLoc;
// Whether the plugin has a --<name>-credentials argument
const has_config_arg = certificate.meta.dns_provider !== 'route53';

let cmd =
storeKey + ' && ' +
let main_cmd =
certbot_command + ' certonly --non-interactive ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
'--dns-cloudflare --dns-cloudflare-credentials ' + tokenLoc +
(le_staging ? ' --staging' : '')
+ ' && rm ' + tokenLoc;
'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
(
has_config_arg
? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentials_loc + '"'
: ''
) +
(
certificate.meta.propagation_seconds !== undefined
? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
: ''
) +
(le_staging ? ' --staging' : '');

// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
}

const teardown_cmd = `rm '${credentials_loc}'`;

if (debug_mode) {
logger.info('Command:', cmd);
logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`);
}

return utils.exec(cmd).then((result) => {
logger.info(result);
return result;
});
return utils.exec(credentials_cmd)
.then(() => {
return utils.exec(prepare_cmd)
.then(() => {
return utils.exec(main_cmd)
.then(async (result) => {
await utils.exec(teardown_cmd);
logger.info(result);
return result;
});
});
});
},


Expand All @@ -817,7 +853,7 @@ const internalCertificate = {
})
.then((certificate) => {
if (certificate.provider === 'letsencrypt') {
let renewMethod = certificate.meta.cloudflare_use ? internalCertificate.renewLetsEncryptCloudFlareSsl : internalCertificate.renewLetsEncryptSsl;
let renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;

return renewMethod(certificate)
.then(() => {
Expand Down Expand Up @@ -877,22 +913,47 @@ const internalCertificate = {
* @param {Object} certificate the certificate row
* @returns {Promise}
*/
renewLetsEncryptCloudFlareSsl: (certificate) => {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
const dns_plugin = dns_plugins[certificate.meta.dns_provider];

let cmd = certbot_command + ' renew --non-interactive ' +
if (!dns_plugin) {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
}

logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);

const credentials_loc = '/etc/letsencrypt/credentials-' + certificate.id;
const credentials_cmd = 'echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
const prepare_cmd = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version;

let main_cmd =
certbot_command + ' renew --non-interactive ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--disable-hook-validation ' +
(le_staging ? '--staging' : '');
'--disable-hook-validation' +
(le_staging ? ' --staging' : '');

// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
}

const teardown_cmd = `rm '${credentials_loc}'`;

if (debug_mode) {
logger.info('Command:', cmd);
logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`);
}

return utils.exec(cmd)
.then((result) => {
logger.info(result);
return result;
return utils.exec(credentials_cmd)
.then(() => {
return utils.exec(prepare_cmd)
.then(() => {
return utils.exec(main_cmd)
.then(async (result) => {
await utils.exec(teardown_cmd);
logger.info(result);
return result;
});
});
});
},

Expand Down
41 changes: 41 additions & 0 deletions backend/migrations/20201014143841_pass_auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const migrate_name = 'pass_auth';
const logger = require('../logger').migrate;

/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {

logger.info('[' + migrate_name + '] Migrating Up...');

return knex.schema.table('access_list', function (access_list) {
access_list.integer('pass_auth').notNull().defaultTo(1);
})
.then(() => {
logger.info('[' + migrate_name + '] access_list Table altered');
});
};

/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Down...');

return knex.schema.table('access_list', function (access_list) {
access_list.dropColumn('pass_auth');
})
.then(() => {
logger.info('[' + migrate_name + '] access_list pass_auth Column dropped');
});
};
4 changes: 4 additions & 0 deletions backend/models/access_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class AccessList extends Model {
get satisfy() {
return this.satisfy_any ? 'satisfy any' : 'satisfy all';
}

get passauth() {
return this.pass_auth ? '' : 'proxy_set_header Authorization "";';
}
}

module.exports = AccessList;
2 changes: 2 additions & 0 deletions backend/routes/api/nginx/certificates.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ router
.post((req, res, next) => {
apiValidator({$ref: 'endpoints/certificates#/links/1/schema'}, req.body)
.then((payload) => {
req.setTimeout(900000); // 15 minutes timeout
return internalCertificate.create(res.locals.access, payload);
})
.then((result) => {
Expand Down Expand Up @@ -197,6 +198,7 @@ router
* Renew certificate
*/
.post((req, res, next) => {
req.setTimeout(900000); // 15 minutes timeout
internalCertificate.renew(res.locals.access, {
id: parseInt(req.params.certificate_id, 10)
})
Expand Down
9 changes: 9 additions & 0 deletions backend/schema/endpoints/access-lists.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"satisfy_any": {
"type": "boolean"
},
"pass_auth": {
"type": "boolean"
},
"meta": {
"type": "object"
}
Expand Down Expand Up @@ -102,6 +105,9 @@
"satisfy_any": {
"$ref": "#/definitions/satisfy_any"
},
"pass_auth": {
"$ref": "#/definitions/pass_auth"
},
"items": {
"type": "array",
"minItems": 0,
Expand Down Expand Up @@ -167,6 +173,9 @@
"satisfy_any": {
"$ref": "#/definitions/satisfy_any"
},
"pass_auth": {
"$ref": "#/definitions/pass_auth"
},
"items": {
"type": "array",
"minItems": 0,
Expand Down
16 changes: 14 additions & 2 deletions backend/schema/endpoints/certificates.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,23 @@
"letsencrypt_agree": {
"type": "boolean"
},
"cloudflare_use": {
"dns_challenge": {
"type": "boolean"
},
"cloudflare_token": {
"dns_provider": {
"type": "string"
},
"dns_provider_credentials": {
"type": "string"
},
"propagation_seconds": {
"anyOf": [
{
"type": "integer",
"minimum": 0
}
]

}
}
}
Expand Down
Loading