Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions src/ipahealthcheck/ipa/certs.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,9 @@ def check(self):
'nickname'))
notafter = int(request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF,
'not-valid-after'))
cert = str(request.prop_if.Get(certmonger.DBUS_CM_REQUEST_IF,
'cert'))

if notafter == 0:
yield Result(self, constants.ERROR,
key=id,
Expand All @@ -312,27 +315,53 @@ def check(self):
'has not been issued yet.')
continue

# if we have notafter then we should have a cert but lets not
# assume.
is_ipa_issued = None
if cert:
try:
cert = x509.load_certificate_list(cert.encode('utf-8'))[0]
except Exception as e:
logger.debug("Failed to load certificate: ", e)
else:
is_ipa_issued = is_ipa_issued_cert(api, cert)

if is_ipa_issued is False:
external_msg = (
'This is not an IPA-issued certificate and '
'will not auto-renew.')
else:
external_msg = ''

nafter = datetime.fromtimestamp(notafter, timezone.utc)
now = datetime.now(timezone.utc)

if now > nafter:
msg = (
'Request id {key} expired on {expiration_date}. '
+ external_msg)
yield Result(self, constants.ERROR,
key=id,
expiration_date=generalized_time(nafter),
msg='Request id {key} expired on '
'{expiration_date}')
msg=msg)
else:
delta = nafter - now
diff = int(delta.total_seconds() / DAY)
if diff < int(self.config.cert_expiration_days):
msg = 'Request id {key} expires in {days} days. '
if external_msg:
msg = msg + external_msg
else:
msg = msg + (
'certmonger should renew this '
'automatically. Watch the status with '
'getcert list -i {key}.'
)
yield Result(self, constants.WARNING,
key=id,
expiration_date=generalized_time(nafter),
days=diff,
msg='Request id {key} expires in {days} '
'days. certmonger should renew this '
'automatically. Watch the status with '
'getcert list -i {key}.')
msg=msg)
else:
yield Result(self, constants.SUCCESS,
key=id)
Expand Down
2 changes: 2 additions & 0 deletions tests/mock_certmonger.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
'cert-storage': 'FILE',
'cert-presave-command': template % 'renew_ra_cert_pre',
'cert-postsave-command': template % 'renew_ra_cert',
'cert': '----- BEGIN -----',
'not-valid-after': (
int(
datetime(1970, 1, 1, 0, 17, 4, tzinfo=timezone.utc).timestamp()
Expand All @@ -37,6 +38,7 @@
'template_profile': 'caIPAserviceCert',
'cert-storage': 'FILE',
'cert-postsave-command': template % 'restart_httpd',
'cert': '----- BEGIN -----',
'not-valid-after': (
int(
(
Expand Down
87 changes: 84 additions & 3 deletions tests/test_ipa_expiration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,21 @@ class TestExpiration(BaseTest):
'ipalib.install.certmonger._certmonger':
Mock(return_value=_certmonger())
}
root_ca = 'CN=Certificate Authority,O=EXAMPLE.TEST'
subject = 'CN=Certificate Authority,O=EXAMPLE.TEST'

def test_expiration(self):
@patch('ipalib.x509.load_certificate_list')
@patch('ipahealthcheck.ipa.certs.is_ipa_issued_cert')
def test_expiration(self, mock_external, mock_load):
mock_external.return_value = True
mock_load.return_value = [
FakeIPACertificate(
None,
subject=self.subject,
issuer=self.root_ca,
not_after=datetime.now(timezone.utc) + timedelta(days=20)
),
]
set_requests()

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

def test_expiration_warning(self):
@patch('ipalib.x509.load_certificate_list')
@patch('ipahealthcheck.ipa.certs.is_ipa_issued_cert')
def test_expiration_warning(self, mock_external, mock_load):
mock_external.return_value = True
mock_load.return_value = [
FakeIPACertificate(
None,
subject=self.subject,
issuer=self.root_ca,
not_after=datetime.now(timezone.utc) + timedelta(days=20)
),
]
warning = datetime.now(timezone.utc) + timedelta(days=20)
replaceme = {
'nickname': '7777',
'cert-file': paths.RA_AGENT_PEM,
'key-file': paths.RA_AGENT_KEY,
'ca-name': 'dogtag-ipa-ca-renew-agent',
'not-valid-after': int(warning.timestamp()),
'cert': '----- BEGIN -----'
}

set_requests(remove=0, add=replaceme)

framework = object()
registry.initialize(framework, config.Config)
f = IPACertmongerExpirationCheck(registry)

f.config.cert_expiration_days = str(CERT_EXPIRATION_DAYS)
self.results = capture_results(f)

assert len(self.results) == 2

result = self.results.results[0]
assert result.result == constants.SUCCESS
assert result.source == 'ipahealthcheck.ipa.certs'
assert result.check == 'IPACertmongerExpirationCheck'
assert result.kw.get('key') == '5678'

result = self.results.results[1]
assert result.result == constants.WARNING
assert result.source == 'ipahealthcheck.ipa.certs'
assert result.check == 'IPACertmongerExpirationCheck'
assert result.kw.get('key') == '7777'
assert result.kw.get('days') == 19
assert 'This is not an IPA-issued cert' not in result.kw.get('msg')

@patch('ipalib.x509.load_certificate_list')
@patch('ipahealthcheck.ipa.certs.is_ipa_issued_cert')
def test_external_expiration_warning(self, mock_external, mock_load):
root_ca = 'CN=Certificate Shack Root CA,O=Certificate Shack Ltd'
subject = 'CN=Certificate Authority,O=EXAMPLE.TEST'
mock_external.return_value = False
mock_load.return_value = [
FakeIPACertificate(
None,
subject=subject,
issuer=root_ca,
not_after=datetime.now(timezone.utc) + timedelta(days=20)
),
]
warning = datetime.now(timezone.utc) + timedelta(days=20)
replaceme = {
'nickname': '7777',
'cert-file': paths.RA_AGENT_PEM,
'key-file': paths.RA_AGENT_KEY,
'ca-name': 'dogtag-ipa-ca-renew-agent',
'not-valid-after': int(warning.timestamp()),
'cert': '----- BEGIN -----'
}

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


class FakeIPACertificate:
def __init__(self, cert, backend=None, subject=None, not_after=None):
def __init__(self, cert, backend=None, subject=None, issuer=None,
not_after=None):
self.subj = subject
self._issuer = issuer
self.not_after = not_after

@property
def subject(self):
return self.subj

@property
def issuer(self):
return self._issuer

@property
def not_valid_after_utc(self):
return self.not_after
Expand Down
3 changes: 2 additions & 1 deletion tests/test_ipa_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def test_missing_cert_tracking(self):
"cert-presave-command=" \
"/usr/libexec/ipa/certmonger/renew_ra_cert_pre, " \
"cert-postsave-command=" \
"/usr/libexec/ipa/certmonger/renew_ra_cert"
"/usr/libexec/ipa/certmonger/renew_ra_cert, " \
"cert=----- BEGIN -----"

def test_unknown_cert_tracking(self):
# Add a custom, unknown request
Expand Down