Skip to content

Commit 684e8cc

Browse files
committed
Authorization:Changes for adding a script commandlet Get-AzureAuthorizationChangeLog
1 parent 8da68f1 commit 684e8cc

File tree

7 files changed

+425
-5
lines changed

7 files changed

+425
-5
lines changed

src/ResourceManager/Resources/Commands.Resources.Test/Commands.Resources.Test.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@
6464
<SpecificVersion>False</SpecificVersion>
6565
<HintPath>..\..\..\packages\Microsoft.Azure.Graph.RBAC.1.7.0-preview\lib\net40\Microsoft.Azure.Graph.RBAC.dll</HintPath>
6666
</Reference>
67+
<Reference Include="Microsoft.Azure.Insights">
68+
<HintPath>..\..\..\packages\Microsoft.Azure.Insights.0.7.7-preview\lib\net45\Microsoft.Azure.Insights.dll</HintPath>
69+
</Reference>
70+
<Reference Include="Microsoft.Azure.KeyVault.Core">
71+
<HintPath>..\..\..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll</HintPath>
72+
</Reference>
6773
<Reference Include="Microsoft.Azure.Management.Authorization, Version=0.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
6874
<SpecificVersion>False</SpecificVersion>
6975
<HintPath>..\..\..\packages\Microsoft.Azure.Management.Authorization.0.19.2-preview\lib\net40\Microsoft.Azure.Management.Authorization.dll</HintPath>
@@ -467,6 +473,9 @@
467473
</None>
468474
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.ResourceTests\TestSetAResourceTest.json">
469475
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
476+
</None>
477+
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests\RaAuthorizationChangeLog.json">
478+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
470479
</None>
471480
<None Include="SessionRecords\Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests\RaByResource.json">
472481
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

src/ResourceManager/Resources/Commands.Resources.Test/ScenarioTests/ResourcesController.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using Microsoft.Azure.Common.Authentication;
2323
using Microsoft.Azure.Gallery;
2424
using Microsoft.Azure.Graph.RBAC;
25+
using Microsoft.Azure.Insights;
2526
using Microsoft.Azure.Management.Authorization;
2627
using Microsoft.Azure.Management.Resources;
2728
using Microsoft.Azure.Subscriptions;
@@ -47,6 +48,8 @@ public sealed class ResourcesController
4748
public SubscriptionClient SubscriptionClient { get; private set; }
4849

4950
public GalleryClient GalleryClient { get; private set; }
51+
52+
public InsightsClient InsightsClient { get; private set; }
5053

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

116120
try
117121
{
@@ -142,6 +146,7 @@ private void SetupManagementClients()
142146
GalleryClient = GetGalleryClient();
143147
AuthorizationManagementClient = GetAuthorizationManagementClient();
144148
GraphClient = GetGraphClient();
149+
InsightsClient = GetInsightsClient();
145150
this.FeatureClient = this.GetFeatureClient();
146151
HttpClientHelperFactory.Instance = new TestHttpClientHelperFactory(this.csmTestFactory.GetTestEnvironment().Credentials as SubscriptionCloudCredentials);
147152

@@ -150,6 +155,7 @@ private void SetupManagementClients()
150155
GalleryClient,
151156
AuthorizationManagementClient,
152157
GraphClient,
158+
InsightsClient,
153159
this.FeatureClient);
154160
}
155161

@@ -206,6 +212,11 @@ private GalleryClient GetGalleryClient()
206212
return TestBase.GetServiceClient<GalleryClient>(this.csmTestFactory);
207213
}
208214

215+
private InsightsClient GetInsightsClient()
216+
{
217+
return TestBase.GetServiceClient<InsightsClient>(this.csmTestFactory);
218+
}
219+
209220
/// <summary>
210221
/// The test http client helper factory.
211222
/// </summary>

src/ResourceManager/Resources/Commands.Resources.Test/ScenarioTests/RoleAssignmentTests.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,26 @@
1616
using Microsoft.Azure.Graph.RBAC;
1717
using Microsoft.Azure.Graph.RBAC.Models;
1818
using Microsoft.Azure.Management.Authorization;
19-
using Microsoft.Azure.Management.Authorization.Models;
2019
using Microsoft.Azure.Management.Resources;
2120
using Microsoft.Azure.Management.Resources.Models;
22-
using Microsoft.Azure.Test.HttpRecorder;
2321
using Microsoft.WindowsAzure.Commands.ScenarioTest;
2422
using Microsoft.Azure.Test;
2523
using System;
2624
using System.Linq;
27-
using System.Threading;
2825
using Microsoft.WindowsAzure.Commands.Utilities.Common;
2926
using Xunit;
3027

