Моя настройка инфраструктуры выглядит следующим образом:
Есть два арендатора – арендатор А и арендатор Б.
В Тенанте А у меня есть группа ресурсов с кластером k8s, управляемым удостоверением, учетной записью хранения с контейнером и другими ресурсами. В арендаторе Б у меня есть группа ресурсов только с управляемым удостоверением и учетной записью хранения с контейнером.
Я могу успешно писать из приложения, работающего в кластере k8s в Тенанте А, в контейнер хранилища в Тенанте Б. Я могу это сделать, потому что в Тенанте Б я назначил роли Storage Account Contributor
и Storage Blob Data Owner
управляемому удостоверению в той же группе ресурсов. Затем я создал федеративные учетные данные, которые позволяют кластеру k8s в арендаторе A выполнять аутентификацию управляемого удостоверения в арендаторе 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, иначе 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 и идентификатором клиента управляемой идентификации).
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)
Затем я вызываю query.Encode()
, генерируя подписанный URL-адрес.
Мне удалось записать значение 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, которые продолжают отклоняться.
Обновлено: обновление, теперь я воспроизвел ту же проблему со всем в одном арендаторе.
Проблема заключалась в том, что я не мог указать BlobName
в BlobSignatureValues
при подписании с помощью UserDelegationCredential.
Однако вы можете указать там BlobName
, когда я использую SharedKeyCredential и подписываюсь с помощью SignWithSharedKey