Additional work on certificate processing
Code to handle returns from certificate plugins. Implements: blueprint add-ssl-ca-support Change-Id: I4728bc37cddcdd9a0f0cc74185829cfbb482fec2
This commit is contained in:
@@ -25,6 +25,7 @@ from barbican.common import utils
|
||||
from barbican.openstack.common import gettextutils as u
|
||||
from barbican.openstack.common import jsonutils as json
|
||||
from barbican.openstack.common import policy
|
||||
from barbican.plugin.interface import certificate_manager as cert_manager
|
||||
from barbican.plugin.interface import secret_store as s
|
||||
|
||||
|
||||
@@ -139,6 +140,14 @@ def generate_safe_exception_message(operation_name, excep):
|
||||
reason = u._("Requested algorithm is not supported")
|
||||
status = 400
|
||||
|
||||
except cert_manager.CertificateStatusClientDataIssue as cdi:
|
||||
reason = cdi.reason
|
||||
status = 400
|
||||
|
||||
except cert_manager.CertificateStatusInvalidOperation as cio:
|
||||
reason = cio.reason
|
||||
status = 400
|
||||
|
||||
except exception.NoDataToProcess:
|
||||
reason = u._("No information provided to process")
|
||||
status = 400
|
||||
|
||||
@@ -58,6 +58,12 @@ class OrderType(object):
|
||||
return order_type in cls.__dict__
|
||||
|
||||
|
||||
class OrderStatus(object):
|
||||
def __init__(self, id, message):
|
||||
self.id = id
|
||||
self.message = message
|
||||
|
||||
|
||||
@compiler.compiles(sa.BigInteger, 'sqlite')
|
||||
def compile_big_int_sqlite(type_, compiler, **kw):
|
||||
return 'INTEGER'
|
||||
|
||||
@@ -526,4 +526,11 @@ class DogtagCAPlugin(cm.CertificatePluginBase):
|
||||
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
|
||||
|
||||
@@ -27,6 +27,7 @@ import six
|
||||
from stevedore import named
|
||||
|
||||
from barbican.common import exception
|
||||
import barbican.common.utils as utils
|
||||
from barbican.openstack.common import gettextutils as u
|
||||
|
||||
|
||||
@@ -50,8 +51,16 @@ cert_opts = [
|
||||
CONF.register_group(cert_opt_group)
|
||||
CONF.register_opts(cert_opts, group=cert_opt_group)
|
||||
|
||||
ERROR_RETRY_MSEC = 300000
|
||||
RETRY_MSEC = 3600000
|
||||
|
||||
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"
|
||||
|
||||
|
||||
class CertificatePluginNotFound(exception.BarbicanException):
|
||||
"""Raised when no plugins are installed."""
|
||||
@@ -76,6 +85,26 @@ class CertificateGeneralException(exception.BarbicanException):
|
||||
self.reason = reason
|
||||
|
||||
|
||||
class CertificateStatusClientDataIssue(exception.BarbicanException):
|
||||
"""Raised when the CA has encountered an issue with request data."""
|
||||
def __init__(self, reason=u._('Unknown')):
|
||||
super(CertificateStatusClientDataIssue, self).__init__(
|
||||
u._('Problem with data in certificate request - '
|
||||
'Reason: {0}').format(reason)
|
||||
)
|
||||
self.reason = reason
|
||||
|
||||
|
||||
class CertificateStatusInvalidOperation(exception.BarbicanException):
|
||||
"""Raised when the CA has encountered an issue with request data."""
|
||||
def __init__(self, reason=u._('Unknown')):
|
||||
super(CertificateStatusInvalidOperation, self).__init__(
|
||||
u._('Invalid operation requested - '
|
||||
'Reason: {0}').format(reason)
|
||||
)
|
||||
self.reason = reason
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class CertificatePluginBase(object):
|
||||
"""Base class for certificate plugins.
|
||||
@@ -189,7 +218,7 @@ class ResultDTO(object):
|
||||
self.status_message = status_message
|
||||
self.certificate = certificate
|
||||
self.intermediates = intermediates
|
||||
self.retry_msec = retry_msec
|
||||
self.retry_msec = int(retry_msec)
|
||||
self.retry_method = retry_method
|
||||
|
||||
|
||||
@@ -209,9 +238,20 @@ class CertificatePluginManager(named.NamedExtensionManager):
|
||||
|
||||
:param certificate_spec: Contains details on the certificate to
|
||||
generate the certificate order
|
||||
:returns: CertficiatePluginBase plugin implementation
|
||||
:returns: CertificatePluginBase plugin implementation
|
||||
"""
|
||||
for ext in self.extensions:
|
||||
if ext.obj.supports(certificate_spec):
|
||||
return ext.obj
|
||||
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 ext in self.extensions:
|
||||
if utils.generate_fullname_for(ext.obj) == plugin_name:
|
||||
return ext.obj
|
||||
raise CertificatePluginNotFound()
|
||||
|
||||
@@ -73,9 +73,11 @@ def store_secret(unencrypted_raw, content_type_raw, content_encoding,
|
||||
raise ValueError('Secret already has encrypted data stored for it.')
|
||||
|
||||
# Create a KeySpec to find a plugin that will support storing the secret
|
||||
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
|
||||
bit_length=spec.get('bit_length'),
|
||||
mode=spec.get('mode'))
|
||||
key_spec = None
|
||||
if spec:
|
||||
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
|
||||
bit_length=spec.get('bit_length'),
|
||||
mode=spec.get('mode'))
|
||||
|
||||
# If there is no secret data to store, then just create Secret entity and
|
||||
# leave. A subsequent call to this method should provide both the Secret
|
||||
|
||||
@@ -13,12 +13,59 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import barbican.common.utils as utils
|
||||
from barbican.model import models
|
||||
from barbican.plugin.interface import certificate_manager as cert
|
||||
from barbican.plugin import resources as plugin
|
||||
|
||||
# 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 issue_certificate_request(order_model, repos):
|
||||
"""Create the initial order with CA."""
|
||||
def issue_certificate_request(order_model, tenant_model, repos):
|
||||
"""Create the initial order with CA.
|
||||
|
||||
:param: order_model - order associated with this cert request
|
||||
:param: tenant_model - tenant associated with this request
|
||||
:param; repos - repos (to be removed)
|
||||
:returns: container_model - container with the relevant cert if
|
||||
the request has been completed. None otherwise
|
||||
"""
|
||||
container_model = None
|
||||
plugin_meta = _get_plugin_meta(order_model)
|
||||
|
||||
# Locate a suitable plugin to issue a certificate.
|
||||
@@ -27,28 +74,146 @@ def issue_certificate_request(order_model, repos):
|
||||
result = cert_plugin.issue_certificate_request(order_model.id,
|
||||
order_model.meta,
|
||||
plugin_meta)
|
||||
# Handle result
|
||||
if cert.CertificateStatus.WAITING_FOR_CA == result.status:
|
||||
#TODO(chellygel): Logic for retry
|
||||
pass
|
||||
elif cert.CertificateStatus.CERTIFICATE_GENERATED == result.status:
|
||||
#TODO(chellygel): Logic for store cert in container
|
||||
pass
|
||||
elif cert.CertificateStatus.CLIENT_DATA_ISSUE_SEEN == result.status:
|
||||
#TODO(chellygel): Logic for notify client of issue
|
||||
pass
|
||||
elif cert.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST == result.status:
|
||||
#TODO(chellygel): Logic for notify client of CA unavailable and retry
|
||||
pass
|
||||
elif cert.CertificateStatus.INVALID_OPERATION == result.status:
|
||||
#TODO(chellygel): Logic for notify client of CA conflict
|
||||
pass
|
||||
else:
|
||||
raise cert.CertificateStatusNotSupported(result.status)
|
||||
|
||||
# Save plugin order plugin state
|
||||
_save_plugin_metadata(order_model, plugin_meta, repos)
|
||||
|
||||
# Handle result
|
||||
if cert.CertificateStatus.WAITING_FOR_CA == result.status:
|
||||
#TODO(alee-3): Add code to set sub status of "waiting for CA"
|
||||
_update_order_status(ORDER_STATUS_REQUEST_PENDING)
|
||||
_schedule_check_cert_request(cert_plugin, order_model, plugin_meta,
|
||||
repos, result, tenant_model,
|
||||
cert.RETRY_MSEC)
|
||||
elif cert.CertificateStatus.CERTIFICATE_GENERATED == result.status:
|
||||
_update_order_status(ORDER_STATUS_CERT_GENERATED)
|
||||
container_model = _save_secrets(result, tenant_model, repos)
|
||||
elif cert.CertificateStatus.CLIENT_DATA_ISSUE_SEEN == result.status:
|
||||
_update_order_status(ORDER_STATUS_DATA_INVALID)
|
||||
raise cert.CertificateStatusClientDataIssue(result.status_message)
|
||||
elif cert.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST == result.status:
|
||||
#TODO(alee-3): set retry counter and error out if retries are exceeded
|
||||
_update_order_status(ORDER_STATUS_CA_UNAVAIL_FOR_ISSUE)
|
||||
|
||||
_schedule_issue_cert_request(cert_plugin, order_model, plugin_meta,
|
||||
repos, result, tenant_model,
|
||||
cert.ERROR_RETRY_MSEC)
|
||||
elif cert.CertificateStatus.INVALID_OPERATION == result.status:
|
||||
_update_order_status(ORDER_STATUS_INVALID_OPERATION)
|
||||
|
||||
raise cert.CertificateStatusInvalidOperation(result.status_message)
|
||||
else:
|
||||
_update_order_status(ORDER_STATUS_INTERNAL_ERROR)
|
||||
raise cert.CertificateStatusNotSupported(result.status)
|
||||
|
||||
return container_model
|
||||
|
||||
|
||||
def check_certificate_request(order_model, tenant_model, plugin_name, repos):
|
||||
"""Check the status of a certificate request with the CA.
|
||||
|
||||
:param: order_model - order associated with this cert request
|
||||
:param: tenant_model - tenant associated with this request
|
||||
:param: plugin_name - plugin the issued the certificate request
|
||||
:param; repos - repos (to be removed)
|
||||
:returns: container_model - container with the relevant cert if the
|
||||
request has been completed. None otherwise.
|
||||
"""
|
||||
container_model = None
|
||||
plugin_meta = _get_plugin_meta(order_model)
|
||||
cert_plugin = cert.CertificatePluginManager().get_plugin_by_name(
|
||||
plugin_name)
|
||||
|
||||
result = cert_plugin.check_certificate_request(order_model.id,
|
||||
order_model.meta,
|
||||
plugin_meta)
|
||||
|
||||
# Save plugin order plugin state
|
||||
_save_plugin_metadata(order_model, plugin_meta, repos)
|
||||
|
||||
# Handle result
|
||||
if cert.CertificateStatus.WAITING_FOR_CA == result.status:
|
||||
_update_order_status(ORDER_STATUS_REQUEST_PENDING)
|
||||
_schedule_check_cert_request(cert_plugin, order_model, plugin_meta,
|
||||
repos, result, tenant_model,
|
||||
cert.RETRY_MSEC)
|
||||
elif cert.CertificateStatus.CERTIFICATE_GENERATED == result.status:
|
||||
_update_order_status(ORDER_STATUS_CERT_GENERATED)
|
||||
container_model = _save_secrets(result, tenant_model, repos)
|
||||
elif cert.CertificateStatus.CLIENT_DATA_ISSUE_SEEN == result.status:
|
||||
_update_order_status(cert.ORDER_STATUS_DATA_INVALID)
|
||||
raise cert.CertificateStatusClientDataIssue(result.status_message)
|
||||
elif cert.CertificateStatus.CA_UNAVAILABLE_FOR_REQUEST == result.status:
|
||||
#TODO(alee-3): decide what to do about retries here
|
||||
_update_order_status(ORDER_STATUS_CA_UNAVAIL_FOR_CHECK)
|
||||
_schedule_check_cert_request(cert_plugin, order_model, plugin_meta,
|
||||
repos, result, tenant_model,
|
||||
cert.ERROR_RETRY_MSEC)
|
||||
|
||||
elif cert.CertificateStatus.INVALID_OPERATION == result.status:
|
||||
_update_order_status(ORDER_STATUS_INVALID_OPERATION)
|
||||
raise cert.CertificateStatusInvalidOperation(result.status_message)
|
||||
else:
|
||||
_update_order_status(ORDER_STATUS_INTERNAL_ERROR)
|
||||
raise cert.CertificateStatusNotSupported(result.status)
|
||||
|
||||
return container_model
|
||||
|
||||
|
||||
def _schedule_cert_retry_task(cert_result_dto, cert_plugin, order_model,
|
||||
plugin_meta,
|
||||
retry_method=None,
|
||||
retry_object=None,
|
||||
retry_time=None,
|
||||
retry_args=None):
|
||||
if cert_result_dto.retry_msec > 0:
|
||||
retry_time = cert_result_dto.retry_msec
|
||||
|
||||
if cert_result_dto.retry_method:
|
||||
retry_method = cert_result_dto.retry_method
|
||||
retry_object = utils.generate_fullname_for(cert_plugin)
|
||||
retry_args = [order_model.id, order_model.meta, plugin_meta]
|
||||
|
||||
_schedule_retry_task(retry_object, retry_method, retry_time, retry_args)
|
||||
|
||||
|
||||
def _schedule_issue_cert_request(cert_plugin, order_model, plugin_meta, repos,
|
||||
cert_result_dto, tenant_model, retry_time):
|
||||
retry_args = [order_model,
|
||||
tenant_model,
|
||||
repos]
|
||||
_schedule_cert_retry_task(
|
||||
cert_result_dto, cert_plugin, order_model, plugin_meta,
|
||||
retry_method="issue_certificate_request",
|
||||
retry_object="barbican.tasks.certificate_resources",
|
||||
retry_time=retry_time,
|
||||
retry_args=retry_args)
|
||||
|
||||
|
||||
def _schedule_check_cert_request(cert_plugin, order_model, plugin_meta, repos,
|
||||
cert_result_dto, tenant_model, retry_time):
|
||||
retry_args = [order_model,
|
||||
tenant_model,
|
||||
utils.generate_fullname_for(cert_plugin),
|
||||
repos]
|
||||
_schedule_cert_retry_task(
|
||||
cert_result_dto, cert_plugin, order_model, plugin_meta,
|
||||
retry_method="check_certificate_request",
|
||||
retry_object="barbican.tasks.certificate_resources",
|
||||
retry_time=retry_time,
|
||||
retry_args=retry_args)
|
||||
|
||||
|
||||
def _update_order_status(order_status):
|
||||
# TODO(alee-3): add code to set order substatus, substatus message
|
||||
# and save the order. most likely this call methods defined in Order.
|
||||
pass
|
||||
|
||||
|
||||
def _schedule_retry_task(retry_object, retry_method, retry_time, args):
|
||||
# TODO(alee-3): Implement this method - here or elsewhere .
|
||||
pass
|
||||
|
||||
|
||||
def _get_plugin_meta(order_model):
|
||||
if order_model:
|
||||
@@ -61,7 +226,56 @@ def _get_plugin_meta(order_model):
|
||||
|
||||
def _save_plugin_metadata(order_model, plugin_meta, repos):
|
||||
"""Add plugin metadata to an order."""
|
||||
|
||||
if not isinstance(plugin_meta, dict):
|
||||
plugin_meta = dict()
|
||||
|
||||
repos.order_plugin_meta_repo.save(plugin_meta, order_model)
|
||||
|
||||
|
||||
def _save_secrets(result, tenant_model, repos):
|
||||
cert_secret_model, transport_key_model = plugin.store_secret(
|
||||
unencrypted_raw=result.certificate,
|
||||
content_type_raw='text/plain',
|
||||
content_encoding='base64',
|
||||
spec={},
|
||||
secret_model=None,
|
||||
tenant_model=tenant_model,
|
||||
repos=repos)
|
||||
|
||||
# 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='text/plain',
|
||||
content_encoding='base64',
|
||||
spec={},
|
||||
secret_model=None,
|
||||
tenant_model=tenant_model,
|
||||
repos=repos
|
||||
)
|
||||
else:
|
||||
intermediates_secret_model = None
|
||||
|
||||
container_model = models.Container()
|
||||
container_model.type = "certificate"
|
||||
container_model.status = models.States.ACTIVE
|
||||
container_model.tenant_id = tenant_model.id
|
||||
repos.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
|
||||
repos.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
|
||||
repos.container_secret_repo.create_from(new_consec_assoc)
|
||||
|
||||
return container_model
|
||||
|
||||
@@ -184,6 +184,7 @@ class BeginOrder(BaseTask):
|
||||
generation.
|
||||
|
||||
:param order: Order to process on behalf of.
|
||||
:return SUCCESS_STATUS, PENDING_STATUS
|
||||
"""
|
||||
order_info = order.to_dict_fields()
|
||||
secret_info = order_info['secret']
|
||||
@@ -241,7 +242,14 @@ class BeginTypeOrder(BaseTask):
|
||||
self.repos.order_repo.save(order)
|
||||
|
||||
def handle_success(self, order, *args, **kwargs):
|
||||
order.status = models.States.ACTIVE
|
||||
if models.OrderType.CERTIFICATE != order.type:
|
||||
order.status = models.States.ACTIVE
|
||||
else:
|
||||
# TODO(alee-3): enable the code below when sub status is added
|
||||
# if cert.ORDER_STATUS_CERT_GENERATED.id == order.sub_status:
|
||||
# order.status = models.States.ACTIVE
|
||||
order.status = models.States.ACTIVE
|
||||
|
||||
self.repos.order_repo.save(order)
|
||||
|
||||
def handle_order(self, order):
|
||||
@@ -282,5 +290,8 @@ class BeginTypeOrder(BaseTask):
|
||||
LOG.debug("...done creating asymmetric order's secret.")
|
||||
elif order_type == models.OrderType.CERTIFICATE:
|
||||
# Request a certificate
|
||||
cert.issue_certificate_request(order, self.repos)
|
||||
new_container = cert.issue_certificate_request(
|
||||
order, tenant, self.repos)
|
||||
if new_container:
|
||||
order.container_id = new_container.id
|
||||
LOG.debug("...done requesting a certificate.")
|
||||
|
||||
@@ -36,6 +36,7 @@ class WhenIssuingCertificateRequests(testtools.TestCase):
|
||||
self.order_model.id = self.order_id
|
||||
self.order_model.meta = self.order_meta
|
||||
self.repos = mock.MagicMock()
|
||||
self.tenant_model = mock.MagicMock()
|
||||
|
||||
# Setting up mock data for the plugin manager.
|
||||
cert_plugin_config = {
|
||||
@@ -73,29 +74,39 @@ class WhenIssuingCertificateRequests(testtools.TestCase):
|
||||
def test_should_return_waiting_for_ca(self):
|
||||
self.result.status = cert_man.CertificateStatus.WAITING_FOR_CA
|
||||
|
||||
cert_res.issue_certificate_request(self.order_model, self.repos)
|
||||
cert_res.issue_certificate_request(self.order_model,
|
||||
self.tenant_model,
|
||||
self.repos)
|
||||
|
||||
self._verify_issue_certificate_plugins_called()
|
||||
|
||||
def test_should_return_certificate_generated(self):
|
||||
self.result.status = cert_man.CertificateStatus.CERTIFICATE_GENERATED
|
||||
|
||||
cert_res.issue_certificate_request(self.order_model, self.repos)
|
||||
cert_res.issue_certificate_request(self.order_model,
|
||||
self.tenant_model,
|
||||
self.repos)
|
||||
|
||||
self._verify_issue_certificate_plugins_called()
|
||||
|
||||
def test_should_return_client_data_issue_seen(self):
|
||||
def test_should_raise_client_data_issue_seen(self):
|
||||
self.result.status = cert_man.CertificateStatus.CLIENT_DATA_ISSUE_SEEN
|
||||
|
||||
cert_res.issue_certificate_request(self.order_model, self.repos)
|
||||
|
||||
self._verify_issue_certificate_plugins_called()
|
||||
self.assertRaises(
|
||||
cert_man.CertificateStatusClientDataIssue,
|
||||
cert_res.issue_certificate_request,
|
||||
self.order_model,
|
||||
self.tenant_model,
|
||||
self.repos
|
||||
)
|
||||
|
||||
def test_should_return_ca_unavailable_for_request(self):
|
||||
self.result.status = cert_man.CertificateStatus.\
|
||||
CA_UNAVAILABLE_FOR_REQUEST
|
||||
|
||||
cert_res.issue_certificate_request(self.order_model, self.repos)
|
||||
cert_res.issue_certificate_request(self.order_model,
|
||||
self.tenant_model,
|
||||
self.repos)
|
||||
|
||||
self._verify_issue_certificate_plugins_called()
|
||||
|
||||
@@ -106,6 +117,7 @@ class WhenIssuingCertificateRequests(testtools.TestCase):
|
||||
cert_man.CertificateStatusNotSupported,
|
||||
cert_res.issue_certificate_request,
|
||||
self.order_model,
|
||||
self.tenant_model,
|
||||
self.repos
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user