barbican/barbican/plugin/interface/certificate_manager.py
jfwood f29d08610d Update log messages to oslo.i18n
The OpenStack internationalization (i18n) system was moved from
oslo-incubator to the oslo.i18n library. This new library also has a
refined way to tag logging strings for translation as detailed in on
this page:
http://docs.openstack.org/developer/oslo.i18n/guidelines.html
This CR updates Barbican's logging text messages accordingly. A
separate CR will update the barbican/openstack package structure from
oslo-incubator to remove all references to the old i18n system.

Change-Id: Ibc2700324495d01c571343937a9d1771ba9e5b85
2014-12-07 19:04:46 -06:00

415 lines
16 KiB
Python

# 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
from oslo.config import cfg
import six
from stevedore import named
from barbican.common import exception
import barbican.common.utils as utils
from barbican import i18n as u
CONF = cfg.CONF
# 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)
# 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_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 certificate plugin supporting a request is available."""
def __init__(self, plugin_name=None):
if plugin_name:
message = u._(
'Certificate plugin "{name}"'
' not found or configured.').format(name=plugin_name)
else:
message = u._("Certificate plugin not found or configured.")
super(CertificatePluginNotFound, 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 or '
'configured.').format(name=plugin_name)
else:
message = u._("Certificate event plugin not found or configured.")
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.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: {reason}').format(reason=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: {reason}').format(reason=reason)
)
self.reason = reason
@six.add_metaclass(abc.ABCMeta)
class CertificateEventPluginBase(object):
"""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: HATEOS reference URI to the submitted Barbican Order
:param container_ref: HATEOS 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: HATEOS 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
@six.add_metaclass(abc.ABCMeta)
class CertificatePluginBase(object):
"""Base class for certificate plugins.
This class is the base plugin contract for certificates.
"""
@abc.abstractmethod
def issue_certificate_request(self, order_id, order_meta, plugin_meta):
"""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
: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):
"""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
: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):
"""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
: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):
"""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
: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
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 CertificatePluginManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
super(CertificatePluginManager, self).__init__(
conf.certificate.namespace,
conf.certificate.enabled_certificate_plugins,
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=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
"""
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(plugin_name)
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 EVENT_PLUGIN_MANAGER at the module
level.
"""
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
super(_CertificateEventPluginManager, self).__init__(
conf.certificate_event.namespace,
conf.certificate_event.enabled_certificate_event_plugins,
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
def get_plugin_by_name(self, plugin_name):
"""Gets a supporting certificate event plugin.
:returns: CertficiateEventPluginBase plugin implementation
"""
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
return ext.obj
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."""
if len(self.extensions) < 1:
raise CertificateEventPluginNotFound()
for ext in self.extensions:
getattr(ext.obj, method)(*args, **kwargs)
EVENT_PLUGIN_MANAGER = _CertificateEventPluginManager()