ResourceManagerStartup.ps1

# ----------------------------------------------------------------------------------
#
# Copyright Microsoft Corporation
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------------

@{
    # Sql aliases
    "Get-AzureRmSqlDatabaseServerAuditingPolicy" = "Get-AzureRmSqlServerAuditingPolicy";
    "Remove-AzureRmSqlDatabaseServerAuditing" = "Remove-AzureRmSqlServerAuditing";
    "Set-AzureRmSqlDatabaseServerAuditingPolicy" = "Set-AzureRmSqlServerAuditingPolicy";
    "Use-AzureRmSqlDatabaseServerAuditingPolicy" = "Use-AzureRmSqlServerAuditingPolicy";

    # Storage aliases
    "Get-AzureRmStorageContainerAcl" = "Get-AzureRmStorageContainer";
    "Start-CopyAzureStorageBlob" = "Start-AzureRmStorageBlobCopy";
    "Stop-CopyAzureStorageBlob" = "Stop-AzureRmStorageBlobCopy";
}.GetEnumerator() | Select @{Name='Name'; Expression={$_.Key}}, @{Name='Value'; Expression={$_.Value}} | New-Alias -Description "AzureAlias"


# Authorization script commandlet that builds on top of existing Insights comandlets.
# This commandlet gets all events for the "Microsoft.Authorization" resource provider by calling the "Get-AzureRmResourceProviderLog" commandlet

