puppet: Default to installing smokescreen on application frontends.

This is an additional security hardening step, to make Zulip default
to preventing SSRF attacks.  The overhead of running Smokescreen is
minimal, and there is no reason to force deployments to take
additional steps in order to secure themselves against SSRF attacks.

Deployments which already have a different external proxy configured
will not gain a local Smokescreen installation, and running without
Smokescreen is supported by explicitly unsetting the `host` or `port`
values in `/etc/zulip/zulip.conf`.
This commit is contained in:
Alex Vandiver 2021-11-17 13:17:56 -08:00 committed by Tim Abbott
parent 44f1ea6bae
commit c33562f0a8
7 changed files with 49 additions and 45 deletions

View File

@ -307,7 +307,7 @@ log][commit-log] for an up-to-date list of raw changes.
major release.
[docker-zulip-manual]: https://github.com/zulip/docker-zulip#manual-configuration
[smokescreen]: ../production/deployment.html#using-an-outgoing-http-proxy
[smokescreen]: ../production/deployment.html#customizing-the-outgoing-http-proxy
[update-settings-docs]: ../production/upgrade-or-modify.html#updating-settings-py-inline-documentation
#### Full feature changelog

View File

@ -223,28 +223,14 @@ behind reverse proxies.
[using-http]: ../production/deployment.html#configuring-zulip-to-allow-http
## Using an outgoing HTTP proxy
## Customizing the outgoing HTTP proxy
Zulip supports routing all of its outgoing HTTP and HTTPS traffic
through an HTTP `CONNECT` proxy, such as [Smokescreen][smokescreen];
this includes outgoing webhooks, image and website previews, and
mobile push notifications. You may wish to enable this feature to
provide a consistent egress point, or enforce access control on URLs
to prevent [SSRF][ssrf] against internal resources.
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.
To use Smokescreen:
1. Add `, zulip::profile::smokescreen` to the list of `puppet_classes`
in `/etc/zulip/zulip.conf`. A typical value after this change is:
```ini
puppet_classes = zulip::profile::standalone, zulip::profile::smokescreen
```
1. Optionally, configure the [smokescreen ACLs][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.
To use a custom outgoing proxy:
1. Add the following block to `/etc/zulip/zulip.conf`, substituting in
your proxy's hostname/IP and port:
@ -255,19 +241,28 @@ To use Smokescreen:
port = 4750
```
1. If you intend to also make the Smokescreen install available to
other hosts, set `listen_address` in the same block. Note that you
must control access to the Smokescreen port if you do this, as
failing to do so opens a public HTTP proxy!
1. As root, run
`/home/zulip/deployments/current/scripts/zulip-puppet-apply`. This
will compile and install Smokescreen, reconfigure services to use
it, and restart Zulip.
will reconfigure and restart Zulip.
If you would like to use an already-installed HTTP proxy, omit the
first step, and adjust the IP address and port in the second step
accordingly.
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.
[smokescreen]: https://github.com/stripe/smokescreen
[smokescreen-acls]: https://github.com/stripe/smokescreen#acls
@ -651,11 +646,13 @@ load balancers whose `X-Forwarded-For` should be respected.
#### `host`
The hostname or IP address of an [outgoing HTTP `CONNECT`
proxy](#using-an-outgoing-http-proxy).
proxy](#customizing-the-outgoing-http-proxy). Defaults to `localhost`
if unspecified.
#### `port`
The TCP port of the HTTP `CONNECT` proxy on the host specified above.
Defaults to `4750` if unspecified.
#### `listen_address`

View File

@ -18,7 +18,7 @@ support forwarding push notifications to a central push notification
forwarding service. Accessing this service requires outgoing HTTPS
access to the public Internet; if that is restricted by a proxy, you
will need to [configure Zulip to use your outgoing HTTP
proxy](../production/deployment.html#using-an-outgoing-http-proxy)
proxy](../production/deployment.html#customizing-the-outgoing-http-proxy)
first.
You can enable this for your Zulip server as follows:

View File

@ -96,13 +96,14 @@ on hardware requirements for larger organizations.
address as its external hostname (though we don't recommend that
configuration).
- Zulip supports [running behind a reverse proxy][reverse-proxy].
- Zulip servers running inside a private network should configure the
[Smokescreen integration][smokescreen-proxy] to protect against
[SSRF attacks][ssrf], where users could make the Zulip server make
requests to private resources.
- Zulip configures [Smokescreen, and outgoing HTTP
proxy][smokescreen-proxy], to protect against [SSRF attacks][ssrf],
which prevents user from making the Zulip server make requests to
private resources. If your network has its own outgoing HTTP proxy,
Zulip supports using that instead.
[ssrf]: https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
[smokescreen-proxy]: ../production/deployment.html#using-an-outgoing-http-proxy
[smokescreen-proxy]: ../production/deployment.html#customizing-the-outgoing-http-proxy
[reverse-proxy]: ../production/deployment.html#putting-the-zulip-application-behind-a-reverse-proxy
[email-mirror-code]: https://github.com/zulip/zulip/blob/main/zerver/management/commands/email_mirror.py

View File

@ -259,15 +259,15 @@ strength allowed is controlled by two settings in
- Mobile push notifications (must be configured to be enabled)
- Notably, these first 3 features give end users (limited) control to cause
the Zulip server to make HTTP requests on their behalf. As a result,
Zulip supports routing all outgoing outgoing HTTP requests [through
the Zulip server to make HTTP requests on their behalf. Because of this,
Zulip routes all outgoing outgoing HTTP requests [through
Smokescreen][smokescreen-setup] to ensure that Zulip cannot be
used to execute [SSRF attacks][ssrf] against other systems on an
internal corporate network. The default Smokescreen configuration
denies access to all non-public IP addresses, including 127.0.0.1.
[ssrf]: https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
[smokescreen-setup]: ../production/deployment.html#using-an-outgoing-http-proxy
[smokescreen-setup]: ../production/deployment.html#customizing-the-outgoing-http-proxy
## Final notes and security response

View File

@ -96,8 +96,14 @@ class zulip::app_frontend_base {
$uwsgi_default_processes = 4
}
$tornado_ports = $zulip::tornado_sharding::tornado_ports
$proxy_host = zulipconf('http_proxy', 'host', '')
$proxy_port = zulipconf('http_proxy', 'port', '')
$proxy_host = zulipconf('http_proxy', 'host', 'localhost')
$proxy_port = zulipconf('http_proxy', 'port', '4750')
if ($proxy_host in ['localhost', '127.0.0.1', '::1']) and ($proxy_port == '4750') {
include zulip::smokescreen
}
if $proxy_host != '' and $proxy_port != '' {
$proxy = "http://${proxy_host}:${proxy_port}"
} else {

View File

@ -2,8 +2,8 @@
# in a cluster.
class zulip::app_frontend_once {
$proxy_host = zulipconf('http_proxy', 'host', '')
$proxy_port = zulipconf('http_proxy', 'port', '')
$proxy_host = zulipconf('http_proxy', 'host', 'localhost')
$proxy_port = zulipconf('http_proxy', 'port', '4750')
if $proxy_host != '' and $proxy_port != '' {
$proxy = "http://${proxy_host}:${proxy_port}"
} else {