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:11 UTC

[skywalking-java] branch mtls created (now 3d31f0e)

This is an automated email from the ASF dual-hosted git repository.

daming pushed a change to branch mtls
in repository https://gitbox.apache.org/repos/asf/skywalking-java.git.


      at 3d31f0e  Support mTLS for gRPC channel

This branch includes the following new commits:

     new 3d31f0e  Support mTLS for gRPC channel

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[skywalking-java] 01/01: Support mTLS for gRPC channel

Posted by da...@apache.org.
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