def can_provision_for_domain(domain): from status_checks import normalize_ip # Let's Encrypt doesn't yet support IDNA domains. # We store domains in IDNA (ASCII). To see if this domain is IDNA, # we'll see if its IDNA-decoded form is different. if idna.decode(domain.encode("ascii")) != domain: problems[ domain] = "Let's Encrypt does not yet support provisioning certificates for internationalized domains." return False # Does the domain resolve to this machine in public DNS? If not, # we can't do domain control validation. For IPv6 is configured, # make sure both IPv4 and IPv6 are correct because we don't know # how Let's Encrypt will connect. import dns.resolver for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: if not value: continue # IPv6 is not configured try: # Must make the qname absolute to prevent a fall-back lookup with a # search domain appended, by adding a period to the end. response = dns.resolver.query(domain + ".", rtype) except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e: problems[ domain] = "DNS isn't configured properly for this domain: DNS resolution failed (%s: %s)." % ( rtype, str(e) or repr(e)) # NoAnswer's str is empty return False except Exception as e: problems[ domain] = "DNS isn't configured properly for this domain: DNS lookup had an error: %s." % str( e) return False # Unfortunately, the response.__str__ returns bytes # instead of string, if it resulted from an AAAA-query. # We need to convert manually, until this is fixed: # https://github.com/rthalley/dnspython/issues/204 # # BEGIN HOTFIX def rdata__str__(r): s = r.to_text() if isinstance(s, bytes): s = s.decode('utf-8') return s # END HOTFIX if len(response) != 1 or normalize_ip(rdata__str__( response[0])) != normalize_ip(value): problems[ domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % ( rtype, ", ".join(rdata__str__(r) for r in response)) return False return True
def can_provision_for_domain(domain): from status_checks import normalize_ip # Does the domain resolve to this machine in public DNS? If not, # we can't do domain control validation. For IPv6 is configured, # make sure both IPv4 and IPv6 are correct because we don't know # how Let's Encrypt will connect. import dns.resolver for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: if not value: continue # IPv6 is not configured try: # Must make the qname absolute to prevent a fall-back lookup with a # search domain appended, by adding a period to the end. response = dns.resolver.query(domain + ".", rtype) except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e: problems[domain] = "DNS isn't configured properly for this domain: DNS resolution failed (%s: %s)." % (rtype, str(e) or repr(e)) # NoAnswer's str is empty return False except Exception as e: problems[domain] = "DNS isn't configured properly for this domain: DNS lookup had an error: %s." % str(e) return False # Unfortunately, the response.__str__ returns bytes # instead of string, if it resulted from an AAAA-query. # We need to convert manually, until this is fixed: # https://github.com/rthalley/dnspython/issues/204 # # BEGIN HOTFIX def rdata__str__(r): s = r.to_text() if isinstance(s, bytes): s = s.decode('utf-8') return s # END HOTFIX if len(response) != 1 or normalize_ip(rdata__str__(response[0])) != normalize_ip(value): problems[domain] = "Domain control validation cannot be performed for this domain because DNS points the domain to another machine (%s %s)." % (rtype, ", ".join(rdata__str__(r) for r in response)) return False return True
def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True): # Get a set of domain names that we can provision certificates for # using certbot. We start with domains that the box is serving web # for and subtract: # * domains not in limit_domains if limit_domains is not empty # * domains with custom "A" records, i.e. they are hosted elsewhere # * domains with actual "A" records that point elsewhere (misconfiguration) # * domains that already have certificates that will be valid for a while from web_update import get_web_domains from status_checks import query_dns, normalize_ip existing_certs = get_ssl_certificates(env) plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False) actual_web_domains = get_web_domains(env) domains_to_provision = set() domains_cant_provision = {} for domain in plausible_web_domains: # Skip domains that the user doesn't want to provision now. if limit_domains and domain not in limit_domains: continue # Check that there isn't an explicit A/AAAA record. if domain not in actual_web_domains: domains_cant_provision[ domain] = "The domain has a custom DNS A/AAAA record that points the domain elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)." # Check that the DNS resolves to here. else: # Does the domain resolve to this machine in public DNS? If not, # we can't do domain control validation. For IPv6 is configured, # make sure both IPv4 and IPv6 are correct because we don't know # how Let's Encrypt will connect. bad_dns = [] for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: if not value: continue # IPv6 is not configured response = query_dns(domain, rtype) if response != normalize_ip(value): bad_dns.append("%s (%s)" % (response, rtype)) if bad_dns: domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \ + (", ".join(bad_dns)) \ + "." else: # DNS is all good. # Check for a good existing cert. existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True) if existing_cert: existing_cert_check = check_certificate( domain, existing_cert['certificate'], existing_cert['private-key'], warn_if_expiring_soon=14) if existing_cert_check[0] == "OK": if show_valid_certs: domains_cant_provision[ domain] = "The domain has a valid certificate already. ({} Certificate: {}, private key {})".format( existing_cert_check[1], existing_cert['certificate'], existing_cert['private-key']) continue domains_to_provision.add(domain) return (domains_to_provision, domains_cant_provision)
def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True): # Get a set of domain names that we can provision certificates for # using certbot. We start with domains that the box is serving web # for and subtract: # * domains not in limit_domains if limit_domains is not empty # * domains with custom "A" records, i.e. they are hosted elsewhere # * domains with actual "A" records that point elsewhere # * domains that already have certificates that will be valid for a while from web_update import get_web_domains from status_checks import query_dns, normalize_ip existing_certs = get_ssl_certificates(env) plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False) actual_web_domains = get_web_domains(env) domains_to_provision = set() domains_cant_provision = { } for domain in plausible_web_domains: # Skip domains that the user doesn't want to provision now. if limit_domains and domain not in limit_domains: continue # Check that there isn't an explicit A/AAAA record. if domain not in actual_web_domains: domains_cant_provision[domain] = "The domain has a custom DNS A/AAAA record that points the domain elsewhere, so there is no point to installing a TLS certificate here and we could not automatically provision one anyway because provisioning requires access to the website (which isn't here)." # Check that the DNS resolves to here. else: # Does the domain resolve to this machine in public DNS? If not, # we can't do domain control validation. For IPv6 is configured, # make sure both IPv4 and IPv6 are correct because we don't know # how Let's Encrypt will connect. bad_dns = [] for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: if not value: continue # IPv6 is not configured response = query_dns(domain, rtype) if response != normalize_ip(value): bad_dns.append("%s (%s)" % (response, rtype)) if bad_dns: domains_cant_provision[domain] = "The domain name does not resolve to this machine: " \ + (", ".join(bad_dns)) \ + "." else: # DNS is all good. # Check for a good existing cert. existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True) if existing_cert: existing_cert_check = check_certificate(domain, existing_cert['certificate'], existing_cert['private-key'], warn_if_expiring_soon=14) if existing_cert_check[0] == "OK": if show_valid_certs: domains_cant_provision[domain] = "The domain has a valid certificate already. ({} Certificate: {}, private key {})".format( existing_cert_check[1], existing_cert['certificate'], existing_cert['private-key']) continue domains_to_provision.add(domain) return (domains_to_provision, domains_cant_provision)