Skip to content
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

Add license usage reporting #16

Merged
merged 3 commits into from
Dec 20, 2024
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
52 changes: 52 additions & 0 deletions BeyondTrustConnector/LicenseUsageUpdater.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.IO.Compression;
using BeyondTrustConnector.Model.Dto;
using BeyondTrustConnector.Service;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace BeyondTrustConnector
{
public class LicenseUsageUpdater(BeyondTrustService beyondTrustService, IngestionService ingestionService, ILogger<LicenseUsageUpdater> logger)
{
[Function(nameof(LicenseUsageUpdater))]
public async Task Run([TimerTrigger("0 0 8 * * *", RunOnStartup = false)] TimerInfo myTimer)
{
var reportArchive = await beyondTrustService.GetEndpointLicenseUsageReportAsync();
ZipArchive archive = new(new MemoryStream(reportArchive));
var jumpItemReport = archive.GetEntry("jump_items.csv");
if (jumpItemReport is null)
{
logger.LogError("Jump Items report not found in archive");
throw new Exception("Jump Items report not found in archive");
}

using var jumpItemStream = jumpItemReport.Open();
using var jumpItemReader = new StreamReader(jumpItemStream);
var csvLine = await jumpItemReader.ReadLineAsync();

var licenseEntries = new List<BeyondTrustLicenseEntryDto>();
while ((csvLine = await jumpItemReader.ReadLineAsync()) != null){
var fields = csvLine.Split(',');
var licenseEntry = new BeyondTrustLicenseEntryDto
{
JumpItemName = fields[0].Trim('\"'),
HostnameOrIp = fields[1].Trim('\"'),
Jumpoint = fields[2].Trim('\"'),
RemoteApplicationName = fields[3].Trim('\"'),
License = fields[4].Trim('\"') == "yes",
JumpMethod = fields[5].Trim('\"'),
JumpGroup = fields[6].Trim('\"'),
};
licenseEntries.Add(licenseEntry);
}
if(licenseEntries.Count == 0)
{
logger.LogWarning("No license entries found in report");
return;
}

await ingestionService.IngestLicenseUsage(licenseEntries);
}
}
}
12 changes: 12 additions & 0 deletions BeyondTrustConnector/Model/Dto/BeyondTrustLicenseEntryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace BeyondTrustConnector.Model.Dto;

