313 lines
13 KiB
Python
313 lines
13 KiB
Python
# 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 itertools
|
|
import json
|
|
|
|
from oslo_context import context as context_utils
|
|
from oslo_log import log
|
|
|
|
from poppy.distributed_task.taskflow.flow import create_ssl_certificate
|
|
from poppy.distributed_task.taskflow.flow import delete_ssl_certificate
|
|
from poppy.distributed_task.taskflow.flow import recreate_ssl_certificate
|
|
from poppy.manager import base
|
|
from poppy.model.helpers import domain
|
|
from poppy.model import ssl_certificate
|
|
from poppy.transport.validators import helpers as validators
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
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 = self._driver.storage.certificates_controller
|
|
self.service_storage = self._driver.storage.services_controller
|
|
self.flavor_controller = self._driver.storage.flavors_controller
|
|
|
|
def create_ssl_certificate(self, project_id, cert_obj):
|
|
|
|
if (not validators.is_valid_domain_name(cert_obj.domain_name)) or \
|
|
(validators.is_root_domain(
|
|
domain.Domain(cert_obj.domain_name).to_dict())):
|
|
# here created a http domain object but it does not matter http or
|
|
# https
|
|
raise ValueError('%s must be a valid non-root domain' %
|
|
cert_obj.domain_name)
|
|
|
|
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.create_certificate(
|
|
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()),
|
|
'context_dict': context_utils.get_current().to_dict()
|
|
}
|
|
self.distributed_task_controller.submit_task(
|
|
create_ssl_certificate.create_ssl_certificate,
|
|
**kwargs)
|
|
return kwargs
|
|
|
|
def delete_ssl_certificate(self, project_id, domain_name, cert_type):
|
|
kwargs = {
|
|
'project_id': project_id,
|
|
'domain_name': domain_name,
|
|
'cert_type': cert_type,
|
|
'context_dict': context_utils.get_current().to_dict()
|
|
}
|
|
self.distributed_task_controller.submit_task(
|
|
delete_ssl_certificate.delete_ssl_certificate,
|
|
**kwargs)
|
|
return kwargs
|
|
|
|
def get_certs_info_by_domain(self, domain_name, project_id):
|
|
try:
|
|
certs_info = self.storage.get_certs_by_domain(
|
|
domain_name=domain_name,
|
|
project_id=project_id)
|
|
if not certs_info:
|
|
raise ValueError("certificate information"
|
|
"not found for {0} ".format(domain_name))
|
|
|
|
return certs_info
|
|
|
|
except ValueError as e:
|
|
raise e
|
|
|
|
def get_san_retry_list(self):
|
|
if 'akamai' in self._driver.providers:
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
res = akamai_driver.mod_san_queue.traverse_queue()
|
|
# For other providers san_retry_list implementation goes here
|
|
else:
|
|
# if not using akamai driver just return an empty list
|
|
return []
|
|
res = [json.loads(r) for r in res]
|
|
return [
|
|
{"domain_name": r['domain_name'],
|
|
"project_id": r['project_id'],
|
|
"flavor_id": r['flavor_id'],
|
|
"validate_service": r.get('validate_service', True)}
|
|
for r in res
|
|
]
|
|
|
|
def update_san_retry_list(self, queue_data_list):
|
|
for r in queue_data_list:
|
|
service_obj = self.service_storage\
|
|
.get_service_details_by_domain_name(r['domain_name'])
|
|
if service_obj is None and r.get('validate_service', True):
|
|
raise LookupError(u'Domain {0} does not exist on any service, '
|
|
'are you sure you want to proceed request, '
|
|
'{1}? You can set validate_service to False '
|
|
'to retry this san-retry request forcefully'.
|
|
format(r['domain_name'], r))
|
|
|
|
cert_for_domain = self.storage.get_certs_by_domain(
|
|
r['domain_name'])
|
|
if cert_for_domain != []:
|
|
if cert_for_domain.get_cert_status() == "deployed":
|
|
raise ValueError(u'Cert on {0} already exists'.
|
|
format(r['domain_name']))
|
|
|
|
new_queue_data = [
|
|
json.dumps({'flavor_id': r['flavor_id'], # flavor_id
|
|
'domain_name': r['domain_name'], # domain_name
|
|
'project_id': r['project_id'],
|
|
'validate_service': r.get('validate_service', True)})
|
|
for r in queue_data_list
|
|
]
|
|
res, deleted = [], []
|
|
if 'akamai' in self._driver.providers:
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
orig = [json.loads(r) for r in
|
|
akamai_driver.mod_san_queue.traverse_queue()]
|
|
res = [json.loads(r) for r in
|
|
akamai_driver.mod_san_queue.put_queue_data(new_queue_data)]
|
|
|
|
deleted = tuple(x for x in orig if x not in res)
|
|
# other provider's retry-list implementation goes here
|
|
return res, deleted
|
|
|
|
def rerun_san_retry_list(self):
|
|
if 'akamai' in self._driver.providers:
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
retry_list = []
|
|
while len(akamai_driver.mod_san_queue.mod_san_queue_backend) > 0:
|
|
res = akamai_driver.mod_san_queue.dequeue_mod_san_request()
|
|
retry_list.append(json.loads(res.decode('utf-8')))
|
|
|
|
# remove duplicates
|
|
# see http://bit.ly/1mX2Vcb for details
|
|
def remove_duplicates(data):
|
|
"""Remove duplicates from the data (normally a list).
|
|
|
|
The data must be sortable and have an equality operator
|
|
"""
|
|
data = sorted(data)
|
|
return [k for k, _ in itertools.groupby(data)]
|
|
retry_list = remove_duplicates(retry_list)
|
|
|
|
# double check in POST. This check should really be first done in
|
|
# PUT
|
|
for r in retry_list:
|
|
service_obj = self.service_storage\
|
|
.get_service_details_by_domain_name(r['domain_name'])
|
|
if service_obj is None and r.get('validate_service', True):
|
|
raise LookupError(u'Domain {0} does not exist on any '
|
|
'service, are you sure you want to '
|
|
'proceed request, {1}? You can set '
|
|
'validate_service to False to retry this'
|
|
' san-retry request forcefully'.
|
|
format(r['domain_name'], r))
|
|
|
|
cert_for_domain = self.storage.get_certs_by_domain(
|
|
r['domain_name'])
|
|
if cert_for_domain != []:
|
|
if cert_for_domain.get_cert_status() == "deployed":
|
|
raise ValueError(u'Certificate on {0} has already been'
|
|
' provisioned successfully.'.
|
|
format(r['domain_name']))
|
|
|
|
for cert_obj_dict in retry_list:
|
|
try:
|
|
cert_obj = ssl_certificate.SSLCertificate(
|
|
cert_obj_dict['flavor_id'],
|
|
cert_obj_dict['domain_name'],
|
|
'san',
|
|
project_id=cert_obj_dict['project_id']
|
|
)
|
|
|
|
cert_for_domain = (
|
|
self.storage.get_certs_by_domain(
|
|
cert_obj.domain_name,
|
|
project_id=cert_obj.project_id,
|
|
flavor_id=cert_obj.flavor_id,
|
|
cert_type=cert_obj.cert_type))
|
|
if cert_for_domain == []:
|
|
pass
|
|
else:
|
|
# If this cert has been deployed through manual
|
|
# process we ignore the rerun process for this entry
|
|
if cert_for_domain.get_cert_status() == 'deployed':
|
|
continue
|
|
# rerun the san process
|
|
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
|
|
|
|
providers = [p.provider_id for p in flavor.providers]
|
|
kwargs = {
|
|
'project_id': cert_obj.project_id,
|
|
'domain_name': cert_obj.domain_name,
|
|
'cert_type': 'san',
|
|
'providers_list_json': json.dumps(providers),
|
|
'cert_obj_json': json.dumps(cert_obj.to_dict()),
|
|
'enqueue': False,
|
|
}
|
|
self.distributed_task_controller.submit_task(
|
|
recreate_ssl_certificate.recreate_ssl_certificate,
|
|
**kwargs)
|
|
except Exception as e:
|
|
# When exception happens we log it and re-queue this
|
|
# request
|
|
LOG.exception(e)
|
|
akamai_driver.mod_san_queue.enqueue_mod_san_request(
|
|
json.dumps(cert_obj_dict)
|
|
)
|
|
# For other providers post san_retry_list implementation goes here
|
|
else:
|
|
# if not using akamai driver just return None
|
|
pass
|
|
|
|
return None
|
|
|
|
def get_san_cert_configuration(self, san_cert_name):
|
|
if 'akamai' in self._driver.providers:
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
if san_cert_name not in akamai_driver.san_cert_cnames:
|
|
raise ValueError(
|
|
"%s is not a valid san cert, valid san certs are: %s" %
|
|
(san_cert_name, akamai_driver.san_cert_cnames))
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
res = akamai_driver.cert_info_storage.get_cert_config(
|
|
san_cert_name
|
|
)
|
|
else:
|
|
# if not using akamai driver just return an empty list
|
|
res = {}
|
|
|
|
return res
|
|
|
|
def update_san_cert_configuration(self, san_cert_name, new_cert_config):
|
|
if 'akamai' in self._driver.providers:
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
if san_cert_name not in akamai_driver.san_cert_cnames:
|
|
raise ValueError(
|
|
"%s is not a valid san cert, valid san certs are: %s" %
|
|
(san_cert_name, akamai_driver.san_cert_cnames))
|
|
akamai_driver = self._driver.providers['akamai'].obj
|
|
# given the spsId, determine the most recent jobId
|
|
# and persist the jobId
|
|
if new_cert_config.get('spsId') is not None:
|
|
resp = akamai_driver.sps_api_client.get(
|
|
akamai_driver.akamai_sps_api_base_url.format(
|
|
spsId=new_cert_config['spsId']
|
|
),
|
|
)
|
|
if resp.status_code != 200:
|
|
raise RuntimeError(
|
|
'SPS GET Request failed. Exception: {0}'.format(
|
|
resp.text
|
|
)
|
|
)
|
|
else:
|
|
resp_json = resp.json()
|
|
new_cert_config['jobId'] = (
|
|
resp_json['requestList'][0]['jobId']
|
|
)
|
|
res = akamai_driver.cert_info_storage.update_cert_config(
|
|
san_cert_name, new_cert_config)
|
|
else:
|
|
# if not using akamai driver just return an empty list
|
|
res = {}
|
|
|
|
return res
|
|
|
|
def get_certs_by_status(self, status):
|
|
services_project_ids = self.storage.get_certs_by_status(status)
|
|
|
|
return services_project_ids
|