|
|
6ec482 |
From f830f450c0c5818090eba9f9f0e0cec5551a1cef Mon Sep 17 00:00:00 2001
|
|
|
6ec482 |
From: Fraser Tweedale <ftweedal@redhat.com>
|
|
|
6ec482 |
Date: Thu, 30 May 2019 20:57:10 +1000
|
|
|
6ec482 |
Subject: [PATCH] Handle missing LWCA certificate or chain
|
|
|
6ec482 |
|
|
|
6ec482 |
If lightweight CA key replication has not completed, requests for
|
|
|
6ec482 |
the certificate or chain will return 404**. This can occur in
|
|
|
6ec482 |
normal operation, and should be a temporary condition. Detect this
|
|
|
6ec482 |
case and handle it by simply omitting the 'certificate' and/or
|
|
|
6ec482 |
'certificate_out' fields in the response, and add a warning message
|
|
|
6ec482 |
to the response.
|
|
|
6ec482 |
|
|
|
6ec482 |
Also update the client-side plugin that handles the
|
|
|
6ec482 |
--certificate-out option. Because the CLI will automatically print
|
|
|
6ec482 |
the warning message, if the expected field is missing from the
|
|
|
6ec482 |
response, just ignore it and continue processing.
|
|
|
6ec482 |
|
|
|
6ec482 |
** after the Dogtag NullPointerException gets fixed!
|
|
|
6ec482 |
|
|
|
6ec482 |
Part of: https://pagure.io/freeipa/issue/7964
|
|
|
6ec482 |
|
|
|
6ec482 |
Reviewed-By: Christian Heimes <cheimes@redhat.com>
|
|
|
6ec482 |
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
|
|
|
6ec482 |
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
|
|
|
6ec482 |
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
|
|
6ec482 |
---
|
|
|
6ec482 |
ipaclient/plugins/ca.py | 19 +++++++++++---
|
|
|
6ec482 |
ipalib/messages.py | 9 +++++++
|
|
|
6ec482 |
ipaserver/plugins/ca.py | 57 +++++++++++++++++++++++++++++++----------
|
|
|
6ec482 |
3 files changed, 68 insertions(+), 17 deletions(-)
|
|
|
6ec482 |
|
|
|
6ec482 |
diff --git a/ipaclient/plugins/ca.py b/ipaclient/plugins/ca.py
|
|
|
6ec482 |
index f0e7d5ced0d3d9318e34aba84cbc37cf42b9410d..ab47ae85df398e1dc40191691a26639eb3772493 100644
|
|
|
6ec482 |
--- a/ipaclient/plugins/ca.py
|
|
|
6ec482 |
+++ b/ipaclient/plugins/ca.py
|
|
|
6ec482 |
@@ -33,13 +33,24 @@ class WithCertOutArgs(MethodOverride):
|
|
|
6ec482 |
error=str(e))
|
|
|
6ec482 |
|
|
|
6ec482 |
result = super(WithCertOutArgs, self).forward(*keys, **options)
|
|
|
6ec482 |
+
|
|
|
6ec482 |
if filename:
|
|
|
6ec482 |
+ # if result certificate / certificate_chain not present in result,
|
|
|
6ec482 |
+ # it means Dogtag did not provide it (probably due to LWCA key
|
|
|
6ec482 |
+ # replication lag or failure. The server transmits a warning
|
|
|
6ec482 |
+ # message in this case, which the client automatically prints.
|
|
|
6ec482 |
+ # So in this section we just ignore it and move on.
|
|
|
6ec482 |
+ certs = None
|
|
|
6ec482 |
if options.get('chain', False):
|
|
|
6ec482 |
- certs = result['result']['certificate_chain']
|
|
|
6ec482 |
+ if 'certificate_chain' in result['result']:
|
|
|
6ec482 |
+ certs = result['result']['certificate_chain']
|
|
|
6ec482 |
else:
|
|
|
6ec482 |
- certs = [base64.b64decode(result['result']['certificate'])]
|
|
|
6ec482 |
- certs = (x509.load_der_x509_certificate(cert) for cert in certs)
|
|
|
6ec482 |
- x509.write_certificate_list(certs, filename)
|
|
|
6ec482 |
+ if 'certificate' in result['result']:
|
|
|
6ec482 |
+ certs = [base64.b64decode(result['result']['certificate'])]
|
|
|
6ec482 |
+ if certs:
|
|
|
6ec482 |
+ x509.write_certificate_list(
|
|
|
6ec482 |
+ (x509.load_der_x509_certificate(cert) for cert in certs),
|
|
|
6ec482 |
+ filename)
|
|
|
6ec482 |
|
|
|
6ec482 |
return result
|
|
|
6ec482 |
|
|
|
6ec482 |
diff --git a/ipalib/messages.py b/ipalib/messages.py
|
|
|
6ec482 |
index 9e2c990d6db8ee41daf3fba6085eed8355dccbe7..646662795648b5a44a5ce25b7610982d5500cfac 100644
|
|
|
6ec482 |
--- a/ipalib/messages.py
|
|
|
6ec482 |
+++ b/ipalib/messages.py
|
|
|
6ec482 |
@@ -487,6 +487,15 @@ class FailedToAddHostDNSRecords(PublicMessage):
|
|
|
6ec482 |
"%(reason)s")
|
|
|
6ec482 |
|
|
|
6ec482 |
|
|
|
6ec482 |
+class LightweightCACertificateNotAvailable(PublicMessage):
|
|
|
6ec482 |
+ """
|
|
|
6ec482 |
+ **13031** Certificate is not available
|
|
|
6ec482 |
+ """
|
|
|
6ec482 |
+ errno = 13031
|
|
|
6ec482 |
+ type = "error"
|
|
|
6ec482 |
+ format = _("The certificate for %(ca)s is not available on this server.")
|
|
|
6ec482 |
+
|
|
|
6ec482 |
+
|
|
|
6ec482 |
def iter_messages(variables, base):
|
|
|
6ec482 |
"""Return a tuple with all subclasses
|
|
|
6ec482 |
"""
|
|
|
6ec482 |
diff --git a/ipaserver/plugins/ca.py b/ipaserver/plugins/ca.py
|
|
|
6ec482 |
index 88e7ec2a9f50a3c4f90947c8e3d38e327627a878..c8f1630c65d55ee9e820ea50ef34e08f92c66f4a 100644
|
|
|
6ec482 |
--- a/ipaserver/plugins/ca.py
|
|
|
6ec482 |
+++ b/ipaserver/plugins/ca.py
|
|
|
6ec482 |
@@ -6,7 +6,7 @@ import base64
|
|
|
6ec482 |
|
|
|
6ec482 |
import six
|
|
|
6ec482 |
|
|
|
6ec482 |
-from ipalib import api, errors, output, Bytes, DNParam, Flag, Str
|
|
|
6ec482 |
+from ipalib import api, errors, messages, output, Bytes, DNParam, Flag, Str
|
|
|
6ec482 |
from ipalib.constants import IPA_CA_CN
|
|
|
6ec482 |
from ipalib.plugable import Registry
|
|
|
6ec482 |
from ipapython.dn import ATTR_NAME_BY_OID
|
|
|
6ec482 |
@@ -163,28 +163,53 @@ class ca(LDAPObject):
|
|
|
6ec482 |
|
|
|
6ec482 |
|
|
|
6ec482 |
def set_certificate_attrs(entry, options, want_cert=True):
|
|
|
6ec482 |
+ """
|
|
|
6ec482 |
+ Set certificate attributes into the entry. Depending on
|
|
|
6ec482 |
+ options, this may contact Dogtag to retrieve certificate or
|
|
|
6ec482 |
+ chain. If the retrieval fails with 404 (which can occur under
|
|
|
6ec482 |
+ normal operation due to lightweight CA key replication delay),
|
|
|
6ec482 |
+ return a message object that should be set in the response.
|
|
|
6ec482 |
+
|
|
|
6ec482 |
+ """
|
|
|
6ec482 |
try:
|
|
|
6ec482 |
ca_id = entry['ipacaid'][0]
|
|
|
6ec482 |
except KeyError:
|
|
|
6ec482 |
- return
|
|
|
6ec482 |
+ return None
|
|
|
6ec482 |
full = options.get('all', False)
|
|
|
6ec482 |
want_chain = options.get('chain', False)
|
|
|
6ec482 |
|
|
|
6ec482 |
want_data = want_cert or want_chain or full
|
|
|
6ec482 |
if not want_data:
|
|
|
6ec482 |
- return
|
|
|
6ec482 |
+ return None
|
|
|
6ec482 |
+
|
|
|
6ec482 |
+ msg = None
|
|
|
6ec482 |
|
|
|
6ec482 |
with api.Backend.ra_lightweight_ca as ca_api:
|
|
|
6ec482 |
if want_cert or full:
|
|
|
6ec482 |
- der = ca_api.read_ca_cert(ca_id)
|
|
|
6ec482 |
- entry['certificate'] = base64.b64encode(der).decode('ascii')
|
|
|
6ec482 |
+ try:
|
|
|
6ec482 |
+ der = ca_api.read_ca_cert(ca_id)
|
|
|
6ec482 |
+ entry['certificate'] = base64.b64encode(der).decode('ascii')
|
|
|
6ec482 |
+ except errors.HTTPRequestError as e:
|
|
|
6ec482 |
+ if e.status == 404: # pylint: disable=no-member
|
|
|
6ec482 |
+ msg = messages.LightweightCACertificateNotAvailable(
|
|
|
6ec482 |
+ ca=entry['cn'][0])
|
|
|
6ec482 |
+ else:
|
|
|
6ec482 |
+ raise e
|
|
|
6ec482 |
|
|
|
6ec482 |
if want_chain or full:
|
|
|
6ec482 |
- pkcs7_der = ca_api.read_ca_chain(ca_id)
|
|
|
6ec482 |
- certs = x509.pkcs7_to_certs(pkcs7_der, x509.DER)
|
|
|
6ec482 |
- ders = [cert.public_bytes(x509.Encoding.DER) for cert in certs]
|
|
|
6ec482 |
- entry['certificate_chain'] = ders
|
|
|
6ec482 |
-
|
|
|
6ec482 |
+ try:
|
|
|
6ec482 |
+ pkcs7_der = ca_api.read_ca_chain(ca_id)
|
|
|
6ec482 |
+ certs = x509.pkcs7_to_certs(pkcs7_der, x509.DER)
|
|
|
6ec482 |
+ ders = [cert.public_bytes(x509.Encoding.DER) for cert in certs]
|
|
|
6ec482 |
+ entry['certificate_chain'] = ders
|
|
|
6ec482 |
+ except errors.HTTPRequestError as e:
|
|
|
6ec482 |
+ if e.status == 404: # pylint: disable=no-member
|
|
|
6ec482 |
+ msg = messages.LightweightCACertificateNotAvailable(
|
|
|
6ec482 |
+ ca=entry['cn'][0])
|
|
|
6ec482 |
+ else:
|
|
|
6ec482 |
+ raise e
|
|
|
6ec482 |
+
|
|
|
6ec482 |
+ return msg
|
|
|
6ec482 |
|
|
|
6ec482 |
@register()
|
|
|
6ec482 |
class ca_find(LDAPSearch):
|
|
|
6ec482 |
@@ -198,7 +223,9 @@ class ca_find(LDAPSearch):
|
|
|
6ec482 |
result = super(ca_find, self).execute(*keys, **options)
|
|
|
6ec482 |
if not options.get('pkey_only', False):
|
|
|
6ec482 |
for entry in result['result']:
|
|
|
6ec482 |
- set_certificate_attrs(entry, options, want_cert=False)
|
|
|
6ec482 |
+ msg = set_certificate_attrs(entry, options, want_cert=False)
|
|
|
6ec482 |
+ if msg:
|
|
|
6ec482 |
+ self.add_message(msg)
|
|
|
6ec482 |
return result
|
|
|
6ec482 |
|
|
|
6ec482 |
|
|
|
6ec482 |
@@ -220,7 +247,9 @@ class ca_show(LDAPRetrieve):
|
|
|
6ec482 |
def execute(self, *keys, **options):
|
|
|
6ec482 |
ca_enabled_check(self.api)
|
|
|
6ec482 |
result = super(ca_show, self).execute(*keys, **options)
|
|
|
6ec482 |
- set_certificate_attrs(result['result'], options)
|
|
|
6ec482 |
+ msg = set_certificate_attrs(result['result'], options)
|
|
|
6ec482 |
+ if msg:
|
|
|
6ec482 |
+ self.add_message(msg)
|
|
|
6ec482 |
return result
|
|
|
6ec482 |
|
|
|
6ec482 |
|
|
|
6ec482 |
@@ -284,7 +313,9 @@ class ca_add(LDAPCreate):
|
|
|
6ec482 |
return dn
|
|
|
6ec482 |
|
|
|
6ec482 |
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
|
|
6ec482 |
- set_certificate_attrs(entry_attrs, options)
|
|
|
6ec482 |
+ msg = set_certificate_attrs(entry_attrs, options)
|
|
|
6ec482 |
+ if msg:
|
|
|
6ec482 |
+ self.add_message(msg)
|
|
|
6ec482 |
return dn
|
|
|
6ec482 |
|
|
|
6ec482 |
|
|
|
6ec482 |
--
|
|
|
6ec482 |
2.20.1
|
|
|
6ec482 |
|