Drop all remaining logics for certificate resources
Since we removed certificate order, we no longer have to maintain these logics. This also removes the release note for deprecation of symantec certificate plugin, which was added during this cycle, because the plugin is also being removed by this change. Change-Id: I8e901024677e889d05ad8653389fb46487bc7745
This commit is contained in:
parent
901cf2cc39
commit
9833751613
@ -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 |