This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-java.git
The following commit(s) were added to refs/heads/main by this push:
new dd0d8af Support mTLS for gRPC channel (#15)
dd0d8af is described below
commit dd0d8afe8849d6e86a1e0d61933a13172f782309
Author: Daming <[email protected]>
AuthorDate: Tue Aug 31 15:56:30 2021 +0800
Support mTLS for gRPC channel (#15)
---
CHANGES.md | 1 +
.../skywalking/apm/agent/core/conf/Config.java | 15 ++++
.../apm/agent/core/remote/TLSChannelBuilder.java | 39 ++++++++---
.../apm/agent/core/util/PrivateKeyUtil.java | 80 ++++++++++++++++++++++
apm-sniffer/config/agent.config | 8 +++
docs/en/setup/service-agent/java-agent/TLS.md | 43 +++++++++---
.../service-agent/java-agent/configurations.md | 3 +
7 files changed, 172 insertions(+), 17 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 04cb623..f97699e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -14,6 +14,7 @@ Release Notes.
* Fix kafka-reporter-plugin shade package conflict
* Add all config items to `agent.conf` file for convenient containerization
use cases.
* Advanced Kafka Producer configuration enhancement.
+* Suport mTLS for gRPC channel.
#### Documentation
diff --git
a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java
b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java
index 85c2b9c..b646416 100755
---
a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java
+++
b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java
@@ -135,6 +135,21 @@ public class Config {
* Force open TLS for gRPC channel if true.
*/
public static boolean FORCE_TLS = false;
+
+ /**
+ * SSL trusted ca file. If it exists, will enable TLS for gRPC channel.
+ */
+ public static String SSL_TRUSTED_CA_PATH = "ca" +
Constants.PATH_SEPARATOR + "ca.crt";
+
+ /**
+ * Key cert chain file. If ssl_cert_chain and ssl_key exist, will
enable mTLS for gRPC channel.
+ */
+ public static String SSL_CERT_CHAIN_PATH;
+
+ /**
+ * Private key file. If ssl_cert_chain and ssl_key exist, will enable
mTLS for gRPC channel.
+ */
+ public static String SSL_KEY_PATH;
}
public static class OsInfo {
diff --git
a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/TLSChannelBuilder.java
b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/TLSChannelBuilder.java
index 5a5e769..6732467 100644
---
a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/TLSChannelBuilder.java
+++
b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/remote/TLSChannelBuilder.java
@@ -23,31 +23,54 @@ import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.File;
-import javax.net.ssl.SSLException;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import org.apache.skywalking.apm.agent.core.boot.AgentPackageNotFoundException;
import org.apache.skywalking.apm.agent.core.boot.AgentPackagePath;
import org.apache.skywalking.apm.agent.core.conf.Config;
-import org.apache.skywalking.apm.agent.core.conf.Constants;
+import org.apache.skywalking.apm.agent.core.logging.api.ILog;
+import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
+import org.apache.skywalking.apm.agent.core.util.PrivateKeyUtil;
+import org.apache.skywalking.apm.util.StringUtil;
/**
- * Detect the `/ca` folder in agent package, if `ca.crt` exists, start TLS (no
mutual auth).
+ * If only ca.crt exists, start TLS. If cert, key and ca files exist, enable
mTLS.
*/
public class TLSChannelBuilder implements ChannelBuilder<NettyChannelBuilder> {
- private static String CA_FILE_NAME = "ca" + Constants.PATH_SEPARATOR +
"ca.crt";
+ private static final ILog LOGGER =
LogManager.getLogger(TLSChannelBuilder.class);
@Override
public NettyChannelBuilder build(
- NettyChannelBuilder managedChannelBuilder) throws
AgentPackageNotFoundException, SSLException {
- File caFile = new File(AgentPackagePath.getPath(), CA_FILE_NAME);
+ NettyChannelBuilder managedChannelBuilder) throws
AgentPackageNotFoundException, IOException {
+
+ File caFile = new File(AgentPackagePath.getPath(),
Config.Agent.SSL_TRUSTED_CA_PATH);
boolean isCAFileExist = caFile.exists() && caFile.isFile();
if (Config.Agent.FORCE_TLS || isCAFileExist) {
SslContextBuilder builder = GrpcSslContexts.forClient();
+
if (isCAFileExist) {
+ String certPath = Config.Agent.SSL_CERT_CHAIN_PATH;
+ String keyPath = Config.Agent.SSL_KEY_PATH;
+ if (StringUtil.isNotBlank(certPath) &&
StringUtil.isNotBlank(keyPath)) {
+ File keyFile = new File(AgentPackagePath.getPath(),
keyPath);
+ File certFile = new File(AgentPackagePath.getPath(),
certPath);
+
+ if (certFile.isFile() && keyFile.isFile()) {
+ try (InputStream cert = new FileInputStream(certFile);
+ InputStream key =
PrivateKeyUtil.loadDecryptionKey(keyPath)) {
+ builder.keyManager(cert, key);
+ }
+ } else if (!certFile.isFile() || !keyFile.isFile()) {
+ LOGGER.warn("Failed to enable mTLS caused by cert or
key cannot be found.");
+ }
+ }
+
builder.trustManager(caFile);
}
- managedChannelBuilder =
managedChannelBuilder.negotiationType(NegotiationType.TLS)
-
.sslContext(builder.build());
+
managedChannelBuilder.negotiationType(NegotiationType.TLS).sslContext(builder.build());
}
return managedChannelBuilder;
}
+
}
diff --git
a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/PrivateKeyUtil.java
b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/PrivateKeyUtil.java
new file mode 100644
index 0000000..c0c0625
--- /dev/null
+++
b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/PrivateKeyUtil.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.apm.agent.core.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Base64;
+
+/**
+ * Util intends to parse PKCS#1 and PKCS#8 at same time.
+ */
+public class PrivateKeyUtil {
+ private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE
KEY-----";
+ private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE
KEY-----";
+ private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE
KEY-----";
+ private static final String PKCS_8_PEM_FOOTER = "-----END PRIVATE
KEY-----";
+
+ /**
+ * Load a RSA decryption key from a file (PEM or DER).
+ */
+ public static InputStream loadDecryptionKey(String keyFilePath) throws
IOException {
+ byte[] keyDataBytes = Files.readAllBytes(Paths.get(keyFilePath));
+ String keyDataString = new String(keyDataBytes,
StandardCharsets.UTF_8);
+
+ if (keyDataString.contains(PKCS_1_PEM_HEADER)) {
+ // OpenSSL / PKCS#1 Base64 PEM encoded file
+ keyDataString = keyDataString.replace(PKCS_1_PEM_HEADER, "");
+ keyDataString = keyDataString.replace(PKCS_1_PEM_FOOTER, "");
+ keyDataString = keyDataString.replace("\n", "");
+ return
readPkcs1PrivateKey(Base64.getDecoder().decode(keyDataString));
+ }
+
+ return new ByteArrayInputStream(keyDataString.getBytes());
+ }
+
+ /**
+ * Create a InputStream instance from raw PKCS#1 bytes. Raw Java API can't
recognize ASN.1 format, so we should
+ * convert it into a pkcs#8 format Java can understand.
+ */
+ private static InputStream readPkcs1PrivateKey(byte[] pkcs1Bytes) {
+ int pkcs1Length = pkcs1Bytes.length;
+ int totalLength = pkcs1Length + 22;
+ byte[] pkcs8Header = new byte[] {
+ 0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte)
(totalLength & 0xff), // Sequence + total length
+ 0x2, 0x1, 0x0, // Integer (0)
+ 0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte)
0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
+ 0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte)
(pkcs1Length & 0xff) // Octet string + length
+ };
+ StringBuilder pkcs8 = new StringBuilder(PKCS_8_PEM_HEADER);
+ pkcs8.append("\n").append(new
String(Base64.getEncoder().encode(join(pkcs8Header, pkcs1Bytes))));
+ pkcs8.append("\n").append(PKCS_8_PEM_FOOTER);
+ return new ByteArrayInputStream(pkcs8.toString().getBytes());
+ }
+
+ private static byte[] join(byte[] byteArray1, byte[] byteArray2) {
+ byte[] bytes = new byte[byteArray1.length + byteArray2.length];
+ System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
+ System.arraycopy(byteArray2, 0, bytes, byteArray1.length,
byteArray2.length);
+ return bytes;
+ }
+}
diff --git a/apm-sniffer/config/agent.config b/apm-sniffer/config/agent.config
index 5f95696..dd3b654 100755
--- a/apm-sniffer/config/agent.config
+++ b/apm-sniffer/config/agent.config
@@ -68,6 +68,14 @@ agent.keep_tracing=${SW_AGENT_KEEP_TRACING:false}
# If true, SkyWalking agent uses TLS even no CA file detected.
agent.force_tls=${SW_AGENT_FORCE_TLS:false}
+# gRPC SSL trusted ca file.
+agent.ssl_trusted_ca_path=${SW_AGENT_SSL_TRUSTED_CA_PATH:/ca/ca.crt}
+
+# enable mTLS when ssl_key_path and ssl_cert_chain_path exist.
+agent.ssl_key_path=${SW_AGENT_SSL_KEY_PATH:}
+
+agent.ssl_cert_chain_path=${SW_AGENT_SSL_CERT_CHAIN_PATH:}
+
# Limit the length of the ipv4 list size.
osinfo.ipv4_list_size=${SW_AGENT_OSINFO_IPV4_LIST_SIZE:10}
diff --git a/docs/en/setup/service-agent/java-agent/TLS.md
b/docs/en/setup/service-agent/java-agent/TLS.md
index 05cc1a2..5ae9793 100644
--- a/docs/en/setup/service-agent/java-agent/TLS.md
+++ b/docs/en/setup/service-agent/java-agent/TLS.md
@@ -7,20 +7,45 @@ at the same time, the SkyWalking backend is in another region
(VPC).
>
> Because of that, security requirement is very obvious.
+## Creating SSL/TLS Certificates
+
+The first step is to generate certificates and key files for encrypting
communication. This is
+fairly straightforward: use `openssl` from the command line.
+
+Use this [script](../../../../../tools/TLS/tls_key_generate.sh) if you are not
familiar with how to generate key files.
+
+We need the following files:
+ - `client.pem`: A private RSA key to sign and authenticate the public key.
It's either a PKCS#8(PEM) or PKCS#1(DER).
+ - `client.crt`: Self-signed X.509 public keys for distribution.
+ - `ca.crt`: A certificate authority public key for a client to validate the
server's certificate.
+
## Authentication Mode
-Only support **no mutual auth**.
-- Use this [script](../../../../../tools/TLS/tls_key_generate.sh) if you are
not familiar with how to generate key files.
-- Find `ca.crt`, and use it at client side
-- Find `server.crt` ,`server.pem` and `ca.crt`. Use them at server side.
Please refer to `gRPC SSL` of the OAP server doc.
- for more details.
+- Find `ca.crt`, and use it at client side. In `mTLS` mode, `client.crt` and
`client.pem` are required at client side.
+- Find `server.crt`, `server.pem` and `ca.crt`. Use them at server side.
Please refer to `gRPC Security` of the OAP server doc for more details.
## Open and config TLS
### Agent config
-- Place `ca.crt` into `/ca` folder in agent package. Notice, `/ca` is not
created in distribution, please create it by yourself.
-
-- Agent open TLS automatically after the `/ca/ca.crt` file detected.
+- Agent enables TLS automatically after the `ca.crt`(by default `/ca` folder
in agent package) file is detected.
- TLS with no CA mode could be activated by this setting.
```
-agent.force_tls=${SW_AGENT_FORCE_TLS:false}
+agent.force_tls=${SW_AGENT_FORCE_TLS:true}
+```
+
+## Enable mutual TLS
+- Sharing gRPC server must be started with mTLS enabled. More details can be
found in `receiver-sharing-server` section in `application.yaml`. Please refer
to `gRPC Security` and `gRPC/HTTP server for receiver`.
+- Copy CA certificate, certificate and private key of client into `agent/ca`.
+- Configure client-side SSL/TLS in `agent.conf`.
+- Change `SW_AGENT_COLLECTOR_BACKEND_SERVICES` targeting to host and port of
`receiver-sharing-server`.
+
+For example:
```
+agent.force_tls=${SW_AGENT_FORCE_TLS:true}
+agent.ssl_trusted_ca_path=${SW_AGENT_SSL_TRUSTED_CA_PATH:/ca/ca.crt}
+agent.ssl_key_path=${SW_AGENT_SSL_KEY_PATH:/ca/client.pem}
+agent.ssl_cert_chain_path=${SW_AGENT_SSL_CERT_CHAIN_PATH:/ca/client.crt}
+
+collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:skywalking-oap:11801}
+```
+
+Notice, the client-side's certificate and the private key are from the same CA
certificate with server-side.
diff --git a/docs/en/setup/service-agent/java-agent/configurations.md
b/docs/en/setup/service-agent/java-agent/configurations.md
index 5c14407..324dbd9 100644
--- a/docs/en/setup/service-agent/java-agent/configurations.md
+++ b/docs/en/setup/service-agent/java-agent/configurations.md
@@ -20,6 +20,9 @@ property key | Description | Default |
`agent.operation_name_threshold `|The operationName max length, setting this
value > 190 is not recommended.|`150`|
`agent.keep_tracing`|Keep tracing even the backend is not available if this
value is `true`.|`false`|
`agent.force_tls`|Force open TLS for gRPC channel if this value is
`true`.|`false`|
+`agent.ssl_trusted_ca_path` | gRPC SSL trusted ca file. | `/ca/ca.crt` |
+`agent.ssl_key_path`| The private key file. Enable mTLS when ssl_key_path and
ssl_cert_chain_path exist. | `""` |
+`agent.ssl_cert_chain_path`| The certificate file. Enable mTLS when
ssl_key_path and ssl_cert_chain_path exist. | `""` |
`osinfo.ipv4_list_size`| Limit the length of the ipv4 list size. |`10`|
`collector.grpc_channel_check_interval`|grpc channel status check
interval.|`30`|
`collector.heartbeat_period`|agent heartbeat report period. Unit, second.|`30`|