Skip to content

Authorization:Changes for adding a script commandlet Get-AzureAuthorizationChangeLog #810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\packages\Microsoft.Azure.Graph.RBAC.1.7.0-preview\lib\net40\Microsoft.Azure.Graph.RBAC.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Azure.Insights">
<HintPath>..\..\..\packages\Microsoft.Azure.Insights.0.7.7-preview\lib\net45\Microsoft.Azure.Insights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Azure.KeyVault.Core">
<HintPath>..\..\..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Azure.Management.Authorization, Version=0.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\packages\Microsoft.Azure.Management.Authorization.0.19.2-preview\lib\net40\Microsoft.Azure.Management.Authorization.dll</HintPath>
Expand Down Expand Up @@ -467,6 +473,9 @@
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.ResourceTests\TestSetAResourceTest.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests\RaAuthorizationChangeLog.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests\RaByResource.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Microsoft.Azure.Common.Authentication;
using Microsoft.Azure.Gallery;
using Microsoft.Azure.Graph.RBAC;
using Microsoft.Azure.Insights;
using Microsoft.Azure.Management.Authorization;
using Microsoft.Azure.Management.Resources;
using Microsoft.Azure.Subscriptions;
Expand All @@ -47,6 +48,8 @@ public sealed class ResourcesController
public SubscriptionClient SubscriptionClient { get; private set; }

public GalleryClient GalleryClient { get; private set; }

public InsightsClient InsightsClient { get; private set; }

// TODO: http://vstfrd:8080/Azure/RD/_workitems#_a=edit&id=3247094
//public EventsClient EventsClient { get; private set; }
Expand Down Expand Up @@ -111,7 +114,8 @@ public void RunPsTestWorkflow(
helper.SetupModules(
AzureModule.AzureResourceManager,
"ScenarioTests\\Common.ps1",
"ScenarioTests\\" + callingClassName + ".ps1");
"ScenarioTests\\" + callingClassName + ".ps1",
"ResourceManagerStartup.ps1");

try
{
Expand Down Expand Up @@ -142,6 +146,7 @@ private void SetupManagementClients()
GalleryClient = GetGalleryClient();
AuthorizationManagementClient = GetAuthorizationManagementClient();
GraphClient = GetGraphClient();
InsightsClient = GetInsightsClient();
this.FeatureClient = this.GetFeatureClient();
HttpClientHelperFactory.Instance = new TestHttpClientHelperFactory(this.csmTestFactory.GetTestEnvironment().Credentials as SubscriptionCloudCredentials);

Expand All @@ -150,6 +155,7 @@ private void SetupManagementClients()
GalleryClient,
AuthorizationManagementClient,
GraphClient,
InsightsClient,
this.FeatureClient);
}

Expand Down Expand Up @@ -206,6 +212,11 @@ private GalleryClient GetGalleryClient()
return TestBase.GetServiceClient<GalleryClient>(this.csmTestFactory);
}

private InsightsClient GetInsightsClient()
{
return TestBase.GetServiceClient<InsightsClient>(this.csmTestFactory);
}

/// <summary>
/// The test http client helper factory.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,26 @@
using Microsoft.Azure.Graph.RBAC;
using Microsoft.Azure.Graph.RBAC.Models;
using Microsoft.Azure.Management.Authorization;
using Microsoft.Azure.Management.Authorization.Models;
using Microsoft.Azure.Management.Resources;
using Microsoft.Azure.Management.Resources.Models;
using Microsoft.Azure.Test.HttpRecorder;
using Microsoft.WindowsAzure.Commands.ScenarioTest;
using Microsoft.Azure.Test;
using System;
using System.Linq;
using System.Threading;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using Xunit;

