diff --git a/nginx-proxy/README.md b/nginx-proxy/README.md index 5051c7e..acda81f 100644 --- a/nginx-proxy/README.md +++ b/nginx-proxy/README.md @@ -29,7 +29,7 @@ htpasswd -c /etc/nginx/htpasswd/default username ### Wildcard Authentication (WordPress Multisite) -For WordPress multisite with subdomain configuration, you can use a single htpasswd file to protect both the main domain and all subdomains. +For WordPress multisite with subdomain configuration, the container's `VIRTUAL_HOST` contains both `domain.com` and `*.domain.com`. The `*.domain.com` entry gets its own server block, and a single `_wildcard.` htpasswd file protects it. #### Naming Convention @@ -39,52 +39,34 @@ Use the `_wildcard.` prefix: /etc/nginx/htpasswd/_wildcard.domain.com ``` -This file will apply HTTP auth to: -- `domain.com` (main domain) -- `*.domain.com` (all subdomains like `blog.domain.com`, `shop.domain.com`, etc.) +This file applies only to hosts that literally start with `*.`, i.e. the `*.domain.com` server block (all subdomains like `blog.domain.com`, `shop.domain.com` that are served by it). It does not apply to `domain.com` itself, which uses its exact file `/etc/nginx/htpasswd/domain.com`, and it never applies to a separately configured host such as a different site on `shop.domain.com`. + +There are no label-counting or multi-level TLD heuristics: `*.domain.co.in` maps to `_wildcard.domain.co.in` and `*.ms.dev.example.com` maps to `_wildcard.ms.dev.example.com`. #### Lookup Order -The template checks for htpasswd files in this order: +For each host, the template checks for htpasswd files in this order: + +1. **Exact match**: `/etc/nginx/htpasswd/` (e.g. `domain.com`) +2. **Wildcard**: `/etc/nginx/htpasswd/_wildcard.`, only when the host is `*.` +3. **Default**: `/etc/nginx/htpasswd/default` -1. **Exact match**: `/etc/nginx/htpasswd/blog.domain.com` -2. **Wildcard (3 parts)**: `/etc/nginx/htpasswd/_wildcard.domain.co.in` (for 4+ part domains only) -3. **Wildcard (2 parts)**: `/etc/nginx/htpasswd/_wildcard.example.com` (for 2-3 part domains, or fallback) -4. **Default**: `/etc/nginx/htpasswd/default` +| Host | Files checked | +|------|---------------| +| `example.com` | `example.com`, then `default` | +| `*.example.com` | `*.example.com`, then `_wildcard.example.com`, then `default` | +| `shop.example.com` (its own `VIRTUAL_HOST`) | `shop.example.com`, then `default` | +| `*.domain.co.in` | `*.domain.co.in`, then `_wildcard.domain.co.in`, then `default` | #### Example Setup ```bash -# Create wildcard htpasswd for WordPress multisite +# Protect a WordPress subdomain multisite (VIRTUAL_HOST=example.com,*.example.com) +htpasswd -c /etc/nginx/htpasswd/example.com admin htpasswd -c /etc/nginx/htpasswd/_wildcard.example.com admin - -# This protects: example.com, blog.example.com, shop.example.com, etc. - -# Optional: Override for a specific subdomain -htpasswd -c /etc/nginx/htpasswd/api.example.com api_user ``` -#### Multi-level TLDs - -Multi-level TLDs (e.g., `.co.in`, `.com.au`) are fully supported: - -| Host | Wildcard File Checked | -|------|----------------------| -| `blog.domain.co.in` (4 parts) | `_wildcard.domain.co.in` first, then `_wildcard.co.in` | -| `domain.co.in` (3 parts) | `_wildcard.co.in` | -| `blog.example.com` (3 parts) | `_wildcard.example.com` | -| `example.com` (2 parts) | `_wildcard.example.com` | - -```bash -# For domain.co.in multisite (multi-level TLD) -htpasswd -c /etc/nginx/htpasswd/_wildcard.domain.co.in admin - -# This will protect: -# - domain.co.in -# - blog.domain.co.in -# - shop.domain.co.in -# - etc. -``` +When auth is enabled, the ACL include follows the same mapping: a `*.` host uses `/etc/nginx/vhost.d/_wildcard._acl` (see below). --- @@ -96,6 +78,9 @@ Create ACL files to restrict access by IP: # Per-domain ACL /etc/nginx/vhost.d/example.com_acl +# ACL for a *.example.com host +/etc/nginx/vhost.d/_wildcard.example.com_acl + # Default ACL for all sites /etc/nginx/vhost.d/default_acl ``` @@ -165,5 +150,5 @@ services: image: wordpress environment: - VIRTUAL_HOST=example.com,*.example.com - # HTTP auth via /etc/nginx/htpasswd/_wildcard.example.com + # HTTP auth via /etc/nginx/htpasswd/example.com and /etc/nginx/htpasswd/_wildcard.example.com ``` diff --git a/nginx-proxy/nginx.tmpl b/nginx-proxy/nginx.tmpl index 25e9995..2cca9e8 100644 --- a/nginx-proxy/nginx.tmpl +++ b/nginx-proxy/nginx.tmpl @@ -22,6 +22,18 @@ {{ end }} +{{ define "acl" }} + {{ $aclKey := . }} + {{ if hasPrefix "*." . }} + {{ $aclKey = printf "_wildcard.%s" (trimPrefix "*." .) }} + {{ end }} + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" $aclKey)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" $aclKey }}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} +{{ end }} + {{ define "location" }} location {{ .Path }} { {{ if eq .Proto "uwsgi" }} @@ -35,117 +47,27 @@ proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}/; {{ end }} + {{/* _wildcard.X only covers the literal *.X host; X itself uses its exact file. */}} + {{ $authFile := "" }} + {{ $realm := printf "Restricted %s" .Host }} {{ if eq .Path "/ee-admin/mailhog/" }} - {{ if (exists (printf "/etc/nginx/htpasswd/default_admin_tools")) }} - auth_basic "Restricted {{ .Host }} Mailhog"; - auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/default_admin_tools") }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} + {{ $realm = printf "%s Mailhog" $realm }} + {{ if (exists "/etc/nginx/htpasswd/default_admin_tools") }} + {{ $authFile = "/etc/nginx/htpasswd/default_admin_tools" }} {{ else if (exists "/etc/nginx/htpasswd/default") }} - auth_basic "Restricted {{ .Host }} Mailhog"; - auth_basic_user_file /etc/nginx/htpasswd/default; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} + {{ $authFile = "/etc/nginx/htpasswd/default" }} {{ end }} {{ else if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{/* - Wildcard htpasswd support for WordPress Multisite. - Naming convention: _wildcard.domain.com applies to domain.com AND *.domain.com - Supports multi-level TLDs: _wildcard.domain.co.in works for domain.co.in AND *.domain.co.in - - Lookup order (after exact match check on line 56): - - For 4+ part domains: checks _wildcard.{last-3-parts}, then _wildcard.{last-2-parts}, then default - - For 2-3 part domains: checks _wildcard.{last-2-parts}, then falls back to default - - For single-part hostnames: uses default only - - Note: Uses sprig's splitList and sub functions (available in docker-gen 0.7.4+) - */}} - {{ else }} - {{ $hostParts := splitList "." .Host }} - {{ $partsLen := len $hostParts }} - {{/* For 4+ part domains, check last 3 parts first (e.g., _wildcard.domain.co.in for blog.domain.co.in) */}} - {{ if ge $partsLen 4 }} - {{ $idx3 := sub $partsLen 3 }} - {{ $idx2 := sub $partsLen 2 }} - {{ $idx1 := sub $partsLen 1 }} - {{ $baseDomain3 := printf "%s.%s.%s" (index $hostParts $idx3) (index $hostParts $idx2) (index $hostParts $idx1) }} - {{ $wildcardHtpasswd3 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain3 }} - {{ if (exists $wildcardHtpasswd3) }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file {{ ($wildcardHtpasswd3) }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{ else }} - {{/* Fallback: check last 2 parts (e.g., _wildcard.co.in for blog.domain.co.in) */}} - {{ $baseDomain2 := printf "%s.%s" (index $hostParts $idx2) (index $hostParts $idx1) }} - {{ $wildcardHtpasswd2 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain2 }} - {{ if (exists $wildcardHtpasswd2) }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file {{ ($wildcardHtpasswd2) }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{ else if (exists "/etc/nginx/htpasswd/default") }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file /etc/nginx/htpasswd/default; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{ end }} - {{ end }} - {{ else if ge $partsLen 2 }} - {{/* For 2-3 part domains, check last 2 parts (e.g., _wildcard.example.com for blog.example.com or example.com) */}} - {{ $idx2 := sub $partsLen 2 }} - {{ $idx1 := sub $partsLen 1 }} - {{ $baseDomain2 := printf "%s.%s" (index $hostParts $idx2) (index $hostParts $idx1) }} - {{ $wildcardHtpasswd2 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain2 }} - {{ if (exists $wildcardHtpasswd2) }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file {{ ($wildcardHtpasswd2) }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{ else if (exists "/etc/nginx/htpasswd/default") }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file /etc/nginx/htpasswd/default; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{ end }} - {{ else if (exists "/etc/nginx/htpasswd/default") }} - {{/* Single-part hostname - use default */}} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file /etc/nginx/htpasswd/default; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} - {{ end }} + {{ $authFile = printf "/etc/nginx/htpasswd/%s" .Host }} + {{ else if (and (hasPrefix "*." .Host) (exists (printf "/etc/nginx/htpasswd/_wildcard.%s" (trimPrefix "*." .Host)))) }} + {{ $authFile = printf "/etc/nginx/htpasswd/_wildcard.%s" (trimPrefix "*." .Host) }} + {{ else if (exists "/etc/nginx/htpasswd/default") }} + {{ $authFile = "/etc/nginx/htpasswd/default" }} + {{ end }} + {{ if $authFile }} + auth_basic "{{ $realm }}"; + auth_basic_user_file {{ $authFile }}; + {{ template "acl" .Host }} {{ end }} {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} @@ -479,19 +401,11 @@ server { {{ if (exists (printf "/etc/nginx/htpasswd/default_admin_tools")) }} auth_basic "Restricted {{ $host }} Admin Tools"; auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/default_admin_tools") }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" $host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" $host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} + {{ template "acl" $host }} {{ else if (exists "/etc/nginx/htpasswd/default") }} auth_basic "Restricted {{ $host }} Admin Tools"; auth_basic_user_file "/etc/nginx/htpasswd/default"; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" $host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" $host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} + {{ template "acl" $host }} {{ end }} } {{ end }} @@ -535,19 +449,11 @@ server { {{ if (exists (printf "/etc/nginx/htpasswd/default_admin_tools")) }} auth_basic "Restricted {{ $host }} Admin Tools"; auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/default_admin_tools") }}; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" $host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" $host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} + {{ template "acl" $host }} {{ else if (exists "/etc/nginx/htpasswd/default") }} auth_basic "Restricted {{ $host }} Admin Tools"; auth_basic_user_file "/etc/nginx/htpasswd/default"; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" $host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" $host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; - {{ end }} + {{ template "acl" $host }} {{ end }} } {{ end }}