Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit fc3e7cc

Browse files
authored
fix: add default event uri to client expired background job (#139)
* fix: add default event uri to client expired background job * Update ClientSecretExpirationJob.cs * Update ClientSecretExpirationJob.cs * pr-fix: use sec event tracking * pr-fix: follow-up merge w/ 'master'
1 parent ce061a6 commit fc3e7cc

File tree

4 files changed

+95
-37
lines changed

4 files changed

+95
-37
lines changed

src/Arcus.BackgroundJobs.AzureActiveDirectory/ClientSecretExpirationJob.cs

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Net.Http.Headers;
43
using System.Threading;
54
using System.Threading.Tasks;
65
using Arcus.EventGrid.Publishing.Interfaces;
7-
using Arcus.Security.Core;
86
using Azure.Identity;
97
using CloudNative.CloudEvents;
108
using CronScheduler.Extensions.Scheduler;
@@ -72,30 +70,49 @@ public async Task ExecuteAsync(CancellationToken stoppingToken)
7270
{
7371
try
7472
{
75-
_logger.LogTrace("Executing {Name}", nameof(ClientSecretExpirationJob));
76-
var graphServiceClient = new GraphServiceClient(new DefaultAzureCredential());
77-
_logger.LogTrace("Token retrieved, getting a list of applications with expired or about to expire secrets");
78-
79-
var clientSecretExpirationInfoProvider = new ClientSecretExpirationInfoProvider(graphServiceClient, _logger);
80-
IEnumerable<AzureApplication> applications =
81-
await clientSecretExpirationInfoProvider.GetApplicationsWithPotentialExpiredSecrets(_options.UserOptions.ExpirationThreshold);
73+
_logger.LogTrace("Executing {Name}", Name);
74+
IEnumerable<AzureApplication> applications = await GetAzureApplicationsWithPotentialExpiredSecretsAsync();
8275

8376
foreach (AzureApplication application in applications)
8477
{
8578
ClientSecretExpirationEventType eventType = DetermineExpirationEventType(application);
79+
80+
LogPotentialSecretEvent(application, eventType);
8681

87-
CloudEvent @event = _options.UserOptions.CreateEvent(application, eventType, _options.UserOptions.EventUri);
88-
await _eventGridPublisher.PublishAsync(@event);
82+
await PublishPotentialSecretEventAsync(application, eventType);
8983
}
90-
_logger.LogTrace("Executing {Name} finished", nameof(ClientSecretExpirationJob));
84+
_logger.LogTrace("Executing {Name} finished", Name);
9185
}
9286
catch (Exception exception)
9387
{
9488
_logger.LogCritical(exception, "Could not correctly publish Azure EventGrid events for potential expired client secrets in the Azure Active Directory due to an exception");
9589
}
9690
}
9791

98-
private ClientSecretExpirationEventType DetermineExpirationEventType(AzureApplication application)
92+
private async Task<IEnumerable<AzureApplication>> GetAzureApplicationsWithPotentialExpiredSecretsAsync()
93+
{
94+
var graphServiceClient = new GraphServiceClient(new DefaultAzureCredential());
95+
_logger.LogTrace("Token retrieved, getting a list of applications with expired or about to expire secrets");
96+
97+
var clientSecretExpirationInfoProvider = new ClientSecretExpirationInfoProvider(graphServiceClient, _logger);
98+
99+
IEnumerable<AzureApplication> applications =
100+
await clientSecretExpirationInfoProvider.GetApplicationsWithPotentialExpiredSecrets(_options.UserOptions.ExpirationThreshold);
101+
102+
return applications;
103+
}
104+
105+
private static ClientSecretExpirationEventType DetermineExpirationEventType(AzureApplication application)
106+
{
107+
if (application.RemainingValidDays < 0)
108+
{
109+
return ClientSecretExpirationEventType.ClientSecretExpired;
110+
}
111+
112+
return ClientSecretExpirationEventType.ClientSecretAboutToExpire;
113+
}
114+
115+
private void LogPotentialSecretEvent(AzureApplication application, ClientSecretExpirationEventType eventType)
99116
{
100117
var telemetryContext = new Dictionary<string, object>
101118
{
@@ -104,18 +121,27 @@ private ClientSecretExpirationEventType DetermineExpirationEventType(AzureApplic
104121
{ "RemainingValidDays", application.RemainingValidDays }
105122
};
106123

107-
var eventType = ClientSecretExpirationEventType.ClientSecretAboutToExpire;
108-
if (application.RemainingValidDays < 0)
124+
switch (eventType)
109125
{
110-
eventType = ClientSecretExpirationEventType.ClientSecretExpired;
111-
_logger.LogEvent($"The secret {application.KeyId} for Azure Active Directory application {application.Name} has expired.", telemetryContext);
112-
}
113-
else
114-
{
115-
_logger.LogEvent($"The secret {application.KeyId} for Azure Active Directory application {application.Name} will expire within {application.RemainingValidDays} days.", telemetryContext);
126+
case ClientSecretExpirationEventType.ClientSecretExpired:
127+
telemetryContext["Description"] = $"The secret {application.KeyId} for Azure Active Directory application {application.Name} has expired.";
128+
_logger.LogSecurityEvent("Expired Azure Active Directory application secret", telemetryContext);
129+
break;
130+
131+
case ClientSecretExpirationEventType.ClientSecretAboutToExpire:
132+
telemetryContext["Description"] = $"The secret {application.KeyId} for Azure Active Directory application {application.Name} will expire within {application.RemainingValidDays} days.";
133+
_logger.LogSecurityEvent("Soon expired Azure Active Directory application secret", telemetryContext);
134+
break;
135+
136+
default:
137+
throw new ArgumentOutOfRangeException(nameof(eventType), eventType, "Could not determine event type for potential expired Azure Application secret");
116138
}
139+
}
117140

118-
return eventType;
141+
private async Task PublishPotentialSecretEventAsync(AzureApplication application, ClientSecretExpirationEventType eventType)
142+
{
143+
CloudEvent @event = _options.UserOptions.CreateEvent(application, eventType);
144+
await _eventGridPublisher.PublishAsync(@event);
119145
}
120146
}
121147
}

