Skip to content

Commit 97975ef

Browse files
authored
Merge pull request #20 from Icinga/features/Certificates
Add IcingaCheckCertificate
2 parents 1b48f18 + 40e85c5 commit 97975ef

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
<#
2+
.SYNOPSIS
3+
Check whether a certificate is still trusted and when it runs out or starts.
4+
.DESCRIPTION
5+
Invoke-IcingaCheckCertificate returns either 'OK', 'WARNING' or 'CRITICAL', based on the thresholds set.
6+
e.g a certificate will run out in 30 days, WARNING is set to '20d:', CRITICAL is set to '50d:'. In this case the check will return 'WARNING'.
7+
8+
More Information on https://github.com/Icinga/icinga-powershell-plugins
9+
.FUNCTIONALITY
10+
This module is intended to be used to check if a certificate is still valid or about to become valid.
11+
.EXAMPLE
12+
You can check certificates in the local certificate store of Windows:
13+
14+
PS> Invoke-IcingaCheckCertificate -CertStore 'LocalMachine' -CertStorePath 'My' -CertSubject '*' -WarningEnd '30d:' -CriticalEnd '10d:'
15+
[OK] Check package "Certificates" (Match All)
16+
\_ [OK] Certificate 'test.example.com' (valid until 2033-11-19 : 4993d) valid for: 431464965.59
17+
.EXAMPLE
18+
Also a directory with a file name pattern is possible:
19+
20+
PS> Invoke-IcingaCheckCertificate -CertPaths "C:\ProgramData\icinga2\var\lib\icinga2\certs" -CertName '*.crt' -WarningEnd '10000d:'
21+
[WARNING] Check package "Certificates" (Match All) - [WARNING] Certificate 'test.example.com' (valid until 2033-11-19 : 4993d) valid for, Certificate 'Icinga CA' (valid until 2032-09-18 : 4566d) valid for
22+
\_ [WARNING] Certificate 'test.example.com' (valid until 2033-11-19 : 4993d) valid for: Value "431464907.76" is lower than threshold "864000000"
23+
\_ [WARNING] Certificate 'Icinga CA' (valid until 2032-09-18 : 4566d) valid for: Value "394583054.72" is lower than threshold "864000000"
24+
.EXAMPLE
25+
The checks can be combined into a single check:
26+
27+
PS> Invoke-IcingaCheckCertificate -CertStore 'LocalMachine' -CertStorePath 'My' -CertThumbprint '*'-CertPaths "C:\ProgramData\icinga2\var\lib\icinga2\certs" -CertName '*.crt' -Trusted
28+
[CRITICAL] Check package "Certificates" (Match All) - [CRITICAL] Certificate 'test.example.com' trusted, Certificate 'Icinga CA' trusted
29+
\_ [CRITICAL] Check package "Certificate 'test.example.com'" (Match All)
30+
\_ [OK] Certificate 'test.example.com' (valid until 2033-11-19 : 4993d) valid for: 431464853.88
31+
\_ [CRITICAL] Certificate 'test.example.com' trusted: Value "False" is not matching threshold "True"
32+
\_ [CRITICAL] Check package "Certificate 'Icinga CA'" (Match All)
33+
\_ [OK] Certificate 'Icinga CA' (valid until 2032-09-18 : 4566d) valid for: 394583000.86
34+
\_ [CRITICAL] Certificate 'Icinga CA' trusted: Value "False" is not matching threshold "True"
35+
36+
.PARAMETER Trusted
37+
Used to switch on trusted behavior. Whether to check, If the certificate is trusted by the system root.
38+
Will return Critical in case of untrust.
39+
40+
Note: it is currently required that the root and intermediate CA is known and trusted by the local system.
41+
42+
.PARAMETER CriticalStart
43+
Used to specify a date. The start date of the certificate has to be past the date specified, otherwise the check results in critical. Use carefully.
44+
Use format like: 'yyyy-MM-dd'
45+
46+
.PARAMETER WarningEnd
47+
Used to specify a Warning range for the end date of an certificate. In this case a string.
48+
Allowed units include: ms, s, m, h, d, w, M, y
49+
50+
.PARAMETER CriticalEnd
51+
Used to specify a Critical range for the end date of an certificate. In this case a string.
52+
Allowed units include: ms, s, m, h, d, w, M, y
53+
54+
.PARAMETER CertStore
55+
Used to specify which CertStore to check. Valid choices are '*', 'LocalMachine', 'CurrentUser', ''
56+
57+
.PARAMETER CertThumbprint
58+
Used to specify an array of Thumbprints, which are used to determine what certificate to check, within the CertStore.
59+
60+
.PARAMETER CertSubject
61+
Used to specify an array of Subjects, which are used to determine what certificate to check, within the CertStore.
62+
63+
.PARAMETER CertStorePath
64+
Used to specify which path within the CertStore should be checked.
65+
66+
.PARAMETER CertPaths
67+
Used to specify an array of paths on your system, where certificate files are. Use with CertName.
68+
69+
.PARAMETER CertName
70+
Used to specify an array of certificate names of certificate files to check. Use with CertPaths.
71+
72+
.INPUTS
73+
System.String
74+
.OUTPUTS
75+
System.String
76+
.LINK
77+
https://github.com/Icinga/icinga-powershell-plugins
78+
.NOTES
79+
#>
80+
81+
function Invoke-IcingaCheckCertificate()
82+
{
83+
param(
84+
#Checking
85+
[switch]$Trusted = $FALSE,
86+
$CriticalStart = $null,
87+
$WarningEnd = '30d:',
88+
$CriticalEnd = '10d:',
89+
#CertStore-Related Param
90+
[ValidateSet('*', 'LocalMachine', 'CurrentUser', $null)]
91+
[string]$CertStore = $null,
92+
[array]$CertThumbprint = $null,
93+
[array]$CertSubject = $null,
94+
$CertStorePath = '*',
95+
#Local Certs
96+
[array]$CertPaths = $null,
97+
[array]$CertName = $null,
98+
[switch]$Recurse = $FALSE,
99+
#Other
100+
[ValidateSet(0, 1, 2, 3)]
101+
[int]$Verbosity = 3
102+
);
103+
104+
$CertData = Get-IcingaCertificateData `
105+
-CertStore $CertStore -CertThumbprint $CertThumbprint -CertSubject $CertSubject `
106+
-CertPaths $CertPaths -CertName $CertName -CertStorePath $CertStorePath -Recurse $Recurse;
107+
$CertPackage = New-IcingaCheckPackage -Name 'Certificates' -OperatorAnd -Verbose $Verbosity;
108+
109+
if ($null -ne $CriticalStart) {
110+
try {
111+
[datetime]$CritDateTime = $CriticalStart
112+
} catch {
113+
Exit-IcingaThrowException -ExceptionType 'Custom' -CustomMessage 'DateTimeParseError' -InputString (
114+
[string]::Format('The provided value "{0}" for argument "CriticalStart" could not be parsed as DateTime.', $CriticalStart)
115+
) -Force;
116+
}
117+
}
118+
119+
foreach ($data in $CertData) {
120+
$Cert = $data.Cert;
121+
122+
if ($null -eq $Cert) {
123+
# Not a valid cert file - unknown check
124+
$CertPackage.AddCheck(
125+
(New-IcingaCheck -Name ([string]::Format("Not a certificate: {0}", $data.Path))).SetUnknown()
126+
);
127+
continue;
128+
}
129+
130+
$SpanTilAfter = (New-TimeSpan -Start (Get-Date) -End $Cert.NotAfter);
131+
if ($Cert.Subject.Contains(',')) {
132+
[string]$CertName = $Cert.Subject.Split(",")[0];
133+
} else {
134+
[string]$CertName = $Cert.Subject;
135+
}
136+
137+
$CertName = $CertName -ireplace '(cn|ou)=', '';
138+
$CheckNamePrefix = "Certificate '${CertName}'";
139+
if ($data.ContainsKey('Path')) {
140+
$CheckNamePrefix += (" at " + $data.Path)
141+
}
142+
143+
$checks = @();
144+
145+
if ($Trusted) {
146+
$CertValid = Test-Certificate $cert -ErrorAction SilentlyContinue -WarningAction SilentlyContinue;
147+
$IcingaCheck = New-IcingaCheck -Name "${CheckNamePrefix} trusted" -Value $CertValid;
148+
$IcingaCheck.CritIfNotMatch($TRUE) | Out-Null;
149+
$checks += $IcingaCheck;
150+
}
151+
152+
if ($null -ne $CriticalStart) {
153+
$CritStart = ((New-TimeSpan -Start $Cert.NotBefore -End $CritDateTime) -gt 0)
154+
$IcingaCheck = New-IcingaCheck -Name "${CheckNamePrefix} already valid" -Value $CritStart;
155+
$IcingaCheck.CritIfNotMatch($TRUE) | Out-Null;
156+
$checks += $IcingaCheck;
157+
}
158+
159+
if(($null -ne $WarningEnd) -Or ($null -ne $CriticalEnd)) {
160+
$ValidityInfo = ([string]::Format('valid until {0} : {1}d', $Cert.NotAfter.ToString('yyyy-MM-dd'), $SpanTilAfter.Days));
161+
$IcingaCheck = New-IcingaCheck -Name "${CheckNamePrefix} ($ValidityInfo) valid for" -Value (New-TimeSpan -End $Cert.NotAfter.DateTime).TotalSeconds;
162+
$IcingaCheck.WarnOutOfRange((ConvertTo-SecondsFromIcingaThresholds -Threshold $WarningEnd)).CritOutOfRange((ConvertTo-SecondsFromIcingaThresholds -Threshold $CriticalEnd)) | Out-Null;
163+
$checks += $IcingaCheck;
164+
}
165+
166+
if ($checks.Length -eq 1) {
167+
# Only add one check instead of the package
168+
$CertPackage.AddCheck($checks[0])
169+
} else {
170+
$CertCheck = New-IcingaCheckPackage -Name $CheckNamePrefix -OperatorAnd;
171+
foreach ($check in $checks) {
172+
$CertCheck.AddCheck($check)
173+
}
174+
$CertPackage.AddCheck($CertCheck)
175+
}
176+
}
177+
178+
return (New-IcingaCheckResult -Name 'Certificates' -Check $CertPackage -NoPerfData $TRUE -Compile);
179+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
function Get-IcingaCertificateData()
2+
{
3+
param(
4+
#CertStore-Related Param
5+
[ValidateSet('*', 'LocalMachine', 'CurrentUser', $null)]
6+
[string]$CertStore = $null,
7+
[array]$CertThumbprint = $null,
8+
[array]$CertSubject = $null,
9+
$CertStorePath = '*',
10+
#Local Certs
11+
[array]$CertPaths = $null,
12+
[array]$CertName = $null,
13+
[bool]$Recurse = $FALSE
14+
);
15+
16+
[array]$CertData = @();
17+
18+
if ([string]::IsNullOrEmpty($CertStore) -eq $FALSE){
19+
$CertData += Get-IcingaCertStoreCertificates -CertStore $CertStore -CertThumbprint $CertThumbprint -CertSubject $CertSubject -CertStorePath $CertStorePath;
20+
}
21+
22+
if (($null -ne $CertPaths) -or ($null -ne $CertName)) {
23+
$CertDataFile = @();
24+
25+
foreach ($path in $CertPaths) {
26+
foreach ($name in $CertName) {
27+
$searchPath = $path;
28+
[array]$files = Get-ChildItem -Recurse:$Recurse -Filter $name -Path $searchPath;
29+
30+
if ($null -ne $files) {
31+
$CertDataFile += $files;
32+
} else {
33+
# Remember that pattern didn't match
34+
if ($CertPaths.length -eq 1) {
35+
$certPath = $name;
36+
} else {
37+
$certPath = "${path}\${name}";
38+
}
39+
$CertData += @{
40+
Path = $certPath;
41+
Cert = $null;
42+
};
43+
}
44+
}
45+
}
46+
}
47+
48+
if ($null -ne $CertDataFile) {
49+
foreach ($Cert in $CertDataFile) {
50+
$path = $Cert.FullName;
51+
52+
if ($CertPaths.length -eq 1) {
53+
$path = $path.Replace("${CertPaths}\", '');
54+
}
55+
56+
try {
57+
$CertConverted = New-Object Security.Cryptography.X509Certificates.X509Certificate2 $Cert.FullName;
58+
$CertData += @{
59+
Path = $path;
60+
Cert = $CertConverted;
61+
};
62+
} catch {
63+
# Not a valid certificate
64+
$CertData += @{
65+
Path = $path;
66+
Cert = $null;
67+
};
68+
}
69+
}
70+
}
71+
72+
return $CertData;
73+
}
74+
75+
function Get-IcingaCertStoreCertificates()
76+
{
77+
param (
78+
#CertStore-Related Param
79+
[ValidateSet('*', 'LocalMachine', 'CurrentUser')]
80+
[string]$CertStore = '*',
81+
[array]$CertThumbprint = @(),
82+
[array]$CertSubject = @(),
83+
$CertStorePath = '*'
84+
);
85+
86+
$CertStoreArray = @();
87+
$CertStorePath = [string]::Format('Cert:\{0}\{1}', $CertStore, $CertStorePath);
88+
$CertStoreCerts = Get-ChildItem -Path $CertStorePath -Recurse;
89+
90+
if ($CertSubject.Count -eq 0 -And $CertThumbprint.Count -eq 0) {
91+
$CertSubject += '*';
92+
}
93+
94+
foreach ($Cert in $CertStoreCerts) {
95+
$data = @{
96+
Thumbprint = $Cert.Thumbprint;
97+
Cert = $Cert;
98+
}
99+
if (($CertThumbprint -Contains '*') -Or ($CertThumbprint -Contains $Cert.Thumbprint)) {
100+
$CertStoreArray += $data;
101+
continue;
102+
}
103+
104+
foreach ($Subject in $CertSubject) {
105+
if ($Subject -eq '*' -Or ($Cert.Subject -Like $Subject)) {
106+
$CertStoreArray += $data;
107+
break;
108+
}
109+
}
110+
}
111+
112+
return $CertStoreArray;
113+
}

0 commit comments

Comments
 (0)