Skip to content

Commit fc82521

Browse files
committed
ARTEMIS-3915 Support PROXY Protocol
This commit implements support the PROXY protocol so that the broker will be able to determine a client's original IP address despite the client's connection coming through a reverse proxy (e.g. HAProxy, ngingx, etc.). Changes include: - A new Netty handler to extract relevant details out of PROXY Protocol messages and make them available to the broker - A new Netty handler to enforce the acceptor's PROXY Protocol config - A new chapter in the user manual - Updated logging to use this new data - Expose this new data via management - Disambiguate some variables names related to SOCKS proxy support - Embedded Netty-based PROXY server implementation for testing
1 parent 3384d54 commit fc82521

28 files changed

Lines changed: 1397 additions & 93 deletions

File tree

artemis-commons/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@
7979
<groupId>io.netty</groupId>
8080
<artifactId>netty-common</artifactId>
8181
</dependency>
82+
<dependency>
83+
<groupId>io.netty</groupId>
84+
<artifactId>netty-transport</artifactId>
85+
</dependency>
8286
<dependency>
8387
<groupId>commons-beanutils</groupId>
8488
<artifactId>commons-beanutils</artifactId>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.activemq.artemis.utils;
18+
19+
import io.netty.channel.Channel;
20+
import io.netty.util.AttributeKey;
21+
22+
public class ProxyProtocolUtil {
23+
public static final AttributeKey<String> PROXY_PROTOCOL_SOURCE_ADDRESS = AttributeKey.valueOf("proxyProtocolSourceAddress");
24+
public static final AttributeKey<String> PROXY_PROTOCOL_DESTINATION_ADDRESS = AttributeKey.valueOf("proxyProtocolDestinationAddress");
25+
public static final AttributeKey<String> PROXY_PROTOCOL_VERSION = AttributeKey.valueOf("proxyProtocolVersion");
26+
27+
/**
28+
* {@return a string representation of the remote address of this Channel taking into account whether the PROXY
29+
* protocol was used by the remote client}
30+
*/
31+
public static String getRemoteAddress(Channel channel) {
32+
String addressFromAttribute = getAddressFromAttribute(PROXY_PROTOCOL_SOURCE_ADDRESS, channel);
33+
if (addressFromAttribute != null) {
34+
return addressFromAttribute;
35+
} else {
36+
return SocketAddressUtil.toString(channel.remoteAddress());
37+
}
38+
}
39+
40+
/**
41+
* {@return a string representation of the proxy address of this Channel if and only if the PROXY protocol was used
42+
* by the remote client; otherwise null}
43+
*/
44+
public static String getProxyAddress(Channel channel) {
45+
return getAddressFromAttribute(PROXY_PROTOCOL_DESTINATION_ADDRESS, channel);
46+
}
47+
48+
private static String getAddressFromAttribute(AttributeKey<String> addressKey, Channel channel) {
49+
String address = channel.attr(addressKey).get();
50+
if (address != null && !address.isEmpty()) {
51+
return address;
52+
} else {
53+
return null;
54+
}
55+
}
56+
57+
public static String getProxyProtocolVersion(Channel channel) {
58+
return channel.attr(PROXY_PROTOCOL_VERSION).get() == null ? null : channel.attr(PROXY_PROTOCOL_VERSION).get().toString();
59+
}
60+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.activemq.artemis.utils;
18+
19+
import java.net.SocketAddress;
20+
21+
public class SocketAddressUtil {
22+
23+
/**
24+
* {@return a string representation of the {@code SocketAddress} with the leading "/" removed (if applicable)}
25+
*/
26+
public static String toString(SocketAddress address) {
27+
if (address == null) {
28+
return null;
29+
}
30+
String result = address.toString();
31+
if (result.startsWith("/")) {
32+
return result.substring(1);
33+
} else {
34+
return result;
35+
}
36+
}
37+
}

artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnection.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.apache.activemq.artemis.utils.ConfigurationHelper;
4747
import org.apache.activemq.artemis.utils.Env;
4848
import org.apache.activemq.artemis.utils.IPV6Util;
49+
import org.apache.activemq.artemis.utils.SocketAddressUtil;
4950
import org.slf4j.Logger;
5051
import org.slf4j.LoggerFactory;
5152

@@ -387,17 +388,8 @@ private static void flushAndWait(final Channel channel, final ChannelPromise pro
387388
}
388389

389390
@Override
390-
public final String getRemoteAddress() {
391-
SocketAddress address = channel.remoteAddress();
392-
if (address == null) {
393-
return null;
394-
}
395-
String result = address.toString();
396-
if (result.startsWith("/")) {
397-
return result.substring(1);
398-
} else {
399-
return result;
400-
}
391+
public String getRemoteAddress() {
392+
return SocketAddressUtil.toString(channel.remoteAddress());
401393
}
402394

403395
@Override
@@ -477,7 +469,7 @@ private boolean isLocalhost(String hostname) {
477469

478470
@Override
479471
public final String toString() {
480-
return super.toString() + "[ID=" + getID() + ", local= " + channel.localAddress() + ", remote=" + channel.remoteAddress() + "]";
472+
return super.toString() + "[ID=" + getID() + ", local= " + channel.localAddress() + ", remote=" + getRemoteAddress() + "]";
481473
}
482474

483475
private void closeChannel(final Channel channel, boolean inEventLoop) {
@@ -489,4 +481,4 @@ private void closeChannel(final Channel channel, boolean inEventLoop) {
489481
}
490482
}
491483

492-
}
484+
}

artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -211,19 +211,19 @@ public class NettyConnector extends AbstractConnector {
211211
// will be handled by the server's http server.
212212
private boolean httpUpgradeEnabled;
213213

214-
private boolean proxyEnabled;
214+
private boolean socksEnabled;
215215

216-
private String proxyHost;
216+
private String socksHost;
217217

218-
private int proxyPort;
218+
private int socksPort;
219219

220-
private SocksVersion proxyVersion;
220+
private SocksVersion socksVersion;
221221

222-
private String proxyUsername;
222+
private String socksUsername;
223223

224-
private String proxyPassword;
224+
private String socksPassword;
225225

226-
private boolean proxyRemoteDNS;
226+
private boolean socksRemoteDNS;
227227

228228
private boolean useServlet;
229229

@@ -382,18 +382,18 @@ public NettyConnector(final Map<String, Object> configuration,
382382

383383
httpUpgradeEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.HTTP_UPGRADE_ENABLED_PROP_NAME, TransportConstants.DEFAULT_HTTP_UPGRADE_ENABLED, configuration);
384384

385-
proxyEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.PROXY_ENABLED_PROP_NAME, TransportConstants.DEFAULT_PROXY_ENABLED, configuration);
386-
if (proxyEnabled) {
387-
proxyHost = ConfigurationHelper.getStringProperty(TransportConstants.PROXY_HOST_PROP_NAME, TransportConstants.DEFAULT_PROXY_HOST, configuration);
388-
proxyPort = ConfigurationHelper.getIntProperty(TransportConstants.PROXY_PORT_PROP_NAME, TransportConstants.DEFAULT_PROXY_PORT, configuration);
385+
socksEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SOCKS_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SOCKS_ENABLED, configuration);
386+
if (socksEnabled) {
387+
socksHost = ConfigurationHelper.getStringProperty(TransportConstants.SOCKS_HOST_PROP_NAME, TransportConstants.DEFAULT_SOCKS_HOST, configuration);
388+
socksPort = ConfigurationHelper.getIntProperty(TransportConstants.SOCKS_PORT_PROP_NAME, TransportConstants.DEFAULT_SOCKS_PORT, configuration);
389389

390-
int socksVersionNumber = ConfigurationHelper.getIntProperty(TransportConstants.PROXY_VERSION_PROP_NAME, TransportConstants.DEFAULT_PROXY_VERSION, configuration);
391-
proxyVersion = SocksVersion.valueOf((byte) socksVersionNumber);
390+
int socksVersionNumber = ConfigurationHelper.getIntProperty(TransportConstants.SOCKS_VERSION_PROP_NAME, TransportConstants.DEFAULT_SOCKS_VERSION, configuration);
391+
socksVersion = SocksVersion.valueOf((byte) socksVersionNumber);
392392

393-
proxyUsername = ConfigurationHelper.getStringProperty(TransportConstants.PROXY_USERNAME_PROP_NAME, TransportConstants.DEFAULT_PROXY_USERNAME, configuration);
394-
proxyPassword = ConfigurationHelper.getStringProperty(TransportConstants.PROXY_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_PROXY_PASSWORD, configuration);
393+
socksUsername = ConfigurationHelper.getStringProperty(TransportConstants.SOCKS_USERNAME_PROP_NAME, TransportConstants.DEFAULT_SOCKS_USERNAME, configuration);
394+
socksPassword = ConfigurationHelper.getStringProperty(TransportConstants.SOCKS_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_SOCKS_PASSWORD, configuration);
395395

396-
proxyRemoteDNS = ConfigurationHelper.getBooleanProperty(TransportConstants.PROXY_REMOTE_DNS_PROP_NAME, TransportConstants.DEFAULT_PROXY_REMOTE_DNS, configuration);
396+
socksRemoteDNS = ConfigurationHelper.getBooleanProperty(TransportConstants.SOCKS_REMOTE_DNS_PROP_NAME, TransportConstants.SOCKS_PROXY_REMOTE_DNS, configuration);
397397
}
398398

399399
remotingThreads = ConfigurationHelper.getIntProperty(TransportConstants.NIO_REMOTING_THREADS_PROPNAME, -1, configuration);
@@ -656,19 +656,19 @@ public synchronized void start() {
656656
public void initChannel(Channel channel) throws Exception {
657657
final ChannelPipeline pipeline = channel.pipeline();
658658

659-
if (proxyEnabled && (proxyRemoteDNS || !isTargetLocalHost())) {
660-
InetSocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort);
661-
ProxyHandler proxyHandler = switch (proxyVersion) {
662-
case SOCKS5 -> new Socks5ProxyHandler(proxyAddress, proxyUsername, proxyPassword);
663-
case SOCKS4a -> new Socks4ProxyHandler(proxyAddress, proxyUsername);
659+
if (socksEnabled && (socksRemoteDNS || !isTargetLocalHost())) {
660+
InetSocketAddress proxyAddress = new InetSocketAddress(socksHost, socksPort);
661+
ProxyHandler proxyHandler = switch (socksVersion) {
662+
case SOCKS5 -> new Socks5ProxyHandler(proxyAddress, socksUsername, socksPassword);
663+
case SOCKS4a -> new Socks4ProxyHandler(proxyAddress, socksUsername);
664664
default -> throw new IllegalArgumentException("Unknown SOCKS proxy version");
665665
};
666666

667667
channel.pipeline().addLast(proxyHandler);
668668

669-
logger.debug("Using a SOCKS proxy at {}:{}", proxyHost, proxyPort);
669+
logger.debug("Using a SOCKS proxy at {}:{}", socksHost, socksPort);
670670

671-
if (proxyRemoteDNS) {
671+
if (socksRemoteDNS) {
672672
bootstrap.resolver(NoopAddressResolverGroup.INSTANCE);
673673
}
674674
}
@@ -876,7 +876,7 @@ public final Connection createConnection(Consumer<ChannelFuture> onConnect) {
876876

877877
public NettyConnection createConnection(Consumer<ChannelFuture> onConnect, String host, int port) {
878878
InetSocketAddress remoteDestination;
879-
if (proxyEnabled && proxyRemoteDNS) {
879+
if (socksEnabled && socksRemoteDNS) {
880880
remoteDestination = InetSocketAddress.createUnresolved(IPV6Util.stripBracketsAndZoneID(host), port);
881881
} else {
882882
remoteDestination = new InetSocketAddress(IPV6Util.stripBracketsAndZoneID(host), port);

artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class TransportConstants {
3636

3737
public static final String SSL_ENABLED_PROP_NAME = "sslEnabled";
3838

39+
public static final String PROXY_PROTOCOL_ENABLED_PROP_NAME = "proxyProtocolEnabled";
40+
3941
public static final String SSL_AUTO_RELOAD_PROP_NAME = "sslAutoReload";
4042

4143
public static final boolean DEFAULT_SSL_AUTO_RELOAD = false;
@@ -186,26 +188,28 @@ public class TransportConstants {
186188

187189
public static final int STOMP_DEFAULT_CONSUMER_WINDOW_SIZE = 10 * 1024; // 10K
188190

189-
public static final String PROXY_ENABLED_PROP_NAME = "socksEnabled";
191+
public static final String SOCKS_ENABLED_PROP_NAME = "socksEnabled";
190192

191-
public static final String PROXY_HOST_PROP_NAME = "socksHost";
193+
public static final String SOCKS_HOST_PROP_NAME = "socksHost";
192194

193-
public static final String PROXY_PORT_PROP_NAME = "socksPort";
195+
public static final String SOCKS_PORT_PROP_NAME = "socksPort";
194196

195-
public static final String PROXY_VERSION_PROP_NAME = "socksVersion";
197+
public static final String SOCKS_VERSION_PROP_NAME = "socksVersion";
196198

197-
public static final String PROXY_USERNAME_PROP_NAME = "socksUsername";
199+
public static final String SOCKS_USERNAME_PROP_NAME = "socksUsername";
198200

199-
public static final String PROXY_PASSWORD_PROP_NAME = "socksPassword";
201+
public static final String SOCKS_PASSWORD_PROP_NAME = "socksPassword";
200202

201-
public static final String PROXY_REMOTE_DNS_PROP_NAME = "socksRemoteDNS";
203+
public static final String SOCKS_REMOTE_DNS_PROP_NAME = "socksRemoteDNS";
202204

203205
public static final String AUTO_START = "autoStart";
204206

205207
public static final boolean DEFAULT_AUTO_START = true;
206208

207209
public static final boolean DEFAULT_SSL_ENABLED = false;
208210

211+
public static final boolean DEFAULT_PROXY_PROTOCOL_ENABLED = false;
212+
209213
public static final String DEFAULT_SNIHOST_CONFIG = null;
210214

211215
public static final boolean DEFAULT_USE_GLOBAL_WORKER_POOL = true;
@@ -381,19 +385,19 @@ public class TransportConstants {
381385
*/
382386
public static final int DEFAULT_SHUTDOWN_TIMEOUT = parseDefaultVariable("DEFAULT_SHUTDOWN_TIMEOUT", 3_000);
383387

384-
public static final boolean DEFAULT_PROXY_ENABLED = false;
388+
public static final boolean DEFAULT_SOCKS_ENABLED = false;
385389

386-
public static final String DEFAULT_PROXY_HOST = null;
390+
public static final String DEFAULT_SOCKS_HOST = null;
387391

388-
public static final int DEFAULT_PROXY_PORT = 0;
392+
public static final int DEFAULT_SOCKS_PORT = 0;
389393

390-
public static final byte DEFAULT_PROXY_VERSION = SocksVersion.SOCKS5.byteValue();
394+
public static final byte DEFAULT_SOCKS_VERSION = SocksVersion.SOCKS5.byteValue();
391395

392-
public static final String DEFAULT_PROXY_USERNAME = null;
396+
public static final String DEFAULT_SOCKS_USERNAME = null;
393397

394-
public static final String DEFAULT_PROXY_PASSWORD = null;
398+
public static final String DEFAULT_SOCKS_PASSWORD = null;
395399

396-
public static final boolean DEFAULT_PROXY_REMOTE_DNS = false;
400+
public static final boolean SOCKS_PROXY_REMOTE_DNS = false;
397401

398402
public static final String ROUTER = "router";
399403

@@ -479,6 +483,7 @@ private static int parseDefaultVariable(String variableName, int defaultValue) {
479483
allowableAcceptorKeys.add(TransportConstants.DISABLE_STOMP_SERVER_HEADER);
480484
allowableAcceptorKeys.add(TransportConstants.AUTO_START);
481485
allowableAcceptorKeys.add(TransportConstants.ROUTER);
486+
allowableAcceptorKeys.add(TransportConstants.PROXY_PROTOCOL_ENABLED_PROP_NAME);
482487

483488
ALLOWABLE_ACCEPTOR_KEYS = Collections.unmodifiableSet(allowableAcceptorKeys);
484489

@@ -525,13 +530,13 @@ private static int parseDefaultVariable(String variableName, int defaultValue) {
525530
allowableConnectorKeys.add(TransportConstants.NIO_REMOTING_THREADS_PROPNAME);
526531
allowableConnectorKeys.add(TransportConstants.REMOTING_THREADS_PROPNAME);
527532
allowableConnectorKeys.add(TransportConstants.BATCH_DELAY);
528-
allowableConnectorKeys.add(TransportConstants.PROXY_ENABLED_PROP_NAME);
529-
allowableConnectorKeys.add(TransportConstants.PROXY_HOST_PROP_NAME);
530-
allowableConnectorKeys.add(TransportConstants.PROXY_PORT_PROP_NAME);
531-
allowableConnectorKeys.add(TransportConstants.PROXY_VERSION_PROP_NAME);
532-
allowableConnectorKeys.add(TransportConstants.PROXY_USERNAME_PROP_NAME);
533-
allowableConnectorKeys.add(TransportConstants.PROXY_PASSWORD_PROP_NAME);
534-
allowableConnectorKeys.add(TransportConstants.PROXY_REMOTE_DNS_PROP_NAME);
533+
allowableConnectorKeys.add(TransportConstants.SOCKS_ENABLED_PROP_NAME);
534+
allowableConnectorKeys.add(TransportConstants.SOCKS_HOST_PROP_NAME);
535+
allowableConnectorKeys.add(TransportConstants.SOCKS_PORT_PROP_NAME);
536+
allowableConnectorKeys.add(TransportConstants.SOCKS_VERSION_PROP_NAME);
537+
allowableConnectorKeys.add(TransportConstants.SOCKS_USERNAME_PROP_NAME);
538+
allowableConnectorKeys.add(TransportConstants.SOCKS_PASSWORD_PROP_NAME);
539+
allowableConnectorKeys.add(TransportConstants.SOCKS_REMOTE_DNS_PROP_NAME);
535540
allowableConnectorKeys.add(ActiveMQDefaultConfiguration.getPropMaskPassword());
536541
allowableConnectorKeys.add(ActiveMQDefaultConfiguration.getPropPasswordCodec());
537542
allowableConnectorKeys.add(TransportConstants.NETTY_CONNECT_TIMEOUT);

artemis-features/src/main/resources/features.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<bundle>mvn:io.netty/netty-buffer/${netty.version}</bundle>
3636
<bundle>mvn:io.netty/netty-codec/${netty.version}</bundle>
3737
<bundle>mvn:io.netty/netty-codec-socks/${netty.version}</bundle>
38+
<bundle>mvn:io.netty/netty-codec-haproxy/${netty.version}</bundle>
3839
<bundle>mvn:io.netty/netty-codec-http/${netty.version}</bundle>
3940
<bundle>mvn:io.netty/netty-handler/${netty.version}</bundle>
4041
<bundle>mvn:io.netty/netty-handler-proxy/${netty.version}</bundle>

artemis-pom/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@
431431
<version>${netty.version}</version>
432432
<!-- License: Apache 2.0 -->
433433
</dependency>
434+
<dependency>
435+
<groupId>io.netty</groupId>
436+
<artifactId>netty-codec-haproxy</artifactId>
437+
<version>${netty.version}</version>
438+
<!-- License: Apache 2.0 -->
439+
</dependency>
434440
<dependency>
435441
<groupId>io.netty</groupId>
436442
<artifactId>netty-codec-socks</artifactId>

artemis-server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@
106106
<groupId>io.netty</groupId>
107107
<artifactId>netty-codec-http</artifactId>
108108
</dependency>
109+
<dependency>
110+
<groupId>io.netty</groupId>
111+
<artifactId>netty-codec-haproxy</artifactId>
112+
</dependency>
109113
<dependency>
110114
<groupId>io.netty</groupId>
111115
<artifactId>netty-common</artifactId>

artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConnectionField.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ public enum ConnectionField {
2929
LOCAL_ADDRESS("localAddress"),
3030
SESSION_ID("sessionID"),
3131
CREATION_TIME("creationTime"),
32-
IMPLEMENTATION("implementation");
32+
IMPLEMENTATION("implementation"),
33+
PROXY_ADDRESS("proxyAddress"),
34+
PROXY_PROTOCOL_VERSION("proxyProtocolVersion");
3335

3436
private static final Map<String, ConnectionField> lookup = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
3537

0 commit comments

Comments
 (0)