class GitHubPages::HealthCheck::Domain

Constants

CURRENT_IPV6_ADDRESSES
CURRENT_IP_ADDRESSES
CURRENT_IP_ADDRESSES_ALL
HASH_METHODS
LEGACY_IP_ADDRESSES
REQUESTED_RECORD_TYPES

Attributes

host[R]
nameservers[R]
resolver[R]

Public Class Methods

new(host, nameservers: :default) click to toggle source
# File lib/github-pages-health-check/domain.rb, line 105
def initialize(host, nameservers: :default)
  unless host.is_a? String
    raise ArgumentError, "Expected string, got #{host.class}"
  end

  @host = normalize_host(host)
  @nameservers = nameservers
  @resolver = GitHubPages::HealthCheck::Resolver.new(self.host,
                                                     :nameservers => nameservers)
end
redundant(host) click to toggle source
# File lib/github-pages-health-check/domain.rb, line 101
def self.redundant(host)
  GitHubPages::HealthCheck::RedundantCheck.new(host).check
end

Public Instance Methods

a_record?() click to toggle source

Is this domain’s first response an A record?

# File lib/github-pages-health-check/domain.rb, line 371
def a_record?
  return @is_a_record if defined?(@is_a_record)
  return unless dns?

  @is_a_record = Dnsruby::Types::A == dns.first.type
end
a_record_present?() click to toggle source

Does this domain has an A record setup (not necessarily as the first record)?

# File lib/github-pages-health-check/domain.rb, line 387
def a_record_present?
  return unless dns?

  dns.any? { |answer| answer.type == Dnsruby::Types::A && answer.name.to_s == host }
end
aaaa_record?() click to toggle source

Is this domain’s first response an AAAA record?

# File lib/github-pages-health-check/domain.rb, line 379
def aaaa_record?
  return @is_aaaa_record if defined?(@is_aaaa_record)
  return unless dns?

  @is_aaaa_record = Dnsruby::Types::AAAA == dns.first.type
end
aaaa_record_present?() click to toggle source

Does this domain has an AAAA record setup (not necessarily as the first record)?

# File lib/github-pages-health-check/domain.rb, line 394
def aaaa_record_present?
  return unless dns?

  dns.any? { |answer| answer.type == Dnsruby::Types::AAAA && answer.name.to_s == host }
end
apex_domain?() click to toggle source

Is this domain an apex domain, meaning a CNAME would be inappropriate

# File lib/github-pages-health-check/domain.rb, line 174
def apex_domain?
  return @apex_domain if defined?(@apex_domain)

  return false unless valid_domain?

  return true if dns_zone_soa? && dns_zone_ns?

  # PublicSuffix.domain pulls out the apex-level domain name.
  # E.g. PublicSuffix.domain("techblog.netflix.com") # => "netflix.com"
  # It's aware of multi-step top-level domain names:
  # E.g. PublicSuffix.domain("blog.digital.gov.uk") # => "digital.gov.uk"
  # For apex-level domain names, DNS providers do not support CNAME records.
  unicode_host = Addressable::IDNA.to_unicode(host)
  PublicSuffix.domain(unicode_host,
                      :default_rule => nil,
                      :ignore_private => true) == unicode_host
end
caa_error() click to toggle source

Any errors querying CAA records

# File lib/github-pages-health-check/domain.rb, line 486
def caa_error
  return nil unless caa&.errored?

  caa.error.class.name
end
check!() click to toggle source

Runs all checks, raises an error if invalid rubocop:disable Metrics/AbcSize

# File lib/github-pages-health-check/domain.rb, line 118
def check!
  raise Errors::InvalidDomainError.new :domain => self unless valid_domain?
  raise Errors::InvalidDNSError.new :domain => self    unless dns_resolves?
  raise Errors::DeprecatedIPError.new :domain => self  if deprecated_ip?
  return true if proxied?
  raise Errors::InvalidARecordError.new :domain => self    if invalid_a_record?
  raise Errors::InvalidCNAMEError.new :domain => self      if invalid_cname?
  raise Errors::InvalidAAAARecordError.new :domain => self if invalid_aaaa_record?
  raise Errors::NotServedByPagesError.new :domain => self  unless served_by_pages?

  true
