ssl-cert-provision endpoint.This allows user to create a certificate with akamai driver.
Should be letting user assoicate a certificate with ta domain. Implements blueprint: ssl-certificates Implements blueprint: akamai-ssl-driver Change-Id: Iab5dc13d6a0d36bc4e4857364ae3d27a1bcd5113
This commit is contained in:
parent
72a0d26d01
commit
df58d6014a
|
@ -118,6 +118,13 @@ delay = 1
|
||||||
[drivers:provider]
|
[drivers:provider]
|
||||||
default_cache_ttl = 86400
|
default_cache_ttl = 86400
|
||||||
|
|
||||||
|
[drivers:notification:mailgun]
|
||||||
|
mailgun_api_key = "<operator_api_key>"
|
||||||
|
mailgun_request_url = "https://api.mailgun.net/v2/{0}/events"
|
||||||
|
sand_box = "<your_sand_box_domain>"
|
||||||
|
from_address = "<your_send_from_email_address>"
|
||||||
|
recipients="<a_list_of_email_recipient>"
|
||||||
|
|
||||||
[drivers:provider:fastly]
|
[drivers:provider:fastly]
|
||||||
apikey = "MYAPIKEY"
|
apikey = "MYAPIKEY"
|
||||||
# scheme = "https"
|
# scheme = "https"
|
||||||
|
@ -151,6 +158,12 @@ akamai_https_san_config_numbers = 'MY_AKAMAI_HTTPS_CONFIG_SAN_NUMBER'
|
||||||
akamai_https_custom_config_numbers = 'MY_AKAMAI_HTTPS_CONFIG_CUSTOM_NUMBER'
|
akamai_https_custom_config_numbers = 'MY_AKAMAI_HTTPS_CONFIG_CUSTOM_NUMBER'
|
||||||
san_cert_cnames = "MY_SAN_CERT_LIST"
|
san_cert_cnames = "MY_SAN_CERT_LIST"
|
||||||
san_cert_hostname_limit = "MY_SAN_HOSTNAMES_LMIT"
|
san_cert_hostname_limit = "MY_SAN_HOSTNAMES_LMIT"
|
||||||
|
contract_id = "MY_CONTRACT_ID"
|
||||||
|
group_id = "MY_GROUP_ID"
|
||||||
|
property_id = "MY_PROPERTY_ID"
|
||||||
|
storage_backend_type = zookeeper
|
||||||
|
storage_backend_host = <your_transport_server(s)>
|
||||||
|
storage_backend_port = <your_transport_port>
|
||||||
|
|
||||||
[drivers:notification:mailgun]
|
[drivers:notification:mailgun]
|
||||||
mailgun_api_key = "<operator_api_key>"
|
mailgun_api_key = "<operator_api_key>"
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Copyright (c) 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 oslo_config import cfg
|
||||||
|
from taskflow.patterns import graph_flow
|
||||||
|
from taskflow.patterns import linear_flow
|
||||||
|
from taskflow import retry
|
||||||
|
|
||||||
|
from poppy.distributed_task.taskflow.task import create_ssl_certificate_tasks
|
||||||
|
from poppy.openstack.common import log
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
conf = cfg.CONF
|
||||||
|
conf(project='poppy', prog='poppy', args=[])
|
||||||
|
|
||||||
|
|
||||||
|
def create_ssl_certificate():
|
||||||
|
flow = graph_flow.Flow('Creating poppy ssl certificate').add(
|
||||||
|
linear_flow.Flow("Provision poppy ssl certificate",
|
||||||
|
retry=retry.Times(5)).add(
|
||||||
|
create_ssl_certificate_tasks.CreateProviderSSLCertificateTask()
|
||||||
|
),
|
||||||
|
create_ssl_certificate_tasks.SendNotificationTask(),
|
||||||
|
create_ssl_certificate_tasks.UpdateCertInfoTask()
|
||||||
|
)
|
||||||
|
return flow
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Copyright (c) 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 json
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from taskflow import task
|
||||||
|
|
||||||
|
from poppy.distributed_task.utils import memoized_controllers
|
||||||
|
from poppy.openstack.common import log
|
||||||
|
from poppy.transport.pecan.models.request import ssl_certificate
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
conf = cfg.CONF
|
||||||
|
conf(project='poppy', prog='poppy', args=[])
|
||||||
|
|
||||||
|
|
||||||
|
class CreateProviderSSLCertificateTask(task.Task):
|
||||||
|
default_provides = "responders"
|
||||||
|
|
||||||
|
def execute(self, providers_list_json, cert_obj_json):
|
||||||
|
service_controller = memoized_controllers.task_controllers('poppy')
|
||||||
|
|
||||||
|
# call provider create_ssl_certificate function
|
||||||
|
providers_list = json.loads(providers_list_json)
|
||||||
|
cert_obj = ssl_certificate.load_from_json(json.loads(cert_obj_json))
|
||||||
|
|
||||||
|
responders = []
|
||||||
|
# try to create all service from each provider
|
||||||
|
for provider in providers_list:
|
||||||
|
LOG.info('Starting to create ssl certificate: {0}'.format(
|
||||||
|
cert_obj.to_dict()))
|
||||||
|
LOG.info('from {0}'.format(provider))
|
||||||
|
responder = service_controller.provider_wrapper.create_certificate(
|
||||||
|
service_controller._driver.providers[provider],
|
||||||
|
cert_obj
|
||||||
|
)
|
||||||
|
responders.append(responder)
|
||||||
|
|
||||||
|
return responders
|
||||||
|
|
||||||
|
|
||||||
|
class SendNotificationTask(task.Task):
|
||||||
|
|
||||||
|
def execute(self, project_id, responders):
|
||||||
|
service_controller = memoized_controllers.task_controllers('poppy')
|
||||||
|
|
||||||
|
notification_content = ""
|
||||||
|
for responder in responders:
|
||||||
|
for provider in responder:
|
||||||
|
notification_content += (
|
||||||
|
"Project ID: %s, Provider: %s, Detail: %s" %
|
||||||
|
(project_id, provider, str(responder[provider])))
|
||||||
|
|
||||||
|
for n_driver in service_controller._driver.notification:
|
||||||
|
service_controller.notification_wrapper.send(
|
||||||
|
n_driver,
|
||||||
|
n_driver.obj.notification_subject,
|
||||||
|
notification_content)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateCertInfoTask(task.Task):
|
||||||
|
|
||||||
|
def execute(self, project_id, cert_obj_json, responders):
|
||||||
|
service_controller, self.storage_controller = \
|
||||||
|
memoized_controllers.task_controllers('poppy', 'storage')
|
||||||
|
|
||||||
|
cert_details = {}
|
||||||
|
for responder in responders:
|
||||||
|
for provider in responder:
|
||||||
|
cert_details[provider] = json.dumps(responder[provider])
|
||||||
|
|
||||||
|
cert_obj = ssl_certificate.load_from_json(json.loads(cert_obj_json))
|
||||||
|
self.storage_controller.update_cert_info(cert_obj.domain_name,
|
||||||
|
cert_obj.cert_type,
|
||||||
|
cert_obj.flavor_id,
|
||||||
|
cert_details)
|
||||||
|
|
||||||
|
return
|
|
@ -46,5 +46,8 @@ def task_controllers(program, controller=None):
|
||||||
return service_controller, service_controller.storage_controller
|
return service_controller, service_controller.storage_controller
|
||||||
if controller == 'dns':
|
if controller == 'dns':
|
||||||
return service_controller, service_controller.dns_controller
|
return service_controller, service_controller.dns_controller
|
||||||
|
if controller == 'ssl_certificate':
|
||||||
|
return service_controller, (
|
||||||
|
bootstrap_obj.manager.ssl_certificate_controller)
|
||||||
else:
|
else:
|
||||||
return service_controller
|
return service_controller
|
||||||
|
|
|
@ -17,6 +17,7 @@ from poppy.manager.base import driver
|
||||||
from poppy.manager.base import flavors
|
from poppy.manager.base import flavors
|
||||||
from poppy.manager.base import home
|
from poppy.manager.base import home
|
||||||
from poppy.manager.base import services
|
from poppy.manager.base import services
|
||||||
|
from poppy.manager.base import ssl_certificate
|
||||||
|
|
||||||
|
|
||||||
Driver = driver.ManagerDriverBase
|
Driver = driver.ManagerDriverBase
|
||||||
|
@ -24,3 +25,4 @@ Driver = driver.ManagerDriverBase
|
||||||
FlavorsController = flavors.FlavorsControllerBase
|
FlavorsController = flavors.FlavorsControllerBase
|
||||||
ServicesController = services.ServicesControllerBase
|
ServicesController = services.ServicesControllerBase
|
||||||
HomeController = home.HomeControllerBase
|
HomeController = home.HomeControllerBase
|
||||||
|
SSLCertificateController = ssl_certificate.SSLCertificateController
|
||||||
|
|
|
@ -73,3 +73,13 @@ class ProviderWrapper(object):
|
||||||
service_obj,
|
service_obj,
|
||||||
hard,
|
hard,
|
||||||
purge_url)
|
purge_url)
|
||||||
|
|
||||||
|
def create_certificate(self, ext, cert_obj):
|
||||||
|
"""Create a provider
|
||||||
|
|
||||||
|
:param ext
|
||||||
|
:param service_obj
|
||||||
|
:returns: ext.obj.service_controller.create(service_obj)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return ext.obj.service_controller.create_certificate(cert_obj)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright (c) 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 abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from poppy.manager.base import controller
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class SSLCertificateController(controller.ManagerControllerBase):
|
||||||
|
"""Home controller base class."""
|
||||||
|
|
||||||
|
def __init__(self, manager):
|
||||||
|
super(SSLCertificateController, self).__init__(manager)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_ssl_certificate(self, project_id, domain_name, **extras):
|
||||||
|
"""create_ssl_certificate
|
||||||
|
|
||||||
|
:param project_id
|
||||||
|
:param domain_name
|
||||||
|
:raises: NotImplementedError
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
|
@ -17,9 +17,11 @@ from poppy.manager.default import flavors
|
||||||
from poppy.manager.default import health
|
from poppy.manager.default import health
|
||||||
from poppy.manager.default import home
|
from poppy.manager.default import home
|
||||||
from poppy.manager.default import services
|
from poppy.manager.default import services
|
||||||
|
from poppy.manager.default import ssl_certificate
|
||||||
|
|
||||||
|
|
||||||
Home = home.DefaultHomeController
|
Home = home.DefaultHomeController
|
||||||
Flavors = flavors.DefaultFlavorsController
|
Flavors = flavors.DefaultFlavorsController
|
||||||
Health = health.DefaultHealthController
|
Health = health.DefaultHealthController
|
||||||
Services = services.DefaultServicesController
|
Services = services.DefaultServicesController
|
||||||
|
SSLCertificate = ssl_certificate.DefaultSSLCertificateController
|
||||||
|
|
|
@ -43,3 +43,7 @@ class DefaultManagerDriver(base.Driver):
|
||||||
@decorators.lazy_property(write=False)
|
@decorators.lazy_property(write=False)
|
||||||
def health_controller(self):
|
def health_controller(self):
|
||||||
return controllers.Health(self)
|
return controllers.Health(self)
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def ssl_certificate_controller(self):
|
||||||
|
return controllers.SSLCertificate(self)
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Copyright (c) 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 json
|
||||||
|
|
||||||
|
from poppy.distributed_task.taskflow.flow import create_ssl_certificate
|
||||||
|
from poppy.manager import base
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultSSLCertificateController(base.SSLCertificateController):
|
||||||
|
|
||||||
|
def __init__(self, manager):
|
||||||
|
super(DefaultSSLCertificateController, self).__init__(manager)
|
||||||
|
|
||||||
|
self.distributed_task_controller = (
|
||||||
|
self._driver.distributed_task.services_controller)
|
||||||
|
self.storage_controller = self._driver.storage.services_controller
|
||||||
|
self.flavor_controller = self._driver.storage.flavors_controller
|
||||||
|
|
||||||
|
def create_ssl_certificate(self, project_id, cert_obj):
|
||||||
|
|
||||||
|
try:
|
||||||
|
flavor = self.flavor_controller.get(cert_obj.flavor_id)
|
||||||
|
# raise a lookup error if the flavor is not found
|
||||||
|
except LookupError as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.storage_controller.create_cert(
|
||||||
|
project_id,
|
||||||
|
cert_obj)
|
||||||
|
# ValueError will be raised if the cert_info has already existed
|
||||||
|
except ValueError as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
providers = [p.provider_id for p in flavor.providers]
|
||||||
|
kwargs = {
|
||||||
|
'providers_list_json': json.dumps(providers),
|
||||||
|
'project_id': project_id,
|
||||||
|
'cert_obj_json': json.dumps(cert_obj.to_dict())
|
||||||
|
}
|
||||||
|
self.distributed_task_controller.submit_task(
|
||||||
|
create_ssl_certificate.create_ssl_certificate,
|
||||||
|
**kwargs)
|
||||||
|
return kwargs
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Copyright (c) 2015 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 poppy.model import common
|
||||||
|
|
||||||
|
|
||||||
|
VALID_CERT_TYPES = [u'san', u'custom']
|
||||||
|
|
||||||
|
|
||||||
|
class SSLCertificate(common.DictSerializableModel):
|
||||||
|
|
||||||
|
"""SSL Certificate Class."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
flavor_id,
|
||||||
|
domain_name,
|
||||||
|
cert_type):
|
||||||
|
self._flavor_id = flavor_id
|
||||||
|
self._domain_name = domain_name
|
||||||
|
self._cert_type = cert_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flavor_id(self):
|
||||||
|
"""Get or set flavor ref."""
|
||||||
|
return self._flavor_id
|
||||||
|
|
||||||
|
@flavor_id.setter
|
||||||
|
def flavor_id(self, value):
|
||||||
|
self._flavor_id = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain_name(self):
|
||||||
|
"""Get service id."""
|
||||||
|
return self._domain_name
|
||||||
|
|
||||||
|
@domain_name.setter
|
||||||
|
def domain_name(self, value):
|
||||||
|
self._domain_name = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cert_type(self):
|
||||||
|
"""Get service id."""
|
||||||
|
return self._cert_type
|
||||||
|
|
||||||
|
@cert_type.setter
|
||||||
|
def cert_type(self, value):
|
||||||
|
if (value in VALID_CERT_TYPES):
|
||||||
|
self._cert_type = value
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
u'Cert type: {0} not in valid options: {1}'.format(
|
||||||
|
value,
|
||||||
|
VALID_CERT_TYPES)
|
||||||
|
)
|
|
@ -34,7 +34,10 @@ MAIL_NOTIFICATION_OPTIONS = [
|
||||||
cfg.StrOpt('from_address', default='noreply@poppycdn.org',
|
cfg.StrOpt('from_address', default='noreply@poppycdn.org',
|
||||||
help='Sent from email address'),
|
help='Sent from email address'),
|
||||||
cfg.ListOpt('recipients',
|
cfg.ListOpt('recipients',
|
||||||
help='A list of emails addresses to receive notification ')
|
help='A list of emails addresses to receive notification '),
|
||||||
|
cfg.StrOpt('notification_subject',
|
||||||
|
default='Poppy SSL Certificate Provisioned',
|
||||||
|
help='The subject of the email notification ')
|
||||||
]
|
]
|
||||||
|
|
||||||
MAIL_NOTIFICATION_GROUP = 'drivers:notification:mailgun'
|
MAIL_NOTIFICATION_GROUP = 'drivers:notification:mailgun'
|
||||||
|
@ -60,6 +63,8 @@ class MailNotificationDriver(base.Driver):
|
||||||
self.sand_box = self.mail_notification_conf.sand_box
|
self.sand_box = self.mail_notification_conf.sand_box
|
||||||
self.from_address = self.mail_notification_conf.from_address
|
self.from_address = self.mail_notification_conf.from_address
|
||||||
self.recipients = self.mail_notification_conf.recipients
|
self.recipients = self.mail_notification_conf.recipients
|
||||||
|
self.notification_subject = (
|
||||||
|
self.mail_notification_conf.notification_subject)
|
||||||
|
|
||||||
# validate email addresses
|
# validate email addresses
|
||||||
if not validate_email_address(self.from_address):
|
if not validate_email_address(self.from_address):
|
||||||
|
|
|
@ -23,6 +23,8 @@ import requests
|
||||||
|
|
||||||
from poppy.openstack.common import log
|
from poppy.openstack.common import log
|
||||||
from poppy.provider.akamai import controllers
|
from poppy.provider.akamai import controllers
|
||||||
|
from poppy.provider.akamai.mod_san_queue import zookeeper_queue
|
||||||
|
from poppy.provider.akamai.san_info_storage import zookeeper_storage
|
||||||
from poppy.provider import base
|
from poppy.provider import base
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
@ -89,6 +91,17 @@ AKAMAI_OPTIONS = [
|
||||||
cfg.IntOpt('san_cert_hostname_limit', default=80,
|
cfg.IntOpt('san_cert_hostname_limit', default=80,
|
||||||
help='default limit on how many hostnames can'
|
help='default limit on how many hostnames can'
|
||||||
' be held by a SAN cert'),
|
' be held by a SAN cert'),
|
||||||
|
|
||||||
|
# related info for SPS && PAPI APIs
|
||||||
|
cfg.StrOpt(
|
||||||
|
'contract_id',
|
||||||
|
help='Operator contractID'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'group_id',
|
||||||
|
help='Operator groupID'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'property_id',
|
||||||
|
help='Operator propertyID')
|
||||||
]
|
]
|
||||||
|
|
||||||
AKAMAI_GROUP = 'drivers:provider:akamai'
|
AKAMAI_GROUP = 'drivers:provider:akamai'
|
||||||
|
@ -139,9 +152,25 @@ class CDNProvider(base.Driver):
|
||||||
access_token=self.akamai_conf.ccu_api_access_token
|
access_token=self.akamai_conf.ccu_api_access_token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.akamai_sps_api_base_url = ''.join([
|
||||||
|
str(self.akamai_conf.policy_api_base_url),
|
||||||
|
'config-secure-provisioning-service/v1'
|
||||||
|
'/sps-requests/{spsId}?contractId=%s&groupId=%s' % (
|
||||||
|
self.akamai_conf.contract_id,
|
||||||
|
self.akamai_conf.group_id
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
self.san_cert_cnames = self.akamai_conf.san_cert_cnames
|
self.san_cert_cnames = self.akamai_conf.san_cert_cnames
|
||||||
self.san_cert_hostname_limit = self.akamai_conf.san_cert_hostname_limit
|
self.san_cert_hostname_limit = self.akamai_conf.san_cert_hostname_limit
|
||||||
|
|
||||||
|
self.akamai_sps_api_client = self.akamai_policy_api_client
|
||||||
|
|
||||||
|
self.san_info_storage = (
|
||||||
|
zookeeper_storage.ZookeeperSanInfoStorage(self._conf))
|
||||||
|
self.mod_san_queue = (
|
||||||
|
zookeeper_queue.ZookeeperModSanQueue(self._conf))
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self):
|
||||||
|
|
||||||
request_headers = {
|
request_headers = {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Copyright (c) 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 abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class ModSanQueue(object):
|
||||||
|
"""Interface definition for Akamai Mod San Queue.
|
||||||
|
|
||||||
|
The purpose of this queue is to buffer the client's
|
||||||
|
mod_san request (Currently one request will make one
|
||||||
|
san_cert pending, if currently there is no active san
|
||||||
|
cert to serve the client request, it is needed to keep
|
||||||
|
the request in a queue)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
self._conf = conf
|
||||||
|
|
||||||
|
def enqueue_mod_san_request(self, domain_name):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def dequeue_mod_san_request(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def move_request_to_top(self):
|
||||||
|
raise NotImplementedError
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Copyright (c) 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 kazoo.recipe import queue
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from poppy.common import decorators
|
||||||
|
from poppy.provider.akamai.mod_san_queue import base
|
||||||
|
from poppy.provider.akamai import utils
|
||||||
|
|
||||||
|
|
||||||
|
AKAMAI_OPTIONS = [
|
||||||
|
# queue backend configs
|
||||||
|
cfg.StrOpt(
|
||||||
|
'queue_backend_type',
|
||||||
|
help='SAN Cert Queueing backend'),
|
||||||
|
cfg.ListOpt('queue_backend_host', default=['localhost'],
|
||||||
|
help='default queue backend server hosts'),
|
||||||
|
cfg.IntOpt('queue_backend_port', default=2181, help='default'
|
||||||
|
' default queue backend server port (e.g: 2181)'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'mod_san_queue_path', default='/mod_san_queue', help='Zookeeper path '
|
||||||
|
'for mod_san_queue'),
|
||||||
|
]
|
||||||
|
|
||||||
|
AKAMAI_GROUP = 'drivers:provider:akamai'
|
||||||
|
|
||||||
|
|
||||||
|
class ZookeeperModSanQueue(base.ModSanQueue):
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(ZookeeperModSanQueue, self).__init__(conf)
|
||||||
|
|
||||||
|
self._conf.register_opts(AKAMAI_OPTIONS,
|
||||||
|
group=AKAMAI_GROUP)
|
||||||
|
self.akamai_conf = self._conf[AKAMAI_GROUP]
|
||||||
|
|
||||||
|
self.mod_san_queue_backend = queue.LockingQueue(
|
||||||
|
self.zk_client,
|
||||||
|
self.akamai_conf.mod_san_queue_path)
|
||||||
|
|
||||||
|
@decorators.lazy_property(write=False)
|
||||||
|
def zk_client(self):
|
||||||
|
return utils.connect_to_zookeeper_queue_backend(self.akamai_conf)
|
||||||
|
|
||||||
|
def enqueue_mod_san_request(self, cert_obj_json):
|
||||||
|
self.mod_san_queue_backend.put(cert_obj_json)
|
||||||
|
|
||||||
|
def dequeue_mod_san_request(self, consume=True):
|
||||||
|
res = self.mod_san_queue_backend.get()
|
||||||
|
if consume:
|
||||||
|
self.mod_san_queue_backend.consume()
|
||||||
|
return res
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Copyright (c) 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 abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BaseAkamaiSanInfoStorage(object):
|
||||||
|
"""Interface definition for Akamai San Info Storage.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
self._conf = conf
|
||||||
|
|
||||||
|
def get_cert_info(self, san_cert_name):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def save_cert_last_spsid(self, san_cert_name, sps_id_value):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_cert_last_spsid(self, san_cert_name):
|
||||||
|
raise NotImplementedError
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Copyright (c) 2015 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 oslo_config import cfg
|
||||||
|
|
||||||
|
from poppy.provider.akamai.san_info_storage import base
|
||||||
|
from poppy.provider.akamai import utils
|
||||||
|
|
||||||
|
|
||||||
|
AKAMAI_OPTIONS = [
|
||||||
|
# storage backend configs for long running tasks
|
||||||
|
cfg.StrOpt(
|
||||||
|
'storage_backend_type',
|
||||||
|
default='zookeeper',
|
||||||
|
help='SAN Cert info storage backend'),
|
||||||
|
cfg.ListOpt('storage_backend_host', default=['localhost'],
|
||||||
|
help='default san info storage backend server hosts'),
|
||||||
|
cfg.IntOpt('storage_backend_port', default=2181, help='default'
|
||||||
|
' default san info storage backend server port (e.g: 2181)'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'san_info_storage_path', default='/san_info', help='zookeeper backend'
|
||||||
|
' path for san cert info'),
|
||||||
|
]
|
||||||
|
|
||||||
|
AKAMAI_GROUP = 'drivers:provider:akamai'
|
||||||
|
|
||||||
|
|
||||||
|
class ZookeeperSanInfoStorage(base.BaseAkamaiSanInfoStorage):
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(ZookeeperSanInfoStorage, self).__init__(conf)
|
||||||
|
|
||||||
|
self._conf.register_opts(AKAMAI_OPTIONS,
|
||||||
|
group=AKAMAI_GROUP)
|
||||||
|
self.akamai_conf = self._conf[AKAMAI_GROUP]
|
||||||
|
self.san_info_storage_path = self.akamai_conf.san_info_storage_path
|
||||||
|
|
||||||
|
self.zookeeper_client = utils.connect_to_zookeeper_storage_backend(
|
||||||
|
self.akamai_conf)
|
||||||
|
|
||||||
|
def _zk_path(self, san_cert_name, property_name=None):
|
||||||
|
path_names_list = [self.san_info_storage_path, san_cert_name,
|
||||||
|
property_name] if property_name else (
|
||||||
|
[self.san_info_storage_path, san_cert_name])
|
||||||
|
return '/'.join(path_names_list)
|
||||||
|
|
||||||
|
def list_all_san_cert_names(self):
|
||||||
|
self.zookeeper_client.ensure_path(self.san_info_storage_path)
|
||||||
|
return self.zookeeper_client.get_children(self.san_info_storage_path)
|
||||||
|
|
||||||
|
def get_cert_info(self, san_cert_name):
|
||||||
|
self.zookeeper_client.ensure_path(self._zk_path(san_cert_name, None))
|
||||||
|
jobId, _ = self.zookeeper_client.get(self._zk_path(san_cert_name,
|
||||||
|
"jobId"))
|
||||||
|
issuer, _ = self.zookeeper_client.get(self._zk_path(san_cert_name,
|
||||||
|
"issuer"))
|
||||||
|
ipVersion, _ = self.zookeeper_client.get(
|
||||||
|
self._zk_path(san_cert_name, "ipVersion"))
|
||||||
|
slot_deployment_klass, _ = self.zookeeper_client.get(
|
||||||
|
self._zk_path(san_cert_name, "slot_deployment_klass"))
|
||||||
|
return {
|
||||||
|
# This will always be the san cert name
|
||||||
|
'cnameHostname': san_cert_name,
|
||||||
|
'jobId': jobId,
|
||||||
|
'issuer': issuer,
|
||||||
|
'createType': 'modSan',
|
||||||
|
'ipVersion': ipVersion,
|
||||||
|
'slot-deployment.class': slot_deployment_klass
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_cert_last_spsid(self, san_cert_name, sps_id_value):
|
||||||
|
self._save_cert_property_value(san_cert_name,
|
||||||
|
'spsId', sps_id_value)
|
||||||
|
|
||||||
|
def get_cert_last_spsid(self, san_cert_name):
|
||||||
|
my_sps_id_path = self._zk_path(san_cert_name, 'spsId')
|
||||||
|
self.zookeeper_client.ensure_path(my_sps_id_path)
|
||||||
|
spsId, _ = self.zookeeper_client.get(my_sps_id_path)
|
||||||
|
return spsId
|
||||||
|
|
||||||
|
def _save_cert_property_value(self, san_cert_name,
|
||||||
|
property_name, value):
|
||||||
|
property_name_path = self._zk_path(san_cert_name, property_name)
|
||||||
|
self.zookeeper_client.ensure_path(property_name_path)
|
||||||
|
self.zookeeper_client.set(property_name_path, str(value))
|
|
@ -37,12 +37,25 @@ class ServiceController(base.ServiceBase):
|
||||||
def ccu_api_client(self):
|
def ccu_api_client(self):
|
||||||
return self.driver.ccu_api_client
|
return self.driver.ccu_api_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sps_api_client(self):
|
||||||
|
return self.driver.akamai_sps_api_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def san_info_storage(self):
|
||||||
|
return self.driver.san_info_storage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mod_san_queue(self):
|
||||||
|
return self.driver.mod_san_queue
|
||||||
|
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
super(ServiceController, self).__init__(driver)
|
super(ServiceController, self).__init__(driver)
|
||||||
|
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
self.policy_api_base_url = self.driver.akamai_policy_api_base_url
|
self.policy_api_base_url = self.driver.akamai_policy_api_base_url
|
||||||
self.ccu_api_base_url = self.driver.akamai_ccu_api_base_url
|
self.ccu_api_base_url = self.driver.akamai_ccu_api_base_url
|
||||||
|
self.sps_api_base_url = self.driver.akamai_sps_api_base_url
|
||||||
self.request_header = {'Content-type': 'application/json',
|
self.request_header = {'Content-type': 'application/json',
|
||||||
'Accept': 'text/plain'}
|
'Accept': 'text/plain'}
|
||||||
|
|
||||||
|
@ -481,6 +494,73 @@ class ServiceController(base.ServiceBase):
|
||||||
format(provider_service_id))
|
format(provider_service_id))
|
||||||
return self.responder.failed(str(e))
|
return self.responder.failed(str(e))
|
||||||
|
|
||||||
|
def create_certificate(self, cert_obj):
|
||||||
|
if cert_obj.cert_type == 'san':
|
||||||
|
for san_cert_name in self.san_cert_cnames:
|
||||||
|
lastSpsId = (
|
||||||
|
self.san_info_storage.get_cert_last_spsid(san_cert_name))
|
||||||
|
if lastSpsId not in [None, ""]:
|
||||||
|
LOG.info('Latest spsId for %s is: %s' % (san_cert_name,
|
||||||
|
lastSpsId))
|
||||||
|
resp = self.sps_api_client.get(
|
||||||
|
self.sps_api_base_url.format(spsId=lastSpsId),
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError('SPS API Request Failed'
|
||||||
|
'Exception: %s' % resp.text)
|
||||||
|
status = json.loads(resp.text)['requestList'][0]['status']
|
||||||
|
# This SAN Cert is on pending status
|
||||||
|
if status != 'SPS Request Complete':
|
||||||
|
LOG.info("SPS Not completed for %s..." %
|
||||||
|
self.san_cert_name)
|
||||||
|
continue
|
||||||
|
# issue modify san_cert sps request
|
||||||
|
cert_info = self.san_info_storage.get_cert_info(san_cert_name)
|
||||||
|
cert_info['add.sans'] = cert_obj.domain_name
|
||||||
|
string_post_data = '&'.join(
|
||||||
|
['%s=%s' % (k, v) for (k, v) in cert_info.items()])
|
||||||
|
LOG.info('Post modSan request with request data: %s' %
|
||||||
|
string_post_data)
|
||||||
|
resp = self.sps_api_client.post(
|
||||||
|
self.sps_api_base_url.format(spsId=""),
|
||||||
|
data=string_post_data
|
||||||
|
)
|
||||||
|
if resp.status_code != 202:
|
||||||
|
raise RuntimeError('SPS Request failed.'
|
||||||
|
'Exception: %s' % resp.text)
|
||||||
|
else:
|
||||||
|
resp_dict = json.loads(resp.text)
|
||||||
|
LOG.info('modSan request submitted. Response: %s' %
|
||||||
|
str(resp_dict))
|
||||||
|
this_sps_id = resp_dict['spsId']
|
||||||
|
self.san_info_storage.save_cert_last_spsid(san_cert_name,
|
||||||
|
this_sps_id)
|
||||||
|
return self.responder.ssl_certificate_provisioned(
|
||||||
|
san_cert_name, {
|
||||||
|
'status': 'create_in_progress',
|
||||||
|
'san cert': san_cert_name,
|
||||||
|
'akamai_spsId': this_sps_id,
|
||||||
|
'create_at': str(datetime.datetime.now()),
|
||||||
|
'action': 'Waiting for customer domain '
|
||||||
|
'validation for %s' %
|
||||||
|
(cert_obj.domain_name)
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
self.mod_san_queue.enqueue_mod_san_request(
|
||||||
|
json.dumps(cert_obj.to_dict()))
|
||||||
|
return self.responder.ssl_certificate_provisioned(None, {
|
||||||
|
'status': 'failed',
|
||||||
|
'san cert': None,
|
||||||
|
'action': 'No available san cert for %s right now.'
|
||||||
|
' More provisioning might be needed' %
|
||||||
|
(cert_obj.domain_name)
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return self.responder.ssl_certificate_provisioned(None, {
|
||||||
|
'status': 'failed',
|
||||||
|
'reason': 'Cert type : %s hasn\'t been implemented'
|
||||||
|
})
|
||||||
|
|
||||||
@decorators.lazy_property(write=False)
|
@decorators.lazy_property(write=False)
|
||||||
def current_customer(self):
|
def current_customer(self):
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from kazoo import client
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ def get_ssl_number_of_hosts(remote_host):
|
||||||
|
|
||||||
# We can actually print all the Subject Alternative Names
|
# We can actually print all the Subject Alternative Names
|
||||||
# for san in sans:
|
# for san in sans:
|
||||||
# print san
|
# print(san)
|
||||||
result = len(sans)
|
result = len(sans)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -73,6 +74,28 @@ def get_ssl_number_of_hosts(remote_host):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def connect_to_zookeeper_storage_backend(conf):
|
||||||
|
"""Connect to a zookeeper cluster"""
|
||||||
|
storage_backend_hosts = ','.join(['%s:%s' % (
|
||||||
|
host, conf.storage_backend_port)
|
||||||
|
for host in
|
||||||
|
conf.storage_backend_host])
|
||||||
|
zk_client = client.KazooClient(storage_backend_hosts)
|
||||||
|
zk_client.start()
|
||||||
|
return zk_client
|
||||||
|
|
||||||
|
|
||||||
|
def connect_to_zookeeper_queue_backend(conf):
|
||||||
|
"""Connect to a zookeeper cluster"""
|
||||||
|
storage_backend_hosts = ','.join(['%s:%s' % (
|
||||||
|
host, conf.queue_backend_port)
|
||||||
|
for host in
|
||||||
|
conf.queue_backend_host])
|
||||||
|
zk_client = client.KazooClient(storage_backend_hosts)
|
||||||
|
zk_client.start()
|
||||||
|
return zk_client
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print('Usage: %s <remote_host_you_want_get_cert_on>' % sys.argv[0])
|
print('Usage: %s <remote_host_you_want_get_cert_on>' % sys.argv[0])
|
||||||
|
|
|
@ -123,3 +123,17 @@ class Responder(object):
|
||||||
'caching': cache_list
|
'caching': cache_list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def ssl_certificate_provisioned(self, cert_domain, extra_info=None):
|
||||||
|
"""ssl_certificate_provisioned.
|
||||||
|
|
||||||
|
:param cert_domain
|
||||||
|
:param extra_info
|
||||||
|
:returns provider msg{cert_domain, extra_info}
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
self.provider: {
|
||||||
|
'cert_domain': cert_domain,
|
||||||
|
'extra_info': extra_info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -112,6 +112,28 @@ class ServicesControllerBase(controller.StorageControllerBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_cert(self, project_id, cert_obj):
|
||||||
|
"""create_cert
|
||||||
|
|
||||||
|
:param project_id
|
||||||
|
:param cert_obj
|
||||||
|
:raise NotImplementedError
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update_cert_info(self, domain_name, cert_type, flavor_id,
|
||||||
|
cert_details):
|
||||||
|
"""update_cert_info.
|
||||||
|
|
||||||
|
:param domain_name
|
||||||
|
:param cert_type
|
||||||
|
:param flavor_id
|
||||||
|
:param cert_info
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_result(result):
|
def format_result(result):
|
||||||
"""format_result
|
"""format_result
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
CREATE TABLE certificate_info (
|
||||||
|
project_id VARCHAR,
|
||||||
|
flavor_id VARCHAR,
|
||||||
|
cert_type VARCHAR,
|
||||||
|
domain_name VARCHAR,
|
||||||
|
cert_details MAP<TEXT, TEXT>,
|
||||||
|
PRIMARY KEY (domain_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_cert_type
|
||||||
|
ON certificate_info (cert_type);
|
||||||
|
|
||||||
|
--//@UNDO
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS certificate_info;
|
|
@ -0,0 +1,9 @@
|
||||||
|
# This for running cdeploy command
|
||||||
|
|
||||||
|
development:
|
||||||
|
hosts: [localhost]
|
||||||
|
keyspace: poppy
|
||||||
|
|
||||||
|
production:
|
||||||
|
hosts: [your_production_env_host(s)]
|
||||||
|
keyspace: poppy
|
|
@ -166,6 +166,30 @@ CQL_CREATE_SERVICE = '''
|
||||||
%(log_delivery)s)
|
%(log_delivery)s)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
CQL_CREATE_CERT = '''
|
||||||
|
INSERT INTO certificate_info (project_id,
|
||||||
|
flavor_id,
|
||||||
|
cert_type,
|
||||||
|
domain_name,
|
||||||
|
cert_details
|
||||||
|
)
|
||||||
|
VALUES (%(project_id)s,
|
||||||
|
%(flavor_id)s,
|
||||||
|
%(cert_type)s,
|
||||||
|
%(domain_name)s,
|
||||||
|
%(cert_details)s)
|
||||||
|
'''
|
||||||
|
|
||||||
|
CQL_VERIFY_CERT = '''
|
||||||
|
SELECT project_id,
|
||||||
|
flavor_id,
|
||||||
|
cert_type,
|
||||||
|
domain_name
|
||||||
|
FROM certificate_info
|
||||||
|
WHERE domain_name = %(domain_name)s
|
||||||
|
ALLOW FILTERING
|
||||||
|
'''
|
||||||
|
|
||||||
CQL_UPDATE_SERVICE = CQL_CREATE_SERVICE
|
CQL_UPDATE_SERVICE = CQL_CREATE_SERVICE
|
||||||
|
|
||||||
CQL_GET_PROVIDER_DETAILS = '''
|
CQL_GET_PROVIDER_DETAILS = '''
|
||||||
|
@ -180,6 +204,13 @@ CQL_UPDATE_PROVIDER_DETAILS = '''
|
||||||
WHERE project_id = %(project_id)s AND service_id = %(service_id)s
|
WHERE project_id = %(project_id)s AND service_id = %(service_id)s
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
CQL_UPDATE_CERT_DETAILS = '''
|
||||||
|
UPDATE certificate_info
|
||||||
|
set cert_details = %(cert_details)s
|
||||||
|
WHERE domain_name = %(domain_name)s
|
||||||
|
IF cert_type = %(cert_type)s AND flavor_id = %(flavor_id)s
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
class ServicesController(base.ServicesController):
|
class ServicesController(base.ServicesController):
|
||||||
|
|
||||||
|
@ -291,6 +322,51 @@ class ServicesController(base.ServicesController):
|
||||||
LOG.exception(ex)
|
LOG.exception(ex)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def cert_already_exist(self, domain_name, comparing_cert_type,
|
||||||
|
comparing_flavor_id,
|
||||||
|
comparing_project_id):
|
||||||
|
"""cert_already_exist
|
||||||
|
|
||||||
|
Check if a cert with this domain name and type has already been
|
||||||
|
created, or if the domain has been taken by other customers
|
||||||
|
|
||||||
|
:param domain_name
|
||||||
|
:param cert_type
|
||||||
|
:param comparing_project_id
|
||||||
|
|
||||||
|
:raises ValueError
|
||||||
|
:returns Boolean if the cert with same type exists with another user.
|
||||||
|
"""
|
||||||
|
LOG.info("Check if cert on '{0}' exists".format(domain_name))
|
||||||
|
args = {
|
||||||
|
'domain_name': domain_name.lower()
|
||||||
|
}
|
||||||
|
stmt = query.SimpleStatement(
|
||||||
|
CQL_VERIFY_CERT,
|
||||||
|
consistency_level=self._driver.consistency_level)
|
||||||
|
results = self.session.execute(stmt, args)
|
||||||
|
|
||||||
|
if results:
|
||||||
|
msg = None
|
||||||
|
for r in results:
|
||||||
|
if str(r.get('project_id')) != str(comparing_project_id):
|
||||||
|
msg = "Domain '{0}' has already been created cert by {1}"\
|
||||||
|
.format(domain_name, r.get('project_id'))
|
||||||
|
LOG.warn(msg)
|
||||||
|
raise ValueError(msg)
|
||||||
|
elif (str(r.get('flavor_id')) == str(comparing_flavor_id)
|
||||||
|
and
|
||||||
|
str(r.get('cert_type')) == str(comparing_cert_type)):
|
||||||
|
msg = "{0} have already created cert of type {1} on {2}"\
|
||||||
|
.format(str(comparing_project_id),
|
||||||
|
comparing_cert_type,
|
||||||
|
domain_name)
|
||||||
|
LOG.warn(msg)
|
||||||
|
raise ValueError(msg)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def create(self, project_id, service_obj):
|
def create(self, project_id, service_obj):
|
||||||
"""create.
|
"""create.
|
||||||
|
|
||||||
|
@ -506,6 +582,29 @@ class ServicesController(base.ServicesController):
|
||||||
consistency_level=self._driver.consistency_level)
|
consistency_level=self._driver.consistency_level)
|
||||||
self.session.execute(stmt, delete_args)
|
self.session.execute(stmt, delete_args)
|
||||||
|
|
||||||
|
def create_cert(self, project_id, cert_obj):
|
||||||
|
|
||||||
|
if not self.cert_already_exist(cert_obj.domain_name,
|
||||||
|
cert_obj.cert_type,
|
||||||
|
cert_obj.flavor_id,
|
||||||
|
project_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'project_id': project_id,
|
||||||
|
'flavor_id': cert_obj.flavor_id,
|
||||||
|
'cert_type': cert_obj.cert_type,
|
||||||
|
'domain_name': cert_obj.domain_name,
|
||||||
|
# when create the cert, cert domain has not been assigned yet
|
||||||
|
# In future we can tweak the logic to assign cert_domain
|
||||||
|
'cert_domain': '',
|
||||||
|
'cert_details': {}
|
||||||
|
}
|
||||||
|
stmt = query.SimpleStatement(
|
||||||
|
CQL_CREATE_CERT,
|
||||||
|
consistency_level=self._driver.consistency_level)
|
||||||
|
self.session.execute(stmt, args)
|
||||||
|
|
||||||
def get_provider_details(self, project_id, service_id):
|
def get_provider_details(self, project_id, service_id):
|
||||||
"""get_provider_details.
|
"""get_provider_details.
|
||||||
|
|
||||||
|
@ -613,6 +712,27 @@ class ServicesController(base.ServicesController):
|
||||||
consistency_level=self._driver.consistency_level)
|
consistency_level=self._driver.consistency_level)
|
||||||
self.session.execute(stmt, args)
|
self.session.execute(stmt, args)
|
||||||
|
|
||||||
|
def update_cert_info(self, domain_name, cert_type, flavor_id,
|
||||||
|
cert_details):
|
||||||
|
"""update_cert_info.
|
||||||
|
|
||||||
|
:param domain_name
|
||||||
|
:param cert_type
|
||||||
|
:param flavor_id
|
||||||
|
:param cert_info
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'domain_name': domain_name,
|
||||||
|
'cert_type': cert_type,
|
||||||
|
'flavor_id': flavor_id,
|
||||||
|
'cert_details': cert_details
|
||||||
|
}
|
||||||
|
stmt = query.SimpleStatement(
|
||||||
|
CQL_UPDATE_CERT_DETAILS,
|
||||||
|
consistency_level=self._driver.consistency_level)
|
||||||
|
self.session.execute(stmt, args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_result(result):
|
def format_result(result):
|
||||||
"""format_result.
|
"""format_result.
|
||||||
|
|
|
@ -123,6 +123,13 @@ class ServicesController(base.ServicesController):
|
||||||
def domain_exists_elsewhere(self, domain_name, service_id):
|
def domain_exists_elsewhere(self, domain_name, service_id):
|
||||||
return domain_name in self.claimed_domains
|
return domain_name in self.claimed_domains
|
||||||
|
|
||||||
|
def update_cert_info(self, domain_name, cert_type, flavor_id,
|
||||||
|
cert_details):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_cert(self, project_id, cert_obj):
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_result(result):
|
def format_result(result):
|
||||||
service_id = result.get('service_id')
|
service_id = result.get('service_id')
|
||||||
|
|
|
@ -21,6 +21,7 @@ from poppy.transport.pecan.controllers.v1 import health
|
||||||
from poppy.transport.pecan.controllers.v1 import home
|
from poppy.transport.pecan.controllers.v1 import home
|
||||||
from poppy.transport.pecan.controllers.v1 import ping
|
from poppy.transport.pecan.controllers.v1 import ping
|
||||||
from poppy.transport.pecan.controllers.v1 import services
|
from poppy.transport.pecan.controllers.v1 import services
|
||||||
|
from poppy.transport.pecan.controllers.v1 import ssl_certificates
|
||||||
|
|
||||||
|
|
||||||
# Hoist into package namespace
|
# Hoist into package namespace
|
||||||
|
@ -33,3 +34,4 @@ DNSHealth = health.DNSHealthController
|
||||||
StorageHealth = health.StorageHealthController
|
StorageHealth = health.StorageHealthController
|
||||||
ProviderHealth = health.ProviderHealthController
|
ProviderHealth = health.ProviderHealthController
|
||||||
Admin = admin.AdminController
|
Admin = admin.AdminController
|
||||||
|
SSLCertificate = ssl_certificates.SSLCertificateController
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Copyright (c) 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 json
|
||||||
|
|
||||||
|
import pecan
|
||||||
|
from pecan import hooks
|
||||||
|
|
||||||
|
from poppy.transport.pecan.controllers import base
|
||||||
|
from poppy.transport.pecan import hooks as poppy_hooks
|
||||||
|
from poppy.transport.pecan.models.request import ssl_certificate
|
||||||
|
from poppy.transport.validators import helpers
|
||||||
|
from poppy.transport.validators.schemas import ssl_certificate\
|
||||||
|
as ssl_certificate_validation
|
||||||
|
from poppy.transport.validators.stoplight import decorators
|
||||||
|
from poppy.transport.validators.stoplight import helpers as stoplight_helpers
|
||||||
|
from poppy.transport.validators.stoplight import rule
|
||||||
|
|
||||||
|
|
||||||
|
class SSLCertificateController(base.Controller, hooks.HookController):
|
||||||
|
|
||||||
|
__hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()]
|
||||||
|
|
||||||
|
@pecan.expose('json')
|
||||||
|
@decorators.validate(
|
||||||
|
request=rule.Rule(
|
||||||
|
helpers.json_matches_service_schema(
|
||||||
|
ssl_certificate_validation.SSLCertificateSchema.get_schema(
|
||||||
|
"ssl_certificate",
|
||||||
|
"POST")),
|
||||||
|
helpers.abort_with_message,
|
||||||
|
stoplight_helpers.pecan_getter))
|
||||||
|
def post(self):
|
||||||
|
ssl_certificate_controller = (
|
||||||
|
self._driver.manager.ssl_certificate_controller)
|
||||||
|
|
||||||
|
certificate_info_dict = json.loads(pecan.request.body.decode('utf-8'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
cert_obj = ssl_certificate.load_from_json(certificate_info_dict)
|
||||||
|
ssl_certificate_controller.create_ssl_certificate(self.project_id,
|
||||||
|
cert_obj)
|
||||||
|
except LookupError as e:
|
||||||
|
pecan.abort(400, detail='Provisioning ssl certificate failed. '
|
||||||
|
'Reason: %s' % str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
pecan.abort(400, detail='Provisioning ssl certificate failed. '
|
||||||
|
'Reason: %s' % str(e))
|
||||||
|
|
||||||
|
return pecan.Response(None, 202)
|
|
@ -63,6 +63,8 @@ class PecanTransportDriver(transport.Driver):
|
||||||
home_controller.add_controller('services', v1.Services(self))
|
home_controller.add_controller('services', v1.Services(self))
|
||||||
home_controller.add_controller('flavors', v1.Flavors(self))
|
home_controller.add_controller('flavors', v1.Flavors(self))
|
||||||
home_controller.add_controller('admin', v1.Admin(self))
|
home_controller.add_controller('admin', v1.Admin(self))
|
||||||
|
home_controller.add_controller('ssl_certificate',
|
||||||
|
v1.SSLCertificate(self))
|
||||||
|
|
||||||
self._app = pecan.make_app(root_controller,
|
self._app = pecan.make_app(root_controller,
|
||||||
guess_content_type_from_ext=False)
|
guess_content_type_from_ext=False)
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright (c) 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 poppy.model import ssl_certificate
|
||||||
|
|
||||||
|
|
||||||
|
def load_from_json(json_data):
|
||||||
|
flavor_id = json_data.get("flavor_id")
|
||||||
|
domain_name = json_data.get("domain_name")
|
||||||
|
cert_type = json_data.get("cert_type")
|
||||||
|
|
||||||
|
return ssl_certificate.SSLCertificate(flavor_id, domain_name, cert_type)
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Copyright (c) 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.
|
||||||
|
try:
|
||||||
|
import ordereddict as collections
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
import collections # pragma: no cover
|
||||||
|
|
||||||
|
from poppy.common import util
|
||||||
|
|
||||||
|
|
||||||
|
class Model(collections.OrderedDict):
|
||||||
|
|
||||||
|
'response class for SSLCertificate'
|
||||||
|
|
||||||
|
def __init__(self, ssl_certificate):
|
||||||
|
super(Model, self).__init__()
|
||||||
|
self["flavor_id"] = ssl_certificate.flavor_id
|
||||||
|
self['domain_name'] = util.help_escape(ssl_certificate.domain_name)
|
||||||
|
self['cert_type'] = ssl_certificate.cert_type
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright (c) 2015 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 poppy.transport.validators import schema_base
|
||||||
|
|
||||||
|
|
||||||
|
class SSLCertificateSchema(schema_base.SchemaBase):
|
||||||
|
|
||||||
|
'''JSON Schmema validation for /ssl_certificate.'''
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
'ssl_certificate': {
|
||||||
|
'POST': {
|
||||||
|
'type': 'object',
|
||||||
|
'additionalProperties': False,
|
||||||
|
'properties': {
|
||||||
|
'flavor_id': {
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'minLength': 1,
|
||||||
|
'maxLength': 256
|
||||||
|
},
|
||||||
|
'cert_type': {
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'enum': ['san'],
|
||||||
|
},
|
||||||
|
'domain_name': {
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'minLength': 3,
|
||||||
|
'maxLength': 253
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Copyright (c) 2015 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 oslo_config import cfg
|
||||||
|
|
||||||
|
from poppy.provider.akamai.san_info_storage import zookeeper_storage
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_cli_opts(zookeeper_storage.AKAMAI_OPTIONS,
|
||||||
|
group=zookeeper_storage.AKAMAI_GROUP)
|
||||||
|
CONF(prog='akamai-config')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
zk_storage = zookeeper_storage.ZookeeperSanInfoStorage(CONF)
|
||||||
|
|
||||||
|
all_san_cert_names = zk_storage.list_all_san_cert_names()
|
||||||
|
|
||||||
|
if not all_san_cert_names:
|
||||||
|
print ("Currently no SAN cert info has been intialized")
|
||||||
|
|
||||||
|
for san_cert_name in all_san_cert_names:
|
||||||
|
print("%s:%s" % (san_cert_name,
|
||||||
|
str(zk_storage.get_cert_info(san_cert_name))))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Copyright (c) 2015 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 oslo_config import cfg
|
||||||
|
|
||||||
|
from poppy.provider.akamai.san_info_storage import zookeeper_storage
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_cli_opts(zookeeper_storage.AKAMAI_OPTIONS,
|
||||||
|
group=zookeeper_storage.AKAMAI_GROUP)
|
||||||
|
CONF.register_cli_opt(
|
||||||
|
cfg.ListOpt('san_cert_cnames',
|
||||||
|
help='A list of san certs cnamehost names'),
|
||||||
|
group=zookeeper_storage.AKAMAI_GROUP)
|
||||||
|
CONF(prog='akamai-config')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
zk_storage = zookeeper_storage.ZookeeperSanInfoStorage(CONF)
|
||||||
|
|
||||||
|
san_attribute_default_list = {
|
||||||
|
'issuer': 'symentec',
|
||||||
|
'ipVersion': 'ipv4',
|
||||||
|
'slot_deployment_klass': 'esslType',
|
||||||
|
'jobId': None}
|
||||||
|
for san_cert_name in CONF[zookeeper_storage.AKAMAI_GROUP].san_cert_cnames:
|
||||||
|
print("Upsert SAN info for :%s" % (san_cert_name))
|
||||||
|
for attr in san_attribute_default_list:
|
||||||
|
user_input = None
|
||||||
|
while ((user_input or "").strip() or user_input) in ["", None]:
|
||||||
|
user_input = raw_input('Please input value for attr: %s, '
|
||||||
|
'San cert: %s,'
|
||||||
|
'default value: %s'
|
||||||
|
' (if default is None, '
|
||||||
|
'that means a real value has to'
|
||||||
|
' be input): ' %
|
||||||
|
(attr,
|
||||||
|
san_cert_name,
|
||||||
|
san_attribute_default_list[attr]))
|
||||||
|
if san_attribute_default_list[attr] is None:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
user_input = san_attribute_default_list[attr]
|
||||||
|
break
|
||||||
|
zk_storage._save_cert_property_value(san_cert_name, attr,
|
||||||
|
user_input)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
'''example usage:
|
||||||
|
python upsert_san_cert_info.py '
|
||||||
|
'--drivers:provider:akamai-storage_backend_type zookeeper'
|
||||||
|
'--drivers:provider:akamai-storage_backend_host 192.168.59.103'
|
||||||
|
'--drivers:provider:akamai-san_cert_cnames'
|
||||||
|
secure1.san1.altcdn.com,secure2.san1.altcdn.com'''
|
||||||
|
main()
|
|
@ -64,6 +64,7 @@ poppy.notification =
|
||||||
mailgun = poppy.notification.mailgun:Driver
|
mailgun = poppy.notification.mailgun:Driver
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[wheel]
|
[wheel]
|
||||||
universal = 1
|
universal = 1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"mod_san_test_1": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"missing_cert_type": {
|
||||||
|
"domain_name": "www.abc.com"
|
||||||
|
},
|
||||||
|
"invalid_cert_type": {
|
||||||
|
"cert_type": "not_a_valid_cert_type",
|
||||||
|
"domain_name": "www.abc.com"
|
||||||
|
},
|
||||||
|
"missing_domain_name": {
|
||||||
|
"cert_type": "san"
|
||||||
|
},
|
||||||
|
"missing_flavor_id": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"missing_flavor_id": true
|
||||||
|
},
|
||||||
|
"invalid_flavor_id": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"flavor_id": "not_a_valid_flavor_id"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Copyright (c) 2015 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 ddt
|
||||||
|
|
||||||
|
from tests.api import base
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class TestCreateSSLCertificate(base.TestBase):
|
||||||
|
|
||||||
|
"""Tests for Create SSL Certificate."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCreateSSLCertificate, self).setUp()
|
||||||
|
self.flavor_id = self.test_flavor
|
||||||
|
|
||||||
|
@ddt.file_data('data_create_ssl_certificate_negative.json')
|
||||||
|
def test_create_ssl_certificate_negative(self, test_data):
|
||||||
|
cert_type = test_data.get('cert_type')
|
||||||
|
domain_name = test_data.get('domain_name')
|
||||||
|
flavor_id = test_data.get('flavor_id') or self.flavor_id
|
||||||
|
|
||||||
|
if test_data.get("missing_flavor_id", False):
|
||||||
|
flavor_id = None
|
||||||
|
|
||||||
|
resp = self.client.create_ssl_certificate(
|
||||||
|
cert_type=cert_type,
|
||||||
|
domain_name=domain_name,
|
||||||
|
flavor_id=flavor_id
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 400)
|
||||||
|
|
||||||
|
@ddt.file_data('data_create_ssl_certificate.json')
|
||||||
|
def test_create_ssl_certificate_positive(self, test_data):
|
||||||
|
if self.test_config.run_ssl_tests is False:
|
||||||
|
self.skipTest('Create ssl certificate needs to'
|
||||||
|
' be run when commanded')
|
||||||
|
|
||||||
|
cert_type = test_data.get('cert_type')
|
||||||
|
domain_name = test_data.get('domain_name')
|
||||||
|
flavor_id = test_data.get('flavor_id') or self.flavor_id
|
||||||
|
|
||||||
|
resp = self.client.create_ssl_certificate(
|
||||||
|
cert_type=cert_type,
|
||||||
|
domain_name=domain_name,
|
||||||
|
flavor_id=flavor_id
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
|
@ -404,3 +404,24 @@ class PoppyClient(client.AutoMarshallingHTTPClient):
|
||||||
assert False, ('Timed out waiting for service '
|
assert False, ('Timed out waiting for service '
|
||||||
'to be deleted, after '
|
'to be deleted, after '
|
||||||
'waiting {0} seconds'.format(retry_timeout))
|
'waiting {0} seconds'.format(retry_timeout))
|
||||||
|
|
||||||
|
def create_ssl_certificate(self, cert_type=None,
|
||||||
|
domain_name=None, flavor_id=None,
|
||||||
|
requestslib_kwargs=None,):
|
||||||
|
"""Creates SSL Certificate
|
||||||
|
|
||||||
|
:return: Response Object containing response code 200 and body with
|
||||||
|
details of service
|
||||||
|
POST
|
||||||
|
ssl_certificate
|
||||||
|
"""
|
||||||
|
url = '{0}/ssl_certificate'.format(self.url)
|
||||||
|
|
||||||
|
requests_object = requests.CreateSSLCertificate(
|
||||||
|
cert_type=cert_type,
|
||||||
|
domain_name=domain_name,
|
||||||
|
flavor_id=flavor_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.request('POST', url, request_entity=requests_object,
|
||||||
|
requestslib_kwargs=requestslib_kwargs)
|
||||||
|
|
|
@ -109,3 +109,21 @@ class CreateFlavor(base.AutoMarshallingModel):
|
||||||
"providers": self.provider_list,
|
"providers": self.provider_list,
|
||||||
"limits": self.limits}
|
"limits": self.limits}
|
||||||
return json.dumps(create_flavor_request)
|
return json.dumps(create_flavor_request)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSSLCertificate(base.AutoMarshallingModel):
|
||||||
|
"""Marshalling for Create Flavor requests."""
|
||||||
|
|
||||||
|
def __init__(self, cert_type=None, domain_name=None, flavor_id=None):
|
||||||
|
super(CreateSSLCertificate, self).__init__()
|
||||||
|
|
||||||
|
self.cert_type = cert_type
|
||||||
|
self.domain_name = domain_name
|
||||||
|
self.flavor_id = flavor_id
|
||||||
|
|
||||||
|
def _obj_to_json(self):
|
||||||
|
create_ssl_certificate_request = {
|
||||||
|
"cert_type": self.cert_type,
|
||||||
|
"domain_name": self.domain_name,
|
||||||
|
"flavor_id": self.flavor_id}
|
||||||
|
return json.dumps(create_ssl_certificate_request)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"all_fields": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"flavor_id": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"missing_domain_name": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"flavor_id": "mock"
|
||||||
|
},
|
||||||
|
"missing_cert_type": {
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"flavor_id": "mock"
|
||||||
|
},
|
||||||
|
"non_existing_flavor_input": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"flavor_id": "non_exist"
|
||||||
|
},
|
||||||
|
"missing_flavor_id": {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
# Copyright (c) 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 json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
|
||||||
|
from tests.functional.transport.pecan import base
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class SSLCertificateControllerTest(base.FunctionalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SSLCertificateControllerTest, self).setUp()
|
||||||
|
|
||||||
|
self.project_id = str(uuid.uuid1())
|
||||||
|
self.service_name = str(uuid.uuid1())
|
||||||
|
self.flavor_id = str(uuid.uuid1())
|
||||||
|
|
||||||
|
# create a mock flavor to be used by new service creations
|
||||||
|
flavor_json = {
|
||||||
|
"id": self.flavor_id,
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"provider": "mock",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://mock.cdn",
|
||||||
|
"rel": "provider_url"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
response = self.app.post('/v1.0/flavors',
|
||||||
|
params=json.dumps(flavor_json),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Project-ID": self.project_id})
|
||||||
|
|
||||||
|
self.assertEqual(201, response.status_code)
|
||||||
|
|
||||||
|
@ddt.file_data("data_create_ssl_certificate.json")
|
||||||
|
def test_create_ssl_certificate(self, ssl_certificate_json):
|
||||||
|
|
||||||
|
# override the hardcoded flavor_id in the ddt file with
|
||||||
|
# a custom one defined in setUp()
|
||||||
|
ssl_certificate_json['flavor_id'] = self.flavor_id
|
||||||
|
|
||||||
|
# create with good data
|
||||||
|
response = self.app.post('/v1.0/ssl_certificate',
|
||||||
|
params=json.dumps(ssl_certificate_json),
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Project-ID': self.project_id})
|
||||||
|
self.assertEqual(202, response.status_code)
|
||||||
|
|
||||||
|
def test_create_with_invalid_json(self):
|
||||||
|
# create with errorenous data: invalid json data
|
||||||
|
response = self.app.post('/v1.0/ssl_certificate',
|
||||||
|
params="{",
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Project-ID': self.project_id},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(400, response.status_code)
|
||||||
|
|
||||||
|
@ddt.file_data("data_create_ssl_certificate_bad_input_json.json")
|
||||||
|
def test_create_with_bad_input_json(self, ssl_certificate_json):
|
||||||
|
# create with errorenous data
|
||||||
|
response = self.app.post('/v1.0/ssl_certificate',
|
||||||
|
params=json.dumps(ssl_certificate_json),
|
||||||
|
headers={'Content-Type': 'application/json',
|
||||||
|
'X-Project-ID': self.project_id},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(400, response.status_code)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(SSLCertificateControllerTest, self).tearDown()
|
|
@ -19,6 +19,7 @@ import mock
|
||||||
from taskflow import engines
|
from taskflow import engines
|
||||||
|
|
||||||
from poppy.distributed_task.taskflow.flow import create_service
|
from poppy.distributed_task.taskflow.flow import create_service
|
||||||
|
from poppy.distributed_task.taskflow.flow import create_ssl_certificate
|
||||||
from poppy.distributed_task.taskflow.flow import delete_service
|
from poppy.distributed_task.taskflow.flow import delete_service
|
||||||
from poppy.distributed_task.taskflow.flow import purge_service
|
from poppy.distributed_task.taskflow.flow import purge_service
|
||||||
from poppy.distributed_task.taskflow.flow import update_service
|
from poppy.distributed_task.taskflow.flow import update_service
|
||||||
|
@ -28,6 +29,7 @@ from poppy.distributed_task.utils import memoized_controllers
|
||||||
from poppy.model.helpers import domain
|
from poppy.model.helpers import domain
|
||||||
from poppy.model.helpers import origin
|
from poppy.model.helpers import origin
|
||||||
from poppy.model import service
|
from poppy.model import service
|
||||||
|
from poppy.model import ssl_certificate
|
||||||
from tests.unit import base
|
from tests.unit import base
|
||||||
from tests.unit.manager.default.test_services import MonkeyPatchControllers
|
from tests.unit.manager.default.test_services import MonkeyPatchControllers
|
||||||
|
|
||||||
|
@ -142,8 +144,24 @@ class TestFlowRuns(base.TestCase):
|
||||||
dns_controller.disable = mock.Mock()
|
dns_controller.disable = mock.Mock()
|
||||||
dns_controller.disable._mock_return_value = []
|
dns_controller.disable._mock_return_value = []
|
||||||
|
|
||||||
|
def patch_create_ssl_certificate_flow(self, service_controller,
|
||||||
|
storage_controller, dns_controller):
|
||||||
|
storage_controller.get = mock.Mock()
|
||||||
|
storage_controller.update = mock.Mock()
|
||||||
|
storage_controller._driver.close_connection = mock.Mock()
|
||||||
|
service_controller.provider_wrapper.create_certificate = mock.Mock()
|
||||||
|
service_controller.provider_wrapper.create_certificate.\
|
||||||
|
_mock_return_value = []
|
||||||
|
service_controller._driver = mock.Mock()
|
||||||
|
service_controller._driver.providers.__getitem__ = mock.Mock()
|
||||||
|
service_controller._driver.notification = [mock.Mock()]
|
||||||
|
dns_controller.create = mock.Mock()
|
||||||
|
dns_controller.create._mock_return_value = []
|
||||||
|
common.create_log_delivery_container = mock.Mock()
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_create_flow_normal(self, mock_creds):
|
def test_create_flow_normal(self, mock_creds, mock_dns_client):
|
||||||
providers = ['cdn_provider']
|
providers = ['cdn_provider']
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'providers_list_json': json.dumps(providers),
|
'providers_list_json': json.dumps(providers),
|
||||||
|
@ -167,8 +185,9 @@ class TestFlowRuns(base.TestCase):
|
||||||
dns_controller)
|
dns_controller)
|
||||||
engines.run(create_service.create_service(), store=kwargs)
|
engines.run(create_service.create_service(), store=kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_update_flow_normal(self, mock_creds):
|
def test_update_flow_normal(self, mock_creds, mock_dns_client):
|
||||||
service_id = str(uuid.uuid4())
|
service_id = str(uuid.uuid4())
|
||||||
domains_old = domain.Domain(domain='cdn.poppy.org')
|
domains_old = domain.Domain(domain='cdn.poppy.org')
|
||||||
domains_new = domain.Domain(domain='mycdn.poppy.org')
|
domains_new = domain.Domain(domain='mycdn.poppy.org')
|
||||||
|
@ -206,8 +225,9 @@ class TestFlowRuns(base.TestCase):
|
||||||
dns_controller)
|
dns_controller)
|
||||||
engines.run(update_service.update_service(), store=kwargs)
|
engines.run(update_service.update_service(), store=kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_delete_flow_normal(self, mock_creds):
|
def test_delete_flow_normal(self, mock_creds, mock_dns_client):
|
||||||
service_id = str(uuid.uuid4())
|
service_id = str(uuid.uuid4())
|
||||||
domains_old = domain.Domain(domain='cdn.poppy.org')
|
domains_old = domain.Domain(domain='cdn.poppy.org')
|
||||||
current_origin = origin.Origin(origin='poppy.org')
|
current_origin = origin.Origin(origin='poppy.org')
|
||||||
|
@ -239,8 +259,9 @@ class TestFlowRuns(base.TestCase):
|
||||||
dns_controller)
|
dns_controller)
|
||||||
engines.run(delete_service.delete_service(), store=kwargs)
|
engines.run(delete_service.delete_service(), store=kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_purge_flow_normal(self, mock_creds):
|
def test_purge_flow_normal(self, mock_creds, mock_dns_client):
|
||||||
service_id = str(uuid.uuid4())
|
service_id = str(uuid.uuid4())
|
||||||
domains_old = domain.Domain(domain='cdn.poppy.org')
|
domains_old = domain.Domain(domain='cdn.poppy.org')
|
||||||
current_origin = origin.Origin(origin='poppy.org')
|
current_origin = origin.Origin(origin='poppy.org')
|
||||||
|
@ -273,8 +294,10 @@ class TestFlowRuns(base.TestCase):
|
||||||
dns_controller)
|
dns_controller)
|
||||||
engines.run(purge_service.purge_service(), store=kwargs)
|
engines.run(purge_service.purge_service(), store=kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_service_state_flow_normal(self, mock_creds):
|
def test_service_state_flow_normal(self, mock_creds,
|
||||||
|
mock_dns_client):
|
||||||
service_id = str(uuid.uuid4())
|
service_id = str(uuid.uuid4())
|
||||||
domains_old = domain.Domain(domain='cdn.poppy.org')
|
domains_old = domain.Domain(domain='cdn.poppy.org')
|
||||||
current_origin = origin.Origin(origin='poppy.org')
|
current_origin = origin.Origin(origin='poppy.org')
|
||||||
|
@ -311,8 +334,10 @@ class TestFlowRuns(base.TestCase):
|
||||||
engines.run(update_service_state.disable_service(),
|
engines.run(update_service_state.disable_service(),
|
||||||
store=disable_kwargs)
|
store=disable_kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_create_flow_dns_exception(self, mock_creds):
|
def test_create_flow_dns_exception(self, mock_creds,
|
||||||
|
mock_dns_client):
|
||||||
providers = ['cdn_provider']
|
providers = ['cdn_provider']
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'providers_list_json': json.dumps(providers),
|
'providers_list_json': json.dumps(providers),
|
||||||
|
@ -343,8 +368,10 @@ class TestFlowRuns(base.TestCase):
|
||||||
}
|
}
|
||||||
engines.run(create_service.create_service(), store=kwargs)
|
engines.run(create_service.create_service(), store=kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_update_flow_dns_exception(self, mock_creds):
|
def test_update_flow_dns_exception(self, mock_creds,
|
||||||
|
mock_dns_client):
|
||||||
service_id = str(uuid.uuid4())
|
service_id = str(uuid.uuid4())
|
||||||
domains_old = domain.Domain(domain='cdn.poppy.org')
|
domains_old = domain.Domain(domain='cdn.poppy.org')
|
||||||
domains_new = domain.Domain(domain='mycdn.poppy.org')
|
domains_new = domain.Domain(domain='mycdn.poppy.org')
|
||||||
|
@ -391,8 +418,10 @@ class TestFlowRuns(base.TestCase):
|
||||||
|
|
||||||
engines.run(update_service.update_service(), store=kwargs)
|
engines.run(update_service.update_service(), store=kwargs)
|
||||||
|
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
@mock.patch('pyrax.set_credentials')
|
@mock.patch('pyrax.set_credentials')
|
||||||
def test_delete_flow_dns_exception(self, mock_creds):
|
def test_delete_flow_dns_exception(self, mock_creds,
|
||||||
|
mock_dns_client):
|
||||||
service_id = str(uuid.uuid4())
|
service_id = str(uuid.uuid4())
|
||||||
domains_old = domain.Domain(domain='cdn.poppy.org')
|
domains_old = domain.Domain(domain='cdn.poppy.org')
|
||||||
current_origin = origin.Origin(origin='poppy.org')
|
current_origin = origin.Origin(origin='poppy.org')
|
||||||
|
@ -818,3 +847,31 @@ class TestFlowRuns(base.TestCase):
|
||||||
store=enable_kwargs)
|
store=enable_kwargs)
|
||||||
engines.run(update_service_state.disable_service(),
|
engines.run(update_service_state.disable_service(),
|
||||||
store=disable_kwargs)
|
store=disable_kwargs)
|
||||||
|
|
||||||
|
# Keep create credentials for now
|
||||||
|
@mock.patch('pyrax.cloud_dns')
|
||||||
|
@mock.patch('pyrax.set_credentials')
|
||||||
|
def test_create_ssl_certificate_normal(self, mock_creds, mock_dns_client):
|
||||||
|
providers = ['cdn_provider']
|
||||||
|
cert_obj_json = ssl_certificate.SSLCertificate('cdn',
|
||||||
|
'mytestsite.com',
|
||||||
|
'san')
|
||||||
|
kwargs = {
|
||||||
|
'providers_list_json': json.dumps(providers),
|
||||||
|
'project_id': json.dumps(str(uuid.uuid4())),
|
||||||
|
'cert_obj_json': json.dumps(cert_obj_json.to_dict()),
|
||||||
|
}
|
||||||
|
|
||||||
|
service_controller, storage_controller, dns_controller = \
|
||||||
|
self.all_controllers()
|
||||||
|
|
||||||
|
with MonkeyPatchControllers(service_controller,
|
||||||
|
dns_controller,
|
||||||
|
storage_controller,
|
||||||
|
memoized_controllers.task_controllers):
|
||||||
|
|
||||||
|
self.patch_create_ssl_certificate_flow(service_controller,
|
||||||
|
storage_controller,
|
||||||
|
dns_controller)
|
||||||
|
engines.run(create_ssl_certificate.create_ssl_certificate(),
|
||||||
|
store=kwargs)
|
||||||
|
|
|
@ -31,5 +31,6 @@ class TestProviderWrapper(base.TestCase):
|
||||||
self.notifications_wrapper_obj.send(mock_ext,
|
self.notifications_wrapper_obj.send(mock_ext,
|
||||||
"test_subject",
|
"test_subject",
|
||||||
"test_mail_content")
|
"test_mail_content")
|
||||||
|
|
||||||
mock_ext.obj.services_controller.send.assert_called_once_with(
|
mock_ext.obj.services_controller.send.assert_called_once_with(
|
||||||
"test_subject", "test_mail_content")
|
"test_subject", "test_mail_content")
|
||||||
|
|
|
@ -34,7 +34,10 @@ MAIL_NOTIFICATION_OPTIONS = [
|
||||||
cfg.StrOpt('from_address', default='noreply@poppycdn.org',
|
cfg.StrOpt('from_address', default='noreply@poppycdn.org',
|
||||||
help='Sent from email address'),
|
help='Sent from email address'),
|
||||||
cfg.ListOpt('recipients', default=['recipient@gmail.com'],
|
cfg.ListOpt('recipients', default=['recipient@gmail.com'],
|
||||||
help='A list of emails addresses to receive notification ')
|
help='A list of emails addresses to receive notification '),
|
||||||
|
cfg.StrOpt('notification_subject',
|
||||||
|
default='Poppy SSL Certificate Provisioned',
|
||||||
|
help='The subject of the email notification ')
|
||||||
]
|
]
|
||||||
|
|
||||||
MAIL_NOTIFICATION_GROUP = 'drivers:notification:mailgun'
|
MAIL_NOTIFICATION_GROUP = 'drivers:notification:mailgun'
|
||||||
|
|
|
@ -35,7 +35,10 @@ MAIL_NOTIFICATION_OPTIONS = [
|
||||||
cfg.StrOpt('from_address', default='noreply@poppycdn.org',
|
cfg.StrOpt('from_address', default='noreply@poppycdn.org',
|
||||||
help='Sent from email address'),
|
help='Sent from email address'),
|
||||||
cfg.ListOpt('recipients', default=['recipient@gmail.com'],
|
cfg.ListOpt('recipients', default=['recipient@gmail.com'],
|
||||||
help='A list of emails addresses to receive notification ')
|
help='A list of emails addresses to receive notification '),
|
||||||
|
cfg.StrOpt('notification_subject',
|
||||||
|
default='Poppy SSL Certificate Provisioned',
|
||||||
|
help='The subject of the email notification ')
|
||||||
]
|
]
|
||||||
|
|
||||||
MAIL_NOTIFICATION_GROUP = 'drivers:notification:mail'
|
MAIL_NOTIFICATION_GROUP = 'drivers:notification:mail'
|
||||||
|
|
|
@ -84,6 +84,17 @@ AKAMAI_OPTIONS = [
|
||||||
cfg.IntOpt('san_cert_hostname_limit', default=80,
|
cfg.IntOpt('san_cert_hostname_limit', default=80,
|
||||||
help='default limit on how many hostnames can'
|
help='default limit on how many hostnames can'
|
||||||
' be held by a SAN cert'),
|
' be held by a SAN cert'),
|
||||||
|
|
||||||
|
# related info for SPS && PAPI APIs
|
||||||
|
cfg.StrOpt(
|
||||||
|
'contract_id',
|
||||||
|
help='Operator contractID'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'group_id',
|
||||||
|
help='Operator groupID'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'property_id',
|
||||||
|
help='Operator propertyID')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,6 +126,12 @@ class TestDriver(base.TestCase):
|
||||||
|
|
||||||
self.conf = cfg.ConfigOpts()
|
self.conf = cfg.ConfigOpts()
|
||||||
|
|
||||||
|
zookeeper_client_patcher = mock.patch(
|
||||||
|
'kazoo.client.KazooClient'
|
||||||
|
)
|
||||||
|
zookeeper_client_patcher.start()
|
||||||
|
self.addCleanup(zookeeper_client_patcher.stop)
|
||||||
|
|
||||||
@mock.patch('akamai.edgegrid.EdgeGridAuth')
|
@mock.patch('akamai.edgegrid.EdgeGridAuth')
|
||||||
@mock.patch.object(driver, 'AKAMAI_OPTIONS', new=AKAMAI_OPTIONS)
|
@mock.patch.object(driver, 'AKAMAI_OPTIONS', new=AKAMAI_OPTIONS)
|
||||||
def test_init(self, mock_connect):
|
def test_init(self, mock_connect):
|
||||||
|
@ -158,3 +175,10 @@ class TestDriver(base.TestCase):
|
||||||
provider = driver.CDNProvider(self.conf)
|
provider = driver.CDNProvider(self.conf)
|
||||||
self.assertNotEqual(None, provider.policy_api_client)
|
self.assertNotEqual(None, provider.policy_api_client)
|
||||||
self.assertNotEqual(None, provider.ccu_api_client)
|
self.assertNotEqual(None, provider.ccu_api_client)
|
||||||
|
|
||||||
|
@mock.patch('akamai.edgegrid.EdgeGridAuth')
|
||||||
|
@mock.patch.object(driver, 'AKAMAI_OPTIONS', new=AKAMAI_OPTIONS)
|
||||||
|
def test_san_info_storage(self, mock_connect):
|
||||||
|
mock_connect.return_value = mock.Mock()
|
||||||
|
provider = driver.CDNProvider(self.conf)
|
||||||
|
self.assertNotEqual(None, provider.san_info_storage)
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Copyright (c) 2015 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 mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from poppy.provider.akamai.mod_san_queue import zookeeper_queue
|
||||||
|
from tests.unit import base
|
||||||
|
|
||||||
|
|
||||||
|
AKAMAI_OPTIONS = [
|
||||||
|
# queue backend configs
|
||||||
|
cfg.StrOpt(
|
||||||
|
'queue_backend_type',
|
||||||
|
help='SAN Cert Queueing backend'),
|
||||||
|
cfg.ListOpt('queue_backend_host', default=['localhost'],
|
||||||
|
help='default queue backend server hosts'),
|
||||||
|
cfg.IntOpt('queue_backend_port', default=2181, help='default'
|
||||||
|
' default queue backend server port (e.g: 2181)'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'mod_san_queue_path', default='/mod_san_queue', help='Zookeeper path '
|
||||||
|
'for mod_san_queue'),
|
||||||
|
]
|
||||||
|
|
||||||
|
AKAMAI_GROUP = 'drivers:provider:akamai'
|
||||||
|
|
||||||
|
|
||||||
|
class TestModSanQueue(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestModSanQueue, self).setUp()
|
||||||
|
self.cert_obj_json = {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"flavor_id": "premium"
|
||||||
|
}
|
||||||
|
zookeeper_client_patcher = mock.patch(
|
||||||
|
'kazoo.client.KazooClient'
|
||||||
|
)
|
||||||
|
zookeeper_client_patcher.start()
|
||||||
|
self.addCleanup(zookeeper_client_patcher.stop)
|
||||||
|
|
||||||
|
self.conf = cfg.ConfigOpts()
|
||||||
|
self.zk_queue = zookeeper_queue.ZookeeperModSanQueue(self.conf)
|
||||||
|
|
||||||
|
self.zk_queue.mod_san_queue_backend = mock.Mock()
|
||||||
|
|
||||||
|
def test_enqueue_mod_san_request(self):
|
||||||
|
self.zk_queue.enqueue_mod_san_request(self.cert_obj_json)
|
||||||
|
self.zk_queue.mod_san_queue_backend.put.assert_called_once_with(
|
||||||
|
self.cert_obj_json)
|
||||||
|
|
||||||
|
def test_dequeue_mod_san_request(self):
|
||||||
|
self.zk_queue.dequeue_mod_san_request()
|
||||||
|
self.zk_queue.dequeue_mod_san_request(False)
|
||||||
|
|
||||||
|
calls = [mock.call(), mock.call()]
|
||||||
|
self.zk_queue.mod_san_queue_backend.get.assert_has_calls(calls)
|
||||||
|
self.zk_queue.mod_san_queue_backend.consume.assert_called_once_with()
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Copyright (c) 2015 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 mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from poppy.provider.akamai.san_info_storage import zookeeper_storage
|
||||||
|
from tests.unit import base
|
||||||
|
|
||||||
|
AKAMAI_OPTIONS = [
|
||||||
|
# storage backend configs for long running tasks
|
||||||
|
cfg.StrOpt(
|
||||||
|
'storage_backend_type',
|
||||||
|
help='SAN Cert info storage backend'),
|
||||||
|
cfg.ListOpt('storage_backend_host', default=['localhost'],
|
||||||
|
help='default san info storage backend server hosts'),
|
||||||
|
cfg.IntOpt('storage_backend_port', default=2181, help='default'
|
||||||
|
' default san info storage backend server port (e.g: 2181)'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'san_info_storage_path', default='/san_info', help='zookeeper backend'
|
||||||
|
' path for san cert info'),
|
||||||
|
]
|
||||||
|
|
||||||
|
AKAMAI_GROUP = 'drivers:provider:akamai'
|
||||||
|
|
||||||
|
|
||||||
|
class TestSANInfoStorage(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestSANInfoStorage, self).setUp()
|
||||||
|
zookeeper_client_patcher = mock.patch(
|
||||||
|
'kazoo.client.KazooClient'
|
||||||
|
)
|
||||||
|
zookeeper_client_patcher.start()
|
||||||
|
self.addCleanup(zookeeper_client_patcher.stop)
|
||||||
|
|
||||||
|
self.conf = cfg.ConfigOpts()
|
||||||
|
self.zk_storage = zookeeper_storage.ZookeeperSanInfoStorage(self.conf)
|
||||||
|
|
||||||
|
def zk_get_value_func(zk_path):
|
||||||
|
stat = "good"
|
||||||
|
if 'jobId' in zk_path:
|
||||||
|
return stat, 1789
|
||||||
|
if 'spsId' in zk_path:
|
||||||
|
return stat, 4809
|
||||||
|
if 'issuer' in zk_path:
|
||||||
|
return stat, 'symantec'
|
||||||
|
if 'ipVersion' in zk_path:
|
||||||
|
return stat, 'ipv4'
|
||||||
|
if 'slot_deployment_klass' in zk_path:
|
||||||
|
return stat, 'esslType'
|
||||||
|
return None, None
|
||||||
|
self.zk_storage.zookeeper_client.get.side_effect = zk_get_value_func
|
||||||
|
|
||||||
|
def test__zk_path(self):
|
||||||
|
path1 = self.zk_storage._zk_path('secure.san1.poppycdn.com', 'jobId')
|
||||||
|
self.assertTrue(path1 == '/san_info/secure.san1.poppycdn.com/jobId')
|
||||||
|
|
||||||
|
path2 = self.zk_storage._zk_path('secure.san1.poppycdn.com', None)
|
||||||
|
self.assertTrue(path2 == '/san_info/secure.san1.poppycdn.com')
|
||||||
|
|
||||||
|
def test__save_cert_property_value(self):
|
||||||
|
self.zk_storage._save_cert_property_value('secure.san1.poppycdn.com',
|
||||||
|
'spsId', str(1789))
|
||||||
|
self.zk_storage.zookeeper_client.ensure_path.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/spsId')
|
||||||
|
self.zk_storage.zookeeper_client.set.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/spsId', str(1789))
|
||||||
|
|
||||||
|
def test_save_cert_last_spsid(self):
|
||||||
|
self.zk_storage.save_cert_last_spsid('secure.san1.poppycdn.com', 1789)
|
||||||
|
self.zk_storage.zookeeper_client.ensure_path.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/spsId')
|
||||||
|
self.zk_storage.zookeeper_client.set.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/spsId', str(1789))
|
||||||
|
|
||||||
|
def test_get_cert_last_spsid(self):
|
||||||
|
self.zk_storage.get_cert_last_spsid('secure.san1.poppycdn.com')
|
||||||
|
self.zk_storage.zookeeper_client.ensure_path.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/spsId')
|
||||||
|
self.zk_storage.zookeeper_client.get.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/spsId')
|
||||||
|
|
||||||
|
def list_all_san_cert_names(self):
|
||||||
|
self.zk_storage.list_all_san_cert_names()
|
||||||
|
self.zk_storage.zookeeper_client.get_children.assert_create_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_cert_info(self):
|
||||||
|
res = self.zk_storage.get_cert_info('secure.san1.poppycdn.com')
|
||||||
|
self.zk_storage.zookeeper_client.ensure_path.assert_called_once_with(
|
||||||
|
'/san_info/secure.san1.poppycdn.com'
|
||||||
|
)
|
||||||
|
calls = [mock.call('/san_info/secure.san1.poppycdn.com/jobId'),
|
||||||
|
mock.call('/san_info/secure.san1.poppycdn.com/issuer'),
|
||||||
|
mock.call('/san_info/secure.san1.poppycdn.com/ipVersion'),
|
||||||
|
mock.call(
|
||||||
|
'/san_info/secure.san1.poppycdn.com/slot_deployment_klass')]
|
||||||
|
self.zk_storage.zookeeper_client.get.assert_has_calls(calls)
|
||||||
|
self.assertTrue(isinstance(res, dict))
|
|
@ -28,6 +28,7 @@ from poppy.model.helpers import rule
|
||||||
from poppy.model.service import Service
|
from poppy.model.service import Service
|
||||||
from poppy.provider.akamai import services
|
from poppy.provider.akamai import services
|
||||||
from poppy.transport.pecan.models.request import service
|
from poppy.transport.pecan.models.request import service
|
||||||
|
from poppy.transport.pecan.models.request import ssl_certificate
|
||||||
from tests.unit import base
|
from tests.unit import base
|
||||||
|
|
||||||
|
|
||||||
|
@ -447,3 +448,75 @@ class TestServices(base.TestCase):
|
||||||
break
|
break
|
||||||
|
|
||||||
self.assertTrue(restriction_rule_valid)
|
self.assertTrue(restriction_rule_valid)
|
||||||
|
|
||||||
|
def test_create_ssl_certificate_happy_path(self):
|
||||||
|
controller = services.ServiceController(self.driver)
|
||||||
|
data = {
|
||||||
|
"cert_type": "san",
|
||||||
|
"domain_name": "www.abc.com",
|
||||||
|
"flavor_id": "premium"
|
||||||
|
}
|
||||||
|
controller.san_cert_cnames = ["secure.san1.poppycdn.com",
|
||||||
|
"secure.san2.poppycdn.com"]
|
||||||
|
|
||||||
|
lastSpsId = (
|
||||||
|
controller.san_info_storage.get_cert_last_spsid(
|
||||||
|
"secure.san1.poppycdn.com"))
|
||||||
|
|
||||||
|
controller.san_info_storage.get_cert_info.return_value = {
|
||||||
|
'cnameHostname': "secure.san1.poppycdn.com",
|
||||||
|
'jobId': "secure.san1.poppycdn.com",
|
||||||
|
'issuer': 1789,
|
||||||
|
'createType': 'modSan',
|
||||||
|
'ipVersion': 'ipv4',
|
||||||
|
'slot-deployment.class': 'esslType'
|
||||||
|
}
|
||||||
|
|
||||||
|
cert_info = controller.san_info_storage.get_cert_info(
|
||||||
|
"secure.san1.poppycdn.com")
|
||||||
|
cert_info['add.sans'] = "www.abc.com"
|
||||||
|
string_post_cert_info = '&'.join(
|
||||||
|
['%s=%s' % (k, v) for (k, v) in cert_info.items()])
|
||||||
|
|
||||||
|
controller.sps_api_client.get.return_value = mock.Mock(
|
||||||
|
status_code=200,
|
||||||
|
# Mock an SPS request
|
||||||
|
text=json.dumps({
|
||||||
|
"requestList":
|
||||||
|
[{"resourceUrl": "/config-secure-provisioning-service/"
|
||||||
|
"v1/sps-requests/1849",
|
||||||
|
"parameters": [{
|
||||||
|
"name": "cnameHostname",
|
||||||
|
"value": "secure.san1.poppycdn.com"
|
||||||
|
}, {"name": "createType", "value": "modSan"},
|
||||||
|
{"name": "csr.cn",
|
||||||
|
"value": "secure.san3.poppycdn.com"},
|
||||||
|
{"name": "add.sans",
|
||||||
|
"value": "www.abc.com"}],
|
||||||
|
"lastStatusChange": "2015-03-19T21:47:10Z",
|
||||||
|
"spsId": 1789,
|
||||||
|
"status": "SPS Request Complete",
|
||||||
|
"jobId": 44306}]})
|
||||||
|
)
|
||||||
|
controller.sps_api_client.post.return_value = mock.Mock(
|
||||||
|
status_code=202,
|
||||||
|
text=json.dumps({
|
||||||
|
"spsId": 1789,
|
||||||
|
"resourceLocation":
|
||||||
|
"/config-secure-provisioning-service/v1/sps-requests/1856",
|
||||||
|
"Results": {
|
||||||
|
"size": 1,
|
||||||
|
"data": [{
|
||||||
|
"text": None,
|
||||||
|
"results": {
|
||||||
|
"type": "SUCCESS",
|
||||||
|
"jobID": 44434}
|
||||||
|
}]}})
|
||||||
|
)
|
||||||
|
controller.create_certificate(ssl_certificate.load_from_json(data))
|
||||||
|
controller.sps_api_client.get.assert_called_once_with(
|
||||||
|
controller.sps_api_base_url.format(spsId=lastSpsId))
|
||||||
|
controller.sps_api_client.post.assert_called_once_with(
|
||||||
|
controller.sps_api_base_url.format(spsId=lastSpsId),
|
||||||
|
data=string_post_cert_info)
|
||||||
|
return
|
||||||
|
|
Loading…
Reference in New Issue