-
Couldn't load subscription status.
- Fork 668
KDB-850 add certificate validity period metrics #5112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ylorph
wants to merge
1
commit into
master
Choose a base branch
from
ylorph/cert_metric
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
148 changes: 148 additions & 0 deletions
148
src/KurrentDB.Core.XUnit.Tests/Metrics/CertificatesExpirationMetricTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| // Copyright (c) Kurrent, Inc and/or licensed to Kurrent, Inc under one or more agreements. | ||
| // Kurrent, Inc licenses this file to you under the Kurrent License v1 (see LICENSE.md). | ||
|
|
||
| using System; | ||
| using System.Diagnostics.Metrics; | ||
| using System.Linq; | ||
| using System.Security.Cryptography; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using Amazon.SQS.Model; | ||
| using KurrentDB.Core.Certificates; | ||
| using KurrentDB.Core.Metrics; | ||
| using KurrentDB.Core.Time; | ||
| using Xunit; | ||
|
|
||
|
|
||
| namespace KurrentDB.Core.XUnit.Tests.Metrics; | ||
|
|
||
| public class CertificatesMetricTests : IDisposable { | ||
| private readonly TestMeterListener<double> _doubleListener; | ||
|
|
||
| private readonly Meter _meter; | ||
| private readonly DateTimeOffset _now; | ||
|
|
||
| public CertificatesMetricTests() { | ||
| _meter = new Meter($"{typeof(CertificatesMetricTests)}"); | ||
| _doubleListener = new TestMeterListener<double>(_meter); | ||
| _doubleListener.Observe(); | ||
| _now = DateTimeOffset.FromUnixTimeSeconds(Clock.Instance.SecondsSinceEpoch); | ||
| } | ||
|
|
||
| public void Dispose() => _doubleListener.Dispose(); | ||
|
|
||
| [Fact] | ||
| public void When_Expired() { | ||
|
|
||
|
|
||
| var b = new CertificateProviderTest(_now.AddDays(-10), _now.AddDays(-9)); | ||
| var sut = new CertificatesMetric(_meter, "cert_apocalypse", b); | ||
|
|
||
|
|
||
| sut.Measure(); | ||
| var measurements = _doubleListener.RetrieveMeasurements("cert_apocalypse-day"); | ||
|
|
||
| Assert.Contains( | ||
| measurements, | ||
| m => { | ||
|
|
||
| Assert.True(Math.Abs(m.Value + 9) < 0.01); | ||
|
|
||
|
|
||
| Assert.Equal(CertificatesMetric.Tags.All, m.Tags.Select(t => t.Key)); | ||
| Assert.Contains( | ||
| m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ChainLevel).Value, | ||
| CertificatesMetric.ChainLevels.All | ||
| ); | ||
| Assert.Equal( | ||
| CertificatesMetric.ValidityPeriods.Expired, | ||
| m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ValidityPeriod).Value); | ||
|
|
||
|
|
||
| return true; | ||
| }); | ||
| } | ||
| [Fact] | ||
| public void When_Ok() { | ||
|
|
||
|
|
||
| var b = new CertificateProviderTest(_now.AddDays(-10), _now.AddDays(10)); | ||
| var sut = new CertificatesMetric(_meter, "cert_will_be_apocalypse", b); | ||
|
|
||
|
|
||
| sut.Measure(); | ||
| var measurements = _doubleListener.RetrieveMeasurements("cert_will_be_apocalypse-day"); | ||
|
|
||
| Assert.Contains( | ||
| measurements, | ||
| m => { | ||
|
|
||
| Assert.True(Math.Abs(m.Value -10)< 0.01); | ||
|
|
||
|
|
||
| Assert.Equal(CertificatesMetric.Tags.All, m.Tags.Select(t => t.Key)); | ||
| Assert.Contains( | ||
| m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ChainLevel).Value, | ||
| CertificatesMetric.ChainLevels.All | ||
| ); | ||
| Assert.Equal( | ||
| CertificatesMetric.ValidityPeriods.InPeriod, | ||
| m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ValidityPeriod).Value); | ||
|
|
||
| return true; | ||
| }); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void When_Not_Yet_Valid() { | ||
|
|
||
|
|
||
| var b = new CertificateProviderTest(_now.AddDays(10), _now.AddDays(11)); | ||
| var sut = new CertificatesMetric(_meter, "cert_future_apocalypse", b); | ||
|
|
||
|
|
||
| sut.Measure(); | ||
| var measurements = _doubleListener.RetrieveMeasurements("cert_future_apocalypse-day"); | ||
|
|
||
| Assert.Contains( | ||
| measurements, | ||
| m => { | ||
|
|
||
| Assert.True(Math.Abs(m.Value -11 ) < 0.01); | ||
|
|
||
| Assert.Equal(CertificatesMetric.Tags.All, m.Tags.Select(t => t.Key)); | ||
| Assert.Contains( | ||
| m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ChainLevel).Value, | ||
| CertificatesMetric.ChainLevels.All | ||
| ); | ||
| Assert.Equal( | ||
| CertificatesMetric.ValidityPeriods.NotValidYet, | ||
| m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ValidityPeriod).Value); | ||
| return true; | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| public class CertificateProviderTest : CertificateProvider { | ||
|
|
||
|
|
||
| public CertificateProviderTest(DateTimeOffset notBefore, DateTimeOffset notAfter) { | ||
| using RSA rsa = RSA.Create(); | ||
| var request = new CertificateRequest("CN=SelfSignedCert", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); | ||
|
|
||
|
|
||
| Certificate=request.CreateSelfSigned(notBefore, notAfter); | ||
| IntermediateCerts = new X509Certificate2Collection( | ||
|
|
||
| request.CreateSelfSigned(notBefore, notAfter) | ||
| ); | ||
| TrustedRootCerts = new X509Certificate2Collection(request.CreateSelfSigned(notBefore, notAfter)); | ||
| } | ||
|
|
||
| public override LoadCertificateResult LoadCertificates(ClusterVNodeOptions options) => | ||
| throw new UnsupportedOperationException(""); | ||
|
|
||
| public override string GetReservedNodeCommonName() => | ||
| throw new UnsupportedOperationException(""); | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Copyright (c) Kurrent, Inc and/or licensed to Kurrent, Inc under one or more agreements. | ||
| // Kurrent, Inc licenses this file to you under the Kurrent License v1 (see LICENSE.md). | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.Metrics; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using KurrentDB.Core.Certificates; | ||
| using KurrentDB.Core.Time; | ||
|
|
||
| namespace KurrentDB.Core.Metrics; | ||
|
|
||
| public class CertificatesMetric { | ||
| private readonly Meter _meter; | ||
| private readonly string _name; | ||
| private readonly CertificateProvider _certificateProvider; | ||
| private readonly List<CertificateExpiration> _subMetric= []; | ||
| private readonly IClock _clock; | ||
|
|
||
| public static class ChainLevels { | ||
| public const string Node="node"; | ||
| public const string Intermediate = "intermediate"; | ||
| public const string Root = "root"; | ||
| public static readonly string[] All = [Node, Intermediate, Root]; | ||
| } | ||
| public static class Tags { | ||
| public const string ChainLevel = "chain_level"; | ||
| public const string SerialNumber = "serial_number"; | ||
| public const string Thumbprint = "thumbprint"; | ||
| public const string ValidityPeriod = "validity_period"; | ||
| public static readonly string[] All = [ChainLevel, SerialNumber, Thumbprint, ValidityPeriod]; | ||
| } | ||
| public static class ValidityPeriods { | ||
| public const string InPeriod="0"; | ||
| public const string NotValidYet = "1"; | ||
| public const string Expired = "-1"; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| public CertificatesMetric(Meter meter, string name, | ||
| CertificateProvider certificateProvider, | ||
| IClock clock = null) { | ||
| _meter = meter; | ||
| _name = name; | ||
| _certificateProvider = certificateProvider; | ||
| _clock = clock ?? Clock.Instance; | ||
|
|
||
| MeasureAllCerts(meter, name, certificateProvider); | ||
| } | ||
|
|
||
| public void Measure() { | ||
| foreach (var subMetric in _subMetric) | ||
| subMetric.Measure(); | ||
| } | ||
|
|
||
| public void Renewed() => MeasureAllCerts(_meter, _name, _certificateProvider); | ||
|
|
||
| private void MeasureAllCerts(Meter meter, string name, CertificateProvider certificateProvider) | ||
| { | ||
| _subMetric.Clear(); | ||
| _subMetric.Add(new CertificateExpiration(certificateProvider.Certificate, ChainLevels.Node, meter, name,_clock)); | ||
| foreach (var intermediate in certificateProvider.IntermediateCerts) | ||
| _subMetric.Add(new CertificateExpiration(intermediate, ChainLevels.Intermediate, meter, name,_clock)); | ||
|
|
||
| foreach (var root in certificateProvider.TrustedRootCerts) | ||
| _subMetric.Add(new CertificateExpiration(root, ChainLevels.Root, meter, name,_clock)); | ||
| } | ||
|
|
||
|
|
||
| public class CertificateExpiration(X509Certificate2 cert, string type, Meter meter, string name, IClock clock ) { | ||
|
|
||
| private readonly Gauge<double> _gauge = meter.CreateGauge<double>(name, "day", "Days before the certificate expires"); | ||
|
|
||
| private readonly KeyValuePair<string, object>[] _tags = [ | ||
| new(Tags.ChainLevel, type), | ||
| new(Tags.SerialNumber, cert.GetSerialNumberString()), | ||
| new(Tags.Thumbprint, cert.Thumbprint), | ||
| new(Tags.ValidityPeriod, ValidityPeriod(DateTimeOffset.FromUnixTimeSeconds(clock.SecondsSinceEpoch).DateTime, cert.NotBefore, cert.NotAfter).ToString()) | ||
|
|
||
|
|
||
| ]; | ||
|
|
||
| private static string ValidityPeriod(DateTime now, DateTime notBefore, DateTime notAfter) => | ||
| notBefore <= now && now <= notAfter | ||
| ? ValidityPeriods.InPeriod | ||
| : now <= notBefore | ||
| ? ValidityPeriods.NotValidYet | ||
| : ValidityPeriods.Expired; | ||
|
|
||
| public void Measure() { | ||
| var certApocalypseIn = (cert.NotAfter - DateTime.Now).TotalDays; | ||
| _gauge.Record(certApocalypseIn, _tags); | ||
|
|
||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please reformat the file (add empty lines between classes, remove redundant empty lines)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applies to all files