Skip to content

Instantly share code, notes, and snippets.

@mjsabby
Created October 23, 2023 19:36
Show Gist options
  • Save mjsabby/867c165431367438f4dbbad6f13ebf45 to your computer and use it in GitHub Desktop.
Save mjsabby/867c165431367438f4dbbad6f13ebf45 to your computer and use it in GitHub Desktop.
Dump Azure DevOps Audit Logs
/*
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.3" />
</ItemGroup>
</Project>
*/
namespace AuditLogsDump
{
using System;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
internal static class Program
{
private const string AzureDevOpsScope = "499b84ac-1321-427f-aa17-267ca6975798/.default";
public static async Task Main(string[] args)
{
if (args.Length < 4)
{
Console.WriteLine($"Usage: AuditLogsDump Organization Filter StartTime EndTime [ManagedIdentity]");
Console.WriteLine($"Usage: AuditLogsDump msasg Git.RepositoryCreated 09/23/2023 10/23/2023");
Console.WriteLine($"Usage: AuditLogsDump msasg Git.RepositoryCreated 09/23/2023 10/23/2023 {Guid.NewGuid()}");
return;
}
var organization = args[0];
var filter = new Regex(args[1], RegexOptions.Compiled | RegexOptions.IgnoreCase);
var startTime = DateTime.Parse(args[2], CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal).ToUniversalTime();
var endTime = DateTime.Parse(args[3], CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal).ToUniversalTime();
DefaultAzureCredential credential;
if (args.Length == 5)
{
credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = args[5] });
}
else
{
credential = new DefaultAzureCredential(includeInteractiveCredentials: true);
}
var accessToken = (await credential.GetTokenAsync(new TokenRequestContext(new string[] { AzureDevOpsScope }))).Token;
using (HttpClient client = new())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
AuditLogResponse auditLogs;
var continuationToken = string.Empty;
while (true)
{
auditLogs = await GetAuditLogsAsync(client, organization, continuationToken, startTime, endTime, 5000);
foreach (var decoratedAuditLogEntry in auditLogs.DecoratedAuditLogEntries)
{
if (filter.IsMatch(decoratedAuditLogEntry.ActionId))
{
Console.WriteLine(decoratedAuditLogEntry.Details);
}
}
if (auditLogs.HasMore)
{
continuationToken = auditLogs.ContinuationToken;
Thread.Sleep(1000);
}
else
{
break;
}
}
}
}
private static async Task<AuditLogResponse> GetAuditLogsAsync(HttpClient client, string organization, string continuationToken, DateTime startTime, DateTime endTime, int batchSize) => await GetAsync<AuditLogResponse>(client, $"https://auditservice.dev.azure.com/{organization}/_apis/audit/auditlog?continuationToken={continuationToken}&startTime={startTime:o}&endTime={endTime:o}&batchSize={batchSize}&skipAggregation=false&api-version=7.1-preview.1");
private static async Task<T> GetAsync<T>(HttpClient client, string url)
{
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(responseBody);
}
internal sealed class AuditLogResponse
{
[JsonPropertyName("decoratedAuditLogEntries")]
public DecoratedAuditLogEntry[] DecoratedAuditLogEntries { get; set; }
public sealed class DecoratedAuditLogEntry
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("correlationId")]
public string CorrelationId { get; set; }
[JsonPropertyName("activityId")]
public string ActivityId { get; set; }
[JsonPropertyName("actorCUID")]
public string ActorCUID { get; set; }
[JsonPropertyName("actorUserId")]
public string ActorUserId { get; set; }
[JsonPropertyName("actorClientId")]
public string ActorClientId { get; set; }
[JsonPropertyName("actorUPN")]
public string ActorUPN { get; set; }
[JsonPropertyName("authenticationMechanism")]
public string AuthenticationMechanism { get; set; }
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
[JsonPropertyName("scopeType")]
public string ScopeType { get; set; }
[JsonPropertyName("scopeDisplayName")]
public string ScopeDisplayName { get; set; }
[JsonPropertyName("scopeId")]
public string ScopeId { get; set; }
[JsonPropertyName("projectId")]
public string ProjectId { get; set; }
[JsonPropertyName("projectName")]
public string ProjectName { get; set; }
[JsonPropertyName("ipAddress")]
public string IpAddress { get; set; }
[JsonPropertyName("userAgent")]
public string UserAgent { get; set; }
[JsonPropertyName("actionId")]
public string ActionId { get; set; }
[JsonPropertyName("data")]
public DataClass Data { get; set; }
[JsonPropertyName("details")]
public string Details { get; set; }
[JsonPropertyName("area")]
public string Area { get; set; }
[JsonPropertyName("category")]
public string Category { get; set; }
[JsonPropertyName("categoryDisplayName")]
public string CategoryDisplayName { get; set; }
[JsonPropertyName("actorDisplayName")]
public string ActorDisplayName { get; set; }
[JsonPropertyName("actorImageUrl")]
public string ActorImageUrl { get; set; }
public sealed class DataClass
{
[JsonPropertyName("CallerProcedure")]
public string CallerProcedure { get; set; }
#if !NETFRAMEWORK
[JsonPropertyName("PipelineId")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public int PipelineId { get; set; }
#endif
[JsonPropertyName("PipelineName")]
public string PipelineName { get; set; }
#if !NETFRAMEWORK
[JsonPropertyName("ReleaseId")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public int ReleaseId { get; set; }
#endif
[JsonPropertyName("RequesterId")]
public string RequesterId { get; set; }
[JsonPropertyName("Reason")]
public string Reason { get; set; }
[JsonPropertyName("ReleaseName")]
public string ReleaseName { get; set; }
}
}
[JsonPropertyName("continuationToken")]
public string ContinuationToken { get; set; }
[JsonPropertyName("hasMore")]
public bool HasMore { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment