Support mTLS for gRPC channel (#15)
This commit is contained in:
parent
d47f9cc370
commit
dd0d8afe88
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`|
|
||||
|
|
|
|||
Loading…
Reference in New Issue