Skip to content

Commit 6661eb1

Browse files
committed
Report if an expiring certificate is externally signed
This is intended for externally-signed IPA CA certificates. It should not duplicate user-provided certificats because this check, IPACertmongerExpirationCheck, is based on certmonger tracking and we don't track, by default, user-provided certs. Fixes: #104 Signed-off-by: Rob Crittenden <rcritten@redhat.com>
1 parent 0b3151a commit 6661eb1

4 files changed

Lines changed: 123 additions & 10 deletions

File tree

src/ipahealthcheck/ipa/certs.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ def check(self):
304304
'nickname'))
305305
notafter = int(request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF,
306306
'not-valid-after'))
307+
cert = str(request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF,
308+
'cert'))
309+
307310
if notafter == 0:
308311
yield Result(self, constants.ERROR,
309312
key=id,
@@ -312,27 +315,53 @@ def check(self):
312315
'has not been issued yet.')
313316
continue
314317

318+
# if we have notafter then we should have a cert but lets not
319+
# assume.
320+
is_ipa_issued = None
321+
if cert:
322+
try:
323+
cert = x509.load_certificate_list(cert.encode('utf-8'))[0]
324+
except Exception as e:
325+
logger.debug("Failed to load certificate: ", e)
326+
else:
327+
is_ipa_issued = is_ipa_issued_cert(api, cert)
328+
329+
if is_ipa_issued is False:
330+
external_msg = (
331+
'This is not an IPA-issued certificate and '
332+
'will not auto-renew.')
333+
else:
334+
external_msg = ''
335+
315336
nafter = datetime.fromtimestamp(notafter, timezone.utc)
316337
now = datetime.now(timezone.utc)
317338

318339
if now > nafter:
340+
msg = (
341+
'Request id {key} expired on {expiration_date}. '
342+
+ external_msg)
319343
yield Result(self, constants.ERROR,
320344
key=id,
321345
expiration_date=generalized_time(nafter),
322-
msg='Request id {key} expired on '
323-
'{expiration_date}')
346+
msg=msg)
324347
else:
325348
delta = nafter - now
326349
diff = int(delta.total_seconds() / DAY)
327350
if diff < int(self.config.cert_expiration_days):
351+
msg = 'Request id {key} expires in {days} days. '
352+
if external_msg:
353+
msg = msg + external_msg
354+
else:
355+
msg = msg + (
356+
'certmonger should renew this '
357+
'automatically. Watch the status with '
358+
'getcert list -i {key}.'
359+
)
328360
yield Result(self, constants.WARNING,
329361
key=id,
330362
expiration_date=generalized_time(nafter),
331363
days=diff,
332-
msg='Request id {key} expires in {days} '
333-
'days. certmonger should renew this '
334-
'automatically. Watch the status with '
335-
'getcert list -i {key}.')
364+
msg=msg)
336365
else:
337366
yield Result(self, constants.SUCCESS,
338367
key=id)

