Compare commits

..

45 Commits

Author SHA1 Message Date
081380c8d5 Merge pull request #1716 from jc21/develop
v2.9.14
2022-01-02 22:31:12 +10:00
7e451bce0b Merge pull request #1688 from jlesage/resolvers-fix
Fixed generation of resolvers.conf.
2022-01-02 22:05:32 +10:00
a082ec0604 Merge pull request #1600 from jc21/updates-dnspod-dns-challenge
Fixes dnspod credentials template
2022-01-02 22:05:15 +10:00
973a10a9d1 Bump version 2022-01-02 21:33:16 +10:00
1ec95096d5 Ensure backend build is pulling node:latest first 2022-01-02 21:24:16 +10:00
e81cc45405 Updates dnspod challenge credentials 2022-01-02 11:49:18 +01:00
b9ef11e8bf Merge pull request #1614 from the1ts/feature/proxy-header-additions
Feature: Add two new headers to proxy.conf
2022-01-02 16:11:50 +10:00
0d8dd03c3d Merge pull request #1687 from jlesage/fallback-access-fix
Fixed the access log path to match the HTTP one.
2022-01-02 16:10:25 +10:00
74d610d9ad Merge pull request #1697 from jc21/official-dns-version-requirement
Sets certbot official dns plugin version requirement to match certbot version
2022-01-02 16:09:27 +10:00
9146ca6c63 Merge pull request #1698 from jc21/adds-faq-acl-with-login
Adds FAQ entry for acl plus login
2022-01-02 15:39:04 +10:00
d7e0ae0fa0 Merge pull request #1701 from jc21/escape-credential-backslashes
Correctly escape backslashes in dns plugin credentials
2022-01-02 15:29:31 +10:00
29ee48530c Merge pull request #1703 from luoweihua7/develop
fetch cloudflare ipv4/ipv6 fail #1405
2022-01-02 15:28:58 +10:00
abe53a4bdd Merge pull request #1704 from jc21/letsencrypt-cert-request-delay
Adds delay after reloading nginx before requesting ssl certificate using http challenge
2022-01-02 15:27:39 +10:00
2d23bedf12 Merge pull request #1713 from jc21/adds-tencentcloud-dns-challenge
Adds tencent cloud as dns challenge provider
2022-01-02 15:26:25 +10:00
4e17fb476b Adds tencent cloud as dns challenge provider 2022-01-01 17:57:17 +01:00
c803ec7e26 Adds delay after reloading nginx before requesting ssl certificate using http challenge 2021-12-30 13:21:21 +01:00
7e67f33766 fetch cloudflare ipv4/ipv6 fail #1405 2021-12-30 11:50:21 +08:00
9dd5644183 Correctly escape backslashes in dns plugin credentials 2021-12-29 16:30:49 +01:00
5a8028a72d Adds FAQ entry for acl plus login 2021-12-29 11:35:59 +01:00
747de511d4 Sets certbot official dns plugin version requirement to match certbot version 2021-12-29 09:54:59 +01:00
7800938fd2 Update default.conf 2021-12-28 18:58:22 +00:00
ab80fe13e9 Fixes custom certificate upload modal 2021-12-28 18:58:22 +00:00
5d106c4064 Bump version 2021-12-28 18:58:22 +00:00
2ac1026e4b Hack for python binary in newer node images 2021-12-28 18:58:22 +00:00
b78c7e1c53 Fixes dnspod credentials template 2021-12-27 21:13:27 +01:00
849bdcda7b Fixed generation of resolvers.conf.
This fixes scenarios where `resolv.conf` generated by dhcpcd has a nameserver with `%interface` appended to its IPv6 address.
For example, a line like this must be properly handled:
nameserver fe80::7747:4aff:fe9a:8cb1%br0
2021-12-26 21:49:55 -05:00
5aae8cd0e3 Fixed the access log path to match the HTTP one. This also fixes its handling by logrotate. 2021-12-26 20:56:42 -05:00
adc5a2020a Merge pull request #1666 from TobiasKneidl/patch-1
Update default.conf to follow the default site setting also for ipv6
2021-12-27 11:03:14 +10:00
40b1521f72 Merge pull request #1677 from jc21/fixes-custom-certificate-modal
Fixes custom certificate upload modal
2021-12-27 11:02:16 +10:00
ac23c66659 Fixes custom certificate upload modal 2021-12-23 12:23:30 +01:00
84bc33db8a Merge pull request #1667 from jc21/develop
v2.9.13
2021-12-22 11:53:02 +10:00
6392df36c3 Merge branch 'master' into develop 2021-12-22 11:52:27 +10:00
c82843279c Bump version 2021-12-22 11:51:32 +10:00
b394eb8e55 Hack for python binary in newer node images 2021-12-22 11:05:57 +10:00
bb422d4454 Update default.conf 2021-12-22 00:24:05 +01:00
3dfe23836c Add two new headers to proxy.conf
Fixes #1609. Adding both  X-Forwarded-Host  and X-Forwarded-Port, this is vital for some services behind a proxy (used to allow creation of absolute links in html). I've had to include at least the Host version in the past for jenkins and nexus.
Been running locally for 24 hours, does not appear to break any of my 15+ services currently running behind NPM would allow people to host those services without the need for advanced configuration
2021-11-29 13:48:39 +00:00
d45f39aae3 Merge pull request #1538 from jc21/adds-http-challenge-test
Adds buttons to test availability of server from public internet
2021-11-10 09:15:26 +10:00
cb091040a8 Merge pull request #1578 from jc21/undo-switch-to-variables
Reverts back to proxy_pass without variables
2021-11-10 09:13:02 +10:00
ddd538944a Skip migration if setting does not exist 2021-11-09 14:03:39 +01:00
1f879f67a9 Reverts back to proxy_pass without variables 2021-11-09 13:57:39 +01:00
ee89dedd0f Adds migration to regenerate default host 2021-11-08 23:11:56 +01:00
38ec0f9f95 Adds logs to backend when testing http challenge 2021-11-04 11:16:23 +01:00
9efe6cfb39 Minor fixes 2021-10-31 13:41:29 +01:00
c86a1a50bd Fixes formatting 2021-10-31 00:28:43 +02:00
c55476b196 Adds buttons to test availability of server from public internet 2021-10-31 00:19:18 +02:00
25 changed files with 479 additions and 99 deletions

