Fix provider collisions, UI improvements (#95)
* Fix provider collisions, UI improvements * UI fixes, bolstering Storage
This commit is contained in:
Родитель
654cb07cce
Коммит
8ed075aeac
|
@ -18,11 +18,11 @@
|
|||
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="0.9.1.1" />
|
||||
<PackageReference Include="Blazorise.Sidebar" Version="0.9.1.1" />
|
||||
<PackageReference Include="BlazorTable" Version="1.11.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="3.2.0-preview3.20168.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
@{
|
||||
void RenderCapabilityIcon(string icon, string templateString, bool isLit)
|
||||
{
|
||||
<Icon Name="@icon"
|
||||
Class="@(isLit ? IconLitColor : IconUnlitColor)"
|
||||
title="@GetTitle(templateString, isLit)"
|
||||
Margin="Margin.Is2"></Icon>
|
||||
}
|
||||
|
||||
RenderCapabilityIcon(
|
||||
icon: FontAwesomeIcons.Flask,
|
||||
templateString: "Provider {0} running sanity tests",
|
||||
isLit: HasCapabilities(ProviderCapabilities.CanRunSanityTests));
|
||||
|
||||
RenderCapabilityIcon(
|
||||
icon: FontAwesomeIcons.SyncAlt,
|
||||
templateString: "Provider {0} generating/consuming interim secret values",
|
||||
isLit: HasCapabilitiesOr(ProviderCapabilities.CanDistributeTemporarySecrets,
|
||||
ProviderCapabilities.CanGenerateTemporarySecrets));
|
||||
|
||||
RenderCapabilityIcon(
|
||||
icon: FontAwesomeIcons.LayerGroup,
|
||||
templateString: "Provider {0} resource candidate selection for configuration",
|
||||
isLit: HasCapabilities(ProviderCapabilities.CanEnumerateResourceCandidates));
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public LoadedProviderViewModel Provider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string IconLitColor { get; set; } = "text-success";
|
||||
|
||||
[Parameter]
|
||||
public string IconUnlitColor { get; set; } = "text-dark";
|
||||
|
||||
private const string SUPPORTS = "supports";
|
||||
private const string DOES_NOT_SUPPORT = "does not support";
|
||||
|
||||
private static string GetTitle(string templateString, bool isLit) =>
|
||||
string.Format(templateString, isLit ? SUPPORTS : DOES_NOT_SUPPORT);
|
||||
|
||||
private bool HasCapabilities(params ProviderCapabilities[] capabilities) =>
|
||||
!capabilities.Except(Provider.Capabilities).Any();
|
||||
|
||||
private bool HasCapabilitiesOr(params ProviderCapabilities[] capabilities) =>
|
||||
Provider.Capabilities.Any(c => capabilities.Contains(c));
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<Icon Name="FontAwesomeIcons.Flask" Class="@TestPhaseColor" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
|
||||
<div class="px-3 pt-2 pb-0 bg-light d-inline-block border border-secondary">
|
||||
<Icon Name="FontAwesomeIcons.Database" Class="@GenerateTemporaryPhaseColor" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<Icon Name="FontAwesomeIcons.CloudUploadAlt" Class="@DistributeTemporaryPhaseColor" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<Icon Name="FontAwesomeIcons.Save" Class="@CommitTemporaryPhaseColor" />
|
||||
|
||||
<small class="d-block mt-0 mb-1 text-gray-800">Temporary Secrets</small>
|
||||
</div>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<div class="px-3 pt-2 pb-0 bg-light d-inline-block border border-secondary">
|
||||
<Icon Name="FontAwesomeIcons.Database" Class="@GenerateLongTermPhaseColor" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<Icon Name="FontAwesomeIcons.CloudUploadAlt" Class="@DistributeLongTermPhaseColor" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<Icon Name="FontAwesomeIcons.Save" Class="@CommitLongTermPhaseColor" />
|
||||
|
||||
<small class="d-block mt-0 mb-1 text-gray-800">Long-Term Secrets</small>
|
||||
</div>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<Icon Name="FontAwesomeIcons.Broom" Class="@CleanupPhaseColor" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-dark" />
|
||||
<Icon Name="FontAwesomeIcons.FlagCheckered" Class="@CompletePhaseColor" />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public RekeyingAttemptLogger Attempt { get; set; }
|
||||
|
||||
// TODO: Everything here is a hack to parse the log for indicators
|
||||
// Need to move provider actions into their own objects to track this better
|
||||
private const string TEST_PHASE_INDICATOR = "### Performing provider tests...";
|
||||
|
||||
private const string GENERATE_TEMPORARY_PHASE_INDICATOR = "### Retrieving/generating temporary secrets...";
|
||||
private const string DISTRIBUTE_TEMPORARY_PHASE_INDICATOR = "### Distributing temporary secrets...";
|
||||
private const string COMMIT_TEMPORARY_PHASE_INDICATOR = "### Performing commits for temporary secrets...";
|
||||
|
||||
private const string GENERATE_LONG_TERM_PHASE_INDICATOR = "### Rekeying objects and services...";
|
||||
private const string DISTRIBUTE_LONG_TERM_PHASE_INDICATOR = "### Distributing regenerated secrets...";
|
||||
private const string COMMIT_LONG_TERM_PHASE_INDICATOR = "### Performing commits...";
|
||||
private const string CLEANUP_PHASE_INDICATOR = "### Running cleanup operations...";
|
||||
private const string END_PROCESS_INDICATOR = "########## END REKEYING WORKFLOW ##########";
|
||||
|
||||
private const string PHASE_NOT_STARTED_COLOR = "fa-3x text-secondary";
|
||||
private const string PHASE_IN_PROGRESS_COLOR = "fa-3x text-warning";
|
||||
private const string PHASE_COMPLETE_COLOR = "fa-3x text-success";
|
||||
private const string PHASE_ERROR_COLOR = "fa-3x text-danger";
|
||||
|
||||
public string TestPhaseColor => GetColorFromLogLines(TEST_PHASE_INDICATOR, GENERATE_TEMPORARY_PHASE_INDICATOR);
|
||||
|
||||
public string GenerateTemporaryPhaseColor => GetColorFromLogLines(GENERATE_TEMPORARY_PHASE_INDICATOR, DISTRIBUTE_TEMPORARY_PHASE_INDICATOR);
|
||||
public string DistributeTemporaryPhaseColor => GetColorFromLogLines(DISTRIBUTE_TEMPORARY_PHASE_INDICATOR, COMMIT_TEMPORARY_PHASE_INDICATOR);
|
||||
public string CommitTemporaryPhaseColor => GetColorFromLogLines(COMMIT_TEMPORARY_PHASE_INDICATOR, GENERATE_LONG_TERM_PHASE_INDICATOR);
|
||||
|
||||
public string GenerateLongTermPhaseColor => GetColorFromLogLines(GENERATE_LONG_TERM_PHASE_INDICATOR, DISTRIBUTE_LONG_TERM_PHASE_INDICATOR);
|
||||
public string DistributeLongTermPhaseColor => GetColorFromLogLines(DISTRIBUTE_LONG_TERM_PHASE_INDICATOR, COMMIT_LONG_TERM_PHASE_INDICATOR);
|
||||
public string CommitLongTermPhaseColor => GetColorFromLogLines(COMMIT_LONG_TERM_PHASE_INDICATOR, CLEANUP_PHASE_INDICATOR);
|
||||
|
||||
public string CleanupPhaseColor => GetColorFromLogLines(CLEANUP_PHASE_INDICATOR, END_PROCESS_INDICATOR);
|
||||
public string CompletePhaseColor => GetColorFromLogLines(END_PROCESS_INDICATOR);
|
||||
|
||||
protected string GetColorFromLogLines(string leftBookend, string rightBookend = null)
|
||||
{
|
||||
if (Attempt == null) return PHASE_NOT_STARTED_COLOR;
|
||||
if (Attempt.LogString == null) return PHASE_NOT_STARTED_COLOR;
|
||||
|
||||
if (Attempt.LogString.Contains(leftBookend))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rightBookend))
|
||||
return PHASE_COMPLETE_COLOR;
|
||||
if (Attempt.LogString.Contains(rightBookend))
|
||||
return PHASE_COMPLETE_COLOR;
|
||||
if (!string.IsNullOrEmpty(Attempt.OuterException))
|
||||
return PHASE_ERROR_COLOR;
|
||||
return PHASE_IN_PROGRESS_COLOR;
|
||||
}
|
||||
return PHASE_NOT_STARTED_COLOR;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
<Row>
|
||||
<Column ColumnSize="ColumnSize.Is2" >
|
||||
@((MarkupString)item.Provider.SvgImage)
|
||||
@((MarkupString)item.Provider.Details.SvgImage)
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is10">
|
||||
<Check TValue="bool" CheckedChanged="@(v => UpdateObject(item.ObjectId, v))"><strong>@item.Name</strong></Check>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<Column ColumnSize="ColumnSize.Is6.Is9.OnWidescreen" Padding="Padding.Is2" Class="border-left">
|
||||
@foreach (var item in this.Secret.Resources)
|
||||
{
|
||||
<div style="width:1em;" class="mr-3 float-left" title="@item.Name">@((MarkupString)item.Provider.SvgImage)</div>
|
||||
<div style="width:1em;" class="mr-3 float-left" title="@item.Name">@((MarkupString)item.Provider.Details.SvgImage)</div>
|
||||
}
|
||||
</Column>
|
||||
</Row>
|
||||
|
@ -65,7 +65,7 @@
|
|||
</Button>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6" Class="text-center">
|
||||
<Button Disabled="true" Color="Color.Danger">
|
||||
<Button Color="Color.Danger" Clicked="@(() => { DeleteModalShowing = true; })">
|
||||
<Icon Name="FontAwesomeIcons.Trash" Margin="Margin.Is3.FromRight" />
|
||||
Delete Secret
|
||||
</Button>
|
||||
|
@ -80,7 +80,7 @@
|
|||
<CardBody>
|
||||
<Row>
|
||||
<Column ColumnSize="ColumnSize.Is3" Class="providerImage">
|
||||
@((MarkupString)resource.Provider.SvgImage)
|
||||
@((MarkupString)resource.Provider.Details.SvgImage)
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is9">
|
||||
<Heading Size="HeadingSize.Is3">
|
||||
|
@ -135,11 +135,19 @@
|
|||
</AuthJanitor.UI.Components.HelpSlideInComponent>
|
||||
</Container>
|
||||
|
||||
<SystemWideFooter RefreshDataClicked="@(() => LoadData())"
|
||||
@bind-ContextualHelpVisible="@ContextualHelpVisible"/>
|
||||
<DeleteConfirmationModal @bind-Visible="@DeleteModalShowing"
|
||||
ObjectId="@Secret.ObjectId"
|
||||
ObjectName="@Secret.Name"
|
||||
ResultClicked="@DeleteConfirmCallback" />
|
||||
|
||||
<SystemWideFooter RefreshDataClicked="@(() => LoadData())"
|
||||
@bind-ContextualHelpVisible="@ContextualHelpVisible" />
|
||||
|
||||
@code {
|
||||
public ManagedSecretViewModel Secret { get; set; } = new ManagedSecretViewModel();
|
||||
|
||||
protected bool CreateModalShowing { get; set; }
|
||||
protected bool DeleteModalShowing { get; set; }
|
||||
protected bool ContextualHelpVisible { get; set; }
|
||||
|
||||
[Parameter]
|
||||
|
@ -160,4 +168,14 @@
|
|||
resource.ProviderConfiguration.SerializedConfiguration = resource.SerializedProviderConfiguration;
|
||||
}));
|
||||
}
|
||||
|
||||
protected async Task DeleteConfirmCallback(bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
await Http.AJDelete<ManagedSecretViewModel>(Secret.ObjectId);
|
||||
NavigationManager.NavigateTo("/managedSecrets");
|
||||
}
|
||||
DeleteModalShowing = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
{
|
||||
<ListGroupItem Padding="Padding.Is1.OnAll" Class="justify-content-between align-items-center d-flex">
|
||||
<div>
|
||||
<div style="width:1em;" class="mr-2 float-left">@((MarkupString)resource.Provider.SvgImage)</div>
|
||||
<div style="width:1em;" class="mr-2 float-left">@((MarkupString)resource.Provider.Details.SvgImage)</div>
|
||||
@resource.Name
|
||||
</div>
|
||||
<ColoredRiskScore ShowRiskText="false" Value="@resource.RiskScore" />
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<span class="float-left mr-2" style="width:1em;">
|
||||
@((MarkupString)context.SvgImage)
|
||||
@((MarkupString)context.Details.SvgImage)
|
||||
</span>
|
||||
@context.Details.Name
|
||||
</Template>
|
||||
|
@ -30,24 +30,9 @@
|
|||
Sortable="true" Filterable="true" />
|
||||
|
||||
<BlazorTable.Column TableItem="LoadedProviderViewModel"
|
||||
Title="Features" Field="@(x => x.Details.Features)">
|
||||
Title="Features" Field="@(x => x.Details)">
|
||||
<Template>
|
||||
<Icon Name="FontAwesomeIcons.Flask"
|
||||
Class="@(context.Details.Features.HasFlag(ProviderFeatureFlags.IsTestable) ? "text-success" : "text-dark")"
|
||||
title="@("Provider " + (context.Details.Features.HasFlag(ProviderFeatureFlags.IsTestable) ? "Supports" : "Does Not Support") + " Pre-Testing")"
|
||||
Margin="Margin.Is2" />
|
||||
<Icon Name="FontAwesomeIcons.SyncAlt"
|
||||
Class="@(context.Details.Features.HasFlag(ProviderFeatureFlags.CanRotateWithoutDowntime) ? "text-success" : "text-dark")"
|
||||
title="@("Provider " + (context.Details.Features.HasFlag(ProviderFeatureFlags.CanRotateWithoutDowntime) ? "Can" : "Cannot") + " Rotate Without Downtime")"
|
||||
Margin="Margin.Is2" />
|
||||
<Icon Name="FontAwesomeIcons.LayerGroup"
|
||||
Class="@(context.Details.Features.HasFlag(ProviderFeatureFlags.HasCandidateSelection) ? "text-success" : "text-dark")"
|
||||
title="@("Provider " + (context.Details.Features.HasFlag(ProviderFeatureFlags.HasCandidateSelection) ? "Supports" : "Does Not Support") + " Candidate Selection")"
|
||||
Margin="Margin.Is2" />
|
||||
<Icon Name="FontAwesomeIcons.Key"
|
||||
Class="@(context.Details.Features.HasFlag(ProviderFeatureFlags.SupportsSecondaryKey) ? "text-success" : "text-dark")"
|
||||
title="@("Provider " + (context.Details.Features.HasFlag(ProviderFeatureFlags.SupportsSecondaryKey) ? "Supports" : "Does Not Support") + " Use of a Secondary Key")"
|
||||
Margin="Margin.Is2" />
|
||||
<ProviderCapabilitiesComponent Provider="@context" />
|
||||
</Template>
|
||||
</BlazorTable.Column>
|
||||
|
||||
|
@ -77,7 +62,7 @@
|
|||
@code {
|
||||
protected IEnumerable<LoadedProviderViewModel> ProviderList { get; set; } = new List<LoadedProviderViewModel>();
|
||||
protected bool ContextualHelpVisible { get; set; }
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
|
||||
protected async Task LoadData()
|
||||
|
|
|
@ -1,152 +1,150 @@
|
|||
@page "/rekeyingTasks/{RekeyingTaskId}"
|
||||
|
||||
<Container Fluid="true" Style="position:relative;">
|
||||
<AuthJanitor.UI.Components.BreadcrumbRow Category="Manage"
|
||||
PageGroup="Rekeying Tasks"
|
||||
PageGroupLink="/rekeyingTasks"
|
||||
OptionalObjectName="@Task.ManagedSecret.Name" />
|
||||
<Row Margin="Margin.Is2.OnY">
|
||||
<Column Class="text-center">
|
||||
<Icon Name="FontAwesomeIcons.Flask" Class="fa-3x text-success" />
|
||||
<AuthJanitor.UI.Components.BreadcrumbRow Category="Manage"
|
||||
PageGroup="Rekeying Tasks"
|
||||
PageGroupLink="/rekeyingTasks"
|
||||
OptionalObjectName="@Task.ManagedSecret.Name" />
|
||||
<Row Margin="Margin.Is2.OnY">
|
||||
<Column Class="text-center d-flex align-items-center justify-content-center">
|
||||
<TaskProgressionComponent Attempt="@Attempt" />
|
||||
</Column>
|
||||
</Row>
|
||||
<Row Padding="Padding.Is2.FromBottom" Class="border-bottom">
|
||||
<Column Class="text-center">
|
||||
<DisplayHeading Size="DisplayHeadingSize.Is4">
|
||||
@Task.ManagedSecret.Name
|
||||
<a href="@($"/managedSecrets/{Task.ManagedSecret.ObjectId}")" class="h5">
|
||||
<Icon Name="FontAwesomeIcons.Link" />
|
||||
</a>
|
||||
</DisplayHeading>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-success" />
|
||||
<Icon Name="FontAwesomeIcons.Database" Class="fa-3x text-success" />
|
||||
<Row Class="border-bottom">
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left bg-light">
|
||||
<Heading Size="HeadingSize.Is5" Margin="Margin.IsAuto.OnY">Last Changed</Heading>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left">
|
||||
<Paragraph Margin="Margin.IsAuto.OnY">@Task.ManagedSecret.LastChanged</Paragraph>
|
||||
</Column>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-success" />
|
||||
<Icon Name="FontAwesomeIcons.Cogs" Class="fa-3x text-success" />
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left bg-light">
|
||||
<Heading Size="HeadingSize.Is5" Margin="Margin.IsAuto.OnY">Expires</Heading>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left">
|
||||
<Paragraph Margin="Margin.IsAuto.OnY">@Task.Expiry</Paragraph>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-success" />
|
||||
<Icon Name="FontAwesomeIcons.EllipsisH" Class="fa-3x text-success" />
|
||||
<Row Class="border-bottom">
|
||||
<Column Padding="Padding.Is0">
|
||||
<AuthJanitor.UI.Components.ColoredProgressBar Value="@Task.ManagedSecret.ExpiryPercent" />
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-primary" />
|
||||
<Icon Name="FontAwesomeIcons.Database" Class="fa-3x text-primary" />
|
||||
<Row Padding="Padding.Is3.OnY" Class="shadow-sm bg-light">
|
||||
<Column ColumnSize="ColumnSize.Is6" Class="text-center">
|
||||
<Button Color="Color.Success" Clicked="@(() => { ApproveModalShowing = true; })">
|
||||
<Icon Name="FontAwesomeIcons.Check" Margin="Margin.Is3.FromRight" />
|
||||
Approve Rekeying Task
|
||||
</Button>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6" Class="text-center">
|
||||
<Button Color="Color.Danger" Clicked="@(() => { DeleteModalShowing = true; })">
|
||||
<Icon Name="FontAwesomeIcons.Trash" Margin="Margin.Is3.FromRight" />
|
||||
Delete Rekeying Task
|
||||
</Button>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-secondary" />
|
||||
<Icon Name="FontAwesomeIcons.Cogs" Class="fa-3x text-secondary" />
|
||||
|
||||
<Icon Name="FontAwesomeIcons.ArrowRight" Margin="Margin.Is2.FromBottom.Is2.OnX" Class="text-secondary" />
|
||||
<Icon Name="FontAwesomeIcons.FlagCheckered" Class="fa-3x text-secondary" />
|
||||
</Column>
|
||||
</Row>
|
||||
<Row Padding="Padding.Is2.FromBottom" Class="border-bottom">
|
||||
<Column Class="text-center">
|
||||
<DisplayHeading Size="DisplayHeadingSize.Is4">
|
||||
@Task.ManagedSecret.Name
|
||||
<a href="@($"/managedSecrets/{Task.ManagedSecret.ObjectId}")" class="h5">
|
||||
<Icon Name="FontAwesomeIcons.Link" />
|
||||
</a>
|
||||
</DisplayHeading>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row Class="border-bottom">
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left bg-light">
|
||||
<Heading Size="HeadingSize.Is5" Margin="Margin.IsAuto.OnY">Last Changed</Heading>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left">
|
||||
<Paragraph Margin="Margin.IsAuto.OnY">@Task.ManagedSecret.LastChanged</Paragraph>
|
||||
</Column>
|
||||
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left bg-light">
|
||||
<Heading Size="HeadingSize.Is5" Margin="Margin.IsAuto.OnY">Expires</Heading>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6.Is3.OnWidescreen" Padding="Padding.Is2" Class="border-left">
|
||||
<Paragraph Margin="Margin.IsAuto.OnY">@Task.Expiry</Paragraph>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row Class="border-bottom">
|
||||
<Column Padding="Padding.Is0">
|
||||
<AuthJanitor.UI.Components.ColoredProgressBar Value="@Task.ManagedSecret.ExpiryPercent" />
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row Padding="Padding.Is3.OnY" Class="shadow-sm bg-light">
|
||||
<Column ColumnSize="ColumnSize.Is6" Class="text-center">
|
||||
<Button Color="Color.Success" Disabled="true">
|
||||
<Icon Name="FontAwesomeIcons.Check" Margin="Margin.Is3.FromRight" />
|
||||
Approve Rekeying Task
|
||||
</Button>
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is6" Class="text-center">
|
||||
<Button Color="Color.Danger" Disabled="true">
|
||||
<Icon Name="FontAwesomeIcons.Trash" Margin="Margin.Is3.FromRight" />
|
||||
Delete Rekeying Task
|
||||
</Button>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
@if (Task.Attempts.Any())
|
||||
{
|
||||
<Row Margin="Margin.Is3.FromTop">
|
||||
<Column ColumnSize="ColumnSize.Is12">
|
||||
<Tabs SelectedTab="@SelectedAttemptTab" SelectedTabChanged="@OnSelectedTabChanged">
|
||||
<Items>
|
||||
@foreach (var attempt in Task.Attempts)
|
||||
{
|
||||
<Tab Name="@attempt.AttemptStarted.ToString()">
|
||||
@if (attempt.IsSuccessfulAttempt && attempt.AttemptFinished != default)
|
||||
@if (Task.Attempts.Any())
|
||||
{
|
||||
<Row Margin="Margin.Is3.FromTop">
|
||||
<Column ColumnSize="ColumnSize.Is12">
|
||||
<Tabs SelectedTab="@SelectedAttemptTab" SelectedTabChanged="@OnSelectedTabChanged">
|
||||
<Items>
|
||||
@foreach (var attempt in Task.Attempts)
|
||||
{
|
||||
<Tab Name="@attempt.AttemptStarted.ToString()">
|
||||
@if (attempt.IsComplete && attempt.IsSuccessfulAttempt)
|
||||
{
|
||||
<Icon Name="FontAwesomeIcons.CheckCircle" Class="text-success" Margin="Margin.IsAuto.OnY.Is1.FromRight" />
|
||||
<Icon Name="FontAwesomeIcons.CheckCircle" Class="text-success" Margin="Margin.IsAuto.OnY.Is1.FromRight" />
|
||||
}
|
||||
else if (attempt.AttemptFinished != default)
|
||||
else if (attempt.IsComplete && !attempt.IsSuccessfulAttempt)
|
||||
{
|
||||
<Icon Name="FontAwesomeIcons.TimesCircle" Class="text-danger" Margin="Margin.IsAuto.OnY.Is1.FromRight" />
|
||||
<Icon Name="FontAwesomeIcons.TimesCircle" Class="text-danger" Margin="Margin.IsAuto.OnY.Is1.FromRight" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<Icon Name="FontAwesomeIcons.PlayCircle" Class="text-info" Margin="Margin.IsAuto.OnY.Is1.FromRight" />
|
||||
<Icon Name="FontAwesomeIcons.PlayCircle" Class="text-info" Margin="Margin.IsAuto.OnY.Is1.FromRight" />
|
||||
}
|
||||
@attempt.UserDisplayName<br />
|
||||
<small>@attempt.AttemptStarted.ToString()</small>
|
||||
</Tab>
|
||||
}
|
||||
</Items>
|
||||
<Content>
|
||||
@foreach (var attempt in Task.Attempts)
|
||||
{
|
||||
<TabPanel Name="@attempt.AttemptStarted.ToString()">
|
||||
<pre><code>@attempt.LogString</code></pre>
|
||||
@if (!attempt.IsSuccessfulAttempt)
|
||||
@attempt.UserDisplayName<br />
|
||||
<small>@attempt.AttemptStarted.ToString()</small>
|
||||
</Tab>
|
||||
}
|
||||
</Items>
|
||||
<Content>
|
||||
@foreach (var attempt in Task.Attempts)
|
||||
{
|
||||
<TabPanel Name="@attempt.AttemptStarted.ToString()">
|
||||
<pre><code>@attempt.LogString</code></pre>
|
||||
@if (!attempt.IsSuccessfulAttempt)
|
||||
{
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="text-danger">
|
||||
Exception thrown!
|
||||
</h4>
|
||||
<p>
|
||||
<pre><code>@attempt.OuterException</code></pre>
|
||||
</p>
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="text-danger">
|
||||
Exception thrown!
|
||||
</h4>
|
||||
<p>
|
||||
<pre><code>@attempt.OuterException</code></pre>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
}
|
||||
</Content>
|
||||
</Tabs>
|
||||
</Column>
|
||||
</Row>
|
||||
}
|
||||
</TabPanel>
|
||||
}
|
||||
</Content>
|
||||
</Tabs>
|
||||
</Column>
|
||||
</Row>
|
||||
}
|
||||
|
||||
<AuthJanitor.UI.Components.HelpSlideInComponent Title="Rekeying Tasks"
|
||||
Icon="@FontAwesomeIcons.Tasks"
|
||||
@bind-Visible="@ContextualHelpVisible">
|
||||
<Paragraph>
|
||||
<strong>Rekeying Tasks</strong> are created, either automatically by the system as a key or secret nears its expiry,
|
||||
or manually by an administrator. A <strong>Rekeying Task</strong> is associated with a <em>single</em>
|
||||
<strong>Secret</strong>. <strong>Rekeying Tasks</strong> can have multiple attempts by different administrators
|
||||
or service accounts.
|
||||
</Paragraph>
|
||||
</AuthJanitor.UI.Components.HelpSlideInComponent>
|
||||
<AuthJanitor.UI.Components.HelpSlideInComponent Title="Rekeying Tasks"
|
||||
Icon="@FontAwesomeIcons.Tasks"
|
||||
@bind-Visible="@ContextualHelpVisible">
|
||||
<Paragraph>
|
||||
<strong>Rekeying Tasks</strong> are created, either automatically by the system as a key or secret nears its expiry,
|
||||
or manually by an administrator. A <strong>Rekeying Task</strong> is associated with a <em>single</em>
|
||||
<strong>Secret</strong>. <strong>Rekeying Tasks</strong> can have multiple attempts by different administrators
|
||||
or service accounts.
|
||||
</Paragraph>
|
||||
</AuthJanitor.UI.Components.HelpSlideInComponent>
|
||||
</Container>
|
||||
|
||||
<DataModal @bind-Visible="@ApproveModalShowing"
|
||||
Title="Approve Task"
|
||||
YesButton="Approve"
|
||||
NoButton="Cancel"
|
||||
ResultClicked="@ApproveCallback">
|
||||
<ApprovalSummary Task="@Task" />
|
||||
</DataModal>
|
||||
<DeleteConfirmationModal @bind-Visible="@DeleteModalShowing"
|
||||
ObjectId="@Task.ObjectId"
|
||||
ObjectName="@Task.ObjectId.ToString()"
|
||||
ResultClicked="@DeleteConfirmCallback" />
|
||||
|
||||
<SystemWideFooter RefreshDataClicked="@(() => LoadData())"
|
||||
@bind-ContextualHelpVisible="@ContextualHelpVisible"/>
|
||||
@bind-ContextualHelpVisible="@ContextualHelpVisible" />
|
||||
|
||||
@using AuthJanitor.UI.Shared.ViewModels
|
||||
@code {
|
||||
public ManagedSecretViewModel Secret => Task == null ? new ManagedSecretViewModel() : Task.ManagedSecret;
|
||||
public RekeyingTaskViewModel Task { get; set; } = new RekeyingTaskViewModel();
|
||||
|
||||
protected bool ContextualHelpVisible { get; set; }
|
||||
protected bool ApproveModalShowing { get; set; }
|
||||
protected bool DeleteModalShowing { get; set; }
|
||||
public bool ContextualHelpVisible { get; set; }
|
||||
|
||||
public RekeyingAttemptLogger Attempt { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string RekeyingTaskId { get; set; }
|
||||
|
@ -154,25 +152,53 @@
|
|||
public TimeSpan DurationSoFar => DateTimeOffset.UtcNow - Secret.LastChanged.GetValueOrDefault();
|
||||
protected IEnumerable<LoadedProviderViewModel> _providers;
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
protected async Task LoadData()
|
||||
{
|
||||
_providers = await Http.AJList<LoadedProviderViewModel>();
|
||||
Task = await Http.AJGet<RekeyingTaskViewModel>(Guid.Parse(RekeyingTaskId));
|
||||
if (Task.Attempts.Any())
|
||||
SelectedAttemptTab = Task.Attempts.OrderByDescending(a => a.AttemptStarted).FirstOrDefault()?.AttemptStarted.ToString();
|
||||
|
||||
await System.Threading.Tasks.Task.WhenAll(Task.ManagedSecret.Resources.Select(async resource =>
|
||||
{
|
||||
resource.ProviderConfiguration = await Http.AJGet<ProviderConfigurationViewModel>(resource.ProviderType);
|
||||
resource.ProviderConfiguration.SerializedConfiguration = resource.SerializedProviderConfiguration;
|
||||
}));
|
||||
SelectedAttemptTab = Task.Attempts.OrderByDescending(a => a.AttemptStarted).FirstOrDefault()?.AttemptStarted.ToString();
|
||||
Attempt = Task.Attempts.OrderByDescending(a => a.AttemptFinished).FirstOrDefault();
|
||||
}
|
||||
|
||||
//await System.Threading.Tasks.Task.WhenAll(Task.ManagedSecret.Resources.Select(async resource =>
|
||||
//{
|
||||
// resource.ProviderConfiguration = await Http.AJGet<ProviderConfigurationViewModel>(resource.ProviderType);
|
||||
// resource.ProviderConfiguration.SerializedConfiguration = resource.SerializedProviderConfiguration;
|
||||
//}));
|
||||
}
|
||||
|
||||
string SelectedAttemptTab;
|
||||
private void OnSelectedTabChanged(string name)
|
||||
{
|
||||
SelectedAttemptTab = name;
|
||||
Attempt = Task.Attempts.FirstOrDefault(a => a.AttemptStarted.ToString() == name);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task ApproveCallback(bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
await Http.PostAsync($"/api/tasks/{Task.ObjectId}/approve", new StringContent(string.Empty));
|
||||
await LoadData();
|
||||
}
|
||||
ApproveModalShowing = false;
|
||||
}
|
||||
|
||||
protected async Task DeleteConfirmCallback(bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
await Http.AJDelete<RekeyingTaskViewModel>(Task.ObjectId);
|
||||
NavigationManager.NavigateTo("/rekeyingTasks");
|
||||
}
|
||||
DeleteModalShowing = false;
|
||||
}
|
||||
}
|
|
@ -75,7 +75,7 @@
|
|||
{
|
||||
<ListGroupItem Padding="Padding.Is1.OnAll" Class="justify-content-between align-items-center d-flex">
|
||||
<div>
|
||||
<div style="width:1em;" class="mr-2 float-left">@((MarkupString)resource.Provider.SvgImage)</div>
|
||||
<div style="width:1em;" class="mr-2 float-left">@((MarkupString)resource.Provider.Details.SvgImage)</div>
|
||||
@resource.Name
|
||||
</div>
|
||||
<ColoredRiskScore ShowRiskText="false" Value="@resource.RiskScore" />
|
||||
|
@ -136,10 +136,13 @@
|
|||
|
||||
protected async Task LoadData() => Tasks = await Http.AJList<RekeyingTaskViewModel>();
|
||||
|
||||
protected async Task ApproveCallback()
|
||||
protected async Task ApproveCallback(bool result)
|
||||
{
|
||||
await Http.PostAsync($"/api/tasks/{SelectedValue.ObjectId}/approve", new StringContent(string.Empty));
|
||||
await LoadData();
|
||||
if (result)
|
||||
{
|
||||
await Http.PostAsync($"/api/tasks/{SelectedValue.ObjectId}/approve", new StringContent(string.Empty));
|
||||
await LoadData();
|
||||
}
|
||||
ApproveModalShowing = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
@if (_provider != null)
|
||||
{
|
||||
<Column Class="bg-light" Margin="Margin.IsAuto.OnY" ColumnSize="ColumnSize.Is1">
|
||||
<div class="providerImage my-2 mx-auto">@((MarkupString)_provider.SvgImage)</div>
|
||||
<div class="providerImage my-2 mx-auto">@((MarkupString)_provider.Details.SvgImage)</div>
|
||||
<div class="d-none d-sm-block text-center border-top py-2 small">@_provider.Details.Name</div>
|
||||
</Column>
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
|||
ProviderConfiguration="@Resource.ProviderConfiguration" />
|
||||
</Column>
|
||||
<Column ColumnSize="ColumnSize.Is4" Class="text-center">
|
||||
<Button Color="Color.Danger" Disabled="true">
|
||||
<Button Color="Color.Danger" Clicked="@(() => { DeleteModalShowing = true; })">
|
||||
<Icon Name="FontAwesomeIcons.Trash" Margin="Margin.Is3.FromRight" />
|
||||
Delete Resource
|
||||
</Button>
|
||||
|
@ -101,6 +101,11 @@
|
|||
|
||||
</Container>
|
||||
|
||||
<DeleteConfirmationModal @bind-Visible="@DeleteModalShowing"
|
||||
ObjectId="@Resource.ObjectId"
|
||||
ObjectName="@Resource.Name"
|
||||
ResultClicked="@DeleteConfirmCallback" />
|
||||
|
||||
<SystemWideFooter RefreshDataClicked="@(() => LoadData())"
|
||||
@bind-ContextualHelpVisible="@ContextualHelpVisible"/>
|
||||
|
||||
|
@ -109,6 +114,7 @@
|
|||
[Parameter]
|
||||
public string ResourceId { get; set; }
|
||||
|
||||
protected bool DeleteModalShowing { get; set; }
|
||||
protected bool ContextualHelpVisible { get; set; }
|
||||
|
||||
public ResourceViewModel Resource { get; set; } = new ResourceViewModel();
|
||||
|
@ -124,4 +130,14 @@
|
|||
Resource.ProviderConfiguration = await Http.AJGet<ProviderConfigurationViewModel>(Resource.ProviderType);
|
||||
Resource.ProviderConfiguration.SerializedConfiguration = Resource.SerializedProviderConfiguration;
|
||||
}
|
||||
|
||||
protected async Task DeleteConfirmCallback(bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
await Http.AJDelete<ResourceViewModel>(Resource.ObjectId);
|
||||
await LoadData();
|
||||
}
|
||||
DeleteModalShowing = false;
|
||||
}
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 83 KiB |
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<!-- Copyright (c) Microsoft Corporation. -->
|
||||
<!-- Licensed under the MIT license. -->
|
||||
<html>
|
||||
|
@ -33,7 +33,6 @@
|
|||
|
||||
<link href="_content/Blazorise/blazorise.css" rel="stylesheet" />
|
||||
<link href="_content/Blazorise.Bootstrap/blazorise.bootstrap.css" rel="stylesheet" />
|
||||
<link href="_content/AuthJanitor.AspNet.UI.Razor/style.css" rel="stylesheet" />
|
||||
|
||||
<link href="css/style.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
@ -41,33 +40,26 @@
|
|||
<body>
|
||||
<app>
|
||||
<style type="text/css">
|
||||
/*
|
||||
Logo font: Berlin Sans Demi
|
||||
Loader BG: #305a70
|
||||
SVG has 2 rectangles which must match bg color for effects
|
||||
Note: Need to fix vector clip issue on "A" lid in animated logo
|
||||
*/
|
||||
body {
|
||||
background-color: #305a70;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.insetText {
|
||||
background-color: #103a50;
|
||||
color: transparent;
|
||||
text-shadow: 2px 2px 1px rgba(255,255,255,0.3);
|
||||
-webkit-background-clip: text;
|
||||
-moz-background-clip: text;
|
||||
background-clip: text;
|
||||
div.ajAnimated {
|
||||
width: 8vw;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
<div class="fixed-top text-center mt-3">
|
||||
<h1 class="h1 insetText" style="
|
||||
font-size: 80px;
|
||||
font-weight: bold;
|
||||
margin: 25px auto;">
|
||||
AuthJanitor
|
||||
</h1>
|
||||
<div class="loader">
|
||||
<div style="position: relative; width: 100%; height: 100%;">
|
||||
<div style="position: absolute; top: 0; bottom: 0; left: 0; right: 0; text-align: center; margin-top: calc(50% - 45pt); z-index: 100; color: #ccc; font-size: 40pt;">
|
||||
<i class="fa fa-2x fas fa-user-lock"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ajAnimated">
|
||||
<object class="img-fluid" type="image/svg+xml" data="css/AJAnimated.svg"></object>
|
||||
</div>
|
||||
</div>
|
||||
</app>
|
||||
|
@ -77,7 +69,7 @@
|
|||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||
<script src="https://use.fontawesome.com/releases/v5.13.0/js/all.js"></script>
|
||||
<!--<script src="https://use.fontawesome.com/releases/v5.13.0/js/all.js"></script>-->
|
||||
|
||||
<script src="_content/Blazorise/blazorise.js"></script>
|
||||
<script src="_content/Blazorise.Bootstrap/blazorise.bootstrap.js"></script>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,205 +1,219 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.UI.Shared.Models;
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.UI.Shared.Models;
|
||||
using AuthJanitor.EventSinks;
|
||||
using AuthJanitor.IdentityServices;
|
||||
using AuthJanitor.Integrations.DataStores;
|
||||
using AuthJanitor.Providers;
|
||||
using AuthJanitor.Integrations.DataStores;
|
||||
using AuthJanitor.Providers;
|
||||
using AuthJanitor.SecureStorage;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.UI.Shared.MetaServices
|
||||
{
|
||||
public class TaskExecutionMetaService
|
||||
{
|
||||
private readonly IDataStore<ManagedSecret> _managedSecrets;
|
||||
private readonly IDataStore<RekeyingTask> _rekeyingTasks;
|
||||
private readonly IDataStore<Resource> _resources;
|
||||
private readonly ISecureStorage _secureStorageProvider;
|
||||
|
||||
private readonly ProviderManagerService _providerManagerService;
|
||||
|
||||
private readonly EventDispatcherMetaService _eventDispatcherMetaService;
|
||||
private readonly IIdentityService _identityService;
|
||||
|
||||
public TaskExecutionMetaService(
|
||||
EventDispatcherMetaService eventDispatcherMetaService,
|
||||
IIdentityService identityService,
|
||||
ProviderManagerService providerManagerService,
|
||||
IDataStore<ManagedSecret> managedSecrets,
|
||||
IDataStore<RekeyingTask> rekeyingTasks,
|
||||
IDataStore<Resource> resources,
|
||||
ISecureStorage secureStorageProvider)
|
||||
{
|
||||
_eventDispatcherMetaService = eventDispatcherMetaService;
|
||||
_identityService = identityService;
|
||||
_providerManagerService = providerManagerService;
|
||||
_managedSecrets = managedSecrets;
|
||||
_rekeyingTasks = rekeyingTasks;
|
||||
_resources = resources;
|
||||
_secureStorageProvider = secureStorageProvider;
|
||||
}
|
||||
|
||||
public async Task CacheBackCredentialsForTaskIdAsync(Guid taskId, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await _rekeyingTasks.GetOne(taskId, cancellationToken);
|
||||
if (task == null)
|
||||
throw new KeyNotFoundException("Task not found");
|
||||
|
||||
if (task.ConfirmationType != TaskConfirmationStrategies.AdminCachesSignOff)
|
||||
throw new InvalidOperationException("Task does not persist credentials");
|
||||
|
||||
if (_secureStorageProvider == null)
|
||||
throw new NotSupportedException("Must register an ISecureStorageProvider");
|
||||
|
||||
var credentialId = await _identityService.GetAccessTokenOnBehalfOfCurrentUserAsync()
|
||||
.ContinueWith(t => _secureStorageProvider.Persist(task.Expiry, t.Result))
|
||||
.Unwrap();
|
||||
|
||||
task.PersistedCredentialId = credentialId;
|
||||
task.PersistedCredentialUser = _identityService.UserName;
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ExecuteTask(Guid taskId, CancellationToken cancellationToken)
|
||||
{
|
||||
// Prepare record
|
||||
var task = await _rekeyingTasks.GetOne(taskId, cancellationToken);
|
||||
task.RekeyingInProgress = true;
|
||||
var rekeyingAttemptLog = new RekeyingAttemptLogger();
|
||||
task.Attempts.Add(rekeyingAttemptLog);
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
|
||||
var logUpdateCancellationTokenSource = new CancellationTokenSource();
|
||||
var logUpdateTask = Task.Run(async () =>
|
||||
{
|
||||
while (task.RekeyingInProgress)
|
||||
{
|
||||
await Task.Delay(15 * 1000);
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
}, logUpdateCancellationTokenSource.Token);
|
||||
|
||||
// Retrieve credentials for Task
|
||||
AccessTokenCredential credential = null;
|
||||
try
|
||||
{
|
||||
if (task.ConfirmationType == TaskConfirmationStrategies.AdminCachesSignOff)
|
||||
{
|
||||
if (task.PersistedCredentialId == default)
|
||||
throw new KeyNotFoundException("Cached sign-off is preferred but no credentials were persisted!");
|
||||
|
||||
if (_secureStorageProvider == null)
|
||||
throw new NotSupportedException("Must register an ISecureStorageProvider");
|
||||
|
||||
credential = await _secureStorageProvider.Retrieve<AccessTokenCredential>(task.PersistedCredentialId);
|
||||
}
|
||||
else if (task.ConfirmationType == TaskConfirmationStrategies.AdminSignsOffJustInTime)
|
||||
credential = await _identityService.GetAccessTokenOnBehalfOfCurrentUserAsync();
|
||||
else if (task.ConfirmationType.UsesServicePrincipal())
|
||||
credential = await _identityService.GetAccessTokenForApplicationAsync();
|
||||
else
|
||||
throw new NotSupportedException("No Access Tokens could be generated for this Task!");
|
||||
|
||||
if (credential == null || string.IsNullOrEmpty(credential.AccessToken))
|
||||
throw new InvalidOperationException("Access Token was found, but was blank or invalid");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await EmbedException(task, ex, cancellationToken, "Exception retrieving Access Token");
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
return;
|
||||
}
|
||||
|
||||
// Embed credential context in attempt log
|
||||
rekeyingAttemptLog.UserDisplayName = credential.Username;
|
||||
rekeyingAttemptLog.UserEmail = credential.Username;
|
||||
if (task.ConfirmationType.UsesOBOTokens())
|
||||
rekeyingAttemptLog.UserDisplayName = task.PersistedCredentialUser;
|
||||
|
||||
// Retrieve targets
|
||||
var secret = await _managedSecrets.GetOne(task.ManagedSecretId, cancellationToken);
|
||||
rekeyingAttemptLog.LogInformation("Beginning rekeying of secret ID {SecretId}", task.ManagedSecretId);
|
||||
var resources = await _resources.Get(r => secret.ResourceIds.Contains(r.ObjectId), cancellationToken);
|
||||
|
||||
// Execute rekeying workflow
|
||||
try
|
||||
{
|
||||
var providers = resources.Select(r => _providerManagerService.GetProviderInstance(
|
||||
r.ProviderType,
|
||||
r.ProviderConfiguration)).ToList();
|
||||
|
||||
// Link in automation bindings from the outer flow
|
||||
providers.ForEach(p => p.Credential = credential);
|
||||
|
||||
await _providerManagerService.ExecuteRekeyingWorkflow(rekeyingAttemptLog, secret.ValidPeriod, providers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await EmbedException(task, ex, cancellationToken, "Error executing rekeying workflow!");
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
}
|
||||
|
||||
// Update Task record
|
||||
task.RekeyingInProgress = false;
|
||||
task.RekeyingCompleted = rekeyingAttemptLog.IsSuccessfulAttempt;
|
||||
task.RekeyingFailed = !rekeyingAttemptLog.IsSuccessfulAttempt;
|
||||
|
||||
logUpdateCancellationTokenSource.Cancel();
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
|
||||
// Run cleanup if Task is complete
|
||||
if (task.RekeyingCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
secret.LastChanged = DateTimeOffset.UtcNow;
|
||||
await _managedSecrets.Update(secret, cancellationToken);
|
||||
|
||||
if (task.PersistedCredentialId != default && task.PersistedCredentialId != Guid.Empty)
|
||||
{
|
||||
rekeyingAttemptLog.LogInformation("Destroying persisted credential");
|
||||
await _secureStorageProvider.Destroy(task.PersistedCredentialId);
|
||||
|
||||
task.PersistedCredentialId = default;
|
||||
task.PersistedCredentialUser = default;
|
||||
}
|
||||
|
||||
rekeyingAttemptLog.LogInformation("Completed rekeying workflow for ManagedSecret '{ManagedSecretName}' (ID {ManagedSecretId})", secret.Name, secret.ObjectId);
|
||||
rekeyingAttemptLog.LogInformation("Rekeying task completed");
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await EmbedException(task, ex, cancellationToken, "Error cleaning up after rekeying!");
|
||||
}
|
||||
|
||||
|
||||
if (task.ConfirmationType.UsesOBOTokens())
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskCompletedManually, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
else
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskCompletedAutomatically, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
}
|
||||
else
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
}
|
||||
|
||||
private async Task EmbedException(RekeyingTask task, Exception ex, CancellationToken cancellationToken, string text = "Exception Occurred")
|
||||
{
|
||||
var myAttempt = task.Attempts.OrderByDescending(a => a.AttemptStarted).First();
|
||||
if (text != default) myAttempt.LogCritical(ex, text);
|
||||
myAttempt.OuterException = $"{ex.Message}{Environment.NewLine}{ex.StackTrace}";
|
||||
task.RekeyingInProgress = false;
|
||||
task.RekeyingFailed = true;
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AuthJanitor.UI.Shared.MetaServices
|
||||
{
|
||||
public class TaskExecutionMetaService
|
||||
{
|
||||
private readonly IDataStore<ManagedSecret> _managedSecrets;
|
||||
private readonly IDataStore<RekeyingTask> _rekeyingTasks;
|
||||
private readonly IDataStore<Resource> _resources;
|
||||
private readonly ISecureStorage _secureStorageProvider;
|
||||
|
||||
private readonly ProviderManagerService _providerManagerService;
|
||||
|
||||
private readonly EventDispatcherMetaService _eventDispatcherMetaService;
|
||||
private readonly IIdentityService _identityService;
|
||||
|
||||
public TaskExecutionMetaService(
|
||||
EventDispatcherMetaService eventDispatcherMetaService,
|
||||
IIdentityService identityService,
|
||||
ProviderManagerService providerManagerService,
|
||||
IDataStore<ManagedSecret> managedSecrets,
|
||||
IDataStore<RekeyingTask> rekeyingTasks,
|
||||
IDataStore<Resource> resources,
|
||||
ISecureStorage secureStorageProvider)
|
||||
{
|
||||
_eventDispatcherMetaService = eventDispatcherMetaService;
|
||||
_identityService = identityService;
|
||||
_providerManagerService = providerManagerService;
|
||||
_managedSecrets = managedSecrets;
|
||||
_rekeyingTasks = rekeyingTasks;
|
||||
_resources = resources;
|
||||
_secureStorageProvider = secureStorageProvider;
|
||||
}
|
||||
|
||||
public async Task CacheBackCredentialsForTaskIdAsync(Guid taskId, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await _rekeyingTasks.GetOne(taskId, cancellationToken);
|
||||
if (task == null)
|
||||
throw new KeyNotFoundException("Task not found");
|
||||
|
||||
if (task.ConfirmationType != TaskConfirmationStrategies.AdminCachesSignOff)
|
||||
throw new InvalidOperationException("Task does not persist credentials");
|
||||
|
||||
if (_secureStorageProvider == null)
|
||||
throw new NotSupportedException("Must register an ISecureStorageProvider");
|
||||
|
||||
var credentialId = await _identityService.GetAccessTokenOnBehalfOfCurrentUserAsync()
|
||||
.ContinueWith(t => _secureStorageProvider.Persist(task.Expiry, t.Result))
|
||||
.Unwrap();
|
||||
|
||||
task.PersistedCredentialId = credentialId;
|
||||
task.PersistedCredentialUser = _identityService.UserName;
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ExecuteTask(Guid taskId, CancellationToken cancellationToken)
|
||||
{
|
||||
// Prepare record
|
||||
var task = await _rekeyingTasks.GetOne(taskId, cancellationToken);
|
||||
task.RekeyingInProgress = true;
|
||||
var rekeyingAttemptLog = new RekeyingAttemptLogger();
|
||||
task.Attempts.Add(rekeyingAttemptLog);
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
|
||||
var logUpdateCancellationTokenSource = new CancellationTokenSource();
|
||||
var logUpdateTask = Task.Run(async () =>
|
||||
{
|
||||
while (task.RekeyingInProgress)
|
||||
{
|
||||
await Task.Delay(15 * 1000);
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
}, logUpdateCancellationTokenSource.Token);
|
||||
|
||||
// Retrieve credentials for Task
|
||||
AccessTokenCredential credential = null;
|
||||
try
|
||||
{
|
||||
if (task.ConfirmationType == TaskConfirmationStrategies.AdminCachesSignOff)
|
||||
{
|
||||
if (task.PersistedCredentialId == default)
|
||||
throw new KeyNotFoundException("Cached sign-off is preferred but no credentials were persisted!");
|
||||
|
||||
if (_secureStorageProvider == null)
|
||||
throw new NotSupportedException("Must register an ISecureStorageProvider");
|
||||
|
||||
credential = await _secureStorageProvider.Retrieve<AccessTokenCredential>(task.PersistedCredentialId);
|
||||
}
|
||||
else if (task.ConfirmationType == TaskConfirmationStrategies.AdminSignsOffJustInTime)
|
||||
credential = await _identityService.GetAccessTokenOnBehalfOfCurrentUserAsync();
|
||||
else if (task.ConfirmationType.UsesServicePrincipal())
|
||||
credential = await _identityService.GetAccessTokenForApplicationAsync();
|
||||
else
|
||||
throw new NotSupportedException("No Access Tokens could be generated for this Task!");
|
||||
|
||||
if (credential == null || string.IsNullOrEmpty(credential.AccessToken))
|
||||
throw new InvalidOperationException("Access Token was found, but was blank or invalid");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await EmbedException(task, ex, cancellationToken, "Exception retrieving Access Token");
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
return;
|
||||
}
|
||||
|
||||
// Embed credential context in attempt log
|
||||
rekeyingAttemptLog.UserDisplayName = credential.Username;
|
||||
rekeyingAttemptLog.UserEmail = credential.Username;
|
||||
if (task.ConfirmationType.UsesOBOTokens())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(task.PersistedCredentialUser))
|
||||
rekeyingAttemptLog.UserDisplayName = task.PersistedCredentialUser;
|
||||
else
|
||||
{
|
||||
rekeyingAttemptLog.UserDisplayName = _identityService.UserName;
|
||||
rekeyingAttemptLog.UserEmail = _identityService.UserEmail;
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve targets
|
||||
var secret = await _managedSecrets.GetOne(task.ManagedSecretId, cancellationToken);
|
||||
rekeyingAttemptLog.LogInformation("Beginning rekeying of secret ID {SecretId}", task.ManagedSecretId);
|
||||
var resources = await _resources.Get(r => secret.ResourceIds.Contains(r.ObjectId), cancellationToken);
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
|
||||
// Execute rekeying workflow
|
||||
try
|
||||
{
|
||||
var providers = resources.Select(r => _providerManagerService.GetProviderInstance(
|
||||
r.ProviderType,
|
||||
r.ProviderConfiguration)).ToList();
|
||||
|
||||
// Link in automation bindings from the outer flow
|
||||
providers.ForEach(p => p.Credential = credential);
|
||||
|
||||
await _providerManagerService.ExecuteRekeyingWorkflow(rekeyingAttemptLog, secret.ValidPeriod, providers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rekeyingAttemptLog.IsComplete = true;
|
||||
await EmbedException(task, ex, cancellationToken, "Error executing rekeying workflow!");
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
}
|
||||
|
||||
// Update Task record
|
||||
task.RekeyingInProgress = false;
|
||||
task.RekeyingCompleted = rekeyingAttemptLog.IsSuccessfulAttempt;
|
||||
task.RekeyingFailed = !rekeyingAttemptLog.IsSuccessfulAttempt;
|
||||
|
||||
logUpdateCancellationTokenSource.Cancel();
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
|
||||
// Run cleanup if Task is complete
|
||||
if (task.RekeyingCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
secret.LastChanged = DateTimeOffset.UtcNow;
|
||||
await _managedSecrets.Update(secret, cancellationToken);
|
||||
|
||||
if (task.PersistedCredentialId != default && task.PersistedCredentialId != Guid.Empty)
|
||||
{
|
||||
rekeyingAttemptLog.LogInformation("Destroying persisted credential");
|
||||
await _secureStorageProvider.Destroy(task.PersistedCredentialId);
|
||||
|
||||
task.PersistedCredentialId = default;
|
||||
task.PersistedCredentialUser = default;
|
||||
}
|
||||
|
||||
rekeyingAttemptLog.LogInformation("Completed rekeying workflow for ManagedSecret '{ManagedSecretName}' (ID {ManagedSecretId})", secret.Name, secret.ObjectId);
|
||||
rekeyingAttemptLog.LogInformation("Rekeying task completed");
|
||||
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await EmbedException(task, ex, cancellationToken, "Error cleaning up after rekeying!");
|
||||
}
|
||||
|
||||
|
||||
if (task.ConfirmationType.UsesOBOTokens())
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskCompletedManually, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
else
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskCompletedAutomatically, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
}
|
||||
else
|
||||
await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task);
|
||||
}
|
||||
|
||||
private async Task EmbedException(RekeyingTask task, Exception ex, CancellationToken cancellationToken, string text = "Exception Occurred")
|
||||
{
|
||||
var myAttempt = task.Attempts.OrderByDescending(a => a.AttemptStarted).First();
|
||||
if (text != default) myAttempt.LogCritical(ex, text);
|
||||
myAttempt.OuterException = Regex.Replace(JsonConvert.SerializeObject(ex, Formatting.Indented), "Bearer [A-Za-z0-9\\-\\._~\\+\\/]+=*", "<<REDACTED BEARER TOKEN>>");
|
||||
//myAttempt.OuterException = $"{ex.Message}{Environment.NewLine}{ex.StackTrace}";
|
||||
task.RekeyingInProgress = false;
|
||||
task.RekeyingFailed = true;
|
||||
await _rekeyingTasks.Update(task, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using AuthJanitor.IdentityServices;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
|
||||
namespace AuthJanitor.UI.Shared
|
||||
{
|
||||
|
@ -95,9 +96,31 @@ namespace AuthJanitor.UI.Shared
|
|||
IsRekeyableObjectProvider = provider.IsRekeyableObjectProvider,
|
||||
OriginatingFile = Path.GetFileName(provider.OriginatingFile),
|
||||
ProviderTypeName = provider.ProviderTypeName,
|
||||
SvgImage = provider.SvgImage
|
||||
Capabilities = GetProviderCapabilities(provider.ProviderType)
|
||||
};
|
||||
|
||||
private static IEnumerable<ProviderCapabilities> GetProviderCapabilities(Type providerType)
|
||||
{
|
||||
var capabilities = new List<ProviderCapabilities>();
|
||||
if (typeof(ICanEnumerateResourceCandidates).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanEnumerateResourceCandidates);
|
||||
if (typeof(ICanRunSanityTests).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanRunSanityTests);
|
||||
if (typeof(ICanCleanup).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanCleanup);
|
||||
if (typeof(ICanDistributeTemporarySecretValues).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanDistributeTemporarySecrets);
|
||||
if (typeof(ICanGenerateTemporarySecretValue).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanGenerateTemporarySecrets);
|
||||
if (typeof(ICanPerformUnifiedCommit).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanPerformUnifiedCommits);
|
||||
if (typeof(ICanPerformUnifiedCommitForTemporarySecretValues).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanPerformUnifiedCommitForTemporarySecret);
|
||||
if (typeof(ICanCleanup).IsAssignableFrom(providerType))
|
||||
capabilities.Add(ProviderCapabilities.CanCleanup);
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
private static ManagedSecretViewModel GetViewModel(IServiceProvider serviceProvider, ManagedSecret secret, CancellationToken cancellationToken)
|
||||
{
|
||||
var providerManagerService = serviceProvider.GetRequiredService<ProviderManagerService>();
|
||||
|
|
|
@ -1,17 +1,29 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Collections.Generic;
|
||||
using AuthJanitor.Providers;
|
||||
|
||||
namespace AuthJanitor.UI.Shared.ViewModels
|
||||
{
|
||||
public enum ProviderCapabilities
|
||||
{
|
||||
None,
|
||||
CanCleanup,
|
||||
CanDistributeTemporarySecrets,
|
||||
CanEnumerateResourceCandidates,
|
||||
CanGenerateTemporarySecrets,
|
||||
CanPerformUnifiedCommits,
|
||||
CanPerformUnifiedCommitForTemporarySecret,
|
||||
CanRunSanityTests
|
||||
}
|
||||
|
||||
public class LoadedProviderViewModel : IAuthJanitorViewModel
|
||||
{
|
||||
public string OriginatingFile { get; set; }
|
||||
public string ProviderTypeName { get; set; }
|
||||
public bool IsRekeyableObjectProvider { get; set; }
|
||||
public ProviderAttribute Details { get; set; }
|
||||
public string SvgImage { get; set; }
|
||||
|
||||
public IEnumerable<ProviderCapabilities> Capabilities { get; set; } = new[] { ProviderCapabilities.None };
|
||||
public string AssemblyVersion { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ namespace AuthJanitor.UI.Shared.ViewModels
|
|||
var deserialized = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(value);
|
||||
foreach (var item in ConfigurationItems)
|
||||
{
|
||||
System.Console.WriteLine($"{item.Name} => {deserialized[item.Name]} ({item.InputType})");
|
||||
if (deserialized.ContainsKey(item.Name))
|
||||
{
|
||||
switch (item.InputType)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AuthenticodeExaminer" Version="0.3.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -10,21 +10,10 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
public interface IApplicationLifecycleProvider : IAuthJanitorProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Call to prepare the application for a new secret, passing in a secret
|
||||
/// which will be valid while the Rekeying is taking place (for zero-downtime)
|
||||
/// </summary>
|
||||
Task BeforeRekeying(List<RegeneratedSecret> temporaryUseSecrets);
|
||||
|
||||
/// <summary>
|
||||
/// Call to commit the newly generated secret(s)
|
||||
/// </summary>
|
||||
Task CommitNewSecrets(List<RegeneratedSecret> newSecrets);
|
||||
|
||||
/// <summary>
|
||||
/// Call after all new keys have been committed
|
||||
/// </summary>
|
||||
Task AfterRekeying();
|
||||
Task DistributeLongTermSecretValues(List<RegeneratedSecret> newSecretValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -33,26 +22,9 @@ namespace AuthJanitor.Providers
|
|||
public abstract class ApplicationLifecycleProvider<TProviderConfiguration> : AuthJanitorProvider<TProviderConfiguration>, IApplicationLifecycleProvider
|
||||
where TProviderConfiguration : AuthJanitorProviderConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Call to prepare the application for a new secret, passing in a secret
|
||||
/// which will be valid while the Rekeying is taking place (for zero-downtime)
|
||||
/// </summary>
|
||||
public virtual Task BeforeRekeying(List<RegeneratedSecret> temporaryUseSecrets)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to commit the newly generated secret(s)
|
||||
/// </summary>
|
||||
public abstract Task CommitNewSecrets(List<RegeneratedSecret> newSecrets);
|
||||
|
||||
/// <summary>
|
||||
/// Call after all new keys have been committed
|
||||
/// </summary>
|
||||
public virtual Task AfterRekeying()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
public abstract Task DistributeLongTermSecretValues(List<RegeneratedSecret> newSecretValues);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers
|
||||
{
|
||||
|
@ -21,12 +19,6 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
AccessTokenCredential Credential { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Test if the current credentials can execute an Extension
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task Test();
|
||||
|
||||
/// <summary>
|
||||
/// Get a text description of the action which is taken by the Extension
|
||||
/// </summary>
|
||||
|
@ -50,6 +42,8 @@ namespace AuthJanitor.Providers
|
|||
/// Get the Provider's metadata
|
||||
/// </summary>
|
||||
ProviderAttribute ProviderMetadata => GetType().GetCustomAttribute<ProviderAttribute>();
|
||||
|
||||
int GenerateResourceIdentifierHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -77,15 +71,6 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
public AccessTokenCredential Credential { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Test if the current credentials can execute an Extension
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual Task Test()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a text description of the action which is taken by the Provider
|
||||
/// </summary>
|
||||
|
@ -104,5 +89,7 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual IList<RiskyConfigurationItem> GetRisks() => new List<RiskyConfigurationItem>();
|
||||
|
||||
public int GenerateResourceIdentifierHashCode() => Configuration.GenerateResourceIdentifierHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,12 @@ namespace AuthJanitor.Providers
|
|||
/// RegeneratedSecrets entering an ApplicationLifecycleProvider
|
||||
/// </summary>
|
||||
public string UserHint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a HashCode for this configuration based on common resources.
|
||||
/// This is used to group items for parallelization and unified commits.
|
||||
/// For example, this might be a ResourceGroup/ResourceName.
|
||||
/// </summary>
|
||||
public abstract int GenerateResourceIdentifierHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface IAuthJanitorCapability : IAuthJanitorProvider { }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanCleanup : IAuthJanitorCapability
|
||||
{
|
||||
Task Cleanup();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanDistributeTemporarySecretValues : IAuthJanitorCapability
|
||||
{
|
||||
Task DistributeTemporarySecretValues(List<RegeneratedSecret> secretValues);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Providers;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanEnumerateResourceCandidates : IAuthJanitorCapability
|
||||
{
|
||||
Task<List<AuthJanitorProviderConfiguration>> EnumerateResourceCandidates(AuthJanitorProviderConfiguration baseConfig);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanGenerateTemporarySecretValue : IAuthJanitorCapability
|
||||
{
|
||||
Task<RegeneratedSecret> GenerateTemporarySecretValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanPerformUnifiedCommit : IAuthJanitorCapability
|
||||
{
|
||||
Task UnifiedCommit();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanPerformUnifiedCommitForTemporarySecretValues : IAuthJanitorCapability
|
||||
{
|
||||
Task UnifiedCommitForTemporarySecretValues();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Capabilities
|
||||
{
|
||||
public interface ICanRunSanityTests : IAuthJanitorCapability
|
||||
{
|
||||
Task Test();
|
||||
}
|
||||
}
|
|
@ -37,11 +37,6 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
public ProviderAttribute Details { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provider SVG logo image
|
||||
/// </summary>
|
||||
public string SvgImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NET Assembly where the Provider was loaded from
|
||||
/// </summary>
|
||||
|
|
|
@ -4,17 +4,6 @@ using System;
|
|||
|
||||
namespace AuthJanitor.Providers
|
||||
{
|
||||
[Flags]
|
||||
public enum ProviderFeatureFlags : int
|
||||
{
|
||||
None = 0b00000000,
|
||||
|
||||
IsTestable = 0b00000010,
|
||||
CanRotateWithoutDowntime = 0b00000100,
|
||||
SupportsSecondaryKey = 0b00001000,
|
||||
HasCandidateSelection = 0b00010000
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class ProviderAttribute : Attribute
|
||||
{
|
||||
|
@ -28,21 +17,9 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Features supported by this Provider
|
||||
/// </summary>
|
||||
public ProviderFeatureFlags Features { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class ProviderImageAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// SVG logo image for Provider
|
||||
/// </summary>
|
||||
public string SvgImage { get; set; }
|
||||
|
||||
public ProviderImageAttribute(string svgImage) =>
|
||||
SvgImage = svgImage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
@ -13,9 +14,9 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace AuthJanitor.Providers
|
||||
{
|
||||
|
||||
public class ProviderManagerService
|
||||
{
|
||||
private const int DELAY_BETWEEN_ACTIONS_MS = 1000;
|
||||
public static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions()
|
||||
{
|
||||
WriteIndented = false,
|
||||
|
@ -40,8 +41,7 @@ namespace AuthJanitor.Providers
|
|||
ProviderTypeName = type.AssemblyQualifiedName,
|
||||
ProviderType = type,
|
||||
ProviderConfigurationType = type.BaseType.GetGenericArguments()[0],
|
||||
Details = type.GetCustomAttribute<ProviderAttribute>(),
|
||||
SvgImage = type.GetCustomAttribute<ProviderImageAttribute>()?.SvgImage
|
||||
Details = type.GetCustomAttribute<ProviderAttribute>()
|
||||
})
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
|
@ -97,63 +97,72 @@ namespace AuthJanitor.Providers
|
|||
IEnumerable<IAuthJanitorProvider> providers)
|
||||
{
|
||||
logger.LogInformation("########## BEGIN REKEYING WORKFLOW ##########");
|
||||
var rkoProviders = providers.OfType<IRekeyableObjectProvider>().ToList();
|
||||
var alcProviders = providers.OfType<IApplicationLifecycleProvider>().ToList();
|
||||
|
||||
// NOTE: avoid costs of generating list of providers if information logging not turned on
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
logger.LogInformation("RKO: {ProviderTypeNames}", string.Join(", ", rkoProviders.Select(p => p.GetType().Name)));
|
||||
logger.LogInformation("ALC: {ProviderTypeNames}", string.Join(", ", alcProviders.Select(p => p.GetType().Name)));
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Performing Provider Tests.");
|
||||
logger.LogInformation("### Performing provider tests...");
|
||||
|
||||
await PerformProviderActions(
|
||||
logger,
|
||||
providers,
|
||||
await PerformActionsInParallel(
|
||||
logger,
|
||||
providers.OfType<ICanRunSanityTests>(),
|
||||
p => p.Test(),
|
||||
"Error running sanity test on provider '{ProviderName}'",
|
||||
"Error running one or more sanity tests!");
|
||||
|
||||
logger.LogInformation("### Retrieving/generating temporary secrets.");
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Retrieving/generating temporary secrets...");
|
||||
|
||||
var temporarySecrets = new List<RegeneratedSecret>();
|
||||
await PerformProviderActions(
|
||||
await PerformActionsInParallel(
|
||||
logger,
|
||||
rkoProviders,
|
||||
p => p.GetSecretToUseDuringRekeying()
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.Result != null)
|
||||
{
|
||||
temporarySecrets.Add(t.Result);
|
||||
}
|
||||
}),
|
||||
providers.OfType<ICanGenerateTemporarySecretValue>(),
|
||||
p => p.GenerateTemporarySecretValue()
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.Result != null)
|
||||
{
|
||||
temporarySecrets.Add(t.Result);
|
||||
}
|
||||
}),
|
||||
"Error getting temporary secret from provider '{ProviderName}'",
|
||||
"Error retrieving temporary secrets from one or more Rekeyable Object Providers!");
|
||||
|
||||
logger.LogInformation("{SecretCount} temporary secrets were created/read to be used during operation.", temporarySecrets.Count);
|
||||
|
||||
// ---
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Preparing {ProviderCount} Application Lifecycle Providers for rekeying...", alcProviders.Count);
|
||||
await PerformProviderActions(
|
||||
logger.LogInformation("### Distributing temporary secrets...");
|
||||
|
||||
await PerformActionsInParallelGroups(
|
||||
logger,
|
||||
alcProviders,
|
||||
p => p.BeforeRekeying(temporarySecrets),
|
||||
"Error preparing ALC provider '{ProviderName}'",
|
||||
"Error preparing one or more Application Lifecycle Providers for rekeying!");
|
||||
providers.OfType<ICanDistributeTemporarySecretValues>()
|
||||
.GroupBy(p => p.GenerateResourceIdentifierHashCode()),
|
||||
p => p.DistributeTemporarySecretValues(temporarySecrets),
|
||||
"Error distributing secrets to ALC provider '{ProviderName}'",
|
||||
"Error distributing secrets!");
|
||||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Rekeying {ProviderCount} Rekeyable Object Providers...", rkoProviders.Count);
|
||||
var newSecrets = new List<RegeneratedSecret>();
|
||||
await PerformProviderActions(
|
||||
logger.LogInformation("### Performing commits for temporary secrets...");
|
||||
|
||||
await PerformActionsInParallel(
|
||||
logger,
|
||||
rkoProviders,
|
||||
providers.OfType<ICanPerformUnifiedCommitForTemporarySecretValues>()
|
||||
.GroupBy(p => p.GenerateResourceIdentifierHashCode())
|
||||
.Select(g => g.First()),
|
||||
p => p.UnifiedCommitForTemporarySecretValues(),
|
||||
"Error committing temporary secrets for ALC provider '{ProviderName}'",
|
||||
"Error committing temporary secrets!");
|
||||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Rekeying objects and services...");
|
||||
|
||||
var newSecrets = new List<RegeneratedSecret>();
|
||||
await PerformActionsInParallel(
|
||||
logger,
|
||||
providers.OfType<IRekeyableObjectProvider>(),
|
||||
p => p.Rekey(validPeriod)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
|
@ -169,43 +178,104 @@ namespace AuthJanitor.Providers
|
|||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Committing {SecretCount} regenerated secrets to {ProviderCount} Application Lifecycle Providers...",
|
||||
newSecrets.Count,
|
||||
alcProviders.Count);
|
||||
logger.LogInformation("### Distributing regenerated secrets...");
|
||||
|
||||
await PerformProviderActions(
|
||||
await PerformActionsInParallelGroups(
|
||||
logger,
|
||||
alcProviders,
|
||||
p => p.CommitNewSecrets(newSecrets),
|
||||
providers.OfType<IApplicationLifecycleProvider>()
|
||||
.GroupBy(p => p.GenerateResourceIdentifierHashCode()),
|
||||
p => p.DistributeLongTermSecretValues(newSecrets),
|
||||
"Error committing to provider '{ProviderName}'",
|
||||
"Error committing regenerated secrets!");
|
||||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Completing post-rekey operations on Application Lifecycle Providers...");
|
||||
|
||||
await PerformProviderActions(
|
||||
logger,
|
||||
alcProviders,
|
||||
p => p.AfterRekeying(),
|
||||
"Error running post-rekey operations on provider '{ProviderName}'",
|
||||
"Error running post-rekey operations on one or more Application Lifecycle Providers!");
|
||||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Completing finalizing operations on Rekeyable Object Providers...");
|
||||
logger.LogInformation("### Performing commits...");
|
||||
|
||||
await PerformProviderActions(
|
||||
await PerformActionsInParallel(
|
||||
logger,
|
||||
rkoProviders,
|
||||
p => p.OnConsumingApplicationSwapped(),
|
||||
"Error running after-swap operations on provider '{ProviderName}'",
|
||||
"Error running after-swap operations on one or more Rekeyable Object Providers!");
|
||||
providers.OfType<ICanPerformUnifiedCommit>()
|
||||
.GroupBy(p => p.GenerateResourceIdentifierHashCode())
|
||||
.Select(g => g.First()),
|
||||
p => p.UnifiedCommit(),
|
||||
"Error committing secrets for ALC provider '{ProviderName}'",
|
||||
"Error committing secrets!");
|
||||
|
||||
// -----
|
||||
|
||||
logger.LogInformation("### Running cleanup operations...");
|
||||
|
||||
await PerformActionsInParallelGroups(
|
||||
logger,
|
||||
providers.OfType<ICanCleanup>()
|
||||
.GroupBy(p => p.GenerateResourceIdentifierHashCode()),
|
||||
p => p.Cleanup(),
|
||||
"Error cleaning up provider '{ProviderName}'",
|
||||
"Error cleaning up!");
|
||||
|
||||
logger.LogInformation("########## END REKEYING WORKFLOW ##########");
|
||||
|
||||
logger.IsComplete = true;
|
||||
}
|
||||
|
||||
private static async Task PerformProviderActions<TProviderType>(
|
||||
private static async Task PerformActionsInSerial<TProviderType>(
|
||||
ILogger logger,
|
||||
IEnumerable<TProviderType> providers,
|
||||
Func<TProviderType, Task> providerAction,
|
||||
string individualFailureErrorLogMessageTemplate)
|
||||
where TProviderType : IAuthJanitorProvider
|
||||
{
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Running action in serial on {0}", provider.GetType().Name);
|
||||
await providerAction(provider);
|
||||
await Task.Delay(DELAY_BETWEEN_ACTIONS_MS / 2);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError(exception, individualFailureErrorLogMessageTemplate, provider.GetType().Name);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task PerformActionsInParallelGroups<TProviderType>(
|
||||
ILogger logger,
|
||||
IEnumerable<IGrouping<int, TProviderType>> providers,
|
||||
Func<TProviderType, Task> providerAction,
|
||||
string individualFailureErrorLogMessageTemplate,
|
||||
string anyFailureExceptionMessage)
|
||||
where TProviderType : IAuthJanitorProvider
|
||||
{
|
||||
var providerActions = providers.Select(async p =>
|
||||
{
|
||||
await PerformActionsInSerial(
|
||||
logger,
|
||||
p,
|
||||
providerAction,
|
||||
individualFailureErrorLogMessageTemplate);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(providerActions);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new Exception(anyFailureExceptionMessage, exception);
|
||||
}
|
||||
|
||||
if (providers.Any())
|
||||
{
|
||||
logger.LogInformation("Sleeping for {SleepTime}", DELAY_BETWEEN_ACTIONS_MS);
|
||||
await Task.Delay(DELAY_BETWEEN_ACTIONS_MS);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task PerformActionsInParallel<TProviderType>(
|
||||
ILogger logger,
|
||||
IEnumerable<TProviderType> providers,
|
||||
Func<TProviderType, Task> providerAction,
|
||||
|
@ -217,6 +287,7 @@ namespace AuthJanitor.Providers
|
|||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Running action in parallel on {0}", p.GetType().Name);
|
||||
await providerAction(p);
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
@ -235,6 +306,12 @@ namespace AuthJanitor.Providers
|
|||
{
|
||||
throw new Exception(anyFailureExceptionMessage, exception);
|
||||
}
|
||||
|
||||
if (providers.Any())
|
||||
{
|
||||
logger.LogInformation("Sleeping for {SleepTime}", DELAY_BETWEEN_ACTIONS_MS);
|
||||
await Task.Delay(DELAY_BETWEEN_ACTIONS_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,25 +7,12 @@ namespace AuthJanitor.Providers
|
|||
{
|
||||
public interface IRekeyableObjectProvider : IAuthJanitorProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Call before Rekeying occurs to get a secondary secret which will continue
|
||||
/// to work while Rekeying is taking place (if any).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<RegeneratedSecret> GetSecretToUseDuringRekeying();
|
||||
|
||||
/// <summary>
|
||||
/// Call when ready to rekey a given RekeyableService.
|
||||
/// </summary>
|
||||
/// <param name="requestedValidPeriod">Requested period of validity for new key/secret</param>
|
||||
/// <returns></returns>
|
||||
Task<RegeneratedSecret> Rekey(TimeSpan requestedValidPeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Call when the ConsumingApplication has been moved to the RegeneratedKey (from Rekey())
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task OnConsumingApplicationSwapped();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -33,30 +20,11 @@ namespace AuthJanitor.Providers
|
|||
/// </summary>
|
||||
public abstract class RekeyableObjectProvider<TConfiguration> : AuthJanitorProvider<TConfiguration>, IRekeyableObjectProvider where TConfiguration : AuthJanitorProviderConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Call before Rekeying occurs to get a secondary secret which will continue
|
||||
/// to work while Rekeying is taking place (if any).
|
||||
/// </summary>
|
||||
public virtual async Task<RegeneratedSecret> GetSecretToUseDuringRekeying()
|
||||
{
|
||||
await Task.Yield();
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call when ready to rekey a given RekeyableService.
|
||||
/// </summary>
|
||||
/// <param name="requestedValidPeriod">Requested period of validity for new key/secret</param>
|
||||
/// <returns></returns>
|
||||
public abstract Task<RegeneratedSecret> Rekey(TimeSpan requestedValidPeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Call when the ConsumingApplication has been moved to the RegeneratedKey (from Rekey())
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual Task OnConsumingApplicationSwapped()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace AuthJanitor.Providers
|
|||
public class RekeyingAttemptLogger : ILogger
|
||||
{
|
||||
public bool IsSuccessfulAttempt => string.IsNullOrEmpty(OuterException);
|
||||
public bool IsComplete { get; set; }
|
||||
public string OuterException { get; set; }
|
||||
|
||||
public DateTimeOffset AttemptStarted { get; set; }
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AuthJanitor.AspNet\AuthJanitor.AspNet.Core.csproj" />
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Functions
|
||||
|
@ -24,10 +25,12 @@ namespace AuthJanitor.Functions
|
|||
}
|
||||
|
||||
[FunctionName("AccessManagement-Add")]
|
||||
public async Task<IActionResult> Add([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "access")] AuthJanitorAuthorizedUser newAuthorizedUserRole)
|
||||
public async Task<IActionResult> Add([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "access")] string userJson) //AuthJanitorAuthorizedUser newAuthorizedUserRole)
|
||||
{
|
||||
if (!_identityService.CurrentUserHasRole(AuthJanitorRoles.GlobalAdmin)) return new UnauthorizedResult();
|
||||
|
||||
var newAuthorizedUserRole = JsonConvert.DeserializeObject<AuthJanitorAuthorizedUser>(userJson);
|
||||
|
||||
return await _managementService.AddAuthorizedUser(newAuthorizedUserRole.UPN, newAuthorizedUserRole.RoleValue);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -26,8 +27,10 @@ namespace AuthJanitor.Functions
|
|||
}
|
||||
|
||||
[FunctionName("ManagedSecrets-Create")]
|
||||
public async Task<IActionResult> Create([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "managedSecrets")] ManagedSecretViewModel inputSecret, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Create([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "managedSecrets")] string secretJson, //ManagedSecretViewModel inputSecret,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputSecret = JsonConvert.DeserializeObject<ManagedSecretViewModel>(secretJson);
|
||||
return await _service.Create(inputSecret, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -53,9 +56,10 @@ namespace AuthJanitor.Functions
|
|||
|
||||
[FunctionName("ManagedSecrets-Update")]
|
||||
public async Task<IActionResult> Update(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "managedSecrets/{secretId}")] ManagedSecretViewModel inputSecret,
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "managedSecrets/{secretId}")] string secretJson, //ManagedSecretViewModel inputSecret,
|
||||
string secretId, CancellationToken cancellationToken)
|
||||
{
|
||||
var inputSecret = JsonConvert.DeserializeObject<ManagedSecretViewModel>(secretJson);
|
||||
return await _service.Update(inputSecret, Guid.Parse(secretId), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +1,70 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Services;
|
||||
using AuthJanitor.UI.Shared.ViewModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using System;
|
||||
using AuthJanitor.UI.Shared.ViewModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Functions
|
||||
{
|
||||
/// <summary>
|
||||
/// API functions to control the creation and management of AuthJanitor Resources.
|
||||
/// A Resource is the description of how to connect to an object or resource, using a given Provider.
|
||||
/// </summary>
|
||||
public class Resources
|
||||
{
|
||||
private readonly ResourcesService _service;
|
||||
|
||||
public Resources(ResourcesService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Create")]
|
||||
public async Task<IActionResult> Create(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "resources")] ResourceViewModel resource,
|
||||
HttpRequest req, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.Create(resource, req, cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-List")]
|
||||
public async Task<IActionResult> List([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "resources")] HttpRequest req, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.List(req, cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Get")]
|
||||
public async Task<IActionResult> Get(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "resources/{resourceId}")] HttpRequest req,
|
||||
string resourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.Get(req, Guid.Parse(resourceId), cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Delete")]
|
||||
public async Task<IActionResult> Delete(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "resources/{resourceId}")] HttpRequest req,
|
||||
string resourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.Delete(req, Guid.Parse(resourceId), cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Update")]
|
||||
public async Task<IActionResult> Update(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "resources/{resourceId}")] ResourceViewModel resource,
|
||||
HttpRequest req,
|
||||
string resourceId, CancellationToken cancellationToken)
|
||||
namespace AuthJanitor.Functions
|
||||
{
|
||||
/// <summary>
|
||||
/// API functions to control the creation and management of AuthJanitor Resources.
|
||||
/// A Resource is the description of how to connect to an object or resource, using a given Provider.
|
||||
/// </summary>
|
||||
public class Resources
|
||||
{
|
||||
private readonly ResourcesService _service;
|
||||
|
||||
public Resources(ResourcesService service)
|
||||
{
|
||||
return await _service.Update(resource, req, Guid.Parse(resourceId), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
_service = service;
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Create")]
|
||||
public async Task<IActionResult> Create(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "resources")] string resourceJson, /*ResourceViewModel resource,*/
|
||||
HttpRequest req, CancellationToken cancellationToken)
|
||||
{
|
||||
var resource = JsonConvert.DeserializeObject<ResourceViewModel>(resourceJson);
|
||||
return await _service.Create(resource, req, cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-List")]
|
||||
public async Task<IActionResult> List([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "resources")] HttpRequest req, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.List(req, cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Get")]
|
||||
public async Task<IActionResult> Get(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "resources/{resourceId}")] HttpRequest req,
|
||||
string resourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.Get(req, Guid.Parse(resourceId), cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Delete")]
|
||||
public async Task<IActionResult> Delete(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "resources/{resourceId}")] HttpRequest req,
|
||||
string resourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _service.Delete(req, Guid.Parse(resourceId), cancellationToken);
|
||||
}
|
||||
|
||||
[FunctionName("Resources-Update")]
|
||||
public async Task<IActionResult> Update(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "resources/{resourceId}")] string resourceJson, //ResourceViewModel resource,
|
||||
HttpRequest req,
|
||||
string resourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var resource = JsonConvert.DeserializeObject<ResourceViewModel>(resourceJson);
|
||||
return await _service.Update(resource, req, Guid.Parse(resourceId), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
using AuthJanitor.UI.Shared;
|
||||
using AuthJanitor.UI.Shared.MetaServices;
|
||||
using AuthJanitor.UI.Shared.ViewModels;
|
||||
using AuthJanitor.EventSinks;
|
||||
using AuthJanitor.IdentityServices;
|
||||
using AuthJanitor.EventSinks;
|
||||
using AuthJanitor.IdentityServices;
|
||||
using AuthJanitor.Providers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -14,6 +14,7 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
|
||||
namespace AuthJanitor.Services
|
||||
{
|
||||
|
@ -112,18 +113,23 @@ namespace AuthJanitor.Services
|
|||
return new NotFoundResult();
|
||||
}
|
||||
|
||||
try
|
||||
if (typeof(ICanRunSanityTests).IsAssignableFrom(provider.ProviderType))
|
||||
{
|
||||
var instance = _providerManager.GetProviderInstance(provider.ProviderTypeName, providerConfiguration);
|
||||
if (instance == null)
|
||||
return new BadRequestErrorMessageResult("Provider configuration is invalid!");
|
||||
instance.Credential = credential;
|
||||
await instance.Test();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BadRequestErrorMessageResult(ex.Message);
|
||||
try
|
||||
{
|
||||
var instance = _providerManager.GetProviderInstance(provider.ProviderTypeName, providerConfiguration);
|
||||
if (instance == null)
|
||||
return new BadRequestErrorMessageResult("Provider configuration is invalid!");
|
||||
instance.Credential = credential;
|
||||
await (instance as ICanRunSanityTests).Test();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BadRequestErrorMessageResult(ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
return new BadRequestErrorMessageResult("Provider does not support testing!");
|
||||
|
||||
return new OkResult();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"version": "2.0",
|
||||
"functionTimeout": "00:10:00",
|
||||
"extensions": {
|
||||
"http": {
|
||||
"routePrefix": "api",
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.6" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AuthJanitor.AspNet\AuthJanitor.AspNet.Core.csproj" />
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.2" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.4" />
|
||||
<PackageReference Include="SendGrid" Version="9.15.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
|
||||
<PackageReference Include="SendGrid" Version="9.18.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AuthJanitor.Core\AuthJanitor.Core.csproj" />
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="3.8.0" />
|
||||
<PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.5" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AuthJanitor.Core\AuthJanitor.Core.csproj" />
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.AppService.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.AppService.Fluent" Version="1.34.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -16,9 +16,7 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
/// </summary>
|
||||
[Provider(Name = "Functions App - AppSettings",
|
||||
Description = "Manages the lifecycle of an Azure Functions app which reads a Managed Secret from its Application Settings",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.FUNCTIONS_SVG)]
|
||||
SvgImage = ProviderImages.FUNCTIONS_SVG)]
|
||||
public class AppSettingsFunctionsApplicationLifecycleProvider : SlottableAzureApplicationLifecycleProvider<AppSettingConfiguration, IFunctionApp>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
@ -27,7 +25,7 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
protected override async Task ApplyUpdate(IFunctionApp resource, string slotName, List<RegeneratedSecret> secrets)
|
||||
{
|
||||
var updateBase = (await resource.DeploymentSlots.GetByNameAsync(slotName)).Update();
|
||||
|
@ -49,9 +47,14 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
$"Functions application called {Configuration.ResourceName} (Resource Group " +
|
||||
$"'{Configuration.ResourceGroup}'). During the rekeying, the Functions App will " +
|
||||
$"be moved from slot '{Configuration.SourceSlot}' to slot '{Configuration.TemporarySlot}' " +
|
||||
$"temporarily, and then to slot '{Configuration.DestinationSlot}'.";
|
||||
$"temporarily, and then back.";
|
||||
|
||||
protected override Task SwapSlotAsync(IFunctionApp resource, string slotName) => resource.SwapAsync(slotName);
|
||||
protected override async Task SwapSlotAsync(IFunctionApp resource, string sourceSlotName) =>
|
||||
await resource.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override async Task SwapSlotAsync(IFunctionApp resource, string sourceSlotName, string destinationSlotName) =>
|
||||
await (await resource.DeploymentSlots.GetByNameAsync(destinationSlotName))
|
||||
.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override ISupportsGettingByResourceGroup<IFunctionApp> GetResourceCollection(IAzure azure) => azure.AppServices.FunctionApps;
|
||||
|
||||
|
|
|
@ -13,9 +13,7 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
{
|
||||
[Provider(Name = "Functions App - Connection String",
|
||||
Description = "Manages the lifecycle of an Azure Functions app which reads from a Connection String",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.FUNCTIONS_SVG)]
|
||||
SvgImage = ProviderImages.FUNCTIONS_SVG)]
|
||||
public class ConnectionStringFunctionsApplicationLifecycleProvider : SlottableAzureApplicationLifecycleProvider<ConnectionStringConfiguration, IFunctionApp>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
@ -40,7 +38,12 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
await updateBase.ApplyAsync();
|
||||
}
|
||||
|
||||
protected override Task SwapSlotAsync(IFunctionApp resource, string slotName) => resource.SwapAsync(slotName);
|
||||
protected override async Task SwapSlotAsync(IFunctionApp resource, string sourceSlotName) =>
|
||||
await resource.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override async Task SwapSlotAsync(IFunctionApp resource, string sourceSlotName, string destinationSlotName) =>
|
||||
await (await resource.DeploymentSlots.GetByNameAsync(destinationSlotName))
|
||||
.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override ISupportsGettingByResourceGroup<IFunctionApp> GetResourceCollection(IAzure azure) => azure.AppServices.FunctionApps;
|
||||
|
||||
|
@ -56,6 +59,6 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
$"Functions application called {Configuration.ResourceName} (Resource Group " +
|
||||
$"'{Configuration.ResourceGroup}'). During the rekeying, the Functions App will " +
|
||||
$"be moved from slot '{Configuration.SourceSlot}' to slot '{Configuration.TemporarySlot}' " +
|
||||
$"temporarily, and then to slot '{Configuration.DestinationSlot}'.";
|
||||
$"temporarily, and then back.";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Integrations.CryptographicImplementations;
|
||||
using AuthJanitor.Providers.Azure;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Microsoft.Azure.Management.AppService.Fluent;
|
||||
using Microsoft.Azure.Management.Fluent;
|
||||
using Microsoft.Azure.Management.ResourceManager.Fluent.Core.CollectionActions;
|
||||
|
@ -13,9 +14,10 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
{
|
||||
[Provider(Name = "Functions App Key",
|
||||
Description = "Regenerates a Function Key for an Azure Functions application",
|
||||
Features = ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.FUNCTIONS_SVG)]
|
||||
public class FunctionKeyRekeyableObjectProvider : AzureRekeyableObjectProvider<FunctionKeyConfiguration, IFunctionApp>
|
||||
SvgImage = ProviderImages.FUNCTIONS_SVG)]
|
||||
public class FunctionKeyRekeyableObjectProvider :
|
||||
AzureRekeyableObjectProvider<FunctionKeyConfiguration, IFunctionApp>,
|
||||
ICanRunSanityTests
|
||||
{
|
||||
private readonly ICryptographicImplementation _cryptographicImplementation;
|
||||
private readonly ILogger _logger;
|
||||
|
@ -28,7 +30,7 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
_cryptographicImplementation = cryptographicImplementation;
|
||||
}
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var functionsApp = await GetResourceAsync();
|
||||
if (functionsApp == null)
|
||||
|
@ -66,10 +68,6 @@ namespace AuthJanitor.Providers.AppServices.Functions
|
|||
$"Functions application called {Configuration.ResourceName} (Resource Group " +
|
||||
$"'{Configuration.ResourceGroup}').";
|
||||
|
||||
public override Task<RegeneratedSecret> GetSecretToUseDuringRekeying() => Task.FromResult<RegeneratedSecret>(null);
|
||||
|
||||
public override Task OnConsumingApplicationSwapped() => Task.FromResult(0);
|
||||
|
||||
protected override ISupportsGettingByResourceGroup<IFunctionApp> GetResourceCollection(IAzure azure) => azure.AppServices.FunctionApps;
|
||||
|
||||
// TODO: Zero-downtime rotation here with similar slotting?
|
||||
|
|
|
@ -12,10 +12,9 @@ namespace AuthJanitor.Providers.AppServices.WebApps
|
|||
{
|
||||
[Provider(Name = "WebApp - AppSettings",
|
||||
Description = "Manages the lifecycle of an Azure Web App which reads a Managed Secret from its Application Settings",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.WEBAPPS_SVG)]
|
||||
public class AppSettingsWebAppApplicationLifecycleProvider : SlottableAzureApplicationLifecycleProvider<AppSettingConfiguration, IWebApp>
|
||||
SvgImage = ProviderImages.WEBAPPS_SVG)]
|
||||
public class AppSettingsWebAppApplicationLifecycleProvider :
|
||||
SlottableAzureApplicationLifecycleProvider<AppSettingConfiguration, IWebApp>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
@ -40,7 +39,12 @@ namespace AuthJanitor.Providers.AppServices.WebApps
|
|||
await updateBase.ApplyAsync();
|
||||
}
|
||||
|
||||
protected override Task SwapSlotAsync(IWebApp resource, string slotName) => resource.SwapAsync(slotName);
|
||||
protected override async Task SwapSlotAsync(IWebApp resource, string sourceSlotName) =>
|
||||
await resource.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override async Task SwapSlotAsync(IWebApp resource, string sourceSlotName, string destinationSlotName) =>
|
||||
await (await resource.DeploymentSlots.GetByNameAsync(destinationSlotName))
|
||||
.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override ISupportsGettingByResourceGroup<IWebApp> GetResourceCollection(IAzure azure) => azure.AppServices.WebApps;
|
||||
|
||||
|
@ -55,6 +59,6 @@ namespace AuthJanitor.Providers.AppServices.WebApps
|
|||
$"Web Application called {Configuration.ResourceName} (Resource Group " +
|
||||
$"'{Configuration.ResourceGroup}'). During the rekeying, the Functions App will " +
|
||||
$"be moved from slot '{Configuration.SourceSlot}' to slot '{Configuration.TemporarySlot}' " +
|
||||
$"temporarily, and then to slot '{Configuration.DestinationSlot}'.";
|
||||
$"temporarily, and then back.";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,9 @@ namespace AuthJanitor.Providers.AppServices.WebApps
|
|||
{
|
||||
[Provider(Name = "WebApp - Connection String",
|
||||
Description = "Manages the lifecycle of an Azure Web App which reads from a Connection String",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.WEBAPPS_SVG)]
|
||||
public class ConnectionStringWebAppApplicationLifecycleProvider : SlottableAzureApplicationLifecycleProvider<ConnectionStringConfiguration, IWebApp>
|
||||
SvgImage = ProviderImages.WEBAPPS_SVG)]
|
||||
public class ConnectionStringWebAppApplicationLifecycleProvider :
|
||||
SlottableAzureApplicationLifecycleProvider<ConnectionStringConfiguration, IWebApp>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
@ -38,7 +37,12 @@ namespace AuthJanitor.Providers.AppServices.WebApps
|
|||
await updateBase.ApplyAsync();
|
||||
}
|
||||
|
||||
protected override Task SwapSlotAsync(IWebApp resource, string slotName) => resource.SwapAsync(slotName);
|
||||
protected override async Task SwapSlotAsync(IWebApp resource, string sourceSlotName) =>
|
||||
await resource.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override async Task SwapSlotAsync(IWebApp resource, string sourceSlotName, string destinationSlotName) =>
|
||||
await (await resource.DeploymentSlots.GetByNameAsync(destinationSlotName))
|
||||
.SwapAsync(sourceSlotName);
|
||||
|
||||
protected override ISupportsGettingByResourceGroup<IWebApp> GetResourceCollection(IAzure azure) => azure.AppServices.WebApps;
|
||||
|
||||
|
@ -54,6 +58,6 @@ namespace AuthJanitor.Providers.AppServices.WebApps
|
|||
$"Web Application called {Configuration.ResourceName} (Resource Group " +
|
||||
$"'{Configuration.ResourceGroup}'). During the rekeying, the Functions App will " +
|
||||
$"be moved from slot '{Configuration.SourceSlot}' to slot '{Configuration.TemporarySlot}' " +
|
||||
$"temporarily, and then to slot '{Configuration.DestinationSlot}'.";
|
||||
$"temporarily, and then back.";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.34.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -5,11 +5,10 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace AuthJanitor.Providers.Azure
|
||||
{
|
||||
public abstract class AzureApplicationLifecycleProvider<TConfiguration, TResource> : AzureAuthJanitorProvider<TConfiguration, TResource>, IApplicationLifecycleProvider
|
||||
public abstract class AzureApplicationLifecycleProvider<TConfiguration, TResource> :
|
||||
AzureAuthJanitorProvider<TConfiguration, TResource>, IApplicationLifecycleProvider
|
||||
where TConfiguration : AzureAuthJanitorProviderConfiguration
|
||||
{
|
||||
public abstract Task AfterRekeying();
|
||||
public abstract Task BeforeRekeying(List<RegeneratedSecret> temporaryUseSecrets);
|
||||
public abstract Task CommitNewSecrets(List<RegeneratedSecret> newSecrets);
|
||||
public abstract Task DistributeLongTermSecretValues(List<RegeneratedSecret> newSecretValues);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AuthJanitor.Providers.Azure
|
||||
|
@ -15,5 +16,8 @@ namespace AuthJanitor.Providers.Azure
|
|||
[DisplayName("Subscription ID")]
|
||||
[Description("If this is left blank, the default subscription will be used.")]
|
||||
public string SubscriptionId { get; set; }
|
||||
|
||||
public override int GenerateResourceIdentifierHashCode() =>
|
||||
HashCode.Combine(SubscriptionId, ResourceGroup, ResourceName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ namespace AuthJanitor.Providers.Azure
|
|||
public abstract class AzureRekeyableObjectProvider<TConfiguration, TResource> : AzureAuthJanitorProvider<TConfiguration, TResource>, IRekeyableObjectProvider
|
||||
where TConfiguration : AzureAuthJanitorProviderConfiguration
|
||||
{
|
||||
public abstract Task<RegeneratedSecret> GetSecretToUseDuringRekeying();
|
||||
public abstract Task OnConsumingApplicationSwapped();
|
||||
public abstract Task<RegeneratedSecret> Rekey(TimeSpan requestedValidPeriod);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AuthJanitor.Providers.Azure.Workflows
|
||||
{
|
||||
public abstract class SlottableAzureApplicationLifecycleProvider<TConfiguration, TResource> : AzureApplicationLifecycleProvider<TConfiguration, TResource>
|
||||
public abstract class SlottableAzureApplicationLifecycleProvider<TConfiguration, TResource> :
|
||||
AzureApplicationLifecycleProvider<TConfiguration, TResource>,
|
||||
ICanRunSanityTests,
|
||||
ICanDistributeTemporarySecretValues,
|
||||
ICanPerformUnifiedCommitForTemporarySecretValues,
|
||||
ICanPerformUnifiedCommit
|
||||
where TConfiguration : SlottableAzureAuthJanitorProviderConfiguration
|
||||
{
|
||||
private const string PRODUCTION_SLOT_NAME = "production";
|
||||
protected const string PRODUCTION_SLOT_NAME = "production";
|
||||
private readonly ILogger _logger;
|
||||
protected SlottableAzureApplicationLifecycleProvider(ILogger logger) => _logger = logger;
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var resource = await GetResourceAsync();
|
||||
if (Configuration.SourceSlot != PRODUCTION_SLOT_NAME)
|
||||
|
@ -24,36 +30,41 @@ namespace AuthJanitor.Providers.Azure.Workflows
|
|||
await TestSlotAsync(resource, Configuration.DestinationSlot);
|
||||
}
|
||||
|
||||
public override async Task AfterRekeying()
|
||||
public async Task DistributeTemporarySecretValues(List<RegeneratedSecret> secretValues)
|
||||
{
|
||||
_logger.LogInformation("Swapping to '{SlotName}'", Configuration.TemporarySlot);
|
||||
await SwapSlotAsync(await GetResourceAsync(), Configuration.TemporarySlot);
|
||||
_logger.LogInformation("Swap complete!");
|
||||
var resource = await GetResourceAsync();
|
||||
await ApplyUpdate(resource, Configuration.TemporarySlot, secretValues);
|
||||
}
|
||||
|
||||
public override async Task BeforeRekeying(List<RegeneratedSecret> temporaryUseSecrets)
|
||||
public async Task UnifiedCommitForTemporarySecretValues()
|
||||
{
|
||||
await ApplySecretSwap(temporaryUseSecrets);
|
||||
_logger.LogInformation("BeforeRekeying completed!");
|
||||
_logger.LogInformation("Swapping settings from {TemporarySlot} to {SourceSlot}",
|
||||
Configuration.TemporarySlot,
|
||||
Configuration.SourceSlot);
|
||||
var resource = await GetResourceAsync();
|
||||
await SwapSlotAsync(resource, Configuration.TemporarySlot);
|
||||
}
|
||||
|
||||
public override async Task CommitNewSecrets(List<RegeneratedSecret> newSecrets)
|
||||
public override async Task DistributeLongTermSecretValues(List<RegeneratedSecret> secretValues)
|
||||
{
|
||||
await ApplySecretSwap(newSecrets);
|
||||
_logger.LogInformation("CommitNewSecrets completed!");
|
||||
var resource = await GetResourceAsync();
|
||||
await ApplyUpdate(resource, Configuration.TemporarySlot, secretValues);
|
||||
}
|
||||
|
||||
public async Task UnifiedCommit()
|
||||
{
|
||||
_logger.LogInformation("Swapping from {SourceSlot} to {DestinationSlot}",
|
||||
Configuration.TemporarySlot,
|
||||
Configuration.DestinationSlot);
|
||||
var resource = await GetResourceAsync();
|
||||
await SwapSlotAsync(resource, Configuration.TemporarySlot);
|
||||
}
|
||||
|
||||
protected abstract Task ApplyUpdate(TResource resource, string slotName, List<RegeneratedSecret> secrets);
|
||||
protected abstract Task SwapSlotAsync(TResource resource, string slotName);
|
||||
// applies to slot
|
||||
protected abstract Task SwapSlotAsync(TResource resource, string sourceSlotName, string destinationSlotName);
|
||||
// applies to prod
|
||||
protected abstract Task SwapSlotAsync(TResource resource, string sourceSlotName);
|
||||
protected abstract Task TestSlotAsync(TResource resource, string slotName);
|
||||
|
||||
private async Task ApplySecretSwap(List<RegeneratedSecret> secrets)
|
||||
{
|
||||
var resource = await GetResourceAsync();
|
||||
await ApplyUpdate(resource, Configuration.TemporarySlot, secrets);
|
||||
|
||||
_logger.LogInformation("Swapping to '{SlotName}'", Configuration.TemporarySlot);
|
||||
await SwapSlotAsync(resource, Configuration.TemporarySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ namespace AuthJanitor.Providers.Azure.Workflows
|
|||
{
|
||||
public const string DEFAULT_ORIGINAL_SLOT = "production";
|
||||
public const string DEFAULT_TEMPORARY_SLOT = "aj-temporary";
|
||||
public const string DEFAULT_DESTINATION_SLOT = DEFAULT_ORIGINAL_SLOT;
|
||||
|
||||
/// <summary>
|
||||
/// Source Slot (original application)
|
||||
|
@ -25,10 +24,10 @@ namespace AuthJanitor.Providers.Azure.Workflows
|
|||
public string TemporarySlot { get; set; } = DEFAULT_TEMPORARY_SLOT;
|
||||
|
||||
/// <summary>
|
||||
/// Destination Slot (updated application). By default this is the same as the Source Slot.
|
||||
/// Destination Slot (where the app ends up)
|
||||
/// </summary>
|
||||
[DisplayName("Destination Application Slot")]
|
||||
[Description("Slot to swap to by the end of the secret rotation process.")]
|
||||
public string DestinationSlot { get; set; } = DEFAULT_DESTINATION_SLOT;
|
||||
[Description("Slot to copy the final settings to")]
|
||||
public string DestinationSlot { get; set; } = DEFAULT_ORIGINAL_SLOT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -9,7 +10,11 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace AuthJanitor.Providers.Azure.Workflows
|
||||
{
|
||||
public abstract class TwoKeyAzureRekeyableObjectProvider<TConfiguration, TResource, TKeyring, TKeyType, TSdkKeyType> : AzureRekeyableObjectProvider<TConfiguration, TResource>
|
||||
public abstract class TwoKeyAzureRekeyableObjectProvider<TConfiguration, TResource, TKeyring, TKeyType, TSdkKeyType> :
|
||||
AzureRekeyableObjectProvider<TConfiguration, TResource>,
|
||||
ICanRunSanityTests,
|
||||
ICanGenerateTemporarySecretValue,
|
||||
ICanCleanup
|
||||
where TConfiguration : TwoKeyAzureAuthJanitorProviderConfiguration<TKeyType>
|
||||
where TKeyType : struct, Enum
|
||||
{
|
||||
|
@ -24,7 +29,7 @@ namespace AuthJanitor.Providers.Azure.Workflows
|
|||
|
||||
protected abstract RegeneratedSecret CreateSecretFromKeyring(TKeyring keyring, TKeyType keyType);
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var resource = await GetResourceAsync();
|
||||
if (resource == null) throw new Exception("Could not retrieve resource");
|
||||
|
@ -40,7 +45,7 @@ namespace AuthJanitor.Providers.Azure.Workflows
|
|||
await Task.WhenAll(currentKeyringTask, otherKeyringTask);
|
||||
}
|
||||
|
||||
public override async Task<RegeneratedSecret> GetSecretToUseDuringRekeying()
|
||||
public async Task<RegeneratedSecret> GenerateTemporarySecretValue()
|
||||
{
|
||||
_logger.LogInformation("Getting temporary secret to use during rekeying from other ({OtherKeyType}) key...", GetOtherPairedKey(Configuration.KeyType));
|
||||
var resource = await GetResourceAsync();
|
||||
|
@ -67,7 +72,7 @@ namespace AuthJanitor.Providers.Azure.Workflows
|
|||
return regeneratedSecret;
|
||||
}
|
||||
|
||||
public override async Task OnConsumingApplicationSwapped()
|
||||
public async Task Cleanup()
|
||||
{
|
||||
if (!Configuration.SkipScramblingOtherKey)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AuthJanitor.Providers.AzureAD
|
||||
|
@ -19,5 +20,8 @@ namespace AuthJanitor.Providers.AzureAD
|
|||
[DisplayName("Auto-Refresh?")]
|
||||
[Description("Allow AuthJanitor to automatically refresh the Access Token when it expires?")]
|
||||
public bool AutomaticallyRefresh { get; set; }
|
||||
|
||||
public override int GenerateResourceIdentifierHashCode() =>
|
||||
HashCode.Combine(Scopes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ namespace AuthJanitor.Providers.AzureAD
|
|||
{
|
||||
[Provider(Name = "Access Token",
|
||||
Description = "Acquires an Access Token from Azure AD with a given set of scopes",
|
||||
Features = ProviderFeatureFlags.None)]
|
||||
[ProviderImage(ProviderImages.AZURE_AD_SVG)]
|
||||
public class AccessTokenRekeyableObjectProvider : RekeyableObjectProvider<AccessTokenConfiguration>
|
||||
SvgImage = ProviderImages.AZURE_AD_SVG)]
|
||||
public class AccessTokenRekeyableObjectProvider :
|
||||
RekeyableObjectProvider<AccessTokenConfiguration>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Core" Version="1.2.1" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Azure.Core" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Maps" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Integrations.CryptographicImplementations;
|
||||
using AuthJanitor.Providers.Azure;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Microsoft.Azure.Management.Maps;
|
||||
using Microsoft.Azure.Management.Maps.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -13,11 +14,11 @@ namespace AuthJanitor.Providers.AzureMaps
|
|||
{
|
||||
[Provider(Name = "Azure Maps Key",
|
||||
Description = "Regenerates a key for an Azure Maps instance",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.AZURE_MAPS_SVG)]
|
||||
public class AzureMapsRekeyableObjectProvider : RekeyableObjectProvider<AzureMapsConfiguration>
|
||||
SvgImage = ProviderImages.AZURE_MAPS_SVG)]
|
||||
public class AzureMapsRekeyableObjectProvider :
|
||||
RekeyableObjectProvider<AzureMapsConfiguration>,
|
||||
ICanRunSanityTests,
|
||||
ICanGenerateTemporarySecretValue
|
||||
{
|
||||
private const string PRIMARY_KEY = "primary";
|
||||
private const string SECONDARY_KEY = "secondary";
|
||||
|
@ -29,7 +30,7 @@ namespace AuthJanitor.Providers.AzureMaps
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var keys = await ManagementClient.Accounts.ListKeysAsync(
|
||||
Configuration.ResourceGroup,
|
||||
|
@ -37,7 +38,7 @@ namespace AuthJanitor.Providers.AzureMaps
|
|||
if (keys == null) throw new Exception("Could not access Azure Maps keys");
|
||||
}
|
||||
|
||||
public override async Task<RegeneratedSecret> GetSecretToUseDuringRekeying()
|
||||
public async Task<RegeneratedSecret> GenerateTemporarySecretValue()
|
||||
{
|
||||
_logger.LogInformation("Getting temporary secret to use during rekeying from other ({OtherKeyType}) key...", GetOtherKeyType);
|
||||
var keys = await ManagementClient.Accounts.ListKeysAsync(
|
||||
|
@ -68,7 +69,7 @@ namespace AuthJanitor.Providers.AzureMaps
|
|||
};
|
||||
}
|
||||
|
||||
public override async Task OnConsumingApplicationSwapped()
|
||||
public async Task Cleanup()
|
||||
{
|
||||
if (!Configuration.SkipScramblingOtherKey)
|
||||
{
|
||||
|
|
|
@ -14,10 +14,7 @@ namespace AuthJanitor.Providers.AzureSearch
|
|||
{
|
||||
[Provider(Name = "Azure Search Admin Key",
|
||||
Description = "Regenerates an Admin Key for an Azure Search service",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.AZURE_SEARCH_SVG)]
|
||||
SvgImage = ProviderImages.AZURE_SEARCH_SVG)]
|
||||
public class AzureSearchAdminKeyRekeyableObjectProvider : TwoKeyAzureRekeyableObjectProvider<AzureSearchAdminKeyConfiguration, ISearchService, IAdminKeys, AzureSearchAdminKeyConfiguration.AzureSearchKeyKinds, AdminKeyKind>
|
||||
{
|
||||
public AzureSearchAdminKeyRekeyableObjectProvider(ILogger<AzureSearchAdminKeyRekeyableObjectProvider> logger) : base(logger) { }
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Sql.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Sql.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Integrations.CryptographicImplementations;
|
||||
using AuthJanitor.Providers.Azure;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Microsoft.Azure.Management.Fluent;
|
||||
using Microsoft.Azure.Management.ResourceManager.Fluent.Core.CollectionActions;
|
||||
using Microsoft.Azure.Management.Sql.Fluent;
|
||||
|
@ -15,9 +16,10 @@ namespace AuthJanitor.Providers.AzureSql
|
|||
{
|
||||
[Provider(Name = "Azure SQL Server Administrator Password",
|
||||
Description = "Regenerates the administrator password of an Azure SQL Server",
|
||||
Features = ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.SQL_SERVER_SVG)]
|
||||
public class AzureSqlAdministratorPasswordRekeyableObjectProvider : AzureRekeyableObjectProvider<AzureSqlAdministratorPasswordConfiguration, ISqlServer>
|
||||
SvgImage = ProviderImages.SQL_SERVER_SVG)]
|
||||
public class AzureSqlAdministratorPasswordRekeyableObjectProvider :
|
||||
AzureRekeyableObjectProvider<AzureSqlAdministratorPasswordConfiguration, ISqlServer>,
|
||||
ICanRunSanityTests
|
||||
{
|
||||
private readonly ICryptographicImplementation _cryptographicImplementation;
|
||||
private readonly ILogger _logger;
|
||||
|
@ -30,15 +32,13 @@ namespace AuthJanitor.Providers.AzureSql
|
|||
_cryptographicImplementation = cryptographicImplementation;
|
||||
}
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var sqlServer = await GetResourceAsync();
|
||||
if (sqlServer == null)
|
||||
throw new Exception($"Cannot locate Azure Sql server called '{Configuration.ResourceName}' in group '{Configuration.ResourceGroup}'");
|
||||
}
|
||||
|
||||
public override Task<RegeneratedSecret> GetSecretToUseDuringRekeying() => Task.FromResult<RegeneratedSecret>(null);
|
||||
|
||||
|
||||
public override async Task<RegeneratedSecret> Rekey(TimeSpan requestedValidPeriod)
|
||||
{
|
||||
_logger.LogInformation("Generating new password of length {PasswordLength}", Configuration.PasswordLength);
|
||||
|
@ -60,8 +60,6 @@ namespace AuthJanitor.Providers.AzureSql
|
|||
};
|
||||
}
|
||||
|
||||
public override Task OnConsumingApplicationSwapped() => Task.FromResult(0);
|
||||
|
||||
public override IList<RiskyConfigurationItem> GetRisks()
|
||||
{
|
||||
List<RiskyConfigurationItem> issues = new List<RiskyConfigurationItem>();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -13,10 +13,7 @@ namespace AuthJanitor.Providers.CosmosDb
|
|||
{
|
||||
[Provider(Name = "CosmosDB Master Key",
|
||||
Description = "Regenerates a Master Key for an Azure CosmosDB instance",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.COSMOS_DB_SVG)]
|
||||
SvgImage = ProviderImages.COSMOS_DB_SVG)]
|
||||
public class CosmosDbRekeyableObjectProvider : TwoKeyAzureRekeyableObjectProvider<CosmosDbKeyConfiguration, ICosmosDBAccount, IDatabaseAccountListKeysResult, CosmosDbKeyConfiguration.CosmosDbKeyKinds, string>
|
||||
{
|
||||
private const string PRIMARY_READONLY_KEY = "primaryReadOnly";
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.EventHub.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.EventHub.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -15,10 +15,7 @@ namespace AuthJanitor.Providers.EventHub
|
|||
{
|
||||
[Provider(Name = "Event Hub Key",
|
||||
Description = "Regenerates an Azure Event Hub Key",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.EVENT_HUB_SVG)]
|
||||
SvgImage = ProviderImages.EVENT_HUB_SVG)]
|
||||
public class EventHubRekeyableObjectProvider : TwoKeyAzureRekeyableObjectProvider<EventHubKeyConfiguration, IEventHubNamespace, IEventHubAuthorizationKey, EventHubKeyConfiguration.EventHubKeyTypes, KeyType>
|
||||
{
|
||||
public EventHubRekeyableObjectProvider(ILogger<EventHubRekeyableObjectProvider> logger) : base(logger) { }
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.0.3" />
|
||||
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.0.4" />
|
||||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AuthJanitor.Providers.KeyVault
|
||||
|
@ -19,5 +20,8 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
[DisplayName("Key Name")]
|
||||
[Description("Key Name to manage")]
|
||||
public string KeyName { get; set; }
|
||||
|
||||
public override int GenerateResourceIdentifierHashCode() =>
|
||||
HashCode.Combine(VaultName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Integrations.CryptographicImplementations;
|
||||
using AuthJanitor.Providers.Azure;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Azure;
|
||||
using Azure.Security.KeyVault.Keys;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -14,11 +15,11 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
{
|
||||
[Provider(Name = "Key Vault Key",
|
||||
Description = "Regenerates an Azure Key Vault Key with the same parameters as the previous version",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.KEY_VAULT_SVG)]
|
||||
public class KeyVaultKeyRekeyableObjectProvider : RekeyableObjectProvider<KeyVaultKeyConfiguration>
|
||||
SvgImage = ProviderImages.KEY_VAULT_SVG)]
|
||||
public class KeyVaultKeyRekeyableObjectProvider :
|
||||
RekeyableObjectProvider<KeyVaultKeyConfiguration>,
|
||||
ICanRunSanityTests,
|
||||
ICanGenerateTemporarySecretValue
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
@ -27,13 +28,13 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var key = await GetKeyClient().GetKeyAsync(Configuration.KeyName);
|
||||
if (key == null) throw new Exception("Key was not found or not accessible");
|
||||
}
|
||||
|
||||
public override async Task<RegeneratedSecret> GetSecretToUseDuringRekeying()
|
||||
public async Task<RegeneratedSecret> GenerateTemporarySecretValue()
|
||||
{
|
||||
_logger.LogInformation("Getting temporary secret to use during rekeying based on current KID");
|
||||
var client = GetKeyClient();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Integrations.CryptographicImplementations;
|
||||
using AuthJanitor.Providers.Azure;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Azure;
|
||||
using Azure.Security.KeyVault.Secrets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -13,9 +14,10 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
{
|
||||
[Provider(Name = "Key Vault Secret",
|
||||
Description = "Manages the lifecycle of a Key Vault Secret where a Managed Secret's value is stored",
|
||||
Features = ProviderFeatureFlags.IsTestable)]
|
||||
[ProviderImage(ProviderImages.KEY_VAULT_SVG)]
|
||||
public class KeyVaultSecretApplicationLifecycleProvider : ApplicationLifecycleProvider<KeyVaultSecretLifecycleConfiguration>
|
||||
SvgImage = ProviderImages.KEY_VAULT_SVG)]
|
||||
public class KeyVaultSecretApplicationLifecycleProvider :
|
||||
ApplicationLifecycleProvider<KeyVaultSecretLifecycleConfiguration>,
|
||||
ICanRunSanityTests
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
@ -24,20 +26,17 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var secret = await GetSecretClient().GetSecretAsync(Configuration.SecretName);
|
||||
if (secret == null) throw new Exception("Could not access Key Vault Secret");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to commit the newly generated secret
|
||||
/// </summary>
|
||||
public override async Task CommitNewSecrets(List<RegeneratedSecret> newSecrets)
|
||||
|
||||
public override async Task DistributeLongTermSecretValues(List<RegeneratedSecret> newSecretValues)
|
||||
{
|
||||
_logger.LogInformation("Committing new secrets to Key Vault secret {SecretName}", Configuration.SecretName);
|
||||
var client = GetSecretClient();
|
||||
foreach (RegeneratedSecret secret in newSecrets)
|
||||
foreach (RegeneratedSecret secret in newSecretValues)
|
||||
{
|
||||
_logger.LogInformation("Getting current secret version from secret name {SecretName}", Configuration.SecretName);
|
||||
Response<KeyVaultSecret> currentSecret = await client.GetSecretAsync(Configuration.SecretName);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AuthJanitor.Providers.KeyVault
|
||||
|
@ -28,5 +29,8 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
[DisplayName("Secret Length")]
|
||||
[Description("Length of secret to generate")]
|
||||
public int SecretLength { get; set; } = DEFAULT_SECRET_LENGTH;
|
||||
|
||||
public override int GenerateResourceIdentifierHashCode() =>
|
||||
HashCode.Combine(VaultName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AuthJanitor.Providers.KeyVault
|
||||
|
@ -26,5 +27,8 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
[DisplayName("Commit Connection String")]
|
||||
[Description("Commit a Connection String instead of a Key to this AppSetting, when available")]
|
||||
public bool CommitAsConnectionString { get; set; }
|
||||
|
||||
public override int GenerateResourceIdentifierHashCode() =>
|
||||
HashCode.Combine(VaultName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
using AuthJanitor.Integrations.CryptographicImplementations;
|
||||
using AuthJanitor.Providers.Azure;
|
||||
using AuthJanitor.Providers.Capabilities;
|
||||
using Azure;
|
||||
using Azure.Security.KeyVault.Secrets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -14,11 +15,11 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
{
|
||||
[Provider(Name = "Key Vault Secret",
|
||||
Description = "Regenerates a Key Vault Secret with a given length",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.KEY_VAULT_SVG)]
|
||||
public class KeyVaultSecretRekeyableObjectProvider : RekeyableObjectProvider<KeyVaultSecretConfiguration>
|
||||
SvgImage = ProviderImages.KEY_VAULT_SVG)]
|
||||
public class KeyVaultSecretRekeyableObjectProvider :
|
||||
RekeyableObjectProvider<KeyVaultSecretConfiguration>,
|
||||
ICanRunSanityTests,
|
||||
ICanGenerateTemporarySecretValue
|
||||
{
|
||||
private readonly ICryptographicImplementation _cryptographicImplementation;
|
||||
private readonly ILogger _logger;
|
||||
|
@ -31,13 +32,13 @@ namespace AuthJanitor.Providers.KeyVault
|
|||
_cryptographicImplementation = cryptographicImplementation;
|
||||
}
|
||||
|
||||
public override async Task Test()
|
||||
public async Task Test()
|
||||
{
|
||||
var secret = await GetSecretClient().GetSecretAsync(Configuration.SecretName);
|
||||
if (secret == null) throw new Exception("Could not access Key Vault Secret");
|
||||
}
|
||||
|
||||
public override async Task<RegeneratedSecret> GetSecretToUseDuringRekeying()
|
||||
public async Task<RegeneratedSecret> GenerateTemporarySecretValue()
|
||||
{
|
||||
_logger.LogInformation("Getting temporary secret based on current version...");
|
||||
var client = GetSecretClient();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Redis.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Redis.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -14,10 +14,7 @@ namespace AuthJanitor.Providers.Redis
|
|||
{
|
||||
[Provider(Name = "Redis Cache Key",
|
||||
Description = "Regenerates a Master Key for a Redis Cache instance",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.REDIS_SVG)]
|
||||
SvgImage = ProviderImages.REDIS_SVG)]
|
||||
public class RedisCacheKeyRekeyableObjectProvider : TwoKeyAzureRekeyableObjectProvider<RedisCacheKeyConfiguration, IRedisCache, IRedisAccessKeys, RedisCacheKeyConfiguration.RedisKeyTypes, RedisKeyType>
|
||||
{
|
||||
protected override string Service => "Redis Cache";
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -14,10 +14,7 @@ namespace AuthJanitor.Providers.ServiceBus
|
|||
{
|
||||
[Provider(Name = "Service Bus Key",
|
||||
Description = "Regenerates an Azure Service Bus Key",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.SERVICE_BUS_SVG)]
|
||||
SvgImage = ProviderImages.SERVICE_BUS_SVG)]
|
||||
public class ServiceBusRekeyableObjectProvider : TwoKeyAzureRekeyableObjectProvider<ServiceBusKeyConfiguration, IServiceBusNamespace, IAuthorizationKeys, ServiceBusKeyConfiguration.ServiceBusKeyTypes, Policykey>
|
||||
{
|
||||
public ServiceBusRekeyableObjectProvider(ILogger<ServiceBusRekeyableObjectProvider> logger) : base(logger) { }
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.33.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="1.34.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -16,10 +16,7 @@ namespace AuthJanitor.Providers.Storage
|
|||
{
|
||||
[Provider(Name = "Storage Account Key",
|
||||
Description = "Regenerates a key of a specified type for an Azure Storage Account",
|
||||
Features = ProviderFeatureFlags.CanRotateWithoutDowntime |
|
||||
ProviderFeatureFlags.IsTestable |
|
||||
ProviderFeatureFlags.SupportsSecondaryKey)]
|
||||
[ProviderImage(ProviderImages.STORAGE_ACCOUNT_SVG)]
|
||||
SvgImage = ProviderImages.STORAGE_ACCOUNT_SVG)]
|
||||
public class StorageAccountRekeyableObjectProvider : TwoKeyAzureRekeyableObjectProvider<StorageAccountKeyConfiguration, IStorageAccount, IReadOnlyList<StorageAccountKey>, StorageAccountKeyConfiguration.StorageKeyTypes, string>
|
||||
{
|
||||
private const string KEY1 = "key1";
|
||||
|
@ -45,7 +42,20 @@ namespace AuthJanitor.Providers.Storage
|
|||
|
||||
protected override Task<IReadOnlyList<StorageAccountKey>> RetrieveCurrentKeyring(IStorageAccount resource, string keyType) => resource.GetKeysAsync();
|
||||
|
||||
protected override Task<IReadOnlyList<StorageAccountKey>> RotateKeyringValue(IStorageAccount resource, string keyType) => resource.RegenerateKeyAsync(keyType);
|
||||
protected override async Task<IReadOnlyList<StorageAccountKey>> RotateKeyringValue(IStorageAccount resource, string keyType)
|
||||
{
|
||||
var result = await resource.RegenerateKeyAsync(keyType);
|
||||
var expectedNewKey = result.FirstOrDefault(k => k.KeyName == keyType);
|
||||
var end = DateTime.Now.AddMinutes(2);
|
||||
while (DateTime.Now < end)
|
||||
{
|
||||
var keyring = await RetrieveCurrentKeyring(resource, keyType);
|
||||
var key = keyring.FirstOrDefault(k => k.KeyName == keyType);
|
||||
if (key == null) continue;
|
||||
if (key.Value == expectedNewKey.Value) return result;
|
||||
}
|
||||
throw new Exception("Storage key was reported as rotated, but didn't resync within 2 minutes!");
|
||||
}
|
||||
|
||||
protected override string Translate(StorageAccountKeyConfiguration.StorageKeyTypes keyType) => keyType switch
|
||||
{
|
||||
|
|
|
@ -7,11 +7,17 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Загрузка…
Ссылка в новой задаче