使用 KMS 时尝试将一些代码转换为 Go CDK

Attempting to convert some code to Go CDK when using KMS

我有一些代码可以从 Google 云存储上传和下载文件。下面是一个简化的例子:

import (
    "context"
    "io"
    "cloud.google.com/go/storage"
)

func upload(bucket, keyName, path string, reader io.Reader) error {
    ctx := context.Background()
    client, err := storage.NewClient(ctx)

    if err != nil {
        return err
    }
    defer client.Close()

    obj := client.Bucket(bucket).Object(path)
    writer := obj.NewWriter(ctx)

    defer writer.Close()

    writer.KMSKeyName = keyName

    if _, err = io.Copy(writer, reader); err != nil {
        return err
    }
    if err = writer.Close(); err != nil {
        return err
    }
    return nil
}

棘手的部分是我使用 Google KMS 来管理我用来加密文件的密钥(Google 所谓的 "customer-managed encryption key" 方案)。我的理解是这种加密发生在 Google 的末端。

我发现使用 Go CDK 的唯一解决方案是使用 Google KMS 加密文件,然后上传加密的 blob。有没有办法像我之前使用 Go CDK 那样指定加密密钥?

谢谢

您发布的代码是正确的。具体来说,此行指示 API 使用提供的 Cloud KMS 密钥加密数据:

writer.KMSKeyName = keyName

一些可能会妨碍的事情:

  • 确保您的 Cloud KMS 密钥和 Cloud Storage 存储桶位于同一区域。例如,如果您在 "US" 中有一个存储桶,您的 KMS 密钥也必须在 "US".

  • 确保您已授予云存储服务帐户使用 KMS 密钥的权限。您可以 find the email of your Cloud Storage service account 然后授予它使用您的 KMS 密钥的权限:

    $ gcloud kms keys add-iam-policy-binding my-key \
        --project my-project \
        --keyring my-keyring \
        --location us \
        --member serviceAccount:service-1234567890@gs-project-accounts.iam.gserviceaccount.com \
        --role roles/cloudkms.cryptoKeyEncrypterDecrypter 
    
  • 确保您捕获了从 upload 上游返回的错误。所有这些故障模式都会导致错误,所以我想知道它是否在某处丢失了。

它有效的证明:

// Completely unmodified version of your `upload` function
func main() {
    bucket := "<hidden>"
    kmsKey := "projects/<hidden>/locations/us/keyRings/kr/cryptoKeys/k"
    if err := upload(bucket, kmsKey, "foo", strings.NewReader("hello world")); err != nil {
        log.Fatal(err)
    }
}

执行时,这会在 CMEK 支持的 GCS 中正确创建一个对象。

如果您需要在 Go CDK 中使用特定于提供程序的设置,您可以使用各种 As functions to get handles to the underlying provider API. In this case, you would want to use the blob.WriterOptions.BeforeWrite 选项。这样做的好处是,如果您稍后决定切换存储桶提供程序(例如用于单元测试),BeforeWrite 将不执行任何操作。

import (
    "context"
    "io"

    "cloud.google.com/go/storage"
    "gocloud.dev/blob"
    _ "gocloud.dev/blob/gcsblob" // link in "gs://" URLs
)

func upload(ctx context.Context, bucket, keyName, path string, reader io.Reader) error {
    bucket, err := blob.OpenBucket(ctx, "gs://" + bucket)
    if err != nil {
        return err
    }
    defer bucket.Close()

    writeCtx, cancelWrite := context.WithCancel(ctx)
    defer cancelWrite()
    writer, err := bucket.NewWriter(writeCtx, path, &blob.WriterOptions{
        // Use BeforeWrite to set provider-specific properties.
        BeforeWrite: func(asFunc func(interface{}) bool) error {
            var gcsWriter *storage.Writer
            // asFunc returns true if the writer can be converted to the type
            // pointed to.
            if asFunc(&gcsWriter) {
                gcsWriter.KMSKeyName = keyName
            }
            return nil
        },
    })
    if err != nil {
        return err
    }
    if _, err := io.Copy(writer, reader); err != nil {
        cancelWrite()  // Abort the write to the bucket.
        writer.Close()
        return err
    }
    if err := writer.Close(); err != nil {
        return err
    }
    return nil
}

(虽然与您的问题没有直接关系,但我添加了代码以中止写入错误以避免将部分对象上传到您的存储提供程序。我们正在添加将演示如何使用 Go 执行常见任务的文档未来的 CDK API,请参阅 #1576。)