Repository: Miserlou/Zappa

Issue:
Having two hosted zones (one public and one private) with same name makes certify with let's encrypt (DNS auth) fail
When you have two hosted zones (one public and one private) with the same name under the same registered domain, deploying to this domain with let's encrypt certificate using DNS auth doesn't work.

What happens is that the `_acme-challenge.XX.XX` link always end up in the private hosted zone. Therefore let's encrypt can't access it so the validation process fails with error:
`DNS problem: NXDOMAIN looking up TXT for _acme-challenge.XX.XX`

I guess it would be good if Zappa was able to handle this particular case.

Solution seems simple:
In `get_best_match_zone()` in `zappa.py` we just need to check if the matched zone is private or not, then continue until finding a public one and return None (or fail ?) if there isn't any public hosted zone for the domain. 
If the issue is agreed I can work on it.


Discussion:
That's an excellent find! Thank you!

Sounds like you've got a good test environment for that, do you wan to submit a Pull Request for that fix? Would be happy to accept.

Great ! Yes I'll do the modification+tests and submit a PR when done.

Nice! You are awesome.


Resolving patch:
diff --git a/zappa/zappa.py b/zappa/zappa.py
--- a/zappa/zappa.py
+++ b/zappa/zappa.py
@@ -1710,7 +1710,11 @@ def get_hosted_zone_id_for_domain(self, domain):
     @staticmethod
     def get_best_match_zone(all_zones, domain):
         """Return zone id which name is closer matched with domain name."""
-        zones = {zone['Name'][:-1]: zone['Id'] for zone in all_zones['HostedZones'] if zone['Name'][:-1] in domain}
+
+        # Related: https://github.com/Miserlou/Zappa/issues/459
+        public_zones = [zone for zone in all_zones['HostedZones'] if not zone['Config']['PrivateZone']]
+
+        zones = {zone['Name'][:-1]: zone['Id'] for zone in public_zones if zone['Name'][:-1] in domain}
         if zones:
             keys = max(zones.keys(), key=lambda a: len(a))  # get longest key -- best match.
             return zones[keys]


Test patch:
diff --git a/tests/tests.py b/tests/tests.py
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -862,7 +862,10 @@ def test_domain_name_match(self):
         zone = Zappa.get_best_match_zone(all_zones={ 'HostedZones': [
             {
                 'Name': 'example.com.au.',
-                'Id': 'zone-correct'
+                'Id': 'zone-correct',
+                'Config': {
+                    'PrivateZone': False
+                }
             }
         ]},
             domain='www.example.com.au')
@@ -872,7 +875,10 @@ def test_domain_name_match(self):
         zone = Zappa.get_best_match_zone(all_zones={'HostedZones': [
             {
                 'Name': 'example.com.au.',
-                'Id': 'zone-incorrect'
+                'Id': 'zone-incorrect',
+                'Config': {
+                    'PrivateZone': False
+                }
             }
         ]},
             domain='something-else.com.au')
@@ -882,16 +888,54 @@ def test_domain_name_match(self):
         zone = Zappa.get_best_match_zone(all_zones={'HostedZones': [
             {
                 'Name': 'example.com.au.',
-                'Id': 'zone-incorrect'
+                'Id': 'zone-incorrect',
+                'Config': {
+                    'PrivateZone': False
+                }
             },
             {
                 'Name': 'subdomain.example.com.au.',
-                'Id': 'zone-correct'
+                'Id': 'zone-correct',
+                'Config': {
+                    'PrivateZone': False
+                }
             }
         ]},
             domain='www.subdomain.example.com.au')
         assert zone == 'zone-correct'
 
+        # Check private zone is not matched
+        zone = Zappa.get_best_match_zone(all_zones={ 'HostedZones': [
+            {
+                'Name': 'example.com.au.',
+                'Id': 'zone-private',
+                'Config': {
+                    'PrivateZone': True
+                }
+            }
+        ]},
+            domain='www.example.com.au')
+        assert zone is None
+
+        # More involved, should ignore the private zone and match the public.
+        zone = Zappa.get_best_match_zone(all_zones={'HostedZones': [
+            {
+                'Name': 'subdomain.example.com.au.',
+                'Id': 'zone-private',
+                'Config': {
+                    'PrivateZone': True
+                }
+            },
+            {
+                'Name': 'subdomain.example.com.au.',
+                'Id': 'zone-public',
+                'Config': {
+                    'PrivateZone': False
+                }
+            }
+        ]},
+            domain='www.subdomain.example.com.au')
+        assert zone == 'zone-public'
 
     ##
     # Let's Encrypt / ACME