Introduce auth.httpTrustedProxyNetworks for securing HTTP auth

When using auth=HTTP or auth=HTTP_LDAP, Gerrit relies on receiving the
incoming username from an HTTP header. When misusing this configuration
with a remote proxy and having Gerrit listening to an external interface
over plain HTTP without firewalls, the security of the incoming username
is subject to HTTP header spoofing.

Highlight the risk in the documentation and also add a mitigation of the
problem with a new auth.httpTrustedProxyNetworks that would restrict the
fetching of the username from HTTP to a subset of CIDRs.

The solution is similar to the mitigation adopted in other projects with
a similar vulnerability.

The parsing of the auth.httpTrustedProxyNetworks is expensive because it
involves the creation of a SubnetUtils object and the string-based
parsing of CIDRs. As a mitigation of the computational costs, parse and
store the subnets in the RemoteUserUtil during the singleton
instantiation, so that incoming HTTP calls can reuse the SubnetUtils
objects and reduce the runtime memory and CPU footprint.

Also, with regard to the subnet matching, differentiate between
individual IP matching (CIDRs ending with /32) and full networking
address matching with SubnetUtils. The individual IP matching is a
simple O(1) independently of the number of entries; however, the full
CIDRs matching will loop through all the networks and check the netmasks
against the IP, with an O(N) complexity.

The detection of the proxy-IP is quite complex because it also involves
the wrapping of the X-Forwarded* headers management by Jetty, which
overwrites the socket remote IP address with the original client IP
forwarded by the proxy in between. It is necessary to wrap the concept
of the extraction of the address into a generic interface, which can be
overridden by actual implementations, with a default fallback to the
standard servlet mechanism.

The auth path in RemoteUserUtil needs exactly one piece of information
that the servlet API does not give it directly: the pre-X-Forwarded-*
TCP peer address.

- RemoteUserUtil declares a public PROXY_REMOTE_ADDRESS_ATTR
  constant. Its javadoc documents the contract: any HTTP layer that
  wants its requests evaluated against auth.httpTrustedProxyNetworks
  sets this attribute to the pre-rewrite peer address. If unset,
  RemoteUserUtil falls back to HttpServletRequest.getRemoteAddr().

- JettyServer writes the attribute (one line) before delegating to
  the parent ForwardedRequestCustomizer, which is where Jetty rewrites
  getRemoteAddr() with the X-Forwarded-For value.

Beyond the smaller surface, this design generalizes to non-Jetty HTTP
layers without any new class hierarchy. A Tomcat valve, an embedded
Netty pipeline stage, or a plain servlet filter installed before the
auth filter can opt into the trusted-proxy check by setting the same
attribute on the request. No subclass of ProxyAddressProvider, no module
override, no Guice rebinding -- the contract is the constant plus its
javadoc.

In the case of JettyServer, we need to extend the
ForwardedRequestCustomizer to remember what the original IP was before
the wrapping and store into a request attribute. The runtime filter then
accesses the attribute transparently and, if present, uses the proxy IP
instead of the wrapped client IP. This also protects from malicious
attackers trying to fake their client IP by spoofing the X-Forwarded*
header values.

Co-Author: David Ostrovsky <david.ostrovsky@gmail.com>
Bug: Issue 520369774
Release-Notes: Introduce auth.httpTrustedProxyNetworks for mitigating the vulnerability of auth=HTTP or auth=HTTP_LDAP on external IPs
Change-Id: If9809ff092520aa467fa108b4179b7b75afe0a85
6 files changed
tree: 3e18b5d5f1a1bdc88da55694be677147ebde8db8
  1. .github/
  2. .settings/
  3. .ts-out/
  4. antlr3/
  5. contrib/
  6. Documentation/
  7. e2e-tests/
  8. java/
  9. javatests/
  10. lib/
  11. modules/
  12. plugins/
  13. polygerrit-ui/
  14. prolog/
  15. prologtests/
  16. proto/
  17. resources/
  18. tools/
  19. webapp/
  20. .bazelignore
  21. .bazelproject
  22. .bazelrc
  23. .bazelversion
  24. .editorconfig
  25. .git-blame-ignore-revs
  26. .gitignore
  27. .gitmodules
  28. .gitreview
  29. .mailmap
  30. .pydevproject
  31. .zuul.yaml
  32. BUILD
  33. COPYING
  34. INSTALL
  35. Jenkinsfile
  36. MODULE.bazel
  37. package.json
  38. README.md
  39. SUBMITTING_PATCHES
  40. version.bzl
  41. web-dev-server.config.mjs
  42. WORKSPACE
  43. yarn.lock
README.md

Gerrit Code Review

Gerrit is a code review and project management tool for Git based projects.

Build Status Maven Central

Objective

Gerrit makes reviews easier by showing changes in a side-by-side display, and allowing inline comments to be added by any reviewer.

Gerrit simplifies Git based project maintainership by permitting any authorized user to submit changes to the master Git repository, rather than requiring all approved changes to be merged in by hand by the project maintainer.

Documentation

For information about how to install and use Gerrit, refer to the documentation.

Source

Our canonical Git repository is located on googlesource.com. There is a mirror of the repository on Github.

Reporting bugs

Please report bugs on the issue tracker.

Contribute

Gerrit is the work of hundreds of contributors. We appreciate your help!

Please read the contribution guidelines.

Note that we do not accept Pull Requests via the Github mirror.

Getting in contact

The Developer Mailing list is repo-discuss on Google Groups.

License

Gerrit is provided under the Apache License 2.0.

Build

Install Bazel and run the following:

    git clone --recurse-submodules https://gerrit.googlesource.com/gerrit
    cd gerrit && bazel build release

Install binary packages (Deb/Rpm)

The instruction how to configure GerritForge/BinTray repositories is here

On Debian/Ubuntu run:

    apt-get update && apt-get install gerrit=<version>-<release>

NOTE: release is a counter that starts with 1 and indicates the number of packages that have been released with the same version of the software.

On CentOS/RedHat run:

    yum clean all && yum install gerrit-<version>[-<release>]

On Fedora run:

    dnf clean all && dnf install gerrit-<version>[-<release>]

Use pre-built Gerrit images on Docker

Docker images of Gerrit are available on DockerHub

To run a CentOS 8 based Gerrit image:

    docker run -p 8080:8080 gerritcodereview/gerrit[:version]-centos8

To run a Ubuntu 20.04 based Gerrit image:

    docker run -p 8080:8080 gerritcodereview/gerrit[:version]-ubuntu20

NOTE: release is optional. Last released package of the version is installed if the release number is omitted.