【问题标题】:How to generate signed URLs for Google Cloud Storage objects in GKE (Go)如何在 GKE (Go) 中为 Google Cloud Storage 对象生成签名 URL
【发布时间】:2020-10-07 21:03:39
【问题描述】:

目标:

在 GKE pod 中生成签名 URL,无需手动注入服务帐号 JSON 密钥。生成它们的语法需要服务帐户电子邮件和私钥。

//import "cloud.google.com/go/storage"
url, err := storage.SignedURL(bucketName, objectName, &storage.SignedURLOptions{
    ContentType:    contentType,
    GoogleAccessID: saEmail,
    PrivateKey:     saPrivateKey,
})

换句话说,我想从 GKE 节点中自动提供的默认凭据中加载 saEmailsaPrivateKey

尝试:

ctx := context.Background()
//errors ignored for brevity
//import "golang.org/x/oauth2/google"
creds, _ := google.FindDefaultCredentials(ctx, storage.ScopeReadWrite)
cfg, _ := google.JWTConfigFromJSON(creds.JSON)
url, _ := storage.SignedURL(bucketName, objectName, &storage.SignedURLOptions{
    ContentType:    contentType,
    GoogleAccessID: cfg.Email,
    PrivateKey:     cfg.PrivateKey,
})

当我在 GKE pod 中运行 google.FindDefaultCredentials() 时,结果 JSON 为空。

环境:

  • Go1.13
  • GKE1.14.10-gke.36
  • cloud.google.com/go v0.58.0
  • cloud.google.com/go/storagev1.8.0

补充说明:

我已经测试了两种可能的替代方案,涉及手动将服务帐户密钥 (JSON) 注入 pod,但我希望尽可能避免使用它们:

  • 将服务帐户密钥写入文件并将GOOGLE_APPLICATION_CREDENTIALS 设置为其路径。完成后,google.FindDefaultCredentials() 会加载电子邮件和私钥。

  • 将服务帐户密钥作为字符串传递到 pod 并使用 google.JWTConfigFromJSON() 解析它。

【问题讨论】:

  • 看起来这可能是打字系统问题。 FindDefaultCredentials() 方法 returns a custom struct type 可能没有编组为 JSON,因此,它不能被下一个方法 JWTConfigFromJSON() expects a []byte array 读取。我可能在这里遗漏了一些东西,但是您是否在传递第一个返回值之前对其进行了转换?

标签: go google-cloud-platform google-cloud-storage google-kubernetes-engine


【解决方案1】:

要生成签名 URL,您需要有一个私钥。

当您使用 GCP 服务时(此处为 Compute Instances,即 K8S 集群的节点,但与 Cloud RUN、Cloud Functions 等其他 GCP 服务相同)并且您使用默认凭据(并且没有 GOOGLE_APPLICATION_CREDENTIALS env var 定义),库使用metadata server

元数据服务器允许您生成访问令牌

curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

或身份令牌(参数中包含观众)

curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://www.google.com

因此,在您没有任何秘密或私钥的情况下,这些库能够生成用于访问外部 API 的令牌(访问权限或身份)。

但是,元数据服务器不提供密钥(私钥),您不能使用它来生成签名 URL。

您需要一个服务帐户密钥文件

您可以通过多种方式以安全的方式将其提供给 pod。

我不建议您将服务帐户密钥文件直接放在容器中,这并不安全

另一种解决方案

最终,您可以即时生成密钥并将其定义为服务帐户密钥(名称为user-defined service account key)。

  • 您可以在集群上部署服务时生成它。像这样,您不必存储秘密,它是每次即时生成的。
  • 您可以在容器启动时生成密钥,将其设置在服务帐户中,并将其保存在内存中。

然后,当您在代码中需要它时使用它,它应该可以工作,因为它链接到您的服务帐户。

但是,您还需要考虑如何清理旧的和无用的钥匙。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题并在这里找到了解决方案: https://github.com/googleapis/google-cloud-go/issues/1130

    TL;DR

    您可以给它一个自定义签名函数,而不是将私钥传递给storage.SignedURL。您可以使用 Google 的 IAM SDK 使用您的服务帐户的凭据对 blob 进行签名。这样你就根本不需要知道私钥。

    一个潜在的缺点是SignBlob() 将执行 HTTP 请求来进行签名,而如果您传入私钥,则签名将在本地计算。

    先决条件:为您的服务帐户提供服务帐户令牌创建者角色。

    您的代码将如下所示:

    //import (
    //    "cloud.google.com/go/storage"
    //    credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"
    //    credentials "cloud.google.com/go/iam/credentials/apiv1"
    //)
    
    ctx := context.Background()
    saEmail := "your-service-account-email@something-something.iam.gserviceaccount.com"
    
    c, err := credentials.NewIamCredentialsClient(ctx)
    if err != nil {
        panic(err)
    }
    
    url, err := storage.SignedURL(bucketName, objectName, &storage.SignedURLOptions{
        ContentType:    contentType,
        GoogleAccessID: saEmail,
        SignBytes: func(b []byte) ([]byte, error) {
            req := &credentialspb.SignBlobRequest{
                Payload: b,
                Name:    saEmail,
            }
            resp, err := c.SignBlob(ctx, req)
            if err != nil {
                panic(err)
            }
            return resp.SignedBlob, err
        }
    })
    

    【讨论】:

    • 我想这样做,但无法找到在运行时获取当前服务帐户电子邮件的解决方案,而无需对其进行硬编码或作为 env var 传递。你知道怎么找回当前账号邮箱吗,(我在云上跑)
    • 得到我的答案,等待 PR:github.com/googleapis/google-cloud-go/issues/…
    【解决方案3】:

    1.18.0 版本开始,您可以这样做:

        storageClient, _ := storage.NewClient(ctx)
        s, _ := storageClient.Bucket(bucketName).SignedURL(objectName, &storage.SignedURLOptions{
            Method:  http.MethodGet,
            Expires: expires,
        })
    

    引自here。感谢@Fogia 将我指向添加它的 PR。

    【讨论】:

      猜你喜欢
      • 2019-02-05
      • 1970-01-01
      • 2019-06-04
      • 2021-01-21
      • 2012-04-19
      • 2016-03-28
      • 1970-01-01
      • 2017-04-03
      • 2014-01-12
      相关资源
      最近更新 更多