Merge "Drop all remaining logics for certificate resources"

This commit is contained in:
Zuul 2024-03-08 16:18:59 +00:00 committed by Gerrit Code Review
commit b6edfda344
27 changed files with 12 additions and 6228 deletions

View File

@ -15,10 +15,10 @@ storage keys that are stored either in a software NSS database or in an HSM. It
can serve as a secret store for barbican, and interacts with barbican core through
the Dogtag KRA plugin.
In this guide, we will provide instructions on how to set up a basic Dogtag instance
containing a CA and a KRA, and how to configure barbican to use this instance for a
secret store and a certificate plugin. Much more detail about Dogtag, its deployment
options and its administration are available in the `RHCS documentation
In this guide, we will provide instructions on how to set up a basic Dogtag
instance containing a CA and a KRA, and how to configure barbican to use this
instance for a secret store. Much more detail about Dogtag, its deployment
options and its administration are available in the `RHCS documentation
<https://access.redhat.com/documentation/en-US/Red_Hat_Certificate_System>`_.
**Note:** The code below is taken from the devstack Barbican-Dogtag gate job. You can
@ -166,8 +166,8 @@ created with trusted agent credentials.
chown $USER $BARBICAN_CONF_DIR/kra_admin_cert.pem
The barbican config file (/etc/barbican/barbican.conf) needs to be modified.
The modifications below set the Dogtag plugins as the only enabled secret store and
certificate plugins. Be sure to restart barbican once these changes are made.
The modifications below set the Dogtag plugins as the only enabled secret store.
Makee sure to restart barbican once these changes are made.
Note that the actual hostname of the machine should be used in the script (rather
than localhost) because the hostname is used in the subject name for the SSL
@ -189,10 +189,6 @@ server certificate for the CA.
namespace = barbican.secretstore.plugin
enabled_secretstore_plugins = dogtag_crypto
[certificate]
namespace = barbican.certificate.plugin
enabled_certificate_plugins = dogtag
Testing the Setup
*****************

View File

@ -52,7 +52,6 @@ class States(object):
class OrderType(object):
KEY = 'key'
ASYMMETRIC = 'asymmetric'
CERTIFICATE = 'certificate'
@classmethod
def is_valid(cls, order_type):

View File