function Get-AzureRmAuthorizationChangeLog { 
    [CmdletBinding()] 
    param(  
        [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true, HelpMessage = "The start time. Optional
             If both StartTime and EndTime are not provided, defaults to querying for the past 1 hour. Maximum allowed difference in StartTime and EndTime is 15 days"
)] 
        [DateTime] $StartTime,

        [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true, HelpMessage = "The end time. Optional.
            If both StartTime and EndTime are not provided, defaults to querying for the past 1 hour. Maximum allowed difference in StartTime and EndTime is 15 days"
)] 
        [DateTime] $EndTime
    ) 
    PROCESS { 
         # Get all events for the "Microsoft.Authorization" provider by calling the Insights commandlet
         $events = Get-AzureRmResourceProviderLog -ResourceProvider "Microsoft.Authorization" -DetailedOutput -StartTime $StartTime -EndTime $EndTime
             
         $startEvents = @{}
         $endEvents = @{}
         $offlineEvents = @()

         # StartEvents and EndEvents will contain matching pairs of logs for when role assignments (and definitions) were created or deleted.
         # i.e. A PUT on roleassignments will have a Start-End event combination and a DELETE on roleassignments will have another Start-End event combination
         $startEvents = $events | ? { $_.httpRequest -and $_.Status -ieq "Started" }
         $events | ? { $_.httpRequest -and $_.Status -ne "Started" } | % { $endEvents[$_.OperationId] = $_ }
         # This filters non-RBAC events like classic administrator write or delete
         $events | ? { $_.httpRequest -eq $null } | % { $offlineEvents += $_ } 

         $output = @()

         # Get all role definitions once from the service and cache to use for all 'startevents'
         $azureRoleDefinitionCache = @{}
         Get-AzureRmRoleDefinition | % { $azureRoleDefinitionCache[$_.Id] = $_ }

         $principalDetailsCache = @{}

         # Process StartEvents
         # Find matching EndEvents that succeeded and relating to role assignments only
         $startEvents | ? { $endEvents.ContainsKey($_.OperationId) `
             -and $endEvents[$_.OperationId] -ne $null `
             -and $endevents[$_.OperationId].OperationName.StartsWith("Microsoft.Authorization/roleAssignments", [System.StringComparison]::OrdinalIgnoreCase)  `
             -and $endEvents[$_.OperationId].Status -ieq "Succeeded"} |  % {
       
         $endEvent = $endEvents[$_.OperationId];
        
         # Create the output structure
         $out = "" | select Timestamp, Caller, Action, PrincipalId, PrincipalName, PrincipalType, Scope, ScopeName, ScopeType, RoleDefinitionId, RoleName
                 
         $out.Timestamp = Get-Date -Date $endEvent.EventTimestamp -Format u
         $out.Caller = $_.Caller
         if ($_.HttpRequest.Method -ieq "PUT") {
            $out.Action = "Granted"
            if ($_.Properties.Content.ContainsKey("requestbody")) {
                $messageBody = ConvertFrom-Json $_.Properties.Content["requestbody"]
            }
             
          $out.Scope =  $_.Authorization.Scope
        } 
        elseif ($_.HttpRequest.Method -ieq "DELETE") {
            $out.Action = "Revoked"
            if ($endEvent.Properties.Content.ContainsKey("responseBody")) {
                $messageBody = ConvertFrom-Json $endEvent.Properties.Content["responseBody"]
            }
        }

        if ($messageBody) {
            # Process principal details
            $out.PrincipalId = $messageBody.properties.principalId
            if ($out.PrincipalId -ne $null) { 
                # Get principal details by querying Graph. Cache principal details and read from cache if present
                $principalId = $out.PrincipalId 
                
                if($principalDetailsCache.ContainsKey($principalId)) {
                    # Found in cache
                    $principalDetails = $principalDetailsCache[$principalId]
                } else { # not in cache
                    $principalDetails = "" | select Name, Type
                    $user = Get-AzureRmADUser -ObjectId $principalId
                    if ($user) {
                        $principalDetails.Name = $user.DisplayName
                        $principalDetails.Type = "User"    
                    } else {
                        $group = Get-AzureRmADGroup -ObjectId $principalId
                        if ($group) {
                            $principalDetails.Name = $group.DisplayName
                            $principalDetails.Type = "Group"        
                        } else {
                            $servicePrincipal = Get-AzureRmADServicePrincipal -objectId $principalId
                            if ($servicePrincipal) {
                                $principalDetails.Name = $servicePrincipal.DisplayName
                                $principalDetails.Type = "Service Principal"                        
                            }
                        }
                    }              
                    # add principal details to cache
                    $principalDetailsCache.Add($principalId, $principalDetails);
                }

                $out.PrincipalName = $principalDetails.Name
                $out.PrincipalType = $principalDetails.Type
            }

            # Process scope details
            if ([string]::IsNullOrEmpty($out.Scope)) { $out.Scope = $messageBody.properties.Scope }
            if ($out.Scope -ne $null) {
                # Remove the authorization provider details from the scope, if present
                if ($out.Scope.ToLower().Contains("/providers/microsoft.authorization")) {
                    $index = $out.Scope.ToLower().IndexOf("/providers/microsoft.authorization") 
                    $out.Scope = $out.Scope.Substring(0, $index) 
                }

                  $scope = $out.Scope 
                $resourceDetails = "" | select Name, Type
                $scopeParts = $scope.Split('/', [System.StringSplitOptions]::RemoveEmptyEntries)
                $len = $scopeParts.Length

                if ($len -gt 0 -and $len -le 2 -and $scope.ToLower().Contains("subscriptions"))    {
                    $resourceDetails.Type = "Subscription"
                    $resourceDetails.Name  = $scopeParts[1]
                } elseif ($len -gt 0 -and $len -le 4 -and $scope.ToLower().Contains("resourcegroups")) {
                    $resourceDetails.Type = "Resource Group"
                    $resourceDetails.Name  = $scopeParts[3]
                    } elseif ($len -ge 6 -and $scope.ToLower().Contains("providers")) {
                        $resourceDetails.Type = "Resource"
                        $resourceDetails.Name  = $scopeParts[$len -1]
                        }
                
                $out.ScopeName = $resourceDetails.Name
                $out.ScopeType = $resourceDetails.Type
            }

            # Process Role definition details
            $out.RoleDefinitionId = $messageBody.properties.roleDefinitionId
            if ($out.RoleDefinitionId -ne $null) {
                if ($azureRoleDefinitionCache[$out.RoleDefinitionId]) {
                    $out.RoleName = $azureRoleDefinitionCache[$out.RoleDefinitionId].Name
                } else {
                    $out.RoleName = ""
                }
            }
        }
        $output += $out
    } # start event processing complete

    # Filter classic admins events
    $offlineEvents | % {
        if($_.Status -ne $null -and $_.Status -ieq "Succeeded" -and $_.OperationName -ne $null -and $_.operationName.StartsWith("Microsoft.Authorization/ClassicAdministrators", [System.StringComparison]::OrdinalIgnoreCase)) {
            
            $out = "" | select Timestamp, Caller, Action, PrincipalId, PrincipalName, PrincipalType, Scope, ScopeName, ScopeType, RoleDefinitionId, RoleName
            $out.Timestamp = Get-Date -Date $_.EventTimestamp -Format u
            $out.Caller = "Subscription Admin"

            if($_.operationName -ieq "Microsoft.Authorization/ClassicAdministrators/write"){
                $out.Action = "Granted"
            } 
            elseif($_.operationName -ieq "Microsoft.Authorization/ClassicAdministrators/delete"){
                $out.Action = "Revoked"
            }

            $out.RoleDefinitionId = $null
            $out.PrincipalId = $null
            $out.PrincipalType = "User"
            $out.Scope = "/subscriptions/" + $_.SubscriptionId
            $out.ScopeType = "Subscription"
            $out.ScopeName = $_.SubscriptionId
                                
            if($_.Properties -ne $null){
                $out.PrincipalName = $_.Properties.Content["adminEmail"]
                $out.RoleName = "Classic " + $_.Properties.Content["adminType"]
            }
                     
            $output += $out
        }
    } # end offline events

    $output | Sort Timestamp
} 
} # End commandlet