end
cloudflare_ip?() click to toggle source

Does the domain resolve to a CloudFlare-owned IP

# File lib/github-pages-health-check/domain.rb, line 299
def cloudflare_ip?
  cdn_ip?(CloudFlare)
end
cname() click to toggle source

The domain to which this domain’s CNAME resolves Returns nil if the domain is not a CNAME

# File lib/github-pages-health-check/domain.rb, line 411
def cname
  return unless dns?

  cnames = dns.take_while { |answer| answer.type == Dnsruby::Types::CNAME }
  return if cnames.empty?

  www_cname(cnames.last)
  @cname ||= Domain.new(cnames.last.cname.to_s)
end
cname?()
Alias for: cname_record?
cname_record?() click to toggle source

Is this domain’s first response a CNAME record?

# File lib/github-pages-health-check/domain.rb, line 401
def cname_record?
  return unless dns?
  return false unless cname

  cname.valid_domain?
end
Also aliased as: cname?
cname_to_domain_to_pages?() click to toggle source

Check if the CNAME points to a Domain that points to pages e.g. CNAME -> Domain -> Pages rubocop:disable Metrics/AbcSize

# File lib/github-pages-health-check/domain.rb, line 249
def cname_to_domain_to_pages?
  return false unless dns?

  a_record_to_pages = dns.select { |d| d.type == Dnsruby::Types::A && d.name.to_s == host }.first

  return false unless a_record_to_pages && cname? && !cname_to_pages_dot_github_dot_com? && @www_cname

  CURRENT_IP_ADDRESSES.include?(a_record_to_pages.address.to_s.downcase)
end
cname_to_fastly?() click to toggle source

Is the given domain CNAME’d directly to our Fastly account?

# File lib/github-pages-health-check/domain.rb, line 269
def cname_to_fastly?
  cname? && !pages_domain? && cname.fastly?
end
cname_to_github_user_domain?() click to toggle source

Is the domain’s first response a CNAME to a pages domain?

# File lib/github-pages-health-check/domain.rb, line 242
def cname_to_github_user_domain?
  cname? && !cname_to_pages_dot_github_dot_com? && cname.pages_domain?
end
cname_to_pages_dot_github_dot_com?() click to toggle source

Is the given domain a CNAME to pages.github.(io|com) instead of being CNAME’d to the user’s subdomain?

domain - the domain to check, generally the target of a cname

# File lib/github-pages-health-check/domain.rb, line 264
def cname_to_pages_dot_github_dot_com?
  cname? && cname.pages_dot_github_dot_com?
end
deprecated_ip?() click to toggle source

rubocop:enable Metrics/AbcSize

# File lib/github-pages-health-check/domain.rb, line 132
def deprecated_ip?
  return @deprecated_ip if defined? @deprecated_ip

  @deprecated_ip = (valid_domain? && a_record? && old_ip_address?)
end
dns() click to toggle source

Returns an array of DNS answers

# File lib/github-pages-health-check/domain.rb, line 338
def dns
  return @dns if defined? @dns
  return unless valid_domain?

  @dns = Timeout.timeout(TIMEOUT) do
    GitHubPages::HealthCheck.without_warnings do
      next if host.nil?

      REQUESTED_RECORD_TYPES
        .map { |type| resolver.query(type) }
        .flatten.uniq
    end
  end
rescue StandardError
  @dns = nil
end
dns?() click to toggle source

Are we even able to get the DNS record?

# File lib/github-pages-health-check/domain.rb, line 356
def dns?
  !(dns.nil? || dns.empty?)
end
Also aliased as: dns_resolves?
dns_resolves?()
Alias for: dns?
dns_zone_ns?() click to toggle source

Does the domain have associated NS records?

