Passwordless Journey

В документе будут записаны всякие демки и примеры как мы пытались понять сможем ли мы сделать эдакий passwordless общение между своими сервисами

На выходе получиться что то типа такого

screenshot

Но перед тем как запрыгивать во всю эту историю нужно понять как работает более простой сетап

История 1: S3

Сценарий такой - мы хотим S3 только ажурный

Для этого создаем в azure storage account

Выбираем любую из подписок, создаем свою ресурсную группу, название и дальше все настройки можно оставить по умолчанию

screenshot

Примечание: здесь и далее желательно себе выписывать что создается и меняется, что бы затем можно было почистить за собой, так же очень удобно, везде где можно использовать одну и ту же ресурсную группу - можно будет из одного места все грохнуть

Так, создал я сам storage account, но теперь внутри него нужно создать так называемый container

screenshot

Опять таки все по умолчанию, нам особо не интересует что там приватное, что публичное и вот это все

Ну и следом туда руками закинуть какой то файлик

Далее идем в раздел Access keys и забираем свои ключи доступа

screenshot

Гуглим как это дело правильно готовить со стороны dotnet и попадаем на вот эту статью

У меня получилось что то типа такого:

using Azure.Storage.Blobs;

// dotnet add package Azure.Storage.Blobs
var client = new BlobServiceClient("DefaultEndpointsProtocol=https;AccountName=mactemp;AccountKey=xxxxxxxxxxxxx;EndpointSuffix=core.windows.net");
var container = client.GetBlobContainerClient("demo");
foreach(var blob in container.GetBlobs()) {
    Console.WriteLine(blob.Name);
}

screenshot

Короче не ракето-строение и на деле такое же простое как S3

Дальше начинается самое интересное и по сути наше первое знакомство с passwordless подходами

Всем этим делом будет заправлять пакет Azure.Identity вот пример кода

using Azure.Identity;
using Azure.Storage.Blobs;

// dotnet add package Azure.Storage.Blobs
// dotnet add package Azure.Identity
var client = new BlobServiceClient(new Uri("https://mactemp.blob.core.windows.net"), new DefaultAzureCredential());
var container = client.GetBlobContainerClient("demo");
foreach(var blob in container.GetBlobs()) {
    Console.WriteLine(blob.Name);
}

Вот эта штука DefaultAzureCredential под капотом перебирает все возможные сценарии и пытается разобраться как подключиться к ажуру

Один из сценариев это az cli, который уже у всех стоит и в котором есть смысл выполнить az login, что бы освежить его токен

Но, есть еще один момент, в ключами доступа у нас уже были все все права на S3, а тут то мы логинимся как некий alexandrm@rabota.ua, соотв вопрос - а у него должны быть доступы вообще к этому делу или нет 🤔

Идем в наш S3 и в настройках IAM выдаем права

screenshot screenshot screenshot

После чего наша приложенька радостно заработала

screenshot

Примечание: что важно, у нас уже нет никаких паролей которые нужно было бы куда то в appsettings.json прописывать, затем в octopus deploy, затем их подменять на разных окружениях, как то прятать и ротировать

Как эта штука работает - под капотом там перебрались все возможные варианты и оно увидело что мы залогинены в az cli и за не имением ничего лучшего с этим пользователем и попробовало пойти в s3, а по скольку пользователю мы только что выдали права - все сработало

screenshot

На деле ж там все работает через эти access token'ы которые jwt

К сожалению примеры с curl для storage - проще застрелиться, потому привожу два других примера что бы было нагляднее понятно что там под капотом делается

Например если мы хотим сходить в azure api

az account get-access-token --query accessToken -o tsv

Мы можем посмотреть что токен выписан для management.core.windows.net, и для меня

screenshot

А значит, я с этим токеном могу ломиться в это api

curl -s -H "Authorization: Bearer $(az account get-access-token --query accessToken -o tsv)" 'https://management.azure.com/subscriptions?api-version=2022-09-01' | jq ".value"

screenshot

Ок, с локальной разработкой разобрались, все четко и красиво, но как быть с кубером 🤔

Дальше начинает интересная многоходовочка, мы создадим сервисныю учетку и в ажуре и в кубере и как бы свяжем их между собой, тем самым сделаем так что бы наше приложение бежало из под этой сервисной учетки, выдадим ей такие же права как выдавали себе, и если все сделано правильно оно один в один так же само заработает в кубере, без необходимости что либо менять в коде, appsettings, octopus, переменных окружения и прочем - просто из коробки все будет работать