3128
namespace Microsoft.Azure.Commands.Resources.Test.ScenarioTests
3229
{
3330
public class RoleAssignmentTests
3431
{
32+
[Fact]
33+
[Trait(Category.AcceptanceType, Category.CheckIn)]
34+
public void RaAuthorizationChangeLog()
35+
{
36+
ResourcesController.NewInstance.RunPsTest("Test-RaAuthorizationChangeLog");
37+
}
38+
3539
[Fact]
3640
[Trait(Category.AcceptanceType, Category.CheckIn)]
3741
public void RaNegativeScenarios()

src/ResourceManager/Resources/Commands.Resources.Test/ScenarioTests/RoleAssignmentTests.ps1

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,20 @@ function Test-RaUserPermissions
232232
Assert-AreEqual $action $permissions.Permissions[0].Actions[0] "Permission action mismatch."
233233
}
234234

235+
<#
236+
.SYNOPSIS
237+
Tests verifies Get-AzureAuthorizationChangeLog
238+
#>
239+
function Test-RaAuthorizationChangeLog
240+
{
241+
$log1 = Get-AzureAuthorizationChangeLog -startTime 2015-08-27 -EndTime 2015-08-27T22:30:00Z
242+
243+
# Assert
244+
Assert-True { $log1.Count -ge 1 } "At least one record should be returned for the user"
245+
}
246+
247+
248+
235249
<#
236250
.SYNOPSIS
237251
Creates role assignment

src/ResourceManager/Resources/Commands.Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleAssignmentTests/RaAuthorizationChangeLog.json

Lines changed: 194 additions & 0 deletions
Large diffs are not rendered by default.

src/ResourceManager/Resources/Commands.Resources.Test/packages.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<package id="Microsoft.Azure.Common.Dependencies" version="1.0.0" targetFramework="net45" />
77
<package id="Microsoft.Azure.Gallery" version="2.6.2-preview" targetFramework="net45" />
88
<package id="Microsoft.Azure.Graph.RBAC" version="1.7.0-preview" targetFramework="net45" />
9+
<package id="Microsoft.Azure.Insights" version="0.7.7-preview" targetFramework="net45" />
10+
<package id="Microsoft.Azure.KeyVault.Core" version="1.0.0" targetFramework="net45" />
911
<package id="Microsoft.Azure.Management.Authorization" version="0.19.2-preview" targetFramework="net45" />
1012
<package id="Microsoft.Azure.Management.Resources" version="2.18.7-preview" targetFramework="net45" />
1113
<package id="Microsoft.Azure.Test.Framework" version="1.0.5687.28567-prerelease" targetFramework="net45" />

src/ResourceManager/Resources/Commands.Resources/ResourceManagerStartup.ps1

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,190 @@
2323
"Get-AzureStorageContainerAcl" = "Get-AzureStorageContainer";
2424
"Start-CopyAzureStorageBlob" = "Start-AzureStorageBlobCopy";
2525
"Stop-CopyAzureStorageBlob" = "Stop-AzureStorageBlobCopy";
26-
}.GetEnumerator() | Select @{Name='Name'; Expression={$_.Key}}, @{Name='Value'; Expression={$_.Value}} | New-Alias -Description "AzureAlias"
26+
}.GetEnumerator() | Select @{Name='Name'; Expression={$_.Key}}, @{Name='Value'; Expression={$_.Value}} | New-Alias -Description "AzureAlias"
27+
28+
29+
# Authorization script commandlet that builds on top of existing Insights comandlets.
30+
# This commandlet gets all events for the "Microsoft.Authorization" resource provider by calling the "Get-AzureResourceProviderLog" commandlet
31+
32+
function Get-AzureAuthorizationChangeLog {
33+
[CmdletBinding()]
34+
param(
35+
[parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true, HelpMessage = "The start time. Optional
36+
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")]
37+
[DateTime] $StartTime,
38+
39+
[parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true, HelpMessage = "The end time. Optional.
40+
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")]
41+
[DateTime] $EndTime
42+
)
43+
PROCESS {
44+
# Get all events for the "Microsoft.Authorization" provider by calling the Insights commandlet
45+
$events = Get-AzureResourceProviderLog -ResourceProvider "Microsoft.Authorization" -DetailedOutput -StartTime $StartTime -EndTime $EndTime
46+
47+
$startEvents = @{}
48+
$endEvents = @{}
49+
$offlineEvents = @()
50+
51+
# StartEvents and EndEvents will contain matching pairs of logs for when role assignments (and definitions) were created or deleted.
52+
# 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
53+
$startEvents = $events | ? { $_.httpRequest -and $_.Status -ieq "Started" }
54+
$events | ? { $_.httpRequest -and $_.Status -ne "Started" } | % { $endEvents[$_.OperationId] = $_ }
55+
# This filters non-RBAC events like classic administrator write or delete
56+
$events | ? { $_.httpRequest -eq $null } | % { $offlineEvents += $_ }
57+
58+
$output = @()
59+
60+
# Get all role definitions once from the service and cache to use for all 'startevents'
61+
$azureRoleDefinitionCache = @{}
62+
Get-AzureRoleDefinition | % { $azureRoleDefinitionCache[$_.Id] = $_ }
63+
64+
$principalDetailsCache = @{}
65+
66+
# Process StartEvents
67+
# Find matching EndEvents that succeeded and relating to role assignments only
68+
$startEvents | ? { $endEvents.ContainsKey($_.OperationId) `
69+
-and $endEvents[$_.OperationId] -ne $null `
70+
-and $endevents[$_.OperationId].OperationName.StartsWith("Microsoft.Authorization/roleAssignments", [System.StringComparison]::OrdinalIgnoreCase) `
71+
-and $endEvents[$_.OperationId].Status -ieq "Succeeded"} | % {
72+
73+
$endEvent = $endEvents[$_.OperationId];
74+
75+
# Create the output structure
76+
$out = "" | select Timestamp, Caller, Action, PrincipalId, PrincipalName, PrincipalType, Scope, ScopeName, ScopeType, RoleDefinitionId, RoleName
77+
$out.Timestamp = $endEvent.EventTimestamp
78+
$out.Caller = $_.Caller
79+
if ($_.HttpRequest.Method -ieq "PUT") {
80+
$out.Action = "Granted"
81+
if ($_.Properties.Content.ContainsKey("requestbody")) {
82+
$messageBody = ConvertFrom-Json $_.Properties.Content["requestbody"]
83+
}
84+
85+
$out.Scope = $_.Authorization.Scope
86+
}
87+
elseif ($_.HttpRequest.Method -ieq "DELETE") {
88+
$out.Action = "Revoked"
89+
if ($endEvent.Properties.Content.ContainsKey("responseBody")) {
90+
$messageBody = ConvertFrom-Json $endEvent.Properties.Content["responseBody"]
91+
}
92+
}
93+
94+
if ($messageBody) {
95+
96+
$out.PrincipalId = $messageBody.properties.principalId
97+
if ($out.PrincipalId -ne $null) {
98+
$principalDetails = Get-PrincipalDetails $out.PrincipalId ([REF]$principalDetailsCache)
99+
$out.PrincipalName = $principalDetails.Name
100+
$out.PrincipalType = $principalDetails.Type
101+
}
102+
103+
if ([string]::IsNullOrEmpty($out.Scope)) { $out.Scope = $messageBody.properties.Scope }
104+
if ($out.Scope -ne $null) {
105+
$resourceDetails = Get-ResourceDetails $out.Scope
106+
$out.ScopeName = $resourceDetails.Name
107+
$out.ScopeType = $resourceDetails.Type
108+
}
109+
110+
$out.RoleDefinitionId = $messageBody.properties.roleDefinitionId
111+
if ($out.RoleDefinitionId -ne $null) {
112+
if ($azureRoleDefinitionCache[$out.RoleDefinitionId]) {
113+
$out.RoleName = $azureRoleDefinitionCache[$out.RoleDefinitionId].Name
114+
} else {
115+
$out.RoleName = ""
116+
}
117+
}
118+
}
119+
$output += $out
120+
} # start event processing complete
121+
122+
# Filter classic admins events
123+
$offlineEvents | % {
124+
if($_.Status -ne $null -and $_.Status -ieq "Succeeded" -and $_.OperationName -ne $null -and $_.operationName.StartsWith("Microsoft.Authorization/ClassicAdministrators", [System.StringComparison]::OrdinalIgnoreCase)) {
125+
126+
$out = "" | select Timestamp, Caller, Action, PrincipalId, PrincipalName, PrincipalType, Scope, ScopeName, ScopeType, RoleDefinitionId, RoleName
127+
$out.Timestamp = $_.EventTimestamp
128+
$out.Caller = "Subscription Admin"
129+
130+
if($_.operationName -ieq "Microsoft.Authorization/ClassicAdministrators/write"){
131+
$out.Action = "Granted"
132+
}
133+
elseif($_.operationName -ieq "Microsoft.Authorization/ClassicAdministrators/delete"){
134+
$out.Action = "Revoked"
135+
}
136+
137+
$out.RoleDefinitionId = $null
138+
$out.PrincipalId = $null
139+
$out.PrincipalType = "User"
140+
$out.Scope = "/subscriptions/" + $_.SubscriptionId
141+
$out.ScopeType = "Subscription"
142+
$out.ScopeName = $_.SubscriptionId
143+
144+
if($_.Properties -ne $null){
145+
$out.PrincipalName = $_.Properties.Content["adminEmail"]
146+
$out.RoleName = "Classic " + $_.Properties.Content["adminType"]
147+
}
148+
149+
$output += $out
150+
}
151+
} # end offline events
152+
153+
$output | Sort Timestamp
154+
}
155+
} # End commandlet
156+
157+
# Helper functions
158+
# Resolve a principal. If the principal's object id was encountered in the principals resolved so far, return principalDetails from the cache.
159+
# Else make a Grpah call and add that principal to cache of known principals
160+
function Get-PrincipalDetails($principalId, [REF]$principalDetailsCache)
161+
{
162+
if($principalDetailsCache.Value.ContainsKey($principalId)) {
163+
return $principalDetailsCache.Value[$principalId]
164+
}
165+
166+
$principalDetails = "" | select Name, Type
167+
$user = Get-AzureADUser -ObjectId $principalId
168+
if ($user) {
169+
$principalDetails.Name = $user.DisplayName
170+
$principalDetails.Type = "User"
171+
} else {
172+
$group = Get-AzureADGroup -ObjectId $principalId
173+
if ($group) {
174+
$principalDetails.Name = $group.DisplayName
175+
$principalDetails.Type = "Group"
176+
} else {
177+
$servicePrincipal = Get-AzureADServicePrincipal -objectId $principalId
178+
if ($servicePrincipal) {
179+
$principalDetails.Name = $servicePrincipal.DisplayName
180+
$principalDetails.Type = "Service Principal"
181+
}
182+
}
183+
}
184+
185+
$principalDetailsCache.Value.Add($principalId, $principalDetails);
186+
187+
$principalDetails
188+
}
189+
190+
# Get resource details from scope
191+
function Get-ResourceDetails($scope)
192+
{
193+
$resourceDetails = "" | select Name, Type
194+
$scopeParts = $scope.Split('/', [System.StringSplitOptions]::RemoveEmptyEntries)
195+
$len = $scopeParts.Length
196+
197+
if ($len -gt 0 -and $len -le 2 -and $scope.ToLower().Contains("subscriptions")) {
198+
$resourceDetails.Type = "Subscription"
199+
$resourceDetails.Name = $scopeParts[1]
200+
}
201+
elseif ($len -gt 0 -and $len -le 4 -and $scope.ToLower().Contains("resourcegroups")) {
202+
$resourceDetails.Type = "Resource Group"
203+
$resourceDetails.Name = $scopeParts[3]
204+
}
205+
elseif ($len -ge 6 -and $scope.ToLower().Contains("providers")) {
206+
$resourceDetails.Type = "Resource"
207+
$resourceDetails.Name = $scopeParts[$len -1]
208+
}
209+
210+
$resourceDetails
211+
}
212+

0 commit comments

Comments
 (0)