diff --git a/poppy/manager/default/ssl_certificate.py b/poppy/manager/default/ssl_certificate.py index 4e5427d7..8e8635ec 100644 --- a/poppy/manager/default/ssl_certificate.py +++ b/poppy/manager/default/ssl_certificate.py @@ -48,7 +48,8 @@ class DefaultSSLCertificateController(base.SSLCertificateController): if (not validators.is_valid_domain_name(cert_obj.domain_name)) or \ (validators.is_root_domain( - domain.Domain(cert_obj.domain_name).to_dict())): + domain.Domain(cert_obj.domain_name).to_dict())) or \ + (not validators.is_valid_tld(cert_obj.domain_name)): # here created a http domain object but it does not matter http or # https raise ValueError('%s must be a valid non-root domain' % diff --git a/poppy/transport/validators/helpers.py b/poppy/transport/validators/helpers.py index 77cfba97..db847867 100644 --- a/poppy/transport/validators/helpers.py +++ b/poppy/transport/validators/helpers.py @@ -14,9 +14,12 @@ # limitations under the License. import datetime +import dns.resolver import functools import json import re +import whois + try: set except NameError: # noqa pragma: no cover @@ -30,6 +33,7 @@ from poppy.common import util from poppy.transport.validators import root_domain_regexes as regexes from poppy.transport.validators.stoplight import decorators from poppy.transport.validators.stoplight import exceptions +from tld import get_tld def req_accepts_json_pecan(request, desired_content_type='application/json'): @@ -134,6 +138,23 @@ def is_valid_shared_ssl_domain_name(domain_name): return re.match(shared_ssl_domain_regex, domain_name) is not None +def is_valid_tld(domain_name): + try: + status = whois.whois(domain_name)['status'] + if status is not None or status != '': + url = 'https://{domain}' + tld_obj = get_tld(url.format(domain=domain_name), + as_object=True) + tld = tld_obj.suffix + try: + dns.resolver.query(tld + '.', 'SOA') + return True + except dns.resolver.NXDOMAIN: + return False + except Exception: + return False + + def is_valid_domain_name(domain_name): # only allow ascii domain_regex = ('^((?=[a-z0-9-]{1,63}\.)[a-z0-9]+' diff --git a/requirements/common.txt b/requirements/common.txt index 33b73a9b..74f3ba44 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -19,3 +19,7 @@ oslo.log>=1.12.1 oslo.serialization>=1.7.0 oslo.utils>=2.0.0 SecretStorage==2.1.4 + +python-whois>=0.6.2 +tld>=0.7.6 +dnspython>=1.14.0 diff --git a/tests/functional/transport/pecan/controllers/test_ssl_certificate.py b/tests/functional/transport/pecan/controllers/test_ssl_certificate.py index a80c690a..f8c04754 100644 --- a/tests/functional/transport/pecan/controllers/test_ssl_certificate.py +++ b/tests/functional/transport/pecan/controllers/test_ssl_certificate.py @@ -17,7 +17,9 @@ import json import uuid import ddt +import mock +from poppy.transport.validators import helpers as validators from tests.functional.transport.pecan import base @@ -56,6 +58,7 @@ class SSLCertificateControllerTest(base.FunctionalTest): @ddt.file_data("data_create_ssl_certificate.json") def test_create_ssl_certificate(self, ssl_certificate_json): + validators.is_valid_tld = mock.Mock(return_value=True) # override the hardcoded flavor_id in the ddt file with # a custom one defined in setUp() @@ -81,6 +84,7 @@ class SSLCertificateControllerTest(base.FunctionalTest): self.assertEqual(404, response.status_code) def test_get_ssl_certificate_existing_domain(self): + validators.is_valid_tld = mock.Mock(return_value=True) domain = 'www.iexist.com' ssl_certificate_json = { "cert_type": "san", @@ -113,6 +117,7 @@ class SSLCertificateControllerTest(base.FunctionalTest): response_list[0]["project_id"]) def test_get_ssl_certificate_existing_domain_different_project_id(self): + validators.is_valid_tld = mock.Mock(return_value=True) domain = 'www.iexist.com' ssl_certificate_json = { "cert_type": "san", diff --git a/tests/test-requirements.txt b/tests/test-requirements.txt index 029b58a1..27a76cdb 100644 --- a/tests/test-requirements.txt +++ b/tests/test-requirements.txt @@ -1,6 +1,5 @@ coverage ddt==1.0.0 -dnspython fixtures hacking mock @@ -11,4 +10,4 @@ requests-mock testrepository testtools beautifulsoup4 -hypothesis \ No newline at end of file +hypothesis diff --git a/tests/unit/manager/default/test_ssl_certificate.py b/tests/unit/manager/default/test_ssl_certificate.py index 91235c19..4c969d81 100644 --- a/tests/unit/manager/default/test_ssl_certificate.py +++ b/tests/unit/manager/default/test_ssl_certificate.py @@ -22,6 +22,7 @@ import testtools from poppy.manager.default import driver from poppy.manager.default import ssl_certificate from poppy.model import ssl_certificate as ssl_cert_model +from poppy.transport.validators import helpers as validators from tests.unit import base @@ -88,6 +89,17 @@ class DefaultSSLCertificateControllerTests(base.TestCase): with testtools.ExpectedException(ValueError): self.scc.create_ssl_certificate('project_id', cert_obj=cert_obj) + def test_create_ssl_certificate_invalid_domain(self): + cert_obj = ssl_cert_model.SSLCertificate( + 'premium', + 'www.krusty.happyclowns', + 'san', + project_id='000' + ) + validators.is_valid_tld = mock.Mock(return_value=False) + with testtools.ExpectedException(ValueError): + self.scc.create_ssl_certificate('project_id', cert_obj=cert_obj) + def test_create_ssl_certificate_exception_storage_create_cert(self): cert_obj = ssl_cert_model.SSLCertificate( 'flavor_id',