美文网首页
用Go和TLS保护gRPC连接的实用指南 - 第2部分

用Go和TLS保护gRPC连接的实用指南 - 第2部分

作者: 开心人开发世界 | 来源:发表于2019-08-11 12:38 被阅读0次

    在上一篇文章中,我们检查了不同的(SSL / TLS)证书组合以保护gRPC通道。随着端点数量的增加,这个过程很快就会变得太复杂而无法手动执行。现在是时候看看如何自动生成签名证书,我们的gRPC端点可以在没有我们干预的情况下使用它们。我们将探索私有和公共领域的替代方案。如果要直接跳转到代码中,请查看存储库

    这是一系列三篇文章的第2部分。在第1部分中,我们介绍了手动设置gRPC TLS连接。相互认证将在第3部分中讨论。

    介绍

    我们需要一个我们可以通过Go gRPC端点与之交互的证书颁发机构(CA)。

    对于私有域,我们选择的CA将是Vault PKI Secrets Engine。为了从我们的gRPC端点生成证书签名请求(CSR)和续订,我们将使用Certify

    对于公共证书的生成和分发,我们将使用Let's Encrypt ; 一个免费的,自动化的,开放的证书颁发机构 ......这有多酷!?他们唯一需要的是使用自动证书管理环境(ACME)协议演示对域的控制。这意味着我们需要一个ACME客户端,幸运的是,我们可以为此选择一个Go 列表。在这个机会中,我们将使用autocert的易用性和对TLS-ALPN-01挑战的支持。

    私有域:Vault和Certify

    Vault

    Vault是一个秘密管理和数据保护开源项目,可以存储和控制对证书的访问,以及密码和令牌等其他秘密。它以二进制形式发布,可以放在你的任何地方$PATH。如果您想了解有关Vault的更多信息,其入门指南是一个很好的起点。此处记录了此帖子所用设置的所有详细信息。

    首先,我们运行Vault vault server -config=vault_config.hcl。配置文件(vault_config.hcl)提供storage存储Vault数据的后端。为简单起见,我们只使用本地文件。您也可以选择将其存储在内存中,云存储提供商或其他地方。查看存储Stanza中的所有选项。

    storage "file" {
      path = ".../data"
    }
    

    此外,我们还指定了Vault将绑定的地址。默认情况下启用TLS,因此我们需要提供证书和私钥对。如果您选择对这些进行自签名(请参阅这些说明以获取示例),请确保将Root证书(ca.cert)保留为方便,稍后您将需要它来向Vault(*)发出请求。tcp Listener Parameters中记录了其他TCP配置选项。

    listener "tcp" {
      address     = "localhost:8200"
      tls_cert_file = ".../vault.pem"
      tls_key_file = ".../vault.key"
    }
    

    经过初始化Vault的服务器解封Vault可以验证正在与API调用。

    $ curl \
        --cacert ca.cert \
        -i https://localhost:8200/v1/sys/health
    HTTP/1.1 200 OK
    ...
    {"initialized":true,"sealed":false,"standby":false, ...}
    

    下一步是启用Vault PKI Secrets Engine后端vault secrets enable pki,生成CA证书和Vault将用于签署证书的私钥,并创建一个my-role可以为我们的域(localhost)发出请求的角色()。在这里查看所有细节

    vault write pki/roles/my-role \
        allowed_domains=localhost \
        allow_subdomains=true \
        max_ttl=72h
    

    证明

    现在我们的证书颁发机构(CA)已准备就绪,我们可以向它发出请求,以便签署我们的证书。您可能会询问哪些证书,以及如果我们还没有它们,如何自动告诉我们的gRPC端点使用它们?输入Certify,Go库,以便在需要时自动执行证书分发和续订。它不仅适用于Vault作为CA后端,还适用于Cloudflare CFSSLAWS ACM

    配置Certify的第一步是issuer在这种情况下指定后端Vault。

    issuer := &vault.Issuer{
        URL: &url.URL{
            Scheme: "https",
            Host:   "localhost:8200",
        },
        TLSConfig: &tls.Config{
            RootCAs: cp,
        },
        Token: getenv("TOKEN"),
        Role:  "my-role",
    }
    

    在此示例中,我们通过提供以下内容来标识Vault实例和访问凭据:

    • 我们为Vault(localhost:8200)配置的侦听器地址。
    • 初始化库的服务器后,我们得到的TOKEN
    • 我们创建的角色(my-role)。
    • 我们在Vault配置中提供的证书颁发者的CA证书。cp是一个x509.CertPool包括ca.cert在这种情况下,如在(*)指出。

    您可以选择通过提供证书详细信息CertConfig。在这种情况下,我们这样做是为了指定我们想要使用RSA算法而不是Certify的默认值为我们的证书签名请求(CSR)生成私钥ECDSA P256

    cfg := certify.CertConfig{
        SubjectAlternativeNames: []string{"localhost"},
        IPSubjectAlternativeNames: []net.IP{
            net.ParseIP("127.0.0.1"),
            net.ParseIP("::1"),
        },
        KeyGenerator: RSA{bits: 2048},
    }
    

    通过我们现在构建的Certify类型验证钩子GetCertificateGetClientCertificate方法; 先前收集的信息,防止为每个传入连接请求新证书方法,以及登录插件(在该示例中)。tls.ConfigCache go-kit/log

    c := &certify.Certify{
        CommonName:  "localhost",
        Issuer:      issuer,
        Cache:       certify.NewMemCache(),
        CertConfig:  &cfg,
        RenewBefore: 24 * time.Hour,
        Logger:      kit.New(logger),
    }
    

    最后一步是创建一个tls.Config指向我们刚刚创建的GetCertificate方法Certify。然后,在我们的gRPC服务器中使用此配置。

    // Client
    // ... as in http://bit.ly/go-grpc-tls-ca ...
    
    // Server
    tlsConfig := &tls.Config{
      GetCertificate: c.GetCertificate,
    }
    
    s := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
    // ... register gRPC services ...
    if err = s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
    

    您可以通过make run-server-vaultmake run-client-ca将环境变量CAFILE指向Vault的证书文件(ca-vault.cert)后在一个选项卡和另一个选项卡中运行来重现此操作,您可以按如下方式获取该文件:

    $ curl \
    --cacert ca.cert \
    [https://localhost:8200/v1/pki/ca/pem](https://localhost:8200/v1/pki/ca/pem?source=post_page---------------------------) \
    -o ca-vault.cert
    

    服务器:

    $ make run-server-vault
    ...
    level=debug time=2019-07-15T19:37:12.694833Z caller=logger.go:36 server_name=localhost remote_addr=[::1]:64103 msg="Getting server certificate"
    level=debug time=2019-07-15T19:37:12.694936Z caller=logger.go:36 msg="Requesting new certificate from issuer"
    level=debug time=2019-07-15T19:37:12.815081Z caller=logger.go:36 serial=451331845556263599050597627925015657462097174315 expiry=2019-07-18T19:37:12Z msg="New certificate issued"
    level=debug time=2019-07-15T19:37:12.815115Z caller=logger.go:36 serial=451331845556263599050597627925015657462097174315 took=120.284897ms msg="Certificate found"
    

    客户:

    $ export CAFILE="ca-vault.cert"
    $ make run-client-ca
    ...
    User found:  Nicolas
    

    检查我们生成并自动签名的证书,将揭示我们刚刚配置的一些细节。

    $ openssl x509 -in grpc-cert.pem -text -noout
    Certificate:
        Data:
        ...
            Validity
                Not Before: Jul 15 19:36:42 2019 GMT
                Not After : Jul 18 19:37:12 2019 GMT
            Subject: CN=localhost
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    Public-Key: (2048 bit)
                    Modulus:
                        00:bf:3c:a3:d8:8c:d8:3c:d0:bd:0c:e0:4c:9d:4d:
                        ...
            X509v3 extensions:
                ...
                Authority Information Access:
                    CA Issuers - URI:https://localhost:8200/v1/pki/ca
    X509v3 Subject Alternative Name:
                    DNS:localhost, DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
    

    公共域:让我们自动完成加密

    我们加密吧

    我们可以使用Let's Encrypt for gRPC吗?嗯,它确实对我有用。问题可能在于公开面对gRPC API是否是个好主意。Google Cloud似乎正在这样做,请参阅Google API。但是,这不是一种非常普遍的做法。无论如何,我在这里是如何使用我们自动从Let的加密获得的证书公开公共gRPC API。

    重要的是要强调这个例子并不意味着要复制内部/私人服务。在与Let's Encrypt的Jacob Hoffman-Andrews交谈时,他提到:

    一般情况下,我建议人们不要将let的加密证书用于gRPC或其他内部RPC服务。在我看来,使用minica生成单一用途的内部CA并使用它生成服务器和客户端证书既简单又安全。这样你就不必将你的RPC服务器打开到外部互联网,而且你将信任范围限制在内部RPC所需的范围内,而且你可以拥有更长的证书生命周期,而且你可以获得撤销作品。

    让加密使用ACME协议来验证证书申请人是否合法地代表证书中的域名。它还为其他证书管理功能提供了便利,例如证书撤销。ACME描述了用于自动化发布和域验证过程的可扩展框架,从而允许服务器和基础设施软件在没有用户交互的情况下获得证书。[ RFC 8555 ]

    简而言之,我们需要做的就是利用Let的加密来运行ACME客户端。我们将在此示例中使用autocert

    autocert

    autocert软件包可以自动访问Let's Encrypt和任何其他基于ACME的CA的证书。但是,请记住,此包正在进行中,并且不会产生API稳定性承诺。[ 文件 ]

    在规范的要求而言,第一步是声明一个ManagerPrompt指示帐户注册过程中接受CA的服务条款的,一个Cache方法来存储和检索先前获得的证书(在这种情况下,本地文件系统的目录),一个HostPolicy使用我们可以响应的域列表,以及可选地和Email 地址来通知已颁发证书的问题

    manager := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        Cache:      autocert.DirCache("golang-autocert"),
        HostPolicy: autocert.HostWhitelist(host),
        Email:      "test@example.com",
    }
    

    Manager将自动为我们创建一个TLS配置,负责与Let的加密交互。另一方面,客户端只需要一个指向空tlsconfig(&tls.Config{})的指针,默认情况下,它会加载系统CA证书,因此信任我们的CA(Let's Encrypt)。

    // Client
    config := &tls.Config{}
    
    conn, err := grpc.Dial(address, grpc.WithTransportCredentials(credentials.NewTLS(config)))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    
    
    // Server
    creds := credentials.NewTLS(manager.TLSConfig())
    s := grpc.NewServer(grpc.Creds(creds))
    // ... register gRPC services ...
    
    // Listener...
    

    如果你正在密切关注,你可能已经注意到我们在这个例子中没有包含监听器部分。原因是基于ACME TLS的挑战TLS-ALPN-01如何工作。具有应用程序级协议协商(TLS ALPN)验证方法的TLS通过要求客户端配置TLS服务器以响应利用具有标识信息的ALPN扩展的特定连接尝试来证明对域名的控制。[ draft-ietf-acme-tls-alpn-05 ]。

    作为旁注,autocert 在Let's Encrypt宣布所有TLS-SNI-01验证支持的生命周期终止添加了对TLS-ALPN-01支持

    换句话说,我们需要监听HTTPS请求。好消息是autocert一应俱全,并可以创建这个特殊的监听manager.Listener()。现在,问题是HTTPS和gRPC是否应该在同一个端口上监听?长话短说,我无法使其与独立端口一起工作,但如果两个服务都在443上听,它可以完美地工作。

    gRPC和HTTPS在同一个端口上......说什么!?我知道,只因为你不能意味着你应该这样做。但是,Go gRPC库提供的ServeHTTP方法可以帮助我们将传入的请求路由到相应的服务。请注意,*ServeHTTP*使用Go的*HTTP/2*服务器实现,它与grpc-go的*HTTP/2*服务器完全分开性能和功能可能因两条路径而异。[ go-grpc ]。您可以在gRPC serveHTTP性能损失中看到一些基准。话虽如此,路由将如下所示:

    func grpcHandlerFunc(g *grpc.Server, h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ct := r.Header.Get("Content-Type")
            if r.ProtoMajor == 2 && strings.Contains(ct, "application/grpc") {
                g.ServeHTTP(w, r)
            } else {
                h.ServeHTTP(w, r)
            }
        })
    }
    

    所以我们可以按如下方式收听请求,注意我们提供了grpcHandlerFunc刚创建的处理程序http.Serve

    // Listener
    lis = manager.Listener()
    
    if err = http.Serve(lis, grpcHandlerFunc(s, httpsHandler())); err != nil {
        log.Fatalf("failed to serve: %v", err))
    }
    

    您可以通过make run-server-public在一个选项卡和make run-client-default另一个选项卡中运行来重现此问题。为此,您需要拥有一个域(HOST)。在我的情况下我用过:

    export HOST=grpc.nleiva.com
    export PORT=443
    make run-server-public
    

    现在,我可以通过互联网从世界上任何地方发出gRPC请求:

    $ export HOST=grpc.nleiva.com
    $ export PORT=443
    $ make run-client-default
    User found:  Nicolas
    

    最后,我们可以查看通过发出HTTPS请求生成的证书。

    结论

    如果您利用本文中讨论的一些资源,管理和分发gRPC端点的证书应该不会有麻烦。

    到目前为止,虽然连接已加密且客户端已验证服务器的完整性,但服务器尚未对客户端进行身份验证。这可能是某些微服务场景所必需的,我们将在本博客系列的下一部分中讨论相互TLS。敬请关注!

    转: https://www.jianshu.com/writer#/notebooks/35830493/notes/51571844/preview

    相关文章

      网友评论

          本文标题:用Go和TLS保护gRPC连接的实用指南 - 第2部分

          本文链接:https://www.haomeiwen.com/subject/ebhnrctx.html