Skip to content

Commit ef12985

Browse files
committed
#KDB-850 add certificate validity period metrics
1 parent 5d84698 commit ef12985

2 files changed

Lines changed: 245 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (c) Kurrent, Inc and/or licensed to Kurrent, Inc under one or more agreements.
2+
// Kurrent, Inc licenses this file to you under the Kurrent License v1 (see LICENSE.md).
3+
4+
using System;
5+
using System.Diagnostics.Metrics;
6+
using System.Linq;
7+
using System.Security.Cryptography;
8+
using System.Security.Cryptography.X509Certificates;
9+
using Amazon.SQS.Model;
10+
using KurrentDB.Core.Certificates;
11+
using KurrentDB.Core.Metrics;
12+
using KurrentDB.Core.Time;
13+
using Xunit;
14+
15+
16+
namespace KurrentDB.Core.XUnit.Tests.Metrics;
17+
18+
public class CertificatesMetricTests : IDisposable {
19+
private readonly TestMeterListener<double> _doubleListener;
20+
21+
private readonly Meter _meter;
22+
private readonly DateTimeOffset _now;
23+
24+
public CertificatesMetricTests() {
25+
_meter = new Meter($"{typeof(CertificatesMetricTests)}");
26+
_doubleListener = new TestMeterListener<double>(_meter);
27+
_doubleListener.Observe();
28+
_now = DateTimeOffset.FromUnixTimeSeconds(Clock.Instance.SecondsSinceEpoch);
29+
}
30+
31+
public void Dispose() => _doubleListener.Dispose();
32+
33+
[Fact]
34+
public void When_Expired() {
35+
36+
37+
var b = new CertificateProviderTest(_now.AddDays(-10), _now.AddDays(-9));
38+
var sut = new CertificatesMetric(_meter, "cert_apocalypse", b);
39+
40+
41+
sut.Measure();
42+
var measurements = _doubleListener.RetrieveMeasurements("cert_apocalypse-day");
43+
44+
Assert.Contains(
45+
measurements,
46+
m => {
47+
48+
Assert.True(Math.Abs(m.Value + 9) < 0.01);
49+
50+
51+
Assert.Equal(CertificatesMetric.Tags.All, m.Tags.Select(t => t.Key));
52+
Assert.Contains(
53+
m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ChainLevel).Value,
54+
CertificatesMetric.ChainLevels.All
55+
);
56+
Assert.Equal(
57+
CertificatesMetric.ValidityPeriods.Expired,
58+
m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ValidityPeriod).Value);
59+
60+
61+
return true;
62+
});
63+
}
64+
[Fact]
65+
public void When_Ok() {
66+
67+
68+
var b = new CertificateProviderTest(_now.AddDays(-10), _now.AddDays(10));
69+
var sut = new CertificatesMetric(_meter, "cert_will_be_apocalypse", b);
70+
71+
72+
sut.Measure();
73+
var measurements = _doubleListener.RetrieveMeasurements("cert_will_be_apocalypse-day");
74+
75+
Assert.Contains(
76+
measurements,
77+
m => {
78+
79+
Assert.True(Math.Abs(m.Value -10)< 0.01);
80+
81+
82+
Assert.Equal(CertificatesMetric.Tags.All, m.Tags.Select(t => t.Key));
83+
Assert.Contains(
84+
m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ChainLevel).Value,
85+
CertificatesMetric.ChainLevels.All
86+
);
87+
Assert.Equal(
88+
CertificatesMetric.ValidityPeriods.InPeriod,
89+
m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ValidityPeriod).Value);
90+
91+
return true;
92+
});
93+
}
94+
95+
[Fact]
96+
public void When_Not_Yet_Valid() {
97+
98+
99+
var b = new CertificateProviderTest(_now.AddDays(10), _now.AddDays(11));
100+
var sut = new CertificatesMetric(_meter, "cert_future_apocalypse", b);
101+
102+
103+
sut.Measure();
104+
var measurements = _doubleListener.RetrieveMeasurements("cert_future_apocalypse-day");
105+
106+
Assert.Contains(
107+
measurements,
108+
m => {
109+
110+
Assert.True(Math.Abs(m.Value -11 ) < 0.01);
111+
112+
Assert.Equal(CertificatesMetric.Tags.All, m.Tags.Select(t => t.Key));
113+
Assert.Contains(
114+
m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ChainLevel).Value,
115+
CertificatesMetric.ChainLevels.All
116+
);
117+
Assert.Equal(
118+
CertificatesMetric.ValidityPeriods.NotValidYet,
119+
m.Tags.Single(t => t.Key == CertificatesMetric.Tags.ValidityPeriod).Value);
120+
return true;
121+
});
122+
}
123+
124+
125+
public class CertificateProviderTest : CertificateProvider {
126+
127+
128+
public CertificateProviderTest(DateTimeOffset notBefore, DateTimeOffset notAfter) {
129+
using RSA rsa = RSA.Create();
130+
var request = new CertificateRequest("CN=SelfSignedCert", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
131+
132+
133+
Certificate=request.CreateSelfSigned(notBefore, notAfter);
134+
IntermediateCerts = new X509Certificate2Collection(
135+
136+
request.CreateSelfSigned(notBefore, notAfter)
137+
);
138+
TrustedRootCerts = new X509Certificate2Collection(request.CreateSelfSigned(notBefore, notAfter));
139+
}
140+
141+
public override LoadCertificateResult LoadCertificates(ClusterVNodeOptions options) =>
142+
throw new UnsupportedOperationException("");
143+
144+
public override string GetReservedNodeCommonName() =>
145+
throw new UnsupportedOperationException("");
146+
}
147+
148+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) Kurrent, Inc and/or licensed to Kurrent, Inc under one or more agreements.
2+
// Kurrent, Inc licenses this file to you under the Kurrent License v1 (see LICENSE.md).
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Metrics;
7+
using System.Security.Cryptography.X509Certificates;
8+
using KurrentDB.Core.Certificates;
9+
using KurrentDB.Core.Time;
10+
11+
namespace KurrentDB.Core.Metrics;
12+
13+
public class CertificatesMetric {
14+
private readonly Meter _meter;
15+
private readonly string _name;
16+
private readonly CertificateProvider _certificateProvider;
17+
private readonly List<CertificateExpiration> _subMetric= [];
18+
private readonly IClock _clock;
19+
20+
public static class ChainLevels {
21+
public const string Node="node";
22+
public const string Intermediate = "intermediate";
23+
public const string Root = "root";
24+
public static readonly string[] All = [Node, Intermediate, Root];
25+
}
26+
public static class Tags {
27+
public const string ChainLevel = "chain_level";
28+
public const string SerialNumber = "serial_number";
29+
public const string Thumbprint = "thumbprint";
30+
public const string ValidityPeriod = "validity_period";
31+
public static readonly string[] All = [ChainLevel, SerialNumber, Thumbprint, ValidityPeriod];
32+
}
33+
public static class ValidityPeriods {
34+
public const string InPeriod="0";
35+
public const string NotValidYet = "1";
36+
public const string Expired = "-1";
37+
}
38+
39+
40+
41+
public CertificatesMetric(Meter meter, string name,
42+
CertificateProvider certificateProvider,
43+
IClock clock = null) {
44+
_meter = meter;
45+
_name = name;
46+
_certificateProvider = certificateProvider;
47+
_clock = clock ?? Clock.Instance;
48+
49+
MeasureAllCerts(meter, name, certificateProvider);
50+
}
51+
52+
public void Measure() {
53+
foreach (var subMetric in _subMetric)
54+
subMetric.Measure();
55+
}
56+
57+
public void Renewed() => MeasureAllCerts(_meter, _name, _certificateProvider);
58+
59+
private void MeasureAllCerts(Meter meter, string name, CertificateProvider certificateProvider)
60+
{
61+
_subMetric.Clear();
62+
_subMetric.Add(new CertificateExpiration(certificateProvider.Certificate, ChainLevels.Node, meter, name,_clock));
63+
foreach (var intermediate in certificateProvider.IntermediateCerts)
64+
_subMetric.Add(new CertificateExpiration(intermediate, ChainLevels.Intermediate, meter, name,_clock));
65+
66+
foreach (var root in certificateProvider.TrustedRootCerts)
67+
_subMetric.Add(new CertificateExpiration(root, ChainLevels.Root, meter, name,_clock));
68+
}
69+
70+
71+
public class CertificateExpiration(X509Certificate2 cert, string type, Meter meter, string name, IClock clock ) {
72+
73+
private readonly Gauge<double> _gauge = meter.CreateGauge<double>(name, "day", "Days before the certificate expires");
74+
75+
private readonly KeyValuePair<string, object>[] _tags = [
76+
new(Tags.ChainLevel, type),
77+
new(Tags.SerialNumber, cert.GetSerialNumberString()),
78+
new(Tags.Thumbprint, cert.Thumbprint),
79+
new(Tags.ValidityPeriod, ValidityPeriod(DateTimeOffset.FromUnixTimeSeconds(clock.SecondsSinceEpoch).DateTime, cert.NotBefore, cert.NotAfter).ToString())
80+
81+
82+
];
83+
84+
private static string ValidityPeriod(DateTime now, DateTime notBefore, DateTime notAfter) =>
85+
notBefore <= now && now <= notAfter
86+
? ValidityPeriods.InPeriod
87+
: now <= notBefore
88+
? ValidityPeriods.NotValidYet
89+
: ValidityPeriods.Expired;
90+
91+
public void Measure() {
92+
var certApocalypseIn = (cert.NotAfter - DateTime.Now).TotalDays;
93+
_gauge.Record(certApocalypseIn, _tags);
94+
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)