Для этого идем в раздел managed identity в портале и создаем свою первую сервисную учетку.

Потрібно врахувати що є обмеження в назві. Вона валідується на стороні Кубера\октопуса.

Для валідного імені облікового запису служби дозволено використовувати лише символи нижнього регістру, цифри, '-' або '.', причому ім'я повинно починатися і закінчуватися буквою або цифрою. Наприклад, допустимі імена: my-service-account, user-service-account-1.

screenshot

Как не сложно догадаться, подписка, ресурсная группа и регион те же самые, ну и дальше next, next, finish

screenshot

Вуаля, готово

Далее нам понадобяться client id с этого экрана, в моем случае это

client_id: 5063dd9c-e304-4544-9e0a-d2c6651da05f

И tenant id, с ним проще, он один общий, одинаковый на все все все (условно это id нашей орг)

tenant_id: 695e64b5-2d13-4ea8-bb11-a6fda2d60c41

Теперь нам нужно создать сервисную учетку в кубере, можно делать yaml, а можно вот так:

kubectl -n dev create serviceaccount mactemp
kubectl -n dev label serviceaccount mactemp "azure.workload.identity/use=true"
kubectl -n dev annotate --overwrite serviceaccount mactemp "azure.workload.identity/client-id=5063dd9c-e304-4544-9e0a-d2c6651da05f" "azure.workload.identity/tenant-id=695e64b5-2d13-4ea8-bb11-a6fda2d60c41"

ну или для yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: mactemp
  namespace: dev
  annotations:
    azure.workload.identity/client-id: 5063dd9c-e304-4544-9e0a-d2c6651da05f
    azure.workload.identity/tenant-id: 695e64b5-2d13-4ea8-bb11-a6fda2d60c41
  labels:
    azure.workload.identity/use: "true"
imagePullSecrets:
  - name: gcr.io

Примечания:

  • если все ок, то kubectl get serviceaccount mactemp его покажет
  • оба tenant_id и client_id - не секреты, а id'ки организации и сервисной учетки в ажуре соотв.
  • лейбочка azure.workload.identity/use: "true" нужна для тулы которая уже установлена в кластере и которая будет делать магию

Многоходовка на этом еще не заканчивается, теперь идем назад в azure в свою сервисную учетку в раздел federated credentials и добавляем новые с такими параметрами

screenshot

cluster issuer url: https://northeurope.oic.prod-aks.azure.com/695e64b5-2d13-4ea8-bb11-a6fda2d60c41/839148b7-1a9a-4c39-9bbb-01d35db879f4/

Теперь самое время собрать и опубликовать образ нашего приложения и попробовать запустить его в кубере

Но перед этим нужно внести две правки:

spect.template.metadata.labels добавить azure.workload.identity/use: "true"

spect.template.spec.serviceAccountName указать mactemp

Полный yaml не привожу, т.к. там ничего интересного не будет, но можно запуститься даже без него командой по типу такой (тут наглядно видно что нужно поменять в yaml):

kubectl run -it --rm mactemp --image=TODO_IMAGE_URL_HERE --overrides='{"spec": { "serviceAccountName": "mactemp", "nodeSelector": {"kubernetes.io/os": "linux"}}, "metadata":{"labels":{"azure.workload.identity/use":"true"},"annotations":{"kubeforce":"skip"}}}'

И оно не сработает - потому что?

Мы не выдали права, откуда ажуру знать, что некой сервисной учетке mactemp можно ходить в s3? Соотв по аналогии с тем как мы выдавали права сами себе, нужно сделать то же самое, только за вместо пользователей выбрать managed identities и нашу учетку - и вот уже после этого оно заработает

Примечание: после всех экспериментов не забыть удалить свой образ из container registry, бо там и так дофига всего и потом, спустя время будем ломать себе голову, а нужно оно или нет и можно ли удалить

Чертова магия в действии, у нас вобще нет никаких секретов вообще нигде, а как следствие нечего прятать\ротировать\отзывать и при этом все работает

