PowerShell Twoim przyjacielem jest! Robimy porządki w subskrypcji. cz. 3

To już 3 post o tym, jak zrobić porządek w swojej subskrypcji. Na tym na chwilę poprzestaniemy. Nie możemy przecież ciągle czegoś kasować i oszczędzać. Czas na coś twórczego. Dlatego kolejny wpis będzie o przygodach z jeszcze jedną usługą PaaS.

Część z obiektów, które zaraz sobie usuniemy nic nas nie kosztuje. Ale… moim zdaniem pozostawianie śmieci wprowadza bałagan, które z czasem naprawdę przeszkadza oraz zwiększa ryzyko.

Zanim jednak do twórczych rzeczy, dziś w agendzie:

  • wywalimy pozostawione Load Balancer’y
  • usuniemy nie wykorzystywane Virtual Network Gateway (czyli to, czego używasz to Site2Site VPN, Point2Site VPN i Vnet2Vnet)
  • pozbędziemy się serwerów SQL, które zawierają tylko jedną bazę danych (czyli master ;))
  • sprawdzimy wszystkie konta Azure Cosmos DB i Azure Search
  • usuniemy nieużywane CDN, zarówno te od Akamai jak i Verizon
  • i na koniec perełka. Usuniemy wszystkie konta, które nie mają żadnych uprawnień do naszej subskrypcji, nie są Administratorami (wszystkie przypadki) i nie są tzw. Service Principal

Pamiętaj! Usuwasz na własną odpowiedzialność. Jeśli znajdziesz błędy, daj znać!

Load Balancer

#Get not attached Load Balancers
$lbs=Get-AzureRmLoadBalancer | where {[string]::IsNullOrWhitespace($_.LoadBalancingRules) -OR [string]::IsNullOrWhitespace($_.Probes)}
foreach ($lb in $lbs)
{
    Write-Host $lb.Name 'will be deleted!'
    #Remove-AzureRmLoadBalancer -Name $lb.Name -ResourceGroupName $lb.ResourceGroupName -Force 
}

Pusty Load Balancer to taki, który nie ma żadnych reguł „rozrzucania ruchu” lub też nie ma żadnej metody do sprawdzania, czy podłączone „końcówki” żyją.

Virtual Network Gateway

#Get virtual network gateways
$ngs=Find-AzureRmResource -ResourceType "Microsoft.Network/virtualNetworkGateways"
foreach ($ng in $ngs)
{
    $vng=Get-AzureRmVirtualNetworkGateway -ResourceGroupName $ng.ResourceGroupName
    $connections=Get-AzureRmVirtualNetworkGatewayConnection -ResourceGroupName $ng.ResourceGroupName 
    if ($connections)
    {
        Write-Host $vng.Name 'will be deleted!'
        #Remove-AzureRmVirtualNetworkGateway -ResourceGroupName $vng.ResourceGroupName -Name $vng.Name -Force
    }
}

Tej części kodu jestem najmniej pewny. Dla mnie pusty VirtualNetworkGateway to taki, który nie ma żadnego połączenia. Połączenie jednak nie zawiera relacje do Gateway i sprawa się komplikuje. Założyłem :), że jedno i drugie są w tej samej ResourceGroup i że taka bramka jest tylko jedna. W przyszłości do poprawy, dla mnie na dziś wystarczy.

Azure SQL Servers oraz SQL Databases

#Get Azure SQL Servers with no DB's
$servers=Find-AzureRmResource -ResourceType "Microsoft.Sql/servers"
foreach ($s in $servers)
{
    
    $databases=Get-AzureRmSqlDatabase -ServerName $s.Name -ResourceGroupName $s.ResourceGroupName
    #every server contains master db
    if ($databases.Count -eq 1) 
    { 
        Write-Host $s.Name 'will be deleted!'
        #Remove-AzureRmResource -ResourceName $s.Name -ResourceGroupName $s.ResourceGroupName -Force
    }
}

Koncepcja jest prosta. Sprawdzamy wszystkie serwery SQL i sprawdzamy, gdzie ilość baz wynosi 1. Należałoby jeszcze zrobić listę baz, których wielkość wskazuje na to, że są puste. Jest to bardzo proste, obiekt bazy ma parametr Size wyrażony w KB.

Azure Cosmos DB oraz Azure Search

