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:
Ade Lee
2014-08-26 12:42:25 -04:00
parent e9886afe16
commit b075737b20
8 changed files with 335 additions and 34 deletions
+9
View File
@@ -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
+6
View File
@@ -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'
+7
View File
@@ -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()
+5 -3
View File
@@ -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
+234 -20
View File
@@ -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
+13 -2
View File
@@ -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
)