Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ <h1>

<p><b>1.12.1</b> (to be determined)</p>
<ul>
<li>Now requires Openfire 5.1.0 or later</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/259">#259</a>] - Prevent system property requests from affecting properties other than the one requested</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/256">#256</a>] - Record configuration changes in audit log</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/251">#251</a>] - Enable JUnit 5 tests</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/249">#249</a>] - Only trust forwarded headers (such as X-Forwarded-For) from trusted reverse proxies</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/248">#248</a>] - Do not expose properties that are encrypted or otherwise sensitive.</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/246">#246</a>] - Require custom authenticator plugin to be annotated as an authenticator</li>
<li>[<a href="https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244">#244</a>] - Prevent REST API plugin from exposing its own configuration (including authentication) via its own endpoints</li>
Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<author>Roman Soldatow</author>
<version>${project.version}</version>
<date>2026-09-24</date>
<minServerVersion>5.0.0</minServerVersion>
<minServerVersion>5.1.0</minServerVersion>
<adminconsole>
<tab id="tab-server">
<sidebar id="sidebar-server-settings">
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>plugins</artifactId>
<groupId>org.igniterealtime.openfire</groupId>
<version>5.0.0</version>
<version>5.1.0</version>
</parent>
<groupId>org.igniterealtime.openfire.plugins</groupId>
<artifactId>restAPI</artifactId>
Expand Down
24 changes: 24 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,30 @@ The third requirement cannot be verified automatically and is the implementer's
implements the interface and carries the annotation, but returns without calling `abortWith(...)` on an
unauthenticated request, will be loaded successfully and will silently grant unauthenticated access.

### Restricting access by IP address

Access to the REST API can additionally be limited to a list of allowed IP addresses. This is configured in the
Openfire Admin console under Server > Server Settings > REST API (backed by the `plugin.restapi.allowedIPs` system
property). When the list is empty, requests from any IP address are accepted.

The plugin checks the IP address of the peer that is directly connected to Openfire. It does not itself inspect
headers like `X-Forwarded-For`, as these can be set to arbitrary values by any client. When the REST API is accessed
through a reverse proxy, configure Openfire's admin console to use the forwarded client address instead: enable
`adminConsole.forwarded.enabled`, and list the IP addresses (or ranges) of your proxies in
`adminConsole.forwarded.trusted.proxies`. Both can be set on the Admin Console Access page (Server > Server
Manager > Admin Console Access). Openfire then uses forwarded headers only on requests that come from one of those
trusted proxies.

Be aware of the following:

- When `adminConsole.forwarded.enabled` is `true` but no trusted proxies are configured, Openfire uses forwarded
headers from _any_ peer. Any client can then bypass the IP address check by sending a forged header. The REST API
shows a warning on its admin console page when it detects this configuration.
- Make sure that your reverse proxy _replaces_ any `Forwarded` or `X-Forwarded-For` header that it receives from the
client, instead of appending to it. Otherwise, a value provided by the client can still end up being used as the
client's address.
- Changes to the `adminConsole.forwarded.*` properties take effect only after the admin console has been restarted.

# User related REST Endpoints

## Retrieve users
Expand Down
15 changes: 4 additions & 11 deletions src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,10 @@ public void filter(ContainerRequestContext containerRequest) throws IOException
}