Если локально мы уже разобрались что под капотом оно использует аккаунт пользователя с которым мы залогинились в az cli, то в кубере происходит следующее:

  1. В отдельном нейспейсе бежит спец деплоймент, который следит за кубером и как только видит что будет запускаться под с лейбочками "azure.workload.identity/use":"true" делает две вещи:
    1. генерит jwt токен
    2. подсовывает этот токен в под в папку /var/run/secrets/azure
    3. подсовывает в под переменные окружения о котороых знает DefaultAzureCredentials
  2. При запуске приложения наш DefaultAzureCredentials видит эти переменные и файлик с токеном, берет все это дело и идет в Azure что бы обменять их на access token к нашему S3
  3. Azure в свою очередь видит что к нему ломиться нечто, выдающее себя за client_id: XXX (наша сервисная учетка) и утверждающее что это действительно оно прикрепив токен
  4. Azure видит что действительно есть такая сервисная учетка, у нее есть федеративные креды, [sub]ject совпадают, [iss]uer совпадают
  5. Далее Azure верифицирует токен у issuer'а (кубера) и если все ок, генерит и отдает в ответ access token
  6. Profit

Примечания:

  • токен внутри кубера в /var/run/secrets/azure сгенерен самим кубером, с этим токеном не выйдет сходить в s3, или другие сервисы, в нем явно указана [aud]ience - api://AzureTokenExchange, этот токен можно использовать в azure api для обмена его на access token к целевому ресурсу
  • токен внутри кубере подписан секретом кубера, о котором azure ничего не знает (условно у нас кубер вообще может где то на нашем сервере работать) именно потому ажур верифицирует его
  • хоть наш кубер и внутри ажура, был бы логичным вопрос - почему бы сразу не выписать access token - ответ прост - access token куда? в s3, или sql или что то еще - на перед же система не знает, плюс не стоит забывать что access token это короткоживущий токен, так что было бы странно если бы нам его подсунули, а условно через час он уже протух
  • вся прелесть этого подхода с обменом токенов в том что он не завязан на ажур и может работать в любых комбинациях, условно у нас может быть свой oidc сервер, и мы бы в федеративные креды добавили бы его, а не кубер и тем самым могли бы авторизироваться под своими учетками, та же история с aws, github, и многими другими
  • вторая прелесть - это то что у каждой системы есть свои секреты, которыми подписываются токены, но при этом никто о них не знает
  • ну и последнее и самое важное - в нашем приложении вообще никаких секретов нет, хоть в публичный докер хаб можно отправлять, но самое главное - больше никакой истории о том что секреты нужно ротировать и вот это все что безопасники любят
  • что бы можно было нормально провалиться в приложение - есть смысл из него сделать web api что бы оно не сразу же закрывалось, затем провалиться внутрь через kubectl exec, очень рекомендую посмотреть в jwt.io что там за токен и его aud, iss и вот это все
  • подробнее о том какие есть опции обмена токанами описано тут

App Registrations

По мимо сервисных учеток в Azure, есть так называемые App Registrations, можно найти в разделе Active Directory

Примечания:

  • tenant_id - который одинаковый для всего на свете - это по сути id нашей орг, и по сути id нашей active directory, и одниаковый во всех подписках и окружениях
  • app registration - глобально регистрируемое во всем мире приложение имеющее свой уникальный client_id
  • enterprise applications - это установленное конретно в нашей active directory экземпляр этого приложения - по сути это и есть его сервисная учетка, в отличии от app registrations, если какая то другая компания поставит у себя наше приложение, то в их active directory в разделе enterprise applications - появиться наша апка - то есть тем самым они как бы содают сервисную учетку от имени нашего приложения в своем active directory. В процессе создания app registration, enterprise application создается автоматом и тут ничего делать не нужно
  • Так же само как и с сервисными учетками, у приложений, в разрезе секретов есть раздел federated credentials которые один в один можно настроить точно так же

Use Case: API to API

For this to work we gonna need an app registration, nothing fancy here, just create one with default settings, no need to change anything

Copy your client id and tenant id, we will need them later

Click “Add an Application ID URI”

screenshot

At the very top click "Set"

screenshot

Accept default

screenshot

One more prerequisite

If you leave everything as is, client will fail with error

Unhandled exception.

Azure.Identity.AuthenticationFailedException:

Azure CLI authentication failed due to an unknown error.
See the troubleshooting guide for more information.
https://aka.ms/azsdk/net/identity/azclicredential/troubleshoot

ERROR: AADSTS65001:

The user or administrator has not consented to use the application with ID '04b07795-8ddb-461a-bbee-02f9e1bf7b46' named 'Microsoft Azure CLI'.
Send an interactive authorization request for this user and resource.

