Merge "Drop all remaining logics for certificate resources"
This commit is contained in:
commit
b6edfda344
@ -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
|
||||
*****************
|
||||
|
@ -52,7 +52,6 @@ class States(object):
|
||||
class OrderType(object):
|
||||
KEY = 'key'
|
||||
ASYMMETRIC = 'asymmetric'
|
||||
CERTIFICATE = 'certificate'
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, order_type):
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
|
@ -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):
|
||||