我的基础设施设置如下:
有两个租户 - 租户 A 和租户 B。
在租户 A 中,我有一个包含 k8s 集群的资源组、托管身份、带有容器的存储帐户以及其他资源。在租户 B 中,我有一个仅包含托管标识的资源组和一个带有容器的存储帐户。
我可以成功地从租户 A 中的 k8s 集群中运行的应用程序写入租户 B 中的存储容器。我之所以能够做到这一点,是因为在租户 B 中,我将
Storage Account Contributor
和 Storage Blob Data Owner
角色授予了托管身份相同的资源组。然后,我创建了一个联合凭证,允许租户 A 中的 k8s 集群通过使用以下命令对租户 B 中的托管身份进行身份验证:
az identity federated-credential create \
--name fc-x-tenant-test \
--identity-name tenantB-managedIdentity \
--resource-group tenantB-rg \
--issuer <Tenant A AKS Cluster OIDC Provider URL> \
--subject system:serviceaccount:default:<Tenant A Service Account name>
(我按照https://paulyu.dev/article/cross-tenant-workload-identity-on-aks/进行了设置 - 谢谢https://stackoverflow.com/users/21008480/pauldotyu! )
然后,使用 azure-sdk-for-go,创建一个客户端(我检查
err != nil
,但为了简单起见,此处省略)
url := fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
opts := &azidentity.WorkloadIdentityCredentialOptions{
TenantID: *tenantBID,
ClientID: *tenantBmanagedIdentityClientID,
}
cred, err := azidentity.NewWorkloadIdentityCredential(opts)
client, err := azblob.NewClient(url, cred, nil)
serviceClient := client.ServiceClient()
containerClient := serviceClient.NewContainerClient(containerName)
从那里,我可以成功地读取和写入容器。注意:创建凭据时,我需要指定租户 B 的租户 ID 和托管身份客户端 ID,否则 SDK 将尝试使用租户 A(AKS 集群所在的租户)进行身份验证!
但是,当我尝试为客户端生成预签名 URL 以便能够将对象放入此容器时,它不起作用。签名的 URL 看起来像这样
https://<Tenant B Storage Account Name>.blob.core.windows.net/<Tenant B Container Name>/path/to/object/metadata.json?se=2024-05-03T01%3A33%3A10Z&sig=KL3t1EGRODo4%2FUUu5Irtaw11vXwIEpBkO2sYTqrTg5o%3D&sp=w&spr=https&sr=b&sv=2023-11-03
。
我得到了 403 错误
ErrorCode:AuthenticationFailed
authenticationerrordetail:Signature did not match
我使用以下代码创建预签名 URL(使用如上所示创建的
client
,以及租户 B 的租户 ID 和托管身份客户端 ID)。
serviceClient := client.ServiceClient()
now := time.Now().UTC().Add(-10 * time.Second)
expiry := now.Add(24 * time.Hour)
info := azblobService.KeyInfo{
Start: to.Ptr(now.UTC().Format(sas.TimeFormat)),
Expiry: to.Ptr(expiry.UTC().Format(sas.TimeFormat)),
}
udc, err := serviceClient.GetUserDelegationCredential(ctx, info, nil)
sigVals := sas.BlobSignatureValues{
Protocol: sas.ProtocolHTTPS,
ExpiryTime: time.Now().Add(24 * time.Hour).UTC(),
Permissions: perm.String(),
ContainerName: containerName,
BlobName: blobName,
}
query, err := sigVals.SignWithUserDelegation(udc)
然后我在生成签名 URL 时调用
query.Encode()
。
我能够记录
query
的值,如下所示。两个字段已被编辑,但描述描述了其中的值。
Query {
version:2023-11-03
services:
resourceTypes:
protocol:https
startTime:{wall:0 ext:0 loc:<nil>}
expiryTime:{wall:151009920 ext:63850466819 loc:<nil>}
snapshotTime:{wall:0 ext:0 loc:<nil>}
ipRange:{Start:[] End:[]}
identifier:
resource:b
permissions:w
signature:bDcn0MCs1kGakVX9OOtKt/ApBllv68AJLZnwZK4g+5g=
cacheControl: contentDisposition: contentEncoding: contentLanguage: contentType:
signedOID:<Tenant B Managed Identity Object ID>
signedTID:<Tenant B Tenant ID>
signedStart:{wall:0 ext:63850380409 loc:<nil>}
signedService:b
signedExpiry:{wall:0 ext:63850466809 loc:<nil>}
signedVersion:2023-11-03
signedDirectoryDepth: authorizedObjectID: unauthorizedObjectID: correlationID: encryptionScope: stTimeFormat: seTimeFormat:
}
在这里我注意到过期时间看起来很奇怪。不过,我验证了
expiryTime
没问题,因为我能够使用共享访问密钥让 SAS 在租户 A 中正常工作,并且它的 expiryTime
就是这样设置的。
签署的开始/到期时间一开始也看起来不一样。所以我记录了这些值,它们看起来是正确的。然后我尝试看看如果我做到了,那么
signedStart
等于 {wall:0 ext:0 loc:<nil>}
会发生什么,但问题仍然存在。 (您可以在创建用户委托 SAS 中了解所有这些值的含义
。)
我还在
Tenant B
中启用了目标存储帐户的日志,并且我可以在 PutBlob
容器中看到成功的 GetBlob
、DeleteBlob
和 $logs
调用(对于跨租户读/写)不要使用 SAS)。但是,我没有看到任何失败的 SAS PUT 不断被拒绝的日志。
编辑:更新,我现在已经在单个租户中重现了相同的问题
问题是,使用 UserDelegationCredential 签名时,我无法在
BlobName
中指定 BlobSignatureValues
。
但是,当我使用 SharedKeyCredential 并使用
SignWithSharedKey进行签名时,您可以在那里指定
BlobName