From 56a92e5c0e7643eaed0ed6b1ce74295912eeeff2 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 09:04:37 +1000 Subject: [PATCH 01/10] Run as root by default Optionally run as another user/group only if the env vars are specified. Should give flexibility to those who need to run processes as root and open ports without having to request additional priveleges --- docker/docker-compose.ci.yml | 2 ++ docker/rootfs/bin/common.sh | 13 +++++++ .../rootfs/etc/s6-overlay/s6-rc.d/backend/run | 26 +++++++++----- .../etc/s6-overlay/s6-rc.d/frontend/run | 16 ++++++--- .../rootfs/etc/s6-overlay/s6-rc.d/nginx/run | 10 ++++-- .../s6-overlay/s6-rc.d/prepare/10-npmuser.sh | 36 +++++++++---------- .../s6-rc.d/prepare/30-ownership.sh | 22 ++++++------ .../s6-overlay/s6-rc.d/prepare/90-banner.sh | 12 ++++--- 8 files changed, 87 insertions(+), 50 deletions(-) diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index c090e19c..9f4edc00 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -33,6 +33,8 @@ services: LE_STAGING: 'true' FORCE_COLOR: 1 DB_SQLITE_FILE: '/data/mydb.sqlite' + PUID: 1000 + PGID: 1000 volumes: - npm_data:/data expose: diff --git a/docker/rootfs/bin/common.sh b/docker/rootfs/bin/common.sh index b95ff941..0bc6468d 100644 --- a/docker/rootfs/bin/common.sh +++ b/docker/rootfs/bin/common.sh @@ -9,6 +9,19 @@ RED='\E[1;31m' RESET='\E[0m' export CYAN BLUE YELLOW RED RESET +PUID=${PUID:-0} +PGID=${PGID:-0} + +if [[ "$PUID" -ne '0' ]] && [ "$PGID" = '0' ]; then + # set group id to same as user id, + # the user probably forgot to specify the group id and + # it would be rediculous to intentionally use the root group + # for a non-root user + PGID=$PUID +fi + +export PUID PGID + log_info () { echo -e "${BLUE}❯ ${CYAN}$1${RESET}" } diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run index b8287643..2f9fa9f6 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run @@ -5,18 +5,28 @@ set -e . /bin/common.sh -log_info 'Starting backend ...' +cd /app || exit 1 -if [ "$DEVELOPMENT" == "true" ]; then - cd /app || exit 1 - # If yarn install fails: add --verbose --network-concurrency 1 - s6-setuidgid npmuser yarn install - exec s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js' +if [ "${DEVELOPMENT:-}" = "true" ]; then + if [ "$PUID" = '0' ]; then + log_info 'Starting backend development ...' + yarn install + node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js + else + log_info "Starting backend development as npmuser ($PUID) ..." + s6-setuidgid npmuser yarn install + exec s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js' + fi else - cd /app || exit 1 while : do - s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --abort_on_uncaught_exception --max_old_space_size=250 index.js' + if [ "$PUID" = '0' ]; then + log_info 'Starting backend ...' + node --abort_on_uncaught_exception --max_old_space_size=250 index.js + else + log_info "Starting backend as npmuser ($PUID) ..." + s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --abort_on_uncaught_exception --max_old_space_size=250 index.js' + fi sleep 1 done fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run index 7a80c25a..19db5733 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run @@ -8,14 +8,20 @@ set -e if [ "$DEVELOPMENT" == "true" ]; then . /bin/common.sh cd /app/frontend || exit 1 - log_info 'Starting frontend ...' HOME=/tmp/npmuserhome export HOME mkdir -p /app/frontend/dist - chown -R npmuser:npmuser /app/frontend/dist - # If yarn install fails: add --verbose --network-concurrency 1 - s6-setuidgid npmuser yarn install - exec s6-setuidgid npmuser yarn watch + chown -R "$PUID:$PGID" /app/frontend/dist + + if [ "$PUID" = '0' ]; then + log_info 'Starting frontend ...' + yarn install + exec yarn watch + else + log_info "Starting frontend as npmuser ($PUID) ..." + s6-setuidgid npmuser yarn install + exec s6-setuidgid npmuser yarn watch + fi else exit 0 fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run index 044e4d30..30f3a71a 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run @@ -5,6 +5,10 @@ set -e . /bin/common.sh -log_info 'Starting nginx ...' - -exec s6-setuidgid npmuser nginx +if [ "$PUID" = '0' ]; then + log_info 'Starting nginx ...' + exec nginx +else + log_info "Starting nginx as npmuser ($PUID) ..." + exec s6-setuidgid npmuser nginx +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh index f8da7b8c..14dd6d28 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh @@ -3,23 +3,23 @@ set -e -PUID=${PUID:-911} -PGID=${PGID:-911} - -log_info 'Configuring npmuser ...' - -groupmod -g 1000 users || exit 1 - -if id -u npmuser; then - # user already exists - usermod -u "${PUID}" npmuser || exit 1 +if [ "$PUID" = '0' ]; then + log_info 'Skipping npmuser configuration' else - # Add npmuser user - useradd -u "${PUID}" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1 -fi + log_info 'Configuring npmuser ...' + groupmod -g 1000 users || exit 1 -usermod -G users npmuser || exit 1 -groupmod -o -g "${PGID}" npmuser || exit 1 -# Home for npmuser -mkdir -p /tmp/npmuserhome -chown -R npmuser:npmuser /tmp/npmuserhome + if id -u npmuser; then + # user already exists + usermod -u "$PUID" npmuser || exit 1 + else + # Add npmuser user + useradd -u "$PUID" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1 + fi + + usermod -G users npmuser || exit 1 + groupmod -o -g "$PGID" npmuser || exit 1 + # Home for npmuser + mkdir -p /tmp/npmuserhome + chown -R npmuser:npmuser /tmp/npmuserhome +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh index 3f5c481d..684166e1 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh @@ -9,16 +9,16 @@ log_info 'Setting ownership ...' chown root /tmp/nginx # npmuser -chown -R npmuser:npmuser /data -chown -R npmuser:npmuser /etc/letsencrypt -chown -R npmuser:npmuser /run/nginx -chown -R npmuser:npmuser /tmp/nginx -chown -R npmuser:npmuser /var/cache/nginx -chown -R npmuser:npmuser /var/lib/logrotate -chown -R npmuser:npmuser /var/lib/nginx -chown -R npmuser:npmuser /var/log/nginx +chown -R "$PUID:$PGID" /data \ + /etc/letsencrypt \ + /run/nginx \ + /tmp/nginx \ + /var/cache/nginx \ + /var/lib/logrotate \ + /var/lib/nginx \ + /var/log/nginx # Don't chown entire /etc/nginx folder as this causes crashes on some systems -chown -R npmuser:npmuser /etc/nginx/nginx -chown -R npmuser:npmuser /etc/nginx/nginx.conf -chown -R npmuser:npmuser /etc/nginx/conf.d +chown -R "$PUID:$PGID" /etc/nginx/nginx \ + /etc/nginx/nginx.conf \ + /etc/nginx/conf.d diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh index af51b46d..ae3ad00f 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh @@ -10,8 +10,10 @@ echo "------------------------------------- | \| | |_) | |\/| | | |\ | __/| | | | |_| \_|_| |_| |_| -------------------------------------- -User UID: $(id -u npmuser) -User GID: $(id -g npmuser) -------------------------------------- -" +-------------------------------------" +if [[ "$PUID" -ne '0' ]]; then + echo "User UID: $(id -u npmuser)" + echo "User GID: $(id -g npmuser)" + echo "-------------------------------------" +fi +echo From dad8561ea15beb43d047eaca01217de7abdd387e Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 10:20:20 +1000 Subject: [PATCH 02/10] Use numbers for permissions in case npmuser doesn't exist --- docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh | 2 +- docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh index 14dd6d28..a749ca2b 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh @@ -21,5 +21,5 @@ else groupmod -o -g "$PGID" npmuser || exit 1 # Home for npmuser mkdir -p /tmp/npmuserhome - chown -R npmuser:npmuser /tmp/npmuserhome + chown -R "$PUID:$PGID" /tmp/npmuserhome fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh index bcd64d25..bc27eb14 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh @@ -29,7 +29,7 @@ process_folder () { done # ensure the files are still owned by the npmuser - chown -R npmuser:npmuser "$1" + chown -R "$PUID:$PGID" "$1" } process_folder /etc/nginx/conf.d From 4a86bb42cc9a44c8d0a48b5ecd9f7290a47401d3 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 11:19:16 +1000 Subject: [PATCH 03/10] Different approach, always create npmuser even if the user id is zero, and then we'll always use it --- docker/rootfs/etc/nginx/nginx.conf | 1 + .../rootfs/etc/s6-overlay/s6-rc.d/backend/run | 23 ++++---------- .../etc/s6-overlay/s6-rc.d/frontend/run | 14 +++------ .../rootfs/etc/s6-overlay/s6-rc.d/nginx/run | 9 ++---- .../s6-overlay/s6-rc.d/prepare/10-npmuser.sh | 31 ++++++++----------- .../s6-overlay/s6-rc.d/prepare/90-banner.sh | 16 +++++----- 6 files changed, 33 insertions(+), 61 deletions(-) diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 438c1bd4..c2ee97cc 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -1,6 +1,7 @@ # run nginx in foreground daemon off; pid /run/nginx/nginx.pid; +user npmuser; # Set number of worker processes automatically based on number of CPU cores. worker_processes auto; diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run index 2f9fa9f6..e8ffa17c 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run @@ -7,26 +7,15 @@ set -e cd /app || exit 1 -if [ "${DEVELOPMENT:-}" = "true" ]; then - if [ "$PUID" = '0' ]; then - log_info 'Starting backend development ...' - yarn install - node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js - else - log_info "Starting backend development as npmuser ($PUID) ..." - s6-setuidgid npmuser yarn install - exec s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js' - fi +log_info 'Starting backend ...' + +if [ "${DEVELOPMENT:-}" = 'true' ]; then + s6-setuidgid npmuser yarn install + exec s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js' else while : do - if [ "$PUID" = '0' ]; then - log_info 'Starting backend ...' - node --abort_on_uncaught_exception --max_old_space_size=250 index.js - else - log_info "Starting backend as npmuser ($PUID) ..." - s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --abort_on_uncaught_exception --max_old_space_size=250 index.js' - fi + s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --abort_on_uncaught_exception --max_old_space_size=250 index.js' sleep 1 done fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run index 19db5733..1181c53e 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run @@ -5,7 +5,7 @@ set -e # This service is DEVELOPMENT only. -if [ "$DEVELOPMENT" == "true" ]; then +if [ "$DEVELOPMENT" = 'true' ]; then . /bin/common.sh cd /app/frontend || exit 1 HOME=/tmp/npmuserhome @@ -13,15 +13,9 @@ if [ "$DEVELOPMENT" == "true" ]; then mkdir -p /app/frontend/dist chown -R "$PUID:$PGID" /app/frontend/dist - if [ "$PUID" = '0' ]; then - log_info 'Starting frontend ...' - yarn install - exec yarn watch - else - log_info "Starting frontend as npmuser ($PUID) ..." - s6-setuidgid npmuser yarn install - exec s6-setuidgid npmuser yarn watch - fi + log_info 'Starting frontend ...' + s6-setuidgid npmuser yarn install + exec s6-setuidgid npmuser yarn watch else exit 0 fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run index 30f3a71a..fa8c1fc5 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run @@ -5,10 +5,5 @@ set -e . /bin/common.sh -if [ "$PUID" = '0' ]; then - log_info 'Starting nginx ...' - exec nginx -else - log_info "Starting nginx as npmuser ($PUID) ..." - exec s6-setuidgid npmuser nginx -fi +log_info 'Starting nginx ...' +exec s6-setuidgid npmuser nginx diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh index a749ca2b..c5cf5435 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh @@ -3,23 +3,18 @@ set -e -if [ "$PUID" = '0' ]; then - log_info 'Skipping npmuser configuration' +log_info 'Configuring npmuser ...' + +if id -u npmuser; then + # user already exists + usermod -u "$PUID" npmuser || exit 1 else - log_info 'Configuring npmuser ...' - groupmod -g 1000 users || exit 1 - - if id -u npmuser; then - # user already exists - usermod -u "$PUID" npmuser || exit 1 - else - # Add npmuser user - useradd -u "$PUID" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1 - fi - - usermod -G users npmuser || exit 1 - groupmod -o -g "$PGID" npmuser || exit 1 - # Home for npmuser - mkdir -p /tmp/npmuserhome - chown -R "$PUID:$PGID" /tmp/npmuserhome + # Add npmuser user + useradd -o -u "$PUID" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1 fi + +usermod -G "$PGID" npmuser || exit 1 +groupmod -o -g "$PGID" npmuser || exit 1 +# Home for npmuser +mkdir -p /tmp/npmuserhome +chown -R "$PUID:$PGID" /tmp/npmuserhome diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh index ae3ad00f..7991ddf4 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh @@ -3,17 +3,15 @@ set -e -echo -echo "------------------------------------- +echo " +------------------------------------- _ _ ____ __ __ | \ | | _ \| \/ | | \| | |_) | |\/| | | |\ | __/| | | | |_| \_|_| |_| |_| --------------------------------------" -if [[ "$PUID" -ne '0' ]]; then - echo "User UID: $(id -u npmuser)" - echo "User GID: $(id -g npmuser)" - echo "-------------------------------------" -fi -echo +------------------------------------- +User ID: $PUID +Group ID: $PGID +------------------------------------- +" From 5d03ede100d74aed2f441ee7e17c1c56a496966f Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 12:44:28 +1000 Subject: [PATCH 04/10] Add test for creating a host --- backend/doc/api.swagger.json | 588 ++++++++++++++------- test/cypress/integration/api/Hosts.spec.js | 48 ++ 2 files changed, 443 insertions(+), 193 deletions(-) create mode 100644 test/cypress/integration/api/Hosts.spec.js diff --git a/backend/doc/api.swagger.json b/backend/doc/api.swagger.json index 06c02564..3fa19fc4 100644 --- a/backend/doc/api.swagger.json +++ b/backend/doc/api.swagger.json @@ -40,6 +40,210 @@ } } }, + "/nginx/proxy-hosts": { + "get": { + "operationId": "getProxyHosts", + "summary": "Get all proxy hosts", + "tags": ["Proxy Hosts"], + "security": [ + { + "BearerAuth": ["users"] + } + ], + "parameters": [ + { + "in": "query", + "name": "expand", + "description": "Expansions", + "schema": { + "type": "string", + "enum": ["access_list", "owner", "certificate"] + } + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": [ + { + "id": 1, + "created_on": "2023-03-30T01:12:23.000Z", + "modified_on": "2023-03-30T02:15:40.000Z", + "owner_user_id": 1, + "domain_names": ["aasdasdad"], + "forward_host": "asdasd", + "forward_port": 80, + "access_list_id": 0, + "certificate_id": 0, + "ssl_forced": 0, + "caching_enabled": 0, + "block_exploits": 0, + "advanced_config": "sdfsdfsdf", + "meta": { + "letsencrypt_agree": false, + "dns_challenge": false, + "nginx_online": false, + "nginx_err": "Command failed: /usr/sbin/nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /etc/nginx/nginx.conf test failed\n" + }, + "allow_websocket_upgrade": 0, + "http2_support": 0, + "forward_scheme": "http", + "enabled": 1, + "locations": [], + "hsts_enabled": 0, + "hsts_subdomains": 0, + "owner": { + "id": 1, + "created_on": "2023-03-30T01:11:50.000Z", + "modified_on": "2023-03-30T01:11:50.000Z", + "is_deleted": 0, + "is_disabled": 0, + "email": "admin@example.com", + "name": "Administrator", + "nickname": "Admin", + "avatar": "", + "roles": ["admin"] + }, + "access_list": null, + "certificate": null + }, + { + "id": 2, + "created_on": "2023-03-30T02:11:49.000Z", + "modified_on": "2023-03-30T02:11:49.000Z", + "owner_user_id": 1, + "domain_names": ["test.example.com"], + "forward_host": "1.1.1.1", + "forward_port": 80, + "access_list_id": 0, + "certificate_id": 0, + "ssl_forced": 0, + "caching_enabled": 0, + "block_exploits": 0, + "advanced_config": "", + "meta": { + "letsencrypt_agree": false, + "dns_challenge": false, + "nginx_online": true, + "nginx_err": null + }, + "allow_websocket_upgrade": 0, + "http2_support": 0, + "forward_scheme": "http", + "enabled": 1, + "locations": [], + "hsts_enabled": 0, + "hsts_subdomains": 0, + "owner": { + "id": 1, + "created_on": "2023-03-30T01:11:50.000Z", + "modified_on": "2023-03-30T01:11:50.000Z", + "is_deleted": 0, + "is_disabled": 0, + "email": "admin@example.com", + "name": "Administrator", + "nickname": "Admin", + "avatar": "", + "roles": ["admin"] + }, + "access_list": null, + "certificate": null + } + ] + } + }, + "schema": { + "$ref": "#/components/schemas/ProxyHostsList" + } + } + } + } + } + }, + "post": { + "operationId": "createProxyHost", + "summary": "Create a Proxy Host", + "tags": ["Proxy Hosts"], + "security": [ + { + "BearerAuth": ["users"] + } + ], + "parameters": [ + { + "in": "body", + "name": "proxyhost", + "description": "Proxy Host Payload", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProxyHostObject" + } + } + ], + "responses": { + "201": { + "description": "201 response", + "content": { + "application/json": { + "examples": { + "default": { + "value": { + "id": 3, + "created_on": "2023-03-30T02:31:27.000Z", + "modified_on": "2023-03-30T02:31:27.000Z", + "owner_user_id": 1, + "domain_names": ["test2.example.com"], + "forward_host": "1.1.1.1", + "forward_port": 80, + "access_list_id": 0, + "certificate_id": 0, + "ssl_forced": 0, + "caching_enabled": 0, + "block_exploits": 0, + "advanced_config": "", + "meta": { + "letsencrypt_agree": false, + "dns_challenge": false + }, + "allow_websocket_upgrade": 0, + "http2_support": 0, + "forward_scheme": "http", + "enabled": 1, + "locations": [], + "hsts_enabled": 0, + "hsts_subdomains": 0, + "certificate": null, + "owner": { + "id": 1, + "created_on": "2023-03-30T01:11:50.000Z", + "modified_on": "2023-03-30T01:11:50.000Z", + "is_deleted": 0, + "is_disabled": 0, + "email": "admin@example.com", + "name": "Administrator", + "nickname": "Admin", + "avatar": "", + "roles": ["admin"] + }, + "access_list": null, + "use_default_location": true, + "ipv6": true + } + } + }, + "schema": { + "$ref": "#/components/schemas/ProxyHostObject" + } + } + } + } + } + } + }, "/schema": { "get": { "operationId": "schema", @@ -55,14 +259,10 @@ "get": { "operationId": "refreshToken", "summary": "Refresh your access token", - "tags": [ - "Tokens" - ], + "tags": ["Tokens"], "security": [ { - "BearerAuth": [ - "tokens" - ] + "BearerAuth": ["tokens"] } ], "responses": { @@ -104,19 +304,14 @@ "scope": { "minLength": 1, "type": "string", - "enum": [ - "user" - ] + "enum": ["user"] }, "secret": { "minLength": 1, "type": "string" } }, - "required": [ - "identity", - "secret" - ], + "required": ["identity", "secret"], "type": "object" } } @@ -144,23 +339,17 @@ } }, "summary": "Request a new access token from credentials", - "tags": [ - "Tokens" - ] + "tags": ["Tokens"] } }, "/settings": { "get": { "operationId": "getSettings", "summary": "Get all settings", - "tags": [ - "Settings" - ], + "tags": ["Settings"], "security": [ { - "BearerAuth": [ - "settings" - ] + "BearerAuth": ["settings"] } ], "responses": { @@ -194,14 +383,10 @@ "get": { "operationId": "getSetting", "summary": "Get a setting", - "tags": [ - "Settings" - ], + "tags": ["Settings"], "security": [ { - "BearerAuth": [ - "settings" - ] + "BearerAuth": ["settings"] } ], "parameters": [ @@ -244,14 +429,10 @@ "put": { "operationId": "updateSetting", "summary": "Update a setting", - "tags": [ - "Settings" - ], + "tags": ["Settings"], "security": [ { - "BearerAuth": [ - "settings" - ] + "BearerAuth": ["settings"] } ], "parameters": [ @@ -305,14 +486,10 @@ "get": { "operationId": "getUsers", "summary": "Get all users", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -322,9 +499,7 @@ "description": "Expansions", "schema": { "type": "string", - "enum": [ - "permissions" - ] + "enum": ["permissions"] } } ], @@ -345,9 +520,7 @@ "name": "Jamie Curnow", "nickname": "James", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": [ - "admin" - ] + "roles": ["admin"] } ] }, @@ -362,9 +535,7 @@ "name": "Jamie Curnow", "nickname": "James", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": [ - "admin" - ], + "roles": ["admin"], "permissions": { "visibility": "all", "proxy_hosts": "manage", @@ -389,14 +560,10 @@ "post": { "operationId": "createUser", "summary": "Create a User", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -426,9 +593,7 @@ "name": "Jamie Curnow", "nickname": "James", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": [ - "admin" - ], + "roles": ["admin"], "permissions": { "visibility": "all", "proxy_hosts": "manage", @@ -454,14 +619,10 @@ "get": { "operationId": "getUser", "summary": "Get a user", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -501,9 +662,7 @@ "name": "Jamie Curnow", "nickname": "James", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": [ - "admin" - ] + "roles": ["admin"] } } }, @@ -518,14 +677,10 @@ "put": { "operationId": "updateUser", "summary": "Update a User", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -574,9 +729,7 @@ "name": "Jamie Curnow", "nickname": "James", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": [ - "admin" - ] + "roles": ["admin"] } } }, @@ -591,14 +744,10 @@ "delete": { "operationId": "deleteUser", "summary": "Delete a User", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -637,14 +786,10 @@ "put": { "operationId": "updateUserAuth", "summary": "Update a User's Authentication", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -700,14 +845,10 @@ "put": { "operationId": "updateUserPermissions", "summary": "Update a User's Permissions", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -755,14 +896,10 @@ "put": { "operationId": "loginAsUser", "summary": "Login as this user", - "tags": [ - "Users" - ], + "tags": ["Users"], "security": [ { - "BearerAuth": [ - "users" - ] + "BearerAuth": ["users"] } ], "parameters": [ @@ -797,9 +934,7 @@ "name": "Jamie Curnow", "nickname": "James", "avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm", - "roles": [ - "admin" - ] + "roles": ["admin"] } } } @@ -807,11 +942,7 @@ "schema": { "type": "object", "description": "Login object", - "required": [ - "expires", - "token", - "user" - ], + "required": ["expires", "token", "user"], "additionalProperties": false, "properties": { "expires": { @@ -840,14 +971,10 @@ "get": { "operationId": "reportsHosts", "summary": "Report on Host Statistics", - "tags": [ - "Reports" - ], + "tags": ["Reports"], "security": [ { - "BearerAuth": [ - "reports" - ] + "BearerAuth": ["reports"] } ], "responses": { @@ -878,14 +1005,10 @@ "get": { "operationId": "getAuditLog", "summary": "Get Audit Log", - "tags": [ - "Audit Log" - ], + "tags": ["Audit Log"], "security": [ { - "BearerAuth": [ - "audit-log" - ] + "BearerAuth": ["audit-log"] } ], "responses": { @@ -925,10 +1048,7 @@ "type": "object", "description": "Health object", "additionalProperties": false, - "required": [ - "status", - "version" - ], + "required": ["status", "version"], "properties": { "status": { "type": "string", @@ -944,11 +1064,7 @@ "revision": 0 }, "additionalProperties": false, - "required": [ - "major", - "minor", - "revision" - ], + "required": ["major", "minor", "revision"], "properties": { "major": { "type": "integer", @@ -969,10 +1085,7 @@ "TokenObject": { "type": "object", "description": "Token object", - "required": [ - "expires", - "token" - ], + "required": ["expires", "token"], "additionalProperties": false, "properties": { "expires": { @@ -988,16 +1101,147 @@ } } }, + "ProxyHostObject": { + "type": "object", + "description": "Proxy Host object", + "required": [ + "id", + "created_on", + "modified_on", + "owner_user_id", + "domain_names", + "forward_host", + "forward_port", + "access_list_id", + "certificate_id", + "ssl_forced", + "caching_enabled", + "block_exploits", + "advanced_config", + "meta", + "allow_websocket_upgrade", + "http2_support", + "forward_scheme", + "enabled", + "locations", + "hsts_enabled", + "hsts_subdomains", + "certificate", + "use_default_location", + "ipv6" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "integer", + "description": "Proxy Host ID", + "minimum": 1, + "example": 1 + }, + "created_on": { + "type": "string", + "description": "Created Date", + "example": "2020-01-30T09:36:08.000Z" + }, + "modified_on": { + "type": "string", + "description": "Modified Date", + "example": "2020-01-30T09:41:04.000Z" + }, + "owner_user_id": { + "type": "integer", + "minimum": 1, + "example": 1 + }, + "domain_names": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "forward_host": { + "type": "string", + "minLength": 1 + }, + "forward_port": { + "type": "integer", + "minimum": 1 + }, + "access_list_id": { + "type": "integer" + }, + "certificate_id": { + "type": "integer" + }, + "ssl_forced": { + "type": "integer" + }, + "caching_enabled": { + "type": "integer" + }, + "block_exploits": { + "type": "integer" + }, + "advanced_config": { + "type": "string" + }, + "meta": { + "type": "object" + }, + "allow_websocket_upgrade": { + "type": "integer" + }, + "http2_support": { + "type": "integer" + }, + "forward_scheme": { + "type": "string" + }, + "enabled": { + "type": "integer" + }, + "locations": { + "type": "array" + }, + "hsts_enabled": { + "type": "integer" + }, + "hsts_subdomains": { + "type": "integer" + }, + "certificate": { + "type": "object", + "nullable": true + }, + "owner": { + "type": "object", + "nullable": true + }, + "access_list": { + "type": "object", + "nullable": true + }, + "use_default_location": { + "type": "boolean" + }, + "ipv6": { + "type": "boolean" + } + } + }, + "ProxyHostsList": { + "type": "array", + "description": "Proxyn Hosts list", + "items": { + "$ref": "#/components/schemas/ProxyHostObject" + } + }, "SettingObject": { "type": "object", "description": "Setting object", - "required": [ - "id", - "name", - "description", - "value", - "meta" - ], + "required": ["id", "name", "description", "value", "meta"], "additionalProperties": false, "properties": { "id": { @@ -1057,17 +1301,7 @@ "UserObject": { "type": "object", "description": "User object", - "required": [ - "id", - "created_on", - "modified_on", - "is_disabled", - "email", - "name", - "nickname", - "avatar", - "roles" - ], + "required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"], "additionalProperties": false, "properties": { "id": { @@ -1117,9 +1351,7 @@ }, "roles": { "description": "Roles applied", - "example": [ - "admin" - ], + "example": ["admin"], "type": "array", "items": { "type": "string" @@ -1137,10 +1369,7 @@ "AuthObject": { "type": "object", "description": "Authentication Object", - "required": [ - "type", - "secret" - ], + "required": ["type", "secret"], "properties": { "type": { "type": "string", @@ -1167,64 +1396,37 @@ "visibility": { "type": "string", "description": "Visibility Type", - "enum": [ - "all", - "user" - ] + "enum": ["all", "user"] }, "access_lists": { "type": "string", "description": "Access Lists Permissions", - "enum": [ - "hidden", - "view", - "manage" - ] + "enum": ["hidden", "view", "manage"] }, "dead_hosts": { "type": "string", "description": "404 Hosts Permissions", - "enum": [ - "hidden", - "view", - "manage" - ] + "enum": ["hidden", "view", "manage"] }, "proxy_hosts": { "type": "string", "description": "Proxy Hosts Permissions", - "enum": [ - "hidden", - "view", - "manage" - ] + "enum": ["hidden", "view", "manage"] }, "redirection_hosts": { "type": "string", "description": "Redirection Permissions", - "enum": [ - "hidden", - "view", - "manage" - ] + "enum": ["hidden", "view", "manage"] }, "streams": { "type": "string", "description": "Streams Permissions", - "enum": [ - "hidden", - "view", - "manage" - ] + "enum": ["hidden", "view", "manage"] }, "certificates": { "type": "string", "description": "Certificates Permissions", - "enum": [ - "hidden", - "view", - "manage" - ] + "enum": ["hidden", "view", "manage"] } } }, @@ -1251,4 +1453,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/cypress/integration/api/Hosts.spec.js b/test/cypress/integration/api/Hosts.spec.js new file mode 100644 index 00000000..4dffe31b --- /dev/null +++ b/test/cypress/integration/api/Hosts.spec.js @@ -0,0 +1,48 @@ +/// + +describe('Hosts endpoints', () => { + let token; + + before(() => { + cy.getToken().then((tok) => { + token = tok; + }); + }); + + it('Should be able to create a http host', function() { + cy.task('backendApiPost', { + token: token, + path: '/api/nginx/proxy-hosts', + data: { + domain_names: ['test.example.com'], + forward_scheme: 'http', + forward_host: '1.1.1.1', + forward_port: 80, + access_list_id: '0', + certificate_id: 0, + meta: { + letsencrypt_agree: false, + dns_challenge: false + }, + advanced_config: '', + locations: [], + block_exploits: false, + caching_enabled: false, + allow_websocket_upgrade: false, + http2_support: false, + hsts_enabled: false, + hsts_subdomains: false, + ssl_forced: false + } + }).then((data) => { + cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data); + expect(data).to.have.property('id'); + expect(data.id).to.be.greaterThan(0); + expect(data).to.have.property('enabled'); + expect(data.enabled).to.be.greaterThan(0); + expect(data).to.have.property('meta'); + expect(data.meta.nginx_online).not.to.exist(); + }); + }); + +}); From 8a4a7d0caf0b88e62e8d329d4e0844fa032071c6 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 12:51:26 +1000 Subject: [PATCH 05/10] Allow 201 as success in test result --- test/cypress/plugins/backendApi/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/plugins/backendApi/client.js b/test/cypress/plugins/backendApi/client.js index 4de39818..29684cfd 100644 --- a/test/cypress/plugins/backendApi/client.js +++ b/test/cypress/plugins/backendApi/client.js @@ -126,7 +126,7 @@ BackendApi.prototype._putPostJson = function(fn, path, data, returnOnError) { logger('Response data:', data); if (!returnOnError && data instanceof Error) { reject(data); - } else if (!returnOnError && response.statusCode != 200) { + } else if (!returnOnError && (response.statusCode < 200 || response.statusCode >= 300)) { if (typeof data === 'object' && typeof data.error === 'object' && typeof data.error.message !== 'undefined') { reject(new Error(data.error.code + ': ' + data.error.message)); } else { From 308a7149ede7b10786ca04489d933ffcb99bb901 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 12:55:20 +1000 Subject: [PATCH 06/10] Tweak test --- test/cypress/integration/api/Hosts.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/integration/api/Hosts.spec.js b/test/cypress/integration/api/Hosts.spec.js index 4dffe31b..a25c7d95 100644 --- a/test/cypress/integration/api/Hosts.spec.js +++ b/test/cypress/integration/api/Hosts.spec.js @@ -41,7 +41,7 @@ describe('Hosts endpoints', () => { expect(data).to.have.property('enabled'); expect(data.enabled).to.be.greaterThan(0); expect(data).to.have.property('meta'); - expect(data.meta.nginx_online).not.to.exist(); + expect(typeof data.meta.nginx_online).not.be.equal('undefined'); }); }); From 9225d5d442f269a5358b76fe9a1c441745d7150d Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 13:00:22 +1000 Subject: [PATCH 07/10] Tweak test --- test/cypress/integration/api/Hosts.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/integration/api/Hosts.spec.js b/test/cypress/integration/api/Hosts.spec.js index a25c7d95..4652c8e0 100644 --- a/test/cypress/integration/api/Hosts.spec.js +++ b/test/cypress/integration/api/Hosts.spec.js @@ -41,7 +41,7 @@ describe('Hosts endpoints', () => { expect(data).to.have.property('enabled'); expect(data.enabled).to.be.greaterThan(0); expect(data).to.have.property('meta'); - expect(typeof data.meta.nginx_online).not.be.equal('undefined'); + expect(typeof data.meta.nginx_online).to.be.equal('undefined'); }); }); From eb2e2e0478609dbbb32fffb36e0d88798a90b21c Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 14:44:15 +1000 Subject: [PATCH 08/10] Throw in a docker restart during testing phase --- Jenkinsfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index cb597eba..9fd2ce21 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -91,10 +91,14 @@ pipeline { // 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' - sh 'docker-compose up cypress-sqlite' + sh 'docker-compose` cypress-sqlite' // Get results sh 'docker cp -L "$(docker-compose ps --all -q cypress-sqlite):/test/results" test/' } From d9b9af543e33439e8d4758b9c8dbff46b0853307 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 15:03:57 +1000 Subject: [PATCH 09/10] Fix text replacement whoops --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9fd2ce21..862b2470 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -98,7 +98,7 @@ pipeline { // Run tests sh 'rm -rf test/results' - sh 'docker-compose` cypress-sqlite' + sh 'docker-compose up cypress-sqlite' // Get results sh 'docker cp -L "$(docker-compose ps --all -q cypress-sqlite):/test/results" test/' } From 9fe07fa6c328a4e756480adffc19ca8ed82da1ff Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 30 Mar 2023 15:37:59 +1000 Subject: [PATCH 10/10] Update documentation --- docs/advanced-config/README.md | 21 +++++++++++++++++++++ docs/setup/README.md | 5 +---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/advanced-config/README.md b/docs/advanced-config/README.md index 7cb8a3a9..a0acdda1 100644 --- a/docs/advanced-config/README.md +++ b/docs/advanced-config/README.md @@ -1,5 +1,26 @@ # Advanced Configuration +## Running processes as a user/group + +By default, the services (nginx etc) will run as `root` user inside the docker container. +You can change this behaviour by setting the following environment variables. +Not only will they run the services as this user/group, they will change the ownership +on the `data` and `letsencrypt` folders at startup. + +```yml +services: + app: + image: 'jc21/nginx-proxy-manager:latest' + environment: + PUID: 1000 + PGID: 1000 + # ... +``` + +This may have the side effect of a failed container start due to permission denied trying +to open port 80 on some systems. The only course to fix that is to remove the variables +and run as the default root user. + ## Best Practice: Use a Docker network For those who have a few of their upstream services running in Docker on the same Docker diff --git a/docs/setup/README.md b/docs/setup/README.md index a78d79e4..032b714c 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -64,9 +64,6 @@ services: # Add any other Stream port you want to expose # - '21:21' # FTP environment: - # Unix user and group IDs, optional - PUID: 1000 - PGID: 1000 # Mysql/Maria connection parameters: DB_MYSQL_HOST: "db" DB_MYSQL_PORT: 3306 @@ -90,7 +87,7 @@ services: MYSQL_USER: 'npm' MYSQL_PASSWORD: 'npm' volumes: - - ./data/mysql:/var/lib/mysql + - ./mysql:/var/lib/mysql ``` ::: warning