1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 12:32:21 +00:00

ch4: 增加 GRPC进阶

This commit is contained in:
chai2010 2018-07-14 15:48:24 +08:00
parent c86d359da9
commit 36e417dbed
10 changed files with 289 additions and 147 deletions

View File

@ -37,7 +37,8 @@
* [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md)
* [4.3. 玩转RPC](ch4-rpc/ch4-03-netrpc-hack.md)
* [4.4. GRPC入门](ch4-rpc/ch4-04-grpc.md)
* [4.5. 补充说明](ch4-rpc/ch4-05-faq.md)
* [4.5. GRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
* [4.6. 补充说明](ch4-rpc/ch4-06-faq.md)
* [第五章 Go和Web](ch5-web/readme.md)
* [5.1. Web开发简介](ch5-web/ch5-01-introduction.md)
* [5.2. Router请求路由](ch5-web/ch5-02-router.md)

View File

@ -1,23 +1,289 @@
# 4.5. GRPC进阶
作为一个基础的RPC框架安全和扩展是经常遇到的问题。本节将简单介绍如何对GRPC进行安全认证。然后介绍通过GRPC等截取器特性以及如何通过截取器优雅低实现Token认证、调用跟踪以及Panic捕获等特性。最后介绍了GRPC服务如何和其他Web服务共存以及如何将一个GRPC服务发布为REST服务。
## 证书认证
GRPC建立在HTTP2协议之上对TLS提供了很好的支持。我们前面章节中GRPC的服务都没有提供证书支持因此客户端在链接服务器中通过`grpc.WithInsecure()`选项跳过了对服务器证书的验证。没有启用证书的GRPC服务在和客户端进行的是明文通讯信息面临被任何第三方监听的风险。为了保障GRPC通信不被第三方监听我们可以对服务器期待TLS加密特性。
可以用以下命令为服务器和客户端分别生成私钥和证书:
```
$ openssl genrsa -out server.key 2048
$ openssl req -new -x509 -days 3650 \
-subj "/C=GB/L=China/O=grpc-server/CN=server.grpc.io" \
-key server.key -out server.crt
$ openssl genrsa -out client.key 2048
$ openssl req -new -x509 -days 3650 \
-subj "/C=GB/L=China/O=grpc-client/CN=client.grpc.io" \
-key client.key -out client.crt
```
以上命令将生成server.key、server.crt、client.key和client.crt四个文件。其中以.key后缀名的时私钥文件需要妥善保管。其以.crt为后缀名是证书文件也可以简单理解为公钥文件并不需要秘密保存。在subj参数中的`/CN=server.grpc.io`表示服务的名字为`server.grpc.io`,在验证服务器的证书时需要用到该信息。
有了证书之后我们就可以在启动GRPC服务时传入证书选项参数
```go
func main() {
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil {
log.Fatal(err)
}
server := grpc.NewServer(grpc.Creds(creds))
...
}
```
其中credentials.NewServerTLSFromFile函数是从文件为服务器构造证书对象然后通过grpc.Creds(creds)函数将证书包装为选项后作为参数输入grpc.NewServer函数。
在客户端基于服务器的证书和服务器名字就可以对服务器进行验证:
```go
func main() {
creds, err := credentials.NewClientTLSFromFile("server.crt", "server.grpc.io")
if err != nil {
log.Fatal(err)
}
conn, err := grpc.Dial("localhost:5000", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
...
}
```
其中redentials.NewClientTLSFromFile是构造客户端用的证书对象第一个参数时服务器的证书文件第二个参数是签发服务器证书时的名字。然后通过grpc.WithTransportCredentials(creds)将证书对象转为参数选项传人grpc.Dial函数。
以上这种方式,需要提前将服务器的证书告知客户端,这样客户端在链接服务器时才能进行对服务器证书认证。在复杂的网络环境中,服务器证书的传输本身也是一个非常危险的问题。如果在中间某个环节,服务器证书被替换那么对服务器的认证也将不再可靠。
为了避免证书的传递过程中被串改,可以通过一个安全可靠的根证书分别对服务器和客户端的证书进行签名。这样客户端或服务器在收到对方的证书后可以通过根证书进行验证证书的有效性。
根证书的生成方式和签名的自签名证书的生成方式类似:
```
$ openssl genrsa -out ca.key 2048
$ openssl req -new -x509 -days 3650 \
-subj "/C=GB/L=China/O=gobook/CN=github.com" \
-key ca.key -out ca.crt
```
然后是重新对服务器端证书进行签名:
```
$ openssl req -new \
-subj "/C=GB/L=China/O=server/CN=server.io" \
-key server.key \
-out server.csr
$ openssl x509 -req -sha256 \
-CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 \
-in server.csr \
-out server.crt
```
签名的过程中引入了一个新的以.csr为后缀名的文件它表示证书签名请求文件。在证书签名完成之后可以删除.csr文件。
然后在客户端就可以基于CA证书对服务器进行证书验证
```go
func main() {
certificate, err := tls.LoadX509KeyPair(client_crt, client_key)
if err != nil {
log.Fatal(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile(ca)
if err != nil {
log.Fatal(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
log.Fatal("failed to append ca certs")
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{certificate},
ServerName: tlsServerName, // NOTE: this is required!
RootCAs: certPool,
})
conn, err := grpc.Dial("localhost:5000", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
...
}
```
在新的客户端代码中我们不再直接依赖服务器端证书文件。在credentials.NewTLS函数调用中客户端通过引入一个CA根证书和服务器的名字来实现对服务器进行验证。客户端在链接服务器时会首先请求服务器的证书然后使用CA根证书对收到服务器端证书进行验证。
如果客户端的证书也采用CA根证书签名的话服务器端也可以对客户端进行证书认证。我们用CA根证书对客户端证书签名
```
$ openssl req -new \
-subj "/C=GB/L=China/O=client/CN=client.io" \
-key client.key \
-out client.csr
$ openssl x509 -req -sha256 \
-CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 \
-in client.csr \
-out client.crt
```
因为引入了CA根证书签名在启动服务器时同样要配置根证书
```go
func main() {
certificate, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatal(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("ca.crt")
if err != nil {
log.Fatal(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
log.Fatal("failed to append certs")
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{certificate},
ClientAuth: tls.RequireAndVerifyClientCert, // NOTE: this is optional!
ClientCAs: certPool,
})
server := grpc.NewServer(grpc.Creds(creds))
...
}
```
服务器端同样改用credentials.NewTLS函数生成证书通过ClientCAs选项CA根证书并通过ClientAuth选项启用对客户端进行验证。
到此我们就实现了一个服务器和客户端进行双向证书验证的通信可靠的GRPC系统。
## Token认证
前面讲述的基于证书的认证是针对每个GRPC链接的认证。GRPC还为每个GRPC方法调用提供了认证支持这样就可以基于不同的用户对不同对方法访问进行权限管理。
要实现对每个GRPC方法进行认证需要实现grpc.PerRPCCredentials接口
```go
type PerRPCCredentials interface {
// GetRequestMetadata gets the current request metadata, refreshing
// tokens if required. This should be called by the transport layer on
// each request, and the data should be populated in headers or other
// context. If a status code is returned, it will be used as the status
// for the RPC. uri is the URI of the entry point for the request.
// When supported by the underlying implementation, ctx can be used for
// timeout and cancellation.
// TODO(zhaoq): Define the set of the qualified keys instead of leaving
// it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity indicates whether the credentials requires
// transport security.
RequireTransportSecurity() bool
}
```
我们可以创建一个Authentication类型用于实现用户名和密码对认证
```go
type Authentication struct {
User string
Password string
}
func (a *Authentication) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
return map[string]string{"login": a.Login, "password": a.Password}, nil
}
func (a *Authentication) RequireTransportSecurity() bool {
return false
}
```
在GetRequestMetadata方法中返回认证需要对必要信息。为了演示代码简单RequireTransportSecurity方法表示不要求底层使用安全链接。在真实对环境中建议底层必须要求启用安全对链接否则认证信息有泄露和被串改的风险。
```go
func main() {
auth := Authentication{
Login: "gopher",
Password: "password",
}
conn, err := grpc.Dial("localhost"+port, grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
...
}
```
通过grpc.WithPerRPCCredentials函数将Authentication对象转为grpc.Dial参数。因为这里没有启用安全链接需要传人grpc.WithInsecure()表示忽略证书认证。
然后在GRPC服务端的每个方法中通过Authentication类型的Auth方法进行身份认证
```go
type grpcServer struct { auth *Authentication }
func (p *grpcServer) SomeMethod(ctx context.Context, in *HelloRequest) (*HelloReply, error) {
if err := p.auth.Auth(ctx); err != nil {
return nil, err
}
return &HelloReply{Message: "Hello " + in.Name}, nil
}
func (a *Authentication) Auth(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return fmt.Errorf("missing credentials")
}
var appid string
var appkey string
if val, ok := md["login"]; ok { appid = val[0] }
if val, ok := md["password"]; ok { appkey = val[0] }
if appid != a.Login || appkey != a.Password {
return grpc.Errorf(codes.Unauthenticated, "invalid token")
}
return nil
}
```
首先通过metadata.FromIncomingContext从ctx上下文中获取元信息然后取出相应的认证信息进行认证。
<!--
## 截取器
流的截取器简单提下,重点是普通函数的截取
登陆日志跟踪panic捕获
TODO
<!--
## 和Web服务共存
中间件/panic捕获/rest/参数认证
TODO
swagger 文档
## 导出Rest服务
--
swagger 生成文档
外网如何访问内网的rpc服务
隐喻:让客服主动打电话给我,然后我反映问题,让客服解决问题
grpc无法定制协议只能时http2但是流特性
bet/rpc on grpc
掉线,心跳?有必要吗
TODO
-->

View File

@ -1,4 +1,4 @@
# 4.5. 补充说明
# 4.6. 补充说明
本章重点讲述了Go标准库的RPC和基于Protobuf衍生的GRPC框架同时也简单展示了如何自己定制一个RPC框架。之所以聚焦在这几个有限的主题是因为这几个技术都是Go语言团队官方在进行维护和Go语言契合也最为默契。不过RPC依然是一个庞大的主题足以单独成书。目前开源世界也有很多富有特色的RPC框架还有针对分布式系统进行深度定制的RPC系统用户可以根据自己实际需求选择合适的工具。

View File

@ -4,16 +4,12 @@ TODO
<!--
本的用法生成http路由生成sdk
于pb扩展打造一个自定义的rest生成
web框架
url中参数先简单处理不支持子结构体内的映射
--
参考 gopl 的 unpark 实现
基于pb扩展打造一个自定义的rpc
请求/应答 api
比如 青云SDK这类
重点是自动生成路由映射,甚至自动关联到 grpc 等服务接口(避免新开一个服务代理)
-->

View File

@ -9,7 +9,6 @@ import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)
@ -53,7 +52,7 @@ func (a *Authentication) GetRequestMetadata(context.Context, ...string) (map[str
return map[string]string{"login": a.Login, "password": a.Password}, nil
}
func (a *Authentication) RequireTransportSecurity() bool {
return true
return false
}
func main() {
@ -64,12 +63,7 @@ func main() {
}
func startServer() {
creds, err := credentials.NewServerTLSFromFile("tls-config/server.crt", "tls-config/server.key")
if err != nil {
log.Fatalf("could not load tls cert: %s", err)
}
server := grpc.NewServer(grpc.Creds(creds))
server := grpc.NewServer()
RegisterGreeterServer(server, new(myGrpcServer))
lis, err := net.Listen("tcp", port)
@ -83,17 +77,12 @@ func startServer() {
}
func doClientWork() {
creds, err := credentials.NewClientTLSFromFile("tls-config/server.crt", "server.grpc.io")
if err != nil {
log.Fatalf("could not load tls cert: %s", err)
}
auth := Authentication{
Login: "gopher",
Password: "password",
}
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&auth))
conn, err := grpc.Dial("localhost"+port, grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))
if err != nil {
log.Fatal(err)
}

View File

@ -1,18 +0,0 @@
default:
# server
openssl genrsa -out server.key 2048
openssl req -new -x509 -days 3650 \
-subj "/C=GB/L=China/O=grpc-server/CN=server.grpc.io" \
-key server.key -out server.crt
# client
openssl genrsa -out client.key 2048
openssl req -new -x509 -days 3650 \
-subj "/C=GB/L=China/O=grpc-client/CN=client.grpc.io" \
-key client.key -out client.crt
clean:
-rm *.key *.crt *.csr *.srl

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDFDCCAfwCCQCrJq8HthdiHTANBgkqhkiG9w0BAQsFADBMMQswCQYDVQQGEwJH
QjEOMAwGA1UEBwwFQ2hpbmExFDASBgNVBAoMC2dycGMtY2xpZW50MRcwFQYDVQQD
DA5jbGllbnQuZ3JwYy5pbzAeFw0xODA3MTQwNzEyMTRaFw0yODA3MTEwNzEyMTRa
MEwxCzAJBgNVBAYTAkdCMQ4wDAYDVQQHDAVDaGluYTEUMBIGA1UECgwLZ3JwYy1j
bGllbnQxFzAVBgNVBAMMDmNsaWVudC5ncnBjLmlvMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAo4r8TJGDq6sVHchIROy8ySwiyyHyNegGwopPYSJ1SgPP
BGwOEidDosYR+wAVXkNP4H5wD0VW1At7jOcQ8nkjRlxKPFywLhdjz4qmZPor50JU
pWj5aizU9z24qk0EGL62CjvxTqHutNjHDkHSS+Londdh+x/AIwSVGP4egCerpoH+
nG+m1TO90bj34FgrUvD+6Pt0PnEvWThWo6/6+6XWFqOeXEqjXKgbqtvZRv1epNu2
p0DNkJENB0XEctcM/frJdFp0F5r08mhppUN1fQZw0G/Fz5fEuHnLvS9v3ap6gwIL
rzqbamR8W8sb9pYKu6kMBU4vPyI6fpKBPZTR43oC5wIDAQABMA0GCSqGSIb3DQEB
CwUAA4IBAQBlH6Au0Owv/ui7I7dzwKRTKpgwJXTs75rdWMN9xmWrMN93yXJJceXC
HSIMcG8KYYPVlMuTOHD21R7doUdCjSIFDmyI1eqThzwrfa4zc3qqWJYcGMbZXu+U
pAyDERQIxjAZnV4o5ZL18NXwAsUuF9AheR5YBszeG/xZurxDSVp2nPYLkjpE52Kw
Nros7xxCIGaOrbUjgtATPKEgjxc3AgnKLVaeemTijI1Q8T+yoFYLK5d2ARZX9YNy
TgbXSe3Bx8Hk1a3V/V799zBK4O9XyaCKjbpbgBWEzq70zz2gqYmjoHb5Rj+jqAJ2
zQzUmp6WfH9TynsNwhoV8DYWch/97JJF
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAo4r8TJGDq6sVHchIROy8ySwiyyHyNegGwopPYSJ1SgPPBGwO
EidDosYR+wAVXkNP4H5wD0VW1At7jOcQ8nkjRlxKPFywLhdjz4qmZPor50JUpWj5
aizU9z24qk0EGL62CjvxTqHutNjHDkHSS+Londdh+x/AIwSVGP4egCerpoH+nG+m
1TO90bj34FgrUvD+6Pt0PnEvWThWo6/6+6XWFqOeXEqjXKgbqtvZRv1epNu2p0DN
kJENB0XEctcM/frJdFp0F5r08mhppUN1fQZw0G/Fz5fEuHnLvS9v3ap6gwILrzqb
amR8W8sb9pYKu6kMBU4vPyI6fpKBPZTR43oC5wIDAQABAoIBACZPUHq/O5DjKrcn
S4aZzj9xz1S2Rv7Js4uuypl+cOs9qSjoBwPJntZqf5vEkTzbl8KG28k3/Pb/GZoQ
JrAmIFVDGUC6laq2X/MmD4Rn9hDeQOXeiU6N4fVox/FcyTDiu9H56LT6yegjP73f
PVv5c3RGF/WzolaRpDuOi6aJpHQEb7pGQ44ZEMBh3DyYNGSrU/fB/abICwkYXWhB
V3cTbV/c1Fb/JQAXjP9SnnASEgOsjLaPUzOWzjChZtcPD4ZfvUa82Yxpt/G1b72M
KLN5ozCBbTlGbRRCHPtrjluV35WkhFgBLwySFux0z+OAR9PEOUFBiwvL5aU9ckc7
koRDV0ECgYEAzopb84ULwlYqG06MEooEaN5I16Up90Fcj+UqmZLrKClwVFVbM1T0
7xp8XRFWYaTsNXUMmah4K2ZTN/wsUS6SckUgzBKdfncdajLbfeFjAF6BTQgXKhHJ
/5xGCidUgaC1faJS2+zyXJaecKc5Ju5xh0XkQVHfpGgg0eKiANotC8MCgYEAyrS6
fXqidx93cGfVr3LiTa+eHGLBQJyrxrvfsulIAx2444KKpt8RziIwTEXrQXkW2h+6
HBzCAaef2QdxqS0zuA9wnQ81Jwy+5pWPN4OWQS9JW2K47XMSwvcDpeSwf/Zf3Xur
u4Z/ih3TN4Laf/Lm/i+xOmk1fR9UnHeqe6lJTg0CgYEAyTBvl4wvQzOpuoXN3jVB
TGHS9PS3J1os7yJdV9GMbcfH2u52c087dDoJkKLV0THUwdHt3zQDMstvHubuyHmk
P9lktEUh01H2fj9iHYHSbUahj0blQZ6odOxmMXfUUp0YjXP0YLSz9UrmtXe/LVQx
lIKZcKNvRXgFGE46XrgoDv8CgYB/SUeFvblBNjgpFHrBRlHG2I1fY7/YU8kl7RmG
XiiDFGniKznnmVGz8BIGAy28Gk54P839Ey3cHicpABymUCUW4lMjvMXytHU3lBiv
kmiKnCfjQ7W+HTFdzgCzbkxKvinqiVsIUWfLEfls9TVBfQUB/m66sBAPdtoJurcK
Gf5XDQKBgEeEVsspejC4duFKsXwq7R8JHRDmvVc9rUdthfhyU8e2po1cfInqmKrb
K6Cn03Pfaa/xZa2YfIiDyWjBbXgpmi+KzaAS9Iay+U0iPYuoeeQrnztXGazInVqf
HLAeZ4xWePJYh9vc3a8ALTJKYIiuainaO/kiTAL2UtN+LOqx3Ho8
-----END RSA PRIVATE KEY-----

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDFDCCAfwCCQDIYtSF3tbGvDANBgkqhkiG9w0BAQsFADBMMQswCQYDVQQGEwJH
QjEOMAwGA1UEBwwFQ2hpbmExFDASBgNVBAoMC2dycGMtc2VydmVyMRcwFQYDVQQD
DA5zZXJ2ZXIuZ3JwYy5pbzAeFw0xODA3MTQwNzEyMTNaFw0yODA3MTEwNzEyMTNa
MEwxCzAJBgNVBAYTAkdCMQ4wDAYDVQQHDAVDaGluYTEUMBIGA1UECgwLZ3JwYy1z
ZXJ2ZXIxFzAVBgNVBAMMDnNlcnZlci5ncnBjLmlvMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA2cZFGgh2jU2sbs0aBN8o5Ce6zZHD/Q3wnR0axeMCsbO8
uV1b/TIA6WiUW7uDwQIEnCcu9Izyi6BW/1xoOyvP3FMb4yQBrueDgkHZIpVV43X/
wgfwsT20kbQjM1DYvn/08e9MmHliIEUOQ3rUOluLqABcZuqGHeJQoocYJyaaEK7U
eMhJhHQPOopClZdMfrtzw860sLgIe/vgEH9MlGFO/5ay1w3N+VsI7d8AmCzqPSua
bxxdRl4z9kXkhMLvHd9MHuZ0VQgeZMMGVxYxxno7Y9HWqpLzv4ANSOxZHYaXEnr4
+Y0qmmPOn8bcXbL+oa8hcNScqbte5sKUODI5n4AqRQIDAQABMA0GCSqGSIb3DQEB
CwUAA4IBAQBNaCkvbLkUxKGT0W1b7wEU7ipDjjn+YsiRc98+go6nkNDfQPuf1AY9
KyEd9b9gHgqiQoZIQR7OzRDIalh22TvNEfKav4lg3WiNSm405AuYqylg4jQ6K06w
qH9dNN8si3H+D3jGdKezVyYJrpyi88HDIKeQ7EAeoxTlSbdzYjj1lJ23prFf7fLl
tYn3bIPsamYtXlB9NMdaPeq0YL7FlkKFhfkfJTH+Wehk16dhDBzjFStIwSAQOnDl
5ckrLrD8Su1fXSK69SPilvAOaCQTKQg5PiBuA/NAN8eW1ph38ooWvGVwOreZXAmR
P+QZkg7xMgYLBVbWNLgXhol9afRoo3K5
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA2cZFGgh2jU2sbs0aBN8o5Ce6zZHD/Q3wnR0axeMCsbO8uV1b
/TIA6WiUW7uDwQIEnCcu9Izyi6BW/1xoOyvP3FMb4yQBrueDgkHZIpVV43X/wgfw
sT20kbQjM1DYvn/08e9MmHliIEUOQ3rUOluLqABcZuqGHeJQoocYJyaaEK7UeMhJ
hHQPOopClZdMfrtzw860sLgIe/vgEH9MlGFO/5ay1w3N+VsI7d8AmCzqPSuabxxd
Rl4z9kXkhMLvHd9MHuZ0VQgeZMMGVxYxxno7Y9HWqpLzv4ANSOxZHYaXEnr4+Y0q
mmPOn8bcXbL+oa8hcNScqbte5sKUODI5n4AqRQIDAQABAoIBAQCmGKvhtCy/j0uB
4tsCdjZY9dhrK88gtb4cBvPBlGgcourBMmyTy8yYOVRWNsnBrslXVCdjDBF93xgb
gh5ZawwJjjXJ40+JWDicY5652s58QyaDlczFUjvjmVHztKjreuqWkB6KeKIX3LOJ
UiiGLgZxZ30SXbNXHaLEo2cPjUiaiWVpAzngBDGwVVb4RmsQKru4ZxQDhPKFbY7E
4bP7EXA1ZRUfA/KhhKF5v2ZLV7VuE8/jecblsW3Ra79cQGDDztNQkMQwzqj0MGGg
L0B+9KJgRVXco7EQDQgnmPLnMKaRTB1tup3YN1iOtVk1znFvpoTBYhNKebeoTKy8
0091rYlBAoGBAPIXbEXLuMAjmHUshXtadcuVRhH8/nMw5hej7ODg3gKV5IvwYpvg
nv78+7CY39CmLD1QtXSZQmOOtv+CMAViI2ktXhvCxoSBq5lHSCu38dq8TNMpkNo2
cbevpS58lDfbCjqqPeBLPhLDLB7oMbSZjqXCYSDpxJKY59OgsG6IYHJ1AoGBAOZJ
M/hGE1L1dGUXgdQO1uogQDRkLrulhVGXi1qLlU88Voz2aCgWNVEc7w0Dln90AtTJ
f7XovK3N35ND5TIPCF7zAdcfHVjUN4NuAX3agzqM9fVP8jgp2ey02hCi3VA8gIok
g5Fm7ktbTn8FhPCJnBwdNIf43pFAshEPEFwtqz6RAoGBAKvRRe3vXYs4eC7JT18x
36KCqj2AElNM5DDpDei4j7jEC3XqQQIilJWilTtL9KVQGtvVmUNMtpqUInsLOevI
MBuosD8BqQVIRrK9rZIhtgwpUlkYCg9lTqYwigqJLmTKF+N1Cf9TR5XnYTv6TbTA
u0YXw/mpD/N/hCFlzXJVZiY1AoGAWOV7VWUfwo+UTg4EBStxVVH71v2xeKiNzZB1
sA9gJQVC2Amq7YadMGG9+kUfTLYo/aGHVl6wr1tg6kV0bZ2V+qlOVY/iUU8i2u6V
TtMSg4C01ez1sS6evJyX7YIhcv7YE/vCelfEt3xY0fn8dqp4g/XWOIMez+2sj59E
DoFmsLECgYEAvGbIyb+imwE67p4YbdwwQ2Lu8oFMOTWnY6QQf2WpuMT9P7T3Lf8h
qoO6Lo6kDvDu8itd5LNVtrnnlG68Wj70ZODsi4h7I6Pa6mI3xWRFJ+h/p3lLYTsR
tAKvMYqax97rWb+okg3P428BoAO2p+rNxT21+DOe0vegGuvzfFLQx4w=
-----END RSA PRIVATE KEY-----