src/Arcus.BackgroundJobs.AzureActiveDirectory/ClientSecretExpirationJobOptions.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
namespace Arcus.BackgroundJobs.AzureActiveDirectory
88
{
99
/// <summary>
10-
/// Represents the additional options that the user can configure during the <see cref="IServiceCollectionExtensions.AddClientSecretExpirationJob(IServiceCollection,Action{ClientSecretExpirationJobOptions})"/> call.
10+
/// Represents the additional options that the user can configure during the <see cref="IServiceCollectionExtensions.AddClientSecretExpirationJob(IServiceCollection, Action{ClientSecretExpirationJobOptions})"/> call.
1111
/// </summary>
1212
public class ClientSecretExpirationJobOptions
1313
{
1414
private int _runAtHour = 0;
1515
private bool _runImmediately = false;
16-
private Uri _eventUri = null;
16+
private Uri _eventUri = new Uri("https://azure.net/");
1717
private int _expirationThreshold = 14;
1818

1919
/// <summary>
@@ -79,25 +79,22 @@ public int ExpirationThreshold
7979
/// </summary>
8080
/// <param name="application">The <see cref="AzureApplication"/> containing the information regarding the application and its expiring or about to expire secret.</param>
8181
/// <param name="type">The type used in the creation of the <see cref="CloudEvent"/>.</param>
82-
/// <param name="eventUri">The uri used in the creation of the <see cref="CloudEvent"/>.</param>
8382
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="application"/> is null.</exception>
8483
/// <exception cref="ArgumentException">Thrown when the <paramref name="application"/> name is blank.</exception>
85-
/// <exception cref="ArgumentException">Thrown when the <paramref name="eventUri"/> is blank.</exception>
86-
internal CloudEvent CreateEvent(AzureApplication application, ClientSecretExpirationEventType type, Uri eventUri)
84+
internal CloudEvent CreateEvent(AzureApplication application, ClientSecretExpirationEventType type)
8785
{
8886
Guard.NotNull(application, nameof(application));
8987
Guard.NotNullOrWhitespace(application.Name, nameof(application.Name));
90-
Guard.NotNullOrWhitespace(eventUri.OriginalString, nameof(eventUri));
9188

9289
string eventSubject = $"/appregistrations/clientsecrets/{application.KeyId}";
9390
string eventId = Guid.NewGuid().ToString();
9491

9592
CloudEvent @event = new CloudEvent(
96-
CloudEventsSpecVersion.V1_0,
97-
type.ToString(),
98-
eventUri,
99-
eventSubject,
100-
eventId)
93+
CloudEventsSpecVersion.V1_0,
94+
type.ToString(),
95+
_eventUri,
96+
eventSubject,
97+
eventId)
10198
{
10299
Data = application,
103100
DataContentType = new ContentType("application/json")

src/Arcus.BackgroundJobs.AzureActiveDirectory/Extensions/IServiceCollectionExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public static class IServiceCollectionExtensions
2020
/// which will query Azure Active Directory for applications that have expired or soon to be expired secrets and send a CloudEvent to an Event Grid Topic.
2121
/// </summary>
2222
/// <remarks>
23-
/// Make sure that the application has an Arcus EventGrid publisher configured.
24-
/// For on the Arcus secret store: <a href="https://eventgrid.arcus-azure.net/Features/publishing-events" />.
23+
/// Make sure that you register an <see cref="IEventGridPublisher"/> instance that the background job can use to publish events for potential expired Azure Application secrets.
24+
/// For more information on Azure EventGrid, see: <a href="https://eventgrid.arcus-azure.net/Features/publishing-events" />.
2525
/// </remarks>
2626
/// <param name="services">The services to add the background job to.</param>
2727
/// <exception cref="ArgumentNullException">Thrown when <paramref name="services"/> is <c>null</c>.</exception>
@@ -36,8 +36,8 @@ public static IServiceCollection AddClientSecretExpirationJob(this IServiceColle
3636
/// which will query Azure Active Directory for applications that have expired or soon to be expired secrets and send a CloudEvent to an Event Grid Topic.
3737
/// </summary>
3838
/// <remarks>
39-
/// Make sure that the application has an Arcus EventGrid publisher configured.
40-
/// For on the Arcus secret store: <a href="https://eventgrid.arcus-azure.net/Features/publishing-events" />.
39+
/// Make sure that you register an <see cref="IEventGridPublisher"/> instance that the background job can use to publish events for potential expired Azure Application secrets.
40+
/// For more information on Azure EventGrid, see: <a href="https://eventgrid.arcus-azure.net/Features/publishing-events" />.
4141
/// </remarks>
4242
/// <param name="services">The services to add the background job to.</param>
4343
/// <param name="configureOptions">The optional additional customized user configuration of options for this background job.</param>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using Arcus.EventGrid.Publishing;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Hosting;
6+
using Xunit;
7+
8+
namespace Arcus.BackgroundJobs.Tests.Unit.AzureActiveDirectory
9+
{
10+
// ReSharper disable once InconsistentNaming
11+
[Trait(name: "Category", value: "Unit")]
12+
public class IServiceCollectionExtensionsTests
13+
{
14+
[Fact]
15+
public void AddClientSecretExpirationJob_WithoutOptions_Succeeds()
16+
{
17+
// Arrange
18+
var services = new ServiceCollection();
19+
services.AddLogging();
20+
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
21+
services.AddSingleton(
22+
EventGridPublisherBuilder
23+
.ForTopic("https://some-topic")
24+
.UsingAuthenticationKey("<key>")
25+
.Build());
26+
27+
// Act
28+
services.AddClientSecretExpirationJob();
29+
30+
// Assert
31+
IServiceProvider provider = services.BuildServiceProvider();
32+
Assert.NotNull(provider.GetService<IHostedService>());
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)