intro
kolejny brzegowy przypadek. tym razem mógł mnie kosztować usunięcie istniejących subskrypcji – a więc 'niuans, który może zabić’.
scenariusz to clean up subskrypcji. jest ich kilkaset, więc trzeba podejść do tematu systemowo. najpierw zajmę się omówieniem scenariusza i moim podejściem do sprzątania, a jak kogoś interesuje sam bug w Azure – to będzie na końcu.
clean up process
w pierwszej kolejności trzeba wyizolować osierocone i puste subskrypcje. o tym jak znaleźć te puste szybko, czyli KQLem – za niedługo, wraz z małym qrsem KQL. dziś o tych 'osieroconych’.
duża część subskrypcji zakładana jest na chwilę, głównie przez developerów, którzy przychodzą i odchodzą…. a subskrypcje zostają. warto więc znaleźć te, których właściciele już nie istnieją w AAD.
nie żeby i tu nie było problemów, bo okazuje się, że zadanie pod tytułem 'jaki to typ subskrypcji’ również okazało się zadaniem nietrywialnym. przez typ, mam na myśli offering. to, co widać w portalu jest trudno wyciągnąć z commandline – potrzeba do tego gimnastyki z REST API do API billingowego… ale to również na inny dzień. krótko mówiąc – tradycyjnie, PowerShellowo, nie da się tego wyłuskać. a jeśli ktoś chciałby mi przedstawić atrybut 'SubscriptionId’ to powiem, że nie tędy droga… ale to również leży w backlogu do opisania.
zadanie wygląda więc tak – zrobić listing wszystkich subskrypcji wraz z osobami które są Ownerami w ARM IAM oraz Classic Administrators – czyli role Service Administrator oraz Co-Administrator. posłużą zarówno jako lista kontaktowa, oraz pozwolą na kolejne odpytania, żeby wyłuskać te konta, które już nie istnieją. subskrypcje bez właścicieli to najlepsze kandydatki do podróży do śmietnika. posłuży do tego taki prosty skrypcik, napisany na kolanie:
$csvOwnerFile = "c:\temp\subsAndOwners.csv" Get-AzSubscription | ? {$_.state -ne 'disabled'} | %{ $subname= $_.Name $subID = $_.SubscriptionId Set-AzContext -Subscription $_|Out-Null write-host "processing $subname..." Get-AzRoleAssignment -IncludeClassicAdministrators | ? { ($_.RoleDefinitionName -eq 'owner' -or $_.RoleDefinitionName -match 'administrator') -and ([string]::IsNullOrEmpty($_.RoleAssignmentId) -or $_.RoleAssignmentId -match 'subscriptions') } | select @{L='SubscriptionName';E={$subname}},@{L='subscriptionID';E={$subID}},RoleDefinitionName,DisplayName,SignInName,roleassignmentid } | export-csv -nti -Encoding utf8 -Path $csvOwnerFile &(convert-CSV2XLS $csvOwnerFile)
([string]::IsNullOrEmpty($_.RoleAssignmentId) -or $_.RoleAssignmentId -match 'subscriptions')
można się również zastanowić nad optymalniejszą metodą… ale to znów REST API a nie mam na to opanowanych bibliotek jeszcze /: kolejny z masy wpisów na backlogu…
$users = load-CSV c:\temp\subsAndOwners.csv connect-azuread $users.SignInName | select -unique | %{ $u=Get-AzureADUser -SearchString $_; if([string]::IsNullOrEmpty($u)) {write-host "$_" -ForegroundColor Red} }
load-CSV to kolejna funkcja z eNlib – jedna z najczęściej przeze mnie używanych. ładuje CSV z automatyczną detekcją znaq oddzielającego, co jest mega istotne jak się pracuje w różnych regionalsach. ma też kilka innych bajerów… ale nie o tym tu. dalej już z górki – wyłusqję SignInNames unikalne i odpytuję AAD o użytkownika. ponieważ brak użytkownika nie jest błędem, a nullem, więc nie można użyć try/catch a trzeba wykorzystać IFa.
i wydawałoby się że mam już to, co potrzeba – wrzucam wynik do excela, robię XLOOKUP i pięknie widać które subskrypcje należą do kont, których już wśród nas nie ma… zabrzmiało złowieszczo khekhe…
Błąd w Azure
…chyba, żeby nie. całe szczęście natchnęło mnie, żeby jednak kilka posprawdzać ręcznie. i nagle okazało się, że konta których nie ma… czasem jednak są. a wszystko rozbija się o 'Classic Administrators’, który musi wewnętrznie źle komunikować się z ARMem. wiele staje się jaśniejsze, jeśli przyjrzeć się jak takie obiekty są listowane przy 'get-AzRoleAssigment -includeClassic’:
RoleAssignmentName : RoleAssignmentId : Scope : /subscriptions/732d1eea-xxxx-xxxx-xxxx-6154fb40f73f DisplayName : uname@w-files.pl SignInName : uname@w-files.pl RoleDefinitionName : ServiceAdministrator RoleDefinitionId : ObjectId : ObjectType : Microsoft.Authorization/classicAdministrators CanDelegate : False Description : ConditionVersion : Condition :
na pierwszy rzut okiem może wydawać się, że jest ok… ale gdzie jest 'ObjectId’? to jednak tylko pierwsza część problemu. porządki mają to do siebie, że robi się je rzadko. a to oznacza bagaż historii. a w tym bagażu np. projekt standaryzacji nazw użytkowników i zmiana UPNów z gsurname@domain.name na givenname.surname@domain.name. okazuje się, że ten staruszek nie ma mechanizmu odświeżania i pomimo zmiany UPN, czyli de facto SignInName, tutaj pokazuje starą nazwę. a więc przy odpytaniu AAD – nie znajduje usera, pomimo, że ten istnieje.
co ciekawe, jeśli wejdzie się do uprawnień IAM w portalu Azure, każdy user jest aktywnym linkiem, pozwalającym na kliknięcie i przeniesienie do AAD, na szczegóły konta. tak z ciekawości spróbujcie to zrobić dla Classic Administrators…
całe szczęście projekt był zrobiony z głową i stare UPNy zostały jako aliasy mailowe, a więc doprecyzowałem zapytanie:
$users.SignInName | select -unique |%{ $u=Get-AzureADUser -Filter "UserPrincipalName eq '$_' or proxyAddresses/any(p:startswith(p,'smtp:$_'))"; if([string]::IsNullOrEmpty($u)) {write-host "$_" -ForegroundColor Red} }
ale co mnie to czasu i nerwów kosztowało…
możliwości filtra AzureADUser też podnoszą ciśnienie… już nie wspominając o tym, że commandlety AzureAD nie wspierają PowerShell 7, a commandlety Az
nie wspierająnie działają dobrze w PowerShell 5. mam od razu w głowie 'niezły burdel tam macie, w tym swoim Archosofcie!’
eN.
Grzegorz
nExoR