Merge "Drop all remaining logics for certificate resources"
This commit is contained in:
commit
b6edfda344
@ -15,9 +15,9 @@ 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
|
||||
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>`_.
|
||||
|
||||
@ -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):
|
||||
"""Gets a supporting certificate plugin.
|
||||
|
||||
:param plugin_name: Name of the plugin to invoke
|
||||
:returns: CertificatePluginBase plugin implementation
|
||||
"""
|
||||
for plugin in plugin_utils.get_active_plugins(self):
|
||||
if utils.generate_fullname_for(plugin) == plugin_name:
|
||||
return plugin
|
||||
raise CertificatePluginNotFound(plugin_name)
|
||||
|
||||
def get_plugin_by_ca_id(self, ca_id):
|
||||
"""Gets a plugin based on the ca_id.
|
||||
|
||||
:param ca_id: id for CA in the CertificateAuthorities table
|
||||
:returns: CertificatePluginBase plugin implementation
|
||||
"""
|
||||
ca = self.ca_repo.get(ca_id, suppress_exception=True)
|
||||
if not ca:
|
||||
raise CertificatePluginNotFoundForCAID(ca_id)
|
||||
|
||||
return self.get_plugin_by_name(ca.plugin_name)
|
||||
|
||||
def refresh_ca_table(self):
|
||||
"""Refreshes the CertificateAuthority table."""
|
||||
updates_made = False
|
||||
for plugin in plugin_utils.get_active_plugins(self):
|
||||
plugin_name = utils.generate_fullname_for(plugin)
|
||||
cas, offset, limit, total = self.ca_repo.get_by_create_date(
|
||||
plugin_name=plugin_name,
|
||||
suppress_exception=True)
|
||||
if total < 1:
|
||||
# if no entries are found, then the plugin has not yet been
|
||||
# queried or that plugin's entries have expired.
|
||||
# Most of the time, this will be a no-op for plugins.
|
||||
self.update_ca_info(plugin)
|
||||
updates_made = True
|
||||
if updates_made:
|
||||
# commit to DB to avoid async issues with different threads
|
||||
repos.commit()
|
||||
|
||||
def update_ca_info(self, cert_plugin):
|
||||
"""Update the CA info for a particular plugin."""
|
||||
|
||||
plugin_name = utils.generate_fullname_for(cert_plugin)
|
||||
try:
|
||||
new_ca_infos = cert_plugin.get_ca_info()
|
||||
except Exception as e:
|
||||
# The plugin gave an invalid CA, log and return
|
||||
LOG.error("ERROR getting CA from plugin: %s",
|
||||
encodeutils.exception_to_unicode(e))
|
||||
return
|
||||
|
||||
old_cas, offset, limit, total = self.ca_repo.get_by_create_date(
|
||||
plugin_name=plugin_name,
|
||||
suppress_exception=True,
|
||||
show_expired=True)
|
||||
|
||||
if old_cas:
|
||||
for old_ca in old_cas:
|
||||
plugin_ca_id = old_ca.plugin_ca_id
|
||||
if plugin_ca_id not in new_ca_infos.keys():
|
||||
# remove CAs that no longer exist
|
||||
self._delete_ca(old_ca)
|
||||
else:
|
||||
# update those that still exist
|
||||
self.ca_repo.update_entity(
|
||||
old_ca,
|
||||
new_ca_infos[plugin_ca_id])
|
||||
old_ids = set([ca.plugin_ca_id for ca in old_cas])
|
||||
else:
|
||||
old_ids = set()
|
||||
|
||||
new_ids = set(new_ca_infos.keys())
|
||||
|
||||
# add new CAs
|
||||
add_ids = new_ids - old_ids
|
||||
for add_id in add_ids:
|
||||
try:
|
||||
self._add_ca(plugin_name, add_id, new_ca_infos[add_id])
|
||||
except Exception as e:
|
||||
# The plugin gave an invalid CA, log and continue
|
||||
LOG.error("ERROR adding CA from plugin: %s",
|
||||
encodeutils.exception_to_unicode(e))
|
||||
|
||||
def _add_ca(self, plugin_name, plugin_ca_id, ca_info):
|
||||
parsed_ca = dict(ca_info)
|
||||
parsed_ca['plugin_name'] = plugin_name
|
||||
parsed_ca['plugin_ca_id'] = plugin_ca_id
|
||||
new_ca = models.CertificateAuthority(parsed_ca)
|
||||
self.ca_repo.create_from(new_ca)
|
||||
|
||||
def _delete_ca(self, ca):
|
||||
self.ca_repo.delete_entity_by_id(ca.id, None)
|
||||
|
||||
|
||||
class _CertificateEventPluginManager(named.NamedExtensionManager,
|
||||
CertificateEventPluginBase):
|
||||
"""Provides services for certificate event plugins.
|
||||
|
||||
This plugin manager differs from others in that it implements the same
|
||||
contract as the plugins that it manages. This allows eventing operations
|
||||
to occur on all installed plugins (with this class acting as a composite
|
||||
plugin), rather than just eventing via an individual plugin.
|
||||
|
||||
Each time this class is initialized it will load a new instance
|
||||
of each enabled plugin. This is undesirable, so rather than initializing a
|
||||
new instance of this class use the get_event_plugin_manager function
|
||||
at the module level.
|
||||
"""
|
||||
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
|
||||
super(_CertificateEventPluginManager, self).__init__(
|
||||
conf.certificate_event.namespace,
|
||||
conf.certificate_event.enabled_certificate_event_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_by_name(self, plugin_name):
|
||||
"""Gets a supporting certificate event plugin.
|
||||
|
||||
:returns: CertificateEventPluginBase plugin implementation
|
||||
"""
|
||||
for plugin in plugin_utils.get_active_plugins(self):
|
||||
if utils.generate_fullname_for(plugin) == plugin_name:
|
||||
return plugin
|
||||
raise CertificateEventPluginNotFound(plugin_name)
|
||||
|
||||
def notify_certificate_is_ready(
|
||||
self, project_id, order_ref, container_ref):
|
||||
self._invoke_certificate_plugins(
|
||||
'notify_certificate_is_ready',
|
||||
project_id, order_ref, container_ref)
|
||||
|
||||
def notify_ca_is_unavailable(
|
||||
self, project_id, order_ref, error_msg, retry_in_msec):
|
||||
self._invoke_certificate_plugins(
|
||||
'notify_ca_is_unavailable',
|
||||
project_id, order_ref, error_msg, retry_in_msec)
|
||||
|
||||
def _invoke_certificate_plugins(self, method, *args, **kwargs):
|
||||
"""Invoke same function on plugins as calling function."""
|
||||
active_plugins = plugin_utils.get_active_plugins(self)
|
||||
|
||||
if not active_plugins:
|
||||
raise CertificateEventPluginNotFound()
|
||||
|
||||
for plugin in active_plugins:
|
||||
getattr(plugin, method)(*args, **kwargs)
|
||||
|
||||
|
||||
def get_event_plugin_manager():
|
||||
global _EVENT_PLUGIN_MANAGER
|
||||
if _EVENT_PLUGIN_MANAGER:
|
||||
return _EVENT_PLUGIN_MANAGER
|
||||
_EVENT_PLUGIN_MANAGER = _CertificateEventPluginManager()
|
||||
return _EVENT_PLUGIN_MANAGER
|
@ -1,160 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Default implementation of Barbican certificate processing plugins and support.
|
||||
"""
|
||||
from barbican.common import utils
|
||||
from barbican.plugin.interface import certificate_manager as cert
|
||||
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
|
||||
MSEC_UNTIL_CHECK_STATUS = 5000
|
||||
|
||||
|
||||
class SimpleCertificatePlugin(cert.CertificatePluginBase):
|
||||
"""Simple/default certificate plugin."""
|
||||
|
||||
def get_default_ca_name(self):
|
||||
return "Simple CA"
|
||||
|
||||
def get_default_signing_cert(self):
|
||||
return "XXXXXXXXXXXXXXXXX"
|
||||
|
||||
def get_default_intermediates(self):
|
||||
return "YYYYYYYYYYYYYYYY"
|
||||
|
||||
def issue_certificate_request(self, order_id, order_meta, plugin_meta,
|
||||
barbican_meta_dto):
|
||||
"""Create the initial order with CA
|
||||
|
||||
: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: additional data needed to process order.
|
||||
:returns: A :class:`ResultDTO` instance containing the result
|
||||
populated by the plugin implementation
|
||||
:rtype: :class:`ResultDTO`
|
||||
"""
|
||||
LOG.info('Invoking issue_certificate_request()')
|
||||
return cert.ResultDTO(
|
||||
cert.CertificateStatus.WAITING_FOR_CA,
|
||||
retry_msec=MSEC_UNTIL_CHECK_STATUS)
|
||||
|
||||
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: additional data needed to process order.
|
||||
:returns: A :class:`ResultDTO` instance containing the result
|
||||
populated by the plugin implementation
|
||||
:rtype: :class:`ResultDTO`
|
||||
"""
|
||||
LOG.info('Invoking modify_certificate_request()')
|
||||
return cert.ResultDTO(cert.CertificateStatus.WAITING_FOR_CA)
|
||||
|
||||
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: additional data needed to process order.
|
||||
:returns: A :class:`ResultDTO` instance containing the result
|
||||
populated by the plugin implementation
|
||||
:rtype: :class:`ResultDTO`
|
||||
"""
|
||||
LOG.info('Invoking cancel_certificate_request()')
|
||||
return cert.ResultDTO(cert.CertificateStatus.REQUEST_CANCELED)
|
||||
|
||||
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: additional data needed to process order.
|
||||
:returns: A :class:`ResultDTO` instance containing the result
|
||||
populated by the plugin implementation
|
||||
:rtype: :class:`ResultDTO`
|
||||
"""
|
||||
LOG.info('Invoking check_certificate_status()')
|
||||
return cert.ResultDTO(cert.CertificateStatus.CERTIFICATE_GENERATED)
|
||||
|
||||
def supports(self, certificate_spec):
|
||||
"""Indicates whether 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
|
||||
"""
|
||||
return True
|
||||
|
||||
def supported_request_types(self):
|
||||
"""Returns the request types supported by this plugin.
|
||||
|
||||
:returns: dict containing Barbican-core defined request types
|
||||
supported by this plugin.
|
||||
"""
|
||||
return [cert.CertificateRequestType.CUSTOM_REQUEST,
|
||||
cert.CertificateRequestType.SIMPLE_CMC_REQUEST,
|
||||
cert.CertificateRequestType.FULL_CMC_REQUEST,
|
||||
cert.CertificateRequestType.STORED_KEY_REQUEST]
|
||||
|
||||
|
||||
class SimpleCertificateEventPlugin(cert.CertificateEventPluginBase):
|
||||
"""Simple/default certificate event plugin."""
|
||||
|
||||
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
|
||||
"""
|
||||
LOG.info('Invoking notify_certificate_is_ready()')
|
||||
|
||||
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
|
||||
"""
|
||||
LOG.info('Invoking notify_ca_is_unavailable()')
|
@ -1,479 +0,0 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
import subprocess # nosec
|
||||
from tempfile import mkstemp
|
||||
import uuid
|
||||
|
||||
from OpenSSL import crypto
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from barbican.common import config
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
import barbican.plugin.interface.certificate_manager as cert_manager
|
||||
|
||||
CONF = config.new_config()
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
|
||||
snakeoil_ca_plugin_group = cfg.OptGroup(name='snakeoil_ca_plugin',
|
||||
title="Snakeoil CA Plugin Options")
|
||||
|
||||
snakeoil_ca_plugin_opts = [
|
||||
cfg.StrOpt('ca_cert_path',
|
||||
help=u._('Path to CA certificate file')),
|
||||
cfg.StrOpt('ca_cert_key_path',
|
||||
help=u._('Path to CA certificate key file')),
|
||||
cfg.StrOpt('ca_cert_chain_path',
|
||||
help=u._('Path to CA certificate chain file')),
|
||||
cfg.StrOpt('ca_cert_pkcs7_path',
|
||||
help=u._('Path to CA chain pkcs7 file')),
|
||||
cfg.StrOpt('subca_cert_key_directory',
|
||||
default='/etc/barbican/snakeoil-cas',
|
||||
help=u._('Directory in which to store certs/keys for subcas')),
|
||||
]
|
||||
|
||||
CONF.register_group(snakeoil_ca_plugin_group)
|
||||
CONF.register_opts(snakeoil_ca_plugin_opts, group=snakeoil_ca_plugin_group)
|
||||
config.parse_args(CONF)
|
||||
|
||||
|
||||
def list_opts():
|
||||
yield snakeoil_ca_plugin_group, snakeoil_ca_plugin_opts
|
||||
|
||||
|
||||
def set_subject_X509Name(target, dn):
|
||||
"""Set target X509Name object with parsed dn.
|
||||
|
||||
This is very basic and should certainly be replaced by something using
|
||||
cryptography for instance, but will do for a basic test CA
|
||||
"""
|
||||
|
||||
# TODO(alee) Figure out why C (country) is not working
|
||||
fields = dn.split(',')
|
||||
for field in fields:
|
||||
m = re.search(r"(\w+)\s*=\s*(.+)", field.strip())
|
||||
name = m.group(1)
|
||||
value = m.group(2)
|
||||
if name.lower() == 'ou':
|
||||
target.OU = value
|
||||
elif name.lower() == 'st':
|
||||
target.ST = value
|
||||
elif name.lower() == 'cn':
|
||||
target.CN = value
|
||||
elif name.lower() == 'l':
|
||||
target.L = value
|
||||
elif name.lower() == 'o':
|
||||
target.O = value # noqa: E741
|
||||
return target
|
||||
|
||||
|
||||
class SnakeoilCA(object):
|
||||
|
||||
def __init__(self, cert_path=None, key_path=None, chain_path=None,
|
||||
pkcs7_path=None, name=None, serial=1,
|
||||
key_size=2048, expiry_days=10 * 365, x509_version=2,
|
||||
subject_dn=None, signing_dn=None, signing_key=None,
|
||||
parent_chain_path=None):
|
||||
self.cert_path = cert_path
|
||||
self.key_path = key_path
|
||||
self.chain_path = chain_path
|
||||
self.pkcs7_path = pkcs7_path
|
||||
self.name = name
|
||||
self.serial = serial
|
||||
self.key_size = key_size
|
||||
self.expiry_days = expiry_days
|
||||
self.x509_version = x509_version
|
||||
|
||||
self.subject_dn = subject_dn
|
||||
|
||||
if signing_dn is not None:
|
||||
self.signing_dn = signing_dn
|
||||
else:
|
||||
self.signing_dn = subject_dn # self-signed
|
||||
|
||||
self.signing_key = signing_key
|
||||
self.parent_chain_path = parent_chain_path
|
||||
|
||||
self._cert_val = None
|
||||
self._key_val = None
|
||||
self._chain_val = None
|
||||
self._pkcs7_val = None
|
||||
|
||||
@property
|
||||
def cert(self):
|
||||
self.ensure_exists()
|
||||
if self.cert_path:
|
||||
with open(self.cert_path, 'rb') as cert_fh:
|
||||
return crypto.load_certificate(crypto.FILETYPE_PEM,
|
||||
cert_fh.read())
|
||||
else:
|
||||
return crypto.load_certificate(crypto.FILETYPE_PEM, self._cert_val)
|
||||
|
||||
@cert.setter
|
||||
def cert(self, val):
|
||||
if self.cert_path:
|
||||
with open(self.cert_path, 'wb') as cert_fh:
|
||||
cert_fh.write(crypto.dump_certificate(crypto.FILETYPE_PEM,
|
||||
val))
|
||||
else:
|
||||
self._cert_val = crypto.dump_certificate(crypto.FILETYPE_PEM, val)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
self.ensure_exists()
|
||||
if self.key_path:
|
||||
with open(self.key_path, 'rb') as key_fh:
|
||||
return crypto.load_privatekey(crypto.FILETYPE_PEM,
|
||||
key_fh.read())
|
||||
else:
|
||||
return crypto.load_privatekey(crypto.FILETYPE_PEM, self._key_val)
|
||||
|
||||
@key.setter
|
||||
def key(self, val):
|
||||
if self.key_path:
|
||||
with open(self.key_path, 'wb') as key_fh:
|
||||
key_fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, val))
|
||||
else:
|
||||
self._key_val = crypto.dump_privatekey(crypto.FILETYPE_PEM, val)
|
||||
|
||||
@property
|
||||
def chain(self):
|
||||
self.ensure_exists()
|
||||
if self.chain_path:
|
||||
with open(self.chain_path, 'rb') as chain_fh:
|
||||
return chain_fh.read()
|
||||
else:
|
||||
return self._chain_val
|
||||
|
||||
@chain.setter
|
||||
def chain(self, val):
|
||||
if self.chain_path:
|
||||
with open(self.chain_path, 'wb') as chain_fh:
|
||||
chain_fh.write(val)
|
||||
else:
|
||||
self._chain_val = val
|
||||
|
||||
@property
|
||||
def pkcs7(self):
|
||||
self.ensure_exists()
|
||||
if self.pkcs7_path:
|
||||
with open(self.pkcs7_path, 'rb') as pkcs7_fh:
|
||||
return pkcs7_fh.read()
|
||||
else:
|
||||
return self._pkcs7_val
|
||||
|
||||
@pkcs7.setter
|
||||
def pkcs7(self, val):
|
||||
if self.pkcs7_path:
|
||||
with open(self.pkcs7_path, 'wb') as pkcs7_fh:
|
||||
pkcs7_fh.write(val)
|
||||
else:
|
||||
self._pkcs7_val = val
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
if self.cert_path is not None:
|
||||
cert_exists = os.path.isfile(self.cert_path)
|
||||
else:
|
||||
cert_exists = self._cert_val is not None
|
||||
|
||||
if self.key_path is not None:
|
||||
key_exists = os.path.isfile(self.key_path)
|
||||
else:
|
||||
key_exists = self._key_val is not None
|
||||
|
||||
if self.chain_path is not None:
|
||||
chain_exists = os.path.isfile(self.chain_path)
|
||||
else:
|
||||
chain_exists = self._chain_val is not None
|
||||
|
||||
if self.pkcs7_path is not None:
|
||||
pkcs7_exists = os.path.isfile(self.pkcs7_path)
|
||||
else:
|
||||
pkcs7_exists = self._pkcs7_val is not None
|
||||
|
||||
return (cert_exists and key_exists and
|
||||
pkcs7_exists and chain_exists)
|
||||
|
||||
def ensure_exists(self):
|
||||
if not self.exists:
|
||||
LOG.debug('Keypair not found, creating new cert/key')
|
||||
self.cert, self.key, self.chain, self.pkcs7 = (
|
||||
self.create_keypair())
|
||||
|
||||
def create_keypair(self):
|
||||
LOG.debug('Generating Snakeoil CA')
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, self.key_size)
|
||||
|
||||
cert = crypto.X509()
|
||||
cert.set_version(self.x509_version)
|
||||
cert.set_serial_number(self.serial)
|
||||
subject = cert.get_subject()
|
||||
set_subject_X509Name(subject, self.subject_dn)
|
||||
cert.set_subject(subject)
|
||||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(self.expiry_days)
|
||||
cert.set_issuer(set_subject_X509Name(
|
||||
cert.get_issuer(), self.signing_dn))
|
||||
cert.set_pubkey(key)
|
||||
cert.add_extensions([
|
||||
crypto.X509Extension(b"basicConstraints", True,
|
||||
b"CA:TRUE, pathlen:5"),
|
||||
])
|
||||
if not self.signing_key:
|
||||
self.signing_key = key # self-signed
|
||||
|
||||
cert.sign(self.signing_key, 'sha256')
|
||||
|
||||
LOG.debug('Snakeoil CA cert/key generated')
|
||||
|
||||
chain = b''
|
||||
if self.parent_chain_path:
|
||||
with open(self.parent_chain_path, 'rb') as fh:
|
||||
chain = fh.read()
|
||||
chain += crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
|
||||
|
||||
pkcs7 = self._generate_pkcs7(chain)
|
||||
return cert, key, chain, pkcs7
|
||||
|
||||
def _generate_pkcs7(self, chain):
|
||||
fin, temp_in = mkstemp()
|
||||
os.write(fin, chain)
|
||||
os.close(fin)
|
||||
|
||||
fout, temp_out = mkstemp()
|
||||
os.close(fout)
|
||||
|
||||
subprocess.call(['/usr/bin/openssl', 'crl2pkcs7', '-nocrl', # nosec
|
||||
'-out', temp_out, '-certfile', temp_in], shell=False)
|
||||
with open(temp_out, 'rb') as pkcs7_fh:
|
||||
pkcs7 = pkcs7_fh.read()
|
||||
|
||||
os.remove(temp_in)
|
||||
os.remove(temp_out)
|
||||
return pkcs7
|
||||
|
||||
|
||||
class CertManager(object):
|
||||
|
||||
def __init__(self, ca):
|
||||
self.ca = ca
|
||||
|
||||
def get_new_serial(self):
|
||||
return uuid.uuid4().int
|
||||
|
||||
def make_certificate(self, csr, expires=2 * 365):
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(self.get_new_serial())
|
||||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(expires)
|
||||
cert.set_issuer(self.ca.cert.get_subject())
|
||||
cert.set_subject(csr.get_subject())
|
||||
cert.set_pubkey(csr.get_pubkey())
|
||||
cert.sign(self.ca.key, 'sha256')
|
||||
return cert
|
||||
|
||||
|
||||
class SnakeoilCACertificatePlugin(cert_manager.CertificatePluginBase):
|
||||
"""Snakeoil CA certificate plugin.
|
||||
|
||||
This is used for easily generating certificates which are not useful in a
|
||||
production environment.
|
||||
"""
|
||||
|
||||
def __init__(self, conf=CONF):
|
||||
self.cas = {}
|
||||
self.ca = SnakeoilCA(
|
||||
cert_path=conf.snakeoil_ca_plugin.ca_cert_path,
|
||||
key_path=conf.snakeoil_ca_plugin.ca_cert_key_path,
|
||||
chain_path=conf.snakeoil_ca_plugin.ca_cert_chain_path,
|
||||
pkcs7_path=conf.snakeoil_ca_plugin.ca_cert_pkcs7_path,
|
||||
name=self.get_default_ca_name(),
|
||||
subject_dn="cn=Snakeoil Certificate,o=example.com"
|
||||
)
|
||||
|
||||
self.cas[self.get_default_ca_name()] = self.ca
|
||||
|
||||
self.subca_directory = conf.snakeoil_ca_plugin.subca_cert_key_directory
|
||||
if self.subca_directory:
|
||||
if not os.path.exists(self.subca_directory):
|
||||
os.makedirs(self.subca_directory) # pragma: no cover
|
||||
else:
|
||||
self._reload_previously_created_subcas()
|
||||
|
||||
self.cert_manager = CertManager(self.ca)
|
||||
|
||||
def _reload_previously_created_subcas(self):
|
||||
for file in os.listdir(self.subca_directory):
|
||||
if fnmatch.fnmatch(file, '*.key'):
|
||||
ca_id, _ext = os.path.splitext(file)
|
||||
self.cas[ca_id] = SnakeoilCA(
|
||||
cert_path=os.path.join(self.subca_directory,
|
||||
ca_id + ".cert"),
|
||||
key_path=os.path.join(self.subca_directory, file),
|
||||
chain_path=os.path.join(self.subca_directory,
|
||||
ca_id + ".chain"),
|
||||
pkcs7_path=os.path.join(self.subca_directory,
|
||||
ca_id + ".p7b")
|
||||
)
|
||||
|
||||
def get_default_ca_name(self):
|
||||
return "Snakeoil CA"
|
||||
|
||||
def get_default_signing_cert(self):
|
||||
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca.cert)
|
||||
|
||||
def get_default_intermediates(self):
|
||||
return None
|
||||
|
||||
def supported_request_types(self):
|
||||
return [cert_manager.CertificateRequestType.CUSTOM_REQUEST,
|
||||
cert_manager.CertificateRequestType.STORED_KEY_REQUEST]
|
||||
|
||||
def issue_certificate_request(self, order_id, order_meta, plugin_meta,
|
||||
barbican_meta_dto):
|
||||
if barbican_meta_dto.generated_csr is not None:
|
||||
encoded_csr = barbican_meta_dto.generated_csr
|
||||
else:
|
||||
try:
|
||||
encoded_csr = base64.b64decode(order_meta['request_data'])
|
||||
except KeyError:
|
||||
return cert_manager.ResultDTO(
|
||||
cert_manager.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
status_message=u._("No request_data specified"))
|
||||
csr = crypto.load_certificate_request(crypto.FILETYPE_PEM, encoded_csr)
|
||||
|
||||
ca_id = barbican_meta_dto.plugin_ca_id
|
||||
if ca_id:
|
||||
ca = self.cas.get(ca_id)
|
||||
if ca is None:
|
||||
raise cert_manager.CertificateGeneralException(
|
||||
"Invalid ca_id passed into snake oil plugin:" + ca_id)
|
||||
else:
|
||||
ca = self.ca
|
||||
|
||||
cert_mgr = CertManager(ca)
|
||||
cert = cert_mgr.make_certificate(csr)
|
||||
cert_enc = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
|
||||
|
||||
return cert_manager.ResultDTO(
|
||||
cert_manager.CertificateStatus.CERTIFICATE_GENERATED,
|
||||
certificate=base64.b64encode(cert_enc),
|
||||
intermediates=base64.b64encode(ca.pkcs7))
|
||||
|
||||
def modify_certificate_request(self, order_id, order_meta, plugin_meta,
|
||||
barbican_meta_dto):
|
||||
raise NotImplementedError
|
||||
|
||||
def cancel_certificate_request(self, order_id, order_meta, plugin_meta,
|
||||
barbican_meta_dto):
|
||||
raise NotImplementedError
|
||||
|
||||
def check_certificate_status(self, order_id, order_meta, plugin_meta,
|
||||
barbican_meta_dto):
|
||||
raise NotImplementedError
|
||||
|
||||
def supports(self, certificate_spec):
|
||||
request_type = certificate_spec.get(
|
||||
cert_manager.REQUEST_TYPE,
|
||||
cert_manager.CertificateRequestType.CUSTOM_REQUEST)
|
||||
return request_type in self.supported_request_types()
|
||||
|
||||
def supports_create_ca(self):
|
||||
return True
|
||||
|
||||
def create_ca(self, ca_create_dto):
|
||||
# get the parent CA from the ca list, return error if not on list
|
||||
parent_ca_id = ca_create_dto.parent_ca_id
|
||||
if not parent_ca_id:
|
||||
raise cert_manager.CertificateGeneralException(
|
||||
"No parent id passed to snake oil plugin on create_ca")
|
||||
|
||||
parent_ca = self.cas.get(parent_ca_id)
|
||||
if not parent_ca:
|
||||
raise cert_manager.CertificateGeneralException(
|
||||
"Invalid parent id passed to snake oil plugin:" + parent_ca_id)
|
||||
|
||||
# create a new ca, passing in key and issuer from the parent
|
||||
new_ca_id = uuidutils.generate_uuid()
|
||||
new_cert_path = os.path.join(self.subca_directory, new_ca_id + ".cert")
|
||||
new_key_path = os.path.join(self.subca_directory, new_ca_id + ".key")
|
||||
new_chain_path = os.path.join(self.subca_directory,
|
||||
new_ca_id + ".chain")
|
||||
new_pkcs7_path = os.path.join(self.subca_directory,
|
||||
new_ca_id + ".p7b")
|
||||
parent_chain_path = parent_ca.chain_path
|
||||
|
||||
new_ca = SnakeoilCA(cert_path=new_cert_path,
|
||||
key_path=new_key_path,
|
||||
chain_path=new_chain_path,
|
||||
pkcs7_path=new_pkcs7_path,
|
||||
name=ca_create_dto.name,
|
||||
subject_dn=ca_create_dto.subject_dn,
|
||||
signing_dn=parent_ca.subject_dn,
|
||||
signing_key=parent_ca.key,
|
||||
parent_chain_path=parent_chain_path)
|
||||
|
||||
self.cas[new_ca_id] = new_ca
|
||||
|
||||
expiration = (datetime.datetime.utcnow() + datetime.timedelta(
|
||||
days=cert_manager.CA_INFO_DEFAULT_EXPIRATION_DAYS))
|
||||
|
||||
return {
|
||||
cert_manager.INFO_NAME: new_ca.name,
|
||||
cert_manager.INFO_CA_SIGNING_CERT: crypto.dump_certificate(
|
||||
crypto.FILETYPE_PEM, new_ca.cert),
|
||||
cert_manager.INFO_EXPIRATION: expiration.isoformat(),
|
||||
cert_manager.INFO_INTERMEDIATES: new_ca.pkcs7,
|
||||
cert_manager.PLUGIN_CA_ID: new_ca_id
|
||||
}
|
||||
|
||||
def get_ca_info(self):
|
||||
expiration = (datetime.datetime.utcnow() + datetime.timedelta(
|
||||
days=cert_manager.CA_INFO_DEFAULT_EXPIRATION_DAYS))
|
||||
|
||||
ret = {}
|
||||
for ca_id, ca in self.cas.items():
|
||||
ca_info = {
|
||||
cert_manager.INFO_NAME: ca.name,
|
||||
cert_manager.INFO_CA_SIGNING_CERT: crypto.dump_certificate(
|
||||
crypto.FILETYPE_PEM, ca.cert),
|
||||
cert_manager.INFO_INTERMEDIATES: ca.pkcs7,
|
||||
cert_manager.INFO_EXPIRATION: expiration.isoformat()
|
||||
}
|
||||
ret[ca_id] = ca_info
|
||||
|
||||
return ret
|
||||
|
||||
def delete_ca(self, ca_id):
|
||||
self.cas.pop(ca_id)
|
||||
|
||||
ca_files = [os.path.join(self.subca_directory, ca_id + ".cert"),
|
||||
os.path.join(self.subca_directory, ca_id + ".key"),
|
||||
os.path.join(self.subca_directory, ca_id + ".chain"),
|
||||
os.path.join(self.subca_directory, ca_id + ".p7b")]
|
||||
|
||||
for ca_file in ca_files:
|
||||
if os.path.exists(ca_file):
|
||||
os.remove(ca_file)
|
@ -1,306 +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.
|
||||
|
||||
"""
|
||||
Barbican certificate processing plugins and support.
|
||||
"""
|
||||
from oslo_config import cfg
|
||||
from requests import exceptions as request_exceptions
|
||||
from symantecssl.core import Symantec
|
||||
from symantecssl import exceptions as symantec_exceptions
|
||||
|
||||
from barbican.common import config
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
from barbican.plugin.interface import certificate_manager as cert
|
||||
|
||||
CONF = config.new_config()
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
symantec_plugin_group = cfg.OptGroup(name='symantec_plugin',
|
||||
title='Symantec Plugin Options')
|
||||
|
||||
symantec_plugin_opts = [
|
||||
cfg.StrOpt('username',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=('Symantec certificate plugin has been '
|
||||
'deprecated'),
|
||||
help=u._('Symantec username for authentication')),
|
||||
cfg.StrOpt('password',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=('Symantec certificate plugin has been '
|
||||
'deprecated'),
|
||||
help=u._('Symantec password for authentication'),
|
||||
secret=True),
|
||||
cfg.StrOpt('url',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=('Symantec certificate plugin has been '
|
||||
'deprecated'),
|
||||
help=u._('Domain of Symantec API'))
|
||||
]
|
||||
|
||||
CONF.register_group(symantec_plugin_group)
|
||||
CONF.register_opts(symantec_plugin_opts, group=symantec_plugin_group)
|
||||
config.parse_args(CONF)
|
||||
|
||||
|
||||
class SymantecCertificatePlugin(cert.CertificatePluginBase):
|
||||
"""Symantec certificate plugin."""
|
||||
|
||||
def __init__(self, conf=CONF):
|
||||
self.username = conf.symantec_plugin.username
|
||||
self.password = conf.symantec_plugin.password
|
||||
self.url = conf.symantec_plugin.url
|
||||
|
||||
if self.username is None:
|
||||
raise ValueError(u._("username is required"))
|
||||
|
||||
if self.password is None:
|
||||
raise ValueError(u._("password is required"))
|
||||
|
||||
if self.url is None:
|
||||
raise ValueError(u._("url is required"))
|
||||
|
||||
LOG.warning('Symantec certificate plugin has been deprecated and '
|
||||
'will be removed in a future release.')
|
||||
|
||||
def get_default_ca_name(self):
|
||||
return "Symantec CA"
|
||||
|
||||
def get_default_signing_cert(self):
|
||||
# TODO(chellygel) Add code to get the signing cert
|
||||
return None
|
||||
|
||||
def get_default_intermediates(self):
|
||||
# TODO(chellygel) Add code to get the cert chain
|
||||
return None
|
||||
|
||||
def issue_certificate_request(self, order_id, order_meta, plugin_meta,
|
||||
barbican_meta_dto):
|
||||
"""Create the initial order with CA
|
||||
|
||||
: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: additional data needed to process order.
|
||||
:returns: ResultDTO
|
||||
"""
|
||||
successful, error_msg, can_retry = _ca_create_order(order_meta,
|
||||
plugin_meta)
|
||||
|
||||
status = cert.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST
|
||||
message = None
|
||||
|
||||
if successful:
|
||||
status = cert.CertificateStatus.WAITING_FOR_CA
|
||||
elif can_retry:
|
||||
status = cert.CertificateStatus.CLIENT_DATA_ISSUE_SEEN
|
||||
message = error_msg
|
||||
|
||||
return cert.ResultDTO(status=status, status_message=message)
|
||||
|
||||
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: additional data needed to process order.
|
||||
"""
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
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: additional data needed to process order.
|
||||
"""
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
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: additional data needed to process order.
|
||||
"""
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def supports(self, certificate_spec):
|
||||
"""Indicates 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
|
||||
"""
|
||||
# TODO(chellygel): Research what certificate types are supported by
|
||||
# symantec. Returning True for testing purposes
|
||||
return True
|
||||
|
||||
|
||||
def _ca_create_order(self, order_meta, plugin_meta):
|
||||
"""Creates an order with the Symantec CA.
|
||||
|
||||
The PartnerOrderId and GeoTrustOrderId are returned and stored in
|
||||
plugin_meta. PartnerCode and ProductCode are also stored in plugin_meta
|
||||
for future use.
|
||||
|
||||
All required order parameters must be stored as a dict in
|
||||
order_meta.
|
||||
Required fields are:
|
||||
PartnerCode, ProductCode, PartnerOrderId, OrganizationName,
|
||||
AddressLine1, City, Region, PostalCode, Country, OrganizationPhone
|
||||
ValidityPeriod, ServerCount, WebServerType, AdminContactFirstName,
|
||||
AdminContactLastName, AdminContactPhone, AdminContactEmail,
|
||||
AdminContactTitle, AdminContactAddressLine1, AdminContactCity,
|
||||
AdminContactRegion, AdminContactPostalCode, AdminContactCountry,
|
||||
BillingContact*, TechContact*, and CSR.
|
||||
|
||||
*The Billing and Tech contact information follows the same convention
|
||||
as the AdminContact fields.
|
||||
|
||||
Optional Parameters: TechSameAsAdmin, BillSameAsAdmin, more options can be
|
||||
found in Symantec's API docs. Contact Symantec for the API document.
|
||||
|
||||
:returns: tuple with success, error message, and can retry
|
||||
"""
|
||||
|
||||
api = Symantec(self.username, self.password, self.url)
|
||||
|
||||
try:
|
||||
order_data = api.order(**order_meta)
|
||||
|
||||
# GeotrustOrderId is used to handle emails from Symantec.
|
||||
# PartnerCode and ProductCode are being stored in plugin_meta for
|
||||
# convenience when calling _ca_get_order_status, _ca_modify_order, etc.
|
||||
plugin_meta["GeotrustOrderID"] = order_data["GeotrustOrderID"]
|
||||
plugin_meta["PartnerOrderID"] = order_data["PartnerOrderID"]
|
||||
plugin_meta["PartnerCode"] = order_meta["OrderDetails"]["PartnerCode"]
|
||||
plugin_meta["ProductCode"] = order_meta["OrderDetails"]["ProductCode"]
|
||||
return True, None, False
|
||||
except symantec_exceptions.SymantecError as e:
|
||||
return False, e, False
|
||||
except request_exceptions.RequestException as e:
|
||||
return False, e, True
|
||||
|
||||
|
||||
def _ca_get_order_status(self, plugin_meta):
|
||||
"""Sends a request to the Symantec CA for details on an order.
|
||||
|
||||
Parameters needed for GetOrderByPartnerOrderID:
|
||||
plugin_meta parameters: PartnerOrderId, PartnerCode
|
||||
|
||||
If the order is complete, the Certificate is returned as a string.
|
||||
returns: tuple with success, error message, can retry,
|
||||
and the certificate (if available).
|
||||
"""
|
||||
api = Symantec(self.username, self.password, self.url)
|
||||
|
||||
order_details = {
|
||||
"PartnerOrderID": plugin_meta["PartnerOrderID"],
|
||||
"PartnerCode": plugin_meta["PartnerCode"],
|
||||
"ReturnCertificateInfo": "TRUE",
|
||||
"ReturnFulfillment": "TRUE",
|
||||
"ReturnCaCerts": "TRUE",
|
||||
}
|
||||
|
||||
try:
|
||||
order_data = api.get_order_by_partner_order_id(**order_details)
|
||||
if order_data["OrderInfo"]["OrderState"] == "COMPLETED":
|
||||
ca = order_data["Fulfillment"]["CACertificates"]["CACertificate"]
|
||||
return True, None, False, ca["CACert"]
|
||||
return True, None, False, None
|
||||
except symantec_exceptions.SymantecError as e:
|
||||
return False, e, False, None
|
||||
except request_exceptions.RequestException as e:
|
||||
return False, e, True, None
|
||||
|
||||
|
||||
def _ca_modify_order(self, order_meta, plugin_meta):
|
||||
"""Sends a request to the Symantec CA to modify an order.
|
||||
|
||||
Parameters needed for modifyOrder:
|
||||
PartnerOrderID - Needed to specify order
|
||||
PartnerCode - Needed to specify order
|
||||
ProductCode - Needed to specify order
|
||||
|
||||
Also need a dict, order_meta with the parameters/values to modify.
|
||||
|
||||
returns: tuple with success, error message, and can retry.
|
||||
"""
|
||||
api = Symantec(self.username, self.password, self.url)
|
||||
|
||||
order_details = {
|
||||
"PartnerOrderID": plugin_meta["PartnerOrderID"],
|
||||
"PartnerCode": plugin_meta["PartnerCode"],
|
||||
"ProductCode": plugin_meta["ProductCode"],
|
||||
}
|
||||
|
||||
order_details.update(order_meta)
|
||||
|
||||
try:
|
||||
api.validate_order_parameters(**order_details)
|
||||
return True, None, False
|
||||
except symantec_exceptions.SymantecError as e:
|
||||
return False, e, False
|
||||
except request_exceptions.RequestException as e:
|
||||
return False, e, True
|
||||
|
||||
|
||||
def _ca_cancel_order(self, plugin_meta):
|
||||
"""Sends a request to the Symantec CA to cancel an order.
|
||||
|
||||
Parameters needed for modifyOrder:
|
||||
PartnerOrderID - Needed to specify order
|
||||
PartnerCode - Needed to specify order
|
||||
ProductCode - Needed to specify order
|
||||
|
||||
returns: tuple with success, error message, and can retry.
|
||||
"""
|
||||
api = Symantec(self.username, self.password, self.url)
|
||||
|
||||
order_details = {
|
||||
"PartnerOrderID": plugin_meta["PartnerOrderID"],
|
||||
"PartnerCode": plugin_meta["PartnerCode"],
|
||||
"ProductCode": plugin_meta["ProductCode"],
|
||||
"ModifyOrderOperation": "CANCEL",
|
||||
}
|
||||
|
||||
try:
|
||||
api.modify_order(**order_details)
|
||||
return True, None, False
|
||||
except symantec_exceptions.SymantecError as e:
|
||||
return False, e, False
|
||||
except request_exceptions.RequestException as e:
|
||||
return False, e, True
|
@ -46,13 +46,6 @@ class TaskClient(object):
|
||||
project_id=project_id,
|
||||
request_id=request_id)
|
||||
|
||||
def check_certificate_status(self, order_id, project_id, request_id):
|
||||
"""Check the status of a certificate order."""
|
||||
self._cast('check_certificate_status',
|
||||
order_id=order_id,
|
||||
project_id=project_id,
|
||||
request_id=request_id)
|
||||
|
||||
def _cast(self, name, **kwargs):
|
||||
"""Asynchronous call handler. Barbican probably only needs casts.
|
||||
|
||||
|
@ -43,9 +43,7 @@ LOG = utils.getLogger(__name__)
|
||||
|
||||
# Maps the common/shared RetryTasks (returned from lower-level business logic
|
||||
# and plugin processing) to top-level RPC tasks in the Tasks class below.
|
||||
MAP_RETRY_TASKS = {
|
||||
common.RetryTasks.INVOKE_CERT_STATUS_CHECK_TASK: 'check_certificate_status'
|
||||
}
|
||||
MAP_RETRY_TASKS = {}
|
||||
|
||||
|
||||
def find_function_name(func, if_no_name=None):
|
||||
@ -211,20 +209,6 @@ class Tasks(object):
|
||||
return resources.BeginTypeOrder().process_and_suppress_exceptions(
|
||||
order_id, project_id)
|
||||
|
||||
@monitored
|
||||
@transactional
|
||||
@retryable_order
|
||||
def check_certificate_status(self, context, order_id,
|
||||
project_id, request_id):
|
||||
"""Check the status of a certificate order."""
|
||||
message = "Processing check certificate status on order: " \
|
||||
"order ID is '%(order)s' and request ID is '%(request)s'"
|
||||
|
||||
LOG.info(message, {'order': order_id, 'request': request_id})
|
||||
check_cert_order = resources.CheckCertificateStatusOrder()
|
||||
return check_cert_order.process_and_suppress_exceptions(
|
||||
order_id, project_id)
|
||||
|
||||
|
||||
class TaskServer(Tasks, service.Service):
|
||||
"""Server to process asynchronous tasking from Barbican API nodes.
|
||||
|
@ -1,594 +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.
|
||||
|
||||
from ldap3.utils.dn import parse_dn
|
||||
from OpenSSL import crypto
|
||||
|
||||
from barbican.common import exception as excep
|
||||
from barbican.common import hrefs
|
||||
from barbican.common import resources as res
|
||||
import barbican.common.utils as utils
|
||||
from barbican.model import models
|
||||
from barbican.model import repositories as repos
|
||||
from barbican.plugin.interface import certificate_manager as cert
|
||||
from barbican.plugin import resources as plugin
|
||||
from barbican.tasks import common
|
||||
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
# Order sub-status definitions
|
||||
ORDER_STATUS_REQUEST_PENDING = models.OrderStatus(
|
||||
"cert_request_pending",
|
||||
"Request has been submitted to the CA. "
|
||||
"Waiting for certificate to be generated"
|
||||
)
|
||||
|
||||
ORDER_STATUS_CERT_GENERATED = models.OrderStatus(
|
||||
"cert_generated",
|
||||
"Certificate has been generated"
|
||||
)
|
||||
|
||||
ORDER_STATUS_DATA_INVALID = models.OrderStatus(
|
||||
"cert_data_invalid",
|
||||
"CA rejected request data as invalid"
|
||||
)
|
||||
|
||||
ORDER_STATUS_CA_UNAVAIL_FOR_ISSUE = models.OrderStatus(
|
||||
"cert_ca_unavail_for_issue",
|
||||
"Unable to submit certificate request. CA unavailable"
|
||||
)
|
||||
|
||||
ORDER_STATUS_INVALID_OPERATION = models.OrderStatus(
|
||||
"cert_invalid_operation",
|
||||
"CA returned invalid operation"
|
||||
)
|
||||
|
||||
ORDER_STATUS_INTERNAL_ERROR = models.OrderStatus(
|
||||
"cert_internal_error",
|
||||
"Internal error during certificate operations"
|
||||
)
|
||||
|
||||
ORDER_STATUS_CA_UNAVAIL_FOR_CHECK = models.OrderStatus(
|
||||
"cert_ca_unavail_for_status_check",
|
||||
"Unable to get certificate request status. CA unavailable."
|
||||
)
|
||||
|
||||
|
||||
def refresh_certificate_resources():
|
||||
# Before CA operations can be performed, the CA table must be populated
|
||||
cert.CertificatePluginManager().refresh_ca_table()
|
||||
|
||||
|
||||
def issue_certificate_request(order_model, project_model, result_follow_on):
|
||||
"""Create the initial order with CA.
|
||||
|
||||
Note that this method may be called more than once if retries are
|
||||
required. Barbican metadata is used to store intermediate information,
|
||||
including selected plugins by name, to support such retries.
|
||||
|
||||
:param: order_model - order associated with this cert request
|
||||
:param: project_model - project associated with this request
|
||||
:param: result_follow_on - A :class:`FollowOnProcessingStatusDTO` instance
|
||||
instantiated by the client that this function may optionally update
|
||||
with information on how to process this task into the future.
|
||||
:returns: container_model - container with the relevant cert if
|
||||
the request has been completed. None otherwise
|
||||
"""
|
||||
plugin_meta = _get_plugin_meta(order_model)
|
||||
barbican_meta = _get_barbican_meta(order_model)
|
||||
|
||||
# TODO(john-wood-w) We need to de-conflict barbican_meta (stored with order
|
||||
# and not shown to plugins) with barbican_meta_dto (shared with plugins).
|
||||
# As a minimum we should change the name of the DTO to something like
|
||||
# 'extended_meta_dto' or some such.
|
||||
barbican_meta_for_plugins_dto = cert.BarbicanMetaDTO()
|
||||
|
||||
# refresh the CA table. This is mostly a no-op unless the entries
|
||||
# for a plugin are expired.
|
||||
cert.CertificatePluginManager().refresh_ca_table()
|
||||
|
||||
cert_plugin = _get_cert_plugin(barbican_meta,
|
||||
barbican_meta_for_plugins_dto,
|
||||
order_model, project_model)
|
||||
barbican_meta['plugin_name'] = utils.generate_fullname_for(cert_plugin)
|
||||
|
||||
# Generate CSR if needed.
|
||||
request_type = order_model.meta.get(cert.REQUEST_TYPE)
|
||||
if request_type == cert.CertificateRequestType.STORED_KEY_REQUEST:
|
||||
csr = barbican_meta.get('generated_csr')
|
||||
if csr is None:
|
||||
# TODO(alee) Fix this to be a non-project specific call once
|
||||
# the ACL patches go in.
|
||||
csr = _generate_csr_from_private_key(order_model, project_model)
|
||||
barbican_meta['generated_csr'] = csr
|
||||
barbican_meta_for_plugins_dto.generated_csr = csr
|
||||
|
||||
result = cert_plugin.issue_certificate_request(
|
||||
order_model.id, order_model.meta,
|
||||
plugin_meta, barbican_meta_for_plugins_dto)
|
||||
|
||||
# Save plugin and barbican metadata for this order.
|
||||
_save_plugin_metadata(order_model, plugin_meta)
|
||||
_save_barbican_metadata(order_model, barbican_meta)
|
||||
|
||||
# Handle result
|
||||
return _handle_task_result(
|
||||
result, result_follow_on, order_model, project_model, request_type,
|
||||
unavailable_status=ORDER_STATUS_CA_UNAVAIL_FOR_ISSUE)
|
||||
|
||||
|
||||
def _get_cert_plugin(barbican_meta, barbican_meta_for_plugins_dto,
|
||||
order_model, project_model):
|
||||
cert_plugin_name = barbican_meta.get('plugin_name')
|
||||
if cert_plugin_name:
|
||||
return cert.CertificatePluginManager().get_plugin_by_name(
|
||||
cert_plugin_name)
|
||||
ca_id = _get_ca_id(order_model.meta, project_model.id)
|
||||
if ca_id:
|
||||
ca = repos.get_ca_repository().get(ca_id)
|
||||
barbican_meta_for_plugins_dto.plugin_ca_id = ca.plugin_ca_id
|
||||
return cert.CertificatePluginManager().get_plugin_by_name(
|
||||
ca.plugin_name)
|
||||
else:
|
||||
return cert.CertificatePluginManager().get_plugin(order_model.meta)
|
||||
|
||||
|
||||
def check_certificate_request(order_model, project_model, result_follow_on):
|
||||
"""Check the status of a certificate request with the CA.
|
||||
|
||||
Note that this method may be called more than once if retries are
|
||||
required. Barbican metadata is used to store intermediate information,
|
||||
including selected plugins by name, to support such retries.
|
||||
|
||||
:param: order_model - order associated with this cert request
|
||||
:param: project_model - project associated with this request
|
||||
:param: result_follow_on - A :class:`FollowOnProcessingStatusDTO` instance
|
||||
instantiated by the client that this function may optionally update
|
||||
with information on how to process this task into the future.
|
||||
:returns: container_model - container with the relevant cert if the
|
||||
request has been completed. None otherwise.
|
||||
"""
|
||||
plugin_meta = _get_plugin_meta(order_model)
|
||||
barbican_meta = _get_barbican_meta(order_model)
|
||||
|
||||
# TODO(john-wood-w) See note above about DTO's name.
|
||||
barbican_meta_for_plugins_dto = cert.BarbicanMetaDTO()
|
||||
|
||||
cert_plugin = cert.CertificatePluginManager().get_plugin_by_name(
|
||||
barbican_meta.get('plugin_name'))
|
||||
|
||||
result = cert_plugin.check_certificate_status(
|
||||
order_model.id, order_model.meta,
|
||||
plugin_meta, barbican_meta_for_plugins_dto)
|
||||
|
||||
# Save plugin order plugin state
|
||||
_save_plugin_metadata(order_model, plugin_meta)
|
||||
|
||||
request_type = order_model.meta.get(cert.REQUEST_TYPE)
|
||||
return _handle_task_result(
|
||||
result, result_follow_on, order_model, project_model, request_type,
|
||||
unavailable_status=ORDER_STATUS_CA_UNAVAIL_FOR_CHECK)
|
||||
|
||||
|
||||
def create_subordinate_ca(project_model, name, description, subject_dn,
|
||||
parent_ca_ref, creator_id):
|
||||
"""Create a subordinate CA
|
||||
|
||||
:param name - name of the subordinate CA
|
||||
:param: description - description of the subordinate CA
|
||||
:param: subject_dn - subject DN of the subordinate CA
|
||||
:param: parent_ca_ref - Barbican URL reference to the parent CA
|
||||
:param: creator_id - id for creator of the subordinate CA
|
||||
:return: :class models.CertificateAuthority model object for new sub CA
|
||||
"""
|
||||
# check that the parent ref exists and is accessible
|
||||
parent_ca_id = hrefs.get_ca_id_from_ref(parent_ca_ref)
|
||||
ca_repo = repos.get_ca_repository()
|
||||
parent_ca = ca_repo.get(entity_id=parent_ca_id, suppress_exception=True)
|
||||
if not parent_ca:
|
||||
raise excep.InvalidParentCA(parent_ca_ref=parent_ca_ref)
|
||||
|
||||
# Parent CA must be a base CA or a subCA owned by this project
|
||||
if (parent_ca.project_id is not None and
|
||||
parent_ca.project_id != project_model.id):
|
||||
raise excep.UnauthorizedSubCA()
|
||||
|
||||
# get the parent plugin, raises CertPluginNotFound if missing
|
||||
cert_plugin = cert.CertificatePluginManager().get_plugin_by_name(
|
||||
parent_ca.plugin_name)
|
||||
|
||||
# confirm that the plugin supports creating subordinate CAs
|
||||
if not cert_plugin.supports_create_ca():
|
||||
raise excep.SubCAsNotSupported()
|
||||
|
||||
# make call to create the subordinate ca
|
||||
create_ca_dto = cert.CACreateDTO(
|
||||
name=name,
|
||||
description=description,
|
||||
subject_dn=subject_dn,
|
||||
parent_ca_id=parent_ca.plugin_ca_id)
|
||||
|
||||
new_ca_dict = cert_plugin.create_ca(create_ca_dto)
|
||||
if not new_ca_dict:
|
||||
raise excep.SubCANotCreated(name=name)
|
||||
|
||||
# create and store the subordinate CA as a new certificate authority object
|
||||
new_ca_dict['plugin_name'] = parent_ca.plugin_name
|
||||
new_ca_dict['creator_id'] = creator_id
|
||||
new_ca_dict['project_id'] = project_model.id
|
||||
new_ca = models.CertificateAuthority(new_ca_dict)
|
||||
ca_repo.create_from(new_ca)
|
||||
|
||||
return new_ca
|
||||
|
||||
|
||||
def delete_subordinate_ca(external_project_id, ca):
|
||||
"""Deletes a subordinate CA and any related artifacts
|
||||
|
||||
:param external_project_id: external project ID
|
||||
:param ca: class:`models.CertificateAuthority` to be deleted
|
||||
:return: None
|
||||
"""
|
||||
# TODO(alee) See if the checks below can be moved to the RBAC code
|
||||
|
||||
# Check that this CA is a subCA
|
||||
if ca.project_id is None:
|
||||
raise excep.CannotDeleteBaseCA()
|
||||
|
||||
# Check that the user's project owns this subCA
|
||||
project = res.get_or_create_project(external_project_id)
|
||||
if ca.project_id != project.id:
|
||||
raise excep.UnauthorizedSubCA()
|
||||
|
||||
project_ca_repo = repos.get_project_ca_repository()
|
||||
(project_cas, _, _, _) = project_ca_repo.get_by_create_date(
|
||||
project_id=project.id, ca_id=ca.id,
|
||||
suppress_exception=True)
|
||||
|
||||
preferred_ca_repo = repos.get_preferred_ca_repository()
|
||||
(preferred_cas, _, _, _) = preferred_ca_repo.get_by_create_date(
|
||||
project_id=project.id, ca_id=ca.id, suppress_exception=True)
|
||||
|
||||
# Can not delete a project preferred CA, if other project CAs exist. One
|
||||
# of those needs to be designated as the preferred CA first.
|
||||
if project_cas and preferred_cas and not is_last_project_ca(project.id):
|
||||
raise excep.CannotDeletePreferredCA()
|
||||
|
||||
# Remove the CA as preferred
|
||||
if preferred_cas:
|
||||
preferred_ca_repo.delete_entity_by_id(preferred_cas[0].id,
|
||||
external_project_id)
|
||||
# Remove the CA from project list
|
||||
if project_cas:
|
||||
project_ca_repo.delete_entity_by_id(project_cas[0].id,
|
||||
external_project_id)
|
||||
|
||||
# Delete the CA entry from plugin
|
||||
cert_plugin = cert.CertificatePluginManager().get_plugin_by_name(
|
||||
ca.plugin_name)
|
||||
cert_plugin.delete_ca(ca.plugin_ca_id)
|
||||
|
||||
# Finally, delete the CA entity from the CA repository
|
||||
ca_repo = repos.get_ca_repository()
|
||||
ca_repo.delete_entity_by_id(
|
||||
entity_id=ca.id,
|
||||
external_project_id=external_project_id)
|
||||
|
||||
|
||||
def is_last_project_ca(project_id):
|
||||
"""Returns True iff project has exactly one project CA
|
||||
|
||||
:param project_id: internal project ID
|
||||
:return: Boolean
|
||||
"""
|
||||
project_ca_repo = repos.get_project_ca_repository()
|
||||
_, _, _, total = project_ca_repo.get_by_create_date(
|
||||
project_id=project_id,
|
||||
suppress_exception=True
|
||||
)
|
||||
return total == 1
|
||||
|
||||
|
||||
def _handle_task_result(result, result_follow_on, order_model,
|
||||
project_model, request_type, unavailable_status):
|
||||
if cert.CertificateStatus.WAITING_FOR_CA == result.status:
|
||||
_update_result_follow_on(
|
||||
result_follow_on,
|
||||
order_status=ORDER_STATUS_REQUEST_PENDING,
|
||||
retry_task=common.RetryTasks.INVOKE_CERT_STATUS_CHECK_TASK,
|
||||
retry_msec=result.retry_msec)
|
||||
elif cert.CertificateStatus.CERTIFICATE_GENERATED == result.status:
|
||||
_update_result_follow_on(
|
||||
result_follow_on,
|
||||
order_status=ORDER_STATUS_CERT_GENERATED)
|
||||
container_model = _save_secrets(result, project_model, request_type,
|
||||
order_model)
|
||||
return container_model
|
||||
elif cert.CertificateStatus.CLIENT_DATA_ISSUE_SEEN == result.status:
|
||||
raise cert.CertificateStatusClientDataIssue(result.status_message)
|
||||
elif cert.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST == result.status:
|
||||
_update_result_follow_on(
|
||||
result_follow_on,
|
||||
order_status=unavailable_status,
|
||||
retry_task=common.RetryTasks.INVOKE_SAME_TASK,
|
||||
retry_msec=cert.ERROR_RETRY_MSEC)
|
||||
_notify_ca_unavailable(order_model, result)
|
||||
elif cert.CertificateStatus.INVALID_OPERATION == result.status:
|
||||
raise cert.CertificateStatusInvalidOperation(result.status_message)
|
||||
else:
|
||||
raise cert.CertificateStatusNotSupported(result.status)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _add_private_key_to_generated_cert_container(container_id, order_model,
|
||||
project_model):
|
||||
keypair_container_id, keypair_container = _get_container_from_order_meta(
|
||||
order_model, project_model)
|
||||
private_key_id = None
|
||||
|
||||
for cs in keypair_container.container_secrets:
|
||||
if cs.name == 'private_key':
|
||||
private_key_id = cs.secret_id
|
||||
|
||||
new_consec_assoc = models.ContainerSecret()
|
||||
new_consec_assoc.name = 'private_key'
|
||||
new_consec_assoc.container_id = container_id
|
||||
new_consec_assoc.secret_id = private_key_id
|
||||
container_secret_repo = repos.get_container_secret_repository()
|
||||
container_secret_repo.create_from(new_consec_assoc)
|
||||
|
||||
|
||||
def modify_certificate_request(order_model, updated_meta):
|
||||
"""Update the order with CA."""
|
||||
# TODO(chellygel): Add the modify certificate request logic.
|
||||
LOG.debug('in modify_certificate_request')
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
|
||||
def get_global_preferred_ca():
|
||||
project = res.get_or_create_global_preferred_project()
|
||||
preferred_ca_repository = repos.get_preferred_ca_repository()
|
||||
cas = preferred_ca_repository.get_project_entities(project.id)
|
||||
if not cas:
|
||||
return None
|
||||
else:
|
||||
return cas[0]
|
||||
|
||||
|
||||
def get_project_preferred_ca_id(project_id):
|
||||
"""Compute the preferred CA ID for a project
|
||||
|
||||
First priority: a preferred CA is defined for the project
|
||||
Second priority: a preferred CA is defined globally
|
||||
Else: None
|
||||
"""
|
||||
preferred_ca_repository = repos.get_preferred_ca_repository()
|
||||
cas, offset, limit, total = preferred_ca_repository.get_by_create_date(
|
||||
project_id=project_id, suppress_exception=True)
|
||||
if total > 0:
|
||||
return cas[0].ca_id
|
||||
global_ca = get_global_preferred_ca()
|
||||
if global_ca:
|
||||
return global_ca.ca_id
|
||||
|
||||
|
||||
def _get_ca_id(order_meta, project_id):
|
||||
ca_id = order_meta.get(cert.CA_ID)
|
||||
if ca_id:
|
||||
return ca_id
|
||||
|
||||
return get_project_preferred_ca_id(project_id)
|
||||
|
||||
|
||||
def _update_result_follow_on(
|
||||
result_follow_on,
|
||||
order_status=None,
|
||||
retry_task=common.RetryTasks.NO_ACTION_REQUIRED,
|
||||
retry_msec=common.RETRY_MSEC_DEFAULT):
|
||||
if order_status:
|
||||
result_follow_on.status = order_status.id
|
||||
result_follow_on.status_message = order_status.message
|
||||
result_follow_on.retry_task = retry_task
|
||||
if retry_msec and retry_msec >= 0:
|
||||
result_follow_on.retry_msec = retry_msec
|
||||
|
||||
|
||||
def _get_plugin_meta(order_model):
|
||||
if order_model:
|
||||
order_plugin_meta_repo = repos.get_order_plugin_meta_repository()
|
||||
return order_plugin_meta_repo.get_metadata_for_order(order_model.id)
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def _get_barbican_meta(order_model):
|
||||
if order_model:
|
||||
order_barbican_meta_repo = repos.get_order_barbican_meta_repository()
|
||||
return order_barbican_meta_repo.get_metadata_for_order(order_model.id)
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def _generate_csr_from_private_key(order_model, project_model):
|
||||
"""Generate a CSR from the private key.
|
||||
|
||||
:param: order_model - order for the request
|
||||
:param: project_model - project for this request
|
||||
:return: CSR (certificate signing request) in PEM format
|
||||
:raise: :class:`StoredKeyPrivateKeyNotFound` if private key not found
|
||||
:class:`StoredKeyContainerNotFound` if container not found
|
||||
"""
|
||||
container_id, container = _get_container_from_order_meta(order_model,
|
||||
project_model)
|
||||
|
||||
if not container:
|
||||
raise excep.StoredKeyContainerNotFound(container_id)
|
||||
|
||||
passphrase = None
|
||||
private_key = None
|
||||
|
||||
for cs in container.container_secrets:
|
||||
secret_repo = repos.get_secret_repository()
|
||||
if cs.name == 'private_key':
|
||||
private_key_model = secret_repo.get(
|
||||
cs.secret_id,
|
||||
project_model.external_id)
|
||||
private_key = plugin.get_secret(
|
||||
'application/pkcs8',
|
||||
private_key_model,
|
||||
project_model)
|
||||
elif cs.name == 'private_key_passphrase':
|
||||
passphrase_model = secret_repo.get(
|
||||
cs.secret_id,
|
||||
project_model.external_id)
|
||||
passphrase = plugin.get_secret(
|
||||
'text/plain;charset=utf-8',
|
||||
passphrase_model,
|
||||
project_model)
|
||||
passphrase = str(passphrase)
|
||||
|
||||
if not private_key:
|
||||
raise excep.StoredKeyPrivateKeyNotFound(container.id)
|
||||
|
||||
if passphrase is None:
|
||||
pkey = crypto.load_privatekey(
|
||||
crypto.FILETYPE_PEM,
|
||||
private_key
|
||||
)
|
||||
else:
|
||||
pkey = crypto.load_privatekey(
|
||||
crypto.FILETYPE_PEM,
|
||||
private_key,
|
||||
passphrase.encode('utf-8')
|
||||
)
|
||||
|
||||
subject_name = order_model.meta.get('subject_dn')
|
||||
subject_name_dns = parse_dn(subject_name)
|
||||
extensions = order_model.meta.get('extensions', None)
|
||||
|
||||
req = crypto.X509Req()
|
||||
subj = req.get_subject()
|
||||
|
||||
# Note: must iterate over the DNs in reverse order, or the resulting
|
||||
# subject name will be reversed.
|
||||
for ava in reversed(subject_name_dns):
|
||||
key, val, extra = ava
|
||||
setattr(subj, key.upper(), val)
|
||||
req.set_pubkey(pkey)
|
||||
if extensions:
|
||||
# TODO(alee-3) We need code here to parse the encoded extensions and
|
||||
# convert them into X509Extension objects. This code will also be
|
||||
# used in the validation code. Commenting out for now till we figure
|
||||
# out how to do this.
|
||||
# req.add_extensions(extensions)
|
||||
pass
|
||||
req.sign(pkey, 'sha256')
|
||||
|
||||
csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
return csr
|
||||
|
||||
|
||||
def _get_container_from_order_meta(order_model, project_model):
|
||||
container_ref = order_model.meta.get('container_ref')
|
||||
|
||||
# extract container_id as the last part of the URL
|
||||
container_id = hrefs.get_container_id_from_ref(container_ref)
|
||||
|
||||
container_repo = repos.get_container_repository()
|
||||
container = container_repo.get(container_id,
|
||||
project_model.external_id,
|
||||
suppress_exception=True)
|
||||
return container_id, container
|
||||
|
||||
|
||||
def _notify_ca_unavailable(order_model, result):
|
||||
"""Notify observer(s) that the CA was unavailable at this time."""
|
||||
cert.get_event_plugin_manager().notify_ca_is_unavailable(
|
||||
order_model.project_id,
|
||||
hrefs.convert_order_to_href(order_model.id),
|
||||
result.status_message,
|
||||
result.retry_msec)
|
||||
|
||||
|
||||
def _save_plugin_metadata(order_model, plugin_meta):
|
||||
"""Add plugin metadata to an order."""
|
||||
|
||||
if not isinstance(plugin_meta, dict):
|
||||
plugin_meta = {}
|
||||
|
||||
order_plugin_meta_repo = repos.get_order_plugin_meta_repository()
|
||||
order_plugin_meta_repo.save(plugin_meta, order_model)
|
||||
|
||||
|
||||
def _save_barbican_metadata(order_model, barbican_meta):
|
||||
"""Add barbican metadata to an order."""
|
||||
|
||||
if not isinstance(barbican_meta, dict):
|
||||
barbican_meta = {}
|
||||
|
||||
order_barbican_meta_repo = repos.get_order_barbican_meta_repository()
|
||||
order_barbican_meta_repo.save(barbican_meta, order_model)
|
||||
|
||||
|
||||
def _save_secrets(result, project_model, request_type, order_model):
|
||||
cert_secret_model, transport_key_model = plugin.store_secret(
|
||||
unencrypted_raw=result.certificate,
|
||||
content_type_raw='application/octet-stream',
|
||||
content_encoding='base64',
|
||||
secret_model=models.Secret(),
|
||||
project_model=project_model)
|
||||
|
||||
# save the certificate chain as a secret.
|
||||
if result.intermediates:
|
||||
intermediates_secret_model, transport_key_model = plugin.store_secret(
|
||||
unencrypted_raw=result.intermediates,
|
||||
content_type_raw='application/octet-stream',
|
||||
content_encoding='base64',
|
||||
secret_model=models.Secret(),
|
||||
project_model=project_model
|
||||
)
|
||||
else:
|
||||
intermediates_secret_model = None
|
||||
|
||||
container_model = models.Container()
|
||||
container_model.type = "certificate"
|
||||
container_model.status = models.States.ACTIVE
|
||||
container_model.project_id = project_model.id
|
||||
container_repo = repos.get_container_repository()
|
||||
container_repo.create_from(container_model)
|
||||
|
||||
# create container_secret for certificate
|
||||
new_consec_assoc = models.ContainerSecret()
|
||||
new_consec_assoc.name = 'certificate'
|
||||
new_consec_assoc.container_id = container_model.id
|
||||
new_consec_assoc.secret_id = cert_secret_model.id
|
||||
container_secret_repo = repos.get_container_secret_repository()
|
||||
container_secret_repo.create_from(new_consec_assoc)
|
||||
|
||||
if intermediates_secret_model:
|
||||
# create container_secret for intermediate certs
|
||||
new_consec_assoc = models.ContainerSecret()
|
||||
new_consec_assoc.name = 'intermediates'
|
||||
new_consec_assoc.container_id = container_model.id
|
||||
new_consec_assoc.secret_id = intermediates_secret_model.id
|
||||
container_secret_repo.create_from(new_consec_assoc)
|
||||
|
||||
if request_type == cert.CertificateRequestType.STORED_KEY_REQUEST:
|
||||
_add_private_key_to_generated_cert_container(container_model.id,
|
||||
order_model,
|
||||
project_model)
|
||||
|
||||
return container_model
|
@ -39,16 +39,10 @@ class RetryTasks(object):
|
||||
|
||||
NO_ACTION_REQUIRED - To retry/scheduling actions are required
|
||||
|
||||
|
||||
The following task/context-specific actions are available:
|
||||
|
||||
INVOKE_CERT_STATUS_CHECK_TASK - Check certificate status later
|
||||
|
||||
"""
|
||||
|
||||
INVOKE_SAME_TASK = "Invoke Same Task Again Later"
|
||||
NO_ACTION_REQUIRED = "No Retry/Schedule Actions Are Needed"
|
||||
INVOKE_CERT_STATUS_CHECK_TASK = "Check Certificate Status Later"
|
||||
|
||||
|
||||
class FollowOnProcessingStatusDTO(object):
|
||||
|
@ -24,7 +24,6 @@ from barbican import i18n as u
|
||||
from barbican.model import models
|
||||
from barbican.model import repositories as rep
|
||||
from barbican.plugin import resources as plugin
|
||||
from barbican.tasks import certificate_resources as cert
|
||||
from barbican.tasks import common
|
||||
|
||||
|
||||
@ -288,13 +287,6 @@ class BeginTypeOrder(BaseTask):
|
||||
project)
|
||||
order.container_id = new_container.id
|
||||
LOG.debug("...done creating asymmetric order's secret.")
|
||||
elif order_type == models.OrderType.CERTIFICATE:
|
||||
# Request a certificate
|
||||
new_container = cert.issue_certificate_request(
|
||||
order, project, result_follow_on)
|
||||
if new_container:
|
||||
order.container_id = new_container.id
|
||||
LOG.debug("...done requesting a certificate.")
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
u._('Order type "{order_type}" not implemented.').format(
|
||||
@ -310,107 +302,3 @@ class BeginTypeOrder(BaseTask):
|
||||
def handle_success(self, order, result, *args, **kwargs):
|
||||
self.helper.handle_success(
|
||||
order, result, *args, **kwargs)
|
||||
|
||||
|
||||
class UpdateOrder(BaseTask):
|
||||
"""Handles updating an order."""
|
||||
def get_name(self):
|
||||
return u._('Update Order')
|
||||
|
||||
def __init__(self):
|
||||
super(UpdateOrder, self).__init__()
|
||||
LOG.debug('Creating UpdateOrder task processor')
|
||||
self.helper = _OrderTaskHelper()
|
||||
|
||||
def retrieve_entity(self, *args, **kwargs):
|
||||
return self.helper.retrieve_entity(*args, **kwargs)
|
||||
|
||||
def handle_processing(
|
||||
self, order, order_id, external_project_id, updated_meta):
|
||||
self.handle_order(order, updated_meta)
|
||||
|
||||
def handle_order(self, order, updated_meta):
|
||||
"""Handle Order Update
|
||||
|
||||
:param order: Order to update.
|
||||
"""
|
||||
|
||||
order_info = order.to_dict_fields()
|
||||
order_type = order_info.get('type')
|
||||
|
||||
if order_type == models.OrderType.CERTIFICATE:
|
||||
# Update a certificate request
|
||||
cert.modify_certificate_request(order, updated_meta)
|
||||
LOG.debug("...done updating a certificate order.")
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
u._('Order type "{order_type}" not implemented.').format(
|
||||
order_type=order_type))
|
||||
|
||||
LOG.debug("...done updating order.")
|
||||
|
||||
def handle_error(self, order, status, message, exception,
|
||||
*args, **kwargs):
|
||||
self.helper.handle_error(
|
||||
order, status, message, exception, *args, **kwargs)
|
||||
|
||||
def handle_success(self, order, result, *args, **kwargs):
|
||||
self.helper.handle_success(
|
||||
order, result, *args, **kwargs)
|
||||
|
||||
|
||||
class CheckCertificateStatusOrder(BaseTask):
|
||||
"""Handles checking the status of a certificate order."""
|
||||
|
||||
def get_name(self):
|
||||
return u._('Check Certificate Order Status')
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('Creating CheckCertificateStatusOrder task processor')
|
||||
self.project_repo = rep.get_project_repository()
|
||||
self.helper = _OrderTaskHelper()
|
||||
|
||||
def retrieve_entity(self, *args, **kwargs):
|
||||
return self.helper.retrieve_entity(*args, **kwargs)
|
||||
|
||||
def handle_processing(self, order, *args, **kwargs):
|
||||
return self.handle_order(order)
|
||||
|
||||
def handle_order(self, order):
|
||||
"""Handle checking the status of a certificate order.
|
||||
|
||||
:param order: Order to process.
|
||||
:return: None if no follow on processing is needed for this task,
|
||||
otherwise a :class:`FollowOnProcessingStatusDTO` instance
|
||||
with information on how to process this task into the future.
|
||||
"""
|
||||
result_follow_on = common.FollowOnProcessingStatusDTO()
|
||||
|
||||
order_info = order.to_dict_fields()
|
||||
order_type = order_info.get('type')
|
||||
|
||||
# Retrieve the project.
|
||||
project = self.project_repo.get(order.project_id)
|
||||
|
||||
if order_type != models.OrderType.CERTIFICATE:
|
||||
raise NotImplementedError(
|
||||
u._('Order type "{order_type}" not supported.').format(
|
||||
order_type=order_type))
|
||||
|
||||
# Request a certificate
|
||||
new_container = cert.check_certificate_request(
|
||||
order, project, result_follow_on)
|
||||
if new_container:
|
||||
order.container_id = new_container.id
|
||||
LOG.debug("...done checking status of a certificate order.")
|
||||
|
||||
return result_follow_on
|
||||
|
||||
def handle_error(self, order, status, message, exception,
|
||||
*args, **kwargs):
|
||||
self.helper.handle_error(
|
||||
order, status, message, exception, *args, **kwargs)
|
||||
|
||||
def handle_success(self, order, result, *args, **kwargs):
|
||||
self.helper.handle_success(
|
||||
order, result, *args, **kwargs)
|
||||
|
@ -1,316 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
import testtools
|
||||
|
||||
from barbican.common import utils as common_utils
|
||||
from barbican.model import models
|
||||
from barbican.plugin.interface import certificate_manager as cm
|
||||
from barbican.tests import database_utils
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
class WhenTestingCertificateEventPluginManager(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingCertificateEventPluginManager, self).setUp()
|
||||
|
||||
self.project_id = '1234'
|
||||
self.order_ref = 'http://www.mycerts.com/v1/orders/123456'
|
||||
self.container_ref = 'http://www.mycerts.com/v1/containers/654321'
|
||||
self.error_msg = 'Something is broken'
|
||||
self.retry_in_msec = 5432
|
||||
|
||||
self.plugin_returned = mock.MagicMock()
|
||||
self.plugin_name = common_utils.generate_fullname_for(
|
||||
self.plugin_returned)
|
||||
self.plugin_loaded = mock.MagicMock(obj=self.plugin_returned)
|
||||
self.manager = cm.get_event_plugin_manager()
|
||||
self.manager.extensions = [self.plugin_loaded]
|
||||
|
||||
def test_get_plugin_by_name(self):
|
||||
self.assertEqual(self.plugin_returned,
|
||||
self.manager.get_plugin_by_name(self.plugin_name))
|
||||
|
||||
def test_notify_ca_is_unavailable(self):
|
||||
self.manager.notify_ca_is_unavailable(
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.error_msg,
|
||||
self.retry_in_msec)
|
||||
|
||||
self.plugin_returned.notify_ca_is_unavailable.assert_called_once_with(
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.error_msg,
|
||||
self.retry_in_msec)
|
||||
|
||||
def test_notify_certificate_is_ready(self):
|
||||
self.manager.notify_certificate_is_ready(
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.container_ref)
|
||||
|
||||
pr = self.plugin_returned
|
||||
pr.notify_certificate_is_ready.assert_called_once_with(
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.container_ref)
|
||||
|
||||
def test_invoke_certificate_plugins(self):
|
||||
self.manager._invoke_certificate_plugins(
|
||||
'test_invoke_certificate_plugins',
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.container_ref)
|
||||
|
||||
# The _invoke_certificate_plugins method should invoke on
|
||||
# self.plugin_returned the same method by name as the function
|
||||
# that invoked it...in this case it is this test method.
|
||||
pr = self.plugin_returned
|
||||
pr.test_invoke_certificate_plugins.assert_called_once_with(
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.container_ref)
|
||||
|
||||
def test_raises_error_with_no_plugin_by_name_found(self):
|
||||
self.manager.extensions = []
|
||||
self.assertRaises(
|
||||
cm.CertificateEventPluginNotFound,
|
||||
self.manager.get_plugin_by_name,
|
||||
'any-name-here'
|
||||
)
|
||||
|
||||
def test_raises_error_with_no_plugin_for_invoke_certificate_plugins(self):
|
||||
self.manager.extensions = []
|
||||
self.assertRaises(
|
||||
cm.CertificateEventPluginNotFound,
|
||||
self.manager._invoke_certificate_plugins,
|
||||
self.project_id,
|
||||
self.order_ref,
|
||||
self.error_msg,
|
||||
self.retry_in_msec,
|
||||
)
|
||||
|
||||
|
||||
class WhenTestingCertificatePluginManager(database_utils.RepositoryTestCase,
|
||||
utils.MockModelRepositoryMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingCertificatePluginManager, self).setUp()
|
||||
self.cert_spec = {}
|
||||
|
||||
self.plugin_returned = mock.MagicMock()
|
||||
self.plugin_name = common_utils.generate_fullname_for(
|
||||
self.plugin_returned)
|
||||
types_list = [cm.CertificateRequestType.SIMPLE_CMC_REQUEST,
|
||||
cm.CertificateRequestType.CUSTOM_REQUEST]
|
||||
self.plugin_returned.supported_request_types.return_value = types_list
|
||||
self.plugin_returned.supports.return_value = True
|
||||
self.plugin_loaded = mock.MagicMock(obj=self.plugin_returned)
|
||||
|
||||
expiration = (datetime.datetime.utcnow() + datetime.timedelta(
|
||||
days=cm.CA_INFO_DEFAULT_EXPIRATION_DAYS))
|
||||
ca_info = {
|
||||
cm.INFO_NAME: "my_ca",
|
||||
cm.INFO_DESCRIPTION: "Certificate Authority my_ca",
|
||||
cm.INFO_CA_SIGNING_CERT: "Undefined",
|
||||
cm.INFO_INTERMEDIATES: "Undefined",
|
||||
cm.INFO_EXPIRATION: expiration.isoformat()
|
||||
}
|
||||
self.plugin_returned.get_ca_info.return_value = {
|
||||
'plugin_ca_id1': ca_info
|
||||
}
|
||||
|
||||
parsed_ca = {
|
||||
'plugin_name': self.plugin_name,
|
||||
'plugin_ca_id': 'plugin_ca_id1',
|
||||
'name': self.plugin_name,
|
||||
'description': 'Master CA for default plugin',
|
||||
'ca_signing_certificate': 'ZZZZZ',
|
||||
'intermediates': 'YYYYY'
|
||||
}
|
||||
self.ca = models.CertificateAuthority(parsed_ca)
|
||||
self.ca.id = 'ca_id'
|
||||
|
||||
self.ca_repo = mock.MagicMock()
|
||||
self.ca_repo.get_by_create_date.return_value = (
|
||||
self.ca, 0, 1, 1)
|
||||
self.ca_repo.create_from.return_value = None
|
||||
self.ca_repo.get.return_value = self.ca
|
||||
|
||||
self.project = models.Project()
|
||||
self.project.id = '12345'
|
||||
|
||||
self.setup_ca_repository_mock(self.ca_repo)
|
||||
|
||||
self.plugin_loaded = mock.MagicMock(obj=self.plugin_returned)
|
||||
self.manager = cm.CertificatePluginManager()
|
||||
self.manager.extensions = [self.plugin_loaded]
|
||||
|
||||
def test_get_plugin_by_name(self):
|
||||
self.assertEqual(self.plugin_returned,
|
||||
self.manager.get_plugin_by_name(self.plugin_name))
|
||||
|
||||
def test_get_plugin_by_ca_id(self):
|
||||
self.assertEqual(self.plugin_returned,
|
||||
self.manager.get_plugin_by_ca_id('ca_id'))
|
||||
|
||||
def test_raises_error_with_no_plugin_by_ca_id_found(self):
|
||||
self.ca_repo.get.return_value = None
|
||||
self.assertRaises(
|
||||
cm.CertificatePluginNotFoundForCAID,
|
||||
self.manager.get_plugin_by_ca_id,
|
||||
'any-name-here'
|
||||
)
|
||||
|
||||
def test_raises_error_with_no_plugin_by_name_found(self):
|
||||
self.manager.extensions = []
|
||||
self.assertRaises(
|
||||
cm.CertificatePluginNotFound,
|
||||
self.manager.get_plugin_by_name,
|
||||
'any-name-here'
|
||||
)
|
||||
|
||||
def test_get_plugin_no_request_type_provided(self):
|
||||
# no request_type defaults to "custom"
|
||||
self.assertEqual(self.plugin_returned,
|
||||
self.manager.get_plugin(self.cert_spec))
|
||||
|
||||
def test_get_plugin_request_type_supported(self):
|
||||
self.cert_spec = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.SIMPLE_CMC_REQUEST}
|
||||
self.assertEqual(self.plugin_returned,
|
||||
self.manager.get_plugin(self.cert_spec))
|
||||
|
||||
def test_raises_error_get_plugin_request_type_not_supported(self):
|
||||
self.cert_spec = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.FULL_CMC_REQUEST}
|
||||
self.assertRaises(
|
||||
cm.CertificatePluginNotFound,
|
||||
self.manager.get_plugin,
|
||||
self.cert_spec
|
||||
)
|
||||
|
||||
def test_raises_error_with_no_plugin_found(self):
|
||||
self.manager.extensions = []
|
||||
self.assertRaises(
|
||||
cm.CertificatePluginNotFound,
|
||||
self.manager.get_plugin,
|
||||
self.cert_spec
|
||||
)
|
||||
|
||||
def test_get_plugin_with_ca_to_be_added(self):
|
||||
self.ca_repo.get_by_create_date.return_value = (
|
||||
None, 0, 1, 0)
|
||||
|
||||
self.assertEqual(self.plugin_returned,
|
||||
self.manager.get_plugin(self.cert_spec))
|
||||
|
||||
def test_refresh_ca_list(self):
|
||||
utc_now = datetime.datetime.utcnow()
|
||||
expired_time = utc_now - datetime.timedelta(days=1)
|
||||
expiration = utc_now + datetime.timedelta(days=1)
|
||||
|
||||
ca1_info = {
|
||||
cm.INFO_NAME: "expired_ca_to_be_modified",
|
||||
cm.INFO_DESCRIPTION: "expired_ca to be modified",
|
||||
cm.INFO_CA_SIGNING_CERT: "XXXXXXX-expired-XXXXXX",
|
||||
cm.INFO_INTERMEDIATES: "YYYYYYY-expired-YYYYYYY",
|
||||
cm.INFO_EXPIRATION: expired_time.isoformat()
|
||||
}
|
||||
|
||||
ca1_modified_info = {
|
||||
cm.INFO_NAME: "expired_ca_to_be_modified",
|
||||
cm.INFO_DESCRIPTION: "expired_ca to be modified",
|
||||
cm.INFO_CA_SIGNING_CERT: "XXXXXXX-no-longer-expired-XXXXXX",
|
||||
cm.INFO_INTERMEDIATES: "YYYYYYY-no-longer-expired-YYYYYYY",
|
||||
cm.INFO_EXPIRATION: expiration.isoformat()
|
||||
}
|
||||
|
||||
ca2_info = {
|
||||
cm.INFO_NAME: "expired_ca_to_be_deleted",
|
||||
cm.INFO_DESCRIPTION: "expired ca to be deleted",
|
||||
cm.INFO_CA_SIGNING_CERT: "XXXX-expired-to-be-deleted-XXXX",
|
||||
cm.INFO_INTERMEDIATES: "YYYY-expired-to-be-deleted-YYYY",
|
||||
cm.INFO_EXPIRATION: expired_time.isoformat()
|
||||
}
|
||||
|
||||
ca3_info = {
|
||||
cm.INFO_NAME: "new-ca-to-be-added",
|
||||
cm.INFO_DESCRIPTION: "new-ca-to-be-added",
|
||||
cm.INFO_CA_SIGNING_CERT: "XXXX-to-be-addeed-XXXX",
|
||||
cm.INFO_INTERMEDIATES: "YYYY-to-be-added-YYYY",
|
||||
cm.INFO_EXPIRATION: expiration.isoformat()
|
||||
}
|
||||
|
||||
self.plugin_returned.get_ca_info.return_value = {
|
||||
'plugin_ca_id_ca1': ca1_modified_info,
|
||||
'plugin_ca_id_ca3': ca3_info
|
||||
}
|
||||
|
||||
parsed_ca1 = dict(ca1_info)
|
||||
parsed_ca1[cm.PLUGIN_CA_ID] = 'plugin_ca_id_ca1'
|
||||
parsed_ca1['plugin_name'] = self.plugin_name
|
||||
ca1 = models.CertificateAuthority(parsed_ca1)
|
||||
ca1.id = "ca1_id"
|
||||
|
||||
parsed_ca2 = dict(ca2_info)
|
||||
parsed_ca2[cm.PLUGIN_CA_ID] = 'plugin_ca_id_ca2'
|
||||
parsed_ca2['plugin_name'] = self.plugin_name
|
||||
ca2 = models.CertificateAuthority(parsed_ca2)
|
||||
ca2.id = "ca2_id"
|
||||
|
||||
side_effect = [(None, 0, 4, 0),
|
||||
([ca1, ca2], 0, 4, 2)]
|
||||
self.ca_repo.get_by_create_date.side_effect = side_effect
|
||||
|
||||
self.manager.refresh_ca_table()
|
||||
self.plugin_returned.get_ca_info.assert_called_once_with()
|
||||
self.ca_repo.update_entity.assert_called_once_with(
|
||||
ca1,
|
||||
ca1_modified_info)
|
||||
|
||||
self.ca_repo.delete_entity_by_id.assert_called_once_with(
|
||||
ca2.id,
|
||||
None)
|
||||
self.ca_repo.create_from.assert_has_calls([])
|
||||
|
||||
def test_refresh_ca_list_plugin_when_get_ca_info_raises(self):
|
||||
self.ca_repo.get_by_create_date.return_value = (None, 0, 4, 0)
|
||||
self.plugin_returned.get_ca_info.side_effect = Exception()
|
||||
|
||||
self.manager.refresh_ca_table()
|
||||
|
||||
self.plugin_returned.get_ca_info.assert_called_once_with()
|
||||
|
||||
def test_refresh_ca_list_with_bad_ca_returned_from_plugin(self):
|
||||
|
||||
ca3_info = {
|
||||
cm.INFO_DESCRIPTION: "PLUGIN FAIL: this-ca-has-no-info",
|
||||
}
|
||||
|
||||
self.plugin_returned.get_ca_info.return_value = {
|
||||
'plugin_ca_id_ca3': ca3_info
|
||||
}
|
||||
|
||||
self.ca_repo.get_by_create_date.return_value = (None, 0, 4, 0)
|
||||
self.ca_repo.create_from.side_effect = Exception()
|
||||
|
||||
self.manager.refresh_ca_table()
|
||||
|
||||
self.plugin_returned.get_ca_info.assert_called_once_with()
|
||||
self.ca_repo.create_from.assert_has_calls([])
|
@ -13,8 +13,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import os
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
@ -22,19 +20,14 @@ from unittest import mock
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from requests import exceptions as request_exceptions
|
||||
import testtools
|
||||
|
||||
from barbican.tests import keys
|
||||
from barbican.tests import utils
|
||||
|
||||
try:
|
||||
import barbican.plugin.dogtag as dogtag_import
|
||||
import barbican.plugin.interface.certificate_manager as cm
|
||||
import barbican.plugin.interface.secret_store as sstore
|
||||
|
||||
import pki
|
||||
import pki.cert as dogtag_cert
|
||||
import pki.key as dogtag_key
|
||||
imports_ok = True
|
||||
except ImportError:
|
||||
@ -276,678 +269,3 @@ class WhenTestingDogtagKRAPlugin(utils.BaseTestCase):
|
||||
self.assertFalse(
|
||||
self.plugin.generate_supports(key_spec)
|
||||
)
|
||||
|
||||
|
||||
@testtools.skipIf(not imports_ok, "Dogtag imports not available")
|
||||
class WhenTestingDogtagCAPlugin(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingDogtagCAPlugin, self).setUp()
|
||||
self.certclient_mock = mock.MagicMock(name="CertClient mock")
|
||||
self.patcher = mock.patch('pki.crypto.NSSCryptoProvider')
|
||||
self.patcher2 = mock.patch('pki.client.PKIConnection')
|
||||
self.patcher.start()
|
||||
self.patcher2.start()
|
||||
|
||||
# create nss db for test only
|
||||
self.nss_dir = tempfile.mkdtemp()
|
||||
|
||||
# create expiration file for test
|
||||
fh, self.expiration_data_path = tempfile.mkstemp()
|
||||
exp_time = datetime.datetime.utcnow() + datetime.timedelta(days=2)
|
||||
os.write(fh, exp_time.strftime(
|
||||
"%Y-%m-%d %H:%M:%S.%f"))
|
||||
os.close(fh)
|
||||
|
||||
# create host CA file for test
|
||||
fh, self.host_ca_path = tempfile.mkstemp()
|
||||
os.write(fh, "host_ca_aid")
|
||||
os.close(fh)
|
||||
|
||||
self.approved_profile_id = "caServerCert"
|
||||
CONF = dogtag_import.CONF
|
||||
CONF.dogtag_plugin.nss_db_path = self.nss_dir
|
||||
CONF.dogtag_plugin.ca_expiration_data_path = self.expiration_data_path
|
||||
CONF.dogtag_plugin.ca_host_aid_path = self.host_ca_path
|
||||
CONF.dogtag_plugin.auto_approved_profiles = [self.approved_profile_id]
|
||||
CONF.dogtag_plugin.dogtag_host = "localhost"
|
||||
CONF.dogtag_plugin.dogtag_port = 8443
|
||||
CONF.dogtag_plugin.simple_cmc_profile = "caOtherCert"
|
||||
self.cfg = CONF
|
||||
|
||||
self.plugin = dogtag_import.DogtagCAPlugin(CONF)
|
||||
self.plugin.certclient = self.certclient_mock
|
||||
self.order_id = mock.MagicMock()
|
||||
self.profile_id = mock.MagicMock()
|
||||
|
||||
# request generated
|
||||
self.request_id_mock = mock.MagicMock()
|
||||
self.request = dogtag_cert.CertRequestInfo()
|
||||
self.request.request_id = self.request_id_mock
|
||||
self.request.request_status = dogtag_cert.CertRequestStatus.COMPLETE
|
||||
self.cert_id_mock = mock.MagicMock()
|
||||
self.request.cert_id = self.cert_id_mock
|
||||
|
||||
# cert generated
|
||||
self.cert = mock.MagicMock()
|
||||
self.cert.encoded = keys.get_certificate_pem()
|
||||
self.cert.pkcs7_cert_chain = keys.get_certificate_der()
|
||||
|
||||
# for cancel/modify
|
||||
self.review_response = mock.MagicMock()
|
||||
|
||||
# modified request
|
||||
self.modified_request = mock.MagicMock()
|
||||
self.modified_request_id_mock = mock.MagicMock()
|
||||
self.modified_request.request_id = self.modified_request_id_mock
|
||||
self.modified_request.request_status = (
|
||||
dogtag_cert.CertRequestStatus.COMPLETE)
|
||||
self.modified_request.cert_id = self.cert_id_mock
|
||||
|
||||
self.barbican_meta_dto = cm.BarbicanMetaDTO()
|
||||
|
||||
def tearDown(self):
|
||||
super(WhenTestingDogtagCAPlugin, self).tearDown()
|
||||
self.patcher2.stop()
|
||||
self.patcher.stop()
|
||||
os.rmdir(self.nss_dir)
|
||||
os.remove(self.host_ca_path)
|
||||
os.remove(self.expiration_data_path)
|
||||
|
||||
def _process_approved_profile_request(self, order_meta, plugin_meta):
|
||||
enrollment_result = dogtag_cert.CertEnrollmentResult(
|
||||
self.request, self.cert)
|
||||
enrollment_results = [enrollment_result]
|
||||
self.certclient_mock.enroll_cert.return_value = enrollment_results
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.enroll_cert.assert_called_once_with(
|
||||
self.approved_profile_id,
|
||||
order_meta)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CERTIFICATE_GENERATED,
|
||||
result_dto.status,
|
||||
"result_dto status incorrect")
|
||||
|
||||
self.assertEqual(base64.b64encode(keys.get_certificate_pem()),
|
||||
result_dto.certificate)
|
||||
|
||||
self.assertEqual(
|
||||
self.request_id_mock,
|
||||
plugin_meta.get(dogtag_import.DogtagCAPlugin.REQUEST_ID)
|
||||
)
|
||||
|
||||
def _process_non_approved_profile_request(self, order_meta, plugin_meta,
|
||||
profile_id, inputs=None):
|
||||
if inputs is None:
|
||||
inputs = {
|
||||
'cert_request_type': 'pkcs10',
|
||||
'cert_request': base64.b64decode(
|
||||
order_meta.get('request_data'))
|
||||
}
|
||||
|
||||
# mock CertRequestInfo
|
||||
enrollment_result = dogtag_cert.CertRequestInfo()
|
||||
enrollment_result.request_id = self.request_id_mock
|
||||
enrollment_result.request_status = (
|
||||
dogtag_cert.CertRequestStatus.PENDING)
|
||||
|
||||
# mock CertRequestInfoCollection
|
||||
enrollment_results = dogtag_cert.CertRequestInfoCollection()
|
||||
enrollment_results.cert_request_info_list = (
|
||||
[enrollment_result])
|
||||
|
||||
self.certclient_mock.create_enrollment_request.return_value = (
|
||||
enrollment_result)
|
||||
self.certclient_mock.submit_enrollment_request.return_value = (
|
||||
enrollment_results)
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.create_enrollment_request.assert_called_once_with(
|
||||
profile_id, inputs)
|
||||
|
||||
self.certclient_mock.submit_enrollment_request.assert_called_once_with(
|
||||
enrollment_result)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.WAITING_FOR_CA,
|
||||
result_dto.status,
|
||||
"result_dto status incorrect")
|
||||
|
||||
self.assertEqual(
|
||||
self.request_id_mock,
|
||||
plugin_meta.get(dogtag_import.DogtagCAPlugin.REQUEST_ID)
|
||||
)
|
||||
|
||||
def test_issue_simple_cmc_request(self):
|
||||
order_meta = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.SIMPLE_CMC_REQUEST,
|
||||
'request_data': base64.b64encode(keys.get_csr_pem())
|
||||
}
|
||||
plugin_meta = {}
|
||||
self._process_non_approved_profile_request(
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.cfg.dogtag_plugin.simple_cmc_profile)
|
||||
|
||||
def test_issue_full_cmc_request(self):
|
||||
order_meta = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.FULL_CMC_REQUEST,
|
||||
'request_data': 'Full CMC data ...'
|
||||
}
|
||||
plugin_meta = {}
|
||||
|
||||
self.assertRaises(
|
||||
dogtag_import.DogtagPluginNotSupportedException,
|
||||
self.plugin.issue_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
def test_issue_stored_key_request(self):
|
||||
order_meta = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.STORED_KEY_REQUEST,
|
||||
'request_data': base64.b64encode(keys.get_csr_pem())
|
||||
}
|
||||
plugin_meta = {}
|
||||
self._process_non_approved_profile_request(
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.cfg.dogtag_plugin.simple_cmc_profile)
|
||||
|
||||
def test_issue_custom_key_request(self):
|
||||
order_meta = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.CUSTOM_REQUEST,
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id,
|
||||
}
|
||||
plugin_meta = {}
|
||||
self._process_approved_profile_request(order_meta, plugin_meta)
|
||||
|
||||
def test_issue_no_cert_request_type_provided(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
self._process_approved_profile_request(order_meta, plugin_meta)
|
||||
|
||||
def test_issue_bad_cert_request_type_provided(self):
|
||||
order_meta = {
|
||||
cm.REQUEST_TYPE: 'BAD_REQUEST_TYPE',
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.profile_id,
|
||||
}
|
||||
plugin_meta = {}
|
||||
|
||||
self.assertRaises(
|
||||
dogtag_import.DogtagPluginNotSupportedException,
|
||||
self.plugin.issue_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
def test_issue_return_data_error_with_no_profile_id(self):
|
||||
order_meta = {}
|
||||
plugin_meta = {}
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.assertEqual(result_dto.status,
|
||||
cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
"result_dto status incorrect")
|
||||
|
||||
self.assertEqual(result_dto.status_message,
|
||||
"No profile_id specified")
|
||||
|
||||
def test_issue_return_data_error_with_request_rejected(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
self.request.request_status = dogtag_cert.CertRequestStatus.REJECTED
|
||||
|
||||
enrollment_result = dogtag_cert.CertEnrollmentResult(
|
||||
self.request, None)
|
||||
enrollment_results = [enrollment_result]
|
||||
self.certclient_mock.enroll_cert.return_value = enrollment_results
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.enroll_cert.assert_called_once_with(
|
||||
self.approved_profile_id,
|
||||
order_meta)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
result_dto.status,
|
||||
"result_dto status incorrect")
|
||||
|
||||
self.assertEqual(
|
||||
self.request_id_mock,
|
||||
plugin_meta.get(dogtag_import.DogtagCAPlugin.REQUEST_ID))
|
||||
|
||||
def test_issue_return_canceled_with_request_canceled(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
self.request.request_status = dogtag_cert.CertRequestStatus.CANCELED
|
||||
|
||||
enrollment_result = dogtag_cert.CertEnrollmentResult(
|
||||
self.request, None)
|
||||
enrollment_results = [enrollment_result]
|
||||
self.certclient_mock.enroll_cert.return_value = enrollment_results
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.enroll_cert.assert_called_once_with(
|
||||
self.approved_profile_id,
|
||||
order_meta)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.REQUEST_CANCELED,
|
||||
result_dto.status,
|
||||
"result_dto status incorrect")
|
||||
|
||||
self.assertEqual(
|
||||
self.request_id_mock,
|
||||
plugin_meta.get(dogtag_import.DogtagCAPlugin.REQUEST_ID),
|
||||
)
|
||||
|
||||
def test_issue_return_waiting_with_request_pending(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: "otherProfile",
|
||||
'cert_request': base64.b64encode(keys.get_csr_pem())}
|
||||
plugin_meta = {}
|
||||
inputs = {
|
||||
'cert_request': keys.get_csr_pem(),
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: "otherProfile"
|
||||
}
|
||||
self._process_non_approved_profile_request(
|
||||
order_meta, plugin_meta, "otherProfile", inputs)
|
||||
|
||||
def test_issue_raises_error_request_complete_no_cert(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
|
||||
enrollment_result = dogtag_cert.CertEnrollmentResult(
|
||||
self.request, None)
|
||||
enrollment_results = [enrollment_result]
|
||||
self.certclient_mock.enroll_cert.return_value = enrollment_results
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.issue_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
self.request_id_mock,
|
||||
plugin_meta.get(dogtag_import.DogtagCAPlugin.REQUEST_ID)
|
||||
)
|
||||
|
||||
def test_issue_raises_error_request_unknown_status(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
|
||||
self.request.request_status = "unknown_status"
|
||||
enrollment_result = dogtag_cert.CertEnrollmentResult(
|
||||
self.request, None)
|
||||
enrollment_results = [enrollment_result]
|
||||
self.certclient_mock.enroll_cert.return_value = enrollment_results
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.issue_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
self.request_id_mock,
|
||||
plugin_meta.get(dogtag_import.DogtagCAPlugin.REQUEST_ID)
|
||||
)
|
||||
|
||||
def test_issue_return_client_error_bad_request_exception(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
|
||||
self.certclient_mock.enroll_cert.side_effect = (
|
||||
pki.BadRequestException("bad request"))
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.enroll_cert.assert_called_once_with(
|
||||
self.approved_profile_id,
|
||||
order_meta)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
result_dto.status,
|
||||
"result_dto status incorrect")
|
||||
|
||||
def test_issue_raises_error_pki_exception(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
|
||||
self.certclient_mock.enroll_cert.side_effect = (
|
||||
pki.PKIException("generic enrollment error"))
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.issue_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
def test_issue_return_ca_unavailable(self):
|
||||
order_meta = {
|
||||
dogtag_import.DogtagCAPlugin.PROFILE_ID: self.approved_profile_id}
|
||||
plugin_meta = {}
|
||||
|
||||
self.certclient_mock.enroll_cert.side_effect = (
|
||||
request_exceptions.RequestException())
|
||||
|
||||
result_dto = self.plugin.issue_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.enroll_cert.assert_called_once_with(
|
||||
self.approved_profile_id,
|
||||
order_meta)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST,
|
||||
result_dto.status,
|
||||
"result_dto status incorrect")
|
||||
|
||||
def test_cancel_request(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.cancel_request.return_value = None
|
||||
self.certclient_mock.review_request.return_value = self.review_response
|
||||
|
||||
result_dto = self.plugin.cancel_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.cancel_request.assert_called_once_with(
|
||||
self.request_id_mock,
|
||||
self.review_response)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.REQUEST_CANCELED,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_cancel_no_request_found(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.review_request.side_effect = (
|
||||
pki.RequestNotFoundException("request_not_found"))
|
||||
|
||||
result_dto = self.plugin.cancel_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.review_request.assert_called_once_with(
|
||||
self.request_id_mock)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_cancel_conflicting_operation(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.review_request.return_value = self.review_response
|
||||
self.certclient_mock.cancel_request.side_effect = (
|
||||
pki.ConflictingOperationException("conflicting_operation"))
|
||||
|
||||
result_dto = self.plugin.cancel_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.cancel_request.assert_called_once_with(
|
||||
self.request_id_mock,
|
||||
self.review_response)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.INVALID_OPERATION,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_cancel_ca_unavailable(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.review_request.side_effect = (
|
||||
request_exceptions.RequestException("request_exception"))
|
||||
|
||||
result_dto = self.plugin.cancel_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_cancel_raise_error_no_request_id(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {}
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.cancel_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
def test_check_status(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.get_request.return_value = self.request
|
||||
self.certclient_mock.get_cert.return_value = self.cert
|
||||
|
||||
result_dto = self.plugin.check_certificate_status(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.get_request.assert_called_once_with(
|
||||
self.request_id_mock)
|
||||
|
||||
self.certclient_mock.get_cert.assert_called_once_with(
|
||||
self.cert_id_mock)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CERTIFICATE_GENERATED,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
self.assertEqual(keys.get_certificate_pem(),
|
||||
result_dto.certificate)
|
||||
|
||||
def test_check_status_raise_error_no_request_id(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {}
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.check_certificate_status,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
def test_check_status_rejected(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.request.request_status = dogtag_cert.CertRequestStatus.REJECTED
|
||||
self.certclient_mock.get_request.return_value = self.request
|
||||
|
||||
result_dto = self.plugin.check_certificate_status(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.get_request.assert_called_once_with(
|
||||
self.request_id_mock)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
self.assertIsNone(result_dto.certificate)
|
||||
|
||||
def test_check_status_canceled(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.request.request_status = dogtag_cert.CertRequestStatus.CANCELED
|
||||
self.certclient_mock.get_request.return_value = self.request
|
||||
|
||||
result_dto = self.plugin.check_certificate_status(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.get_request.assert_called_once_with(
|
||||
self.request_id_mock)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.REQUEST_CANCELED,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
self.assertIsNone(result_dto.certificate)
|
||||
|
||||
def test_check_status_pending(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.request.request_status = dogtag_cert.CertRequestStatus.PENDING
|
||||
self.certclient_mock.get_request.return_value = self.request
|
||||
|
||||
result_dto = self.plugin.check_certificate_status(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.get_request.assert_called_once_with(
|
||||
self.request_id_mock)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.WAITING_FOR_CA,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
self.assertIsNone(result_dto.certificate)
|
||||
|
||||
def test_check_status_raises_error_complete_no_cert(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.get_request.return_value = self.request
|
||||
self.certclient_mock.get_cert.return_value = None
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.check_certificate_status,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
||||
def test_modify_request(self):
|
||||
order_meta = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.SIMPLE_CMC_REQUEST,
|
||||
'request_data': base64.b64encode(keys.get_csr_pem())
|
||||
}
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self._process_non_approved_profile_request(
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.cfg.dogtag_plugin.simple_cmc_profile)
|
||||
|
||||
self.certclient_mock.cancel_request.return_value = None
|
||||
self.certclient_mock.review_request.return_value = self.review_response
|
||||
|
||||
result_dto = self.plugin.modify_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.cancel_request.assert_called_once_with(
|
||||
self.request_id_mock,
|
||||
self.review_response)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.WAITING_FOR_CA,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_modify_no_request_found(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.review_request.side_effect = (
|
||||
pki.RequestNotFoundException("request_not_found"))
|
||||
|
||||
result_dto = self.plugin.modify_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.review_request.assert_called_once_with(
|
||||
self.request_id_mock)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_modify_conflicting_operation(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.review_request.return_value = self.review_response
|
||||
self.certclient_mock.cancel_request.side_effect = (
|
||||
pki.ConflictingOperationException("conflicting_operation"))
|
||||
|
||||
result_dto = self.plugin.modify_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.certclient_mock.cancel_request.assert_called_once_with(
|
||||
self.request_id_mock,
|
||||
self.review_response)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.INVALID_OPERATION,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_modify_ca_unavailable(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {dogtag_import.DogtagCAPlugin.REQUEST_ID:
|
||||
self.request_id_mock}
|
||||
self.certclient_mock.review_request.side_effect = (
|
||||
request_exceptions.RequestException("request_exception"))
|
||||
|
||||
result_dto = self.plugin.modify_certificate_request(
|
||||
self.order_id, order_meta, plugin_meta, self.barbican_meta_dto)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST,
|
||||
result_dto.status,
|
||||
"result_dto_status incorrect")
|
||||
|
||||
def test_modify_raise_error_no_request_id(self):
|
||||
order_meta = mock.ANY
|
||||
plugin_meta = {}
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.modify_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto
|
||||
)
|
||||
|
@ -1,83 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import testtools
|
||||
|
||||
import barbican.plugin.interface.certificate_manager as cm
|
||||
import barbican.plugin.simple_certificate_manager as simple
|
||||
|
||||
|
||||
class WhenTestingSimpleCertificateManagerPlugin(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingSimpleCertificateManagerPlugin, self).setUp()
|
||||
self.plugin = simple.SimpleCertificatePlugin()
|
||||
|
||||
def test_issue_certificate_request(self):
|
||||
result = self.plugin.issue_certificate_request(None, None, None, None)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.WAITING_FOR_CA, result.status)
|
||||
|
||||
def test_check_certificate_status(self):
|
||||
result = self.plugin.check_certificate_status(None, None, None, None)
|
||||
|
||||
self.assertEqual(
|
||||
cm.CertificateStatus.CERTIFICATE_GENERATED, result.status)
|
||||
|
||||
def test_modify_certificate_request(self):
|
||||
result = self.plugin.modify_certificate_request(None, None, None, None)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.WAITING_FOR_CA, result.status)
|
||||
|
||||
def test_cancel_certificate_request(self):
|
||||
result = self.plugin.cancel_certificate_request(None, None, None, None)
|
||||
|
||||
self.assertEqual(cm.CertificateStatus.REQUEST_CANCELED, result.status)
|
||||
|
||||
def test_supports(self):
|
||||
result = self.plugin.supports(None)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_get_ca_info(self):
|
||||
result = self.plugin.get_ca_info()
|
||||
name = self.plugin.get_default_ca_name()
|
||||
self.assertIn(name, result)
|
||||
self.assertEqual(name, result[name][cm.INFO_NAME])
|
||||
self.assertEqual(self.plugin.get_default_signing_cert(),
|
||||
result[name][cm.INFO_CA_SIGNING_CERT])
|
||||
|
||||
def test_supported_request_types(self):
|
||||
result = self.plugin.supported_request_types()
|
||||
supported_list = [cm.CertificateRequestType.CUSTOM_REQUEST,
|
||||
cm.CertificateRequestType.SIMPLE_CMC_REQUEST,
|
||||
cm.CertificateRequestType.FULL_CMC_REQUEST,
|
||||
cm.CertificateRequestType.STORED_KEY_REQUEST]
|
||||
self.assertEqual(supported_list, result)
|
||||
|
||||
|
||||
class WhenTestingSimpleCertificateEventManagerPlugin(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingSimpleCertificateEventManagerPlugin, self).setUp()
|
||||
self.plugin = simple.SimpleCertificateEventPlugin()
|
||||
|
||||
def test_notify_ca_is_unavailable(self):
|
||||
# Test that eventing plugin method does not have side effects such as
|
||||
# raising exceptions.
|
||||
self.plugin.notify_ca_is_unavailable(None, None, None, None)
|
||||
|
||||
def test_notify_certificate_is_ready(self):
|
||||
# Test that eventing plugin method does not have side effects such as
|
||||
# raising exceptions.
|
||||
self.plugin.notify_certificate_is_ready(None, None, None)
|
@ -1,453 +0,0 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import base64
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import pkcs7
|
||||
from cryptography import x509
|
||||
import fixtures
|
||||
from OpenSSL import crypto
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
|
||||
import barbican.plugin.interface.certificate_manager as cm
|
||||
from barbican.plugin import snakeoil_ca
|
||||
from barbican.tests import certificate_utils
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
class BaseTestCase(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self.conf = self.useFixture(oslo_fixture.Config(
|
||||
conf=snakeoil_ca.CONF)).conf
|
||||
self.tmp_dir = self.useFixture(fixtures.TempDir()).path
|
||||
|
||||
def tearDown(self):
|
||||
super(BaseTestCase, self).tearDown()
|
||||
|
||||
|
||||
class CaTestCase(BaseTestCase):
|
||||
|
||||
def test_gen_cacert_no_file_storage(self):
|
||||
subject_dn = (
|
||||
'cn=Test CN,o=Test O,L=Test L,st=Test ST,ou=Test OU'
|
||||
)
|
||||
ca = snakeoil_ca.SnakeoilCA(cert_path=None, key_path=None,
|
||||
key_size=512, subject_dn=subject_dn)
|
||||
subject = ca.cert.get_subject()
|
||||
self.assertIsNotNone(ca.key)
|
||||
self.assertEqual("Test ST", subject.ST)
|
||||
self.assertEqual("Test L", subject.L)
|
||||
self.assertEqual("Test O", subject.O)
|
||||
self.assertEqual("Test CN", subject.CN)
|
||||
self.assertEqual("Test OU", subject.OU)
|
||||
self.assertEqual(
|
||||
ca.chain,
|
||||
crypto.dump_certificate(crypto.FILETYPE_PEM, ca.cert))
|
||||
|
||||
def test_gen_cacert_with_file_storage(self):
|
||||
cert_path = self.tmp_dir + 'cert.pem'
|
||||
key_path = self.tmp_dir + 'key.pem'
|
||||
chain_path = self.tmp_dir + 'cert.chain'
|
||||
pkcs7_path = self.tmp_dir + 'cert.p7b'
|
||||
|
||||
subject_dn = 'cn=Test CN,o=Test O,L=Test L,st=Test ST'
|
||||
ca = snakeoil_ca.SnakeoilCA(
|
||||
cert_path=cert_path,
|
||||
key_path=key_path,
|
||||
chain_path=chain_path,
|
||||
pkcs7_path=pkcs7_path,
|
||||
key_size=2048,
|
||||
subject_dn=subject_dn)
|
||||
|
||||
subject = ca.cert.get_subject()
|
||||
self.assertEqual(
|
||||
ca.chain,
|
||||
crypto.dump_certificate(crypto.FILETYPE_PEM, ca.cert))
|
||||
self.assertIsNotNone(ca.key)
|
||||
self.assertEqual("Test ST", subject.ST)
|
||||
self.assertEqual("Test L", subject.L)
|
||||
self.assertEqual("Test O", subject.O)
|
||||
self.assertEqual("Test CN", subject.CN)
|
||||
|
||||
# Make sure we preserve existing keypairs
|
||||
ca = snakeoil_ca.SnakeoilCA(
|
||||
cert_path=cert_path,
|
||||
key_path=key_path,
|
||||
chain_path=chain_path,
|
||||
pkcs7_path=pkcs7_path
|
||||
)
|
||||
subject = ca.cert.get_subject()
|
||||
self.assertEqual("Test ST", subject.ST)
|
||||
self.assertEqual("Test L", subject.L)
|
||||
self.assertEqual("Test O", subject.O)
|
||||
self.assertEqual("Test CN", subject.CN)
|
||||
|
||||
def test_gen_sub_cacert_with_file_storage(self):
|
||||
cert_path = self.tmp_dir + 'cert.pem'
|
||||
key_path = self.tmp_dir + 'key.pem'
|
||||
chain_path = self.tmp_dir + 'cert.chain'
|
||||
pkcs7_path = self.tmp_dir + 'cert.p7b'
|
||||
|
||||
subject_dn = 'cn=Test CN,o=Test O,L=Test L,st=Test ST'
|
||||
parent_ca = snakeoil_ca.SnakeoilCA(
|
||||
cert_path=cert_path,
|
||||
key_path=key_path,
|
||||
chain_path=chain_path,
|
||||
pkcs7_path=pkcs7_path,
|
||||
key_size=2048,
|
||||
subject_dn=subject_dn)
|
||||
self.assertIsNotNone(parent_ca)
|
||||
|
||||
# create a sub-ca
|
||||
subject_dn = 'cn=Sub CA Test CN,o=Test O,L=Test L,st=Test ST'
|
||||
cert_path = self.tmp_dir + 'sub_cert.pem'
|
||||
key_path = self.tmp_dir + 'sub_key.pem'
|
||||
chain_path = self.tmp_dir + 'sub_cert.chain'
|
||||
pkcs7_path = self.tmp_dir + 'sub_cert.p7b'
|
||||
|
||||
sub_ca = snakeoil_ca.SnakeoilCA(
|
||||
cert_path=cert_path,
|
||||
key_path=key_path,
|
||||
chain_path=chain_path,
|
||||
pkcs7_path=pkcs7_path,
|
||||
key_size=2048,
|
||||
subject_dn=subject_dn,
|
||||
parent_chain_path=parent_ca.chain_path,
|
||||
signing_dn=parent_ca.subject_dn,
|
||||
signing_key=parent_ca.key
|
||||
)
|
||||
|
||||
subject = sub_ca.cert.get_subject()
|
||||
self.assertEqual("Test ST", subject.ST)
|
||||
self.assertEqual("Test L", subject.L)
|
||||
self.assertEqual("Test O", subject.O)
|
||||
self.assertEqual("Sub CA Test CN", subject.CN)
|
||||
|
||||
|
||||
class CertManagerTestCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CertManagerTestCase, self).setUp()
|
||||
subject_dn = 'cn=Test CN,o=Test O,L=Test L,st=Test ST'
|
||||
self.ca = snakeoil_ca.SnakeoilCA(cert_path=None, key_path=None,
|
||||
key_size=512, subject_dn=subject_dn)
|
||||
|
||||
def verify_sig(self, encoded_cert):
|
||||
cert = x509.load_der_x509_certificate(encoded_cert, default_backend())
|
||||
|
||||
crypto.verify(
|
||||
self.ca.cert,
|
||||
cert.signature,
|
||||
cert.tbs_certificate_bytes,
|
||||
'sha256')
|
||||
|
||||
def test_gen_cert_no_file_storage(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
cm = snakeoil_ca.CertManager(self.ca)
|
||||
cert = cm.make_certificate(req)
|
||||
first_serial = cert.get_serial_number()
|
||||
cert_enc = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
|
||||
self.verify_sig(cert_enc)
|
||||
|
||||
cert = cm.make_certificate(req)
|
||||
self.assertNotEqual(first_serial, cert.get_serial_number())
|
||||
self.verify_sig(cert_enc)
|
||||
|
||||
cm = snakeoil_ca.CertManager(self.ca)
|
||||
cert = cm.make_certificate(req)
|
||||
|
||||
def test_gen_cert_with_file_storage(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
cm = snakeoil_ca.CertManager(self.ca)
|
||||
cert = cm.make_certificate(req)
|
||||
cert_enc = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
|
||||
first_serial = cert.get_serial_number()
|
||||
self.verify_sig(cert_enc)
|
||||
|
||||
cm = snakeoil_ca.CertManager(self.ca)
|
||||
cert = cm.make_certificate(req)
|
||||
self.assertNotEqual(first_serial, cert.get_serial_number())
|
||||
|
||||
|
||||
class SnakeoilCAPluginTestCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SnakeoilCAPluginTestCase, self).setUp()
|
||||
self.ca_cert_path = os.path.join(self.tmp_dir, 'ca.cert')
|
||||
self.ca_key_path = os.path.join(self.tmp_dir, 'ca.key')
|
||||
self.ca_chain_path = os.path.join(self.tmp_dir, 'ca.chain')
|
||||
self.ca_pkcs7_path = os.path.join(self.tmp_dir, 'ca.pkcs7')
|
||||
self.db_dir = self.tmp_dir
|
||||
|
||||
self.conf.snakeoil_ca_plugin.subca_cert_key_directory = os.path.join(
|
||||
self.tmp_dir, 'subca_cert_key_dir')
|
||||
self.subca_cert_key_directory = (
|
||||
self.conf.snakeoil_ca_plugin.subca_cert_key_directory)
|
||||
|
||||
self.plugin = snakeoil_ca.SnakeoilCACertificatePlugin(
|
||||
self.conf)
|
||||
self.order_id = mock.MagicMock()
|
||||
self.barbican_meta_dto = cm.BarbicanMetaDTO()
|
||||
|
||||
def test_issue_certificate_request(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
req_enc = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
req_enc = base64.b64encode(req_enc)
|
||||
order_meta = {'request_data': req_enc}
|
||||
resp = self.plugin.issue_certificate_request(self.order_id,
|
||||
order_meta, {},
|
||||
self.barbican_meta_dto)
|
||||
crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, base64.b64decode(resp.certificate))
|
||||
|
||||
def test_issue_certificate_request_with_ca_id(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
req_enc = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
req_enc = base64.b64encode(req_enc)
|
||||
order_meta = {'request_data': req_enc}
|
||||
plugin_meta = {'plugin_ca_id': self.plugin.get_default_ca_name()}
|
||||
self.barbican_meta_dto.plugin_ca_id = self.plugin.get_default_ca_name()
|
||||
resp = self.plugin.issue_certificate_request(self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto)
|
||||
crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, base64.b64decode(resp.certificate))
|
||||
|
||||
def test_issue_raises_with_invalid_ca_id(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
req_enc = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
req_enc = base64.b64encode(req_enc)
|
||||
order_meta = {'request_data': req_enc}
|
||||
plugin_meta = {'plugin_ca_id': "invalid_ca_id"}
|
||||
self.barbican_meta_dto.plugin_ca_id = "invalid_ca_id"
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.issue_certificate_request,
|
||||
self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto)
|
||||
|
||||
def test_issue_certificate_request_set_subject(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
subj = req.get_subject()
|
||||
subj.countryName = 'US'
|
||||
subj.stateOrProvinceName = 'OR'
|
||||
subj.localityName = 'Testlandia'
|
||||
subj.organizationName = 'Testers Anon'
|
||||
subj.organizationalUnitName = 'Testers OU'
|
||||
subj.commonName = 'Testing'
|
||||
|
||||
req_enc = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
req_enc = base64.b64encode(req_enc)
|
||||
order_meta = {'request_data': req_enc}
|
||||
resp = self.plugin.issue_certificate_request(self.order_id,
|
||||
order_meta, {},
|
||||
self.barbican_meta_dto)
|
||||
cert = crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, base64.b64decode(resp.certificate))
|
||||
cert_subj = cert.get_subject()
|
||||
self.assertEqual('US', cert_subj.C)
|
||||
self.assertEqual('OR', cert_subj.ST)
|
||||
self.assertEqual('Testlandia', cert_subj.L)
|
||||
self.assertEqual('Testers Anon', cert_subj.O)
|
||||
self.assertEqual('Testers OU', cert_subj.OU)
|
||||
self.assertEqual('Testing', cert_subj.CN)
|
||||
|
||||
def test_issue_certificate_request_stored_key(self):
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
req_enc = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
self.barbican_meta_dto.generated_csr = req_enc
|
||||
resp = self.plugin.issue_certificate_request(
|
||||
self.order_id, {}, {}, self.barbican_meta_dto)
|
||||
crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, base64.b64decode(resp.certificate))
|
||||
|
||||
def test_no_request_data(self):
|
||||
res = self.plugin.issue_certificate_request(
|
||||
self.order_id, {}, {}, self.barbican_meta_dto)
|
||||
self.assertIs(cm.CertificateStatus.CLIENT_DATA_ISSUE_SEEN,
|
||||
res.status)
|
||||
self.assertEqual("No request_data specified", res.status_message)
|
||||
|
||||
def test_get_default_ca_name(self):
|
||||
self.assertEqual("Snakeoil CA", self.plugin.get_default_ca_name())
|
||||
|
||||
def test_get_default_signing_cert(self):
|
||||
ca_cert = self.plugin.get_default_signing_cert()
|
||||
self.assertEqual(
|
||||
crypto.dump_certificate(crypto.FILETYPE_PEM, self.plugin.ca.cert),
|
||||
ca_cert)
|
||||
|
||||
def test_get_default_intermediates_none(self):
|
||||
intermediates = self.plugin.get_default_intermediates()
|
||||
self.assertIsNone(intermediates)
|
||||
|
||||
def test_not_implemented(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.plugin.modify_certificate_request,
|
||||
'', {}, {}, {})
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.plugin.cancel_certificate_request,
|
||||
'', {}, {}, {})
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.plugin.check_certificate_status,
|
||||
'', {}, {}, {})
|
||||
|
||||
def test_support_request_types(self):
|
||||
manager = cm.CertificatePluginManager()
|
||||
manager.extensions = [mock.MagicMock(obj=self.plugin)]
|
||||
cert_spec = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.CUSTOM_REQUEST}
|
||||
self.assertEqual(self.plugin, manager.get_plugin(cert_spec))
|
||||
self.assertTrue(self.plugin.supports(cert_spec))
|
||||
cert_spec = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.STORED_KEY_REQUEST}
|
||||
self.assertEqual(self.plugin, manager.get_plugin(cert_spec))
|
||||
self.assertTrue(self.plugin.supports(cert_spec))
|
||||
cert_spec = {
|
||||
cm.REQUEST_TYPE: cm.CertificateRequestType.FULL_CMC_REQUEST}
|
||||
self.assertRaises(cm.CertificatePluginNotFound,
|
||||
manager.get_plugin, cert_spec)
|
||||
self.assertFalse(self.plugin.supports(cert_spec))
|
||||
|
||||
def test_supports_create_ca(self):
|
||||
self.assertTrue(self.plugin.supports_create_ca())
|
||||
|
||||
def _create_subca(self):
|
||||
create_ca_dto = cm.CACreateDTO(
|
||||
name="sub ca1",
|
||||
description="subordinate ca",
|
||||
subject_dn="cn=subordinate ca signing cert, o=example.com",
|
||||
parent_ca_id=self.plugin.get_default_ca_name()
|
||||
)
|
||||
return self.plugin.create_ca(create_ca_dto)
|
||||
|
||||
def test_create_ca(self):
|
||||
subca_dict = self._create_subca()
|
||||
self.assertEqual("sub ca1", subca_dict.get(cm.INFO_NAME))
|
||||
self.assertIsNotNone(subca_dict.get(cm.INFO_EXPIRATION))
|
||||
self.assertIsNotNone(subca_dict.get(cm.PLUGIN_CA_ID))
|
||||
ca_cert = subca_dict.get(cm.INFO_CA_SIGNING_CERT)
|
||||
self.assertIsNotNone(ca_cert)
|
||||
|
||||
intermediates = subca_dict.get(cm.INFO_INTERMEDIATES)
|
||||
self.assertIsNotNone(intermediates)
|
||||
|
||||
cacert = crypto.load_certificate(crypto.FILETYPE_PEM, ca_cert)
|
||||
subject = cacert.get_subject()
|
||||
self.assertEqual(
|
||||
"subordinate ca signing cert",
|
||||
subject.CN)
|
||||
|
||||
certs = pkcs7.load_pem_pkcs7_certificates(intermediates)
|
||||
self.assertIsNotNone(certs[0].signature)
|
||||
|
||||
# TODO(alee) Verify that ca cert is signed by parent CA
|
||||
|
||||
def test_issue_certificate_request_with_subca_id(self):
|
||||
subca_dict = self._create_subca()
|
||||
req = certificate_utils.get_valid_csr_object()
|
||||
|
||||
req_enc = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
|
||||
req_enc = base64.b64encode(req_enc)
|
||||
order_meta = {'request_data': req_enc}
|
||||
plugin_meta = {'plugin_ca_id': subca_dict.get(cm.PLUGIN_CA_ID)}
|
||||
self.barbican_meta_dto.plugin_ca_id = subca_dict.get(cm.PLUGIN_CA_ID)
|
||||
resp = self.plugin.issue_certificate_request(self.order_id,
|
||||
order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_meta_dto)
|
||||
new_cert = crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, base64.b64decode(resp.certificate))
|
||||
signing_cert = crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, subca_dict['ca_signing_certificate'])
|
||||
|
||||
self.assertEqual(signing_cert.get_subject(), new_cert.get_issuer())
|
||||
|
||||
def test_delete_ca(self):
|
||||
subca_dict = self._create_subca()
|
||||
ca_id = subca_dict.get(cm.PLUGIN_CA_ID)
|
||||
self.assertIsNotNone(ca_id)
|
||||
|
||||
cert_path = os.path.join(self.subca_cert_key_directory,
|
||||
ca_id + ".cert")
|
||||
key_path = os.path.join(self.subca_cert_key_directory,
|
||||
ca_id + ".key")
|
||||
self.assertTrue(os.path.exists(cert_path))
|
||||
self.assertTrue(os.path.exists(key_path))
|
||||
|
||||
self.plugin.delete_ca(ca_id)
|
||||
self.assertFalse(os.path.exists(cert_path))
|
||||
self.assertFalse(os.path.exists(key_path))
|
||||
|
||||
cas = self.plugin.get_ca_info()
|
||||
self.assertNotIn(ca_id, cas.keys())
|
||||
|
||||
def test_raises_no_parent_id_passed_in(self):
|
||||
create_ca_dto = cm.CACreateDTO(
|
||||
name="sub ca1",
|
||||
description="subordinate ca",
|
||||
subject_dn="cn=subordinate ca signing cert, o=example.com",
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.create_ca,
|
||||
create_ca_dto
|
||||
)
|
||||
|
||||
def test_raises_invalid_parent_id_passed_in(self):
|
||||
create_ca_dto = cm.CACreateDTO(
|
||||
name="sub ca1",
|
||||
description="subordinate ca",
|
||||
subject_dn="cn=subordinate ca signing cert, o=example.com",
|
||||
parent_ca_id="foo"
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
cm.CertificateGeneralException,
|
||||
self.plugin.create_ca,
|
||||
create_ca_dto
|
||||
)
|
||||
|
||||
def test_get_ca_info(self):
|
||||
ca_info = self.plugin.get_ca_info()
|
||||
ca_dict = ca_info.get(self.plugin.ca.name)
|
||||
self.assertIsNotNone(ca_dict)
|
||||
self.assertEqual(self.plugin.ca.name, ca_dict.get(cm.INFO_NAME))
|
||||
self.assertIsNotNone(ca_dict.get(cm.INFO_CA_SIGNING_CERT))
|
||||
self.assertEqual(str, type(ca_dict.get(cm.INFO_EXPIRATION)))
|
||||
|
||||
def test_get_ca_info_with_subca(self):
|
||||
subca_dict = self._create_subca()
|
||||
subca_id = subca_dict.get(cm.PLUGIN_CA_ID)
|
||||
ca_info = self.plugin.get_ca_info()
|
||||
self.assertIn(subca_id, ca_info.keys())
|
||||
self.assertIn(self.plugin.get_default_ca_name(), ca_info.keys())
|
||||
self.assertEqual(str, type(subca_dict.get(cm.INFO_EXPIRATION)))
|
@ -1,111 +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.
|
||||
|
||||
import testtools
|
||||
from unittest import mock
|
||||
|
||||
try:
|
||||
import barbican.plugin.interface.certificate_manager as cm
|
||||
import barbican.plugin.symantec as sym
|
||||
imports_ok = True
|
||||
except ImportError:
|
||||
# Symantec imports probably not available
|
||||
imports_ok = False
|
||||
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
@testtools.skipIf(not imports_ok, "Symantec imports not available")
|
||||
class WhenTestingSymantecPlugin(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingSymantecPlugin, self).setUp()
|
||||
self.order_meta = {
|
||||
'cert_type': 'ssl123',
|
||||
'organization': 'Shinra Corp',
|
||||
'phone': '555-555-5555',
|
||||
'so many things...': 'more...'
|
||||
}
|
||||
|
||||
self.error_msg = 'Error Message Here'
|
||||
self.symantec = sym.SymantecCertificatePlugin()
|
||||
self.barbican_plugin_dto = cm.BarbicanMetaDTO()
|
||||
|
||||
self.symantec_patcher = mock.patch(
|
||||
'barbican.plugin.symantec._ca_create_order'
|
||||
)
|
||||
self.mock_create_order = self.symantec_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(WhenTestingSymantecPlugin, self).tearDown()
|
||||
if hasattr(self, 'mock_create_order'):
|
||||
self.mock_create_order.stop()
|
||||
|
||||
def test_successful_issue_certificate_request(self):
|
||||
self.mock_create_order.return_value = (True, None, None)
|
||||
|
||||
order_id = '1234'
|
||||
plugin_meta = {}
|
||||
|
||||
result = self.symantec.issue_certificate_request(
|
||||
order_id,
|
||||
self.order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_plugin_dto
|
||||
)
|
||||
|
||||
self.assertEqual("waiting for CA", result.status)
|
||||
|
||||
def test_unsuccessful_certificate_request_can_retry(self):
|
||||
self.mock_create_order.return_value = (False, self.error_msg, True)
|
||||
|
||||
order_id = '1234'
|
||||
plugin_meta = {}
|
||||
|
||||
result = self.symantec.issue_certificate_request(
|
||||
order_id,
|
||||
self.order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_plugin_dto
|
||||
)
|
||||
|
||||
self.assertEqual("client data issue seen", result.status)
|
||||
|
||||
def test_unsuccessful_certificate_request_no_retry(self):
|
||||
self.mock_create_order.return_value = (False, self.error_msg, False)
|
||||
|
||||
order_id = '12345'
|
||||
plugin_meta = {}
|
||||
|
||||
result = self.symantec.issue_certificate_request(
|
||||
order_id,
|
||||
self.order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_plugin_dto
|
||||
)
|
||||
|
||||
self.assertEqual("CA unavailable for request", result.status)
|
||||
|
||||
def test_should_raise_unsupported_certificate_request(self):
|
||||
order_id = '1234'
|
||||
plugin_meta = {}
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.symantec.check_certificate_status,
|
||||
order_id,
|
||||
self.order_meta,
|
||||
plugin_meta,
|
||||
self.barbican_plugin_dto
|
||||
)
|
@ -222,25 +222,6 @@ class WhenCallingScheduleOrderRetryTasks(database_utils.RepositoryTestCase):
|
||||
self.assertEqual(
|
||||
'test_should_schedule_invoking_task_for_retry', retry_rpc_method)
|
||||
|
||||
def test_should_schedule_certificate_status_task_for_retry(self):
|
||||
self.result.retry_task = (
|
||||
common.RetryTasks.INVOKE_CERT_STATUS_CHECK_TASK
|
||||
)
|
||||
|
||||
# Schedule this test method as the passed-in 'retry' function.
|
||||
retry_rpc_method = server.schedule_order_retry_tasks(
|
||||
None, # Should be ignored for non-self retries.
|
||||
self.result,
|
||||
None, # Not used.
|
||||
*self.args,
|
||||
**self.kwargs)
|
||||
database_utils.get_session().commit() # Flush to the database.
|
||||
|
||||
self.assertEqual(
|
||||
'check_certificate_status', retry_rpc_method)
|
||||
self._verify_retry_task_entity(
|
||||
'check_certificate_status')
|
||||
|
||||
def _verify_retry_task_entity(self, retry_task):
|
||||
# Retrieve the task retry entity created above and verify it.
|
||||
entities, offset, limit, total = self.repo.get_by_create_date()
|
||||
@ -302,24 +283,6 @@ class WhenCallingTasksMethod(utils.BaseTestCase):
|
||||
mock.ANY, 'result', None, 'order1234',
|
||||
'keystone1234', 'request1234')
|
||||
|
||||
@mock.patch('barbican.queue.server.schedule_order_retry_tasks')
|
||||
@mock.patch('barbican.tasks.resources.CheckCertificateStatusOrder')
|
||||
def test_should_check_certificate_order(
|
||||
self, mock_check_cert, mock_schedule):
|
||||
method = mock_check_cert.return_value.process_and_suppress_exceptions
|
||||
method.return_value = 'result'
|
||||
|
||||
self.tasks.check_certificate_status(
|
||||
None, self.order_id, self.external_project_id, self.request_id)
|
||||
|
||||
mock_process = mock_check_cert.return_value
|
||||
mock_process.process_and_suppress_exceptions.assert_called_with(
|
||||
self.order_id, self.external_project_id
|
||||
)
|
||||
mock_schedule.assert_called_with(
|
||||
mock.ANY, 'result', None, 'order1234',
|
||||
'keystone1234', 'request1234')
|
||||
|
||||
@mock.patch('barbican.tasks.resources.BeginTypeOrder')
|
||||
def test_process_order_catch_exception(self, mock_begin_order):
|
||||
"""Test that BeginTypeOrder's process() handles all exceptions."""
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -254,110 +254,6 @@ class WhenBeginningKeyTypeOrder(BaseOrderTestCase):
|
||||
self.order_repo.save.assert_called_once_with(self.order)
|
||||
|
||||
|
||||
class WhenBeginningCertificateTypeOrder(BaseOrderTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenBeginningCertificateTypeOrder, self).setUp()
|
||||
|
||||
self.order.type = models.OrderType.CERTIFICATE
|
||||
self.resource = resources.BeginTypeOrder()
|
||||
|
||||
@mock.patch(
|
||||
'barbican.tasks.certificate_resources.issue_certificate_request')
|
||||
def test_should_process_order_no_container(
|
||||
self, mock_issue_cert_request):
|
||||
mock_issue_cert_request.return_value = None
|
||||
|
||||
result = self.resource.process_and_suppress_exceptions(
|
||||
self.order.id, self.external_project_id)
|
||||
|
||||
self.order_repo.get.assert_called_once_with(
|
||||
entity_id=self.order.id,
|
||||
external_project_id=self.external_project_id)
|
||||
|
||||
self.assertEqual(self.order.status, models.States.ACTIVE)
|
||||
|
||||
mock_issue_cert_request.assert_called_once_with(
|
||||
self.order,
|
||||
self.project,
|
||||
mock.ANY
|
||||
)
|
||||
self.assertIsNone(self.order.container_id)
|
||||
self.assertIsInstance(result, common.FollowOnProcessingStatusDTO)
|
||||
|
||||
@mock.patch(
|
||||
'barbican.tasks.certificate_resources.issue_certificate_request')
|
||||
def test_should_process_order_with_container(
|
||||
self, mock_issue_cert_request):
|
||||
mock_issue_cert_request.return_value = self.container
|
||||
|
||||
result = self.resource.process(
|
||||
self.order.id, self.external_project_id)
|
||||
|
||||
self.order_repo.get.assert_called_once_with(
|
||||
entity_id=self.order.id,
|
||||
external_project_id=self.external_project_id)
|
||||
|
||||
self.assertEqual(self.order.status, models.States.ACTIVE)
|
||||
|
||||
mock_issue_cert_request.assert_called_once_with(
|
||||
self.order,
|
||||
self.project,
|
||||
mock.ANY
|
||||
)
|
||||
self.assertEqual(self.container.id, self.order.container_id)
|
||||
self.assertIsInstance(result, common.FollowOnProcessingStatusDTO)
|
||||
|
||||
|
||||
class WhenUpdatingOrder(BaseOrderTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenUpdatingOrder, self).setUp()
|
||||
|
||||
self.updated_meta = 'updated'
|
||||
|
||||
self.resource = resources.UpdateOrder()
|
||||
|
||||
@mock.patch(
|
||||
'barbican.tasks.certificate_resources.modify_certificate_request')
|
||||
def test_should_update_certificate_order(self, mock_modify_cert_request):
|
||||
self.order.type = models.OrderType.CERTIFICATE
|
||||
|
||||
self.resource.process_and_suppress_exceptions(
|
||||
self.order.id, self.external_project_id, self.updated_meta)
|
||||
|
||||
self.assertEqual(self.order.status, models.States.ACTIVE)
|
||||
|
||||
mock_modify_cert_request.assert_called_once_with(
|
||||
self.order,
|
||||
self.updated_meta
|
||||
)
|
||||
|
||||
@mock.patch(
|
||||
'barbican.tasks.certificate_resources.modify_certificate_request')
|
||||
def test_should_fail_during_processing(self, mock_mod_cert):
|
||||
mock_mod_cert.side_effect = ValueError('Abort!')
|
||||
|
||||
self.order.type = models.OrderType.CERTIFICATE
|
||||
|
||||
exception = self.assertRaises(
|
||||
ValueError,
|
||||
self.resource.process,
|
||||
self.order_id,
|
||||
self.external_project_id,
|
||||
self.meta
|
||||
)
|
||||
|
||||
self.assertEqual('Abort!', str(exception))
|
||||
|
||||
mock_mod_cert.assert_called_once_with(self.order, self.meta)
|
||||
|
||||
self.assertEqual(models.States.ERROR, self.order.status)
|
||||
self.assertEqual(500, self.order.error_status_code)
|
||||
self.assertEqual(u._('Update Order failure seen - please contact '
|
||||
'site administrator.'), self.order.error_reason)
|
||||
|
||||
|
||||
class WhenBeginningAsymmetricTypeOrder(BaseOrderTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -457,75 +353,3 @@ class WhenBeginningAsymmetricTypeOrder(BaseOrderTestCase):
|
||||
|
||||
self.project_repo.get.assert_called_once_with(self.project_id)
|
||||
self.order_repo.save.assert_called_once_with(self.order)
|
||||
|
||||
|
||||
class WhenCheckingCertificateStatus(BaseOrderTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenCheckingCertificateStatus, self).setUp()
|
||||
|
||||
self.order.type = models.OrderType.CERTIFICATE
|
||||
|
||||
self.resource = resources.CheckCertificateStatusOrder()
|
||||
|
||||
@mock.patch(
|
||||
'barbican.tasks.certificate_resources.check_certificate_request')
|
||||
def test_should_process_order_no_container(
|
||||
self, mock_check_cert_request):
|
||||
mock_check_cert_request.return_value = None
|
||||
|
||||
result = self.resource.process_and_suppress_exceptions(
|
||||
self.order.id, self.external_project_id)
|
||||
|
||||
self.order_repo.get.assert_called_once_with(
|
||||
entity_id=self.order.id,
|
||||
external_project_id=self.external_project_id)
|
||||
|
||||
self.assertEqual(self.order.status, models.States.ACTIVE)
|
||||
|
||||
mock_check_cert_request.assert_called_once_with(
|
||||
self.order,
|
||||
self.project,
|
||||
mock.ANY
|
||||
)
|
||||
self.assertIsNone(self.order.container_id)
|
||||
self.assertIsInstance(result, common.FollowOnProcessingStatusDTO)
|
||||
|
||||
@mock.patch(
|
||||
'barbican.tasks.certificate_resources.check_certificate_request')
|
||||
def test_should_process_order_with_container(
|
||||
self, mock_check_cert_request):
|
||||
mock_check_cert_request.return_value = self.container
|
||||
|
||||
self.resource.process(self.order.id, self.external_project_id)
|
||||
|
||||
self.order_repo.get.assert_called_once_with(
|
||||
entity_id=self.order.id,
|
||||
external_project_id=self.external_project_id)
|
||||
|
||||
self.assertEqual(self.order.status, models.States.ACTIVE)
|
||||
|
||||
mock_check_cert_request.assert_called_once_with(
|
||||
self.order,
|
||||
self.project,
|
||||
mock.ANY
|
||||
)
|
||||
self.assertEqual(self.container.id, self.order.container_id)
|
||||
|
||||
def test_should_fail_with_bogus_order_type(self):
|
||||
self.order.type = 'bogus-type'
|
||||
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.resource.process,
|
||||
self.order.id,
|
||||
self.external_project_id,
|
||||
)
|
||||
|
||||
# Order state should be set to ERROR.
|
||||
self.assertEqual(models.States.ERROR, self.order.status)
|
||||
self.assertEqual(
|
||||
('Check Certificate Order Status failure seen - '
|
||||
'please contact site administrator.'),
|
||||
self.order.error_reason)
|
||||
self.assertEqual(500, self.order.error_status_code)
|
||||
|
@ -113,11 +113,7 @@ function configure_dogtag_plugin {
|
||||
iniset $BARBICAN_CONF dogtag_plugin nss_db_path '/etc/barbican/alias'
|
||||
iniset $BARBICAN_CONF dogtag_plugin nss_db_path_ca '/etc/barbican/alias-ca'
|
||||
iniset $BARBICAN_CONF dogtag_plugin nss_password 'password123'
|
||||
iniset $BARBICAN_CONF dogtag_plugin simple_cmc_profile 'caOtherCert'
|
||||
iniset $BARBICAN_CONF dogtag_plugin ca_expiration_time 1
|
||||
iniset $BARBICAN_CONF dogtag_plugin plugin_working_dir '/etc/barbican/dogtag'
|
||||
iniset $BARBICAN_CONF secretstore enabled_secretstore_plugins dogtag_crypto
|
||||
iniset $BARBICAN_CONF certificate enabled_certificate_plugins dogtag
|
||||
}
|
||||
|
||||
# configure_barbican - Set config files, create data dirs, etc
|
||||
|
@ -1,7 +1,5 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/barbican/barbican.conf.sample
|
||||
namespace = barbican.certificate.plugin
|
||||
namespace = barbican.certificate.plugin.snakeoil
|
||||
namespace = barbican.common.config
|
||||
namespace = barbican.plugin.crypto
|
||||
namespace = barbican.plugin.crypto.p11
|
||||
|
@ -15,8 +15,6 @@
|
||||
|
||||
import testtools
|
||||
|
||||
from barbican.plugin.interface import certificate_manager as cert_interface
|
||||
|
||||
from functionaltests.api import base
|
||||
from functionaltests.api.v1.behaviors import consumer_behaviors
|
||||
from functionaltests.api.v1.behaviors import container_behaviors
|
||||
@ -36,11 +34,6 @@ admin_b = CONF.rbac_users.admin_b
|
||||
service_admin = CONF.identity.service_admin
|
||||
|
||||
|
||||
def is_ca_backend_snakeoil():
|
||||
return 'snakeoil_ca' in\
|
||||
cert_interface.CONF.certificate.enabled_certificate_plugins
|
||||
|
||||
|
||||
@testtools.testcase.attr('no_parallel')
|
||||
class QuotaEnforcementTestCase(base.TestCase):
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
deprecations:
|
||||
- |
|
||||
The Symantec certificate plugin has been deprecated, because lack of
|
||||
maintenance activity of the plugin. Also the symantecssl python library
|
||||
is not generally available and the plugin has never been tested in
|
||||
upstream.
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The certificate plugin and the certificate event plugin were both removed,
|
||||
because these were used for deprecated certificate resources.
|
@ -62,13 +62,6 @@ barbican.secretstore.plugin =
|
||||
barbican.crypto.plugin =
|
||||
p11_crypto = barbican.plugin.crypto.p11_crypto:P11CryptoPlugin
|
||||
simple_crypto = barbican.plugin.crypto.simple_crypto:SimpleCryptoPlugin
|
||||
barbican.certificate.plugin =
|
||||
simple_certificate = barbican.plugin.simple_certificate_manager:SimpleCertificatePlugin
|
||||
snakeoil_ca = barbican.plugin.snakeoil_ca:SnakeoilCACertificatePlugin
|
||||
symantec = barbican.plugin.symantec:SymantecCertificatePlugin
|
||||
dogtag = barbican.plugin.dogtag:DogtagCAPlugin
|
||||
barbican.certificate.event.plugin =
|
||||
simple_certificate_event = barbican.plugin.simple_certificate_manager:SimpleCertificateEventPlugin
|
||||
barbican.test.crypto.plugin =
|
||||
test_crypto = barbican.tests.crypto.test_plugin:TestCryptoPlugin
|
||||
oslo.config.opts =
|
||||
@ -80,7 +73,5 @@ oslo.config.opts =
|
||||
barbican.plugin.crypto.p11 = barbican.plugin.crypto.p11_crypto:list_opts
|
||||
barbican.plugin.secret_store.kmip = barbican.plugin.kmip_secret_store:list_opts
|
||||
barbican.plugin.secret_store.vault = barbican.plugin.vault_secret_store:list_opts
|
||||
barbican.certificate.plugin = barbican.plugin.interface.certificate_manager:list_opts
|
||||
barbican.certificate.plugin.snakeoil = barbican.plugin.snakeoil_ca:list_opts
|
||||
oslo.config.opts.defaults =
|
||||
barbican.common.config = barbican.common.config:set_lib_defaults
|
||||
|
Loading…
Reference in New Issue
Block a user