View File

@ -1 +1 @@
2.9.12
2.9.14

3
Jenkinsfile vendored
View File

@ -62,13 +62,14 @@ pipeline {
stage('Backend') {
steps {
echo 'Checking Syntax ...'
sh 'docker pull node:latest'
// 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"
sh -c "ln -s /usr/bin/python3 /usr/bin/python && yarn install && yarn eslint . && rm -rf node_modules"
'''
echo 'Docker Build ...'

View File

@ -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.9.12-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.9.14-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>
@ -110,9 +110,9 @@ Special thanks to the following contributors:
<table>
<tr>
<td align="center">
<a href="https://github.com/Subv">
<img src="https://avatars1.githubusercontent.com/u/357072?s=460&u=d8adcdc91d749ae53e177973ed9b6bb6c4c894a3&v=4" width="80" alt=""/>
<br /><sub><b>Sebastian Valle</b></sub>
<a href="https://github.com/chaptergy">
<img src="https://avatars2.githubusercontent.com/u/26956711?s=460&u=7d9adebabb6b4e7af7cb05d98d751087a372304b&v=4" width="80" alt=""/>
<br /><sub><b>chaptergy</b></sub>
</a>
</td>
<td align="center">
@ -242,9 +242,9 @@ Special thanks to the following contributors:
</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="80" alt=""/>
<br /><sub><b>chaptergy</b></sub>
<a href="https://github.com/Subv">
<img src="https://avatars1.githubusercontent.com/u/357072?s=460&u=d8adcdc91d749ae53e177973ed9b6bb6c4c894a3&v=4" width="80" alt=""/>
<br /><sub><b>Sebastian Valle</b></sub>
</a>
</td>
<td align="center">
@ -491,6 +491,26 @@ Special thanks to the following contributors:
<br /><sub><b>bergi9</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/luoweihua7">
<img src="https://avatars.githubusercontent.com/u/3157520?v=4" width="80" alt=""/>
<br /><sub><b>luoweihua7</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/TobiasKneidl">
<img src="https://avatars.githubusercontent.com/u/26301707?v=4" width="80" alt=""/>
<br /><sub><b>Tobias Kneidl</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/piuswalter">
<img src="https://avatars.githubusercontent.com/u/64539242?v=4" width="80" alt=""/>
<br /><sub><b>Pius Walter</b></sub>
</a>
</td>
</tr>
</table>
<!-- markdownlint-enable -->

View File

@ -1,5 +1,6 @@
const _ = require('lodash');
const fs = require('fs');
const https = require('https');
const tempWrite = require('temp-write');
const moment = require('moment');
const logger = require('../logger').ssl;
@ -15,6 +16,7 @@ const letsencryptConfig = '/etc/letsencrypt.ini';
const certbotCommand = 'certbot';
const archiver = require('archiver');
const path = require('path');
const { isArray } = require('lodash');
function omissions() {
return ['is_deleted'];
@ -169,6 +171,7 @@ const internalCertificate = {
// 3. Generate the LE config
return internalNginx.generateLetsEncryptRequestConfig(certificate)
.then(internalNginx.reload)
.then(async() => await new Promise((r) => setTimeout(r, 5000)))
.then(() => {
// 4. Request cert
return internalCertificate.requestLetsEncryptSsl(certificate);
@ -868,8 +871,10 @@ const internalCertificate = {
logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
const credentialsCmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
const prepareCmd = 'pip install ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies;
// Escape single quotes and backslashes
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
const credentialsCmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
const prepareCmd = 'pip install ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies;
// Whether the plugin has a --<name>-credentials argument
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
@ -1124,6 +1129,94 @@ const internalCertificate = {
} else {
return Promise.resolve();
}
},
testHttpsChallenge: async (access, domains) => {
await access.can('certificates:list');
if (!isArray(domains)) {
throw new error.InternalValidationError('Domains must be an array of strings');
}
if (domains.length === 0) {
throw new error.InternalValidationError('No domains provided');
}
// Create a test challenge file
const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge';
const testChallengeFile = testChallengeDir + '/test-challenge';
fs.mkdirSync(testChallengeDir, {recursive: true});
fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'});
async function performTestForDomain (domain) {
logger.info('Testing http challenge for ' + domain);
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(formBody)
}
};
const result = await new Promise((resolve) => {
const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) {
let responseBody = '';
res.on('data', (chunk) => responseBody = responseBody + chunk);
res.on('end', function () {
const parsedBody = JSON.parse(responseBody + '');
if (res.statusCode !== 200) {
logger.warn(`Failed to test HTTP challenge for domain ${domain}`, res);
resolve(undefined);
}
resolve(parsedBody);
});
});
// Make sure to write the request body.
req.write(formBody);
req.end();
req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
resolve(undefined); });
});
if (!result) {
// Some error occurred while trying to get the data
return 'failed';
} else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') {
// Server exists and has responded with the correct data
return 'ok';
} else if (`${result.responsecode}` === '200') {
// Server exists but has responded with wrong data
logger.info(`HTTP challenge test failed for domain ${domain} because of invalid returned data:`, result.htmlresponse);
return 'wrong-data';
} else if (`${result.responsecode}` === '404') {
// Server exists but responded with a 404
logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`);
return '404';
} else if (`${result.responsecode}` === '0' || (typeof result.reason === 'string' && result.reason.toLowerCase() === 'host unavailable')) {
// Server does not exist at domain
logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`);
return 'no-host';
} else {
// Other errors
logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`);
return `other:${result.responsecode}`;
}
}
const results = {};
for (const domain of domains){
results[domain] = await performTestForDomain(domain);
}
// Remove the test challenge file
fs.unlinkSync(testChallengeFile);
return results;
}
};

View File

@ -9,6 +9,9 @@ const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
const regIpV4 = /^(\d+\.?){4}\/\d+/;
const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/;
const internalIpRanges = {
interval_timeout: 1000 * 60 * 60 * 6, // 6 hours
@ -74,14 +77,14 @@ const internalIpRanges = {
return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL);
})
.then((cloudfare_data) => {
let items = cloudfare_data.split('\n');
let items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line));
ip_ranges = [... ip_ranges, ... items];
})
.then(() => {
return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL);
})
.then((cloudfare_data) => {
let items = cloudfare_data.split('\n');
let items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line));
ip_ranges = [... ip_ranges, ... items];
})
.then(() => {

View File

@ -0,0 +1,50 @@
const migrate_name = 'stream_domain';
const logger = require('../logger').migrate;
const internalNginx = require('../internal/nginx');
async function regenerateDefaultHost(knex) {
const row = await knex('setting').select('*').where('id', 'default-site').first();
if (!row) {
return Promise.resolve();
}
return internalNginx.deleteConfig('default')
.then(() => {
return internalNginx.generateConfig('default', row);
})
.then(() => {
return internalNginx.test();
})
.then(() => {
return internalNginx.reload();
});
}
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex) {
logger.info('[' + migrate_name + '] Migrating Up...');
return regenerateDefaultHost(knex);
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex) {
logger.info('[' + migrate_name + '] Migrating Down...');
return regenerateDefaultHost(knex);
};

View File

@ -68,6 +68,32 @@ router
.catch(next);
});
/**
* Test HTTP challenge for domains
*
* /api/nginx/certificates/test-http
*/
router
.route('/test-http')
.options((req, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
/**
* GET /api/nginx/certificates/test-http
*
* Test HTTP challenge for domains
*/
.get((req, res, next) => {
internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
.then((result) => {
res.status(200)
.send(result);
})
.catch(next);
});
/**
* Specific certificate
*
@ -209,7 +235,6 @@ router
.catch(next);
});
/**
* Download LE Certs
*

View File

@ -157,6 +157,17 @@
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Test HTTP Challenge",
"description": "Tests whether the HTTP challenge should work",
"href": "/nginx/certificates/{definitions.identity.example}/test-http",
"access": "private",
"method": "GET",
"rel": "info",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
}
}
]
}

View File

@ -181,7 +181,9 @@ const setupCertbotPlugins = () => {
// Make sure credentials file exists
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
// Escape single quotes and backslashes
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
promises.push(utils.exec(credentials_cmd));
}
});

View File

@ -1,16 +1,10 @@
location {{ path }} {
set $targetUri {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
{% unless path contains "~" and path contains "(" and path contains ")" %}
if ($request_uri != /){
set $targetUri $targetUri$request_uri;
}
{% endunless %}
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass $targetUri;
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
{% if access_list_id > 0 %}
{% if access_list.items.length > 0 %}

View File

@ -7,9 +7,9 @@
server {
listen 80 default;
{% if ipv6 -%}
listen [::]:80;
listen [::]:80 default;
{% else -%}
#listen [::]:80;
#listen [::]:80 default;
{% endif %}
server_name default-host.localhost;
access_log /data/logs/default-host_access.log combined;

View File

@ -30,7 +30,7 @@ server {
set $port "443";
server_name localhost;
access_log /data/logs/fallback-access.log standard;
access_log /data/logs/fallback_access.log standard;
error_log /dev/null crit;
ssl_certificate /data/nginx/dummycert.pem;
ssl_certificate_key /data/nginx/dummykey.pem;

View File

@ -1,9 +1,10 @@
set $targetUri $forward_scheme://$server:$port$request_uri;
add_header X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $port;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass $targetUri;
proxy_pass $forward_scheme://$server:$port$request_uri;

View File

@ -24,7 +24,7 @@ chown root /tmp/nginx
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
# thanks @tfmm
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf);" > /etc/nginx/conf.d/include/resolvers.conf
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf);" > /etc/nginx/conf.d/include/resolvers.conf
# Generate dummy self-signed certificate.
if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]

View File

@ -21,3 +21,6 @@ Your best bet is to ask the [Reddit community for support](https://www.reddit.co
Gitter is best left for anyone contributing to the project to ask for help about internals, code reviews etc.
## When adding username and password access control to a proxy host, I can no longer login into the app.
Having an Access Control List (ACL) with username and password requires the browser to always send this username and password in the `Authorization` header on each request. If your proxied app also requires authentication (like Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, as this is the standardized header meant for this kind of information. However having multiples of the same headers is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.

View File

@ -685,6 +685,16 @@ module.exports = {
return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
},
/**
* @param {Number} id
* @returns {Promise}
*/
testHttpChallenge: function (domains) {
return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({
domains: JSON.stringify(domains),
}));
},
/**
* @param {Number} id
* @returns {Promise}

View File

@ -366,6 +366,19 @@ module.exports = {
}
},
/**
* Certificate Test Reachability
*
* @param model
*/
showNginxCertificateTestReachability: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/test'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Audit Log
*/

View File

@ -18,6 +18,14 @@
<input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required>
<div class="text-blue"><i class="fe fe-alert-triangle"></i> <%- i18n('ssl', 'hosts-warning') %></div>
</div>
<div class="mb-3 test-domains-container">
<button type="button" class="btn btn-secondary test-domains col-sm-12"><%- i18n('certificates', 'test-reachability') %></button>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%- i18n('certificates', 'reachability-info') %>
</div>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">

View File

@ -29,6 +29,8 @@ module.exports = Mn.View.extend({
non_loader_content: '.non-loader-content',
le_error_info: '#le-error-info',
domain_names: 'input[name="domain_names"]',
test_domains_container: '.test-domains-container',
test_domains_button: '.test-domains',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
@ -56,10 +58,12 @@ module.exports = Mn.View.extend({
this.ui.dns_provider_credentials.prop('required', 'required');
}
this.ui.dns_challenge_content.show();
this.ui.test_domains_container.hide();
} else {
this.ui.dns_provider.prop('required', false);
this.ui.dns_provider_credentials.prop('required', false);
this.ui.dns_challenge_content.hide();
this.ui.dns_challenge_content.hide();
this.ui.test_domains_container.show();
}
},
@ -205,6 +209,23 @@ module.exports = Mn.View.extend({
this.ui.non_loader_content.show();
});
},
'click @ui.test_domains_button': function (e) {
e.preventDefault();
const domainNames = this.ui.domain_names[0].value.split(',');
if (domainNames && domainNames.length > 0) {
this.model.set('domain_names', domainNames);
this.model.set('back_to_add', true);
App.Controller.showNginxCertificateTestReachability(this.model);
}
},
'change @ui.domain_names': function(e){
const domainNames = e.target.value.split(',');
if (domainNames && domainNames.length > 0) {
this.ui.test_domains_button.prop('disabled', false);
} else {
this.ui.test_domains_button.prop('disabled', true);
}
},
'change @ui.other_certificate_key': function(e){
this.setFileName("other_certificate_key_label", e)
},
@ -257,6 +278,12 @@ module.exports = Mn.View.extend({
this.ui.credentials_file_content.hide();
this.ui.loader_content.hide();
this.ui.le_error_info.hide();
if (this.ui.domain_names[0]) {
const domainNames = this.ui.domain_names[0].value.split(',');
if (!domainNames || domainNames.length === 0 || (domainNames.length === 1 && domainNames[0] === "")) {
this.ui.test_domains_button.prop('disabled', true);
}
}
},
initialize: function (options) {

View File

@ -42,6 +42,9 @@
<% if (provider === 'letsencrypt') { %>
<a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></a>
<a href="#" class="download dropdown-item"><i class="dropdown-icon fe fe-download"></i> <%- i18n('certificates', 'download') %></a>
<% if (meta.dns_challenge === false) { %>
<a href="#" class="test dropdown-item"><i class="dropdown-icon fe fe-globe"></i> <%- i18n('certificates', 'test-reachability') %></a>
<% } %>
<div class="dropdown-divider"></div>
<% } %>
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>

View File

@ -2,7 +2,7 @@ const Mn = require('backbone.marionette');
const moment = require('moment');
const App = require('../../../main');
const template = require('./item.ejs');
const dns_providers = require('../../../../../../global/certbot-dns-plugins')
const dns_providers = require('../../../../../../global/certbot-dns-plugins');
module.exports = Mn.View.extend({
template: template,
@ -12,7 +12,8 @@ module.exports = Mn.View.extend({
host_link: '.host-link',
renew: 'a.renew',
delete: 'a.delete',
download: 'a.download'
download: 'a.download',
test: 'a.test'
},
events: {
@ -31,11 +32,16 @@ module.exports = Mn.View.extend({
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
},
'click @ui.download': function (e) {
e.preventDefault();
App.Api.Nginx.Certificates.download(this.model.get('id'))
}
App.Api.Nginx.Certificates.download(this.model.get('id'));
},
'click @ui.test': function (e) {
e.preventDefault();
App.Controller.showNginxCertificateTestReachability(this.model);
},
},
templateContext: {

View File

@ -0,0 +1,15 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('certificates', 'reachability-title') %></h5>
</div>
<div class="modal-body">
<div class="waiting text-center">
<%= i18n('str', 'please-wait') %>
</div>
<div class="alert alert-danger error" role="alert"></div>
<div class="alert alert-success success" role="alert"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" disabled><%- i18n('str', 'close') %></button>
</div>
</div>

View File

@ -0,0 +1,75 @@
const Mn = require('backbone.marionette');
const App = require('../../main');
const template = require('./test.ejs');
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog',
ui: {
waiting: '.waiting',
error: '.error',
success: '.success',
close: 'button.cancel'
},
events: {
'click @ui.close': function (e) {
e.preventDefault();
if (this.model.get('back_to_add')) {
App.Controller.showNginxCertificateForm(this.model);
} else {
App.UI.closeModal();
}
},
},
onRender: function () {
this.ui.error.hide();
this.ui.success.hide();
App.Api.Nginx.Certificates.testHttpChallenge(this.model.get('domain_names'))
.then((result) => {
let allOk = true;
let text = '';
for (const domain in result) {
const status = result[domain];
if (status === 'ok') {
text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-ok')}</p>`;
} else {
allOk = false;
if (status === 'no-host') {
text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-not-resolved')}</p>`;
} else if (status === 'failed') {
text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-failed-to-check')}</p>`;
} else if (status === '404') {
text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-404')}</p>`;
} else if (status === 'wrong-data') {
text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-wrong-data')}</p>`;
} else if (status.startsWith('other:')) {
const code = status.substring(6);
text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-other', {code})}</p>`;
} else {
// This should never happen
text += `<p><strong>${domain}:</strong> ?</p>`;
}
}
}
this.ui.waiting.hide();
if (allOk) {
this.ui.success.html(text).show();
} else {
this.ui.error.html(text).show();
}
this.ui.close.prop('disabled', false);
})
.catch((e) => {
console.error(e);
this.ui.waiting.hide();
this.ui.error.text(App.i18n('certificates', 'reachability-failed-to-reach-api')).show();
this.ui.close.prop('disabled', false);
});
}
});

View File

@ -190,6 +190,16 @@
"other-certificate-key": "Certificate Key",
"other-intermediate-certificate": "Intermediate Certificate",
"force-renew": "Renew Now",
"test-reachability": "Test Server Reachability",
"reachability-title": "Test Server Reachability",
"reachability-info": "Test whether the domains are reachable from the public internet using Site24x7. This is not necessary when using the DNS Challenge.",
"reachability-failed-to-reach-api": "Communication with the API failed, is NPM running correctly?",
"reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.",
"reachability-ok": "Your server is reachable and creating certificates should be possible.",
"reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.",
"reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.",
"reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"download": "Download",
"renew-title": "Renew Let'sEncrypt Certificate"
},

View File

@ -67,11 +67,11 @@ dns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf274462
},
//####################################################//
cloudflare: {
display_name: 'Cloudflare',
package_name: 'certbot-dns-cloudflare',
// version_requirement: '', // Official plugin, no version requirement
dependencies: 'cloudflare',
credentials: `# Cloudflare API token
display_name: 'Cloudflare',
package_name: 'certbot-dns-cloudflare',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: 'cloudflare',
credentials: `# Cloudflare API token
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567`,
full_plugin_name: 'dns-cloudflare',
},
@ -93,11 +93,11 @@ dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567`,
},
//####################################################//
cloudxns: {
display_name: 'CloudXNS',
package_name: 'certbot-dns-cloudxns',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef
display_name: 'CloudXNS',
package_name: 'certbot-dns-cloudxns',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef
dns_cloudxns_secret_key = 1122334455667788`,
full_plugin_name: 'dns-cloudxns',
},
@ -143,12 +143,12 @@ dns_desec_endpoint = https://desec.io/api/v1/`,
},
//####################################################//
digitalocean: {
display_name: 'DigitalOcean',
package_name: 'certbot-dns-digitalocean',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: 'dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff',
full_plugin_name: 'dns-digitalocean',
display_name: 'DigitalOcean',
package_name: 'certbot-dns-digitalocean',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: 'dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff',
full_plugin_name: 'dns-digitalocean',
},
//####################################################//
directadmin: {
@ -163,20 +163,20 @@ directadmin_password = aSuperStrongPassword`,
},
//####################################################//
dnsimple: {
display_name: 'DNSimple',
package_name: 'certbot-dns-dnsimple',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: 'dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw',
full_plugin_name: 'dns-dnsimple',
display_name: 'DNSimple',
package_name: 'certbot-dns-dnsimple',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: 'dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw',
full_plugin_name: 'dns-dnsimple',
},
//####################################################//
dnsmadeeasy: {
display_name: 'DNS Made Easy',
package_name: 'certbot-dns-dnsmadeeasy',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a
display_name: 'DNS Made Easy',
package_name: 'certbot-dns-dnsmadeeasy',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a
dns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55`,
full_plugin_name: 'dns-dnsmadeeasy',
},
@ -186,8 +186,8 @@ dns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55`,
package_name: 'certbot-dns-dnspod',
version_requirement: '~=0.1.0',
dependencies: '',
credentials: `dns_dnspod_email = "DNSPOD-API-REQUIRES-A-VALID-EMAIL"
dns_dnspod_api_token = "DNSPOD-API-TOKEN"`,
credentials: `dns_dnspod_email = "email@example.com"
dns_dnspod_api_token = "id,key"`,
full_plugin_name: 'dns-dnspod',
},
//####################################################//
@ -235,11 +235,11 @@ dns_godaddy_key = abcdef0123456789abcdef01234567abcdef0123`,
},
//####################################################//
google: {
display_name: 'Google',
package_name: 'certbot-dns-google',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `{
display_name: 'Google',
package_name: 'certbot-dns-google',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `{
"type": "service_account",
...
}`,
@ -319,11 +319,11 @@ dns_joker_domain = <Dynamic DNS Domain>`,
},
//####################################################//
linode: {
display_name: 'Linode',
package_name: 'certbot-dns-linode',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64
display_name: 'Linode',
package_name: 'certbot-dns-linode',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64
dns_linode_version = [<blank>|3|4]`,
full_plugin_name: 'dns-linode',
},
@ -339,11 +339,11 @@ dns_loopia_password = abcdef0123456789abcdef01234567abcdef0123`,
},
//####################################################//
luadns: {
display_name: 'LuaDNS',
package_name: 'certbot-dns-luadns',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `dns_luadns_email = user@example.com
display_name: 'LuaDNS',
package_name: 'certbot-dns-luadns',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `dns_luadns_email = user@example.com
dns_luadns_token = 0123456789abcdef0123456789abcdef`,
full_plugin_name: 'dns-luadns',
},
@ -369,12 +369,12 @@ dns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123`,
},
//####################################################//
nsone: {
display_name: 'NS1',
package_name: 'certbot-dns-nsone',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: 'dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw',
full_plugin_name: 'dns-nsone',
display_name: 'NS1',
package_name: 'certbot-dns-nsone',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: 'dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw',
full_plugin_name: 'dns-nsone',
},
//####################################################//
oci: {
@ -392,11 +392,11 @@ key_file = ~/.oci/oci_api_key.pem`,
},
//####################################################//
ovh: {
display_name: 'OVH',
package_name: 'certbot-dns-ovh',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `dns_ovh_endpoint = ovh-eu
display_name: 'OVH',
package_name: 'certbot-dns-ovh',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `dns_ovh_endpoint = ovh-eu
dns_ovh_application_key = MDAwMDAwMDAwMDAw
dns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
dns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw`,
@ -434,11 +434,11 @@ certbot_regru:dns_password=password`,
},
//####################################################//
rfc2136: {
display_name: 'RFC 2136',
package_name: 'certbot-dns-rfc2136',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `# Target DNS server
display_name: 'RFC 2136',
package_name: 'certbot-dns-rfc2136',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `# Target DNS server
dns_rfc2136_server = 192.0.2.1
# Target DNS port
dns_rfc2136_port = 53
@ -452,11 +452,11 @@ dns_rfc2136_algorithm = HMAC-SHA512`,
},
//####################################################//
route53: {
display_name: 'Route 53 (Amazon)',
package_name: 'certbot-dns-route53',
// version_requirement: '', // Official plugin, no version requirement
dependencies: '',
credentials: `[default]
display_name: 'Route 53 (Amazon)',
package_name: 'certbot-dns-route53',
version_requirement: '==$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')', // official plugin, use certbot version
dependencies: '',
credentials: `[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,
full_plugin_name: 'dns-route53',
@ -472,6 +472,16 @@ dns_transip_key_file = /etc/letsencrypt/transip-rsa.key`,
full_plugin_name: 'dns-transip',
},
//####################################################//
tencentcloud: {
display_name: 'Tencent Cloud',
package_name: 'certbot-dns-tencentcloud',
version_requirement: '~=2.0.0',
dependencies: '',
credentials: `dns_tencentcloud_secret_id = TENCENT_CLOUD_SECRET_ID
dns_tencentcloud_secret_key = TENCENT_CLOUD_SECRET_KEY`,
full_plugin_name: 'dns-tencentcloud',
},
//####################################################//
vultr: {
display_name: 'Vultr',
package_name: 'certbot-dns-vultr',