diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Cli/Commands/StatusCommand.cs b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Cli/Commands/StatusCommand.cs index 46e2612d033..77d9815ef64 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Cli/Commands/StatusCommand.cs +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Cli/Commands/StatusCommand.cs @@ -1,5 +1,8 @@ -using System.CommandLine.Invocation; +using System.CommandLine.Invocation; +using System.Reflection; +using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using Azure.Sdk.Tools.SecretRotation.Configuration; using Azure.Sdk.Tools.SecretRotation.Core; @@ -17,13 +20,130 @@ protected override async Task HandleCommandAsync(ILogger logger, RotationConfigu var timeProvider = new TimeProvider(); IEnumerable plans = rotationConfiguration.GetAllRotationPlans(logger, timeProvider); + List<(RotationPlan Plan, RotationPlanStatus Status)> statuses = new(); + foreach (RotationPlan plan in plans) { - Console.WriteLine(); - Console.WriteLine($"{plan.Name}:"); - + logger.LogInformation($"Getting status for plan '{plan.Name}'"); RotationPlanStatus status = await plan.GetStatusAsync(); - Console.WriteLine(JsonSerializer.Serialize(status, new JsonSerializerOptions { WriteIndented = true })); + + if (logger.IsEnabled(LogLevel.Debug)) + { + var builder = new StringBuilder(); + + builder.AppendLine($" Plan:"); + builder.AppendLine($" RotationPeriod: {plan.RotationPeriod}"); + builder.AppendLine($" RotationThreshold: {plan.RotationThreshold}"); + builder.AppendLine($" RevokeAfterPeriod: {plan.RevokeAfterPeriod}"); + + builder.AppendLine($" Status:"); + builder.AppendLine($" ExpirationDate: {status.ExpirationDate}"); + builder.AppendLine($" Expired: {status.Expired}"); + builder.AppendLine($" ThresholdExpired: {status.ThresholdExpired}"); + builder.AppendLine($" RequiresRevocation: {status.RequiresRevocation}"); + builder.AppendLine($" Exception: {status.Exception?.Message}"); + + logger.LogDebug(builder.ToString()); + } + + + statuses.Add((plan, status)); } + + var errored = statuses.Where(x => x.Status.Exception is not null).ToArray(); + var expired = statuses.Except(errored).Where(x => x.Status is { Expired: true }).ToArray(); + var expiring = statuses.Except(errored).Where(x => x.Status is { Expired: false, ThresholdExpired: true }).ToArray(); + var upToDate = statuses.Except(errored).Where(x => x.Status is { Expired: false, ThresholdExpired: false }).ToArray(); + + var statusBuilder = new StringBuilder(); + + void AppendStatusSection(IList<(RotationPlan Plan, RotationPlanStatus Status)> sectionStatuses, string header) + { + if (!sectionStatuses.Any()) + { + return; + } + + statusBuilder.AppendLine(); + statusBuilder.AppendLine(header); + foreach ((RotationPlan plan, RotationPlanStatus status) in sectionStatuses) + { + foreach (string line in GetPlanStatusLine(plan, status).Split("\n")) + { + statusBuilder.Append(" "); + statusBuilder.AppendLine(line); + } + } + } + + AppendStatusSection(expired, "Expired:"); + AppendStatusSection(expiring, "Expiring:"); + AppendStatusSection(upToDate, "Up-to-date:"); + AppendStatusSection(errored, "Error reading plan status:"); + + logger.LogInformation(statusBuilder.ToString()); + + if (expired.Any()) + { + invocationContext.ExitCode = 1; + } + } + + private static string GetPlanStatusLine(RotationPlan plan, RotationPlanStatus status) + { + if (status.Exception != null) + { + return $"{plan.Name}:\n {status.Exception.Message}"; + } + + DateTimeOffset? expirationDate = status.ExpirationDate; + + DateTimeOffset now = DateTimeOffset.UtcNow; + + string expiration = expirationDate.HasValue + ? $"{FormatTimeSpan(expirationDate.Value.Subtract(now))}" + : "No expiration date"; + + return $"{plan.Name} - {expiration} / ({FormatTimeSpan(plan.RotationPeriod)} @ {FormatTimeSpan(plan.RotationThreshold)})"; + } + + private static string FormatTimeSpan(TimeSpan timeSpan) + { + if (timeSpan == TimeSpan.Zero) + { + return "0d"; + } + + StringBuilder builder = new StringBuilder(); + + if (timeSpan.Days > 0) + { + builder.Append(timeSpan.Days); + builder.Append('d'); + } + + if (timeSpan.Days < 2 && timeSpan.TotalDays - timeSpan.Days > 0) + { + if (timeSpan.Hours > 0) + { + if (builder.Length > 0) + { + builder.Append(' '); + } + builder.Append(timeSpan.Hours); + builder.Append('h'); + } + if (timeSpan.Minutes > 0) + { + if (builder.Length > 0) + { + builder.Append(' '); + } + builder.Append(timeSpan.Minutes); + builder.Append('m'); + } + } + + return builder.ToString(); } } diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Configuration/PlanConfiguration.cs b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Configuration/PlanConfiguration.cs index 342313d754c..4c355314629 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Configuration/PlanConfiguration.cs +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Configuration/PlanConfiguration.cs @@ -18,7 +18,7 @@ public class PlanConfiguration ReadCommentHandling = JsonCommentHandling.Skip, }; - private static readonly Regex schemaPattern = new (@"https\://raw\.githubusercontent\.com/azure/azure-sdk-tools/(?.+?)/schemas/secretrotation/(?.+?)/schema\.json", RegexOptions.IgnoreCase); + private static readonly Regex schemaPattern = new (@"/(?.+?)/plan\.json", RegexOptions.IgnoreCase); public string Name { get; set; } = string.Empty; diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlan.cs b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlan.cs index 9d4144f03c4..6076d1c840a 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlan.cs +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlan.cs @@ -82,42 +82,57 @@ public async Task ExecuteAsync(bool onlyRotateExpiring, bool whatIf) public async Task GetStatusAsync() { - DateTimeOffset invocationTime = this.timeProvider.GetCurrentDateTimeOffset(); + try + { + DateTimeOffset invocationTime = this.timeProvider.GetCurrentDateTimeOffset(); - SecretState primaryStoreState = await PrimaryStore.GetCurrentStateAsync(); + SecretState primaryStoreState = await PrimaryStore.GetCurrentStateAsync(); - IEnumerable rotationArtifacts = await PrimaryStore.GetRotationArtifactsAsync(); + IEnumerable rotationArtifacts = await PrimaryStore.GetRotationArtifactsAsync(); - var secondaryStoreStates = new List(); + var secondaryStoreStates = new List(); - foreach (SecretStore secondaryStore in SecondaryStores) - { - if (secondaryStore.CanRead) + foreach (SecretStore secondaryStore in SecondaryStores) { - secondaryStoreStates.Add(await secondaryStore.GetCurrentStateAsync()); + if (secondaryStore.CanRead) + { + secondaryStoreStates.Add(await secondaryStore.GetCurrentStateAsync()); + } } - } - SecretState[] allStates = secondaryStoreStates.Prepend(primaryStoreState).ToArray(); + SecretState[] allStates = secondaryStoreStates.Prepend(primaryStoreState).ToArray(); - bool anyExpired = allStates.Any(state => state.ExpirationDate <= invocationTime); + DateTimeOffset thresholdDate = this.timeProvider.GetCurrentDateTimeOffset().Add(RotationThreshold); - DateTimeOffset thresholdDate = this.timeProvider.GetCurrentDateTimeOffset().Add(RotationThreshold); + DateTimeOffset? minExpirationDate = allStates.Where(x => x.ExpirationDate.HasValue).Min(x => x.ExpirationDate); - bool anyThresholdExpired = allStates.Any(state => state.ExpirationDate <= thresholdDate); + bool anyExpired = minExpirationDate == null || minExpirationDate <= invocationTime; - bool anyRequireRevocation = rotationArtifacts.Any(state => state.RevokeAfterDate <= invocationTime); + bool anyThresholdExpired = minExpirationDate <= thresholdDate; - var status = new RotationPlanStatus + bool anyRequireRevocation = rotationArtifacts.Any(state => state.RevokeAfterDate <= invocationTime); + + var status = new RotationPlanStatus + { + ExpirationDate = minExpirationDate, + Expired = anyExpired, + ThresholdExpired = anyThresholdExpired, + RequiresRevocation = anyRequireRevocation, + PrimaryStoreState = primaryStoreState, + SecondaryStoreStates = secondaryStoreStates.ToArray() + }; + + return status; + } + catch (RotationException ex) { - Expired = anyExpired, - ThresholdExpired = anyThresholdExpired, - RequiresRevocation = anyRequireRevocation, - PrimaryStoreState = primaryStoreState, - SecondaryStoreStates = secondaryStoreStates.ToArray() - }; - - return status; + var status = new RotationPlanStatus + { + Exception = ex + }; + + return status; + } } private async Task RotateAsync(string operationId, SecretState currentState, bool whatIf) diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlanStatus.cs b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlanStatus.cs index 446509d4414..cbf8a2eedd1 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlanStatus.cs +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Core/RotationPlanStatus.cs @@ -1,4 +1,4 @@ -using Azure.Sdk.Tools.SecretRotation.Core; +using Azure.Sdk.Tools.SecretRotation.Core; namespace Azure.Sdk.Tools.SecretRotation.Core; @@ -13,4 +13,8 @@ public class RotationPlanStatus public SecretState? PrimaryStoreState { get; set; } public IReadOnlyList? SecondaryStoreStates { get; set; } + + public DateTimeOffset? ExpirationDate { get; set; } + + public RotationException? Exception { get; set; } } diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultCertificateStore.cs b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultCertificateStore.cs index c8bef8ca93b..abeb46fc7b1 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultCertificateStore.cs +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultCertificateStore.cs @@ -12,12 +12,15 @@ namespace Azure.Sdk.Tools.SecretRotation.Stores.KeyVault; public class KeyVaultCertificateStore : SecretStore { public const string MappingKey = "Key Vault Certificate"; + private const string RevokeAfterTag = "RevokeAfter"; + private const string OperationIdTag = "OperationId"; + private const string RevokedTag = "Revoked"; private static readonly Regex uriRegex = new( @"^(?https://.+?)/certificates/(?[^/]+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeSpan.FromSeconds(5)); - + private readonly CertificateClient certificateClient; private readonly string certificateName; private readonly ILogger logger; @@ -41,8 +44,6 @@ public KeyVaultCertificateStore( public override bool CanAnnotate => true; - public override bool CanRevoke => true; - public static Func GetSecretStoreFactory(TokenCredential credential, ILogger logger) { @@ -72,7 +73,7 @@ public override async Task GetCurrentStateAsync() { try { - this.logger.LogDebug("Getting certificate '{SecretName}' from vault '{VaultUri}'", this.certificateName, + this.logger.LogDebug("Getting certificate '{CertificateName}' from vault '{VaultUri}'", this.certificateName, this.vaultUri); Response? response = await this.certificateClient.GetCertificateAsync(this.certificateName); @@ -82,9 +83,13 @@ public override async Task GetCurrentStateAsync() ExpirationDate = response.Value.Properties.ExpiresOn, StatusCode = response.GetRawResponse().Status }; } + catch (RequestFailedException ex) when (ex.Status == 404) + { + return new SecretState { ErrorMessage = GetExceptionMessage(ex) }; + } catch (RequestFailedException ex) { - return new SecretState { ExpirationDate = null, StatusCode = ex.Status, ErrorMessage = ex.Message }; + throw new RequestFailedException(GetExceptionMessage(ex), ex); } } @@ -159,29 +164,63 @@ public override async Task> GetRotationArtifactsAsync() { var results = new List(); - await foreach (CertificateProperties? version in - this.certificateClient.GetPropertiesOfCertificateVersionsAsync(this.certificateName)) + try { - if (!version.Tags.TryGetValue("revokeAfter", out string? revokeAfterString)) + await foreach (CertificateProperties version in + this.certificateClient.GetPropertiesOfCertificateVersionsAsync(this.certificateName)) { - continue; + if (!version.Tags.TryGetValue("revokeAfter", out string? revokeAfterString)) + { + continue; + } + + if (!DateTimeOffset.TryParse(revokeAfterString, out DateTimeOffset revokeAfterDate)) + { + // TODO: Warning + continue; + } + + version.Tags.TryGetValue(OperationIdTag, out string? operationId); + + results.Add(new SecretState + { + Id = version.Id.ToString(), + OperationId = operationId, + Tags = version.Tags, + RevokeAfterDate = revokeAfterDate, + }); } - if (!DateTimeOffset.TryParse(revokeAfterString, out DateTimeOffset revokeAfterDate)) + return results; + } + catch (RequestFailedException ex) when (ex.Status == 404) + { + return results; + } + catch (RequestFailedException ex) + { + throw new RotationException(GetExceptionMessage(ex), ex); + } + } + + private string GetExceptionMessage(RequestFailedException exception) + { + if (exception.Status == 403) + { + return $"Key vault request not authorized for vault '{this.vaultUri}'"; + } + + if (exception.Status == 404) + { + if (exception.Message.StartsWith($"A certificate with (name/id) ")) { - // TODO: Warning - continue; + return $"Certificate '{this.certificateName}' not found in vault '{this.vaultUri}'"; } - results.Add(new SecretState { Tags = version.Tags, RevokeAfterDate = revokeAfterDate }); + return $"Vault {this.vaultUri} not found."; } - return results; - } - - public override Func? GetRevocationActionAsync(SecretState secretState, bool whatIf) - { - throw new NotImplementedException(); + return $"Key vault request failed with code {exception.Status}: {exception.Message}"; } private class Parameters diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultSecretStore.cs b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultSecretStore.cs index 5175bdb649a..7f43a6f04b3 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultSecretStore.cs +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Stores.KeyVault/KeyVaultSecretStore.cs @@ -107,44 +107,63 @@ public override async Task GetCurrentStateAsync() return result; } + catch (RequestFailedException ex) when (ex.Status == 404) + { + return new SecretState { ErrorMessage = GetExceptionMessage(ex) }; + } catch (RequestFailedException ex) { - return new SecretState { ExpirationDate = null, StatusCode = ex.Status, ErrorMessage = ex.Message }; + throw new RotationException(GetExceptionMessage(ex), ex); } } public override async Task> GetRotationArtifactsAsync() { var results = new List(); - - await foreach (SecretProperties? versionProperties in this.secretClient.GetPropertiesOfSecretVersionsAsync( - this.secretName)) + + try { - if (versionProperties.Enabled == false - || versionProperties.Tags.ContainsKey(RevokedTag) - || versionProperties.Tags.TryGetValue(RevokeAfterTag, out string? revokeAfterString) == false) - { - continue; - } - if (!DateTimeOffset.TryParse(revokeAfterString, out DateTimeOffset revokeAfterDate)) + this.logger.LogDebug("Getting all version metadata for secret '{SecretName}' from vault '{VaultUri}'", this.secretName, + this.vaultUri); + + await foreach (SecretProperties? versionProperties in this.secretClient.GetPropertiesOfSecretVersionsAsync( + this.secretName)) { - // TODO: Warn about improperly formatted revokeAfter value - continue; - } + if (versionProperties.Enabled == false + || versionProperties.Tags.ContainsKey(RevokedTag) + || versionProperties.Tags.TryGetValue(RevokeAfterTag, out string? revokeAfterString) == false) + { + continue; + } - versionProperties.Tags.TryGetValue(OperationIdTag, out string? operationId); + if (!DateTimeOffset.TryParse(revokeAfterString, out DateTimeOffset revokeAfterDate)) + { + // TODO: Warn about improperly formatted revokeAfter value + continue; + } - results.Add(new SecretState - { - Id = versionProperties.Id.ToString(), - OperationId = operationId, - Tags = versionProperties.Tags, - RevokeAfterDate = revokeAfterDate - }); - } + versionProperties.Tags.TryGetValue(OperationIdTag, out string? operationId); - return results; + results.Add(new SecretState + { + Id = versionProperties.Id.ToString(), + OperationId = operationId, + Tags = versionProperties.Tags, + RevokeAfterDate = revokeAfterDate + }); + } + + return results; + } + catch (RequestFailedException ex) when (ex.Status == 404) + { + return results; + } + catch (RequestFailedException ex) + { + throw new RotationException(GetExceptionMessage(ex), ex); + } } public override async Task WriteSecretAsync(SecretValue secretValue, @@ -303,6 +322,26 @@ private async Task SetRevokeAfterForOldVersionsAsync(string currentVersionId, Da } } + private string GetExceptionMessage(RequestFailedException exception) + { + if (exception.Status == 403) + { + return $"Key vault request not authorized for vault '{this.vaultUri}'"; + } + + if (exception.Status == 404) + { + if (exception.Message.StartsWith($"A secret with (name/id) ")) + { + return $"Secret '{this.secretName}' not found in vault '{this.vaultUri}'"; + } + + return $"Vault {this.vaultUri} not found."; + } + + return $"Key vault request failed with code {exception.Status}: {exception.Message}"; + } + private class Parameters { [JsonPropertyName("secretUri")] diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/four.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/four.json index 24ddafd8e43..c5a9a6c0892 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/four.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/four.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "tags": [ "even", "d" ], diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-1.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-1.json index 5bc60122cc0..8212569a951 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-1.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-1.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "name": "five", diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-2.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-2.json index 0d4a8419050..533f4d7644c 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-2.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/named-2.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "name": "six", diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/one.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/one.json index 00e325062e8..be609d1e242 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/one.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/one.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "tags": [ "odd", "a" ], diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/three.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/three.json index 1f74015acff..4f99f0b3303 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/three.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/three.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "tags": [ "odd", "c" ], diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/two.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/two.json index 2afb17a8bfd..d05a7f22881 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/two.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/TagMatching/two.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "tags": [ "even", "b" ], diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/manual-value.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/manual-value.json index b2830d788b9..0f678b39c23 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/manual-value.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/manual-value.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationPeriod": "1.00:00:00", "rotationThreshold": "23:59:00", "revokeAfterPeriod": "00:05:00", diff --git a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/random-string.json b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/random-string.json index e4882d59f12..6f52d336147 100644 --- a/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/random-string.json +++ b/tools/secret-rotation/Azure.Sdk.Tools.SecretRotation.Tests/TestConfigurations/Valid/random-string.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/schemas/secretrotation/1.0.0/schema.json", + "$schema": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "rotationThreshold": "9.00:00:00", "rotationPeriod": "12.00:00:00", "tags": [ "random" ], diff --git a/schemas/secret-rotation/1.0.0/schema.json b/tools/secret-rotation/schema/1.0.0/plan.json similarity index 85% rename from schemas/secret-rotation/1.0.0/schema.json rename to tools/secret-rotation/schema/1.0.0/plan.json index 1559cab66b8..bbd4122298b 100644 --- a/schemas/secret-rotation/1.0.0/schema.json +++ b/tools/secret-rotation/schema/1.0.0/plan.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/product.schema.json", + "$id": "https://raw.githubusercontent.com/azure/azure-sdk-tools/main/tools/secret-rotation/schema/1.0.0/plan.json", "title": "PlanConfiguration", "description": "A secret rotation plan", "type": "object", @@ -21,6 +21,10 @@ "description": "Time span indicating when the old secret values should be revoked after rotation", "type": "string" }, + "description": { + "description": "A description of the secret being rotated by this plan", + "type": "string" + }, "tags": { "description": "Plan tags", "type": "array", @@ -60,6 +64,10 @@ } }, "minItems": 1 + }, + "$schema": { + "description": "JSON Schema URI (used by some editors)", + "type": "string" } }, "required": [ "rotationThreshold", "rotationPeriod", "stores" ],