发布于 

grpc使用tls 通过openssl指定多个域名和IP

最近在使用grpc做项目,信息安全的同事提出要求,需要将来往报文加密,避免抓包。阅读grpc的文档,发现它已经支持ssl(tls),因此直接选这种认证和加密方式。

服务端和客户端代码参考grpc-java项目中的demo,摘取关键代码如下:

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private SslContextBuilder getSslContextBuilder() {
InputStream cacert = ZooPorterRpcServer.class.getClassLoader().getResourceAsStream("ssl/server.crt");
InputStream privkey = ZooPorterRpcServer.class.getClassLoader().getResourceAsStream("ssl/server.pem");
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(cacert, privkey);
return GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL);
}

private void start() throws IOException {
port = settings.getInt("server.port");
server = NettyServerBuilder.forPort(port).addService(BeanUtil.me().getBean(ElasticsearchServiceGrpcImpl.class))
.sslContext(getSslContextBuilder().build())
.build()
.start();

logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
ZooPorterRpcServer.this.stop();
logger.error("*** server shut down");
}
});
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected final ManagedChannel channel;

private static SslContext buildSslContext() throws SSLException {
SslContextBuilder builder = GrpcSslContexts.forClient();
builder.trustManager(ZooPorterClient.class.getClassLoader().getResourceAsStream("ssl/ca.crt"));
return builder.build();
}

public ZooPorterClient() throws SSLException {

channel = NettyChannelBuilder.forAddress(Settings.HOST, Settings.PORT)
.negotiationType(NegotiationType.TLS)
.sslContext(buildSslContext())
.build();
}

密钥和证书

上述代码中,ca.cert和server.pem/server.cert是通过openssl生成的密钥和证书,生成的脚本可以直接参考项目中的readme文档。

值得注意的是,按照readme中的脚本,只能生成一个指定了域名的证书(localhost),假使我们想通过多个域名甚至多个IP来访问服务(后端做负载均衡和服务发现),这样生成的证书就不合用了,访问时会抛异常。因此修改了脚本,下面脚本仅做记录使用。

1
2
3
4
5
6
7
8
9
openssl genrsa -passout pass:1111 -des3 -out ca.key 1024
openssl req -passin pass:1111 -new -x509 -days 7300 -key ca.key -out ca.crt -subj "/C=CN/ST=GuangDong/CN=www.ido.com" -extensions SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.ido.com,IP:127.0.0.1,IP:180.137.128.151"))
openssl genrsa -passout pass:1111 -des3 -out server.key 1024
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/C=CN/ST=GuangDong/CN=www.ido.com" -reqexts SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.ido.com,IP:127.0.0.1,IP:180.137.128.151"))
# 以下这句是grpc官方例子中生成crt文件的命令,但发现这种方式无法指定ip和多域名,因此弃用
# openssl x509 -req -passin pass:1111 -days 7300 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
openssl ca -passin pass:1111 -days 7300 -in server.csr -keyfile ca.key -cert ca.crt -extensions SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.ido.com,IP:127.0.0.1,IP:180.137.128.151"))
openssl rsa -passin pass:1111 -in server.key -out server.key
openssl pkcs8 -topk8 -nocrypt -in server.key -out server.pem

这样生成的证书,经过测试,可以部署在多个IP(这里是127.0.0.1和180.137.128.151)服务器上,正常访问。

其他

然而,这需要先知道有哪些IP即将部署服务啊!!假如以后部署服务的服务器越来越多,难道要重新生成证书并且更新服务和客户端吗?

得考虑下,这个ip能否是*号,或者其他任何方法,使得服务端增加节点,不影响 客户端正常调用。