Przeglądaj źródła

Cleanup, examples

binwiederhier 8 miesięcy temu
rodzic
commit
7a33e16945
5 zmienionych plików z 57 dodań i 18 usunięć
  1. 6 4
      cmd/serve.go
  2. 44 7
      docs/config.md
  3. 1 1
      server/config.go
  4. 2 2
      server/server.go
  5. 4 4
      server/util.go

+ 6 - 4
cmd/serve.go

@@ -89,8 +89,8 @@ var flagsServe = append(
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: util.FormatDuration(server.DefaultVisitorEmailLimitReplenish), Usage: "interval at which burst limit is replenished (one per x)"}),
 	altsrc.NewBoolFlag(&cli.BoolFlag{Name: "visitor-subscriber-rate-limiting", Aliases: []string{"visitor_subscriber_rate_limiting"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIBER_RATE_LIMITING"}, Value: false, Usage: "enables subscriber-based rate limiting"}),
 	altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use forwarded header (e.g. X-Forwarded-For, X-Client-IP) to determine visitor IP address (for rate limiting)"}),
-	altsrc.NewStringFlag(&cli.StringFlag{Name: "proxy-forwarded-header", Aliases: []string{"proxy_forwarded_header"}, EnvVars: []string{"NTFY_PROXY_FORWARDED_HEADER"}, Value: "X-Forwarded-For", Usage: "if set, use specified header to determine visitor IP address instead of XFF (for rate limiting)"}),
-	altsrc.NewStringFlag(&cli.StringFlag{Name: "proxy-trusted-addrs", Aliases: []string{"proxy_trusted_addrs"}, EnvVars: []string{"NTFY_PROXY_TRUSTED_ADDRS"}, Value: "", Usage: "comma-separated list of trusted IP addresses to remove from forwarded header"}),
+	altsrc.NewStringFlag(&cli.StringFlag{Name: "proxy-forwarded-header", Aliases: []string{"proxy_forwarded_header"}, EnvVars: []string{"NTFY_PROXY_FORWARDED_HEADER"}, Value: "X-Forwarded-For", Usage: "use specified header to determine visitor IP address (for rate limiting)"}),
+	altsrc.NewStringFlag(&cli.StringFlag{Name: "proxy-trusted-addresses", Aliases: []string{"proxy_trusted_addresses"}, EnvVars: []string{"NTFY_PROXY_TRUSTED_ADDRESSES"}, Value: "", Usage: "comma-separated list of trusted IP addresses to remove from forwarded header"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-secret-key", Aliases: []string{"stripe_secret_key"}, EnvVars: []string{"NTFY_STRIPE_SECRET_KEY"}, Value: "", Usage: "key used for the Stripe API communication, this enables payments"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-webhook-key", Aliases: []string{"stripe_webhook_key"}, EnvVars: []string{"NTFY_STRIPE_WEBHOOK_KEY"}, Value: "", Usage: "key required to validate the authenticity of incoming webhooks from Stripe"}),
 	altsrc.NewStringFlag(&cli.StringFlag{Name: "billing-contact", Aliases: []string{"billing_contact"}, EnvVars: []string{"NTFY_BILLING_CONTACT"}, Value: "", Usage: "e-mail or website to display in upgrade dialog (only if payments are enabled)"}),
@@ -193,7 +193,7 @@ func execServe(c *cli.Context) error {
 	visitorEmailLimitReplenishStr := c.String("visitor-email-limit-replenish")
 	behindProxy := c.Bool("behind-proxy")
 	proxyForwardedHeader := c.String("proxy-forwarded-header")
-	proxyTrustedAddrs := util.SplitNoEmpty(c.String("proxy-trusted-addrs"), ",")
+	proxyTrustedAddresses := util.SplitNoEmpty(c.String("proxy-trusted-addresses"), ",")
 	stripeSecretKey := c.String("stripe-secret-key")
 	stripeWebhookKey := c.String("stripe-webhook-key")
 	billingContact := c.String("billing-contact")
@@ -322,6 +322,8 @@ func execServe(c *cli.Context) error {
 		}
 	} else if webPushExpiryWarningDuration > 0 && webPushExpiryWarningDuration > webPushExpiryDuration {
 		return errors.New("web push expiry warning duration cannot be higher than web push expiry duration")
+	} else if behindProxy && proxyForwardedHeader == "" {
+		return errors.New("if behind-proxy is set, proxy-forwarded-header must also be set")
 	}
 
 	// Backwards compatibility
@@ -421,7 +423,7 @@ func execServe(c *cli.Context) error {
 	conf.VisitorSubscriberRateLimiting = visitorSubscriberRateLimiting
 	conf.BehindProxy = behindProxy
 	conf.ProxyForwardedHeader = proxyForwardedHeader
-	conf.ProxyTrustedAddrs = proxyTrustedAddrs
+	conf.ProxyTrustedAddresses = proxyTrustedAddresses
 	conf.StripeSecretKey = stripeSecretKey
 	conf.StripeWebhookKey = stripeWebhookKey
 	conf.BillingContact = billingContact

+ 44 - 7
docs/config.md

@@ -554,15 +554,50 @@ using Let's Encrypt using certbot, or simply because you'd like to share the por
 Whatever your reasons may be, there are a few things to consider. 
 
 If you are running ntfy behind a proxy, you should set the `behind-proxy` flag. This will instruct the 
-[rate limiting](#rate-limiting) logic to use the `X-Forwarded-For` header as the primary identifier for a visitor, 
-as opposed to the remote IP address. If the `behind-proxy` flag is not set, all visitors will
-be counted as one, because from the perspective of the ntfy server, they all share the proxy's IP address. If your proxy or CDN provider uses a custom header to securely pass the source IP/Client IP to your application, you can specify that header instead of using the XFF. Using the custom header (unique per provide/cdn/proxy), will disable the use of the XFF header.
+[rate limiting](#rate-limiting) logic to use the header configured in `proxy-forwarded-header` (default is `X-Forwarded-For`)
+as the primary identifier for a visitor, as opposed to the remote IP address. 
 
-=== "/etc/ntfy/server.yml"
+If the `behind-proxy` flag is not set, all visitors will be counted as one, because from the perspective of the
+ntfy server, they all share the proxy's IP address. 
+
+Relevant flags to consider:
+
+* `behind-proxy`: if set, ntfy will use the `proxy-forwarded-header` to identify visitors (default: `false`)
+* `proxy-forwarded-header`: the header to use to identify visitors (default: `X-Forwarded-For`)
+* `proxy-trusted-addresses`: a comma-separated list of IP addresses that are removed from the forwarded header 
+  to determine the real IP address (default: empty)
+
+=== "/etc/ntfy/server.yml (behind a proxy)"
+    ``` yaml
+    # Tell ntfy to use "X-Forwarded-For" header to identify visitors for rate limiting
+    #
+    # Example: If "X-Forwarded-For: 9.9.9.9, 1.2.3.4" is set, 
+    #          the visitor IP will be 1.2.3.4 (right-most address).
+    #
+    behind-proxy: true
+    ```
+
+=== "/etc/ntfy/server.yml (with custom header)"
     ``` yaml
-    # Tell ntfy to use "X-Forwarded-For" to identify visitors
+    # Tell ntfy to use "X-Client-IP" header to identify visitors for rate limiting
+    #
+    # Example: If "X-Client-IP: 9.9.9.9" is set, 
+    #          the visitor IP will be 9.9.9.9.
+    #
+    behind-proxy: true
+    proxy-forwarded-header: "X-Client-IP"
+    ```
+
+=== "/etc/ntfy/server.yml (multiple proxies)"
+    ``` yaml
+    # Tell ntfy to use "X-Forwarded-For" header to identify visitors for rate limiting,
+    # and to strip the IP addresses of the proxies 1.2.3.4 and 1.2.3.5
+    #
+    # Example: If "X-Forwarded-For: 9.9.9.9, 1.2.3.4" is set, 
+    #          the visitor IP will be 9.9.9.9 (right-most unknown address).
+    #
     behind-proxy: true
-    proxy-client-ip-header: "X-Client-IP"
+    proxy-trusted-addresses: "1.2.3.4, 1.2.3.5"
     ```
 
 ### TLS/SSL
@@ -1391,7 +1426,9 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
 | `cache-batch-timeout`                      | `NTFY_CACHE_BATCH_TIMEOUT`                      | *duration*                                          | 0s                | Timeout for batched async writes to the message cache (if zero, writes are synchronous)                                                                                                                                         |
 | `auth-file`                                | `NTFY_AUTH_FILE`                                | *filename*                                          | -                 | Auth database file used for access control. If set, enables authentication and access control. See [access control](#access-control).                                                                                           |
 | `auth-default-access`                      | `NTFY_AUTH_DEFAULT_ACCESS`                      | `read-write`, `read-only`, `write-only`, `deny-all` | `read-write`      | Default permissions if no matching entries in the auth database are found. Default is `read-write`.                                                                                                                             |
-| `behind-proxy`                             | `NTFY_BEHIND_PROXY`                             | *bool*                                              | false             | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection.                                                                                                 |
+| `behind-proxy`                             | `NTFY_BEHIND_PROXY`                             | *bool*                                              | false             | If set, use forwarded header (e.g. X-Forwarded-For, X-Client-IP) to determine visitor IP address (for rate limiting)                                                                                                            |
+| `proxy-forwarded-header`                   | `NTFY_PROXY_FORWARDED_HEADER`                   | *string*                                            | `X-Forwarded-For` | Use specified header to determine visitor IP address (for rate limiting)                                                                                                                                                        |
+| `proxy-trusted-addresses`                  | `NTFY_PROXY_TRUSTED_ADDRESSES`                  | *comma-separated list of IPs*                       | -                 | Comma-separated list of trusted IP addresses to remove from forwarded header                                                                                                                                                    |
 | `attachment-cache-dir`                     | `NTFY_ATTACHMENT_CACHE_DIR`                     | *directory*                                         | -                 | Cache directory for attached files. To enable attachments, this has to be set.                                                                                                                                                  |
 | `attachment-total-size-limit`              | `NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT`              | *size*                                              | 5G                | Limit of the on-disk attachment cache directory. If the limits is exceeded, new attachments will be rejected.                                                                                                                   |
 | `attachment-file-size-limit`               | `NTFY_ATTACHMENT_FILE_SIZE_LIMIT`               | *size*                                              | 15M               | Per-file attachment size limit (e.g. 300k, 2M, 100M). Larger attachment will be rejected.                                                                                                                                       |

+ 1 - 1
server/config.go

@@ -145,7 +145,7 @@ type Config struct {
 	VisitorSubscriberRateLimiting        bool      // Enable subscriber-based rate limiting for UnifiedPush topics
 	BehindProxy                          bool      // If true, the server will trust the proxy client IP header to determine the client IP address
 	ProxyForwardedHeader                 string    // The header field to read the real/client IP address from, if BehindProxy is true, defaults to "X-Forwarded-For"
-	ProxyTrustedAddrs                    []string  // List of trusted proxy addresses that will be stripped from the Forwarded header if BehindProxy is true
+	ProxyTrustedAddresses                []string  // List of trusted proxy addresses that will be stripped from the Forwarded header if BehindProxy is true
 	StripeSecretKey                      string
 	StripeWebhookKey                     string
 	StripePriceCacheDuration             time.Duration

+ 2 - 2
server/server.go

@@ -1937,7 +1937,7 @@ func (s *Server) authorizeTopic(next handleFunc, perm user.Permission) handleFun
 // that subsequent logging calls still have a visitor context.
 func (s *Server) maybeAuthenticate(r *http.Request) (*visitor, error) {
 	// Read the "Authorization" header value and exit out early if it's not set
-	ip := extractIPAddress(r, s.config.BehindProxy, s.config.ProxyForwardedHeader, s.config.ProxyTrustedAddrs)
+	ip := extractIPAddress(r, s.config.BehindProxy, s.config.ProxyForwardedHeader, s.config.ProxyTrustedAddresses)
 	vip := s.visitor(ip, nil)
 	if s.userManager == nil {
 		return vip, nil
@@ -2012,7 +2012,7 @@ func (s *Server) authenticateBearerAuth(r *http.Request, token string) (*user.Us
 	if err != nil {
 		return nil, err
 	}
-	ip := extractIPAddress(r, s.config.BehindProxy, s.config.ProxyForwardedHeader, s.config.ProxyTrustedAddrs)
+	ip := extractIPAddress(r, s.config.BehindProxy, s.config.ProxyForwardedHeader, s.config.ProxyTrustedAddresses)
 	go s.userManager.EnqueueTokenUpdate(token, &user.TokenUpdate{
 		LastAccess: time.Now(),
 		LastOrigin: ip,

+ 4 - 4
server/util.go

@@ -74,9 +74,9 @@ func readQueryParam(r *http.Request, names ...string) string {
 	return ""
 }
 
-func extractIPAddress(r *http.Request, behindProxy bool, proxyForwardedHeader string, proxyTrustedAddrs []string) netip.Addr {
+func extractIPAddress(r *http.Request, behindProxy bool, proxyForwardedHeader string, proxyTrustedAddresses []string) netip.Addr {
 	if behindProxy && proxyForwardedHeader != "" {
-		if addr, err := extractIPAddressFromHeader(r, proxyForwardedHeader, proxyTrustedAddrs); err == nil {
+		if addr, err := extractIPAddressFromHeader(r, proxyForwardedHeader, proxyTrustedAddresses); err == nil {
 			return addr
 		}
 		// Fall back to the remote address if the header is not found or invalid
@@ -94,14 +94,14 @@ func extractIPAddress(r *http.Request, behindProxy bool, proxyForwardedHeader st
 // X-Forwarded-For can contain multiple addresses (see #328). If we are behind a proxy,
 // only the right-most address can be trusted (as this is the one added by our proxy server).
 // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For for details.
-func extractIPAddressFromHeader(r *http.Request, forwardedHeader string, trustedAddrs []string) (netip.Addr, error) {
+func extractIPAddressFromHeader(r *http.Request, forwardedHeader string, trustedAddresses []string) (netip.Addr, error) {
 	value := strings.TrimSpace(r.Header.Get(forwardedHeader))
 	if value == "" {
 		return netip.IPv4Unspecified(), fmt.Errorf("no %s header found", forwardedHeader)
 	}
 	addrs := util.Map(util.SplitNoEmpty(value, ","), strings.TrimSpace)
 	clientAddrs := util.Filter(addrs, func(addr string) bool {
-		return !slices.Contains(trustedAddrs, addr)
+		return !slices.Contains(trustedAddresses, addr)
 	})
 	if len(clientAddrs) == 0 {
 		return netip.IPv4Unspecified(), fmt.Errorf("no client IP address found in %s header: %s", forwardedHeader, value)