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 <[email protected]> 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
