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)
większość commandletów jest z pakietu modułów Az, a ostatni – convert-CSV2XLS z mojego modułu eNlib [WOA! przekroczył 1k downloadów! jak miło q:]. zakładam, że całość jest prosta, ale to lepiej wyjaśnić:
([string]::IsNullOrEmpty($_.RoleAssignmentId) -or $_.RoleAssignmentId -match 'subscriptions')
ten fragment filtra wyrzuca uprawnienia dziedziczone z Management Groups, żeby było trochę przejrzyściej.
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…
dobra. jest CSV i excel dla czytelności, teraz trzeba sprawdzić czy konta istnieją. w excelu czyszczę, co niepotrzebne, co wiem, co mnie interesuje, export do CSVki z tymi wpisami, które uznałem za ważne, i jedziemy:
$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.

 

-o((:: sprEad the l0ve ::))o-

Comments (2)

  1. Grzegorz

    Odpowiedz

    Nie wiem czy można to nazwać bugiem w Azure. To raczej sposób implementacji, który ma określone konsekwencje. Tu nie ma żadnej synchronizacji pomiędzy obiektami ClassicAdministrators a obiektami w AAD.

    Można to sprawdzić listując ClassicAdministrators przez API serwisu Authorization. Jeśli zalogujemy się przez Connect-AzAccount to można to łatwo zrobić takim onelinerem:

    (Invoke-RestMethod -Method Get -Uri „https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/classicAdministrators?api-version=2015-07-01” -Authentication Bearer -Token ((Get-AzAccessToken).Token | ConvertTo-SecureString -AsPlainText -Force)).value | fl

    W zwróconym obiekcie nie ma żadnych ID związanych z AAD a jedynie adres email.
    Nie ma też żadnej synchronizacji w drugą stronę. Gdy zmieniasz spn, czy cokolwiek innego po stronie AAD to nie jest to synchronizowane do obiektu ClassicAdministrator.

    No i skąd pomysł, że moduł Az nie wspiera PowerShell 5? Już pomijam dokumentację, gdzie jasno napisano, że wspiera PS od wersji 5.1, ale wystarczy zaglądnąć do manifestu modułu gdzie jak byk napisano:

    # Supported PSEditions
    CompatiblePSEditions = 'Core’, 'Desktop’

    # Minimum version of the PowerShell engine required by this module
    PowerShellVersion = '5.1′

    Czyli wspiera zarówno PowerShell 'Core’ (PowerShell wersja 6.0+) jak i PowerShell 'Desktop’ (Windows PowerShell wersja 5.1).

    No i na koniec, jeśli chcesz używać PowerShell 7 (czyli wersję Core) to zamiast Get-AzureADUser z modułu AzureAD możesz użyć Get-AzADUser z modulu Az (a dokładnie z Az.Resources)

    • Odpowiedz

      dzięki za podpowiedzi i cenne uwagi (:
      niemniej ja nadal będę twierdził, że to bug, lub jeśli wolisz – niedoróbstwo. semantyka. jeśli informacje pokazywane są niespójnie, zwłaszcza w przypadku elementów związanych z bezpieczeństwem czy uprawnieniami, to imho jest to niedopuszczalne.

      co do niewspierania PS5 – cokolwiek tam nie wypisują oficjalnie, niestety w praktyce nie działa to dobrze. w PS5 commandlety często się zawieszają, więc owszem, 'niewpierane’ jest nadużyciem – zmienię nie 'nieużywalne’.

Skomentuj nExoR Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Time limit is exhausted. Please reload CAPTCHA.