if (!RESTServicePlugin.ALLOWED_IPS.getValue().isEmpty()) {
// Get client's IP address
String ipAddress = httpRequest.getHeader("x-forwarded-for");
if (ipAddress == null) {
ipAddress = httpRequest.getHeader("X_FORWARDED_FOR");
if (ipAddress == null) {
ipAddress = httpRequest.getHeader("X-Forward-For");
if (ipAddress == null) {
ipAddress = httpRequest.getRemoteAddr();
}
}
}
// Get client's IP address. Do not inspect headers like 'X-Forwarded-For' here: these can be spoofed by the client.
// When Openfire is configured to be accessed through a reverse proxy, its web server already replaces the remote
// address with the value from such headers, but only for requests from proxies that are configured to be trusted.
final String ipAddress = httpRequest.getRemoteAddr();
if (!RESTServicePlugin.ALLOWED_IPS.getValue().contains(ipAddress)) {
LOG.warn("REST API rejected service for IP address: " + ipAddress);
throw new WebApplicationException(Status.UNAUTHORIZED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.jivesoftware.openfire.plugin.rest;

import org.jivesoftware.admin.AuthCheckFilter;
import org.jivesoftware.openfire.container.AdminConsolePlugin;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.plugin.rest.service.JerseyWrapper;
Expand Down Expand Up @@ -148,6 +149,26 @@ public void destroyPlugin() {
AuthCheckFilter.removeExclude(JerseyWrapper.SERVLET_URL);
}

/**
* Checks if the IP addresses that are verified against {@link #ALLOWED_IPS} can be controlled by the client.
*
* The REST API is served by the admin console's web server. When that is configured to use 'Forwarded' or
* 'X-Forwarded-For' style HTTP headers to determine the address of the client, but no trusted reverse proxies are
* configured, then these headers are honored when sent by any peer. In that case, any client can pretend to have
* an address that is on the list of allowed IP addresses.
*
* Note that changes to the admin console configuration only take effect after the admin console is restarted. This
* method evaluates the configured values, which may differ from the configuration that is currently in effect.
*
* @return true if a non-empty list of allowed IP addresses is configured that can be bypassed by spoofing headers.
*/
public static boolean isAllowedIPsCheckSpoofable()
{
return !ALLOWED_IPS.getValue().isEmpty()
&& AdminConsolePlugin.ADMIN_CONSOLE_FORWARDED.getValue()
&& AdminConsolePlugin.ADMIN_CONSOLE_FORWARDED_TRUSTED_PROXIES.getValue().isEmpty();
}

/**
* Validates the custom authentication filter class name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,10 @@ public Response userSerivceRequest() throws IOException {
PrintWriter out = response.getWriter();

if (!RESTServicePlugin.ALLOWED_IPS.getValue().isEmpty()) {
// Get client's IP address
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null) {
ipAddress = request.getHeader("X_FORWARDED_FOR");
if (ipAddress == null) {
ipAddress = request.getHeader("X-Forward-For");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
}
}
}
// Get client's IP address. Do not inspect headers like 'X-Forwarded-For' here: these can be spoofed by the client.
// When Openfire is configured to be accessed through a reverse proxy, its web server already replaces the remote
// address with the value from such headers, but only for requests from proxies that are configured to be trusted.
final String ipAddress = request.getRemoteAddr();
if (!RESTServicePlugin.ALLOWED_IPS.getValue().contains(ipAddress)) {
LOG.warn("User service rejected service to IP address: " + ipAddress);
replyError("RequestNotAuthorised", response, out);
Expand Down
Binary file added src/web/images/warning-16x16.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 27 additions & 3 deletions src/web/rest-api.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@
</head>
<body>

<p>Use the form below to enable or disable the REST API and
configure the authentication.</p>

<%
if (success) {
%>
Expand All @@ -136,6 +133,29 @@
}
%>

<%
if (RESTServicePlugin.isAllowedIPsCheckSpoofable()) {
%>
<div class="jive-warning">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/warning-16x16.gif"
width="16" height="16" border="0"></td>
<td class="jive-icon-label">Access is restricted to specific IP addresses, but the admin console is
configured to determine the client address from forwarded HTTP headers (such as X-Forwarded-For),
without a list of trusted proxies. Any client can bypass the IP address check by sending such a header.
Configure the addresses of your reverse proxies as trusted proxies on the
<a href="../../system-admin-console-access.jsp">Admin Console Access</a> page.
</td>
</tr>
</tbody>
</table>
</div>
<br>
<%
}
%>
<%
if (errors.get("loadingStatus") != null) {
%>
Expand Down Expand Up @@ -175,6 +195,9 @@
}
%>

<p>Use the form below to enable or disable the REST API and
configure the authentication.</p>

<form action="rest-api.jsp?save" method="post">

<fieldset>
Expand Down Expand Up @@ -230,6 +253,7 @@

<label for="allowedIPs">Allowed IP Addresses:</label>
<textarea name="allowedIPs" cols="40" rows="3" wrap="virtual"><%=((allowedIPs != null) ? allowedIPs : "")%></textarea>
<div style="margin-left: 20px; margin-top: 5px;">Note: when the REST API is accessed through a reverse proxy, configure Openfire to use the forwarded client address, and to trust only your proxies, on the <a href="../../system-admin-console-access.jsp">Admin Console Access</a> page.</div>
<br>
<br>

Expand Down
Loading