fix: validate nginx templates as the running nginx user
The wrapper config used to run `nginx -t` against an app's rendered `nginx.conf` declared no `user` directive, so nginx fell back to its compile-time default and chowned every `proxy_cache_path` directory to that user, including directories that already existed. On Debian and Ubuntu that default is `nobody`, so deploying an app shipping a custom `nginx.conf.sigil` took the running server's live cache directory away for the length of the build and turned every request the cache would serve into an HTTP 500, healing only once the deploy's own reload restored the ownership. The wrapper now declares the same `user` as the host's main nginx config, which is also exposed to custom `validate-config` templates as `$.NGINX_USER`.
This commit is contained in:
@@ -409,12 +409,6 @@ fn-nginx-vhosts-pre-validate-custom-template() {
|
||||
|
||||
local TMP_RENDERED="$TMP_WORK_DIR/nginx.conf"
|
||||
local TMP_WRAPPER="$TMP_WORK_DIR/wrapper.conf"
|
||||
local VALIDATE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/validate.conf.sigil"
|
||||
local CUSTOM_VALIDATE_TEMPLATE
|
||||
CUSTOM_VALIDATE_TEMPLATE="$(plugn trigger nginx-app-template-source "$APP" "validate-config")"
|
||||
if [[ -n "$CUSTOM_VALIDATE_TEMPLATE" ]]; then
|
||||
VALIDATE_TEMPLATE="$CUSTOM_VALIDATE_TEMPLATE"
|
||||
fi
|
||||
|
||||
dokku_log_info1 "Pre-validating custom nginx.conf.sigil"
|
||||
|
||||
@@ -424,7 +418,7 @@ fn-nginx-vhosts-pre-validate-custom-template() {
|
||||
dokku_log_fail "Custom nginx.conf.sigil failed to render via sigil"
|
||||
fi
|
||||
|
||||
sigil -f "$VALIDATE_TEMPLATE" NGINX_CONF="$TMP_RENDERED" | cat -s >"$TMP_WRAPPER"
|
||||
fn-nginx-vhosts-render-validate-wrapper "$APP" "$TMP_RENDERED" "$TMP_WRAPPER"
|
||||
|
||||
if ! sudo "$NGINX_LOCATION" -t -c "$TMP_WRAPPER" 2>/dev/null; then
|
||||
dokku_log_warn "Failed to validate custom nginx.conf.sigil. Rendered output below..."
|
||||
|
||||
@@ -37,12 +37,11 @@ trigger-nginx-vhosts-install() {
|
||||
fn-plugin-property-setup "nginx"
|
||||
|
||||
NGINX_BIN="$(fn-nginx-vhosts-nginx-location)"
|
||||
NGINX_ROOT="/etc/nginx"
|
||||
NGINX_ROOT="$(fn-nginx-vhosts-nginx-conf-root)"
|
||||
NGINX_LOG_ROOT="/var/log/nginx"
|
||||
NGINX_INIT_NAME="nginx"
|
||||
NGINX_SUDOERS_FILE="/etc/sudoers.d/dokku-nginx"
|
||||
if fn-nginx-vhosts-uses-openresty; then
|
||||
NGINX_ROOT="/usr/local/openresty/nginx/conf"
|
||||
NGINX_LOG_ROOT="/var/log/openresty"
|
||||
NGINX_INIT_NAME="openresty"
|
||||
NGINX_SUDOERS_FILE="/etc/sudoers.d/dokku-openresty"
|
||||
|
||||
@@ -686,6 +686,58 @@ fn-nginx-vhosts-uses-openresty() {
|
||||
return 1
|
||||
}
|
||||
|
||||
fn-nginx-vhosts-nginx-conf-root() {
|
||||
declare desc="returns the directory holding the main nginx config"
|
||||
local NGINX_CONF_ROOT="/etc/nginx"
|
||||
|
||||
fn-nginx-vhosts-uses-openresty && NGINX_CONF_ROOT="/usr/local/openresty/nginx/conf"
|
||||
echo "$NGINX_CONF_ROOT"
|
||||
}
|
||||
|
||||
fn-nginx-vhosts-nginx-user() {
|
||||
declare desc="returns the user declared by an nginx config, if any"
|
||||
declare NGINX_CONF_PATH="$1"
|
||||
|
||||
if [[ ! -r "$NGINX_CONF_PATH" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
awk '
|
||||
{ sub(/#.*/, "") }
|
||||
$1 == "user" && NF >= 2 {
|
||||
value = $2
|
||||
sub(/;.*$/, "", value)
|
||||
gsub(/"/, "", value)
|
||||
if (value != "") {
|
||||
print value
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "$NGINX_CONF_PATH"
|
||||
}
|
||||
|
||||
fn-nginx-vhosts-validation-user() {
|
||||
declare desc="returns the user to declare in the wrapper config used for nginx -t validation"
|
||||
local NGINX_CONF_PATH NGINX_USER
|
||||
|
||||
NGINX_CONF_PATH="$(fn-nginx-vhosts-nginx-conf-root)/nginx.conf"
|
||||
if [[ -r "$NGINX_CONF_PATH" ]]; then
|
||||
NGINX_USER="$(fn-nginx-vhosts-nginx-user "$NGINX_CONF_PATH")"
|
||||
else
|
||||
NGINX_USER="www-data"
|
||||
fi
|
||||
|
||||
if [[ ! "$NGINX_USER" =~ ^[a-zA-Z_][a-zA-Z0-9_.-]*$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! id -u "$NGINX_USER" &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "$NGINX_USER"
|
||||
}
|
||||
|
||||
fn-nginx-vhosts-nginx-location() {
|
||||
declare desc="check that nginx is at the expected location and return it"
|
||||
local NGINX_LOCATION
|
||||
@@ -839,19 +891,48 @@ nginx_vhosts_validate_single_func() {
|
||||
fi
|
||||
}
|
||||
|
||||
nginx_vhosts_is_valid_nginx_config_func() {
|
||||
declare desc="checks if an app has a valid nginx config"
|
||||
declare APP="$1"
|
||||
fn-nginx-vhosts-backfill-validate-user() {
|
||||
declare desc="adds a user directive to a rendered wrapper config that declares none"
|
||||
declare OUTPUT_PATH="$1" NGINX_USER="$2"
|
||||
local WRAPPER_CONTENTS
|
||||
|
||||
if [[ -z "$NGINX_USER" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$(fn-nginx-vhosts-nginx-user "$OUTPUT_PATH")" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
WRAPPER_CONTENTS="$(cat "$OUTPUT_PATH")"
|
||||
printf 'user %s;\n%s\n' "$NGINX_USER" "$WRAPPER_CONTENTS" >"$OUTPUT_PATH"
|
||||
}
|
||||
|
||||
fn-nginx-vhosts-render-validate-wrapper() {
|
||||
declare desc="renders the wrapper config used to run nginx -t against an app nginx config"
|
||||
declare APP="$1" NGINX_CONF="$2" OUTPUT_PATH="$3"
|
||||
local VALIDATE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/validate.conf.sigil"
|
||||
local TMP_OUTPUT=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
|
||||
trap "rm -rf '$TMP_OUTPUT' >/dev/null" RETURN INT TERM EXIT
|
||||
local CUSTOM_VALIDATE_TEMPLATE NGINX_USER
|
||||
|
||||
CUSTOM_VALIDATE_TEMPLATE="$(plugn trigger nginx-app-template-source "$APP" "validate-config")"
|
||||
if [[ -n "$CUSTOM_VALIDATE_TEMPLATE" ]]; then
|
||||
VALIDATE_TEMPLATE="$CUSTOM_VALIDATE_TEMPLATE"
|
||||
fi
|
||||
|
||||
sigil -f "$VALIDATE_TEMPLATE" NGINX_CONF="$DOKKU_ROOT/$APP/nginx.conf" | cat -s >"$TMP_OUTPUT"
|
||||
NGINX_USER="$(fn-nginx-vhosts-validation-user)"
|
||||
sigil -f "$VALIDATE_TEMPLATE" NGINX_CONF="$NGINX_CONF" NGINX_USER="$NGINX_USER" | cat -s >"$OUTPUT_PATH"
|
||||
fn-nginx-vhosts-backfill-validate-user "$OUTPUT_PATH" "$NGINX_USER"
|
||||
}
|
||||
|
||||
nginx_vhosts_is_valid_nginx_config_func() {
|
||||
declare desc="checks if an app has a valid nginx config"
|
||||
declare APP="$1"
|
||||
local NGINX_LOCATION
|
||||
local TMP_OUTPUT=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
|
||||
trap "rm -rf '$TMP_OUTPUT' >/dev/null" RETURN INT TERM EXIT
|
||||
|
||||
NGINX_LOCATION="$(fn-nginx-vhosts-nginx-location)"
|
||||
fn-nginx-vhosts-render-validate-wrapper "$APP" "$DOKKU_ROOT/$APP/nginx.conf" "$TMP_OUTPUT"
|
||||
sudo "$NGINX_LOCATION" -t -c "$TMP_OUTPUT" 2>/dev/null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{ if $.NGINX_USER }}user {{ $.NGINX_USER }};{{ end }}
|
||||
events { worker_connections 768; }
|
||||
http {
|
||||
access_log off;
|
||||
|
||||
57
plugins/nginx-vhosts/validate_template_test.go
Normal file
57
plugins/nginx-vhosts/validate_template_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package nginxvhosts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gliderlabs/sigil"
|
||||
_ "github.com/gliderlabs/sigil/builtin"
|
||||
)
|
||||
|
||||
func validateTemplatePath(t *testing.T) string {
|
||||
t.Helper()
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("runtime.Caller failed")
|
||||
}
|
||||
return filepath.Join(filepath.Dir(file), "templates", "validate.conf.sigil")
|
||||
}
|
||||
|
||||
func renderValidateTemplate(t *testing.T, nginxUser string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(validateTemplatePath(t))
|
||||
if err != nil {
|
||||
t.Fatalf("read template: %v", err)
|
||||
}
|
||||
|
||||
vars := map[string]interface{}{
|
||||
"NGINX_CONF": "/home/dokku/app/nginx.conf",
|
||||
"NGINX_USER": nginxUser,
|
||||
}
|
||||
buf, err := sigil.Execute(data, vars, "validate.conf.sigil")
|
||||
if err != nil {
|
||||
t.Fatalf("sigil.Execute: %v", err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func TestValidateTemplate_DeclaresNginxUser(t *testing.T) {
|
||||
out := renderValidateTemplate(t, "www-data")
|
||||
mustContain(t, out, "user www-data;")
|
||||
mustContain(t, out, "include /home/dokku/app/nginx.conf;")
|
||||
|
||||
userIndex := strings.Index(out, "user www-data;")
|
||||
eventsIndex := strings.Index(out, "events {")
|
||||
if userIndex > eventsIndex {
|
||||
t.Errorf("expected the user directive before the events block\n--- output ---\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTemplate_OmitsEmptyNginxUser(t *testing.T) {
|
||||
out := renderValidateTemplate(t, "")
|
||||
mustNotContain(t, out, "user ")
|
||||
mustContain(t, out, "include /home/dokku/app/nginx.conf;")
|
||||
}
|
||||
Reference in New Issue
Block a user