Compare commits

..

46 Commits

Author SHA1 Message Date
jc21
fdb22e467b Merge pull request #3952 from Trozz/openidc
Merge develop in to openidc
2024-10-11 14:12:20 +10:00
Michael Leer
694d8a0f21 Merge branch 'develop' into openidc 2024-08-22 23:19:50 +01:00
jc21
63d06da8a8 Merge branch 'master' into develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
2024-07-01 16:12:21 +10:00
Jamie Curnow
b5a0d74654 Bump version 2024-07-01 16:09:33 +10:00
Jamie Curnow
99cce7e2b0 Fix command injection when passing bash commands into the dns provider configuration
- Use built in node functions to write the file
- And to delete the file
2024-07-01 16:08:01 +10:00
jc21
120d50e5c0 Merge pull request #3766 from kroegerama/kroegerama-patch-1
Add include for `root_top.conf` in the nginx.conf
2024-07-01 15:23:43 +10:00
jc21
5454fd61b3 Merge pull request #3781 from jinhei/patch-1
Remove spaces around Cloudflare API Credential
2024-07-01 15:22:43 +10:00
jc21
b33012705b Merge pull request #3790 from DavidLievrouw/initial_admin
Read initial admin email and password from env vars
2024-07-01 15:22:15 +10:00
jc21
e948b60194 Merge pull request #3809 from NginxProxyManager/dependabot/npm_and_yarn/backend/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3 in /backend
2024-07-01 15:20:48 +10:00
jc21
7913c9a07d Merge pull request #3827 from Hadatko/feature/addWedosDns
Add wedos dns
2024-07-01 15:20:23 +10:00
jc21
d1c23b6286 Merge pull request #3833 from NginxProxyManager/dependabot/npm_and_yarn/backend/glob-parent-5.1.2
Bump glob-parent from 5.1.1 to 5.1.2 in /backend
2024-07-01 15:19:39 +10:00
jc21
c7e2946dbf Merge pull request #3837 from Allesanddro/patch-1
Update README.md
2024-07-01 15:19:28 +10:00
jc21
8936402229 Merge pull request #3843 from jay-lab/feature/fix-syntax-cause-err
Fix syntax that causes errors (generateKeys log)
2024-07-01 15:19:00 +10:00
An Seongjin
001c77e686 Fix syntax that causes errors (generateKeys log) 2024-06-30 22:27:54 +09:00
Dusan Cervenka
5578e825b1 Update version
Signed-off-by: Dusan Cervenka <cervenka.dusan@gmail.com>
2024-06-29 21:30:27 +02:00
S.S
c93656a7a1 Update README.md
In 2020, the concept of a single compose specification was introduced, removing the need for versioning.
2024-06-28 20:04:31 +02:00
dependabot[bot]
50aeae234f Bump glob-parent from 5.1.1 to 5.1.2 in /backend
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 18:24:09 +00:00
Dusan Cervenka
a5c06c1a34 Add wedos dns
Signed-off-by: Dusan Cervenka <cervenka.dusan@gmail.com>
2024-06-25 23:26:50 +02:00
jc21
51414ced3a Merge pull request #3810 from Brendon-Mendicino/change_log_format_location
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
Changing `log_format proxy` default location
2024-06-25 10:17:04 +10:00
jc21
5e35e538af Merge pull request #3815 from alexjsp/alex/hover-dns-plugin
Add Hover.com DNS plugin
2024-06-25 10:16:03 +10:00
Alex Stevenson-Price
13fec42d1f Add Hover.com DNS plugin 2024-06-20 11:47:50 +01:00
Brendon Mendicino
b4560d7dde feat: changing log_format proxy default location
This is useful when some user would want to change the default
log format for each of the service, without the need of creating a
new `log_format custom` and changing the `access_log` for each
service.
2024-06-16 15:44:52 +02:00
dependabot[bot]
6f9eed8a61 Bump braces from 3.0.2 to 3.0.3 in /backend
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 10:41:32 +00:00
David Lievrouw
d66e4e03e6 #3790 Attempt to make ci happy. 2024-06-03 13:44:08 +02:00
David Lievrouw
1d19c29bb0 Read initial admin email and password from env vars. 2024-06-03 13:32:23 +02:00
Nicholas Fong
e20a11de4a Remove spaces around cloudflare api credential 2024-05-28 23:32:03 -04:00
Jamie Curnow
d3a654b546 Fix flakey CI due to full stack network determination 2024-05-23 08:12:51 +10:00
Jamie Curnow
bed387ebd4 Small fix for CI cleanup 2024-05-21 13:16:53 +10:00
Jamie Curnow
6ac9a82279 Major update to cypress
- Updated cypress
- Ground work for testing DNS certs in CI
2024-05-21 12:53:07 +10:00
kroegerama
ef23e796ec update advanced config documentation
describe the `root_top.conf` file and add a snippet for enabling the geoip2 module
2024-05-20 10:35:36 +02:00
jc21
3754a569ba Merge pull request #3729 from clhey/custom_proxy
move advanced_config section of /app/templates/_location.conf to top of default config
2024-05-20 13:53:09 +10:00
jc21
b383f46656 Merge pull request #3764 from ransbachm/develop
Fix Cloudflare DNS Auth
2024-05-20 13:46:39 +10:00
kroegerama
3ce477d350 add include for root_top.conf in the nginx.conf
Allow custom configuration of the root config in the top of the file. This can be used to load modules, which is not possible at the end of the config file.
There is already a `http_top.conf`, so `root_top.conf` is a logical addition.
2024-05-19 15:53:02 +02:00
ransbachm
516b4d991c Pin version as requested by dep 2024-05-18 22:49:48 +02:00
clhey
280bac8b43 advanced config move to top of default confg 2024-04-28 18:18:38 +08:00
Jamie Curnow
a91dcb144d Use model for db defaults as sqlite doesn't support them 2021-09-09 08:12:50 +10:00
Subv
e7f7be2a2b OpenIDC: Trigger the change event of the "restrict users" toggle when enabling/disabling oidc.
If this is not triggered and the OIDC toggle is enabled, the "disabled" property will be removed from the restricted user list input, causing an error when trying to submit the form without it.
2021-09-09 08:12:50 +10:00
Subv
076d89b5b5 Use localized strings for the OpenID Connect texts. 2021-09-09 08:12:50 +10:00
Subv
8539930f89 Updated the docs to add a section about OpenID Connect 2021-09-09 08:12:50 +10:00
Subv
87d9babbd3 Fix conditionals in the liquid template for OpenID Connect conf. 2021-09-09 08:12:50 +10:00
Subv
9f2d3a1737 Manually set the default values for the OpenID Connect columns.
There is a Knex issue ( https://github.com/knex/knex/issues/2649 ) that prevents .defaultTo from working for text columns.
2021-09-09 08:12:50 +10:00
Subv
daf399163c Allow limiting OpenID Connect auth to a list of users. 2021-09-09 08:12:50 +10:00
Subv
cdf702e545 Add a field to specify a list of allowed emails when using OpenID Connect auth. 2021-09-09 08:12:50 +10:00
Subv
5811345050 Use OpenResty instead of plain nginx to support OpenID Connect authorization. 2021-09-09 08:12:48 +10:00
Subv
53792a5cf7 Add database columns to store OpenID Connect information for Proxy Hosts. 2021-09-09 08:12:19 +10:00
Subv
8e10b7da37 Add UI tab for specifying OpenID Connect options for proxy hosts. 2021-09-09 08:12:19 +10:00
54 changed files with 1972 additions and 1248 deletions

4
.gitignore vendored
View File

@@ -3,3 +3,7 @@
._*
.vscode
certbot-help.txt
test/node_modules
*/node_modules
docker/dev/dnsrouter-config.json.tmp
docker/dev/resolv.conf

View File

@@ -1 +1 @@
2.11.2
2.11.3

101
Jenkinsfile vendored
View File

@@ -18,10 +18,8 @@ pipeline {
BUILD_VERSION = getVersion()
MAJOR_VERSION = '2'
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('\\\\', '-').replaceAll('/', '-').replaceAll('\\.', '-')}"
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
COMPOSE_FILE = 'docker/docker-compose.ci.yml'
BUILDX_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
COMPOSE_INTERACTIVE_NO_CLI = 1
BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
}
stages {
stage('Environment') {
@@ -94,75 +92,61 @@ pipeline {
}
}
}
stage('Cypress') {
}
}
stage('Test Sqlite') {
environment {
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite"
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml'
}
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
// Creating will also create the network prior to
// using it in parallel stages below and mitigating
// a race condition.
sh 'docker-compose build cypress-sqlite'
sh 'docker-compose build cypress-mysql'
sh 'docker-compose create cypress-sqlite'
sh 'docker-compose create cypress-mysql'
}
}
}
}
stage('Integration Tests') {
parallel {
stage('Sqlite') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-sqlite'
sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-sqlite) 120'
// Stop and Start it, as this will test it's ability to restart with existing data
sh 'docker-compose stop fullstack-sqlite'
sh 'docker-compose start fullstack-sqlite'
sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-sqlite) 120'
// Run tests
sh 'rm -rf test/results-sqlite'
sh 'docker-compose up cypress-sqlite'
// Get results
sh 'docker cp -L "$(docker-compose ps --all -q cypress-sqlite):/test/results" test/results-sqlite'
sh 'rm -rf ./test/results/junit/*'
sh './scripts/ci/fulltest-cypress'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug/sqlite'
sh 'docker-compose logs fullstack-sqlite > debug/sqlite/docker_fullstack_sqlite.log'
// Cypress videos and screenshot artifacts
dir(path: 'test/results-sqlite') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results-sqlite/junit/*'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1'
junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
}
}
stage('Mysql') {
stage('Test Mysql') {
environment {
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql"
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml'
}
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-mysql'
sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-mysql) 120'
// Run tests
sh 'rm -rf test/results-mysql'
sh 'docker-compose up cypress-mysql'
// Get results
sh 'docker cp -L "$(docker-compose ps --all -q cypress-mysql):/test/results" test/results-mysql'
sh 'rm -rf ./test/results/junit/*'
sh './scripts/ci/fulltest-cypress'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug/mysql'
sh 'docker-compose logs fullstack-mysql > debug/mysql/docker_fullstack_mysql.log'
sh 'docker-compose logs db > debug/mysql/docker_db.log'
// Cypress videos and screenshot artifacts
dir(path: 'test/results-mysql') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results-mysql/junit/*'
}
}
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1'
junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
}
}
@@ -214,9 +198,8 @@ pipeline {
}
post {
always {
sh 'docker-compose down --remove-orphans --volumes -t 30'
sh 'echo Reverting ownership'
sh 'docker run --rm -v $(pwd):/data jc21/ci-tools chown -R $(id -u):$(id -g) /data'
sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data'
}
success {
juxtapose event: 'success'

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.11.2-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.11.3-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>
@@ -56,7 +56,6 @@ I won't go in to too much detail here but here are the basics for someone new to
2. Create a docker-compose.yml file similar to this:
```yml
version: '3.8'
services:
app:
image: 'docker.io/jc21/nginx-proxy-manager:latest'

View File

@@ -861,9 +861,8 @@ const internalCertificate = {
logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
// 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 + '\'';
fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true });
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600});
// Whether the plugin has a --<name>-credentials argument
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
@@ -898,17 +897,15 @@ const internalCertificate = {
mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore';
}
logger.info('Command:', `${credentialsCmd} && && ${mainCmd}`);
logger.info('Command:', mainCmd);
try {
await utils.exec(credentialsCmd);
const result = await utils.exec(mainCmd);
logger.info(result);
return result;
} catch (err) {
// Don't fail if file does not exist
const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`;
await utils.exec(delete_credentialsCmd);
// Don't fail if file does not exist, so no need for action in the callback
fs.unlink(credentialsLocation, () => {});
throw err;
}
},

View File

@@ -93,7 +93,7 @@ const generateKeys = () => {
try {
fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
} catch (err) {
logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' . err.message);
logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message);
process.exit(1);
}
logger.info('Wrote JWT key pair to config file: ' + keysFile);

View File

@@ -0,0 +1,48 @@
const migrate_name = 'openid_connect';
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('proxy_host', function (proxy_host) {
proxy_host.integer('openidc_enabled').notNull().unsigned().defaultTo(0);
proxy_host.text('openidc_redirect_uri').notNull().defaultTo('');
proxy_host.text('openidc_discovery').notNull().defaultTo('');
proxy_host.text('openidc_auth_method').notNull().defaultTo('');
proxy_host.text('openidc_client_id').notNull().defaultTo('');
proxy_host.text('openidc_client_secret').notNull().defaultTo('');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.dropColumn('openidc_enabled');
proxy_host.dropColumn('openidc_redirect_uri');
proxy_host.dropColumn('openidc_discovery');
proxy_host.dropColumn('openidc_auth_method');
proxy_host.dropColumn('openidc_client_id');
proxy_host.dropColumn('openidc_client_secret');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};

View File

@@ -0,0 +1,40 @@
const migrate_name = 'openid_allowed_users';
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('proxy_host', function (proxy_host) {
proxy_host.integer('openidc_restrict_users_enabled').notNull().unsigned().defaultTo(0);
proxy_host.json('openidc_allowed_users').notNull().defaultTo([]);
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.dropColumn('openidc_restrict_users_enabled');
proxy_host.dropColumn('openidc_allowed_users');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};

View File

@@ -20,12 +20,23 @@ class ProxyHost extends Model {
this.domain_names = [];
}
// Default for openidc_allowed_users
if (typeof this.openidc_allowed_users === 'undefined') {
this.openidc_allowed_users = [];
}
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
// Openidc defaults
if (typeof this.openidc_auth_method === 'undefined') {
this.openidc_auth_method = 'client_secret_post';
}
this.domain_names.sort();
this.openidc_allowed_users.sort();
}
$beforeUpdate () {
@@ -35,6 +46,11 @@ class ProxyHost extends Model {
if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort();
}
// Sort openidc_allowed_users
if (typeof this.openidc_allowed_users !== 'undefined') {
this.openidc_allowed_users.sort();
}
}
static get name () {
@@ -46,7 +62,7 @@ class ProxyHost extends Model {
}
static get jsonAttributes () {
return ['domain_names', 'meta', 'locations'];
return ['domain_names', 'meta', 'locations', 'openidc_allowed_users'];
}
static get relationMappings () {

View File

@@ -235,6 +235,43 @@
"description": "Should we cache assets",
"example": true,
"type": "boolean"
},
"openidc_enabled": {
"description": "Is OpenID Connect authentication enabled",
"example": true,
"type": "boolean"
},
"openidc_redirect_uri": {
"type": "string"
},
"openidc_discovery": {
"type": "string"
},
"openidc_auth_method": {
"type": "string",
"pattern": "^(client_secret_basic|client_secret_post)$"
},
"openidc_client_id": {
"type": "string"
},
"openidc_client_secret": {
"type": "string"
},
"openidc_restrict_users_enabled": {
"description": "Only allow a specific set of OpenID Connect emails to access the resource",
"example": true,
"type": "boolean"
},
"openidc_allowed_users": {
"type": "array",
"minItems": 0,
"items": {
"type": "string",
"description": "Email Address",
"example": "john@example.com",
"format": "email",
"minLength": 1
}
}
}
}

View File

@@ -64,6 +64,30 @@
"advanced_config": {
"type": "string"
},
"openidc_enabled": {
"$ref": "../definitions.json#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "../definitions.json#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "../definitions.json#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "../definitions.json#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "../definitions.json#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "../definitions.json#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "../definitions.json#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "../definitions.json#/definitions/openidc_allowed_users"
},
"enabled": {
"$ref": "../definitions.json#/definitions/enabled"
},
@@ -161,6 +185,30 @@
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"openidc_enabled": {
"$ref": "#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "#/definitions/openidc_allowed_users"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
@@ -251,6 +299,30 @@
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"openidc_enabled": {
"$ref": "#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "#/definitions/openidc_allowed_users"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
@@ -324,6 +396,30 @@
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"openidc_enabled": {
"$ref": "#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "#/definitions/openidc_allowed_users"
},
"enabled": {
"$ref": "#/definitions/enabled"
},

View File

@@ -21,11 +21,14 @@ const setupDefaultUser = () => {
.then((row) => {
if (!row.count) {
// Create a new user and set password
logger.info('Creating a new user: admin@example.com with password: changeme');
let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
logger.info('Creating a new user: ' + email + ' with password: ' + password);
let data = {
is_deleted: 0,
email: 'admin@example.com',
email: email,
name: 'Administrator',
nickname: 'Admin',
avatar: '',
@@ -41,7 +44,7 @@ const setupDefaultUser = () => {
.insert({
user_id: user.id,
type: 'password',
secret: 'changeme',
secret: password,
meta: {},
})
.then(() => {

View File

@@ -1,4 +1,6 @@
location {{ path }} {
{{ advanced_config }}
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
@@ -17,8 +19,5 @@
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;
{% endif %}
{{ advanced_config }}
}

View File

@@ -0,0 +1,47 @@
{% if openidc_enabled == 1 or openidc_enabled == true -%}
access_by_lua_block {
local openidc = require("resty.openidc")
local opts = {
redirect_uri = "{{- openidc_redirect_uri -}}",
discovery = "{{- openidc_discovery -}}",
token_endpoint_auth_method = "{{- openidc_auth_method -}}",
client_id = "{{- openidc_client_id -}}",
client_secret = "{{- openidc_client_secret -}}",
scope = "openid email profile"
}
local res, err = openidc.authenticate(opts)
if err then
ngx.status = 500
ngx.say(err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
{% if openidc_restrict_users_enabled == 1 or openidc_restrict_users_enabled == true -%}
local function contains(table, val)
for i=1,#table do
if table[i] == val then
return true
end
end
return false
end
local allowed_users = {
{% for user in openidc_allowed_users %}
"{{ user }}",
{% endfor %}
}
if not contains(allowed_users, res.id_token.email) then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
{% endif -%}
ngx.req.set_header("X-OIDC-SUB", res.id_token.sub)
ngx.req.set_header("X-OIDC-EMAIL", res.id_token.email)
ngx.req.set_header("X-OIDC-NAME", res.id_token.name)
}
{% endif %}

View File

@@ -33,6 +33,28 @@ proxy_http_version 1.1;
location / {
{% if access_list_id > 0 %}
{% if access_list.items.length > 0 %}
# Authorization
auth_basic "Authorization required";
auth_basic_user_file /data/access/{{ access_list_id }};
{{ access_list.passauth }}
{% endif %}
# Access Rules
{% for client in access_list.clients %}
{{- client.rule -}};
{% endfor %}deny all;
# Access checks must...
{% if access_list.satisfy %}
{{ access_list.satisfy }};
{% endif %}
{% endif %}
{% include "_openid_connect.conf" %}
{% include "_access.conf" %}
{% include "_hsts.conf" %}

View File

@@ -448,11 +448,11 @@ brace-expansion@^1.1.7:
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.0.1"
fill-range "^7.1.1"
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
version "0.2.13"
@@ -1206,10 +1206,10 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
@@ -1402,9 +1402,9 @@ glob-parent@^6.0.2:
is-glob "^4.0.3"
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"

View File

@@ -0,0 +1,28 @@
{
"log": {
"format": "nice",
"level": "debug"
},
"servers": [
{
"host": "0.0.0.0",
"port": 53,
"upstreams": [
{
"regex": "website[0-9]+.example\\.com",
"upstream": "127.0.0.11"
},
{
"regex": ".*\\.example\\.com",
"upstream": "1.1.1.1"
},
{
"regex": "local",
"nxdomain": true
}
],
"internal": null,
"default_upstream": "127.0.0.11"
}
]
}

View File

@@ -0,0 +1,7 @@
text = True
non-interactive = True
webroot-path = /data/letsencrypt-acme-challenge
key-type = ecdsa
elliptic-curve = secp384r1
preferred-chain = ISRG Root X1
server =

255
docker/dev/pdns-db.sql Normal file
View File

@@ -0,0 +1,255 @@
/*
How this was generated:
1. bring up an empty pdns stack
2. use api to create a zone ...
curl -X POST \
'http://npm.dev:8081/api/v1/servers/localhost/zones' \
--header 'X-API-Key: npm' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "example.com.",
"kind": "Native",
"masters": [],
"nameservers": [
"ns1.pdns.",
"ns2.pdns."
]
}'
3. Dump sql:
docker exec -ti npm.pdns.db mysqldump -u pdns -p pdns
*/
----------------------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `comments`
--
DROP TABLE IF EXISTS `comments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`type` varchar(10) NOT NULL,
`modified_at` int(11) NOT NULL,
`account` varchar(40) CHARACTER SET utf8mb3 DEFAULT NULL,
`comment` text CHARACTER SET utf8mb3 NOT NULL,
PRIMARY KEY (`id`),
KEY `comments_name_type_idx` (`name`,`type`),
KEY `comments_order_idx` (`domain_id`,`modified_at`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `comments`
--
LOCK TABLES `comments` WRITE;
/*!40000 ALTER TABLE `comments` DISABLE KEYS */;
/*!40000 ALTER TABLE `comments` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `cryptokeys`
--
DROP TABLE IF EXISTS `cryptokeys`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cryptokeys` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) NOT NULL,
`flags` int(11) NOT NULL,
`active` tinyint(1) DEFAULT NULL,
`published` tinyint(1) DEFAULT 1,
`content` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `domainidindex` (`domain_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cryptokeys`
--
LOCK TABLES `cryptokeys` WRITE;
/*!40000 ALTER TABLE `cryptokeys` DISABLE KEYS */;
/*!40000 ALTER TABLE `cryptokeys` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `domainmetadata`
--
DROP TABLE IF EXISTS `domainmetadata`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `domainmetadata` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) NOT NULL,
`kind` varchar(32) DEFAULT NULL,
`content` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `domainmetadata_idx` (`domain_id`,`kind`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `domainmetadata`
--
LOCK TABLES `domainmetadata` WRITE;
/*!40000 ALTER TABLE `domainmetadata` DISABLE KEYS */;
INSERT INTO `domainmetadata` VALUES
(1,1,'SOA-EDIT-API','DEFAULT');
/*!40000 ALTER TABLE `domainmetadata` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `domains`
--
DROP TABLE IF EXISTS `domains`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `domains` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`master` varchar(128) DEFAULT NULL,
`last_check` int(11) DEFAULT NULL,
`type` varchar(8) NOT NULL,
`notified_serial` int(10) unsigned DEFAULT NULL,
`account` varchar(40) CHARACTER SET utf8mb3 DEFAULT NULL,
`options` varchar(64000) DEFAULT NULL,
`catalog` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_index` (`name`),
KEY `catalog_idx` (`catalog`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `domains`
--
LOCK TABLES `domains` WRITE;
/*!40000 ALTER TABLE `domains` DISABLE KEYS */;
INSERT INTO `domains` VALUES
(1,'example.com','',NULL,'NATIVE',NULL,'',NULL,NULL);
/*!40000 ALTER TABLE `domains` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `records`
--
DROP TABLE IF EXISTS `records`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `records` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`type` varchar(10) DEFAULT NULL,
`content` varchar(64000) DEFAULT NULL,
`ttl` int(11) DEFAULT NULL,
`prio` int(11) DEFAULT NULL,
`disabled` tinyint(1) DEFAULT 0,
`ordername` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`auth` tinyint(1) DEFAULT 1,
PRIMARY KEY (`id`),
KEY `nametype_index` (`name`,`type`),
KEY `domain_id` (`domain_id`),
KEY `ordername` (`ordername`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `records`
--
LOCK TABLES `records` WRITE;
/*!40000 ALTER TABLE `records` DISABLE KEYS */;
INSERT INTO `records` VALUES
(1,1,'example.com','NS','ns1.pdns',1500,0,0,NULL,1),
(2,1,'example.com','NS','ns2.pdns',1500,0,0,NULL,1),
(3,1,'example.com','SOA','a.misconfigured.dns.server.invalid hostmaster.example.com 2023030501 10800 3600 604800 3600',1500,0,0,NULL,1);
/*!40000 ALTER TABLE `records` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `supermasters`
--
DROP TABLE IF EXISTS `supermasters`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `supermasters` (
`ip` varchar(64) NOT NULL,
`nameserver` varchar(255) NOT NULL,
`account` varchar(40) CHARACTER SET utf8mb3 NOT NULL,
PRIMARY KEY (`ip`,`nameserver`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `supermasters`
--
LOCK TABLES `supermasters` WRITE;
/*!40000 ALTER TABLE `supermasters` DISABLE KEYS */;
/*!40000 ALTER TABLE `supermasters` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `tsigkeys`
--
DROP TABLE IF EXISTS `tsigkeys`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tsigkeys` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`algorithm` varchar(50) DEFAULT NULL,
`secret` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `namealgoindex` (`name`,`algorithm`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `tsigkeys`
--
LOCK TABLES `tsigkeys` WRITE;
/*!40000 ALTER TABLE `tsigkeys` DISABLE KEYS */;
/*!40000 ALTER TABLE `tsigkeys` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

View File

@@ -0,0 +1,12 @@
{
"pebble": {
"listenAddress": "0.0.0.0:443",
"managementListenAddress": "0.0.0.0:15000",
"certificate": "test/certs/localhost/cert.pem",
"privateKey": "test/certs/localhost/key.pem",
"httpPort": 80,
"tlsPort": 443,
"ocspResponderURL": "",
"externalAccountBindingRequired": false
}
}

View File

@@ -0,0 +1,27 @@
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
services:
fullstack:
environment:
DB_MYSQL_HOST: 'db-mysql'
DB_MYSQL_PORT: '3306'
DB_MYSQL_USER: 'npm'
DB_MYSQL_PASSWORD: 'npmpass'
DB_MYSQL_NAME: 'npm'
depends_on:
- db-mysql
db-mysql:
image: jc21/mariadb-aria
environment:
MYSQL_ROOT_PASSWORD: 'npm'
MYSQL_DATABASE: 'npm'
MYSQL_USER: 'npm'
MYSQL_PASSWORD: 'npmpass'
volumes:
- mysql_vol:/var/lib/mysql
networks:
- fulltest
volumes:
mysql_vol:

View File

@@ -0,0 +1,9 @@
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
services:
fullstack:
environment:
DB_SQLITE_FILE: '/data/mydb.sqlite'
PUID: 1000
PGID: 1000
DISABLE_IPV6: 'true'

View File

@@ -1,91 +1,110 @@
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
version: '3.8'
# WARNING: This is a CI docker-compose file used for building
# and testing of the entire app, it should not be used for production.
# This is a base compose file, it should be extended with a
# docker-compose.ci.*.yml file
services:
fullstack-mysql:
image: "${IMAGE}:ci-${BUILD_NUMBER}"
fullstack:
image: "${IMAGE}:${BRANCH_LOWER}-ci-${BUILD_NUMBER}"
environment:
DEBUG: 'true'
LE_STAGING: 'true'
FORCE_COLOR: 1
DB_MYSQL_HOST: 'db'
DB_MYSQL_PORT: '3306'
DB_MYSQL_USER: 'npm'
DB_MYSQL_PASSWORD: 'npm'
DB_MYSQL_NAME: 'npm'
volumes:
- npm_data_mysql:/data
- npm_le_mysql:/etc/letsencrypt
expose:
- 81
- 80
- 443
- 'npm_data_ci:/data'
- 'npm_le_ci:/etc/letsencrypt'
- './dev/letsencrypt.ini:/etc/letsencrypt.ini:ro'
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
networks:
fulltest:
aliases:
- website1.example.com
- website2.example.com
- website3.example.com
stepca:
image: jc21/testca
volumes:
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
networks:
fulltest:
aliases:
- ca.internal
pdns:
image: pschiffe/pdns-mysql
volumes:
- '/etc/localtime:/etc/localtime:ro'
environment:
PDNS_master: 'yes'
PDNS_api: 'yes'
PDNS_api_key: 'npm'
PDNS_webserver: 'yes'
PDNS_webserver_address: '0.0.0.0'
PDNS_webserver_password: 'npm'
PDNS_webserver-allow-from: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8'
PDNS_version_string: 'anonymous'
PDNS_default_ttl: 1500
PDNS_allow_axfr_ips: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8'
PDNS_gmysql_host: pdns-db
PDNS_gmysql_port: 3306
PDNS_gmysql_user: pdns
PDNS_gmysql_password: pdns
PDNS_gmysql_dbname: pdns
depends_on:
- db
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
- pdns-db
networks:
fulltest:
aliases:
- ns1.pdns
- ns2.pdns
fullstack-sqlite:
image: "${IMAGE}:ci-${BUILD_NUMBER}"
pdns-db:
image: mariadb
environment:
DEBUG: 'true'
LE_STAGING: 'true'
FORCE_COLOR: 1
DB_SQLITE_FILE: '/data/mydb.sqlite'
PUID: 1000
PGID: 1000
DISABLE_IPV6: 'true'
MYSQL_ROOT_PASSWORD: 'pdns'
MYSQL_DATABASE: 'pdns'
MYSQL_USER: 'pdns'
MYSQL_PASSWORD: 'pdns'
volumes:
- npm_data_sqlite:/data
- npm_le_sqlite:/etc/letsencrypt
expose:
- 81
- 80
- 443
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
- 'pdns_mysql_vol:/var/lib/mysql'
- '/etc/localtime:/etc/localtime:ro'
- './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro'
networks:
- fulltest
db:
image: jc21/mariadb-aria
environment:
MYSQL_ROOT_PASSWORD: 'npm'
MYSQL_DATABASE: 'npm'
MYSQL_USER: 'npm'
MYSQL_PASSWORD: 'npm'
dnsrouter:
image: jc21/dnsrouter
volumes:
- mysql_data:/var/lib/mysql
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
networks:
- fulltest
cypress-mysql:
cypress:
image: "${IMAGE}-cypress:ci-${BUILD_NUMBER}"
build:
context: ../test/
dockerfile: cypress/Dockerfile
context: ../
dockerfile: test/cypress/Dockerfile
environment:
CYPRESS_baseUrl: 'http://fullstack-mysql:81'
CYPRESS_baseUrl: 'http://fullstack:81'
volumes:
- cypress_logs_mysql:/results
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
cypress-sqlite:
image: "${IMAGE}-cypress:ci-${BUILD_NUMBER}"
build:
context: ../test/
dockerfile: cypress/Dockerfile
environment:
CYPRESS_baseUrl: "http://fullstack-sqlite:81"
volumes:
- cypress_logs_sqlite:/results
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
- 'cypress_logs:/results'
- './dev/resolv.conf:/etc/resolv.conf:ro'
command: cypress run --browser chrome --config-file=cypress/config/ci.js
networks:
- fulltest
volumes:
cypress_logs_mysql:
cypress_logs_sqlite:
npm_data_mysql:
npm_data_sqlite:
npm_le_sqlite:
npm_le_mysql:
mysql_data:
cypress_logs:
npm_data_ci:
npm_le_ci:
pdns_mysql_vol:
networks:
fulltest:
name: "npm-${BRANCH_LOWER}-ci-${BUILD_NUMBER}"

View File

@@ -1,5 +1,4 @@
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
version: '3.8'
services:
npm:

View File

@@ -0,0 +1,4 @@
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
access_log /data/logs/fallback_access.log proxy;

View File

@@ -14,6 +14,9 @@ error_log /data/logs/fallback_error.log warn;
# Includes files with directives to load dynamic modules.
include /etc/nginx/modules/*.conf;
# Custom
include /data/nginx/custom/root_top[.]conf;
events {
include /data/nginx/custom/events[.]conf;
}
@@ -43,11 +46,23 @@ http {
proxy_cache_path /var/lib/nginx/cache/public levels=1:2 keys_zone=public-cache:30m max_size=192m;
proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;
lua_package_path '~/lua/?.lua;;';
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
lua_ssl_verify_depth 5;
# cache for discovery metadata documents
lua_shared_dict discovery 1m;
# cache for JWKs
lua_shared_dict jwks 1m;
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
access_log /data/logs/fallback_access.log proxy;
include /etc/nginx/conf.d/include/log.conf;
# Dynamically generated resolvers file
include /etc/nginx/conf.d/include/resolvers.conf;

View File

@@ -173,6 +173,7 @@ NPM has the ability to include different custom configuration snippets in differ
You can add your custom configuration snippet files at `/data/nginx/custom` as follow:
- `/data/nginx/custom/root_top.conf`: Included at the top of nginx.conf
- `/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
- `/data/nginx/custom/http_top.conf`: Included at the top of the main http block
- `/data/nginx/custom/http.conf`: Included at the end of the main http block
@@ -199,6 +200,28 @@ value by specifying it as a Docker environment variable. The default if not spec
...
```
## OpenID Connect SSO
You can secure any of your proxy hosts with OpenID Connect authentication, providing SSO support from an identity provider like Azure AD or KeyCloak. OpenID Connect support is provided through the [`lua-resty-openidc`](https://github.com/zmartzone/lua-resty-openidc) library of [`OpenResty`](https://github.com/openresty/openresty).
You will need a few things to get started with OpenID Connect:
- A registered application with your identity provider, they will provide you with a `Client ID` and a `Client Secret`. Public OpenID Connect applications (without a client secret) are not yet supported.
- A redirect URL to send the users to after they login with the identity provider, this can be any unused URL under the proxy host, like `https://<proxy host url>/private/callback`, the server will take care of capturing that URL and redirecting you to the proxy host root. You will need to add this URL to the list of allowed redirect URLs for the application you registered with your identity provider.
- The well-known discovery endpoint of the identity provider you want to use, this is an URL usually with the form `https://<provider URL>/.well-known/openid-configuration`.
After you have all this you can proceed to configure the proxy host with OpenID Connect authentication.
You can also add some rudimentary access control through a list of allowed emails in case your identity provider doesn't let you do that, if this option is enabled, any email not on that list will be denied access to the proxied host.
The proxy adds some headers based on the authentication result from the identity provider:
- `X-OIDC-SUB`: The subject identifier, according to the OpenID Coonect spec: `A locally unique and never reassigned identifier within the Issuer for the End-User`.
- `X-OIDC-EMAIL`: The email of the user that logged in, as specified in the `id_token` returned from the identity provider. The same value that will be checked for the email whitelist.
- `X-OIDC-NAME`: The user's name claim from the `id_token`, please note that not all id tokens necessarily contain this claim.
## Customising logrotate settings
By default, NPM rotates the access- and error logs weekly and keeps 4 and 10 log files respectively.
@@ -212,3 +235,12 @@ You can customise the logrotate configuration through a mount (if your custom co
```
For reference, the default configuration can be found [here](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/docker/rootfs/etc/logrotate.d/nginx-proxy-manager).
## Enabling the geoip2 module
To enable the geoip2 module, you can create the custom configuration file `/data/nginx/custom/root_top.conf` and include the following snippet:
```
load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so;
load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so;
```

View File

@@ -11,6 +11,7 @@
<li role="presentation" class="nav-item"><a href="#locations" aria-controls="tab4" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-layers"></i> <%- i18n('all-hosts', 'locations') %></a></li>
<li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li>
<li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></a></li>
<li role="presentation" class="nav-item"><a href="#openidc" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i><%- i18n('proxy-hosts', 'oidc') %></a></li>
</ul>
<div class="tab-content">
@@ -271,6 +272,71 @@
</div>
</div>
</div>
<!-- OpenID Connect -->
<div role="tabpanel" class="tab-pane" id="openidc">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="openidc_enabled" value="1"<%- openidc_enabled ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'oidc-enabled') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 openidc">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'oidc-redirect-uri') %><span class="form-required">*</span></label>
<input type="text" name="openidc_redirect_uri" class="form-control text-monospace" placeholder="" value="<%- openidc_redirect_uri %>" autocomplete="off" maxlength="255" required>
</div>
</div>
<div class="col-sm-12 col-md-12 openidc">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'oidc-discovery-endpoint') %><span class="form-required">*</span></label>
<input type="text" name="openidc_discovery" class="form-control text-monospace" placeholder="" value="<%- openidc_discovery %>" autocomplete="off" maxlength="255" required>
</div>
</div>
<div class="col-sm-12 col-md-12 openidc">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'oidc-token-auth-method') %><span class="form-required">*</span></label>
<select name="openidc_auth_method" class="form-control custom-select" placeholder="client_secret_post">
<option value="client_secret_post" <%- openidc_auth_method === 'client_secret_post' ? 'selected' : '' %>>client_secret_post</option>
<option value="client_secret_basic" <%- openidc_auth_method === 'client_secret_basic' ? 'selected' : '' %>>client_secret_basic</option>
</select>
</div>
</div>
<div class="col-sm-12 col-md-12 openidc">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'oidc-client-id') %><span class="form-required">*</span></label>
<input type="text" name="openidc_client_id" class="form-control text-monospace" placeholder="" value="<%- openidc_client_id %>" autocomplete="off" maxlength="255" required>
</div>
</div>
<div class="col-sm-12 col-md-12 openidc">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'oidc-client-secret') %><span class="form-required">*</span></label>
<input type="text" name="openidc_client_secret" class="form-control text-monospace" placeholder="" value="<%- openidc_client_secret %>" autocomplete="off" maxlength="255" required>
</div>
</div>
<div class="openidc">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="openidc_restrict_users_enabled" value="1"<%- openidc_restrict_users_enabled ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'oidc-allow-only-emails') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 openidc_users">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'oidc-allowed-emails') %><span class="form-required">*</span></label>
<input type="text" name="openidc_allowed_users" class="form-control" id="openidc_allowed_users" value="<%- openidc_allowed_users.join(',') %>" required>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -43,7 +43,12 @@ module.exports = Mn.View.extend({
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
forward_scheme: 'select[name="forward_scheme"]',
letsencrypt: '.letsencrypt'
letsencrypt: '.letsencrypt',
openidc_enabled: 'input[name="openidc_enabled"]',
openidc_restrict_users_enabled: 'input[name="openidc_restrict_users_enabled"]',
openidc_allowed_users: 'input[name="openidc_allowed_users"]',
openidc: '.openidc',
openidc_users: '.openidc_users',
},
regions: {
@@ -129,6 +134,27 @@ module.exports = Mn.View.extend({
}
},
'change @ui.openidc_enabled': function () {
let checked = this.ui.openidc_enabled.prop('checked');
if (checked) {
this.ui.openidc.show().find('input').prop('disabled', false);
} else {
this.ui.openidc.hide().find('input').prop('disabled', true);
}
this.ui.openidc_restrict_users_enabled.trigger('change');
},
'change @ui.openidc_restrict_users_enabled': function () {
let checked = this.ui.openidc_restrict_users_enabled.prop('checked');
if (checked) {
this.ui.openidc_users.show().find('input').prop('disabled', false);
} else {
this.ui.openidc_users.hide().find('input').prop('disabled', true);
}
},
'click @ui.add_location_btn': function (e) {
e.preventDefault();
@@ -167,6 +193,14 @@ module.exports = Mn.View.extend({
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
data.ssl_forced = !!data.ssl_forced;
data.openidc_enabled = data.openidc_enabled === '1';
data.openidc_restrict_users_enabled = data.openidc_restrict_users_enabled === '1';
if (data.openidc_restrict_users_enabled) {
if (typeof data.openidc_allowed_users === 'string' && data.openidc_allowed_users) {
data.openidc_allowed_users = data.openidc_allowed_users.split(',');
}
}
if (typeof data.meta === 'undefined') data.meta = {};
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
@@ -203,6 +237,12 @@ module.exports = Mn.View.extend({
data.certificate_id = parseInt(data.certificate_id, 10);
}
// OpenID Connect won't work with multiple domain names because the redirect URL has to point to a specific one
if (data.openidc_enabled && data.domain_names.length > 1) {
alert('Cannot use mutliple domain names when OpenID Connect is enabled');
return;
}
let method = App.Api.Nginx.ProxyHosts.create;
let is_new = true;
@@ -344,6 +384,23 @@ module.exports = Mn.View.extend({
view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id'));
}
});
// OpenID Connect
this.ui.openidc_allowed_users.selectize({
delimiter: ',',
persist: false,
maxOptions: 15,
create: function (input) {
return {
value: input,
text: input
};
}
});
this.ui.openidc.hide().find('input').prop('disabled', true);
this.ui.openidc_users.hide().find('input').prop('disabled', true);
this.ui.openidc_enabled.trigger('change');
this.ui.openidc_restrict_users_enabled.trigger('change');
},
initialize: function (options) {

View File

@@ -132,6 +132,16 @@
"access-list": "Access List",
"allow-websocket-upgrade": "Websockets Support",
"ignore-invalid-upstream-ssl": "Ignore Invalid SSL",
"custom-forward-host-help": "Use 1.1.1.1/path for sub-folder forwarding",
"oidc": "OpenID Connect",
"oidc-enabled": "Use OpenID Connect authentication",
"oidc-redirect-uri": "Redirect URI",
"oidc-discovery-endpoint": "Well-known discovery endpoint",
"oidc-token-auth-method": "Token endpoint auth method",
"oidc-client-id": "Client ID",
"oidc-client-secret": "Client secret",
"oidc-allow-only-emails": "Allow only these user emails",
"oidc-allowed-emails": "Allowed email addresses",
"custom-forward-host-help": "Add a path for sub-folder forwarding.\nExample: 203.0.113.25/path/",
"search": "Search Host…"
},

View File

@@ -22,6 +22,14 @@ const model = Backbone.Model.extend({
block_exploits: false,
http2_support: false,
advanced_config: '',
openidc_enabled: false,
openidc_redirect_uri: '',
openidc_discovery: '',
openidc_auth_method: 'client_secret_post',
openidc_client_id: '',
openidc_client_secret: '',
openidc_restrict_users_enabled: false,
openidc_allowed_users: [],
enabled: true,
meta: {},
// The following are expansions:

View File

@@ -35,7 +35,7 @@
"name": "Cloudflare",
"package_name": "certbot-dns-cloudflare",
"version": "=={{certbot-version}}",
"dependencies": "cloudflare acme=={{certbot-version}}",
"dependencies": "cloudflare==2.19.* acme=={{certbot-version}}",
"credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567",
"full_plugin_name": "dns-cloudflare"
},
@@ -239,6 +239,14 @@
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
"full_plugin_name": "dns-hetzner"
},
"hover": {
"name": "Hover",
"package_name": "certbot-dns-hover",
"version": "~=1.2.1",
"dependencies": "",
"credentials": "dns_hover_hoverurl = https://www.hover.com\ndns_hover_username = hover-admin-username\ndns_hover_password = hover-admin-password\ndns_hover_totpsecret = 2fa-totp-secret",
"full_plugin_name": "dns-hover"
},
"infomaniak": {
"name": "Infomaniak",
"package_name": "certbot-dns-infomaniak",
@@ -454,5 +462,13 @@
"dependencies": "",
"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>",
"full_plugin_name": "dns-websupport"
},
"wedos":{
"name": "Wedos",
"package_name": "certbot-dns-wedos",
"version": "~=2.2",
"dependencies": "",
"credentials": "dns_wedos_user = <wedos_registration>\ndns_wedos_auth = <wapi_sha256_password>",
"full_plugin_name": "dns-wedos"
}
}

View File

@@ -16,7 +16,7 @@ if hash docker 2>/dev/null; then
-e NODE_OPTIONS=--openssl-legacy-provider \
-v "$(pwd)/frontend:/app/frontend" \
-v "$(pwd)/global:/app/global" \
-w /app/frontend "$DOCKER_IMAGE" \
-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}"

89
scripts/ci/fulltest-cypress Executable file
View File

@@ -0,0 +1,89 @@
#!/bin/bash
set -e
STACK="${1:-sqlite}"
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# remember this is running in "ci" folder..
# Some defaults for running this script outside of CI
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-npm_local_fulltest}"
export IMAGE="${IMAGE:-nginx-proxy-manager}"
export BRANCH_LOWER="${BRANCH_LOWER:-unknown}"
export BUILD_NUMBER="${BUILD_NUMBER:-0000}"
if [ "${COMPOSE_FILE:-}" = "" ]; then
export COMPOSE_FILE="docker/docker-compose.ci.yml:docker/docker-compose.ci.${STACK}.yml"
fi
# Colors
BLUE='\E[1;34m'
RED='\E[1;31m'
CYAN='\E[1;36m'
GREEN='\E[1;32m'
RESET='\E[0m'
YELLOW='\E[1;33m'
export BLUE CYAN GREEN RESET YELLOW
echo -e "${BLUE} ${CYAN}Starting fullstack cypress testing ...${RESET}"
echo -e "${BLUE} $(docker-compose config)${RESET}"
# $1: container_name
get_container_ip () {
local container_name=$1
local container
local ip
container=$(docker-compose ps --all -q "${container_name}" | tail -n1)
ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container")
echo "$ip"
}
# Bring up a stack, in steps so we can inject IPs everywhere
docker-compose up -d pdns pdns-db
PDNS_IP=$(get_container_ip "pdns")
echo -e "${BLUE} ${YELLOW}PDNS IP is ${PDNS_IP}${RESET}"
# adjust the dnsrouter config
LOCAL_DNSROUTER_CONFIG="$DIR/../../docker/dev/dnsrouter-config.json"
rm -rf "$LOCAL_DNSROUTER_CONFIG.tmp"
# IMPORTANT: changes to dnsrouter-config.json will affect this line:
jq --arg a "$PDNS_IP" '.servers[0].upstreams[1].upstream = $a' "$LOCAL_DNSROUTER_CONFIG" > "$LOCAL_DNSROUTER_CONFIG.tmp"
docker-compose up -d dnsrouter
DNSROUTER_IP=$(get_container_ip "dnsrouter")
echo -e "${BLUE} ${YELLOW}DNS Router IP is ${DNSROUTER_IP}"
if [ "${DNSROUTER_IP:-}" = "" ]; then
echo -e "${RED} ERROR: DNS Router IP is not set${RESET}"
exit 1
fi
# mount the resolver
LOCAL_RESOLVE="$DIR/../../docker/dev/resolv.conf"
rm -rf "${LOCAL_RESOLVE}"
printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}"
# bring up all remaining containers, except cypress!
docker-compose up -d --remove-orphans stepca
docker-compose pull db-mysql || true # ok to fail
docker-compose up -d --remove-orphans --pull=never fullstack
# wait for main container to be healthy
bash "$DIR/../wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
# Run tests
rm -rf "$DIR/../../test/results"
docker-compose up --build cypress
# Get results
docker cp -L "$(docker-compose ps --all -q cypress):/test/results" "$DIR/../../test/"
docker cp -L "$(docker-compose ps --all -q fullstack):/data/logs" "$DIR/../../test/results/"
if [ "$2" = "cleanup" ]; then
echo -e "${BLUE} ${CYAN}Cleaning up containers ...${RESET}"
docker-compose down --remove-orphans --volumes -t 30
fi
echo -e "${BLUE} ${GREEN}Fullstack cypress testing complete${RESET}"

View File

@@ -3,8 +3,8 @@
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$DIR/../.common.sh"
DOCKER_IMAGE=nginxproxymanager/nginx-full:certbot-node
docker pull "${DOCKER_IMAGE}"
TESTING_IMAGE=nginxproxymanager/nginx-full:certbot-node
docker pull "${TESTING_IMAGE}"
# Test
echo -e "${BLUE} ${CYAN}Testing backend ...${RESET}"
@@ -12,20 +12,20 @@ docker run --rm \
-v "$(pwd)/backend:/app" \
-v "$(pwd)/global:/app/global" \
-w /app \
"${DOCKER_IMAGE}" \
"${TESTING_IMAGE}" \
sh -c 'yarn install && yarn eslint . && rm -rf node_modules'
echo -e "${BLUE} ${GREEN}Testing Complete${RESET}"
# Build
echo -e "${BLUE} ${CYAN}Building ...${RESET}"
docker build --pull --no-cache --compress \
-t "${IMAGE}:ci-${BUILD_NUMBER}" \
-t "${IMAGE:-nginx-proxy-manager}:${BRANCH_LOWER:-unknown}-ci-${BUILD_NUMBER:-0000}" \
-f docker/Dockerfile \
--progress=plain \
--build-arg TARGETPLATFORM=linux/amd64 \
--build-arg BUILDPLATFORM=linux/amd64 \
--build-arg BUILD_VERSION="${BUILD_VERSION}" \
--build-arg BUILD_COMMIT="${BUILD_COMMIT}" \
--build-arg BUILD_VERSION="${BUILD_VERSION:-unknown}" \
--build-arg BUILD_COMMIT="${BUILD_COMMIT:-unknown}" \
--build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \
.
echo -e "${BLUE} ${GREEN}Building Complete${RESET}"

View File

@@ -23,9 +23,8 @@ until [ "${HEALTHY}" = "healthy" ]; do
((LOOPCOUNT++))
if [ "$LOOPCOUNT" == "$LIMIT" ]; then
echo ""
echo ""
echo -e "${BLUE} ${RED}Timed out waiting for healthy${RESET}"
docker logs --tail 50 "$SERVICE"
exit 1
fi
done

View File

@@ -1 +0,0 @@
node_modules

6
test/.gitignore vendored
View File

@@ -1,4 +1,2 @@
.vscode
node_modules
results
cypress/videos
results/*
cypress/results/*

View File

@@ -1,13 +1,12 @@
FROM cypress/included:9.4.1
FROM cypress/included:13.9.0
COPY --chown=1000 ./ /test
COPY --chown=1000 ./test /test
# mkcert
ENV MKCERT=1.4.2
RUN wget -O /usr/bin/mkcert "https://github.com/FiloSottile/mkcert/releases/download/v${MKCERT}/mkcert-v${MKCERT}-linux-amd64" \
&& chmod +x /usr/bin/mkcert
# Disable Cypress CLI colors
ENV FORCE_COLOR=0
ENV NO_COLOR=1
WORKDIR /test
RUN yarn install
RUN yarn install && yarn cache clean
ENTRYPOINT []
CMD ["cypress", "run"]

22
test/cypress/config/ci.js Normal file
View File

@@ -0,0 +1,22 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
requestTimeout: 30000,
defaultCommandTimeout: 20000,
reporter: 'cypress-multi-reporters',
reporterOptions: {
configFile: 'multi-reporter.json'
},
video: true,
videosFolder: 'results/videos',
screenshotsFolder: 'results/screenshots',
e2e: {
setupNodeEvents(on, config) {
return require("../plugins/index.js")(on, config);
},
env: {
swaggerBase: '{{baseUrl}}/api/schema',
},
baseUrl: 'http://localhost:1234',
}
});

View File

@@ -1,14 +0,0 @@
{
"requestTimeout": 30000,
"defaultCommandTimeout": 20000,
"reporter": "cypress-multi-reporters",
"reporterOptions": {
"configFile": "multi-reporter.json"
},
"videosFolder": "results/videos",
"screenshotsFolder": "results/screenshots",
"env": {
"swaggerBase": "{{baseUrl}}/api/schema",
"RETRIES": 4
}
}

View File

@@ -0,0 +1,22 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
requestTimeout: 30000,
defaultCommandTimeout: 20000,
reporter: 'cypress-multi-reporters',
reporterOptions: {
configFile: 'multi-reporter.json'
},
video: false,
videosFolder: 'results/videos',
screenshotsFolder: 'results/screenshots',
e2e: {
setupNodeEvents(on, config) {
return require("../plugins/index.js")(on, config);
},
env: {
swaggerBase: '{{baseUrl}}/api/schema',
},
baseUrl: 'http://localhost:1234',
}
});

View File

@@ -1,14 +0,0 @@
{
"requestTimeout": 30000,
"defaultCommandTimeout": 20000,
"reporter": "cypress-multi-reporters",
"reporterOptions": {
"configFile": "multi-reporter.json"
},
"videos": false,
"screenshotsFolder": "results/screenshots",
"env": {
"swaggerBase": "{{baseUrl}}/api/schema",
"RETRIES": 0
}
}

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,8 +1,12 @@
const _ = require('lodash');
const chalk = require('chalk');
const _ = require("lodash");
const chalk = require("chalk");
module.exports = function() {
var arr = _.values(arguments);
arr.unshift(chalk.blue.bold('[') + chalk.yellow.bold('Backend API') + chalk.blue.bold(']'));
arr.unshift(
chalk.blue.bold("[") +
chalk.yellow.bold("Backend API") +
chalk.blue.bold("]"),
);
console.log.apply(null, arr);
};

View File

@@ -9,20 +9,32 @@
// ***********************************************
//
import 'cypress-wait-until';
Cypress.Commands.add('randomString', (length) => {
var result = '';
var characters = 'ABCDEFGHIJK LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
});
/**
* Check the swagger schema:
*
* @param {string} method API Method in swagger doc, "get", "put", "post", "delete"
* @param {number} statusCode API status code in swagger doc
* @param {integer} code Swagger doc endpoint response code, exactly as defined in swagger doc
* @param {string} path Swagger doc endpoint path, exactly as defined in swagger doc
* @param {*} data The API response data to check against the swagger schema
*/
Cypress.Commands.add('validateSwaggerSchema', (method, statusCode, path, data) => {
Cypress.Commands.add('validateSwaggerSchema', (method, code, path, data) => {
cy.task('validateSwaggerSchema', {
file: Cypress.env('swaggerBase'),
endpoint: path,
method: method,
statusCode: statusCode,
statusCode: code,
responseSchema: data,
verbose: true
}).should('equal', null);
@@ -40,3 +52,19 @@ Cypress.Commands.add('getToken', () => {
cy.wrap(res.token);
});
});
// TODO: copied from v3, is this usable?
Cypress.Commands.add('waitForCertificateStatus', (token, certID, expected, timeout = 60) => {
cy.log(`Waiting for certificate (${certID}) status (${expected}) timeout (${timeout})`);
cy.waitUntil(() => cy.task('backendApiGet', {
token: token,
path: `/api/certificates/${certID}`
}).then((data) => {
return data.result.status === expected;
}), {
errorMsg: 'Waiting for certificate status failed',
timeout: timeout * 1000,
interval: 5000
});
});

View File

@@ -1,5 +1,3 @@
require('cypress-plugin-retries');
import './commands';
Cypress.on('uncaught:exception', (/*err, runnable*/) => {

View File

@@ -4,19 +4,19 @@
"description": "",
"main": "index.js",
"dependencies": {
"@jc21/cypress-swagger-validation": "^0.0.9",
"@jc21/cypress-swagger-validation": "^0.2.6",
"@jc21/restler": "^3.4.0",
"chalk": "^4.1.0",
"cypress": "^9.4.1",
"cypress-multi-reporters": "^1.4.0",
"cypress-plugin-retries": "^1.5.2",
"eslint": "^7.6.0",
"cypress": "^13.9.0",
"cypress-multi-reporters": "^1.6.4",
"cypress-wait-until": "^3.0.1",
"eslint": "^9.3.0",
"eslint-plugin-align-assignments": "^1.1.2",
"eslint-plugin-chai-friendly": "^0.6.0",
"eslint-plugin-cypress": "^2.11.1",
"lodash": "^4.17.19",
"mocha": "^8.1.1",
"mocha-junit-reporter": "^2.0.0"
"eslint-plugin-chai-friendly": "^0.7.4",
"eslint-plugin-cypress": "^3.2.0",
"lodash": "^4.17.21",
"mocha": "^10.4.0",
"mocha-junit-reporter": "^2.2.1"
},
"scripts": {
"cypress": "cypress open --config-file=cypress/config/dev.json --config baseUrl=${BASE_URL:-http://127.0.0.1:3081}",

File diff suppressed because it is too large Load Diff