namespace Microsoft.Azure.Commands.Resources.Test.ScenarioTests
{
public class RoleAssignmentTests
{
[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void RaAuthorizationChangeLog()
{
ResourcesController.NewInstance.RunPsTest("Test-RaAuthorizationChangeLog");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void RaNegativeScenarios()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ function Test-RaUserPermissions
Assert-AreEqual $action $permissions.Permissions[0].Actions[0] "Permission action mismatch."
}

<#
.SYNOPSIS
Tests verifies Get-AzureAuthorizationChangeLog
#>
function Test-RaAuthorizationChangeLog
{
$log1 = Get-AzureAuthorizationChangeLog -startTime 2015-08-27 -EndTime 2015-08-27T22:30:00Z

# Assert
Assert-True { $log1.Count -ge 1 } "At least one record should be returned for the user"
}



<#
.SYNOPSIS
Creates role assignment
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<package id="Microsoft.Azure.Common.Dependencies" version="1.0.0" targetFramework="net45" />
<package id="Microsoft.Azure.Gallery" version="2.6.2-preview" targetFramework="net45" />
<package id="Microsoft.Azure.Graph.RBAC" version="1.7.0-preview" targetFramework="net45" />
<package id="Microsoft.Azure.Insights" version="0.7.7-preview" targetFramework="net45" />
<package id="Microsoft.Azure.KeyVault.Core" version="1.0.0" targetFramework="net45" />
<package id="Microsoft.Azure.Management.Authorization" version="0.19.2-preview" targetFramework="net45" />
<package id="Microsoft.Azure.Management.Resources" version="2.18.7-preview" targetFramework="net45" />
<package id="Microsoft.Azure.Test.Framework" version="1.0.5687.28567-prerelease" targetFramework="net45" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,190 @@
"Get-AzureStorageContainerAcl" = "Get-AzureStorageContainer";
"Start-CopyAzureStorageBlob" = "Start-AzureStorageBlobCopy";
"Stop-CopyAzureStorageBlob" = "Stop-AzureStorageBlobCopy";
}.GetEnumerator() | Select @{Name='Name'; Expression={$_.Key}}, @{Name='Value'; Expression={$_.Value}} | New-Alias -Description "AzureAlias"
}.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-AzureResourceProviderLog" commandlet

function Get-AzureAuthorizationChangeLog {
[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-AzureResourceProviderLog -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-AzureRoleDefinition | % { $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 = $endEvent.EventTimestamp
$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) {

$out.PrincipalId = $messageBody.properties.principalId
if ($out.PrincipalId -ne $null) {
$principalDetails = Get-PrincipalDetails $out.PrincipalId ([REF]$principalDetailsCache)
$out.PrincipalName = $principalDetails.Name
$out.PrincipalType = $principalDetails.Type
}

if ([string]::IsNullOrEmpty($out.Scope)) { $out.Scope = $messageBody.properties.Scope }
if ($out.Scope -ne $null) {
$resourceDetails = Get-ResourceDetails $out.Scope
$out.ScopeName = $resourceDetails.Name
$out.ScopeType = $resourceDetails.Type
}

$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 = $_.EventTimestamp
$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

# Helper functions
# Resolve a principal. If the principal's object id was encountered in the principals resolved so far, return principalDetails from the cache.
# Else make a Grpah call and add that principal to cache of known principals
function Get-PrincipalDetails($principalId, [REF]$principalDetailsCache)
{
if($principalDetailsCache.Value.ContainsKey($principalId)) {
return $principalDetailsCache.Value[$principalId]
}

$principalDetails = "" | select Name, Type
$user = Get-AzureADUser -ObjectId $principalId
if ($user) {
$principalDetails.Name = $user.DisplayName
$principalDetails.Type = "User"
} else {
$group = Get-AzureADGroup -ObjectId $principalId
if ($group) {
$principalDetails.Name = $group.DisplayName
$principalDetails.Type = "Group"
} else {
$servicePrincipal = Get-AzureADServicePrincipal -objectId $principalId
if ($servicePrincipal) {
$principalDetails.Name = $servicePrincipal.DisplayName
$principalDetails.Type = "Service Principal"
}
}
}

$principalDetailsCache.Value.Add($principalId, $principalDetails);

$principalDetails
}

# Get resource details from scope
function Get-ResourceDetails($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]
}

$resourceDetails
}