diff --git a/docs/production/deployment.md b/docs/production/deployment.md index fdf3bfe2cc..e6a2246c06 100644 --- a/docs/production/deployment.md +++ b/docs/production/deployment.md @@ -246,15 +246,32 @@ behind reverse proxies. ## Customizing the outgoing HTTP proxy -To protect against [SSRF][ssrf], Zulip 4.8 and above default to -routing all outgoing HTTP and HTTPS traffic through -[Smokescreen][smokescreen], an HTTP `CONNECT` proxy; this includes -outgoing webhooks, website previews, and mobile push notifications. -By default, the Camo image proxy will be automatically configured to -use a custom outgoing proxy, but does not use Smokescreen by default -because Camo includes similar logic to deny access to private -subnets. You can [override][proxy.enable_for_camo] this default -configuration if desired. +To protect against [SSRF][ssrf], Zulip routes all outgoing HTTP and +HTTPS traffic through [Smokescreen][smokescreen], an HTTP `CONNECT` +proxy; this includes outgoing webhooks, website previews, and mobile +push notifications. + +### IP address rules + +By default, Smokescreen denies access to all [non-public IP +addresses](https://en.wikipedia.org/wiki/Private_network), including +127.0.0.1, but allows traffic to all public Internet hosts. You can +[adjust those rules][smokescreen-acls]. For instance, if you have an +outgoing webhook at `http://10.17.17.17:80/`, you would need to: + +1. Add the following block to `/etc/zulip/zulip.com`, substituting + your internal host's IP address: + + ```ini + [http_proxy] + allow_addresses = 10.17.17.17 + ``` + +1. As root, run + `/home/zulip/deployments/current/scripts/zulip-puppet-apply`. This + will reconfigure and restart Zulip. + +### Using a different outgoing proxy To use a custom outgoing proxy: @@ -271,29 +288,31 @@ To use a custom outgoing proxy: `/home/zulip/deployments/current/scripts/zulip-puppet-apply`. This will reconfigure and restart Zulip. +If you wish to disable the outgoing proxy entirely, follow the above +steps, configuring an empty `host` value. **This is not +recommended**, as it allows attackers to leverage the Zulip server to +access internal resources. + +### Routing Camo requests through an outgoing proxy + +By default, the Camo image proxy will use a custom outgoing proxy if +one is configured, but will not use the default Smokescreen proxy +(because Camo includes similar logic to deny access to private +subnets). You can [override][proxy.enable_for_camo] this logic in +either direction, if desired. + +### Installing Smokescreen on a separate host + If you have a deployment with multiple frontend servers, or wish to install Smokescreen on a separate host, you can apply the `zulip::profile::smokescreen` Puppet class on that host, and follow the above steps, setting the `[http_proxy]` block to point to that host. -If you wish to disable the outgoing proxy entirely, follow the above -steps, configuring an empty `host` value. - -Optionally, you can also configure the [Smokescreen ACL -list][smokescreen-acls]. By default, Smokescreen denies access to all -[non-public IP -addresses](https://en.wikipedia.org/wiki/Private_network), including -127.0.0.1, but allows traffic to all public Internet hosts. - -In Zulip 4.7 and older, to enable SSRF protection via Smokescreen, you -will need to explicitly add the `zulip::profile::smokescreen` Puppet -class, and configure the `[http_proxy]` block as above. - -[proxy.enable_for_camo]: system-configuration.md#enable_for_camo -[smokescreen]: https://github.com/stripe/smokescreen -[smokescreen-acls]: https://github.com/stripe/smokescreen#acls [ssrf]: https://owasp.org/www-community/attacks/Server_Side_Request_Forgery +[smokescreen]: https://github.com/stripe/smokescreen +[proxy.enable_for_camo]: system-configuration.md#enable_for_camo +[smokescreen-acls]: system-configuration.md#allow_addresses-allow_ranges-deny_addresses-deny_ranges ### S3 file storage requests and outgoing proxies diff --git a/docs/production/system-configuration.md b/docs/production/system-configuration.md index 9a5465cba6..28c4f8fb16 100644 --- a/docs/production/system-configuration.md +++ b/docs/production/system-configuration.md @@ -423,10 +423,13 @@ not_ send any requests to Zulip which came in unencrypted. ### `[http_proxy]` +See "[Customizing the outgoing HTTP +proxy](deployment.md#customizing-the-outgoing-http-proxy)" for general +instructions on the outgoing proxy. + #### `host` -The hostname or IP address of an [outgoing HTTP `CONNECT` -proxy](deployment.md#customizing-the-outgoing-http-proxy). Defaults to +The hostname or IP address of an outgoing HTTP `CONNECT`. Defaults to `localhost` if unspecified. #### `port` @@ -446,6 +449,12 @@ its requests through Smokescreen is generally not necessary. Set to true or false to override the default, which uses the proxy only if it is not the default of Smokescreen on a local host. +#### `allow_addresses`, `allow_ranges`, `deny_addresses`, `deny_ranges` + +Comma-separated lists of IP addresses or CIDR range rules. All +private IP addresses (e.g., 127.0.0.0/8, 192.168.0.0/16) are denied by +default; allow rules override deny rules. + ### `[sentry]` #### `organization` diff --git a/puppet/zulip/manifests/smokescreen.pp b/puppet/zulip/manifests/smokescreen.pp index 10fd6a8a5c..0961f362bd 100644 --- a/puppet/zulip/manifests/smokescreen.pp +++ b/puppet/zulip/manifests/smokescreen.pp @@ -37,6 +37,11 @@ class zulip::smokescreen { } $listen_address = zulipconf('http_proxy', 'listen_address', '127.0.0.1') + $allow_addresses = split(zulipconf('http_proxy', 'allow_addresses', ''), ',') + $allow_ranges = split(zulipconf('http_proxy', 'allow_ranges', ''), ',') + $deny_addresses = split(zulipconf('http_proxy', 'deny_addresses', ''), ',') + $deny_ranges = split(zulipconf('http_proxy', 'deny_ranges', ''), ',') + file { "${zulip::common::supervisor_conf_dir}/smokescreen.conf": ensure => file, require => [ diff --git a/puppet/zulip/templates/supervisor/smokescreen.conf.erb b/puppet/zulip/templates/supervisor/smokescreen.conf.erb index 10e5c31621..d9acadc967 100644 --- a/puppet/zulip/templates/supervisor/smokescreen.conf.erb +++ b/puppet/zulip/templates/supervisor/smokescreen.conf.erb @@ -1,5 +1,20 @@ +<% + +acls = [] +acls.concat(@allow_addresses.map {|a| "--allow-address #{a}"}) +acls.concat(@allow_ranges.map {|a| "--allow-range #{a}"}) +acls.concat(@deny_addresses.map {|a| "--deny-address #{a}"}) +acls.concat(@deny_ranges.map {|a| "--deny-range #{a}"}) + +if acls.empty? + acl = "" +else + acl = " " + acls.join(" ") +end + +-%> [program:smokescreen] -command=<%= @bin %> --listen-ip <%= @listen_address %> --expose-prometheus-metrics --prometheus-port 4760 +command=<%= @bin %> --listen-ip <%= @listen_address %> --expose-prometheus-metrics --prometheus-port 4760<%= acl %> priority=15 autostart=true autorestart=true