谷歌云存储 Google Cloud Storage (GCS) 是谷歌云上的对象存储产品,向所有用户提供低延时、高吞吐、易用并安全的存储服务。很多客户会利用GCS丰富的功能实现各种复杂的场景,包括针对不同部门用户的精细化访问权限管理。比如下面几个场景:
本文介绍一些方法和步骤,可以实现以上需求,做到精细化的权限管理。其中对于用户创建桶后自动授权用户读写该桶,使用了基于条件的 IAM 配置和日志触发云函数授权两种办法——前者配置简单但是有一定限制,后者使用没有限制但是配置稍微复杂。两种方法可以根据实际需要选用。
授权服务账号管理存储桶
基于桶名规则授权用户管理桶
使用云函数自动授权用户管理桶
授权服务账号管理存储桶
针对只允许用户通过合法应用程序访问存储桶这个需求,在谷歌云可以使用服务账号 Service Account 来针对应用程序进行基于IAM服务的授权和基于 OAuth 2.0协议的鉴权,确保安全性。
首先创建一个 Service Account,用于创建存储桶,并管理其创建的存储桶。创建时,对第2步和第3步略过,直接保存。

创建完成后,下载该服务账号的密钥,用于本地访问 GCS 接口。


选择 JSON 格式,点击确认后 key 文件会下载到本地。在 ~/Downloads/youzhi-lab-ff782c45a4ca.json 文件。
本文中创建的服务账号 ID 为:
gcs-manager-test@youzhi-lab.iam.gserviceaccount.com
配置好 gcloud 命令的 project ID 等环境配置。并在命令行中用 gcloud 工具使用该服务账号及其 Key 作为访问 API 的身份验证。
gcloud auth activate-service-account gcs-manager-test@youzhi-lab.iam.gserviceaccount.com \\ --key-file=/Users/eugeneyu/Downloads/youzhi-lab-929004117269.json |
现在尝试列表一个已有的存储桶,可以看到结果失败。这是正常的,因为现在还没有对该服务账号授权。
$ gsutil ls gs://youzhi-lab AccessDeniedException: 403 gcs-manager-test@youzhi-lab.iam.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. |
可以在已有的存储桶权限配置页面,添加此服务账号,并授予相应的桶级别角色,比如对象查看角色。

此时再尝试列表这个桶,可以看到结果成功,因为授权生效。
$ gsutil ls gs://youzhi-lab gs://youzhi-lab/1184.mp3 gs://youzhi-lab/19.m4a gs://youzhi-lab/WechatIMG13-1.png gs://youzhi-lab/WechatIMG13-2.png gs://youzhi-lab/WechatIMG13-4.png ... |
如果尝试写入对象则会失败,因为上面步骤并没有授予创建对象的角色或权限。
$ gsutil cp ./test.py gs://youzhi-lab/ Copying file://./test.py [Content-Type=text/x-python]... AccessDeniedException: 403 gcs-manager-test@youzhi-lab.iam.gserviceaccount.com does not have storage.objects.create access to the Google Cloud Storage object. |
如果尝试查看其它未授权的桶,也会失败。
$ gsutil ls gs://youzhi-lab-bak AccessDeniedException: 403 gcs-manager-test@youzhi-lab.iam.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket. |
如果尝试创建新桶,也会失败,因为没有创建桶的权限。
$ gsutil mb -p youzhi-lab gs://youzhi-lab-bucket-create-test-1 Creating gs://youzhi-lab-bucket-create-test-1/... AccessDeniedException: 403 gcs-manager-test@youzhi-lab.iam.gserviceaccount.com does not have storage.buckets.create access to the Google Cloud project. |
基于桶名规则授权用户管理桶
下面授权这个服务账号创建桶,并管理和操作所创建的桶。
首先在 IAM->Roles 菜单创建一个自定义角色,为其分配桶的创建和删除权限(permission)。
然后在 IAM->IAM 菜单页选择添加用户,选择上面创建的服务账号 ID。并对其授予两个角色。
上面创建的自定义 Storage Bucket Creator 角色——用于创建和删除桶
系统自带的 Storage Object Admin 角色——用于操作桶里的对象

给两个角色分配创建条件,限制其能创建和操作的桶的名字,做到权限范围隔离。比如讲这个服务账号只授予研发团队使用,那么在条件里限制其只能创建和操作桶名以“youzhi-lab-eng-”开头的桶及其里面的对象。条件配置如下所示。


