shared ssl domain implmentation
Change-Id: I81ed3dfd073c00aa56bd2a7968c06ab2798d25b7
This commit is contained in:
parent
6406a3f96d
commit
e84df4ff3e
|
@ -101,6 +101,9 @@ api_key = "<operator_api_key>"
|
|||
use_shards = True
|
||||
num_shards = 400
|
||||
shard_prefix = "cdn"
|
||||
shared_ssl_num_shards = 5
|
||||
shared_ssl_shard_prefix = "scdn"
|
||||
shared_ssl_domain_suffix = "secure.poppycdn.net"
|
||||
url = "poppycdn.net"
|
||||
# You email associated with DNS, for notifications
|
||||
email = "your@email.com"
|
||||
|
|
|
@ -59,3 +59,13 @@ class ServicesControllerBase(controller.DNSControllerBase):
|
|||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_shared_ssl_domain_suffix(self):
|
||||
"""Generate a shared ssl domain suffix,
|
||||
|
||||
to be used with manager for shared ssl feature
|
||||
|
||||
:raises NotImplementedError
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -77,3 +77,11 @@ class ServicesController(base.ServicesBase):
|
|||
access_urls.append(access_url)
|
||||
dns_details[provider_name] = {'access_urls': access_urls}
|
||||
return self.responder.created(dns_details)
|
||||
|
||||
def generate_shared_ssl_domain_suffix(self):
|
||||
"""Default DNS Generate a shared ssl domain suffix,
|
||||
|
||||
to be used with manager for shared ssl feature
|
||||
|
||||
"""
|
||||
return 'scdn023.secure.defaultcdn.com'
|
||||
|
|
|
@ -33,6 +33,14 @@ RACKSPACE_OPTIONS = [
|
|||
cfg.IntOpt('num_shards', default=10, help='Number of Shards to use'),
|
||||
cfg.StrOpt('shard_prefix', default='cdn',
|
||||
help='The shard prefix to use'),
|
||||
cfg.IntOpt('shared_ssl_num_shards', default=5, help='Number of Shards '
|
||||
'to use in generating shared ssl domain suffix'),
|
||||
cfg.StrOpt('shared_ssl_shard_prefix', default='scdn',
|
||||
help='The shard prefix to use '
|
||||
'in generating shared ssl domain suffix'),
|
||||
cfg.StrOpt('shared_ssl_domain_suffix', default='',
|
||||
help='The shared ssl domain suffix to generate'
|
||||
' shared ssl domain'),
|
||||
cfg.StrOpt('url', default='',
|
||||
help='The url for customers to CNAME to'),
|
||||
cfg.StrOpt('email', help='The email to be provided to Rackspace DNS for'
|
||||
|
|
|
@ -68,9 +68,21 @@ class ServicesController(base.ServicesBase):
|
|||
subdomain = self._get_subdomain(subdomain_name)
|
||||
# create CNAME record for adding
|
||||
cname_records = []
|
||||
|
||||
dns_links = {}
|
||||
|
||||
shared_ssl_subdomain_name = None
|
||||
for link in links:
|
||||
name = '{0}.{1}'.format(link, subdomain_name)
|
||||
# pick out shared ssl domains here
|
||||
domain_name, certificate = link
|
||||
if certificate == "shared":
|
||||
shared_ssl_subdomain_name = (
|
||||
'.'.join(domain_name.split('.')[1:]))
|
||||
# perform shared ssl cert logic
|
||||
name = domain_name
|
||||
else:
|
||||
name = '{0}.{1}'.format(domain_name, subdomain_name)
|
||||
|
||||
cname_record = {'type': 'CNAME',
|
||||
'name': name,
|
||||
'data': links[link],
|
||||
|
@ -79,12 +91,19 @@ class ServicesController(base.ServicesBase):
|
|||
'provider_url': links[link],
|
||||
'operator_url': name
|
||||
}
|
||||
cname_records.append(cname_record)
|
||||
|
||||
if certificate == "shared":
|
||||
shared_ssl_subdomain = self._get_subdomain(
|
||||
shared_ssl_subdomain_name)
|
||||
shared_ssl_subdomain.add_records([cname_record])
|
||||
else:
|
||||
cname_records.append(cname_record)
|
||||
# add the cname records
|
||||
subdomain.add_records(cname_records)
|
||||
if cname_records != []:
|
||||
subdomain.add_records(cname_records)
|
||||
return dns_links
|
||||
|
||||
def _delete_cname_record(self, access_url):
|
||||
def _delete_cname_record(self, access_url, shared_ssl_flag):
|
||||
"""Delete a CNAME record
|
||||
|
||||
:param access_url: DNS Access URL
|
||||
|
@ -92,8 +111,18 @@ class ServicesController(base.ServicesBase):
|
|||
"""
|
||||
|
||||
# extract shard name
|
||||
shard_name = access_url.split('.')[-3]
|
||||
subdomain_name = '.'.join([shard_name, self._driver.rackdns_conf.url])
|
||||
if shared_ssl_flag:
|
||||
suffix = self._driver.rackdns_conf.shared_ssl_domain_suffix
|
||||
else:
|
||||
suffix = self._driver.rackdns_conf.url
|
||||
# Note: use rindex to find last occurence of the suffix
|
||||
shard_name = access_url[:access_url.rindex(suffix)-1].split('.')[-1]
|
||||
subdomain_name = '.'.join([shard_name, suffix])
|
||||
|
||||
# for sharding is disabled, the suffix is the subdomain_name
|
||||
if shared_ssl_flag and (
|
||||
self._driver.rackdns_conf.shared_ssl_num_shards == 0):
|
||||
subdomain_name = suffix
|
||||
# get subdomain
|
||||
subdomain = self.client.find(name=subdomain_name)
|
||||
# search and find the CNAME record
|
||||
|
@ -113,6 +142,33 @@ class ServicesController(base.ServicesBase):
|
|||
return error_msg
|
||||
return
|
||||
|
||||
def _generate_sharded_domain_name(self, shard_prefix, num_shards, suffix):
|
||||
"""Generate a sharded domain name based on the scheme:
|
||||
|
||||
'{shard_prefix}{a random shard_id}.{suffix}'
|
||||
|
||||
:return A string of sharded domain name
|
||||
"""
|
||||
if num_shards == 0:
|
||||
# shard disabled, just use the suffix
|
||||
return suffix
|
||||
else:
|
||||
# shard enabled, randomly select a shard
|
||||
shard_id = random.randint(1, num_shards)
|
||||
return '{0}{1}.{2}'.format(shard_prefix, shard_id, suffix)
|
||||
|
||||
def generate_shared_ssl_domain_suffix(self):
|
||||
"""Rackespace DNS scheme to generate a shared ssl domain suffix,
|
||||
|
||||
to be used with manager for shared ssl feature
|
||||
|
||||
:return A string of shared ssl domain name
|
||||
"""
|
||||
return self._generate_sharded_domain_name(
|
||||
self._driver.rackdns_conf.shared_ssl_shard_prefix,
|
||||
self._driver.rackdns_conf.shared_ssl_num_shards,
|
||||
self._driver.rackdns_conf.shared_ssl_domain_suffix)
|
||||
|
||||
def create(self, responders):
|
||||
"""Create CNAME record for a service.
|
||||
|
||||
|
@ -137,7 +193,11 @@ class ServicesController(base.ServicesBase):
|
|||
for provider_name in responder:
|
||||
for link in responder[provider_name]['links']:
|
||||
if link['rel'] == 'access_url':
|
||||
links[link['domain']] = link['href']
|
||||
# We need to distinguish shared ssl domains in
|
||||
# which case the we will use different shard prefix and
|
||||
# and shard number
|
||||
links[(link['domain'], link.get('certificate',
|
||||
None))] = link['href']
|
||||
|
||||
# create CNAME records
|
||||
try:
|
||||
|
@ -157,9 +217,19 @@ class ServicesController(base.ServicesBase):
|
|||
access_url = {
|
||||
'domain': link['domain'],
|
||||
'provider_url':
|
||||
dns_links[link['domain']]['provider_url'],
|
||||
dns_links[(link['domain'],
|
||||
link.get('certificate', None)
|
||||
)]['provider_url'],
|
||||
'operator_url':
|
||||
dns_links[link['domain']]['operator_url']}
|
||||
dns_links[(link['domain'],
|
||||
link.get('certificate', None)
|
||||
)]['operator_url']}
|
||||
# Need to indicate if this access_url is a shared ssl
|
||||
# access url, since its has different shard_prefix and
|
||||
# num_shard
|
||||
if link.get('certificate', None) == 'shared':
|
||||
access_url['shared_ssl_flag'] = True
|
||||
|
||||
access_urls.append(access_url)
|
||||
dns_details[provider_name] = {'access_urls': access_urls}
|
||||
return self.responder.created(dns_details)
|
||||
|
@ -181,7 +251,9 @@ class ServicesController(base.ServicesBase):
|
|||
access_urls = provider_details[provider_name].access_urls
|
||||
for access_url in access_urls:
|
||||
try:
|
||||
msg = self._delete_cname_record(access_url['operator_url'])
|
||||
msg = self._delete_cname_record(
|
||||
access_url['operator_url'],
|
||||
access_url.get('shared_ssl_flag', False))
|
||||
if msg:
|
||||
error_msg = error_msg + msg
|
||||
except exc.NotFound as e:
|
||||
|
@ -228,7 +300,8 @@ class ServicesController(base.ServicesBase):
|
|||
domain_added = (link['rel'] == 'access_url' and
|
||||
link['domain'] in added_domains)
|
||||
if domain_added:
|
||||
links[link['domain']] = link['href']
|
||||
links[(link['domain'], link.get('certificate',
|
||||
None))] = link['href']
|
||||
|
||||
# create CNAME records for added domains
|
||||
try:
|
||||
|
@ -247,9 +320,19 @@ class ServicesController(base.ServicesBase):
|
|||
access_url = {
|
||||
'domain': link['domain'],
|
||||
'provider_url':
|
||||
dns_links[link['domain']]['provider_url'],
|
||||
dns_links[(link['domain'],
|
||||
link.get('certificate', None)
|
||||
)]['provider_url'],
|
||||
'operator_url':
|
||||
dns_links[link['domain']]['operator_url']}
|
||||
dns_links[(link['domain'],
|
||||
link.get('certificate', None)
|
||||
)]['operator_url']}
|
||||
# Need to indicate if this access_url is a shared ssl
|
||||
# access url, since its has different shard_prefix and
|
||||
# num_shard
|
||||
if link.get('certificate', None) == 'shared':
|
||||
access_url['shared_ssl_flag'] = True
|
||||
|
||||
access_urls.append(access_url)
|
||||
dns_details[provider_name] = {'access_urls': access_urls}
|
||||
return dns_details
|
||||
|
@ -276,7 +359,10 @@ class ServicesController(base.ServicesBase):
|
|||
if access_url['domain'] not in removed_domains:
|
||||
continue
|
||||
try:
|
||||
msg = self._delete_cname_record(access_url['operator_url'])
|
||||
msg = self._delete_cname_record(access_url['operator_url'],
|
||||
access_url.get(
|
||||
'shared_ssl_flag',
|
||||
False))
|
||||
if msg:
|
||||
error_msg = error_msg + msg
|
||||
except exc.NotFound as e:
|
||||
|
@ -386,6 +472,9 @@ class ServicesController(base.ServicesBase):
|
|||
'domain': link['domain'],
|
||||
'provider_url': link['href'],
|
||||
'operator_url': operator_url}
|
||||
# if it is a shared ssl access url, we need to store it
|
||||
if new_access_url.get('shared_ssl_flag', False):
|
||||
access_url['shared_ssl_flag'] = True
|
||||
access_urls.append(access_url)
|
||||
elif link['domain'] in common_domains:
|
||||
# iterate through old access urls and get access url
|
||||
|
@ -398,6 +487,9 @@ class ServicesController(base.ServicesBase):
|
|||
'domain': link['domain'],
|
||||
'provider_url': link['href'],
|
||||
'operator_url': operator_url}
|
||||
# if it is a shared ssl access url, we need to store it
|
||||
if old_access_url.get('shared_ssl_flag', False):
|
||||
access_url['shared_ssl_flag'] = True
|
||||
access_urls.append(access_url)
|
||||
dns_details[provider_name] = {'access_urls': access_urls}
|
||||
|
||||
|
|
|
@ -122,6 +122,13 @@ class DefaultServicesController(base.ServicesController):
|
|||
providers = [p.provider_id for p in flavor.providers]
|
||||
service_id = service_obj.service_id
|
||||
|
||||
# deal with shared ssl domains
|
||||
for domain in service_obj.domains:
|
||||
if domain.protocol == 'https' and domain.certificate == 'shared':
|
||||
domain.domain = self._generate_shared_ssl_domain(
|
||||
domain.domain
|
||||
)
|
||||
|
||||
try:
|
||||
self.storage_controller.create(
|
||||
project_id,
|
||||
|
@ -160,6 +167,14 @@ class DefaultServicesController(base.ServicesController):
|
|||
raise errors.ServiceStatusNeitherDeployedNorFailed(
|
||||
u'Service {0} neither deployed nor failed'.format(service_id))
|
||||
|
||||
# Fixing the operator_url domain for ssl
|
||||
existing_shared_domains = {}
|
||||
for domain in service_old.domains:
|
||||
if domain.protocol == 'https' and domain.certificate == 'shared':
|
||||
customer_domain = domain.domain.split('.')[0]
|
||||
existing_shared_domains[customer_domain] = domain.domain
|
||||
domain.domain = customer_domain
|
||||
|
||||
service_old_json = json.loads(json.dumps(service_old.to_dict()))
|
||||
|
||||
# remove fields that cannot be part of PATCH
|
||||
|
@ -192,6 +207,18 @@ class DefaultServicesController(base.ServicesController):
|
|||
raise ValueError(
|
||||
"Domain {0} has already been taken".format(d.domain))
|
||||
|
||||
# fixing the old and new shared ssl domains in service_new
|
||||
for domain in service_new.domains:
|
||||
if domain.protocol == 'https' and domain.certificate == 'shared':
|
||||
customer_domain = domain.domain.split('.')[0]
|
||||
# if this domain is from service_old
|
||||
if customer_domain in existing_shared_domains:
|
||||
domain.domain = existing_shared_domains[customer_domain]
|
||||
else:
|
||||
domain.domain = self._generate_shared_ssl_domain(
|
||||
domain.domain
|
||||
)
|
||||
|
||||
# set status in provider details to u'update_in_progress'
|
||||
provider_details = service_old.provider_details
|
||||
for provider in provider_details:
|
||||
|
@ -260,3 +287,8 @@ class DefaultServicesController(base.ServicesController):
|
|||
purge_service.purge_service, **kwargs)
|
||||
|
||||
return
|
||||
|
||||
def _generate_shared_ssl_domain(self, domain_name):
|
||||
shared_ssl_domain_suffix = (
|
||||
self.dns_controller.generate_shared_ssl_domain_suffix())
|
||||
return '.'.join([domain_name, shared_ssl_domain_suffix])
|
||||
|
|
|
@ -32,7 +32,7 @@ class ProviderDetail(common.DictSerializableModel):
|
|||
|
||||
"""ProviderDetail object for each provider."""
|
||||
|
||||
def __init__(self, provider_service_id=None, access_urls={},
|
||||
def __init__(self, provider_service_id=None, access_urls=[],
|
||||
status=u"deploy_in_progress", name=None, error_info=None,
|
||||
error_message=None):
|
||||
self._provider_service_id = provider_service_id
|
||||
|
@ -122,7 +122,7 @@ class ProviderDetail(common.DictSerializableModel):
|
|||
o = cls("unnamed")
|
||||
o.provider_service_id = dict_obj.get("id",
|
||||
"unknown_id")
|
||||
o.access_urls = dict_obj.get("access_urls", {})
|
||||
o.access_urls = dict_obj.get("access_urls", [])
|
||||
o.status = dict_obj.get("status", u"deploy_in_progress")
|
||||
o.name = dict_obj.get("name", None)
|
||||
o.error_info = dict_obj.get("error_info", None)
|
||||
|
|
|
@ -113,7 +113,8 @@ class ServiceController(base.ServiceBase):
|
|||
raise RuntimeError(resp.text)
|
||||
|
||||
dp_obj = {'policy_name': dp,
|
||||
'protocol': classified_domain.protocol}
|
||||
'protocol': classified_domain.protocol,
|
||||
'certificate': classified_domain.certificate}
|
||||
ids.append(dp_obj)
|
||||
# TODO(tonytan4ever): leave empty links for now
|
||||
# may need to work with dns integration
|
||||
|
@ -123,7 +124,8 @@ class ServiceController(base.ServiceBase):
|
|||
classified_domain, dp)
|
||||
links.append({'href': provider_access_url,
|
||||
'rel': 'access_url',
|
||||
'domain': classified_domain.domain
|
||||
'domain': classified_domain.domain,
|
||||
'certificate': classified_domain.certificate
|
||||
})
|
||||
except Exception as e:
|
||||
LOG.error('Creating policy failed: %s' % traceback.format_exc())
|
||||
|
@ -252,7 +254,9 @@ class ServiceController(base.ServiceBase):
|
|||
data=json.dumps(policy_content),
|
||||
headers=self.request_header)
|
||||
dp_obj = {'policy_name': dp,
|
||||
'protocol': classified_domain.protocol}
|
||||
'protocol': classified_domain.protocol,
|
||||
'certificate':
|
||||
classified_domain.certificate}
|
||||
policies.remove(dp_obj)
|
||||
else:
|
||||
LOG.info('Start to create new policy %s' % dp)
|
||||
|
@ -268,7 +272,8 @@ class ServiceController(base.ServiceBase):
|
|||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
dp_obj = {'policy_name': dp,
|
||||
'protocol': classified_domain.protocol}
|
||||
'protocol': classified_domain.protocol,
|
||||
'certificate': classified_domain.certificate}
|
||||
ids.append(dp_obj)
|
||||
# TODO(tonytan4ever): leave empty links for now
|
||||
# may need to work with dns integration
|
||||
|
@ -278,7 +283,9 @@ class ServiceController(base.ServiceBase):
|
|||
classified_domain, dp)
|
||||
links.append({'href': provider_access_url,
|
||||
'rel': 'access_url',
|
||||
'domain': dp
|
||||
'domain': dp,
|
||||
'certificate':
|
||||
classified_domain.certificate
|
||||
})
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
|
@ -371,7 +378,8 @@ class ServiceController(base.ServiceBase):
|
|||
util.dict2obj(policy), policy['policy_name'])
|
||||
links.append({'href': provider_access_url,
|
||||
'rel': 'access_url',
|
||||
'domain': policy['policy_name']
|
||||
'domain': policy['policy_name'],
|
||||
'certificate': policy['certificate']
|
||||
})
|
||||
ids = policies
|
||||
return self.responder.updated(json.dumps(ids), links)
|
||||
|
@ -619,6 +627,11 @@ class ServiceController(base.ServiceBase):
|
|||
if domain_obj.protocol == 'http':
|
||||
provider_access_url = self.driver.akamai_access_url_link
|
||||
elif domain_obj.protocol == 'https':
|
||||
provider_access_url = '.'.join(
|
||||
[dp, self.driver.akamai_https_access_url_suffix])
|
||||
if domain_obj.certificate == "shared":
|
||||
provider_access_url = '.'.join(
|
||||
['.'.join(dp.split('.')[1:]),
|
||||
self.driver.akamai_https_access_url_suffix])
|
||||
else:
|
||||
provider_access_url = '.'.join(
|
||||
[dp, self.driver.akamai_https_access_url_suffix])
|
||||
return provider_access_url
|
||||
|
|
|
@ -101,7 +101,8 @@ class TestServices(base.TestCase):
|
|||
|
||||
def test_delete_with_exception(self):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
|
||||
# test exception
|
||||
exception = RuntimeError('ding')
|
||||
|
@ -118,7 +119,8 @@ class TestServices(base.TestCase):
|
|||
|
||||
def test_delete_with_4xx_return(self):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
|
||||
# test exception
|
||||
self.controller.policy_api_client.delete.return_value = mock.Mock(
|
||||
|
@ -131,7 +133,8 @@ class TestServices(base.TestCase):
|
|||
|
||||
def test_delete(self):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
|
||||
self.controller.delete(provider_service_id)
|
||||
self.controller.policy_api_client.delete.assert_called_once()
|
||||
|
@ -170,7 +173,8 @@ class TestServices(base.TestCase):
|
|||
@ddt.file_data('data_update_service.json')
|
||||
def test_update(self, service_json):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.policy_api_client.get.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
|
@ -192,7 +196,8 @@ class TestServices(base.TestCase):
|
|||
@ddt.file_data('data_update_service.json')
|
||||
def test_update_with_domain_protocol_change(self, service_json):
|
||||
provider_service_id = json.dumps([{'policy_name': "densely.sage.com",
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.policy_api_client.get.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
|
@ -214,7 +219,8 @@ class TestServices(base.TestCase):
|
|||
@ddt.file_data('data_upsert_service.json')
|
||||
def test_upsert(self, service_json):
|
||||
provider_service_id = json.dumps([{'policy_name': "densely.sage.com",
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.policy_api_client.get.return_value = mock.Mock(
|
||||
status_code=404,
|
||||
|
@ -231,7 +237,8 @@ class TestServices(base.TestCase):
|
|||
|
||||
def test_purge_all(self):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
controller = services.ServiceController(self.driver)
|
||||
resp = controller.purge(provider_service_id, None)
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
@ -244,7 +251,8 @@ class TestServices(base.TestCase):
|
|||
|
||||
def test_purge_with_ccu_exception(self):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'http'}])
|
||||
'protocol': 'http',
|
||||
'certificate': None}])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.ccu_api_client.post.return_value = mock.Mock(
|
||||
status_code=400,
|
||||
|
@ -255,7 +263,8 @@ class TestServices(base.TestCase):
|
|||
|
||||
def test_purge(self):
|
||||
provider_service_id = json.dumps([{'policy_name': str(uuid.uuid1()),
|
||||
'protocol': 'https'}])
|
||||
'protocol': 'https',
|
||||
'certificate': 'shared'}])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.ccu_api_client.post.return_value = mock.Mock(
|
||||
status_code=201,
|
||||
|
|
Loading…
Reference in New Issue