To fix this issue go to exposed api, add some scope and add client with ID mentioned in error

screenshot

Now create an API

mkdir myapi
cd myapi
dotnet new web --exclude-launch-settings --no-https
dotnet add package Microsoft.Identity.Web

And our Program.cs

using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.Authority = "https://sts.windows.net/695e64b5-2d13-4ea8-bb11-a6fda2d60c41";
        options.Audience = "api://fa220c3a-cd6d-416a-ac4f-25a3e14ba38f";
    });

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/", (ClaimsPrincipal user) => user.Claims.Select(c => new KeyValuePair<string, string>(c.Type, c.Value)).ToList());

app.Run();

Now we need client, to simplify things I will create console client

mkdir client
cd client
dotnet new console
dotnet add package Azure.Identity

And here is the code

using System.Net.Http.Headers;
using Azure.Core;
using Azure.Identity;

var credentials = new DefaultAzureCredential();
var tokenResult = await credentials.GetTokenAsync(new TokenRequestContext(new[] { "api://fa220c3a-cd6d-416a-ac4f-25a3e14ba38f/.default" }), CancellationToken.None);
Console.WriteLine(tokenResult.Token);

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Token);
var claims = await client.GetStringAsync("http://localhost:5000/");
Console.WriteLine(claims);

If everything were done correctly you should receive your access token, which then will be used to authenticate against our API

The next step is to check how can we receive such token for managed identity, let’s pretend we already have one configured

At moment it does not work, I was able to acquire access token for managed identity, it is much smaller but from the logs of API it seems to be valid, but at the very end API marks user as not logged in


The easiest way to get an access token created from kubernetes will be:

# note: we are using preexisting "ats" service account, so no need to deal with it
kubectl run -it --rm mactemp --image=ubuntu --overrides='{"spec": { "serviceAccountName": "ats", "nodeSelector": {"kubernetes.io/os": "linux"}}, "metadata":{"labels":{"azure.workload.identity/use":"true"},"annotations":{"kubeforce":"skip"}}}'

apt -qq update && apt install -y curl jq

# check that you have token
env | grep AZURE

# exchange tokens (make sure to replace "fa220c3a-cd6d-416a-ac4f-25a3e14ba38f" with your application registration client id)
curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "scope=api%3A%2F%2Ffa220c3a-cd6d-416a-ac4f-25a3e14ba38f%2F.default&client_id=$AZURE_CLIENT_ID&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$(cat $AZURE_FEDERATED_TOKEN_FILE)&grant_type=client_credentials" "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token"

Then copy received token an locally call your api like so:

curl -H "Authorization: Bearer XXXXX" http://localhost:5000/

myapi

I have created mactemp_myapi

In its expose api section I have set default api://328df3d8-186c-4e8c-b51e-bb60790599e9 app uri, added “Hello” scope, and added 04b07795-8ddb-461a-bbee-02f9e1bf7b46 Azure CLI as allowed client

In properties of enterprise app i have enabled assigment required feature, and added my self to users

Did tried to remove my self at 12:55, still can receive an token, will try later to see how much time is needed for change to propagate. In 14:16 i can not receive a token anymore

console

Created mactemp_console

For easier experiments between user token and console token, have created an client credentials secret

Как дебажить джобу

Сценарий: у нас есть некая крон джоба которая по какой то не ведомой причине перестала работать

Мы хотить перепроверить что происходит с токенами и валидны ли они

В нашем случае речь о доступе к azure storage но сам подход применим ко всему

Запускаем наш контейнер

kubectl run -it --rm mactemp --image=ubuntu --overrides='{"spec": { "serviceAccountName": "ats", "nodeSelector": {"kubernetes.io/os": "linux"}}, "metadata":{"labels":{"azure.workload.identity/use":"true"},"annotations":{"kubeforce":"skip"}}}' bash

Примечания:

  • нужно поменять --image=ubuntu на образ своей джобы
  • нужно поменять serviceAccountName на тот для которого проводим тест
  • bash в самом конце добавляем что бы запустилась не наша джоба, а терминал

Если нужно доставляем софт

apt update
apt install -y curl jq

Теперь начинаем сами проверки

Смотрим наличие переменных окружения

env | grep AZURE

В моем случае все ок, вывело

