Cleanup, examples
This commit is contained in:
parent
eac49feb04
commit
7a33e16945
10
cmd/serve.go
10
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.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: "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.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-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-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-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-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: "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)"}),
|
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")
|
visitorEmailLimitReplenishStr := c.String("visitor-email-limit-replenish")
|
||||||
behindProxy := c.Bool("behind-proxy")
|
behindProxy := c.Bool("behind-proxy")
|
||||||
proxyForwardedHeader := c.String("proxy-forwarded-header")
|
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")
|
stripeSecretKey := c.String("stripe-secret-key")
|
||||||
stripeWebhookKey := c.String("stripe-webhook-key")
|
stripeWebhookKey := c.String("stripe-webhook-key")
|
||||||
billingContact := c.String("billing-contact")
|
billingContact := c.String("billing-contact")
|
||||||
|
|
@ -322,6 +322,8 @@ func execServe(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
} else if webPushExpiryWarningDuration > 0 && webPushExpiryWarningDuration > webPushExpiryDuration {
|
} else if webPushExpiryWarningDuration > 0 && webPushExpiryWarningDuration > webPushExpiryDuration {
|
||||||
return errors.New("web push expiry warning duration cannot be higher than web push expiry duration")
|
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
|
// Backwards compatibility
|
||||||
|
|
@ -421,7 +423,7 @@ func execServe(c *cli.Context) error {
|
||||||
conf.VisitorSubscriberRateLimiting = visitorSubscriberRateLimiting
|
conf.VisitorSubscriberRateLimiting = visitorSubscriberRateLimiting
|
||||||
conf.BehindProxy = behindProxy
|
conf.BehindProxy = behindProxy
|
||||||
conf.ProxyForwardedHeader = proxyForwardedHeader
|
conf.ProxyForwardedHeader = proxyForwardedHeader
|
||||||
conf.ProxyTrustedAddrs = proxyTrustedAddrs
|
conf.ProxyTrustedAddresses = proxyTrustedAddresses
|
||||||
conf.StripeSecretKey = stripeSecretKey
|
conf.StripeSecretKey = stripeSecretKey
|
||||||
conf.StripeWebhookKey = stripeWebhookKey
|
conf.StripeWebhookKey = stripeWebhookKey
|
||||||
conf.BillingContact = billingContact
|
conf.BillingContact = billingContact
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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
|
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,
|
[rate limiting](#rate-limiting) logic to use the header configured in `proxy-forwarded-header` (default is `X-Forwarded-For`)
|
||||||
as opposed to the remote IP address. If the `behind-proxy` flag is not set, all visitors will
|
as the primary identifier for a visitor, as opposed to the remote IP address.
|
||||||
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.
|
|
||||||
|
|
||||||
=== "/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
|
``` yaml
|
||||||
# Tell ntfy to use "X-Forwarded-For" to identify visitors
|
# 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
|
behind-proxy: true
|
||||||
proxy-client-ip-header: "X-Client-IP"
|
```
|
||||||
|
|
||||||
|
=== "/etc/ntfy/server.yml (with custom header)"
|
||||||
|
``` yaml
|
||||||
|
# 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-trusted-addresses: "1.2.3.4, 1.2.3.5"
|
||||||
```
|
```
|
||||||
|
|
||||||
### TLS/SSL
|
### 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) |
|
| `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-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`. |
|
| `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-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-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. |
|
| `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. |
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ type Config struct {
|
||||||
VisitorSubscriberRateLimiting bool // Enable subscriber-based rate limiting for UnifiedPush topics
|
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
|
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"
|
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
|
StripeSecretKey string
|
||||||
StripeWebhookKey string
|
StripeWebhookKey string
|
||||||
StripePriceCacheDuration time.Duration
|
StripePriceCacheDuration time.Duration
|
||||||
|
|
|
||||||
|
|
@ -1937,7 +1937,7 @@ func (s *Server) authorizeTopic(next handleFunc, perm user.Permission) handleFun
|
||||||
// that subsequent logging calls still have a visitor context.
|
// that subsequent logging calls still have a visitor context.
|
||||||
func (s *Server) maybeAuthenticate(r *http.Request) (*visitor, error) {
|
func (s *Server) maybeAuthenticate(r *http.Request) (*visitor, error) {
|
||||||
// Read the "Authorization" header value and exit out early if it's not set
|
// 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)
|
vip := s.visitor(ip, nil)
|
||||||
if s.userManager == nil {
|
if s.userManager == nil {
|
||||||
return vip, nil
|
return vip, nil
|
||||||
|
|
@ -2012,7 +2012,7 @@ func (s *Server) authenticateBearerAuth(r *http.Request, token string) (*user.Us
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
go s.userManager.EnqueueTokenUpdate(token, &user.TokenUpdate{
|
||||||
LastAccess: time.Now(),
|
LastAccess: time.Now(),
|
||||||
LastOrigin: ip,
|
LastOrigin: ip,
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,9 @@ func readQueryParam(r *http.Request, names ...string) string {
|
||||||
return ""
|
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 behindProxy && proxyForwardedHeader != "" {
|
||||||
if addr, err := extractIPAddressFromHeader(r, proxyForwardedHeader, proxyTrustedAddrs); err == nil {
|
if addr, err := extractIPAddressFromHeader(r, proxyForwardedHeader, proxyTrustedAddresses); err == nil {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
// Fall back to the remote address if the header is not found or invalid
|
// 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,
|
// 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).
|
// 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.
|
// 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))
|
value := strings.TrimSpace(r.Header.Get(forwardedHeader))
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return netip.IPv4Unspecified(), fmt.Errorf("no %s header found", forwardedHeader)
|
return netip.IPv4Unspecified(), fmt.Errorf("no %s header found", forwardedHeader)
|
||||||
}
|
}
|
||||||
addrs := util.Map(util.SplitNoEmpty(value, ","), strings.TrimSpace)
|
addrs := util.Map(util.SplitNoEmpty(value, ","), strings.TrimSpace)
|
||||||
clientAddrs := util.Filter(addrs, func(addr string) bool {
|
clientAddrs := util.Filter(addrs, func(addr string) bool {
|
||||||
return !slices.Contains(trustedAddrs, addr)
|
return !slices.Contains(trustedAddresses, addr)
|
||||||
})
|
})
|
||||||
if len(clientAddrs) == 0 {
|
if len(clientAddrs) == 0 {
|
||||||
return netip.IPv4Unspecified(), fmt.Errorf("no client IP address found in %s header: %s", forwardedHeader, value)
|
return netip.IPv4Unspecified(), fmt.Errorf("no client IP address found in %s header: %s", forwardedHeader, value)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue