"Fossies" - the Fresh Open Source Software Archive

Member "barbican-12.0.0/barbican/plugin/interface/certificate_manager.py" (14 Apr 2021, 29511 Bytes) of package /linux/misc/openstack/barbican-12.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 # Copyright (c) 2013-2014 Rackspace, Inc.
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License");
    4 # you may not use this file except in compliance with the License.
    5 # You may obtain a copy of the License at
    6 #
    7 #    http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS,
   11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   12 # implied.
   13 # See the License for the specific language governing permissions and
   14 # limitations under the License.
   15 
   16 """
   17 SSL Certificate resources for Barbican.
   18 
   19 The resources here should be generic across all certificate-related
   20 implementations. Hence do not place vendor-specific content in this module.
   21 """
   22 
   23 import abc
   24 import datetime
   25 
   26 from oslo_config import cfg
   27 from oslo_utils import encodeutils
   28 from stevedore import named
   29 
   30 from barbican.common import config
   31 from barbican.common import exception
   32 import barbican.common.utils as utils
   33 from barbican import i18n as u
   34 from barbican.model import models
   35 from barbican.model import repositories as repos
   36 from barbican.plugin.util import utils as plugin_utils
   37 
   38 LOG = utils.getLogger(__name__)
   39 CONF = config.new_config()
   40 
   41 # Configuration for certificate processing plugins:
   42 DEFAULT_PLUGIN_NAMESPACE = 'barbican.certificate.plugin'
   43 DEFAULT_PLUGINS = ['simple_certificate']
   44 
   45 cert_opt_group = cfg.OptGroup(name='certificate',
   46                               title='Certificate Plugin Options')
   47 cert_opts = [
   48     cfg.StrOpt('namespace',
   49                default=DEFAULT_PLUGIN_NAMESPACE,
   50                help=u._('Extension namespace to search for plugins.')
   51                ),
   52     cfg.MultiStrOpt('enabled_certificate_plugins',
   53                     default=DEFAULT_PLUGINS,
   54                     help=u._('List of certificate plugins to load.')
   55                     )
   56 ]
   57 CONF.register_group(cert_opt_group)
   58 CONF.register_opts(cert_opts, group=cert_opt_group)
   59 config.parse_args(CONF)
   60 
   61 
   62 def list_opts():
   63     yield cert_opt_group, cert_opts
   64     yield cert_event_opt_group, cert_event_opts
   65 
   66 
   67 # Configuration for certificate eventing plugins:
   68 DEFAULT_EVENT_PLUGIN_NAMESPACE = 'barbican.certificate.event.plugin'
   69 DEFAULT_EVENT_PLUGINS = ['simple_certificate_event']
   70 
   71 cert_event_opt_group = cfg.OptGroup(name='certificate_event',
   72                                     title='Certificate Event Plugin Options')
   73 cert_event_opts = [
   74     cfg.StrOpt('namespace',
   75                default=DEFAULT_EVENT_PLUGIN_NAMESPACE,
   76                help=u._('Extension namespace to search for eventing plugins.')
   77                ),
   78     cfg.MultiStrOpt('enabled_certificate_event_plugins',
   79                     default=DEFAULT_EVENT_PLUGINS,
   80                     help=u._('List of certificate plugins to load.')
   81                     )
   82 ]
   83 CONF.register_group(cert_event_opt_group)
   84 CONF.register_opts(cert_event_opts, group=cert_event_opt_group)
   85 
   86 
   87 ERROR_RETRY_MSEC = 300000
   88 RETRY_MSEC = 3600000
   89 CA_INFO_DEFAULT_EXPIRATION_DAYS = 1
   90 
   91 CA_PLUGIN_TYPE_DOGTAG = "dogtag"
   92 CA_PLUGIN_TYPE_SYMANTEC = "symantec"
   93 
   94 # fields to distinguish CA types and subject key identifiers
   95 CA_TYPE = "ca_type"
   96 CA_SUBJECT_KEY_IDENTIFIER = "ca_subject_key_identifier"
   97 
   98 # field to get the certificate request type
   99 REQUEST_TYPE = "request_type"
  100 
  101 # fields for the ca_id, plugin_ca_id
  102 CA_ID = "ca_id"
  103 PLUGIN_CA_ID = "plugin_ca_id"
  104 
  105 # fields for ca_info dict keys
  106 INFO_NAME = "name"
  107 INFO_DESCRIPTION = "description"
  108 INFO_CA_SIGNING_CERT = "ca_signing_certificate"
  109 INFO_INTERMEDIATES = "intermediates"
  110 INFO_EXPIRATION = "expiration"
  111 
  112 
  113 # Singleton to avoid loading the CertificateEventManager plugins more than once
  114 _EVENT_PLUGIN_MANAGER = None
  115 
  116 
  117 class CertificateRequestType(object):
  118     """Constants to define the certificate request type."""
  119     CUSTOM_REQUEST = "custom"
  120     FULL_CMC_REQUEST = "full-cmc"
  121     SIMPLE_CMC_REQUEST = "simple-cmc"
  122     STORED_KEY_REQUEST = "stored-key"
  123 
  124 
  125 class CertificatePluginNotFound(exception.BarbicanException):
  126     """Raised when no certificate plugin supporting a request is available."""
  127     def __init__(self, plugin_name=None):
  128         if plugin_name:
  129             message = u._(
  130                 'Certificate plugin "{name}"'
  131                 ' not found.').format(name=plugin_name)
  132         else:
  133             message = u._("Certificate plugin not found or configured.")
  134         super(CertificatePluginNotFound, self).__init__(message)
  135 
  136 
  137 class CertificatePluginNotFoundForCAID(exception.BarbicanException):
  138     """Raised when no certificate plugin is available for a CA_ID."""
  139     def __init__(self, ca_id):
  140         message = u._(
  141             'Certificate plugin not found for "{ca_id}".').format(ca_id=ca_id)
  142         super(CertificatePluginNotFoundForCAID, self).__init__(message)
  143 
  144 
  145 class CertificateEventPluginNotFound(exception.BarbicanException):
  146     """Raised with no certificate event plugin supporting request."""
  147     def __init__(self, plugin_name=None):
  148         if plugin_name:
  149             message = u._(
  150                 'Certificate event plugin "{name}" '
  151                 'not found.').format(name=plugin_name)
  152         else:
  153             message = u._("Certificate event plugin not found.")
  154         super(CertificateEventPluginNotFound, self).__init__(message)
  155 
  156 
  157 class CertificateStatusNotSupported(exception.BarbicanException):
  158     """Raised when cert status returned is unknown."""
  159     def __init__(self, status):
  160         super(CertificateStatusNotSupported, self).__init__(
  161             u._("Certificate status of {status} not "
  162                 "supported").format(status=status)
  163         )
  164         self.status = status
  165 
  166 
  167 class CertificateGeneralException(exception.BarbicanException):
  168     """Raised when a system fault has occurred."""
  169     def __init__(self, reason=u._('Unknown')):
  170         super(CertificateGeneralException, self).__init__(
  171             u._('Problem seen during certificate processing - '
  172                 'Reason: {reason}').format(reason=reason)
  173         )
  174         self.reason = reason
  175 
  176 
  177 class CertificateStatusClientDataIssue(exception.BarbicanHTTPException):
  178     """Raised when the CA has encountered an issue with request data."""
  179 
  180     client_message = ""
  181     status_code = 400
  182 
  183     def __init__(self, reason=u._('Unknown')):
  184         super(CertificateStatusClientDataIssue, self).__init__(
  185             u._('Problem with data in certificate request - '
  186                 'Reason: {reason}').format(reason=reason)
  187         )
  188         self.client_message = self.message
  189 
  190 
  191 class CertificateStatusInvalidOperation(exception.BarbicanHTTPException):
  192     """Raised when the CA has encountered an issue with request data."""
  193 
  194     client_message = ""
  195     status_code = 400
  196 
  197     def __init__(self, reason=u._('Unknown')):
  198         super(CertificateStatusInvalidOperation, self).__init__(
  199             u._('Invalid operation requested - '
  200                 'Reason: {reason}').format(reason=reason)
  201         )
  202         self.client_message = self.message
  203 
  204 
  205 class CertificateEventPluginBase(object, metaclass=abc.ABCMeta):
  206     """Base class for certificate eventing plugins.
  207 
  208     This class is the base plugin contract for issuing certificate related
  209     events from Barbican.
  210     """
  211 
  212     @abc.abstractmethod
  213     def notify_certificate_is_ready(
  214             self, project_id, order_ref, container_ref):
  215         """Notify that a certificate has been generated and is ready to use.
  216 
  217         :param project_id: Project ID associated with this certificate
  218         :param order_ref: HATEOAS reference URI to the submitted Barbican Order
  219         :param container_ref: HATEOAS reference URI to the Container storing
  220                the certificate
  221         :returns: None
  222         """
  223         raise NotImplementedError  # pragma: no cover
  224 
  225     @abc.abstractmethod
  226     def notify_ca_is_unavailable(
  227             self, project_id, order_ref, error_msg, retry_in_msec):
  228         """Notify that the certificate authority (CA) isn't available.
  229 
  230         :param project_id: Project ID associated with this order
  231         :param order_ref: HATEOAS reference URI to the submitted Barbican Order
  232         :param error_msg: Error message if it is available
  233         :param retry_in_msec: Delay before attempting to talk to the CA again.
  234                If this is 0, then no attempt will be made.
  235         :returns: None
  236         """
  237         raise NotImplementedError  # pragma: no cover
  238 
  239 
  240 class CertificatePluginBase(object, metaclass=abc.ABCMeta):
  241     """Base class for certificate plugins.
  242 
  243     This class is the base plugin contract for certificates.
  244     """
  245 
  246     @abc.abstractmethod
  247     def get_default_ca_name(self):
  248         """Get the default CA name
  249 
  250         Provides a default CA name to be returned in the default
  251         get_ca_info() method.  If get_ca_info() is overridden (to
  252         support multiple CAs for instance), then this method may not
  253         be called.  In that case, just implement this method to return
  254         a dummy variable.
  255 
  256         If this value is used, it should be unique amongst all the CA
  257         plugins.
  258 
  259         :return: The default CA name
  260         :rtype: str
  261         """
  262         raise NotImplementedError   # pragma: no cover
  263 
  264     @abc.abstractmethod
  265     def get_default_signing_cert(self):
  266         """Get the default CA signing cert
  267 
  268         Provides a default CA signing cert to be returned in the default
  269         get_ca_info() method.  If get_ca_info() is overridden (to
  270         support multiple CAs for instance), then this method may not
  271         be called.  In that case, just implement this method to return
  272         a dummy variable.
  273         :return: The default CA signing cert
  274         :rtype: str
  275         """
  276         raise NotImplementedError   # pragma: no cover
  277 
  278     @abc.abstractmethod
  279     def get_default_intermediates(self):
  280         """Get the default CA certificate chain
  281 
  282         Provides a default CA certificate to be returned in the default
  283         get_ca_info() method.  If get_ca_info() is overridden (to
  284         support multiple CAs for instance), then this method may not
  285         be called.  In that case, just implement this method to return
  286         a dummy variable.
  287         :return: The default CA certificate chain
  288         :rtype: str
  289         """
  290         raise NotImplementedError   # pragma: no cover
  291 
  292     @abc.abstractmethod
  293     def issue_certificate_request(self, order_id, order_meta, plugin_meta,
  294                                   barbican_meta_dto):
  295         """Create the initial order
  296 
  297         :param order_id: ID associated with the order
  298         :param order_meta: Dict of meta-data associated with the order
  299         :param plugin_meta: Plugin meta-data previously set by calls to
  300                             this plugin. Plugins may also update/add
  301                             information here which Barbican will persist
  302                             on their behalf
  303         :param barbican_meta_dto:
  304             Data transfer object :class:`BarbicanMetaDTO` containing data
  305             added to the request by the Barbican server to provide additional
  306             context for processing, but which are not in
  307             the original request.  For example, the plugin_ca_id
  308         :returns: A :class:`ResultDTO` instance containing the result
  309                   populated by the plugin implementation
  310         :rtype: :class:`ResultDTO`
  311         """
  312         raise NotImplementedError  # pragma: no cover
  313 
  314     @abc.abstractmethod
  315     def modify_certificate_request(self, order_id, order_meta, plugin_meta,
  316                                    barbican_meta_dto):
  317         """Update the order meta-data
  318 
  319         :param order_id: ID associated with the order
  320         :param order_meta: Dict of meta-data associated with the order
  321         :param plugin_meta: Plugin meta-data previously set by calls to
  322                             this plugin. Plugins may also update/add
  323                             information here which Barbican will persist
  324                             on their behalf
  325         :param barbican_meta_dto:
  326             Data transfer object :class:`BarbicanMetaDTO` containing data
  327             added to the request by the Barbican server to provide additional
  328             context for processing, but which are not in
  329             the original request.  For example, the plugin_ca_id
  330         :returns: A :class:`ResultDTO` instance containing the result
  331                   populated by the plugin implementation
  332         :rtype: :class:`ResultDTO`
  333         """
  334         raise NotImplementedError  # pragma: no cover
  335 
  336     @abc.abstractmethod
  337     def cancel_certificate_request(self, order_id, order_meta, plugin_meta,
  338                                    barbican_meta_dto):
  339         """Cancel the order
  340 
  341         :param order_id: ID associated with the order
  342         :param order_meta: Dict of meta-data associated with the order.
  343         :param plugin_meta: Plugin meta-data previously set by calls to
  344                             this plugin. Plugins may also update/add
  345                             information here which Barbican will persist
  346                             on their behalf
  347         :param barbican_meta_dto:
  348             Data transfer object :class:`BarbicanMetaDTO` containing data
  349             added to the request by the Barbican server to provide additional
  350             context for processing, but which are not in
  351             the original request.  For example, the plugin_ca_id
  352         :returns: A :class:`ResultDTO` instance containing the result
  353                   populated by the plugin implementation
  354         :rtype: :class:`ResultDTO`
  355         """
  356         raise NotImplementedError  # pragma: no cover
  357 
  358     @abc.abstractmethod
  359     def check_certificate_status(self, order_id, order_meta, plugin_meta,
  360                                  barbican_meta_dto):
  361         """Check status of the order
  362 
  363         :param order_id: ID associated with the order
  364         :param order_meta: Dict of meta-data associated with the order
  365         :param plugin_meta: Plugin meta-data previously set by calls to
  366                             this plugin. Plugins may also update/add
  367                             information here which Barbican will persist
  368                             on their behalf
  369         :param barbican_meta_dto:
  370             Data transfer object :class:`BarbicanMetaDTO` containing data
  371             added to the request by the Barbican server to provide additional
  372             context for processing, but which are not in
  373             the original request.  For example, the plugin_ca_id
  374         :returns: A :class:`ResultDTO` instance containing the result
  375                   populated by the plugin implementation
  376         :rtype: :class:`ResultDTO`
  377         """
  378         raise NotImplementedError  # pragma: no cover
  379 
  380     @abc.abstractmethod
  381     def supports(self, certificate_spec):
  382         """Returns if the plugin supports the certificate type.
  383 
  384         :param certificate_spec: Contains details on the certificate to
  385                                  generate the certificate order
  386         :returns: boolean indicating if the plugin supports the certificate
  387                   type
  388         """
  389         raise NotImplementedError  # pragma: no cover
  390 
  391     def supported_request_types(self):
  392         """Returns the request_types supported by this plugin.
  393 
  394         :returns: a list of the Barbican-core defined request_types
  395                   supported by this plugin.
  396         """
  397         return [CertificateRequestType.CUSTOM_REQUEST]  # pragma: no cover
  398 
  399     def get_ca_info(self):
  400         """Returns information about the CA(s) supported by this plugin.
  401 
  402         :returns: dictionary indexed by plugin_ca_id.  Each entry consists
  403                   of a dictionary of key-value pairs.
  404 
  405         An example dictionary containing the current supported attributes
  406         is shown below::
  407 
  408             { "plugin_ca_id1": {
  409                 INFO_NAME : "CA name",
  410                 INFO_DESCRIPTION : "CA user friendly description",
  411                 INFO_CA_SIGNING_CERT : "base 64 encoded signing cert",
  412                 INFO_INTERMEDIATES = "base 64 encoded certificate chain"
  413                 INFO_EXPIRATION = "ISO formatted UTC datetime for when this"
  414                                   "data will become stale"
  415                 }
  416             }
  417 
  418         """
  419         name = self.get_default_ca_name()
  420         expiration = (datetime.datetime.utcnow() +
  421                       datetime.timedelta(days=CA_INFO_DEFAULT_EXPIRATION_DAYS))
  422 
  423         default_info = {
  424             INFO_NAME: name,
  425             INFO_DESCRIPTION: "Certificate Authority - {0}".format(name),
  426             INFO_EXPIRATION: expiration.isoformat()
  427         }
  428 
  429         signing_cert = self.get_default_signing_cert()
  430         if signing_cert is not None:
  431             default_info[INFO_CA_SIGNING_CERT] = signing_cert
  432 
  433         intermediates = self.get_default_intermediates()
  434         if intermediates is not None:
  435             default_info[INFO_INTERMEDIATES] = intermediates
  436 
  437         return {name: default_info}
  438 
  439     def supports_create_ca(self):
  440         """Returns whether the plugin supports on-the-fly generation of subCAs
  441 
  442         :return: boolean, True if supported, defaults to False
  443         """
  444         return False    # pragma: no cover
  445 
  446     def create_ca(self, ca_create_dto):
  447         """Creates a subordinate CA upon request
  448 
  449         This call should only be made if a plugin returns True for
  450         supports_create_ca().
  451 
  452         :param ca_create_dto:
  453             Data transfer object :class:`CACreateDTO` containing data
  454             required to generate a subordinate CA.  This data includes
  455             the subject DN of the new CA signing certificate, a name for
  456             the new CA and a reference to the CA that will issue the new
  457             subordinate CA's signing certificate,
  458 
  459         :return: ca_info:
  460             Dictionary containing the data needed to create a
  461             models.CertificateAuthority object
  462         """
  463         raise NotImplementedError    # pragma: no cover
  464 
  465     def delete_ca(self, ca_id):
  466         """Deletes a subordinate CA
  467 
  468         Like the create_ca call, this should only be made if the plugin
  469         returns True for supports_create_ca()
  470 
  471         :param ca_id: id for the CA as specified by the plugin
  472         :return: None
  473         """
  474         raise NotImplementedError   # pragma: no cover
  475 
  476 
  477 class CACreateDTO(object):
  478     """Class that includes data needed to create a subordinate CA """
  479 
  480     def __init__(self, name=None, description=None, subject_dn=None,
  481                  parent_ca_id=None):
  482         """Creates a new CACreateDTO object.
  483 
  484         :param name: Name for the  subordinate CA
  485         :param description: Description for the subordinate CA
  486         :param subject_dn:
  487             Subject DN for the new subordinate CA's signing certificate
  488         :param parent_ca_id:
  489             ID of the CA which is supposed to sign the subordinate CA's
  490             signing certificate.  This is ID as known to the plugin
  491             (not the Barbican UUID)
  492         """
  493         self.name = name
  494         self.description = description
  495         self.subject_dn = subject_dn
  496         self.parent_ca_id = parent_ca_id
  497 
  498 
  499 class CertificateStatus(object):
  500     """Defines statuses for certificate request process.
  501 
  502     In particular:
  503 
  504     CERTIFICATE_GENERATED - Indicates a certificate was created
  505 
  506     WAITING_FOR_CA - Waiting for Certificate authority (CA) to complete order
  507 
  508     CLIENT_DATA_ISSUE_SEEN - Problem was seen with client-provided data
  509 
  510     CA_UNAVAILABLE_FOR_REQUEST - CA was not available, will try again later
  511 
  512     REQUEST_CANCELED - The client or CA cancelled this order
  513 
  514     INVALID_OPERATION - Unexpected error seen processing order
  515     """
  516 
  517     CERTIFICATE_GENERATED = "certificate generated"
  518     WAITING_FOR_CA = "waiting for CA"
  519     CLIENT_DATA_ISSUE_SEEN = "client data issue seen"
  520     CA_UNAVAILABLE_FOR_REQUEST = "CA unavailable for request"
  521     REQUEST_CANCELED = "request canceled"
  522     INVALID_OPERATION = "invalid operation"
  523 
  524 
  525 class ResultDTO(object):
  526     """Result data transfer object (DTO).
  527 
  528     An object of this type is returned by most certificate plugin methods, and
  529     is used to guide follow on processing and to provide status feedback to
  530     clients.
  531     """
  532     def __init__(self, status, status_message=None, certificate=None,
  533                  intermediates=None, retry_msec=RETRY_MSEC, retry_method=None):
  534         """Creates a new ResultDTO.
  535 
  536         :param status: Status for cert order
  537         :param status_message: Message to explain status type.
  538         :param certificate: Certificate returned from CA to be stored in
  539                             container
  540         :param intermediates: Intermediates to be stored in container
  541         :param retry_msec: Number of milliseconds to wait for retry
  542         :param retry_method: Method to be called for retry, if None then retry
  543                              the current method
  544         """
  545         self.status = status
  546         self.status_message = status_message
  547         self.certificate = certificate
  548         self.intermediates = intermediates
  549         self.retry_msec = int(retry_msec)
  550         self.retry_method = retry_method
  551 
  552 
  553 class BarbicanMetaDTO(object):
  554     """Barbican meta data transfer object
  555 
  556     Information needed to process a certificate request that is not specified
  557     in the original request, and written by Barbican core, that is needed
  558     by the plugin to process requests.
  559     """
  560 
  561     def __init__(self, plugin_ca_id=None, generated_csr=None):
  562         """Creates a new BarbicanMetaDTO.
  563 
  564         :param plugin_ca_id: ca_id as known to the plugin
  565         :param generated_csr: csr generated in the stored-key case
  566         :return: BarbicanMetaDTO
  567         """
  568         self.plugin_ca_id = plugin_ca_id
  569         self.generated_csr = generated_csr
  570 
  571 
  572 class CertificatePluginManager(named.NamedExtensionManager):
  573     def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
  574         self.ca_repo = repos.get_ca_repository()
  575         super(CertificatePluginManager, self).__init__(
  576             conf.certificate.namespace,
  577             conf.certificate.enabled_certificate_plugins,
  578             invoke_on_load=False,  # Defer creating plugins to utility below.
  579             invoke_args=invoke_args,
  580             invoke_kwds=invoke_kwargs
  581         )
  582 
  583         plugin_utils.instantiate_plugins(
  584             self, invoke_args, invoke_kwargs)
  585 
  586     def get_plugin(self, certificate_spec):
  587         """Gets a supporting certificate plugin.
  588 
  589         :param certificate_spec: Contains details on the certificate to
  590                                  generate the certificate order
  591         :returns: CertificatePluginBase plugin implementation
  592         """
  593         request_type = certificate_spec.get(
  594             REQUEST_TYPE,
  595             CertificateRequestType.CUSTOM_REQUEST)
  596 
  597         for plugin in plugin_utils.get_active_plugins(self):
  598             supported_request_types = plugin.supported_request_types()
  599             if request_type not in supported_request_types:
  600                 continue
  601 
  602             if plugin.supports(certificate_spec):
  603                 return plugin
  604 
  605         raise CertificatePluginNotFound()
  606 
  607     def get_plugin_by_name(self, plugin_name):
  608         """Gets a supporting certificate plugin.
  609 
  610         :param plugin_name: Name of the plugin to invoke
  611         :returns: CertificatePluginBase plugin implementation
  612         """
  613         for plugin in plugin_utils.get_active_plugins(self):
  614             if utils.generate_fullname_for(plugin) == plugin_name:
  615                 return plugin
  616         raise CertificatePluginNotFound(plugin_name)
  617 
  618     def get_plugin_by_ca_id(self, ca_id):
  619         """Gets a plugin based on the ca_id.
  620 
  621         :param ca_id: id for CA in the CertificateAuthorities table
  622         :returns: CertificatePluginBase plugin implementation
  623         """
  624         ca = self.ca_repo.get(ca_id, suppress_exception=True)
  625         if not ca:
  626             raise CertificatePluginNotFoundForCAID(ca_id)
  627 
  628         return self.get_plugin_by_name(ca.plugin_name)
  629 
  630     def refresh_ca_table(self):
  631         """Refreshes the CertificateAuthority table."""
  632         updates_made = False
  633         for plugin in plugin_utils.get_active_plugins(self):
  634             plugin_name = utils.generate_fullname_for(plugin)
  635             cas, offset, limit, total = self.ca_repo.get_by_create_date(
  636                 plugin_name=plugin_name,
  637                 suppress_exception=True)
  638             if total < 1:
  639                 # if no entries are found, then the plugin has not yet been
  640                 # queried or that plugin's entries have expired.
  641                 # Most of the time, this will be a no-op for plugins.
  642                 self.update_ca_info(plugin)
  643                 updates_made = True
  644         if updates_made:
  645             # commit to DB to avoid async issues with different threads
  646             repos.commit()
  647 
  648     def update_ca_info(self, cert_plugin):
  649         """Update the CA info for a particular plugin."""
  650 
  651         plugin_name = utils.generate_fullname_for(cert_plugin)
  652         try:
  653             new_ca_infos = cert_plugin.get_ca_info()
  654         except Exception as e:
  655             # The plugin gave an invalid CA, log and return
  656             LOG.error("ERROR getting CA from plugin: %s",
  657                       encodeutils.exception_to_unicode(e))
  658             return
  659 
  660         old_cas, offset, limit, total = self.ca_repo.get_by_create_date(
  661             plugin_name=plugin_name,
  662             suppress_exception=True,
  663             show_expired=True)
  664 
  665         if old_cas:
  666             for old_ca in old_cas:
  667                 plugin_ca_id = old_ca.plugin_ca_id
  668                 if plugin_ca_id not in new_ca_infos.keys():
  669                     # remove CAs that no longer exist
  670                     self._delete_ca(old_ca)
  671                 else:
  672                     # update those that still exist
  673                     self.ca_repo.update_entity(
  674                         old_ca,
  675                         new_ca_infos[plugin_ca_id])
  676             old_ids = set([ca.plugin_ca_id for ca in old_cas])
  677         else:
  678             old_ids = set()
  679 
  680         new_ids = set(new_ca_infos.keys())
  681 
  682         # add new CAs
  683         add_ids = new_ids - old_ids
  684         for add_id in add_ids:
  685             try:
  686                 self._add_ca(plugin_name, add_id, new_ca_infos[add_id])
  687             except Exception as e:
  688                 # The plugin gave an invalid CA, log and continue
  689                 LOG.error("ERROR adding CA from plugin: %s",
  690                           encodeutils.exception_to_unicode(e))
  691 
  692     def _add_ca(self, plugin_name, plugin_ca_id, ca_info):
  693         parsed_ca = dict(ca_info)
  694         parsed_ca['plugin_name'] = plugin_name
  695         parsed_ca['plugin_ca_id'] = plugin_ca_id
  696         new_ca = models.CertificateAuthority(parsed_ca)
  697         self.ca_repo.create_from(new_ca)
  698 
  699     def _delete_ca(self, ca):
  700         self.ca_repo.delete_entity_by_id(ca.id, None)
  701 
  702 
  703 class _CertificateEventPluginManager(named.NamedExtensionManager,
  704                                      CertificateEventPluginBase):
  705     """Provides services for certificate event plugins.
  706 
  707     This plugin manager differs from others in that it implements the same
  708     contract as the plugins that it manages. This allows eventing operations
  709     to occur on all installed plugins (with this class acting as a composite
  710     plugin), rather than just eventing via an individual plugin.
  711 
  712     Each time this class is initialized it will load a new instance
  713     of each enabled plugin. This is undesirable, so rather than initializing a
  714     new instance of this class use the get_event_plugin_manager function
  715     at the module level.
  716     """
  717     def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
  718         super(_CertificateEventPluginManager, self).__init__(
  719             conf.certificate_event.namespace,
  720             conf.certificate_event.enabled_certificate_event_plugins,
  721             invoke_on_load=False,  # Defer creating plugins to utility below.
  722             invoke_args=invoke_args,
  723             invoke_kwds=invoke_kwargs
  724         )
  725 
  726         plugin_utils.instantiate_plugins(
  727             self, invoke_args, invoke_kwargs)
  728 
  729     def get_plugin_by_name(self, plugin_name):
  730         """Gets a supporting certificate event plugin.
  731 
  732         :returns: CertificateEventPluginBase plugin implementation
  733         """
  734         for plugin in plugin_utils.get_active_plugins(self):
  735             if utils.generate_fullname_for(plugin) == plugin_name:
  736                 return plugin
  737         raise CertificateEventPluginNotFound(plugin_name)
  738 
  739     def notify_certificate_is_ready(
  740             self, project_id, order_ref, container_ref):
  741         self._invoke_certificate_plugins(
  742             'notify_certificate_is_ready',
  743             project_id, order_ref, container_ref)
  744 
  745     def notify_ca_is_unavailable(
  746             self, project_id, order_ref, error_msg, retry_in_msec):
  747         self._invoke_certificate_plugins(
  748             'notify_ca_is_unavailable',
  749             project_id, order_ref, error_msg, retry_in_msec)
  750 
  751     def _invoke_certificate_plugins(self, method, *args, **kwargs):
  752         """Invoke same function on plugins as calling function."""
  753         active_plugins = plugin_utils.get_active_plugins(self)
  754 
  755         if not active_plugins:
  756             raise CertificateEventPluginNotFound()
  757 
  758         for plugin in active_plugins:
  759             getattr(plugin, method)(*args, **kwargs)
  760 
  761 
  762 def get_event_plugin_manager():
  763     global _EVENT_PLUGIN_MANAGER
  764     if _EVENT_PLUGIN_MANAGER:
  765         return _EVENT_PLUGIN_MANAGER
  766     _EVENT_PLUGIN_MANAGER = _CertificateEventPluginManager()
  767     return _EVENT_PLUGIN_MANAGER