# File lib/github-pages-health-check/domain.rb, line 207
def dns_zone_ns?
  return @ns_records if defined?(@ns_records)
  return false unless dns?

  @ns_records = dns.any? do |answer|
    answer.type == Dnsruby::Types::NS && answer.name.to_s == host
  end
end
dns_zone_soa?() click to toggle source

Does the domain have an associated SOA record?

# File lib/github-pages-health-check/domain.rb, line 195
def dns_zone_soa?
  return @soa_records if defined?(@soa_records)
  return false unless dns?

  @soa_records = dns.any? do |answer|
    answer.type == Dnsruby::Types::SOA && answer.name.to_s == host
  end
end
enforces_https?() click to toggle source

Does this domain redirect HTTP requests to HTTPS?

# File lib/github-pages-health-check/domain.rb, line 463
def enforces_https?
  return false unless https? && http_response.headers["Location"]

  redirect = Addressable::URI.parse(http_response.headers["Location"])
  redirect.scheme == "https" && redirect.host == host
end
fastly?() click to toggle source

Is the host our Fastly CNAME?

# File lib/github-pages-health-check/domain.rb, line 294
def fastly?
  !!host.match(/\A#{Regexp.union(Fastly::HOSTNAMES)}\z/i)
end
fastly_ip?() click to toggle source

Does the domain resolve to a Fastly-owned IP

# File lib/github-pages-health-check/domain.rb, line 304
def fastly_ip?
  cdn_ip?(Fastly)
end
github_domain?() click to toggle source

Is this domain owned by GitHub?

# File lib/github-pages-health-check/domain.rb, line 289
def github_domain?
  host.downcase.eql?("github.com") || host.downcase.end_with?(".github.com")
end
https?() click to toggle source

Does this domain respond to HTTPS requests with a valid cert?

# File lib/github-pages-health-check/domain.rb, line 452
def https?
  https_response.return_code == :ok
end
https_eligible?() click to toggle source

Can an HTTPS certificate be issued for this domain?

# File lib/github-pages-health-check/domain.rb, line 471
def https_eligible?
  # Can't have any IP's which aren't GitHub's present.
  return false if non_github_pages_ip_present?

  # Can't have underscores in the domain name (Let's Encrypt does not allow it)
  return false if host.include?("_")

  # Must be a CNAME or point to our IPs.
  return true if cname_to_github_user_domain? || cname_to_domain_to_pages?

  # Check CAA records for the full domain and its parent domain.
  pointed_to_github_pages_ip? && caa.lets_encrypt_allowed?
end
https_error() click to toggle source

The response code of the HTTPS request, if it failed. Useful for diagnosing cert errors

# File lib/github-pages-health-check/domain.rb, line 458
def https_error
  https_response.return_code unless https?
end
invalid_a_record?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 144
def invalid_a_record?
  return @invalid_a_record if defined? @invalid_a_record

  @invalid_a_record = (valid_domain? && a_record_present? && !should_be_a_record?)
end
invalid_aaaa_record?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 138
def invalid_aaaa_record?
  return @invalid_aaaa_record if defined? @invalid_aaaa_record

  @invalid_aaaa_record = (valid_domain? && aaaa_record_present? && !should_be_a_record?)
end
invalid_cname?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 150
def invalid_cname?
  return @invalid_cname if defined? @invalid_cname

  @invalid_cname = begin
    return false unless valid_domain?
    return false if github_domain? || apex_domain?
    return true  if cname_to_pages_dot_github_dot_com? || cname_to_fastly?

    !cname_to_github_user_domain? && should_be_cname_record?
  end
end
mx_records_present?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 427
def mx_records_present?
  return unless dns?

  dns.any? { |answer| answer.type == Dnsruby::Types::MX }
end
non_github_pages_ip_present?() click to toggle source

Are any of the domain’s A or AAAA records pointing elsewhere?

# File lib/github-pages-health-check/domain.rb, line 233
def non_github_pages_ip_present?
  return unless dns?

  dns
    .select { |a| Dnsruby::Types::A == a.type || Dnsruby::Types::AAAA == a.type }
    .any? { |a| !github_pages_ip?(a.address.to_s) }
end
old_ip_address?() click to toggle source

Does this domain have any A record that points to the legacy IPs?

# File lib/github-pages-health-check/domain.rb, line 362
def old_ip_address?
  return unless dns?

  dns.any? do |answer|
    answer.type == Dnsruby::Types::A && legacy_ip?(answer.address.to_s)
  end
end
pages_domain?() click to toggle source

Is the host a *.github.(io|com) domain?

# File lib/github-pages-health-check/domain.rb, line 279
def pages_domain?
  !!host.match(/\A[\w-]+\.github\.(io|com)\.?\z/i)
end
pages_dot_github_dot_com?() click to toggle source

Is the host pages.github.com or pages.github.io?

# File lib/github-pages-health-check/domain.rb, line 284
def pages_dot_github_dot_com?
  !!host.match(/\Apages\.github\.(io|com)\.?\z/i)
end
pages_io_domain?() click to toggle source

Is the host a *.github.io domain?

# File lib/github-pages-health-check/domain.rb, line 274
def pages_io_domain?
  !!host.match(/\A[\w-]+\.github\.(io)\.?\z/i)
end
pointed_to_github_pages_ip?() click to toggle source

Is the domain’s first response an A or AAAA record to a valid GitHub Pages IP?

# File lib/github-pages-health-check/domain.rb, line 226
def pointed_to_github_pages_ip?
  return false unless address_record?

  CURRENT_IP_ADDRESSES_ALL.include?(dns.first.address.to_s.downcase)
end
proxied?() click to toggle source

Does this non-GitHub-pages domain proxy a GitHub Pages site?

This can be:

1. A Cloudflare-owned IP address
2. A site that returns GitHub.com server headers, but
   isn't CNAME'd to a GitHub domain
3. A site that returns GitHub.com server headers, but
   isn't CNAME'd to a GitHub IP
# File lib/github-pages-health-check/domain.rb, line 316
def proxied?
  return unless dns?
  return true if cloudflare_ip?
  return false if pointed_to_github_pages_ip?
  return false if cname_to_github_user_domain?
  return false if cname_to_domain_to_pages?
  return false if cname_to_pages_dot_github_dot_com?
  return false if cname_to_fastly? || fastly_ip?

  served_by_pages?
end
served_by_pages?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 433
def served_by_pages?
  return @served_by_pages if defined? @served_by_pages
  return unless dns_resolves?

  @served_by_pages = begin
    return true if response.headers["Server"] == "GitHub.com"

    # Typhoeus mangles the case of the header, compare insensitively
    response.headers.any? { |k, _v| k.downcase == "x-github-request-id" }
  end
end
should_be_a_record?() click to toggle source

Should the domain use an A record?

# File lib/github-pages-health-check/domain.rb, line 217
def should_be_a_record?
  !pages_io_domain? && (apex_domain? || mx_records_present?)
end
should_be_cname_record?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 221
def should_be_cname_record?
  !should_be_a_record?
end
uri(overrides = {}) click to toggle source
# File lib/github-pages-health-check/domain.rb, line 445
def uri(overrides = {})
  options = { :host => host, :scheme => scheme, :path => "/" }
  options = options.merge(overrides)
  Addressable::URI.new(options).normalize.to_s
end
valid_domain?() click to toggle source

Is this a valid domain that PublicSuffix recognizes? Used as an escape hatch to prevent false positives on DNS checks

# File lib/github-pages-health-check/domain.rb, line 164
def valid_domain?
  return @valid if defined? @valid

  unicode_host = Addressable::IDNA.to_unicode(host)
  @valid = PublicSuffix.valid?(unicode_host,
                               :default_rule => nil,
                               :ignore_private => true)
end
www_cname(cname) click to toggle source

Check if we have a ‘www.’ CNAME that matches the domain

# File lib/github-pages-health-check/domain.rb, line 422
def www_cname(cname)
  @www_cname ||= cname.name.to_s.start_with?("www.") &&
    cname.name.to_s.end_with?(cname.domainname.to_s)
end

Private Instance Methods

absolute_domain() click to toggle source

Adjust ‘domain` so that it won’t be searched for with /etc/resolv.conf

GitHubPages::HealthCheck.new("anything.io").absolute_domain
=> "anything.io."
# File lib/github-pages-health-check/domain.rb, line 560
def absolute_domain
  host.end_with?(".") ? host : "#{host}."
end
address_record?() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 494
def address_record?
  a_record? || aaaa_record?
end
caa() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 498
def caa
  @caa ||= GitHubPages::HealthCheck::CAA.new(
    :host => cname&.host || host,
    :nameservers => nameservers
  )
end
cdn_ip?(cdn) click to toggle source

Does the domain resolve to a CDN-owned IP

# File lib/github-pages-health-check/domain.rb, line 569
def cdn_ip?(cdn)
  return unless dns?

  address_records = dns.select do |answer|
    Dnsruby::Types::A == answer.type || Dnsruby::Types::AAAA == answer.type
  end
  return false if !address_records || address_records.empty?

  address_records.all? do |answer|
    cdn.controls_ip?(answer.address)
  end
end
github_pages_ip?(ip_addr) click to toggle source
# File lib/github-pages-health-check/domain.rb, line 586
def github_pages_ip?(ip_addr)
  CURRENT_IP_ADDRESSES_ALL.include?(ip_addr&.to_s&.downcase)
end
http_response() click to toggle source

The domain’s response to HTTP requests, without following redirects

# File lib/github-pages-health-check/domain.rb, line 521
def http_response
  options = GitHubPages::HealthCheck.typhoeus_options.merge(:followlocation => false)
  @http_response ||= Typhoeus.head(uri(:scheme => "http"), options)
end
https_response() click to toggle source

The domain’s response to HTTPS requests, without following redirects

# File lib/github-pages-health-check/domain.rb, line 527
def https_response
  options = GitHubPages::HealthCheck.typhoeus_options.merge(:followlocation => false)
  @https_response ||= Typhoeus.head(uri(:scheme => "https"), options)
end
legacy_ip?(ip_addr) click to toggle source
# File lib/github-pages-health-check/domain.rb, line 582
def legacy_ip?(ip_addr)
  LEGACY_IP_ADDRESSES.include?(ip_addr)
end
normalize_host(domain) click to toggle source

Parse the URI. Accept either domain names or full URI’s. Used by the initializer so we can be more flexible with inputs.

domain - a URI or domain name.

Examples

normalize_host("benbalter.github.com")
# => 'benbalter.github.com'
normalize_host("https://benbalter.github.com")
# => 'benbalter.github.com'
normalize_host("benbalter.github.com/help-me-im-a-path/")
# => 'benbalter.github.com'

Return the hostname.

# File lib/github-pages-health-check/domain.rb, line 547
def normalize_host(domain)
  domain = domain.strip.chomp(".")
  host = Addressable::URI.parse(domain).normalized_host
  host ||= Addressable::URI.parse("http://#{domain}").normalized_host
  host unless host.to_s.empty?
rescue Addressable::URI::InvalidURIError
  nil
end
response() click to toggle source

The domain’s response to HTTP(S) requests, following redirects

# File lib/github-pages-health-check/domain.rb, line 506
def response
  return @response if defined? @response

  @response = Typhoeus.head(uri, GitHubPages::HealthCheck.typhoeus_options)

  # Workaround for webmock not playing nicely with Typhoeus redirects
  # See https://github.com/bblimke/webmock/issues/237
  if @response.mock? && @response.headers["Location"]
    @response = Typhoeus.head(response.headers["Location"], GitHubPages::HealthCheck.typhoeus_options)
  end

  @response
end
scheme() click to toggle source
# File lib/github-pages-health-check/domain.rb, line 564
def scheme
  @scheme ||= github_domain? ? "https" : "http"
end