Skip to content

Commit b66ec15

Browse files
abh1sarLocharla, Sandeep
authored andcommitted
Fix logging of forwarded IPs in logs (apache#11854)
1 parent f82d60b commit b66ec15

File tree

4 files changed

+69
-62
lines changed

4 files changed

+69
-62
lines changed

client/src/main/java/org/apache/cloudstack/ACSRequestLog.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
//
1919
package org.apache.cloudstack;
2020

21+
import com.cloud.api.ApiServlet;
2122
import com.cloud.utils.StringUtils;
2223
import org.eclipse.jetty.server.NCSARequestLog;
2324
import org.eclipse.jetty.server.Request;
2425
import org.eclipse.jetty.server.Response;
2526
import org.eclipse.jetty.util.DateCache;
2627
import org.eclipse.jetty.util.component.LifeCycle;
2728

29+
import java.net.InetAddress;
2830
import java.util.Locale;
2931
import java.util.TimeZone;
3032

@@ -51,9 +53,8 @@ public void log(Request request, Response response) {
5153
StringBuilder sb = buffers.get();
5254
sb.setLength(0);
5355

54-
sb.append(request.getHttpChannel().getEndPoint()
55-
.getRemoteAddress().getAddress()
56-
.getHostAddress())
56+
InetAddress remoteAddress = ApiServlet.getClientAddress(request);
57+
sb.append(remoteAddress.getHostAddress())
5758
.append(" - - [")
5859
.append(dateCache.format(request.getTimeStamp()))
5960
.append("] \"")

client/src/main/java/org/apache/cloudstack/ServerDaemon.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,12 @@
2424
import java.io.InputStream;
2525
import java.lang.management.ManagementFactory;
2626
import java.net.URL;
27-
import java.util.Arrays;
2827
import java.util.Properties;
2928

30-
import com.cloud.api.ApiServer;
3129
import org.apache.commons.daemon.Daemon;
3230
import org.apache.commons.daemon.DaemonContext;
3331
import org.apache.commons.lang3.StringUtils;
3432
import org.eclipse.jetty.jmx.MBeanContainer;
35-
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
3633
import org.eclipse.jetty.server.HttpConfiguration;
3734
import org.eclipse.jetty.server.HttpConnectionFactory;
3835
import org.eclipse.jetty.server.RequestLog;
@@ -193,7 +190,6 @@ public void start() throws Exception {
193190
httpConfig.setResponseHeaderSize(8192);
194191
httpConfig.setSendServerVersion(false);
195192
httpConfig.setSendDateHeader(false);
196-
addForwardingCustomiser(httpConfig);
197193

198194
// HTTP Connector
199195
createHttpConnector(httpConfig);
@@ -216,21 +212,6 @@ public void start() throws Exception {
216212
server.join();
217213
}
218214

219-
/**
220-
* Adds a ForwardedRequestCustomizer to the HTTP configuration to handle forwarded headers.
221-
* The header used for forwarding is determined by the ApiServer.listOfForwardHeaders property.
222-
* Only non empty headers are considered and only the first of the comma-separated list is used.
223-
* @param httpConfig the HTTP configuration to which the customizer will be added
224-
*/
225-
private static void addForwardingCustomiser(HttpConfiguration httpConfig) {
226-
ForwardedRequestCustomizer customiser = new ForwardedRequestCustomizer();
227-
String header = Arrays.stream(ApiServer.listOfForwardHeaders.value().split(",")).findFirst().orElse(null);
228-
if (com.cloud.utils.StringUtils.isNotEmpty(header)) {
229-
customiser.setForwardedForHeader(header);
230-
}
231-
httpConfig.addCustomizer(customiser);
232-
}
233-
234215
@Override
235216
public void stop() throws Exception {
236217
server.stop();

server/src/main/java/com/cloud/api/ApiServlet.java

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -679,38 +679,34 @@ private boolean isSpecificAPI(String commandName) {
679679
}
680680
return false;
681681
}
682-
boolean doUseForwardHeaders() {
682+
static boolean doUseForwardHeaders() {
683683
return Boolean.TRUE.equals(ApiServer.useForwardHeader.value());
684684
}
685685

686-
String[] proxyNets() {
686+
static String[] proxyNets() {
687687
return ApiServer.proxyForwardList.value().split(",");
688688
}
689689
//This method will try to get login IP of user even if servlet is behind reverseProxy or loadBalancer
690-
public InetAddress getClientAddress(final HttpServletRequest request) throws UnknownHostException {
691-
String ip = null;
692-
InetAddress pretender = InetAddress.getByName(request.getRemoteAddr());
693-
if(doUseForwardHeaders()) {
694-
if (NetUtils.isIpInCidrList(pretender, proxyNets())) {
690+
public static InetAddress getClientAddress(final HttpServletRequest request) throws UnknownHostException {
691+
final String remote = request.getRemoteAddr();
692+
if (doUseForwardHeaders()) {
693+
final InetAddress remoteAddr = InetAddress.getByName(remote);
694+
if (NetUtils.isIpInCidrList(remoteAddr, proxyNets())) {
695695
for (String header : getClientAddressHeaders()) {
696696
header = header.trim();
697-
ip = getCorrectIPAddress(request.getHeader(header));
697+
final String ip = getCorrectIPAddress(request.getHeader(header));
698698
if (StringUtils.isNotBlank(ip)) {
699-
LOGGER.debug(String.format("found ip %s in header %s ", ip, header));
700-
break;
699+
LOGGER.debug("found ip {} in header {}", ip, header);
700+
return InetAddress.getByName(ip);
701701
}
702-
} // no address found in header so ip is blank and use remote addr
703-
} // else not an allowed proxy address, ip is blank and use remote addr
704-
}
705-
if (StringUtils.isBlank(ip)) {
706-
LOGGER.trace(String.format("no ip found in headers, returning remote address %s.", pretender.getHostAddress()));
707-
return pretender;
702+
}
703+
}
708704
}
709-
710-
return InetAddress.getByName(ip);
705+
LOGGER.trace("no ip found in headers, returning remote address {}.", remote);
706+
return InetAddress.getByName(remote);
711707
}
712708

713-
private String[] getClientAddressHeaders() {
709+
private static String[] getClientAddressHeaders() {
714710
return ApiServer.listOfForwardHeaders.value().split(",");
715711
}
716712

server/src/test/java/com/cloud/api/ApiServletTest.java

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.cloudstack.api.auth.APIAuthenticator;
3434
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
3535
import org.apache.cloudstack.framework.config.ConfigKey;
36+
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
3637
import org.junit.After;
3738
import org.junit.Assert;
3839
import org.junit.Before;
@@ -97,17 +98,20 @@ public class ApiServletTest {
9798
@Mock
9899
AccountService accountMgr;
99100

100-
@Mock ConfigKey<Boolean> useForwardHeader;
101+
@Mock
102+
ConfigDepotImpl mockConfigDepot;
103+
101104
StringWriter responseWriter;
102105

103106
ApiServlet servlet;
104-
ApiServlet spyServlet;
107+
108+
private ConfigDepotImpl originalConfigDepot;
109+
105110
@SuppressWarnings("unchecked")
106111
@Before
107112
public void setup() throws SecurityException, NoSuchFieldException,
108-
IllegalArgumentException, IllegalAccessException, IOException, UnknownHostException {
113+
IllegalArgumentException, IllegalAccessException, IOException {
109114
servlet = new ApiServlet();
110-
spyServlet = Mockito.spy(servlet);
111115
responseWriter = new StringWriter();
112116
Mockito.when(response.getWriter()).thenReturn(
113117
new PrintWriter(responseWriter));
@@ -131,6 +135,7 @@ public void setup() throws SecurityException, NoSuchFieldException,
131135
apiServerField.setAccessible(true);
132136
apiServerField.set(servlet, apiServer);
133137

138+
setupConfigDepotMock();
134139
}
135140

136141
/**
@@ -151,6 +156,33 @@ public void cleanupEnvironmentHacks() throws Exception {
151156
Field smsField = ApiDBUtils.class.getDeclaredField("s_ms");
152157
smsField.setAccessible(true);
153158
smsField.set(null, null);
159+
restoreConfigDepot();
160+
}
161+
162+
private void setupConfigDepotMock() throws NoSuchFieldException, IllegalAccessException {
163+
Field depotField = ConfigKey.class.getDeclaredField("s_depot");
164+
depotField.setAccessible(true);
165+
originalConfigDepot = (ConfigDepotImpl) depotField.get(null);
166+
depotField.set(null, mockConfigDepot);
167+
Mockito.when(mockConfigDepot.getConfigStringValue(
168+
Mockito.anyString(),
169+
Mockito.any(ConfigKey.Scope.class),
170+
Mockito.any()
171+
)).thenReturn(null);
172+
}
173+
174+
private void restoreConfigDepot() throws Exception {
175+
Field depotField = ConfigKey.class.getDeclaredField("s_depot");
176+
depotField.setAccessible(true);
177+
depotField.set(null, originalConfigDepot);
178+
}
179+
180+
private void setConfigValue(String configName, String value) {
181+
Mockito.when(mockConfigDepot.getConfigStringValue(
182+
Mockito.eq(configName),
183+
Mockito.eq(ConfigKey.Scope.Global),
184+
Mockito.isNull()
185+
)).thenReturn(value);
154186
}
155187

156188
@Test
@@ -261,43 +293,40 @@ public void processRequestInContextLogin() throws UnknownHostException {
261293

262294
@Test
263295
public void getClientAddressWithXForwardedFor() throws UnknownHostException {
264-
String[] proxynet = {"127.0.0.0/8"};
265-
Mockito.when(spyServlet.proxyNets()).thenReturn(proxynet);
266-
Mockito.when(spyServlet.doUseForwardHeaders()).thenReturn(true);
296+
setConfigValue("proxy.header.verify", "true");
297+
setConfigValue("proxy.cidr", "127.0.0.0/8");
298+
Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1");
267299
Mockito.when(request.getHeader(Mockito.eq("X-Forwarded-For"))).thenReturn("192.168.1.1");
268-
Assert.assertEquals(InetAddress.getByName("192.168.1.1"), spyServlet.getClientAddress(request));
300+
Assert.assertEquals(InetAddress.getByName("192.168.1.1"), ApiServlet.getClientAddress(request));
269301
}
270302

271303
@Test
272304
public void getClientAddressWithHttpXForwardedFor() throws UnknownHostException {
273-
String[] proxynet = {"127.0.0.0/8"};
274-
Mockito.when(spyServlet.proxyNets()).thenReturn(proxynet);
275-
Mockito.when(spyServlet.doUseForwardHeaders()).thenReturn(true);
305+
setConfigValue("proxy.header.verify", "true");
306+
setConfigValue("proxy.cidr", "127.0.0.0/8");
276307
Mockito.when(request.getHeader(Mockito.eq("HTTP_X_FORWARDED_FOR"))).thenReturn("192.168.1.1");
277-
Assert.assertEquals(InetAddress.getByName("192.168.1.1"), spyServlet.getClientAddress(request));
308+
Assert.assertEquals(InetAddress.getByName("192.168.1.1"), ApiServlet.getClientAddress(request));
278309
}
279310

280311
@Test
281312
public void getClientAddressWithRemoteAddr() throws UnknownHostException {
282-
String[] proxynet = {"127.0.0.0/8"};
283-
Mockito.when(spyServlet.proxyNets()).thenReturn(proxynet);
284-
Mockito.when(spyServlet.doUseForwardHeaders()).thenReturn(true);
285-
Assert.assertEquals(InetAddress.getByName("127.0.0.1"), spyServlet.getClientAddress(request));
313+
setConfigValue("proxy.header.verify", "true");
314+
setConfigValue("proxy.cidr", "127.0.0.0/8");
315+
Assert.assertEquals(InetAddress.getByName("127.0.0.1"), ApiServlet.getClientAddress(request));
286316
}
287317

288318
@Test
289319
public void getClientAddressWithHttpClientIp() throws UnknownHostException {
290-
String[] proxynet = {"127.0.0.0/8"};
291-
Mockito.when(spyServlet.proxyNets()).thenReturn(proxynet);
292-
Mockito.when(spyServlet.doUseForwardHeaders()).thenReturn(true);
320+
setConfigValue("proxy.header.verify", "true");
321+
setConfigValue("proxy.cidr", "127.0.0.0/8");
293322
Mockito.when(request.getHeader(Mockito.eq("HTTP_CLIENT_IP"))).thenReturn("192.168.1.1");
294-
Assert.assertEquals(InetAddress.getByName("192.168.1.1"), spyServlet.getClientAddress(request));
323+
Assert.assertEquals(InetAddress.getByName("192.168.1.1"), ApiServlet.getClientAddress(request));
295324
}
296325

297326
@Test
298327
public void getClientAddressDefault() throws UnknownHostException {
299328
Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1");
300-
Assert.assertEquals(InetAddress.getByName("127.0.0.1"), spyServlet.getClientAddress(request));
329+
Assert.assertEquals(InetAddress.getByName("127.0.0.1"), ApiServlet.getClientAddress(request));
301330
}
302331

303332
@Test

0 commit comments

Comments
 (0)