AZURE_TENANT_ID=695e64b5-2d13-4ea8-bb11-a6fda2d60c41
AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token
AZURE_AUTHORITY_HOST=https://login.microsoftonline.com/
AZURE_CLIENT_ID=92b87306-6e31-4fa0-bf0c-fa84e64072f2

Это значит что все сработало как надо и нашему поду подсунули токен

Дальше нам остаеться только проверить валидный ли это токен

cat $AZURE_FEDERATED_TOKEN_FILE

Для начала смотрим его в jwt.io

screenshot

Должно быть что то типа такого, в частности aud должен быть проставлен в AzureADTokenExchange, а sub в нашу сервисную учетку

Естественно с таким токеном ходить куда либо не получиться

curl -s -H "Authorization: Bearer $(cat $AZURE_FEDERATED_TOKEN_FILE)" 'https://management.azure.com/subscriptions?api-version=2022-09-01' | jq

Выдаст

{
  "error": {
    "code": "AuthenticationFailed",
    "message": "Authentication failed."
  }
}

Для того что бы работало, нам нужно провернуть “магию” то что происходит под капотом и обменять токен

Делается следующим образом

curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "scope=https%3A%2F%2Fatsrobsad.blob.core.windows.net%2F.default&client_id=$AZURE_CLIENT_ID&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$(cat $AZURE_FEDERATED_TOKEN_FILE)&grant_type=client_credentials" "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" | jq -r ".access_token"

Примечания:

  • в этом запросе самое главное это scope - ресурс к которому мы собираемся стучаться, в моем примере это https://atsrobsad.blob.core.windows.net/.default
  • токен выписывается вот прямо на конретный storage account, а не на все на свете
  • слеши меняем на %2F, двоеточие на %3A
  • если стучимся к своему API то scope будет по типу такого: api://111111-222222-33333-44444, где вот эта простыня это id нашего приложения

Теперь если сходим с полученным токеном в jwt.io должны будем там увидеть в aud наш Azure Storage

screenshot

A sub теперь равен client id сервисной учетке из под которой мы запустили наш контейнер

screenshot

Примечание: в кубере называется ats для удобства, на деле kubectl describe serviceaccount ats и там будет аннотация azure.workload.identity/client-id с этим id

Так ну и теперь финальный запрос к искому сервису

access_token=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "scope=https%3A%2F%2Fatsrobsad.blob.core.windows.net%2F.default&client_id=$AZURE_CLIENT_ID&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$(cat $AZURE_FEDERATED_TOKEN_FILE)&grant_type=client_credentials" "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" | jq -r ".access_token")

curl -H "x-ms-version: 2021-08-06" -H 'accept: application/json' -H "Authorization: Bearer $access_token" 'https://atsrobsad.blob.core.windows.net/?comp=list'

Выведет список контейнеров (папок), что то типа:

<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="https://atsrobsad.blob.core.windows.net/">
    <Containers>
        <Container>
            <Name>olx-resume-files</Name>
            <Properties>
                <Last-Modified>Mon, 26 Jun 2023 12:14:35 GMT</Last-Modified>
                <Etag>"0x8DB763EE7D2E9BF"</Etag>
                <LeaseStatus>unlocked</LeaseStatus>
                <LeaseState>available</LeaseState>
                <PublicAccess>blob</PublicAccess>
                <DefaultEncryptionScope>$account-encryption-key</DefaultEncryptionScope>
                <DenyEncryptionScopeOverride>false</DenyEncryptionScopeOverride>
                <HasImmutabilityPolicy>false</HasImmutabilityPolicy>
                <HasLegalHold>false</HasLegalHold>
                <ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled>
            </Properties>
        </Container>
        <Container>
            <Name>work-resume-files</Name>
            <Properties>
                <Last-Modified>Thu, 23 Mar 2023 10:35:41 GMT</Last-Modified>
                <Etag>"0x8DB2B8A59E410DB"</Etag>
                <LeaseStatus>unlocked</LeaseStatus>
                <LeaseState>available</LeaseState>
                <PublicAccess>blob</PublicAccess>
                <DefaultEncryptionScope>$account-encryption-key</DefaultEncryptionScope>
                <DenyEncryptionScopeOverride>false</DenyEncryptionScopeOverride>
                <HasImmutabilityPolicy>false</HasImmutabilityPolicy>
                <HasLegalHold>false</HasLegalHold>
                <ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled>
            </Properties>
        </Container>
        <Container>
            <Name>work-resume-photos</Name>
            <Properties>
                <Last-Modified>Thu, 23 Mar 2023 10:35:41 GMT</Last-Modified>
                <Etag>"0x8DB2B8A59DAC363"</Etag>
                <LeaseStatus>unlocked</LeaseStatus>
                <LeaseState>available</LeaseState>
                <PublicAccess>blob</PublicAccess>
                <DefaultEncryptionScope>$account-encryption-key</DefaultEncryptionScope>
                <DenyEncryptionScopeOverride>false</DenyEncryptionScopeOverride>
                <HasImmutabilityPolicy>false</HasImmutabilityPolicy>
                <HasLegalHold>false</HasLegalHold>
                <ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled>
            </Properties>
        </Container>
    </Containers>
    <NextMarker />
