You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by da...@apache.org on 2021/08/29 09:12:12 UTC
[skywalking-java] 01/01: Support mTLS for gRPC channel
This is an automated email from the ASF dual-hosted git repository.
daming pushed a commit to branch mtls
in repository https://gitbox.apache.org/repos/asf/skywalking-java.git
commit 3d31f0e59da6702bfa9952272e7ddfb920b00d12
Author: daming <zt...@foxmail.com>
AuthorDate: Sun Aug 29 17:10:31 2021 +0800
Support mTLS for gRPC channel
---
CHANGES.md | 1 +
.../skywalking/apm/agent/core/conf/Config.java | 15 ++++
.../apm/agent/core/remote/TLSChannelBuilder.java | 55 ++++++++++++---
.../apm/agent/core/util/PrivateKeyUtil.java | 80 ++++++++++++++++++++++
docs/en/setup/service-agent/java-agent/TLS.md | 21 ++++--
5 files changed, 157 insertions(+), 15 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..ca7c04d 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,64 @@ 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);
- boolean isCAFileExist = caFile.exists() && caFile.isFile();
- if (Config.Agent.FORCE_TLS || isCAFileExist) {
+ NettyChannelBuilder managedChannelBuilder) throws AgentPackageNotFoundException, IOException {
+
+ File caFile = new File(toAbsolutePath(Config.Agent.SSL_TRUSTED_CA_PATH));
+ if (Config.Agent.FORCE_TLS || caFile.isFile()) {
SslContextBuilder builder = GrpcSslContexts.forClient();
- if (isCAFileExist) {
+
+ if (caFile.isFile()) {
+ 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(toAbsolutePath(keyPath));
+ File certFile = new File(toAbsolutePath(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;
}
+
+ private static String toAbsolutePath(final String path) throws AgentPackageNotFoundException {
+ if (path.startsWith("/")) {
+ return path;
+ } else if (path.startsWith("./")) {
+ return AgentPackagePath.getPath() + path.substring(1);
+ } else {
+ return AgentPackagePath.getPath() + "/" + path;
+ }
+ }
+
}
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/docs/en/setup/service-agent/java-agent/TLS.md b/docs/en/setup/service-agent/java-agent/TLS.md
index 05cc1a2..79a515c 100644
--- a/docs/en/setup/service-agent/java-agent/TLS.md
+++ b/docs/en/setup/service-agent/java-agent/TLS.md
@@ -17,10 +17,23 @@ Only support **no mutual auth**.
## 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 open TLS automatically after the `ca.crt`(by default `/ca` folder in agent package) file 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 enabled mTLS. More details see `receiver-sharing-server` section in `application.yaml`. Please refer to [gRPC SSL](../../backend/grpc-ssl.md)
+- Configure Client-side SSL/TLS in `agent.conf`.
+- Change `SW_AGENT_COLLECTOR_BACKEND_SERVICES` 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:/path/to/ca.crt}
+agent.ssl_key_path=${SW_AGENT_SSL_KEY_PATH:/path/to/client.pem}
+agent.ssl_cert_chain_path=${SW_AGENT_SSL_CERT_CHAIN_PATH:/path/to/client.crt}
+
+collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:skywalking-oap:11801