internal class BeyondTrustLicenseEntryDto
{
public required string JumpItemName { get; set; }
public required string HostnameOrIp { get; set; }
public required string JumpMethod { get; set; }
public required string JumpGroup { get; set; }
public bool License { get; set; }
public required string Jumpoint { get; set; }
public string? RemoteApplicationName { get; set; }
}
22 changes: 19 additions & 3 deletions BeyondTrustConnector/Service/BeyondTrustService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BeyondTrustConnector.Model;
using BeyondTrustConnector.Model.Dto;
using Microsoft.Extensions.Logging;
using System.Net.Http.Json;
using System.Xml;
Expand All @@ -9,7 +10,7 @@ namespace BeyondTrustConnector.Service
{
public class BeyondTrustService(IHttpClientFactory httpClientFactory, ILogger<BeyondTrustService> logger)
{
public async Task<session_list> GetAccessSessionReport(DateTime start, int reportPeriod = 0)
internal async Task<session_list> GetAccessSessionReport(DateTime start, int reportPeriod = 0)
{
var client = httpClientFactory.CreateClient(nameof(BeyondTrustConnector));
var unixTime = ((DateTimeOffset)start).ToUnixTimeSeconds();
Expand All @@ -31,7 +32,7 @@ public async Task<session_list> GetAccessSessionReport(DateTime start, int repor
return sessionList ?? throw new Exception("Failed to deserialize report");
}

public async Task<XDocument> GetVaultActivityReport(DateTime start, int reportPeriod = 0)
internal async Task<XDocument> GetVaultActivityReport(DateTime start, int reportPeriod = 0)
{
var client = httpClientFactory.CreateClient(nameof(BeyondTrustConnector));
var unixTime = ((DateTimeOffset)start).ToUnixTimeSeconds() + 1;
Expand All @@ -48,7 +49,7 @@ public async Task<XDocument> GetVaultActivityReport(DateTime start, int reportPe
return XDocument.Parse(reportContent);
}

public async Task<byte[]> DownloadReportAsync(string report)
internal async Task<byte[]> DownloadReportAsync(string report)
{
var client = httpClientFactory.CreateClient(nameof(BeyondTrustConnector));
var response = await client.GetAsync($"/api/reporting?generate_report={report}");
Expand All @@ -62,6 +63,21 @@ public async Task<byte[]> DownloadReportAsync(string report)
return reportData;
}

internal async Task<byte[]> GetEndpointLicenseUsageReportAsync()
{
var client = httpClientFactory.CreateClient(nameof(BeyondTrustConnector));
var response = await client.GetAsync("/api/reporting?generate_report=EndpointLicenseUsage");
if (!response.IsSuccessStatusCode)
{
var message = await response.Content.ReadAsStringAsync();
logger.LogError("Failed to get license usage: {ErrorMessage}", message);
throw new Exception("Failed to get license usage");
}
var reportArchive = await response.Content.ReadAsByteArrayAsync();

return reportArchive;
}

private async Task<TEntity> GetItem<TEntity>(string endpoint, string query)
{
var client = httpClientFactory.CreateClient(nameof(BeyondTrustConnector));
Expand Down
5 changes: 5 additions & 0 deletions BeyondTrustConnector/Service/IngestionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ internal async Task IngestAccessSessions(List<BeyondTrustAccessSessionDto> sessi
await Ingest("BeyondTrustAccessSession_CL", sessions);
}

internal async Task IngestLicenseUsage(List<BeyondTrustLicenseEntryDto> licenseEntries)
{
await Ingest("BeyondTrustLicenseUsage_CL", licenseEntries);
}

internal async Task IngestVaultActivity(List<BeyondTrustVaultActivityDto> vaultActivities)
{
await Ingest("BeyondTrustVaultActivity_CL", vaultActivities);
Expand Down
85 changes: 84 additions & 1 deletion modules/datacollection.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,48 @@ resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2023
}
}


resource Custom_Table_BeyondTrustLicenseUsage_CL 'Microsoft.OperationalInsights/workspaces/tables@2022-10-01' = {
parent: law
name: 'BeyondTrustLicenseUsage_CL'
properties: {
totalRetentionInDays: 30
plan: 'Analytics'
schema: {
name: 'BeyondTrustLicenseUsage_CL'
columns: [
{
name: 'TimeGenerated'
type: 'datetime'
}
{
name: 'Name'
type: 'string'
}
{
name: 'HostnameOrIp'
type: 'string'
}
{
name: 'Jumpoint'
type: 'string'
}
{
name: 'License'
type: 'boolean'
}
{
name: 'JumpMethod'
type: 'string'
}
{
name: 'JumpGroup'
type: 'string'
}
]
}
retentionInDays: 30
}
}

resource Custom_Table_BeyondTrustVaultActivity_CL 'Microsoft.OperationalInsights/workspaces/tables@2022-10-01' = {
parent: law
Expand Down Expand Up @@ -184,6 +225,38 @@ resource dataCollectionRule 'Microsoft.Insights/dataCollectionRules@2023-03-11'
properties: {
dataCollectionEndpointId: dataCollectionEndpoint.id
streamDeclarations: {
'Custom-BeyondTrustLicenseUsage_CL': {
columns: [
{
name: 'TimeGenerated'
type: 'datetime'
}
{
name: 'Name'
type: 'string'
}
{
name: 'HostnameOrIp'
type: 'string'
}
{
name: 'Jumpoint'
type: 'string'
}
{
name: 'License'
type: 'boolean'
}
{
name: 'JumpMethod'
type: 'string'
}
{
name: 'JumpGroup'
type: 'string'
}
]
}
'Custom-BeyondTrustEvents_CL': {
columns: [
{
Expand Down Expand Up @@ -319,6 +392,16 @@ resource dataCollectionRule 'Microsoft.Insights/dataCollectionRules@2023-03-11'
]
}
dataFlows: [
{
streams: [
'Custom-BeyondTrustLicenseUsage_CL'
]
destinations: [
'beyondTrustWorkspace'
]
transformKql: 'source\n| extend TimeGenerated=now()\n'
outputStream: 'Custom-BeyondTrustLicenseUsage_CL'
}
{
streams: [
'Custom-BeyondTrustEvents_CL'
Expand Down
Loading