配置好并保存后,可以测试效果。
首先创建一个“youzhi-lab-eng-”开头名字的桶,结果成功。
$ gsutil mb -p youzhi-lab -l asia-southeast1 -b on gs://youzhi-lab-eng-bucket-create-test-2 Creating gs://youzhi-lab-eng-bucket-create-test-2/... |
往这个桶里添加对象,结果成功。
$ gsutil cp ./test.py gs://youzhi-lab-eng-bucket-create-test-2/ Copying file://./test.py [Content-Type=text/x-python]... / [1 files][ 971.0 B/ 971.0 B] Operation completed over 1 objects/971.0 B. |
对这个桶做列表操作,结果成功。
$ gsutil ls gs://youzhi-lab-eng-bucket-create-test-2 gs://youzhi-lab-eng-bucket-create-test-2/test.py |
对一个并没有以 “youzhi-lab-eng-” 开头的桶做操作,结果失败。
$ gsutil cp ./test.py gs://youzhi-lab-bucket-create-test-1/ Copying file://./test.py [Content-Type=text/x-python]... AccessDeniedException: 403 gcs-manager-test@youzhi-lab.iam.gserviceaccount.com does not have storage.objects.create access to the Google Cloud Storage object. |
创建一个没有以 “youzhi-lab-eng-” 开头的桶,结果失败。
$ gsutil mb -p youzhi-lab -l asia-southeast1 -b on gs://youzhi-lab-bucket-create-test-1 Creating gs://youzhi-lab-bucket-create-test-1/... AccessDeniedException: 403 gcs-manager-test@youzhi-lab.iam.gserviceaccount.com does not have storage.buckets.create access to the Google Cloud project. |
使用云函数自动授权用户管理桶
下面步骤介绍如何使用基于日志触发云函数,自动将新建桶的操作权限赋予创建桶的用户。其事件顺序为:
用户 A 创建存储桶
GCS 生成创建桶的日志
基于预先配置的日志路由规则,该创建桶日志被投送到 Pub/Sub 的 Topic
Pub/Sub 的 Subscription 触发云函数,此时 Subscription 使用服务账号 B
云函数调用 GCS 的 API,对用户 A 进行授权,此时云函数使用服务账号 C
为了以上事件中的操作有足够权限,首先需要对用到的服务账号授权。本例 B 和 C 都使用示例项目的缺省 GCE 服务账号247839977271-compute@developer.gserviceaccount.com。以下步骤是对这个服务账号授权。
首先授权服务账号 B 触发云函数的权限。在 IAM 中对其做如下授权。


然后对服务账号 C 授权,使其可以调用 GCS 的 API 执行桶权限信息的获取和配置操作。

现在创建一个日志路由规则。在日志服务中,点击 CREATE SINK。

在 Sink 的配置中,设置 Sink 名称为 gcs-bucket-create-sink。

在 Sink 的目标配置中选择 Pub/Sub Topic。此时可以创建一个新的 Topic。

给 Topic 起名为 gcs-bucket-create-log-topic。其它配置不用变动。

在日志筛选配置中,填入以下表达式,选择创建存储桶并成功的日志。

点击 NEXT 并点击 CREATE SINK 按钮。
下面创建一个云函数。

进行如下配置
点击 ADD EVENTARC TRIGGER 按钮
在 Eventarc trigger 页面做如下配置
Event provider: Cloud Pub/Sub
Event: google.cloud.pubsub.topic.v1.messagePublished
Select a Cloud Pub/Sub topic: project/youzhi-lab/topics/gcs-bucket-create-log-topic
Region: asia-southeast1
Service account: 选择一个拥有项目级别 Bucket Admin 权限的服务账号,本例为 Compute Engine 缺省服务账号

在代码配置中,选择 Python 3.9运行时,并设置入口函数为 grant_permissions。

在 main.py 的代码窗口,填入以下代码。
import base64 import json import os import functions_framework
from google.cloud import storage
# Triggered from a message on a Cloud Pub/Sub topic. @functions_framework.cloud_event def grant_permissions(cloud_event): log_text = base64.b64decode(cloud_event.data["message"]["data"]) log_json = json.loads(log_text)
creator_email = log_json['protoPayload']['authenticationInfo']['principalEmail'] principle_type = "user" if creator_email.endswith("gserviceaccount.com"): principle_type = "serviceAccount" creator_user = principle_type + ":" + creator_email
created_bucket_name = log_json['protoPayload']['resourceName'] created_bucket_name = os.path.basename(created_bucket_name)
role_object_admin = "roles/storage.objectAdmin" role_storage_admin = "roles/storage.admin"
add_bucket_iam_member(created_bucket_name, role_storage_admin, creator_user)
def add_bucket_iam_member(bucket_name, role, member): """Add a new member to an IAM Policy""" # bucket_name = "your-bucket-name" # role = "IAM role, e.g., roles/storage.objectViewer" # member = "IAM identity, e.g., user: name@example.com"
storage_client = storage.Client() bucket = storage_client.bucket(bucket_name)
policy = bucket.get_iam_policy(requested_policy_version=3)
policy.bindings.append({"role": role, "members": {member}})
bucket.set_iam_policy(policy)
print(f"Added {member} with role {role} to {bucket_name}.") |
选择 requirements.txt 文件,并在其代码窗口填入以下代码。
functions-framework==3.* google-cloud-storage |
点击 DEPLOY 创建部署函数。
在函数部署完成后,进行测试。可以用服务账号创建一个新的存储桶。
$ gsutil mb -p youzhi-lab gs://youzhi-lab-bucket-create-test-20220615-01 Creating gs://youzhi-lab-bucket-create-test-20220615-01/... |
执行完成后查看上述云函数的执行日志,可以看到有函数执行并成功授权的日志。

此时再往存储桶里写文件,可以成功,证明上述配置有效。
$ gsutil cp ./test.py gs://youzhi-lab-bucket-create-test-20220615-01 Copying file://./test.py [Content-Type=text/x-python]... - [1 files][ 1.4 KiB/ 1.4 KiB] Operation completed over 1 objects/1.4 KiB. |