Te dwa serwisy zostaną potraktowane razem, bo żaden z nich niestety nie ma swoich, dedykowanych poleceń w PowerShell. Natomiast to co można zrobić, to zobaczyć wszystkie usługi jakie mamy dla obu typów. Co więcej, jeśli z portalu pobierzesz sobie klucz API (jak j niżej) i wykorzystasz REST API, to możesz sprawdzić np. liczbę indexów w Azure Search czy liczbę baz lub kolekcji w Cosmos DB. Jeśli nie lubisz REST API, polecam SDK. Możesz też wykonać te polecenia, są super opisane w dokumentacji

#Get Cosmos DB accounts with no DB's created
$docdbaccounts=Find-AzureRmResource -ResourceType "Microsoft.DocumentDb/databaseAccounts"

#Get Azure Search with no collections
$searchServices=Find-AzureRmResource -ResourceType "Microsoft.Search/searchServices" 

<# #with RestApi you can get the list of indexes for particular search service and then decide what to do $ResourceCard='https://mifurmsr.search.windows.net/indexes?api-version=2016-09-01' $ResHeaders = @{‘api-key’ = 'D0271A3D3A33B975CAF10892C17FA402'} $response=Invoke-RestMethod -Uri $ResourceCard -Headers $ResHeaders -ContentType ‘application/json’ #> 

Azure CDN’s

Tutaj wprowadzenie jest zbędne. Pobieramy wszystkie profile i sprawdzamy, które z nich nie mają żadnych „endpointów”. Jeśli nie ma „końcówek” to CDN nie jest potrzebny.

#Get CDNs
$cdns=Get-AzureRmCdnProfile 
foreach ($cdn in $cdns)
{
    $endpoints=Get-AzureRmCdnEndpoint -ProfileName $cdn.Name -ResourceGroupName $cdn.ResourceGroupName
    if ($endpoints.Count -eq 0) 
    { 
        Write-Host $cdn.Name 'will be deleted.'
        #Remove-AzureRmCdnProfile -ProfileName $cdn.Name -ResourceGroupName $cdn.ResourceGroupName -Force
    }
}

I na koniec najtrudniejsze. Proszę Państwa, Azure Active Directory.

Co robimy skrypt:

  • pobiera użytkowników metodą Get-AzureRmAdUser
  • pobiera wszystkich administratorów w ramach subskrypcji
  • sprawdza następnie czy użytkownik aby nie jest adminem (i tych oszczędza) i czy ma jakieś przypisane uprawnienia do zasobów. Jeśli przypisań brak, użytkownik jest usuwany.
  • Jeśli ma jednak jakieś prawa do zasobów, sprawdzamy czy to przypisanie występuje w subskrypcji, do której jesteśmy zalogowani.  Stąd <SUBSCRIPTION ID> w kodzie poniżej. Jeśli są jakieś prawa, warto je wypisać i zobaczyć, czy faktycznie nadal są potrzebne.
#Get Active Directory accounts with no rights to subscrption
$users=Get-AzureRmADUser 
$admins=Get-AzureRmRoleAssignment -IncludeClassicAdministrators | Where { ($_.RoleDefinitionName -like '*Administrator*')} | Select-Object -ExpandProperty SignInName

foreach ($user in $users)
{
    if ($user.UserPrincipalName)
    {
        if (($admins -inotcontains $user.UserPrincipalName) -and ($user.DisplayName -notlike '*On-Premises Directory Synchronization*')) { 
        $assignment=Get-AzureRmRoleAssignment -SignInName $user.UserPrincipalName -ExpandPrincipalGroups 
        if (!$assignment)
        {
            Write-Host $user.DisplayName $user.UserPrincipalName 'will be deleted!'
            #Remove-AzureRmADUser -UPNOrObjectId $user.UserPrincipalName -Force
        }
        else
        {
            $t=($assignment.Scope.Split('/'))[2]
            if ($t -eq '&amp;lt;SUBSCRIPTION ID&amp;gt;') 
            {
                Write-Host $user.DisplayName $user.UserPrincipalName 'has some scopes!'
                Get-AzureRmRoleAssignment -SignInName $user.UserPrincipalName -ExpandPrincipalGroups | FL SignInName, RoleDefinitionName, Scope
            }
        }
      }
    }
}

Kod przetestowany na dwóch subskrypcjach. Ale… testują go dla Ciebie dalej. Od dziś będzie chodził każdego wieczora w Azure Automation i usuwał moje radosne i nieudane próby. Jeśli Ty ma jakiś swój ulubiony skrypt, który skutecznie usuwa śmieci, daj znać!

Ten wpis został opublikowany w kategorii PowerShell i oznaczony tagami , , , , . Dodaj zakładkę do bezpośredniego odnośnika.