tests/mock_certmonger.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
'cert-storage': 'FILE',
2424
'cert-presave-command': template % 'renew_ra_cert_pre',
2525
'cert-postsave-command': template % 'renew_ra_cert',
26+
'cert': '----- BEGIN -----',
2627
'not-valid-after': (
2728
int(
2829
datetime(1970, 1, 1, 0, 17, 4, tzinfo=timezone.utc).timestamp()
@@ -37,6 +38,7 @@
3738
'template_profile': 'caIPAserviceCert',
3839
'cert-storage': 'FILE',
3940
'cert-postsave-command': template % 'restart_httpd',
41+
'cert': '----- BEGIN -----',
4042
'not-valid-after': (
4143
int(
4244
(

tests/test_ipa_expiration.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,21 @@ class TestExpiration(BaseTest):
2929
'ipalib.install.certmonger._certmonger':
3030
Mock(return_value=_certmonger())
3131
}
32+
root_ca = 'CN=Certificate Authority,O=EXAMPLE.TEST'
33+
subject = 'CN=Certificate Authority,O=EXAMPLE.TEST'
3234

33-
def test_expiration(self):
35+
@patch('ipalib.x509.load_certificate_list')
36+
@patch('ipahealthcheck.ipa.certs.is_ipa_issued_cert')
37+
def test_expiration(self, mock_external, mock_load):
38+
mock_external.return_value = True
39+
mock_load.return_value = [
40+
FakeIPACertificate(
41+
None,
42+
subject=self.subject,
43+
issuer=self.root_ca,
44+
not_after=datetime.now(timezone.utc) + timedelta(days=20)
45+
),
46+
]
3447
set_requests()
3548

3649
framework = object()
@@ -55,14 +68,75 @@ def test_expiration(self):
5568
assert result.check == 'IPACertmongerExpirationCheck'
5669
assert result.kw.get('key') == '5678'
5770

58-
def test_expiration_warning(self):
71+
@patch('ipalib.x509.load_certificate_list')
72+
@patch('ipahealthcheck.ipa.certs.is_ipa_issued_cert')
73+
def test_expiration_warning(self, mock_external, mock_load):
74+
mock_external.return_value = True
75+
mock_load.return_value = [
76+
FakeIPACertificate(
77+
None,
78+
subject=self.subject,
79+
issuer=self.root_ca,
80+
not_after=datetime.now(timezone.utc) + timedelta(days=20)
81+
),
82+
]
83+
warning = datetime.now(timezone.utc) + timedelta(days=20)
84+
replaceme = {
85+
'nickname': '7777',
86+
'cert-file': paths.RA_AGENT_PEM,
87+
'key-file': paths.RA_AGENT_KEY,
88+
'ca-name': 'dogtag-ipa-ca-renew-agent',
89+
'not-valid-after': int(warning.timestamp()),
90+
'cert': '----- BEGIN -----'
91+
}
92+
93+
set_requests(remove=0, add=replaceme)
94+
95+
framework = object()
96+
registry.initialize(framework, config.Config)
97+
f = IPACertmongerExpirationCheck(registry)
98+
99+
f.config.cert_expiration_days = str(CERT_EXPIRATION_DAYS)
100+
self.results = capture_results(f)
101+
102+
assert len(self.results) == 2
103+
104+
result = self.results.results[0]
105+
assert result.result == constants.SUCCESS
106+
assert result.source == 'ipahealthcheck.ipa.certs'
107+
assert result.check == 'IPACertmongerExpirationCheck'
108+
assert result.kw.get('key') == '5678'
109+
110+
result = self.results.results[1]
111+
assert result.result == constants.WARNING
112+
assert result.source == 'ipahealthcheck.ipa.certs'
113+
assert result.check == 'IPACertmongerExpirationCheck'
114+
assert result.kw.get('key') == '7777'
115+
assert result.kw.get('days') == 19
116+
assert 'This is not an IPA-issued cert' not in result.kw.get('msg')
117+
118+
@patch('ipalib.x509.load_certificate_list')
119+
@patch('ipahealthcheck.ipa.certs.is_ipa_issued_cert')
120+
def test_external_expiration_warning(self, mock_external, mock_load):
121+
root_ca = 'CN=Certificate Shack Root CA,O=Certificate Shack Ltd'
122+
subject = 'CN=Certificate Authority,O=EXAMPLE.TEST'
123+
mock_external.return_value = False
124+
mock_load.return_value = [
125+
FakeIPACertificate(
126+
None,
127+
subject=subject,
128+
issuer=root_ca,
129+
not_after=datetime.now(timezone.utc) + timedelta(days=20)
130+
),
131+
]
59132
warning = datetime.now(timezone.utc) + timedelta(days=20)
60133
replaceme = {
61134
'nickname': '7777',
62135
'cert-file': paths.RA_AGENT_PEM,
63136
'key-file': paths.RA_AGENT_KEY,
64137
'ca-name': 'dogtag-ipa-ca-renew-agent',
65138
'not-valid-after': int(warning.timestamp()),
139+
'cert': '----- BEGIN -----'
66140
}
67141

68142
set_requests(remove=0, add=replaceme)
@@ -88,17 +162,24 @@ def test_expiration_warning(self):
88162
assert result.check == 'IPACertmongerExpirationCheck'
89163
assert result.kw.get('key') == '7777'
90164
assert result.kw.get('days') == 19
165+
assert 'This is not an IPA-issued cert' in result.kw.get('msg')
91166

92167

93168
class FakeIPACertificate:
94-
def __init__(self, cert, backend=None, subject=None, not_after=None):
169+
def __init__(self, cert, backend=None, subject=None, issuer=None,
170+
not_after=None):
95171
self.subj = subject
172+
self._issuer = issuer
96173
self.not_after = not_after
97174

98175
@property
99176
def subject(self):
100177
return self.subj
101178

179+
@property
180+
def issuer(self):
181+
return self._issuer
182+
102183
@property
103184
def not_valid_after_utc(self):
104185
return self.not_after

tests/test_ipa_tracking.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def test_missing_cert_tracking(self):
5959
"cert-presave-command=" \
6060
"/usr/libexec/ipa/certmonger/renew_ra_cert_pre, " \
6161
"cert-postsave-command=" \
62-
"/usr/libexec/ipa/certmonger/renew_ra_cert"
62+
"/usr/libexec/ipa/certmonger/renew_ra_cert, " \
63+
"cert=----- BEGIN -----"
6364

6465
def test_unknown_cert_tracking(self):
6566
# Add a custom, unknown request

0 commit comments

Comments
 (0)