Skip to content

Commit 4a29b7e

Browse files
authored
[ISSUE #10077] Support password-encrypted private keys for Proxy TLS (#10078)
* [ISSUE #10077] Support password-encrypted private keys for Proxy TLS * Fix bazel error * Fix unit test * Add comments
1 parent 4c9f7a9 commit 4a29b7e

File tree

11 files changed

+221
-6
lines changed

11 files changed

+221
-6
lines changed

docs/cn/Configuration_TLS.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,45 @@ public class ExampleProducer {
126126

127127
// Send messages as usual.
128128
producer.shutdown();
129-
}
129+
}
130130
}
131-
```
131+
```
132+
133+
## 5 Proxy TLS 配置
134+
135+
RocketMQ Proxy 使用 `rmq-proxy.json`(而非 `tls.properties`)进行 TLS 配置。Proxy 的 gRPC 和 Remoting 协议端口均支持 TLS。
136+
137+
### 5.1 配置 rmq-proxy.json
138+
139+
`distribution/conf/rmq-proxy.json` 中添加 TLS 相关字段:
140+
141+
```json
142+
{
143+
"rocketMQClusterName": "DefaultCluster",
144+
"tlsTestModeEnable": false,
145+
"tlsKeyPath": "/opt/certFiles/server.key",
146+
"tlsKeyPassword": "123456",
147+
"tlsCertPath": "/opt/certFiles/server.pem"
148+
}
149+
```
150+
151+
| 字段 | 类型 | 默认值 | 说明 |
152+
|------|------|--------|------|
153+
| `tlsTestModeEnable` | boolean | `true` | 是否使用自签名测试证书,生产环境需设为 `false` |
154+
| `tlsKeyPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.key` | 服务端私钥文件路径(PKCS#8 PEM 格式) |
155+
| `tlsKeyPassword` | string | `""` | 加密私钥的密码,私钥未加密时留空 |
156+
| `tlsCertPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.crt` | 服务端证书链文件路径(X.509 PEM 格式) |
157+
| `tlsCertWatchIntervalMs` | int | `3600000` | 证书文件变更检测间隔(毫秒) |
158+
159+
### 5.2 配置 Proxy 启动参数
160+
161+
编辑 `runproxy.sh`(或启动 Proxy 的脚本),启用 TLS enforcing 模式:
162+
163+
```shell
164+
JAVA_OPT="${JAVA_OPT} -Dtls.server.mode=enforcing"
165+
```
166+
167+
三种 TLS 模式说明:
168+
- `disabled` - 不支持 TLS,拒绝所有 TLS 握手请求
169+
- `permissive` - TLS 可选,同时接受 TLS 和非 TLS 连接
170+
- `enforcing` - 强制 TLS,拒绝非 TLS 连接

docs/en/Configuration_TLS.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,45 @@ public class ExampleProducer {
118118

119119
// Send messages as usual.
120120
producer.shutdown();
121-
}
121+
}
122122
}
123123
```
124+
125+
## 5 Proxy TLS Configuration
126+
127+
RocketMQ Proxy uses `rmq-proxy.json` (not `tls.properties`) for TLS configuration. The proxy supports TLS for both its gRPC and Remoting protocol endpoints.
128+
129+
### 5.1 Configure rmq-proxy.json
130+
131+
Add TLS-related fields to `distribution/conf/rmq-proxy.json`:
132+
133+
```json
134+
{
135+
"rocketMQClusterName": "DefaultCluster",
136+
"tlsTestModeEnable": false,
137+
"tlsKeyPath": "/opt/certFiles/server.key",
138+
"tlsKeyPassword": "123456",
139+
"tlsCertPath": "/opt/certFiles/server.pem"
140+
}
141+
```
142+
143+
| Field | Type | Default | Description |
144+
|-------|------|---------|-------------|
145+
| `tlsTestModeEnable` | boolean | `true` | Use self-signed certificates for testing. Set to `false` for production. |
146+
| `tlsKeyPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.key` | Path to the server private key file (PKCS#8 PEM format). |
147+
| `tlsKeyPassword` | string | `""` | Password for the encrypted private key. Leave empty if the key is not encrypted. |
148+
| `tlsCertPath` | string | `${PROXY_HOME}/conf/tls/rocketmq.crt` | Path to the server certificate chain file (X.509 PEM format). |
149+
| `tlsCertWatchIntervalMs` | int | `3600000` | Interval in milliseconds to check for certificate file changes. |
150+
151+
### 5.2 Update Proxy JVM parameters
152+
153+
Edit `runproxy.sh` (or the script that launches the proxy) to enable TLS enforcing mode:
154+
155+
```shell
156+
JAVA_OPT="${JAVA_OPT} -Dtls.server.mode=enforcing"
157+
```
158+
159+
The three available TLS modes are:
160+
- `disabled` - TLS is not supported; incoming TLS handshakes are rejected.
161+
- `permissive` - TLS is optional; the proxy accepts both TLS and non-TLS connections.
162+
- `enforcing` - TLS is required; non-TLS connections are rejected.

proxy/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ java_library(
7575
"src/test/resources/rmq-proxy-home/conf/broker.conf",
7676
"src/test/resources/rmq-proxy-home/conf/logback_proxy.xml",
7777
"src/test/resources/rmq-proxy-home/conf/rmq-proxy.json",
78-
],
78+
] + glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]),
7979
visibility = ["//visibility:public"],
8080
deps = [
8181
"//auth",

proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public class ProxyConfig implements ConfigFile {
8181
*/
8282
private boolean tlsTestModeEnable = true;
8383
private String tlsKeyPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.key";
84+
private String tlsKeyPassword = "";
8485
private String tlsCertPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.crt";
8586
private int tlsCertWatchIntervalMs = 60 * 60 * 1000; // 1 hour
8687
/**
@@ -501,6 +502,14 @@ public void setTlsKeyPath(String tlsKeyPath) {
501502
this.tlsKeyPath = tlsKeyPath;
502503
}
503504

505+
public String getTlsKeyPassword() {
506+
return tlsKeyPassword;
507+
}
508+
509+
public void setTlsKeyPassword(String tlsKeyPassword) {
510+
this.tlsKeyPassword = tlsKeyPassword;
511+
}
512+
504513
public String getTlsCertPath() {
505514
return tlsCertPath;
506515
}

proxy/src/main/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,14 @@ public static void loadSslContext() throws CertificateException, IOException {
123123
} else {
124124
String tlsCertPath = ConfigurationManager.getProxyConfig().getTlsCertPath();
125125
String tlsKeyPath = ConfigurationManager.getProxyConfig().getTlsKeyPath();
126+
String tlsKeyPassword = ConfigurationManager.getProxyConfig().getTlsKeyPassword();
126127
try (InputStream serverKeyInputStream = Files.newInputStream(
127128
Paths.get(tlsKeyPath));
128129
InputStream serverCertificateStream = Files.newInputStream(
129130
Paths.get(tlsCertPath))) {
130131
sslContext = GrpcSslContexts.forServer(serverCertificateStream,
131-
serverKeyInputStream)
132+
serverKeyInputStream,
133+
StringUtils.isNotBlank(tlsKeyPassword) ? tlsKeyPassword : null)
132134
.trustManager(InsecureTrustManagerFactory.INSTANCE)
133135
.clientAuth(ClientAuth.NONE)
134136
.build();

proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ public RemotingProtocolServer(MessagingProcessor messagingProcessor, TlsCertific
118118
System.setProperty(TlsSystemConfig.TLS_SERVER_CERTPATH, config.getTlsCertPath());
119119
TlsSystemConfig.tlsServerKeyPath = config.getTlsKeyPath();
120120
System.setProperty(TlsSystemConfig.TLS_SERVER_KEYPATH, config.getTlsKeyPath());
121+
TlsSystemConfig.tlsServerKeyPassword = config.getTlsKeyPassword();
122+
System.setProperty(TlsSystemConfig.TLS_SERVER_KEYPASSWORD, config.getTlsKeyPassword());
121123
this.tlsCertificateManager = tlsCertificateManager;
122124
this.tlsReloadHandler = new RemotingTlsReloadHandler();
123125

proxy/src/test/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiatorTest.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,15 @@
2020
import io.grpc.netty.shaded.io.netty.buffer.ByteBuf;
2121
import io.grpc.netty.shaded.io.netty.buffer.Unpooled;
2222
import io.grpc.netty.shaded.io.netty.handler.codec.haproxy.HAProxyTLV;
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.io.InputStream;
2326
import java.nio.charset.StandardCharsets;
27+
import java.nio.file.Files;
28+
import java.nio.file.StandardCopyOption;
2429
import org.apache.rocketmq.proxy.config.ConfigurationManager;
30+
import org.apache.rocketmq.proxy.config.ProxyConfig;
31+
import org.junit.After;
2532
import org.junit.Before;
2633
import org.junit.Test;
2734
import org.junit.runner.RunWith;
@@ -39,11 +46,57 @@ public void setUp() throws Exception {
3946
negotiator = new ProxyAndTlsProtocolNegotiator();
4047
}
4148

49+
@After
50+
public void tearDown() {
51+
ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
52+
proxyConfig.setTlsTestModeEnable(true);
53+
proxyConfig.setTlsKeyPath("");
54+
proxyConfig.setTlsCertPath("");
55+
proxyConfig.setTlsKeyPassword("");
56+
}
57+
4258
@Test
4359
public void handleHAProxyTLV() {
4460
ByteBuf content = Unpooled.buffer();
4561
content.writeBytes("xxxx".getBytes(StandardCharsets.UTF_8));
4662
HAProxyTLV haProxyTLV = new HAProxyTLV((byte) 0xE1, content);
4763
negotiator.handleHAProxyTLV(haProxyTLV, Attributes.newBuilder());
4864
}
49-
}
65+
66+
@Test
67+
public void testLoadSslContextWithUnencryptedKey() throws Exception {
68+
configureTls("server.key", "server.pem", "");
69+
ProxyAndTlsProtocolNegotiator.loadSslContext();
70+
}
71+
72+
@Test
73+
public void testLoadSslContextWithEncryptedKey() throws Exception {
74+
// "1234" is the password of certs/client.key, inherited from remoting module test resources
75+
configureTls("client.key", "client.pem", "1234");
76+
ProxyAndTlsProtocolNegotiator.loadSslContext();
77+
}
78+
79+
@Test(expected = IllegalArgumentException.class)
80+
public void testLoadSslContextWithWrongPassword() throws Exception {
81+
configureTls("client.key", "client.pem", "wrong_password");
82+
ProxyAndTlsProtocolNegotiator.loadSslContext();
83+
}
84+
85+
private void configureTls(String keyFile, String certFile, String password) throws IOException {
86+
ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
87+
proxyConfig.setTlsTestModeEnable(false);
88+
proxyConfig.setTlsKeyPath(getCertsPath(keyFile));
89+
proxyConfig.setTlsCertPath(getCertsPath(certFile));
90+
proxyConfig.setTlsKeyPassword(password);
91+
}
92+
93+
private static String getCertsPath(String fileName) throws IOException {
94+
File tempFile = File.createTempFile(fileName, null);
95+
tempFile.deleteOnExit();
96+
try (InputStream is = ProxyAndTlsProtocolNegotiatorTest.class
97+
.getClassLoader().getResourceAsStream("certs/" + fileName)) {
98+
Files.copy(is, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
99+
}
100+
return tempFile.getAbsolutePath();
101+
}
102+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN ENCRYPTED PRIVATE KEY-----
2+
MIICoTAbBgkqhkiG9w0BBQMwDgQI1vtPpDhOYRcCAggABIICgMHwgw0p9fx95R/+
3+
cWnNdEq8I3ZOOy2wDjammFvPrYXcCJzS3Xg/0GDJ8pdJRKrI7253e4u3mxf5oMuY
4+
RrvpB3KfdelU1k/5QKqOxL/N0gQafQLViN53f6JelyBEAmO1UxQtKZtkTrdZg8ZP
5+
0u1cPPWxmgNdn1Xx3taMw+Wo05ysHjnHJhOEDQ2WT3VXigiRmFSX3H567yjYMRD+
6+
zmvBq+qqR9JPbH9Cn7X1oRXX6c8VsZHWF/Ds0I4i+5zJxsSIuNZxjZw9XXNgXtFv
7+
7FEFC0HDgDQQUY/FNPUbmjQUp1y0YxoOBjlyIqBIx5FWxu95p2xITS0OimQPFT0o
8+
IngaSb+EKRDhqpLxxIVEbDdkQrdRqcmmLGJioAysExTBDsDwkaEJGOp44bLDM4QW
9+
SIA9SB01omuCXgn7RjUyVXb5g0Lz+Nvsfp1YXUkPDO9hILfz3eMHDSW7/FzbB81M
10+
r8URaTagQxBZnvIoCoWszLDXn3JwEjpZEA6y55Naptps3mMRf7+XMt42lX0e4y9a
11+
ogNu5Zw/RZD9YcaTjC2z5XeKiMCs1Ymhy9iuzbo+eRGESqzvUE4VirtsiEwxJRci
12+
JHAvuAl3X4XnpTty4ahOU+DihM9lALxdU68CN9++7mx581pYuvjzrV+Z5+PuptZX
13+
AjCZmkZLDh8TCHSzWRqvP/Hcvo9BjW8l1Lq6tOa222PefSNCc6gs6Hq+jUghbabZ
14+
/ux4WuFc0Zd6bfQWAZohSvd78/ixsdJPNGm2OP+LUIrEDKIkLuH1PM0uq4wzJZNu
15+
Bo7oJ5iFWF67u3MC8oq+BqOVKDNWaCMi7iiSrN2XW8FBo/rpx4Lf/VYREL+Y0mP6
16+
vzJrZqw=
17+
-----END ENCRYPTED PRIVATE KEY-----
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDATCCAekCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV
3+
BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy
4+
b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw
5+
YWNoZS5vcmcwIBcNMTgwMTE2MDYxNjQ0WhgPMjExNzEyMjMwNjE2NDRaMIGSMQsw
6+
CQYDVQQGEwJDTjERMA8GA1UECAwIWmhlamlhbmcxETAPBgNVBAcMCEhhbmd6aG91
7+
MQ8wDQYDVQQKDAZhcGFjaGUxETAPBgNVBAsMCHJvY2tldG1xMRgwFgYDVQQDDA9h
8+
cGFjaGUgcm9ja2V0bXExHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcw
9+
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjPlSjZk37XLBJBc5G/qQNsNdVD
10+
vZnEGntrqW0UuHjF2T/LPtsGOavLP5wCHvn2zwMR2eCXZwKdKIzSvk0L3XOjH/XY
11+
OLgRa3cg90lV7Wzn9UMGq3nOjFtjIODPjtz3lwYAuAt1MH+K0E+ChuCFBgFqdY9U
12+
E0suW3DX0Mt/WB3pAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFGPaZKyCZzQihKj
13+
n/7I1J0wKl1HrU7N4sOie8E+ntcpKeX9zKYAou/4Iy0qwgxgRsnucB1rDous560a
14+
+8DFDU8+FnikK9cQtKfQqu4F266IkkXolviZMSfkmB+NIsByIl95eMJlQHVlAvnX
15+
vnpGdhD/Jhs+acE1VHhO6K+8omKLA6Og8MmYGRwmnBLcxIvqoSNDlEShfQyjaECg
16+
I4bEi4ZhH3lSHE46FybJdoxDbj9IjHWqpOnjM23EOyfd1zcwOZJA7a54kfOpiTjz
17+
wrtes5yoQznun5WtGcLM8ZmyaQ+Jr3j6NyZhOwULzK1+A8YUsW6Ww39xTxQoIHEQ
18+
7eirb54=
19+
-----END CERTIFICATE-----
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOsmp4YtrIRsBdBQ
3+
LyPImafCRynTJls3NNF4g6nZr9e0efBY830gw9kBebcm603sdZNl95fzRr2+srXi
4+
5FJbG7Fmq1+F0xLNK/kKWirGtNMT2DubmhVdKyXYJSvInoGRkrQzbOG0MdAyzE6Q
5+
O6OjjNN+xGkmadWyCyNF6S8YqMJTAgMBAAECgYEAj0OlnOIG0Ube4+N2VN7KfqKm
6+
qJy0Ka6gx14dGUY/E7Qo9n27GujzaSq09RkJExiVKZBeIH1fBAtC5f2uDV7kpy0l
7+
uNpTpQkbw0g2EQLxDsVwaUEYbu+t9qVeXoDd1vFeoXHBuRwvI9UW1BrxVtvKODia
8+
5StU8Lw4yjcm2lQalwECQQD/sKj56thIsIY7D9qBHk7fnFLd8aYzhnP2GsbZX4V/
9+
T1KHRxr/8MqdNQX53DE5qcyM/Mqu95FIpTAniUtvcBujAkEA62+fAMYFTAEWj4Z4
10+
vCmcoPqfVPWhBKFR/wo3L8uUARiIzlbYNU3LIqC2s16QO50+bLUd41oVHNw9Y+uM
11+
fxQpkQJACg/WpncSadHghmR6UchyjCQnsqo2wyJQX+fv2VAD/d2OPtqSem3sW0Fh
12+
6dI7cax36zhrdXUyl2xAt92URV9hBwJALX93sdWSxnpbWsc449wCydVFH00MnfFz
13+
AB+ARLtJ0eBk58M+qyZqgDmgtQ8sPmkH3EgwC3SoKdiiAIJPt2s1EQJBAKnISZZr
14+
qB2F2PfAW2JJbQlrPyVzkxhv9XYdiVNOErmuxLFae3AI7nECgGuFBtvmeqzm2yRj
15+
7RBMCmzyWG7MF3o=
16+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)