</EnumerationResults>

Таким образом мы можем проверить полный цикл и убедиться что все работает

Под капотом AzureDefaultCredentials видит переменные окружения и делает такой вот обмен токена

Альтернативный вариант, если нет какого то конретного сервиса или не понятно как к нему достучаться - это сходить в апи самого ажура

access_token=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "scope=https%3A%2F%2Fmanagement.azure.com%2F.default&client_id=$AZURE_CLIENT_ID&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$(cat $AZURE_FEDERATED_TOKEN_FILE)&grant_type=client_credentials" "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" | jq -r ".access_token")

curl -s -H "Authorization: Bearer $access_token" 'https://management.azure.com/subscriptions?api-version=2022-09-01' | jq ".value"

Тут мы запрашиваем список подписок, в ответ придет что то типа такого:

[
  {
    "id": "/subscriptions/9e9a4757-68f9-4e55-a5c7-1e6666f9539d",
    "authorizationSource": "RoleBased",
    "managedByTenants": [],
    "tags": {
      "organization": "robota",
      "environment": "dev"
    },
    "subscriptionId": "9e9a4757-68f9-4e55-a5c7-1e6666f9539d",
    "tenantId": "695e64b5-2d13-4ea8-bb11-a6fda2d60c41",
    "displayName": "ROBOTA - Dev2",
    "state": "Enabled",
    "subscriptionPolicies": {
      "locationPlacementId": "Public_2014-09-01",
      "quotaId": "CSP_2015-05-01",
      "spendingLimit": "Off"
    }
  }
]

В нашел сулчае все оказалось до боли смешно и нелепо, а ответ был у нас все это время перед глазами

Что бы вся эта штука работала нужно собслюсти 2 момента:

  • пода должна бежать из под service account'а созданного на манер описания в самом верху этого документа
  • пода должна иметь лейбочку azure.workload.identity/use=true

И действительно, если так подумать - с какого перепугу поду должны подсовываться токены, если он бежит из под сервисной учетки, у нас и так куча разных деплойментов, которые бегут из под своих учеток и слухом не слыхивали об ажуре и токенах и оно им не нужно, или к примеру, а если у нас под будет ходить в амазон, именно наличие аннотации запускает всю эту историю

Хоть мы и выяснили что с токенами все ок, приложение все так же не фурыкало, пока суть да дело пробуем все то же самое сделать только из donet, скидываю что бы была заготовка для копипасты

using System.Net.Http.Headers;
using Azure.Core;
using Azure.Identity;

// dotnet add package Azure.Identity

var credentials = new DefaultAzureCredential();
var tokenResult = await credentials.GetTokenAsync(new TokenRequestContext(new[] { "https://atsrobsad.blob.core.windows.net/.default" }), CancellationToken.None);
Console.WriteLine(tokenResult.Token);

var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-ms-version", "2021-08-06");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Token);
var claims = await client.GetStringAsync("https://atsrobsad.blob.core.windows.net/?comp=list");
Console.WriteLine(claims);

Для быстрой проверки гипотез и таких кусков код можно сделать следующий финт ушами

kubectl run -it --rm mactemp --image=mcr.microsoft.com/dotnet/sdk:7.0 --overrides='{"spec": { "serviceAccountName": "ats", "nodeSelector": {"kubernetes.io/os": "linux"}}, "metadata":{"labels":{"azure.workload.identity/use":"true"},"annotations":{"kubeforce":"skip"}}}' bash

Затем внутри

cd ~
mkdir foo
cd foo
dotnet new console
dotnet add package Azure.Identity

apt update
apt install -y vim
vim Program.cs

И дальше уже как обычно dotnet run