@ -14,30 +14,18 @@
# limitations under the License.
import base64
import copy
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import datetime
import os
from oslo_utils import uuidutils
import time
import pki
subcas_available = True
try:
import pki.authority as authority
import pki.feature as feature
except ImportError:
subcas_available = False
import pki.cert
import pki.client
import pki.crypto as cryptoutil
import pki.key as key
import pki.kra
import pki.profile
from requests import exceptions as request_exceptions
from barbican.common import exception
from barbican.common import utils
@ -47,7 +35,6 @@ from barbican import i18n as u
# do not need to import every dogtag requirement to generate the
# sample config
import barbican.plugin.dogtag_config_opts # noqa
import barbican.plugin.interface.certificate_manager as cm
import barbican.plugin.interface.secret_store as sstore
# reuse the conf object to not call config.new_config() twice
@ -618,734 +605,3 @@ class DogtagKRAPlugin(sstore.SecretStoreBase):
)
return twsk
def _catch_request_exception(ca_related_function):
def _catch_ca_unavailable(self, *args, **kwargs):
try:
return ca_related_function(self, *args, **kwargs)
except request_exceptions.RequestException:
return cm.ResultDTO(
cm.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST)
return _catch_ca_unavailable
def _catch_enrollment_exceptions(ca_related_function):
def _catch_enrollment_exception(self, *args, **kwargs):
try:
return ca_related_function(self, *args, **kwargs)
except pki.BadRequestException as e:
return cm.ResultDTO(
cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
status_message=e.message)
except pki.PKIException as e:
raise cm.CertificateGeneralException(
u._("Exception thrown by enroll_cert: {message}").format(
message=e.message))
return _catch_enrollment_exception
def _catch_subca_creation_exceptions(ca_related_function):
def _catch_subca_exception(self, *args, **kwargs):
try:
return ca_related_function(self, *args, **kwargs)
except pki.BadRequestException as e:
raise exception.BadSubCACreationRequest(reason=e.message)
except pki.PKIException as e:
raise exception.SubCACreationErrors(reason=e.message)
except request_exceptions.RequestException:
raise exception.SubCACreationErrors(
reason="Unable to connect to CA")
return _catch_subca_exception
def _catch_subca_deletion_exceptions(ca_related_function):
def _catch_subca_exception(self, *args, **kwargs):
try:
return ca_related_function(self, *args, **kwargs)
except pki.ResourceNotFoundException:
LOG.warning("Sub-CA already deleted")
pass
except pki.PKIException as e:
raise exception.SubCADeletionErrors(reason=e.message)
except request_exceptions.RequestException:
raise exception.SubCACreationErrors(
reason="Unable to connect to CA")
return _catch_subca_exception
class DogtagCAPlugin(cm.CertificatePluginBase):
"""Implementation of the cert plugin with Dogtag CA as the backend."""
# order_metadata fields
PROFILE_ID = "profile_id"
# plugin_metadata fields
REQUEST_ID = "request_id"
def __init__(self, conf=CONF):
"""Constructor - create the cert clients."""
connection = create_connection(conf, 'ca')
self.certclient = pki.cert.CertClient(connection)
self.simple_cmc_profile = conf.dogtag_plugin.simple_cmc_profile
self.auto_approved_profiles = conf.dogtag_plugin.auto_approved_profiles
self.working_dir = conf.dogtag_plugin.plugin_working_dir
if not os.path.isdir(self.working_dir):
os.mkdir(self.working_dir)
self._expiration = None
self._expiration_delta = conf.dogtag_plugin.ca_expiration_time
self._expiration_data_path = os.path.join(self.working_dir,
"expiration_data.txt")
self._host_aid_path = os.path.join(self.working_dir, "host_aid.txt")
self._host_aid = None
if not os.path.isfile(self._expiration_data_path):
self.expiration = datetime.datetime.utcnow()
global subcas_available
subcas_available = self._are_subcas_enabled_on_backend(connection)
if subcas_available:
self.authority_client = authority.AuthorityClient(connection)
if not os.path.isfile(self._host_aid_path):
self.host_aid = self.get_host_aid()
@property
def expiration(self):
if self._expiration is None:
try:
with open(self._expiration_data_path) as expiration_fh:
self._expiration = datetime.datetime.strptime(
expiration_fh.read(),
"%Y-%m-%d %H:%M:%S.%f"
)
except (ValueError, TypeError):
LOG.warning("Invalid data read from expiration file")
self.expiration = datetime.utcnow()
return self._expiration
@expiration.setter
def expiration(self, val):
with open(self._expiration_data_path, 'w') as expiration_fh:
expiration_fh.write(val.strftime("%Y-%m-%d %H:%M:%S.%f"))
self._expiration = val
@property
def host_aid(self):
if self._host_aid is None:
with open(self._host_aid_path) as host_aid_fh:
self._host_aid = host_aid_fh.read()
return self._host_aid
@host_aid.setter
def host_aid(self, val):
if val is not None:
with open(self._host_aid_path, 'w') as host_aid_fh:
host_aid_fh.write(val)
self._host_aid = val
def _are_subcas_enabled_on_backend(self, connection):
"""Check if subca feature is available
SubCA creation must be supported in both the Dogtag client as well
as on the back-end server. Moreover, it must be enabled on the
backend server. This method sets the subcas_available global variable.
:return: True/False
"""
global subcas_available
if subcas_available:
# subcas are supported in the Dogtag client
try:
feature_client = feature.FeatureClient(connection)
authority_feature = feature_client.get_feature("authority")
if authority_feature.enabled:
LOG.info("Sub-CAs are enabled by Dogtag server")
return True
else:
LOG.info("Sub-CAs are not enabled by Dogtag server")
except (request_exceptions.HTTPError,
pki.ResourceNotFoundException):
LOG.info("Sub-CAs are not supported by Dogtag server")
else:
LOG.info("Sub-CAs are not supported by Dogtag client")
return False
def _get_request_id(self, order_id, plugin_meta, operation):
request_id = plugin_meta.get(self.REQUEST_ID, None)
if not request_id:
raise cm.CertificateGeneralException(
u._(
"{request} not found for {operation} for "
"order_id {order_id}"
).format(
request=self.REQUEST_ID,
operation=operation,
order_id=order_id
)
)
return request_id
@_catch_request_exception
def _get_request(self, request_id):
try:
return self.certclient.get_request(request_id)
except pki.RequestNotFoundException:
return None
@_catch_request_exception
def _get_cert(self, cert_id):
try:
return self.certclient.get_cert(cert_id)
except pki.CertNotFoundException:
return None
def get_default_ca_name(self):
return "Dogtag CA"
def get_default_signing_cert(self):
# TODO(alee) Add code to get the signing cert
return None
def get_default_intermediates(self):
# TODO(alee) Add code to get the cert chain
return None
def check_certificate_status(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Check the status of a certificate request.
:param order_id: ID of the order associated with this request
:param order_meta: order_metadata associated with this order
:param plugin_meta: data populated by previous calls for this order,
in particular the request_id
:param barbican_meta_dto: additional data needed to process order.
:return: cm.ResultDTO
"""
request_id = self._get_request_id(order_id, plugin_meta, "checking")
request = self._get_request(request_id)
if not request:
raise cm.CertificateGeneralException(
u._(
"No request found for request_id {request_id} for "
"order {order_id}"
).format(
request_id=request_id,
order_id=order_id
)
)
request_status = request.request_status
if request_status == pki.cert.CertRequestStatus.REJECTED:
return cm.ResultDTO(
cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
status_message=request.error_message)
elif request_status == pki.cert.CertRequestStatus.CANCELED:
return cm.ResultDTO(
cm.CertificateStatus.REQUEST_CANCELED)
elif request_status == pki.cert.CertRequestStatus.PENDING:
return cm.ResultDTO(
cm.CertificateStatus.WAITING_FOR_CA)
elif request_status == pki.cert.CertRequestStatus.COMPLETE:
# get the cert
cert_id = request.cert_id
if not cert_id:
raise cm.CertificateGeneralException(
u._(
"Request {request_id} reports status_complete, but no "
"cert_id has been returned"
).format(
request_id=request_id
)
)
cert = self._get_cert(cert_id)
if not cert:
raise cm.CertificateGeneralException(
u._("Certificate not found for cert_id: {cert_id}").format(
cert_id=cert_id
)
)
return cm.ResultDTO(
cm.CertificateStatus.CERTIFICATE_GENERATED,
certificate=cert.encoded,
intermediates=cert.pkcs7_cert_chain)
else:
raise cm.CertificateGeneralException(
u._("Invalid request_status returned by CA"))
@_catch_request_exception
def issue_certificate_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Issue a certificate request to the Dogtag CA
Call the relevant certificate issuance function depending on the
Barbican defined request type in the order_meta.
:param order_id: ID of the order associated with this request
:param order_meta: dict containing all the inputs for this request.
This includes the request_type.
:param plugin_meta: Used to store data for status check
:param barbican_meta_dto: additional data needed to process order.
:return: cm.ResultDTO
"""
request_type = order_meta.get(
cm.REQUEST_TYPE,
cm.CertificateRequestType.CUSTOM_REQUEST)
jump_table = {
cm.CertificateRequestType.SIMPLE_CMC_REQUEST:
self._issue_simple_cmc_request,
cm.CertificateRequestType.FULL_CMC_REQUEST:
self._issue_full_cmc_request,
cm.CertificateRequestType.STORED_KEY_REQUEST:
self._issue_stored_key_request,
cm.CertificateRequestType.CUSTOM_REQUEST:
self._issue_custom_certificate_request
}
if request_type not in jump_table:
raise DogtagPluginNotSupportedException(u._(
"Dogtag plugin does not support %s request type").format(
request_type))
return jump_table[request_type](order_id, order_meta, plugin_meta,
barbican_meta_dto)
@_catch_enrollment_exceptions
def _issue_simple_cmc_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Issue a simple CMC request to the Dogtag CA.
:param order_id:
:param order_meta:
:param plugin_meta:
:param barbican_meta_dto:
:return: cm.ResultDTO
"""
if barbican_meta_dto.generated_csr is not None:
csr = barbican_meta_dto.generated_csr
else:
# we expect the CSR to be base64 encoded PEM
# Dogtag CA needs it to be unencoded
csr = base64.b64decode(order_meta.get('request_data'))
profile_id = order_meta.get('profile', self.simple_cmc_profile)
inputs = {
'cert_request_type': 'pkcs10',
'cert_request': csr
}
return self._issue_certificate_request(
profile_id, inputs, plugin_meta, barbican_meta_dto)
@_catch_enrollment_exceptions
def _issue_full_cmc_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Issue a full CMC request to the Dogtag CA.
:param order_id:
:param order_meta:
:param plugin_meta:
:param barbican_meta_dto:
:return: cm.ResultDTO
"""
raise DogtagPluginNotSupportedException(u._(
"Dogtag plugin does not support %s request type").format(
cm.CertificateRequestType.FULL_CMC_REQUEST))
@_catch_enrollment_exceptions
def _issue_stored_key_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Issue a simple CMC request to the Dogtag CA.
:param order_id:
:param order_meta:
:param plugin_meta:
:param barbican_meta_dto:
:return: cm.ResultDTO
"""
return self._issue_simple_cmc_request(
order_id,
order_meta,
plugin_meta,
barbican_meta_dto)
@_catch_enrollment_exceptions
def _issue_custom_certificate_request(self, order_id, order_meta,
plugin_meta, barbican_meta_dto):
"""Issue a custom certificate request to Dogtag CA
:param order_id: ID of the order associated with this request
:param order_meta: dict containing all the inputs required for a
particular profile. One of these must be the profile_id.
The exact fields (both optional and mandatory) depend on the
profile, but they will be exposed to the user in a method to
expose syntax. Depending on the profile, only the relevant fields
will be populated in the request. All others will be ignored.
:param plugin_meta: Used to store data for status check.
:param barbican_meta_dto: Extra data to aid in processing.
:return: cm.ResultDTO
"""
profile_id = order_meta.get(self.PROFILE_ID, None)
if not profile_id:
return cm.ResultDTO(
cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
status_message=u._("No profile_id specified"))
# we expect the csr to be base64 encoded PEM data. Dogtag CA expects
# PEM data though so we need to decode it.
updated_meta = copy.deepcopy(order_meta)
if 'cert_request' in updated_meta:
updated_meta['cert_request'] = base64.b64decode(
updated_meta['cert_request'])
return self._issue_certificate_request(
profile_id, updated_meta, plugin_meta, barbican_meta_dto)
def _issue_certificate_request(self, profile_id, inputs, plugin_meta,
barbican_meta_dto):
"""Actually send the cert request to the Dogtag CA
If the profile_id is one of the auto-approved profiles, then use
the convenience enroll_cert() method to create and approve the request
using the Barbican agent cert credentials. If not, then submit the
request and wait for approval by a CA agent on the Dogtag CA.
:param profile_id: enrollment profile
:param inputs: dict of request inputs
:param plugin_meta: Used to store data for status check.
:param barbican_meta_dto: Extra data to aid in processing.
:return: cm.ResultDTO
"""
ca_id = barbican_meta_dto.plugin_ca_id or self.get_default_ca_name()
if profile_id in self.auto_approved_profiles:
if ca_id == self.get_default_ca_name():
results = self.certclient.enroll_cert(profile_id, inputs)
else:
results = self.certclient.enroll_cert(
profile_id, inputs, ca_id)
return self._process_auto_enrollment_results(
results, plugin_meta, barbican_meta_dto)
else:
request = self.certclient.create_enrollment_request(
profile_id, inputs)
if ca_id == self.get_default_ca_name():
results = self.certclient.submit_enrollment_request(request)
else:
results = self.certclient.submit_enrollment_request(
request, ca_id)
return self._process_pending_enrollment_results(
results, plugin_meta, barbican_meta_dto)
def _process_auto_enrollment_results(self, enrollment_results,
plugin_meta, barbican_meta_dto):
"""Process results received from Dogtag CA for auto-enrollment
This processes data from enroll_cert, which submits, approves and
gets the cert issued and returns as a list of CertEnrollment objects.
:param enrollment_results: list of CertEnrollmentResult objects
:param plugin_meta: metadata dict for storing plugin specific data
:param barbican_meta_dto: object containing extra data to help process
the request
:return: cm.ResultDTO
"""
# Although it is possible to create multiple certs in an invocation
# of enroll_cert, Barbican cannot handle this case. Assume
# only once cert and request generated for now.
enrollment_result = enrollment_results[0]
request = enrollment_result.request
if not request:
raise cm.CertificateGeneralException(
u._("No request returned in enrollment_results"))
# store the request_id in the plugin metadata
plugin_meta[self.REQUEST_ID] = request.request_id
cert = enrollment_result.cert
return self._create_dto(request.request_status,
request.request_id,
request.error_message,
cert)
def _process_pending_enrollment_results(self, results, plugin_meta,
barbican_meta_dto):
"""Process results received from Dogtag CA for pending enrollment
This method processes data returned by submit_enrollment_request(),
which creates requests that still need to be approved by an agent.
:param results: CertRequestInfoCollection object
:param plugin_meta: metadata dict for storing plugin specific data
:param barbican_meta_dto: object containing extra data to help process
the request
:return: cm.ResultDTO
"""
# Although it is possible to create multiple certs in an invocation
# of enroll_cert, Barbican cannot handle this case. Assume
# only once cert and request generated for now
cert_request_info = results.cert_request_info_list[0]
status = cert_request_info.request_status
request_id = getattr(cert_request_info, 'request_id', None)
error_message = getattr(cert_request_info, 'error_message', None)
# store the request_id in the plugin metadata
if request_id:
plugin_meta[self.REQUEST_ID] = request_id
return self._create_dto(status, request_id, error_message, None)
def _create_dto(self, request_status, request_id, error_message, cert):
dto = None
if request_status == pki.cert.CertRequestStatus.COMPLETE:
if cert is not None:
# Barbican is expecting base 64 encoded PEM, so we base64
# encode below.
#
# Currently there is an inconsistency in what Dogtag returns
# for certificates and intermediates. For certs, we return
# PEM, whereas for intermediates, we return headerless PEM.
# This is being addressed in Dogtag ticket:
# https://fedorahosted.org/pki/ticket/1374
#
# Until this is addressed, simply add the missing headers
cert_chain = (CERT_HEADER + "\r\n" + cert.pkcs7_cert_chain +
CERT_FOOTER)
dto = cm.ResultDTO(cm.CertificateStatus.CERTIFICATE_GENERATED,
certificate=base64.b64encode(cert.encoded),
intermediates=base64.b64encode(cert_chain))
else:
raise cm.CertificateGeneralException(
u._("request_id {req_id} returns COMPLETE but no cert "
"returned").format(req_id=request_id))
elif request_status == pki.cert.CertRequestStatus.REJECTED:
dto = cm.ResultDTO(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
status_message=error_message)
elif request_status == pki.cert.CertRequestStatus.CANCELED:
dto = cm.ResultDTO(cm.CertificateStatus.REQUEST_CANCELED)
elif request_status == pki.cert.CertRequestStatus.PENDING:
dto = cm.ResultDTO(cm.CertificateStatus.WAITING_FOR_CA)
else:
raise cm.CertificateGeneralException(
u._("Invalid request_status {status} for "
"request_id {request_id}").format(
status=request_status,
request_id=request_id)
)
return dto
def modify_certificate_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Modify a certificate request.
Once a certificate request is generated, it cannot be modified.
The only alternative is to cancel the request (if it has not already
completed) and attempt a fresh enrolment. That is what will be
attempted here.
:param order_id: ID for this order
:param order_meta: order metadata. It is assumed that the newly
modified request data will be present here.
:param plugin_meta: data stored on behalf of the plugin for further
operations
:param barbican_meta_dto: additional data needed to process order.
:return: ResultDTO:
"""
result_dto = self.cancel_certificate_request(
order_id, order_meta, plugin_meta, barbican_meta_dto)
if result_dto.status == cm.CertificateStatus.REQUEST_CANCELED:
return self.issue_certificate_request(
order_id, order_meta, plugin_meta, barbican_meta_dto)
elif result_dto.status == cm.CertificateStatus.INVALID_OPERATION:
return cm.ResultDTO(
cm.CertificateStatus.INVALID_OPERATION,
status_message=u._(
"Modify request: unable to cancel: "
"{message}").format(message=result_dto.status_message)
)
else:
# other status (ca_unavailable, client_data_issue)
# return result from cancel operation
return result_dto
@_catch_request_exception
def cancel_certificate_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Cancel a certificate request.
:param order_id: ID for the order associated with this request
:param order_meta: order metadata fdr this request
:param plugin_meta: data stored by plugin for further processing.
In particular, the request_id
:param barbican_meta_dto: additional data needed to process order.
:return: cm.ResultDTO:
"""
request_id = self._get_request_id(order_id, plugin_meta, "cancelling")
try:
review_response = self.certclient.review_request(request_id)
self.certclient.cancel_request(request_id, review_response)
return cm.ResultDTO(cm.CertificateStatus.REQUEST_CANCELED)
except pki.RequestNotFoundException:
return cm.ResultDTO(
cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
status_message=u._("no request found for this order"))
except pki.ConflictingOperationException as e:
return cm.ResultDTO(
cm.CertificateStatus.INVALID_OPERATION,
status_message=e.message)
def supports(self, certificate_spec):
if cm.CA_TYPE in certificate_spec:
return certificate_spec[cm.CA_TYPE] == cm.CA_PLUGIN_TYPE_DOGTAG
if cm.CA_PLUGIN_TYPE_SYMANTEC in certificate_spec:
# TODO(alee-3) Handle case where SKI is provided
pass
return True
def supported_request_types(self):
"""Returns the request_types supported by this plugin.
:returns: a list of the Barbican-core defined request_types
supported by this plugin.
"""
return [cm.CertificateRequestType.SIMPLE_CMC_REQUEST,
cm.CertificateRequestType.STORED_KEY_REQUEST,
cm.CertificateRequestType.CUSTOM_REQUEST]
def supports_create_ca(self):
"""Returns if this plugin and the backend CA supports subCAs
:return: True/False
"""
return subcas_available
@_catch_subca_creation_exceptions
def create_ca(self, ca_create_dto):
"""Creates a subordinate CA upon request
:param ca_create_dto:
Data transfer object :class:`CACreateDTO` containing data
required to generate a subordinate CA. This data includes
the subject DN of the new CA signing certificate, a name for
the new CA and a reference to the CA that will issue the new
subordinate CA's signing certificate,
:return: ca_info:
Dictionary containing the data needed to create a
models.CertificateAuthority object
"""
if not subcas_available:
raise exception.SubCAsNotSupported(
"Subordinate CAs are not supported by this Dogtag CA")
parent_ca_id = self._get_correct_ca_id(ca_create_dto.parent_ca_id)
ca_data = authority.AuthorityData(
dn=ca_create_dto.subject_dn,
parent_aid=parent_ca_id,
description=ca_create_dto.name)
new_ca_data = self.authority_client.create_ca(ca_data)
cert = self.authority_client.get_cert(new_ca_data.aid, "PEM")
chain = self.authority_client.get_chain(new_ca_data.aid, "PEM")
return {
cm.INFO_NAME: new_ca_data.description,
cm.INFO_CA_SIGNING_CERT: cert,
cm.INFO_EXPIRATION: self.expiration.isoformat(),
cm.INFO_INTERMEDIATES: chain,
cm.PLUGIN_CA_ID: new_ca_data.aid
}
def _get_correct_ca_id(self, plugin_ca_id):
"""Returns the correct authority id
When the Dogtag plugin updates its CA list, any subcas will
have a plugin_ca_id that matches the authority_id (aid) as
returned from the backend CA.
For migration purposes, though, ie. migrating from a non-subca
environment to a subca one, we want the host CA to keep the same
plugin_ca_id (which is the default_ca_name) so that no disruption
occurs. Therefore, we need to store the host CA's authority ID
(in get_ca_info) and return it here instead.
"""
if plugin_ca_id == self.get_default_ca_name():
return self.host_aid
else:
return plugin_ca_id
@_catch_subca_deletion_exceptions
def delete_ca(self, ca_id):
"""Deletes a subordinate CA
:param ca_id: id for the CA as specified by the plugin
:return: None
"""
if not subcas_available:
raise exception.SubCAsNotSupported(
"Subordinate CAs are not supported by this Dogtag CA")
# ca must be disabled first
self.authority_client.disable_ca(ca_id)
self.authority_client.delete_ca(ca_id)
def get_ca_info(self):
if not subcas_available:
return super(DogtagCAPlugin, self).get_ca_info()
self.expiration = (datetime.datetime.utcnow() + datetime.timedelta(
days=int(self._expiration_delta)))
ret = {}
cas = self.authority_client.list_cas()
for ca_data in cas.ca_list:
if not ca_data.enabled:
continue
cert = self.authority_client.get_cert(ca_data.aid, "PEM")
chain = self.authority_client.get_chain(ca_data.aid, "PEM")
ca_info = {
cm.INFO_NAME: ca_data.description,
cm.INFO_CA_SIGNING_CERT: cert,
cm.INFO_INTERMEDIATES: chain,
cm.INFO_EXPIRATION: self.expiration.isoformat()
}
# handle the migration case. The top level CA should continue
# to work as before
if ca_data.is_host_authority:
ret[self.get_default_ca_name()] = ca_info
self.host_aid = ca_data.aid
else:
ret[ca_data.aid] = ca_info
return ret
def get_host_aid(self):
cas = self.authority_client.list_cas()
for ca_data in cas.ca_list:
if ca_data.is_host_authority:
return ca_data.aid
return None

View File

@ -18,8 +18,6 @@ from oslo_config import cfg
from barbican.common import config
from barbican import i18n as u
import barbican.plugin.interface.certificate_manager as cm
CONF = config.new_config()
dogtag_plugin_group = cfg.OptGroup(name='dogtag_plugin',
@ -40,18 +38,6 @@ dogtag_plugin_opts = [
cfg.StrOpt('nss_password',
help=u._('Password for the NSS certificate databases'),
secret=True),
cfg.StrOpt('simple_cmc_profile',
default='caOtherCert',
help=u._('Profile for simple CMC requests')),
cfg.StrOpt('auto_approved_profiles',
default="caServerCert",
help=u._('List of automatically approved enrollment profiles')),
cfg.IntOpt('ca_expiration_time',
default=cm.CA_INFO_DEFAULT_EXPIRATION_DAYS,
help=u._('Time in days for CA entries to expire')),
cfg.StrOpt('plugin_working_dir',
default='/etc/barbican/dogtag',
help=u._('Working directory for Dogtag plugin')),
cfg.StrOpt('plugin_name',
help=u._('User friendly plugin name'),
default='Dogtag KRA'),

View File

@ -1,767 +0,0 @@
# Copyright (c) 2013-2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
SSL Certificate resources for Barbican.
The resources here should be generic across all certificate-related
implementations. Hence do not place vendor-specific content in this module.
"""
import abc
import datetime
from oslo_config import cfg
from oslo_utils import encodeutils
from stevedore import named
from barbican.common import config
from barbican.common import exception
import barbican.common.utils as utils
from barbican import i18n as u
from barbican.model import models
from barbican.model import repositories as repos
from barbican.plugin.util import utils as plugin_utils
LOG = utils.getLogger(__name__)
CONF = config.new_config()
# Configuration for certificate processing plugins:
DEFAULT_PLUGIN_NAMESPACE = 'barbican.certificate.plugin'
DEFAULT_PLUGINS = ['simple_certificate']
cert_opt_group = cfg.OptGroup(name='certificate',
title='Certificate Plugin Options')
cert_opts = [
cfg.StrOpt('namespace',
default=DEFAULT_PLUGIN_NAMESPACE,
help=u._('Extension namespace to search for plugins.')
),
cfg.MultiStrOpt('enabled_certificate_plugins',
default=DEFAULT_PLUGINS,
help=u._('List of certificate plugins to load.')
)
]
CONF.register_group(cert_opt_group)
CONF.register_opts(cert_opts, group=cert_opt_group)
config.parse_args(CONF)
def list_opts():
yield cert_opt_group, cert_opts
yield cert_event_opt_group, cert_event_opts
# Configuration for certificate eventing plugins:
DEFAULT_EVENT_PLUGIN_NAMESPACE = 'barbican.certificate.event.plugin'
DEFAULT_EVENT_PLUGINS = ['simple_certificate_event']
cert_event_opt_group = cfg.OptGroup(name='certificate_event',
title='Certificate Event Plugin Options')
cert_event_opts = [
cfg.StrOpt('namespace',
default=DEFAULT_EVENT_PLUGIN_NAMESPACE,
help=u._('Extension namespace to search for eventing plugins.')
),
cfg.MultiStrOpt('enabled_certificate_event_plugins',
default=DEFAULT_EVENT_PLUGINS,
help=u._('List of certificate plugins to load.')
)
]
CONF.register_group(cert_event_opt_group)
CONF.register_opts(cert_event_opts, group=cert_event_opt_group)
ERROR_RETRY_MSEC = 300000
RETRY_MSEC = 3600000
CA_INFO_DEFAULT_EXPIRATION_DAYS = 1
CA_PLUGIN_TYPE_DOGTAG = "dogtag"
CA_PLUGIN_TYPE_SYMANTEC = "symantec"
# fields to distinguish CA types and subject key identifiers
CA_TYPE = "ca_type"
CA_SUBJECT_KEY_IDENTIFIER = "ca_subject_key_identifier"
# field to get the certificate request type
REQUEST_TYPE = "request_type"
# fields for the ca_id, plugin_ca_id
CA_ID = "ca_id"
PLUGIN_CA_ID = "plugin_ca_id"
# fields for ca_info dict keys
INFO_NAME = "name"
INFO_DESCRIPTION = "description"
INFO_CA_SIGNING_CERT = "ca_signing_certificate"
INFO_INTERMEDIATES = "intermediates"
INFO_EXPIRATION = "expiration"
# Singleton to avoid loading the CertificateEventManager plugins more than once
_EVENT_PLUGIN_MANAGER = None
class CertificateRequestType(object):
"""Constants to define the certificate request type."""
CUSTOM_REQUEST = "custom"
FULL_CMC_REQUEST = "full-cmc"
SIMPLE_CMC_REQUEST = "simple-cmc"
STORED_KEY_REQUEST = "stored-key"
class CertificatePluginNotFound(exception.BarbicanException):
"""Raised when no certificate plugin supporting a request is available."""
def __init__(self, plugin_name=None):
if plugin_name:
message = u._(
'Certificate plugin "{name}"'
' not found.').format(name=plugin_name)
else:
message = u._("Certificate plugin not found or configured.")
super(CertificatePluginNotFound, self).__init__(message)
class CertificatePluginNotFoundForCAID(exception.BarbicanException):
"""Raised when no certificate plugin is available for a CA_ID."""
def __init__(self, ca_id):
message = u._(
'Certificate plugin not found for "{ca_id}".').format(ca_id=ca_id)
super(CertificatePluginNotFoundForCAID, self).__init__(message)
class CertificateEventPluginNotFound(exception.BarbicanException):
"""Raised with no certificate event plugin supporting request."""
def __init__(self, plugin_name=None):
if plugin_name:
message = u._(
'Certificate event plugin "{name}" '
'not found.').format(name=plugin_name)
else:
message = u._("Certificate event plugin not found.")
super(CertificateEventPluginNotFound, self).__init__(message)
class CertificateStatusNotSupported(exception.BarbicanException):
"""Raised when cert status returned is unknown."""
def __init__(self, status):
super(CertificateStatusNotSupported, self).__init__(
u._("Certificate status of {status} not "
"supported").format(status=status)
)
self.status = status
class CertificateGeneralException(exception.BarbicanException):
"""Raised when a system fault has occurred."""
def __init__(self, reason=u._('Unknown')):
super(CertificateGeneralException, self).__init__(
u._('Problem seen during certificate processing - '
'Reason: {reason}').format(reason=reason)
)
self.reason = reason
class CertificateStatusClientDataIssue(exception.BarbicanHTTPException):
"""Raised when the CA has encountered an issue with request data."""
client_message = ""
status_code = 400
def __init__(self, reason=u._('Unknown')):
super(CertificateStatusClientDataIssue, self).__init__(
u._('Problem with data in certificate request - '
'Reason: {reason}').format(reason=reason)
)
self.client_message = self.message
class CertificateStatusInvalidOperation(exception.BarbicanHTTPException):
"""Raised when the CA has encountered an issue with request data."""
client_message = ""
status_code = 400
def __init__(self, reason=u._('Unknown')):
super(CertificateStatusInvalidOperation, self).__init__(
u._('Invalid operation requested - '
'Reason: {reason}').format(reason=reason)
)
self.client_message = self.message
class CertificateEventPluginBase(object, metaclass=abc.ABCMeta):
"""Base class for certificate eventing plugins.
This class is the base plugin contract for issuing certificate related
events from Barbican.
"""
@abc.abstractmethod
def notify_certificate_is_ready(
self, project_id, order_ref, container_ref):
"""Notify that a certificate has been generated and is ready to use.
:param project_id: Project ID associated with this certificate
:param order_ref: HATEOAS reference URI to the submitted Barbican Order
:param container_ref: HATEOAS reference URI to the Container storing
the certificate
:returns: None
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def notify_ca_is_unavailable(
self, project_id, order_ref, error_msg, retry_in_msec):
"""Notify that the certificate authority (CA) isn't available.
:param project_id: Project ID associated with this order
:param order_ref: HATEOAS reference URI to the submitted Barbican Order
:param error_msg: Error message if it is available
:param retry_in_msec: Delay before attempting to talk to the CA again.
If this is 0, then no attempt will be made.
:returns: None
"""
raise NotImplementedError # pragma: no cover
class CertificatePluginBase(object, metaclass=abc.ABCMeta):
"""Base class for certificate plugins.
This class is the base plugin contract for certificates.
"""
@abc.abstractmethod
def get_default_ca_name(self):
"""Get the default CA name
Provides a default CA name to be returned in the default
get_ca_info() method. If get_ca_info() is overridden (to
support multiple CAs for instance), then this method may not
be called. In that case, just implement this method to return
a dummy variable.
If this value is used, it should be unique amongst all the CA
plugins.
:return: The default CA name
:rtype: str
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def get_default_signing_cert(self):
"""Get the default CA signing cert
Provides a default CA signing cert to be returned in the default
get_ca_info() method. If get_ca_info() is overridden (to
support multiple CAs for instance), then this method may not
be called. In that case, just implement this method to return
a dummy variable.
:return: The default CA signing cert
:rtype: str
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def get_default_intermediates(self):
"""Get the default CA certificate chain
Provides a default CA certificate to be returned in the default
get_ca_info() method. If get_ca_info() is overridden (to
support multiple CAs for instance), then this method may not
be called. In that case, just implement this method to return
a dummy variable.
:return: The default CA certificate chain
:rtype: str
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def issue_certificate_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Create the initial order
:param order_id: ID associated with the order
:param order_meta: Dict of meta-data associated with the order
:param plugin_meta: Plugin meta-data previously set by calls to
this plugin. Plugins may also update/add
information here which Barbican will persist
on their behalf
:param barbican_meta_dto:
Data transfer object :class:`BarbicanMetaDTO` containing data
added to the request by the Barbican server to provide additional
context for processing, but which are not in
the original request. For example, the plugin_ca_id
:returns: A :class:`ResultDTO` instance containing the result
populated by the plugin implementation
:rtype: :class:`ResultDTO`
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def modify_certificate_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Update the order meta-data
:param order_id: ID associated with the order
:param order_meta: Dict of meta-data associated with the order
:param plugin_meta: Plugin meta-data previously set by calls to
this plugin. Plugins may also update/add
information here which Barbican will persist
on their behalf
:param barbican_meta_dto:
Data transfer object :class:`BarbicanMetaDTO` containing data
added to the request by the Barbican server to provide additional
context for processing, but which are not in
the original request. For example, the plugin_ca_id
:returns: A :class:`ResultDTO` instance containing the result
populated by the plugin implementation
:rtype: :class:`ResultDTO`
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def cancel_certificate_request(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Cancel the order
:param order_id: ID associated with the order
:param order_meta: Dict of meta-data associated with the order.
:param plugin_meta: Plugin meta-data previously set by calls to
this plugin. Plugins may also update/add
information here which Barbican will persist
on their behalf
:param barbican_meta_dto:
Data transfer object :class:`BarbicanMetaDTO` containing data
added to the request by the Barbican server to provide additional
context for processing, but which are not in
the original request. For example, the plugin_ca_id
:returns: A :class:`ResultDTO` instance containing the result
populated by the plugin implementation
:rtype: :class:`ResultDTO`
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def check_certificate_status(self, order_id, order_meta, plugin_meta,
barbican_meta_dto):
"""Check status of the order
:param order_id: ID associated with the order
:param order_meta: Dict of meta-data associated with the order
:param plugin_meta: Plugin meta-data previously set by calls to
this plugin. Plugins may also update/add
information here which Barbican will persist
on their behalf
:param barbican_meta_dto:
Data transfer object :class:`BarbicanMetaDTO` containing data
added to the request by the Barbican server to provide additional
context for processing, but which are not in
the original request. For example, the plugin_ca_id
:returns: A :class:`ResultDTO` instance containing the result
populated by the plugin implementation
:rtype: :class:`ResultDTO`
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def supports(self, certificate_spec):
"""Returns if the plugin supports the certificate type.
:param certificate_spec: Contains details on the certificate to
generate the certificate order
:returns: boolean indicating if the plugin supports the certificate
type
"""
raise NotImplementedError # pragma: no cover
def supported_request_types(self):
"""Returns the request_types supported by this plugin.
:returns: a list of the Barbican-core defined request_types
supported by this plugin.
"""
return [CertificateRequestType.CUSTOM_REQUEST] # pragma: no cover
def get_ca_info(self):
"""Returns information about the CA(s) supported by this plugin.
:returns: dictionary indexed by plugin_ca_id. Each entry consists
of a dictionary of key-value pairs.
An example dictionary containing the current supported attributes
is shown below::
{ "plugin_ca_id1": {
INFO_NAME : "CA name",
INFO_DESCRIPTION : "CA user friendly description",
INFO_CA_SIGNING_CERT : "base 64 encoded signing cert",
INFO_INTERMEDIATES = "base 64 encoded certificate chain"
INFO_EXPIRATION = "ISO formatted UTC datetime for when this"
"data will become stale"
}
}
"""
name = self.get_default_ca_name()
expiration = (datetime.datetime.utcnow() +
datetime.timedelta(days=CA_INFO_DEFAULT_EXPIRATION_DAYS))
default_info = {
INFO_NAME: name,
INFO_DESCRIPTION: "Certificate Authority - {0}".format(name),
INFO_EXPIRATION: expiration.isoformat()
}
signing_cert = self.get_default_signing_cert()
if signing_cert is not None:
default_info[INFO_CA_SIGNING_CERT] = signing_cert
intermediates = self.get_default_intermediates()
if intermediates is not None:
default_info[INFO_INTERMEDIATES] = intermediates
return {name: default_info}
def supports_create_ca(self):
"""Returns whether the plugin supports on-the-fly generation of subCAs
:return: boolean, True if supported, defaults to False
"""
return False # pragma: no cover
def create_ca(self, ca_create_dto):
"""Creates a subordinate CA upon request
This call should only be made if a plugin returns True for
supports_create_ca().
:param ca_create_dto:
Data transfer object :class:`CACreateDTO` containing data
required to generate a subordinate CA. This data includes
the subject DN of the new CA signing certificate, a name for
the new CA and a reference to the CA that will issue the new
subordinate CA's signing certificate,
:return: ca_info:
Dictionary containing the data needed to create a
models.CertificateAuthority object
"""
raise NotImplementedError # pragma: no cover
def delete_ca(self, ca_id):
"""Deletes a subordinate CA
Like the create_ca call, this should only be made if the plugin
returns True for supports_create_ca()
:param ca_id: id for the CA as specified by the plugin
:return: None
"""
raise NotImplementedError # pragma: no cover
class CACreateDTO(object):
"""Class that includes data needed to create a subordinate CA """
def __init__(self, name=None, description=None, subject_dn=None,
parent_ca_id=None):
"""Creates a new CACreateDTO object.
:param name: Name for the subordinate CA
:param description: Description for the subordinate CA
:param subject_dn:
Subject DN for the new subordinate CA's signing certificate
:param parent_ca_id:
ID of the CA which is supposed to sign the subordinate CA's
signing certificate. This is ID as known to the plugin
(not the Barbican UUID)
"""
self.name = name
self.description = description
self.subject_dn = subject_dn
self.parent_ca_id = parent_ca_id
class CertificateStatus(object):
"""Defines statuses for certificate request process.
In particular:
CERTIFICATE_GENERATED - Indicates a certificate was created
WAITING_FOR_CA - Waiting for Certificate authority (CA) to complete order
CLIENT_DATA_ISSUE_SEEN - Problem was seen with client-provided data
CA_UNAVAILABLE_FOR_REQUEST - CA was not available, will try again later
REQUEST_CANCELED - The client or CA cancelled this order
INVALID_OPERATION - Unexpected error seen processing order
"""
CERTIFICATE_GENERATED = "certificate generated"
WAITING_FOR_CA = "waiting for CA"
CLIENT_DATA_ISSUE_SEEN = "client data issue seen"
CA_UNAVAILABLE_FOR_REQUEST = "CA unavailable for request"
REQUEST_CANCELED = "request canceled"
INVALID_OPERATION = "invalid operation"
class ResultDTO(object):
"""Result data transfer object (DTO).
An object of this type is returned by most certificate plugin methods, and
is used to guide follow on processing and to provide status feedback to
clients.
"""
def __init__(self, status, status_message=None, certificate=None,
intermediates=None, retry_msec=RETRY_MSEC, retry_method=None):
"""Creates a new ResultDTO.
:param status: Status for cert order
:param status_message: Message to explain status type.
:param certificate: Certificate returned from CA to be stored in
container
:param intermediates: Intermediates to be stored in container
:param retry_msec: Number of milliseconds to wait for retry
:param retry_method: Method to be called for retry, if None then retry
the current method
"""
self.status = status
self.status_message = status_message
self.certificate = certificate
self.intermediates = intermediates
self.retry_msec = int(retry_msec)
self.retry_method = retry_method
class BarbicanMetaDTO(object):
"""Barbican meta data transfer object
Information needed to process a certificate request that is not specified
in the original request, and written by Barbican core, that is needed
by the plugin to process requests.
"""
def __init__(self, plugin_ca_id=None, generated_csr=None):
"""Creates a new BarbicanMetaDTO.
:param plugin_ca_id: ca_id as known to the plugin
:param generated_csr: csr generated in the stored-key case
:return: BarbicanMetaDTO
"""
self.plugin_ca_id = plugin_ca_id
self.generated_csr = generated_csr
class CertificatePluginManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
self.ca_repo = repos.get_ca_repository()
super(CertificatePluginManager, self).__init__(
conf.certificate.namespace,
conf.certificate.enabled_certificate_plugins,
invoke_on_load=False, # Defer creating plugins to utility below.
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
plugin_utils.instantiate_plugins(
self, invoke_args, invoke_kwargs)
def get_plugin(self, certificate_spec):
"""Gets a supporting certificate plugin.
:param certificate_spec: Contains details on the certificate to
generate the certificate order
:returns: CertificatePluginBase plugin implementation
"""
request_type = certificate_spec.get(
REQUEST_TYPE,
CertificateRequestType.CUSTOM_REQUEST)
for plugin in plugin_utils.get_active_plugins(self):
supported_request_types = plugin.supported_request_types()
if request_type not in supported_request_types:
continue
if plugin.supports(certificate_spec):
return plugin
raise CertificatePluginNotFound()
def get_plugin_by_name(self, plugin_name):