diff --git a/.version b/.version index fad066f..e70b452 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.5.0 \ No newline at end of file +2.6.0 diff --git a/Jenkinsfile b/Jenkinsfile index f5ec652..74dc0a1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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" diff --git a/README.md b/README.md index b82e515..b94dbcd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + @@ -185,6 +185,26 @@ Special thanks to the following contributors:
Jaap-Jan de Wit + + + +
James Morgan +
+ + + + + + +
chaptergy +
+ + + + +
Philip Mooney +
+ diff --git a/backend/app.js b/backend/app.js index fc39e10..33ffacc 100644 --- a/backend/app.js +++ b/backend/app.js @@ -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 diff --git a/backend/config/sqlite-test-db.json b/backend/config/sqlite-test-db.json index 2806121..ad54886 100644 --- a/backend/config/sqlite-test-db.json +++ b/backend/config/sqlite-test-db.json @@ -4,7 +4,7 @@ "knex": { "client": "sqlite3", "connection": { - "filename": "/app/backend/config/mydb.sqlite" + "filename": "/app/config/mydb.sqlite" }, "pool": { "min": 0, diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index c70a53a..5b817d0 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -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) }); }) @@ -128,6 +129,7 @@ const internalAccessList = { .patch({ name: data.name, satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, }); } }) @@ -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 diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 2acd895..613c837 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -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']; @@ -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(() => { @@ -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]; - let tokenLoc = '~/cloudflare-token'; - let storeKey = 'echo "dns_cloudflare_api_token = ' + apiToken + '" > ' + tokenLoc; + if (!dns_plugin) { + throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); + } - let cmd = - storeKey + ' && ' + + 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; + + // Whether the plugin has a ---credentials argument + const has_config_arg = certificate.meta.dns_provider !== 'route53'; + + 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; + }); + }); + }); }, @@ -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(() => { @@ -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 ' + - '--cert-name "npm-' + certificate.id + '" ' + - '--disable-hook-validation ' + - (le_staging ? '--staging' : ''); - - if (debug_mode) { - logger.info('Command:', cmd); + if (!dns_plugin) { + throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); } - return utils.exec(cmd) - .then((result) => { - logger.info(result); - return result; + 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' : ''); + + // 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:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`); + } + + 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; + }); + }); }); }, diff --git a/backend/migrations/20201014143841_pass_auth.js b/backend/migrations/20201014143841_pass_auth.js new file mode 100644 index 0000000..a7767eb --- /dev/null +++ b/backend/migrations/20201014143841_pass_auth.js @@ -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'); + }); +}; diff --git a/backend/models/access_list.js b/backend/models/access_list.js index 8e63a2a..01974e8 100644 --- a/backend/models/access_list.js +++ b/backend/models/access_list.js @@ -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; diff --git a/backend/routes/api/nginx/certificates.js b/backend/routes/api/nginx/certificates.js index 50d3913..553a0bb 100644 --- a/backend/routes/api/nginx/certificates.js +++ b/backend/routes/api/nginx/certificates.js @@ -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) => { @@ -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) }) diff --git a/backend/schema/endpoints/access-lists.json b/backend/schema/endpoints/access-lists.json index 646306b..404e323 100644 --- a/backend/schema/endpoints/access-lists.json +++ b/backend/schema/endpoints/access-lists.json @@ -42,6 +42,9 @@ "satisfy_any": { "type": "boolean" }, + "pass_auth": { + "type": "boolean" + }, "meta": { "type": "object" } @@ -102,6 +105,9 @@ "satisfy_any": { "$ref": "#/definitions/satisfy_any" }, + "pass_auth": { + "$ref": "#/definitions/pass_auth" + }, "items": { "type": "array", "minItems": 0, @@ -167,6 +173,9 @@ "satisfy_any": { "$ref": "#/definitions/satisfy_any" }, + "pass_auth": { + "$ref": "#/definitions/pass_auth" + }, "items": { "type": "array", "minItems": 0, diff --git a/backend/schema/endpoints/certificates.json b/backend/schema/endpoints/certificates.json index 27ea2d2..49fd6a7 100644 --- a/backend/schema/endpoints/certificates.json +++ b/backend/schema/endpoints/certificates.json @@ -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 + } + ] + } } } diff --git a/backend/templates/proxy_host.conf b/backend/templates/proxy_host.conf index b553e1c..1c2c0e7 100644 --- a/backend/templates/proxy_host.conf +++ b/backend/templates/proxy_host.conf @@ -27,6 +27,8 @@ server { # Authorization auth_basic "Authorization required"; auth_basic_user_file /data/access/{{ access_list_id }}; + + {{ access_list.passauth }} {% endif %} # Access Rules diff --git a/docker/Dockerfile b/docker/Dockerfile index 5224416..acac5fa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,8 +17,8 @@ ENV NODE_ENV=production RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && apk update \ - && apk add python2 py-pip certbot jq \ - && pip install certbot-dns-cloudflare \ + && apk add python3 certbot jq \ + && python3 -m ensurepip \ && rm -rf /var/cache/apk/* ENV NPM_BUILD_VERSION="${BUILD_VERSION}" NPM_BUILD_COMMIT="${BUILD_COMMIT}" NPM_BUILD_DATE="${BUILD_DATE}" @@ -34,6 +34,7 @@ EXPOSE 443 COPY docker/rootfs / ADD backend /app ADD frontend/dist /app/frontend +COPY global /app/global WORKDIR /app RUN yarn install diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 5b67981..45ee534 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -7,8 +7,8 @@ ENV S6_FIX_ATTRS_HIDDEN=1 RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && apk update \ - && apk add python2 py-pip certbot jq \ - && pip install certbot-dns-cloudflare \ + && apk add python3 certbot jq \ + && python3 -m ensurepip \ && rm -rf /var/cache/apk/* # Task diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 4321d86..5668dbd 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -11,6 +11,8 @@ services: - 3080:80 - 3081:81 - 3443:443 + networks: + - nginx_proxy_manager environment: - NODE_ENV=development - FORCE_COLOR=1 @@ -19,13 +21,17 @@ services: volumes: - npm_data:/data - le_data:/etc/letsencrypt - - ..:/app + - ../backend:/app + - ../frontend:/app/frontend + - ../global:/app/global depends_on: - db working_dir: /app db: image: jc21/mariadb-aria + networks: + - nginx_proxy_manager environment: MYSQL_ROOT_PASSWORD: "npm" MYSQL_DATABASE: "npm" @@ -38,6 +44,8 @@ services: image: 'swaggerapi/swagger-ui:latest' ports: - 3001:80 + networks: + - nginx_proxy_manager environment: URL: "http://127.0.0.1:3081/api/schema" PORT: '80' @@ -48,3 +56,6 @@ volumes: npm_data: le_data: db_data: + +networks: + nginx_proxy_manager: diff --git a/docker/rootfs/etc/nginx/conf.d/dev.conf b/docker/rootfs/etc/nginx/conf.d/dev.conf index b70db17..edbdec8 100644 --- a/docker/rootfs/etc/nginx/conf.d/dev.conf +++ b/docker/rootfs/etc/nginx/conf.d/dev.conf @@ -17,6 +17,9 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:3000/; + + proxy_read_timeout 15m; + proxy_send_timeout 15m; } location / { diff --git a/docker/rootfs/etc/nginx/conf.d/production.conf b/docker/rootfs/etc/nginx/conf.d/production.conf index b632bce..877e51d 100644 --- a/docker/rootfs/etc/nginx/conf.d/production.conf +++ b/docker/rootfs/etc/nginx/conf.d/production.conf @@ -18,6 +18,9 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:3000/; + + proxy_read_timeout 15m; + proxy_send_timeout 15m; } location / { diff --git a/docker/rootfs/etc/services.d/manager/run b/docker/rootfs/etc/services.d/manager/run index 3ea1a17..ba0fb05 100755 --- a/docker/rootfs/etc/services.d/manager/run +++ b/docker/rootfs/etc/services.d/manager/run @@ -5,7 +5,7 @@ mkdir -p /data/letsencrypt-acme-challenge cd /app || echo if [ "$DEVELOPMENT" == "true" ]; then - cd /app/backend || exit 1 + cd /app || exit 1 yarn install node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js else diff --git a/docs/package.json b/docs/package.json index 54a1f93..190db76 100644 --- a/docs/package.json +++ b/docs/package.json @@ -434,7 +434,7 @@ "neo-async": "^2.6.2", "nice-try": "^2.0.1", "no-case": "^3.0.3", - "node-forge": "^0.9.1", + "node-forge": "^0.10.0", "node-libs-browser": "^2.2.1", "node-releases": "^1.1.60", "nopt": "^4.0.3", diff --git a/docs/yarn.lock b/docs/yarn.lock index 4dd7fac..f87d492 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -6584,10 +6584,10 @@ node-forge@0.9.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== -node-forge@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5" - integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ== +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== node-libs-browser@^2.2.1: version "2.2.1" diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js index 74356f0..baa5cb1 100644 --- a/frontend/js/app/api.js +++ b/frontend/js/app/api.js @@ -53,7 +53,7 @@ function fetch(verb, path, data, options) { contentType: options.contentType || 'application/json; charset=UTF-8', processData: options.processData || true, crossDomain: true, - timeout: options.timeout ? options.timeout : 30000, + timeout: options.timeout ? options.timeout : 180000, xhrFields: { withCredentials: true }, @@ -587,7 +587,8 @@ module.exports = { * @param {Object} data */ create: function (data) { - return fetch('post', 'nginx/certificates', data); + const timeout = 180000 + (data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0); + return fetch('post', 'nginx/certificates', data, {timeout}); }, /** @@ -630,8 +631,8 @@ module.exports = { * @param {Number} id * @returns {Promise} */ - renew: function (id) { - return fetch('post', 'nginx/certificates/' + id + '/renew'); + renew: function (id, timeout = 180000) { + return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout}); } } }, diff --git a/frontend/js/app/nginx/access/form.ejs b/frontend/js/app/nginx/access/form.ejs index 94423db..b22b99a 100644 --- a/frontend/js/app/nginx/access/form.ejs +++ b/frontend/js/app/nginx/access/form.ejs @@ -31,6 +31,16 @@ + +

+
+ +
+
diff --git a/frontend/js/app/nginx/access/form.js b/frontend/js/app/nginx/access/form.js index 0e4291a..92581f8 100644 --- a/frontend/js/app/nginx/access/form.js +++ b/frontend/js/app/nginx/access/form.js @@ -73,6 +73,7 @@ module.exports = Mn.View.extend({ let data = { name: form_data.name, satisfy_any: !!form_data.satisfy_any, + pass_auth: !!form_data.pass_auth, items: items_data, clients: clients_data }; diff --git a/frontend/js/app/nginx/certificates/form.ejs b/frontend/js/app/nginx/certificates/form.ejs index 207e59e..270ab71 100644 --- a/frontend/js/app/nginx/certificates/form.ejs +++ b/frontend/js/app/nginx/certificates/form.ejs @@ -1,12 +1,20 @@ - +
-
-
- - -
+
+
+
<%= i18n('ssl', 'certbot-warning') %>
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+ + <%= i18n('ssl', 'credentials-file-content-info') %> +
+
+ + <%= i18n('ssl', 'stored-as-plaintext-info') %> +
+
+
+
+ + +
+
+
+ + +
+ + <%= i18n('ssl', 'propagation-seconds-info') %> +
+
+
+
+
diff --git a/frontend/js/app/nginx/proxy/form.js b/frontend/js/app/nginx/proxy/form.js index 0f64281..8802b95 100644 --- a/frontend/js/app/nginx/proxy/form.js +++ b/frontend/js/app/nginx/proxy/form.js @@ -7,6 +7,8 @@ const certListItemTemplate = require('../certificates-list-item.ejs'); const accessListItemTemplate = require('./access-list-item.ejs'); const CustomLocation = require('./location'); const Helpers = require('../../../lib/helpers'); +const i18n = require('../../i18n'); +const dns_providers = require('../../../../../global/certbot-dns-plugins'); require('jquery-serializejson'); @@ -19,25 +21,29 @@ module.exports = Mn.View.extend({ locationsCollection: new ProxyLocationModel.Collection(), ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - forward_host: 'input[name="forward_host"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - add_location_btn: 'button.add_location', - locations_container:'.locations_container', - certificate_select: 'select[name="certificate_id"]', - access_list_select: 'select[name="access_list_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - cloudflare_switch: 'input[name="meta[cloudflare_use]"]', - cloudflare_token: 'input[name="meta[cloudflare_token]"', - cloudflare: '.cloudflare', - forward_scheme: 'select[name="forward_scheme"]', - letsencrypt: '.letsencrypt' + form: 'form', + domain_names: 'input[name="domain_names"]', + forward_host: 'input[name="forward_host"]', + buttons: '.modal-footer button', + cancel: 'button.cancel', + save: 'button.save', + add_location_btn: 'button.add_location', + locations_container: '.locations_container', + le_error_info: '#le-error-info', + certificate_select: 'select[name="certificate_id"]', + access_list_select: 'select[name="access_list_id"]', + ssl_forced: 'input[name="ssl_forced"]', + hsts_enabled: 'input[name="hsts_enabled"]', + hsts_subdomains: 'input[name="hsts_subdomains"]', + http2_support: 'input[name="http2_support"]', + dns_challenge_switch: 'input[name="meta[dns_challenge]"]', + dns_challenge_content: '.dns-challenge', + dns_provider: 'select[name="meta[dns_provider]"]', + credentials_file_content: '.credentials-file-content', + dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', + propagation_seconds: 'input[name="meta[propagation_seconds]"]', + forward_scheme: 'select[name="forward_scheme"]', + letsencrypt: '.letsencrypt' }, regions: { @@ -49,7 +55,7 @@ module.exports = Mn.View.extend({ let id = this.ui.certificate_select.val(); if (id === 'new') { this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.cloudflare.hide(); + this.ui.dns_challenge_content.hide(); } else { this.ui.letsencrypt.hide().find('input').prop('disabled', true); } @@ -95,14 +101,31 @@ module.exports = Mn.View.extend({ } }, - 'change @ui.cloudflare_switch': function() { - let checked = this.ui.cloudflare_switch.prop('checked'); - if (checked) { - this.ui.cloudflare_token.prop('required', 'required'); - this.ui.cloudflare.show(); - } else { - this.ui.cloudflare_token.prop('required', false); - this.ui.cloudflare.hide(); + 'change @ui.dns_challenge_switch': function () { + const checked = this.ui.dns_challenge_switch.prop('checked'); + if (checked) { + this.ui.dns_provider.prop('required', 'required'); + const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; + if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ + this.ui.dns_provider_credentials.prop('required', 'required'); + } + this.ui.dns_challenge_content.show(); + } else { + this.ui.dns_provider.prop('required', false); + this.ui.dns_provider_credentials.prop('required', false); + this.ui.dns_challenge_content.hide(); + } + }, + + 'change @ui.dns_provider': function () { + const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; + if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { + this.ui.dns_provider_credentials.prop('required', 'required'); + this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; + this.ui.credentials_file_content.show(); + } else { + this.ui.dns_provider_credentials.prop('required', false); + this.ui.credentials_file_content.hide(); } }, @@ -115,6 +138,7 @@ module.exports = Mn.View.extend({ 'click @ui.save': function (e) { e.preventDefault(); + this.ui.le_error_info.hide(); if (!this.ui.form[0].checkValidity()) { $('').hide().appendTo(this.ui.form).click().remove(); @@ -143,6 +167,18 @@ module.exports = Mn.View.extend({ data.hsts_enabled = !!data.hsts_enabled; data.hsts_subdomains = !!data.hsts_subdomains; data.ssl_forced = !!data.ssl_forced; + + if (typeof data.meta === 'undefined') data.meta = {}; + data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; + data.meta.dns_challenge = data.meta.dns_challenge == 1; + + if(!data.meta.dns_challenge){ + data.meta.dns_provider = undefined; + data.meta.dns_provider_credentials = undefined; + data.meta.propagation_seconds = undefined; + } else { + if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; + } if (typeof data.domain_names === 'string' && data.domain_names) { data.domain_names = data.domain_names.split(','); @@ -151,7 +187,7 @@ module.exports = Mn.View.extend({ // Check for any domain names containing wildcards, which are not allowed with letsencrypt if (data.certificate_id === 'new') { let domain_err = false; - if (!data.meta.cloudflare_use) { + if (!data.meta.dns_challenge) { data.domain_names.map(function (name) { if (name.match(/\*/im)) { domain_err = true; @@ -160,12 +196,9 @@ module.exports = Mn.View.extend({ } if (domain_err) { - alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.'); + alert(i18n('ssl', 'no-wildcard-without-dns')); return; } - - data.meta.cloudflare_use = data.meta.cloudflare_use === '1'; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; } else { data.certificate_id = parseInt(data.certificate_id, 10); } @@ -194,7 +227,15 @@ module.exports = Mn.View.extend({ }); }) .catch(err => { - alert(err.message); + let more_info = ''; + if(err.code === 500 && err.debug){ + try{ + more_info = JSON.parse(err.debug).debug.stack.join("\n"); + } catch(e) {} + } + this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; + this.ui.le_error_info.show(); + this.ui.le_error_info[0].scrollIntoView(); this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); this.ui.save.removeClass('btn-loading'); }); @@ -204,7 +245,20 @@ module.exports = Mn.View.extend({ templateContext: { getLetsencryptEmail: function () { return App.Cache.User.get('email'); - } + }, + getUseDnsChallenge: function () { + return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; + }, + getDnsProvider: function () { + return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; + }, + getDnsProviderCredentials: function () { + return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; + }, + getPropagationSeconds: function () { + return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; + }, + dns_plugins: dns_providers, }, onRender: function () { @@ -258,6 +312,9 @@ module.exports = Mn.View.extend({ }); // Certificates + this.ui.le_error_info.hide(); + this.ui.dns_challenge_content.hide(); + this.ui.credentials_file_content.hide(); this.ui.letsencrypt.hide(); this.ui.certificate_select.selectize({ valueField: 'id', diff --git a/frontend/js/app/nginx/redirection/form.ejs b/frontend/js/app/nginx/redirection/form.ejs index 7d49769..3247233 100644 --- a/frontend/js/app/nginx/redirection/form.ejs +++ b/frontend/js/app/nginx/redirection/form.ejs @@ -4,6 +4,7 @@
- +
-
-
- - -
+
+
+
<%= i18n('ssl', 'certbot-warning') %>
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+ + <%= i18n('ssl', 'credentials-file-content-info') %> +
+
+ + <%= i18n('ssl', 'stored-as-plaintext-info') %> +
+
+
+
+ + +
+
+
+ + +
+ + <%= i18n('ssl', 'propagation-seconds-info') %> +
+
+
+
+
diff --git a/frontend/js/app/nginx/redirection/form.js b/frontend/js/app/nginx/redirection/form.js index 4e5b168..1f81fee 100644 --- a/frontend/js/app/nginx/redirection/form.js +++ b/frontend/js/app/nginx/redirection/form.js @@ -4,6 +4,9 @@ const RedirectionHostModel = require('../../../models/redirection-host'); const template = require('./form.ejs'); const certListItemTemplate = require('../certificates-list-item.ejs'); const Helpers = require('../../../lib/helpers'); +const i18n = require('../../i18n'); +const dns_providers = require('../../../../../global/certbot-dns-plugins'); + require('jquery-serializejson'); require('selectize'); @@ -13,20 +16,24 @@ module.exports = Mn.View.extend({ className: 'modal-dialog', ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - cloudflare_switch: 'input[name="meta[cloudflare_use]"]', - cloudflare_token: 'input[name="meta[cloudflare_token]"', - cloudflare: '.cloudflare', - letsencrypt: '.letsencrypt' + form: 'form', + domain_names: 'input[name="domain_names"]', + buttons: '.modal-footer button', + cancel: 'button.cancel', + save: 'button.save', + le_error_info: '#le-error-info', + certificate_select: 'select[name="certificate_id"]', + ssl_forced: 'input[name="ssl_forced"]', + hsts_enabled: 'input[name="hsts_enabled"]', + hsts_subdomains: 'input[name="hsts_subdomains"]', + http2_support: 'input[name="http2_support"]', + dns_challenge_switch: 'input[name="meta[dns_challenge]"]', + dns_challenge_content: '.dns-challenge', + dns_provider: 'select[name="meta[dns_provider]"]', + credentials_file_content: '.credentials-file-content', + dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', + propagation_seconds: 'input[name="meta[propagation_seconds]"]', + letsencrypt: '.letsencrypt' }, events: { @@ -34,7 +41,7 @@ module.exports = Mn.View.extend({ let id = this.ui.certificate_select.val(); if (id === 'new') { this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.cloudflare.hide(); + this.ui.dns_challenge_content.hide(); } else { this.ui.letsencrypt.hide().find('input').prop('disabled', true); } @@ -80,19 +87,37 @@ module.exports = Mn.View.extend({ } }, - 'change @ui.cloudflare_switch': function() { - let checked = this.ui.cloudflare_switch.prop('checked'); - if (checked) { - this.ui.cloudflare_token.prop('required', 'required'); - this.ui.cloudflare.show(); - } else { - this.ui.cloudflare_token.prop('required', false); - this.ui.cloudflare.hide(); + 'change @ui.dns_challenge_switch': function () { + const checked = this.ui.dns_challenge_switch.prop('checked'); + if (checked) { + this.ui.dns_provider.prop('required', 'required'); + const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; + if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ + this.ui.dns_provider_credentials.prop('required', 'required'); + } + this.ui.dns_challenge_content.show(); + } else { + this.ui.dns_provider.prop('required', false); + this.ui.dns_provider_credentials.prop('required', false); + this.ui.dns_challenge_content.hide(); + } + }, + + 'change @ui.dns_provider': function () { + const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; + if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { + this.ui.dns_provider_credentials.prop('required', 'required'); + this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; + this.ui.credentials_file_content.show(); + } else { + this.ui.dns_provider_credentials.prop('required', false); + this.ui.credentials_file_content.hide(); } }, 'click @ui.save': function (e) { e.preventDefault(); + this.ui.le_error_info.hide(); if (!this.ui.form[0].checkValidity()) { $('').hide().appendTo(this.ui.form).click().remove(); @@ -103,12 +128,24 @@ module.exports = Mn.View.extend({ let data = this.ui.form.serializeJSON(); // Manipulate - data.block_exploits = !!data.block_exploits; - data.preserve_path = !!data.preserve_path; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; + data.block_exploits = !!data.block_exploits; + data.preserve_path = !!data.preserve_path; + data.http2_support = !!data.http2_support; + data.hsts_enabled = !!data.hsts_enabled; + data.hsts_subdomains = !!data.hsts_subdomains; + data.ssl_forced = !!data.ssl_forced; + + if (typeof data.meta === 'undefined') data.meta = {}; + data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; + data.meta.dns_challenge = data.meta.dns_challenge == 1; + + if(!data.meta.dns_challenge){ + data.meta.dns_provider = undefined; + data.meta.dns_provider_credentials = undefined; + data.meta.propagation_seconds = undefined; + } else { + if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; + } if (typeof data.domain_names === 'string' && data.domain_names) { data.domain_names = data.domain_names.split(','); @@ -117,7 +154,7 @@ module.exports = Mn.View.extend({ // Check for any domain names containing wildcards, which are not allowed with letsencrypt if (data.certificate_id === 'new') { let domain_err = false; - if (!data.meta.cloudflare_use) { + if (!data.meta.dns_challenge) { data.domain_names.map(function (name) { if (name.match(/\*/im)) { domain_err = true; @@ -126,12 +163,9 @@ module.exports = Mn.View.extend({ } if (domain_err) { - alert('Cannot request Let\'s Encrypt Certificate for wildcard domains without CloudFlare DNS.'); + alert(i18n('ssl', 'no-wildcard-without-dns')); return; - } - - data.meta.cloudflare_use = data.meta.cloudflare_use === '1'; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; + } } else { data.certificate_id = parseInt(data.certificate_id, 10); } @@ -160,7 +194,15 @@ module.exports = Mn.View.extend({ }); }) .catch(err => { - alert(err.message); + let more_info = ''; + if(err.code === 500 && err.debug){ + try{ + more_info = JSON.parse(err.debug).debug.stack.join("\n"); + } catch(e) {} + } + this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; + this.ui.le_error_info.show(); + this.ui.le_error_info[0].scrollIntoView(); this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); this.ui.save.removeClass('btn-loading'); }); @@ -170,7 +212,20 @@ module.exports = Mn.View.extend({ templateContext: { getLetsencryptEmail: function () { return App.Cache.User.get('email'); - } + }, + getUseDnsChallenge: function () { + return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; + }, + getDnsProvider: function () { + return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; + }, + getDnsProviderCredentials: function () { + return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; + }, + getPropagationSeconds: function () { + return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; + }, + dns_plugins: dns_providers, }, onRender: function () { @@ -191,6 +246,9 @@ module.exports = Mn.View.extend({ }); // Certificates + this.ui.le_error_info.hide(); + this.ui.dns_challenge_content.hide(); + this.ui.credentials_file_content.hide(); this.ui.letsencrypt.hide(); this.ui.certificate_select.selectize({ valueField: 'id', diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json index d0c9d8e..4bfb190 100644 --- a/frontend/js/i18n/messages.json +++ b/frontend/js/i18n/messages.json @@ -102,7 +102,17 @@ "letsencrypt-agree": "I Agree to the Let's Encrypt Terms of Service", "delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.", "hosts-warning": "These domains must be already configured to point to this installation", - "use-cloudflare": "Use CloudFlare DNS verification" + "no-wildcard-without-dns": "Cannot request Let's Encrypt Certificate for wildcard domains when not using DNS challenge", + "dns-challenge": "Use a DNS Challenge", + "certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.", + "dns-provider": "DNS Provider", + "please-choose": "Please Choose...", + "credentials-file-content": "Credentials File Content", + "credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider", + "stored-as-plaintext-info": "This data will be stored as plaintext in the database!", + "propagation-seconds": "Propagation Seconds", + "propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.", + "obtaining-certificate-info": "Obtaining certificate... This might take a few minutes." }, "proxy-hosts": { "title": "Proxy Hosts", @@ -196,7 +206,8 @@ "authorization": "Authorization", "access": "Access", "satisfy": "Satisfy", - "satisfy-any": "Satisfy Any" + "satisfy-any": "Satisfy Any", + "pass-auth": "Pass Auth to Host" }, "users": { "title": "Users", diff --git a/global/certbot-dns-plugins.js b/global/certbot-dns-plugins.js new file mode 100644 index 0000000..8170f73 --- /dev/null +++ b/global/certbot-dns-plugins.js @@ -0,0 +1,251 @@ +/** + * This file contains info about available Certbot DNS plugins. + * This only works for plugins which use the standard argument structure, so: + * --authenticator ---credentials ---propagation-seconds + * + * File Structure: + * + * { + * cloudflare: { + * display_name: "Name displayed to the user", + * package_name: "Package name in PyPi repo", + * package_version: "Package version in PyPi repo", + * credentials: `Template of the credentials file`, + * full_plugin_name: "The full plugin name as used in the commandline with certbot, including prefixes, e.g. 'certbot-dns-njalla:dns-njalla'", + * credentials_file: Whether the plugin has a credentials file + * }, + * ... + * } + * + */ + +module.exports = { + cloudflare: { + display_name: 'Cloudflare', + package_name: 'certbot-dns-cloudflare', + package_version: '1.8.0', + credentials: `# Cloudflare API token +dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567`, + full_plugin_name: 'dns-cloudflare', + }, + //####################################################// + cloudxns: { + display_name: 'CloudXNS', + package_name: 'certbot-dns-cloudxns', + package_version: '1.8.0', + credentials: `dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef +dns_cloudxns_secret_key = 1122334455667788`, + full_plugin_name: 'dns-cloudxns', + }, + //####################################################// + corenetworks: { + display_name: 'Core Networks', + package_name: 'certbot-dns-corenetworks', + package_version: '0.1.4', + credentials: `certbot_dns_corenetworks:dns_corenetworks_username = asaHB12r +certbot_dns_corenetworks:dns_corenetworks_password = secure_password`, + full_plugin_name: 'certbot-dns-corenetworks:dns-corenetworks', + }, + //####################################################// + cpanel: { + display_name: 'cPanel', + package_name: 'certbot-dns-cpanel', + package_version: '0.2.2', + credentials: `certbot_dns_cpanel:cpanel_url = https://cpanel.example.com:2083 +certbot_dns_cpanel:cpanel_username = user +certbot_dns_cpanel:cpanel_password = hunter2`, + full_plugin_name: 'certbot-dns-cpanel:cpanel', + }, + //####################################################// + digitalocean: { + display_name: 'DigitalOcean', + package_name: 'certbot-dns-digitalocean', + package_version: '1.8.0', + credentials: 'dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff', + full_plugin_name: 'dns-digitalocean', + }, + //####################################################// + directadmin: { + display_name: 'DirectAdmin', + package_name: 'certbot-dns-directadmin', + package_version: '0.0.20', + credentials: `directadmin_url = https://my.directadminserver.com:2222 +directadmin_username = username +directadmin_password = aSuperStrongPassword`, + full_plugin_name: 'certbot-dns-directadmin:directadmin', + }, + //####################################################// + dnsimple: { + display_name: 'DNSimple', + package_name: 'certbot-dns-dnsimple', + package_version: '1.8.0', + credentials: 'dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw', + full_plugin_name: 'dns-dnsimple', + }, + //####################################################// + dnsmadeeasy: { + display_name: 'DNS Made Easy', + package_name: 'certbot-dns-dnsmadeeasy', + package_version: '1.8.0', + credentials: `dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a +dns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55`, + full_plugin_name: 'dns-dnsmadeeasy', + }, + //####################################################// + dnspod: { + display_name: 'DNSPod', + package_name: 'certbot-dns-dnspod', + package_version: '0.1.0', + credentials: `certbot_dns_dnspod:dns_dnspod_email = "DNSPOD-API-REQUIRES-A-VALID-EMAIL" +certbot_dns_dnspod:dns_dnspod_api_token = "DNSPOD-API-TOKEN"`, + full_plugin_name: 'certbot-dns-dnspod:dns-dnspod', + }, + //####################################################// + google: { + display_name: 'Google', + package_name: 'certbot-dns-google', + package_version: '1.8.0', + credentials: `{ + "type": "service_account", + ... +}`, + full_plugin_name: 'dns-google', + }, + //####################################################// + hetzner: { + display_name: 'Hetzner', + package_name: 'certbot-dns-hetzner', + package_version: '1.0.4', + credentials: 'certbot_dns_hetzner:dns_hetzner_api_token = 0123456789abcdef0123456789abcdef', + full_plugin_name: 'certbot-dns-hetzner:dns-hetzner', + }, + //####################################################// + inwx: { + display_name: 'INWX', + package_name: 'certbot-dns-inwx', + package_version: '2.1.2', + credentials: `certbot_dns_inwx:dns_inwx_url = https://api.domrobot.com/xmlrpc/ +certbot_dns_inwx:dns_inwx_username = your_username +certbot_dns_inwx:dns_inwx_password = your_password +certbot_dns_inwx:dns_inwx_shared_secret = your_shared_secret optional`, + full_plugin_name: 'certbot-dns-inwx:dns-inwx', + }, + //####################################################// + ispconfig: { + display_name: 'ISPConfig', + package_name: 'certbot-dns-ispconfig', + package_version: '0.2.0', + credentials: `certbot_dns_ispconfig:dns_ispconfig_username = myremoteuser +certbot_dns_ispconfig:dns_ispconfig_password = verysecureremoteuserpassword +certbot_dns_ispconfig:dns_ispconfig_endpoint = https://localhost:8080`, + full_plugin_name: 'certbot-dns-ispconfig:dns-ispconfig', + }, + //####################################################// + isset: { + display_name: 'Isset', + package_name: 'certbot-dns-isset', + package_version: '0.0.3', + credentials: `certbot_dns_isset:dns_isset_endpoint="https://customer.isset.net/api" +certbot_dns_isset:dns_isset_token=""`, + full_plugin_name: 'certbot-dns-isset:dns-isset', + }, + //####################################################// + linode: { + display_name: 'Linode', + package_name: 'certbot-dns-linode', + package_version: '1.8.0', + credentials: `dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64 +dns_linode_version = [|3|4]`, + full_plugin_name: 'dns-linode', + }, + //####################################################// + luadns: { + display_name: 'LuaDNS', + package_name: 'certbot-dns-luadns', + package_version: '1.8.0', + credentials: `dns_luadns_email = user@example.com +dns_luadns_token = 0123456789abcdef0123456789abcdef`, + full_plugin_name: 'dns-luadns', + }, + //####################################################// + netcup: { + display_name: 'netcup', + package_name: 'certbot-dns-netcup', + package_version: '1.0.0', + credentials: `dns_netcup_customer_id = 123456 +dns_netcup_api_key = 0123456789abcdef0123456789abcdef01234567 +dns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123`, + full_plugin_name: 'certbot-dns-netcup:dns-netcup', + }, + //####################################################// + njalla: { + display_name: 'Njalla', + package_name: 'certbot-dns-njalla', + package_version: '0.0.4', + credentials: 'certbot_dns_njalla:dns_njalla_token = 0123456789abcdef0123456789abcdef01234567', + full_plugin_name: 'certbot-dns-njalla:dns-njalla', + }, + //####################################################// + nsone: { + display_name: 'NS1', + package_name: 'certbot-dns-nsone', + package_version: '1.8.0', + credentials: 'dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw', + full_plugin_name: 'dns-nsone', + }, + //####################################################// + ovh: { + display_name: 'OVH', + package_name: 'certbot-dns-ovh', + package_version: '1.8.0', + credentials: `dns_ovh_endpoint = ovh-eu +dns_ovh_application_key = MDAwMDAwMDAwMDAw +dns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw +dns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw`, + full_plugin_name: 'dns-ovh', + }, + //####################################################// + powerdns: { + display_name: 'PowerDNS', + package_name: 'certbot-dns-powerdns', + package_version: '0.2.0', + credentials: `certbot_dns_powerdns:dns_powerdns_api_url = https://api.mypowerdns.example.org +certbot_dns_powerdns:dns_powerdns_api_key = AbCbASsd!@34`, + full_plugin_name: 'certbot-dns-powerdns:dns-powerdns', + }, + //####################################################// + rfc2136: { + display_name: 'RFC 2136', + package_name: 'certbot-dns-rfc2136', + package_version: '1.8.0', + credentials: `# Target DNS server +dns_rfc2136_server = 192.0.2.1 +# Target DNS port +dns_rfc2136_port = 53 +# TSIG key name +dns_rfc2136_name = keyname. +# TSIG key secret +dns_rfc2136_secret = 4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs AmKd7ak51vWKgSl12ib86oQRPkpDjg== +# TSIG key algorithm +dns_rfc2136_algorithm = HMAC-SHA512`, + full_plugin_name: 'dns-rfc2136', + }, + //####################################################// + route53: { + display_name: 'Route 53 (Amazon)', + package_name: 'certbot-dns-route53', + package_version: '1.8.0', + credentials: `[default] +aws_access_key_id=AKIAIOSFODNN7EXAMPLE +aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`, + full_plugin_name: 'dns-route53', + }, + //####################################################// + vultr: { + display_name: 'Vultr', + package_name: 'certbot-dns-vultr', + package_version: '1.0.3', + credentials: 'certbot_dns_vultr:dns_vultr_key = YOUR_VULTR_API_KEY', + full_plugin_name: 'certbot-dns-vultr:dns-vultr', + }, +}; diff --git a/scripts/frontend-build b/scripts/frontend-build index 506a334..45c6d59 100755 --- a/scripts/frontend-build +++ b/scripts/frontend-build @@ -10,7 +10,7 @@ if hash docker 2>/dev/null; then docker pull "${DOCKER_IMAGE}" cd "${DIR}/.." echo -e "${BLUE}❯ ${CYAN}Building Frontend ...${RESET}" - docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend" + docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -v "$(pwd)/global:/app/global" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend" echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}" else echo -e "${RED}❯ docker command is not available${RESET}" diff --git a/scripts/test-dev b/scripts/test-dev index eb5c5bd..f75527b 100755 --- a/scripts/test-dev +++ b/scripts/test-dev @@ -7,7 +7,7 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if hash docker-compose 2>/dev/null; then cd "${DIR}/.." echo -e "${BLUE}❯ ${CYAN}Testing Dev Stack ...${RESET}" - docker-compose exec -T npm bash -c "cd /app/backend && task test" + docker-compose exec -T npm bash -c "cd /app && task test" else echo -e "${RED}❯ docker-compose command is not available${RESET}" fi