Class: GitHubPages::HealthCheck::Domain

Inherits:
Checkable
  • Object
show all
Defined in:
lib/github-pages-health-check/domain.rb

Constant Summary collapse

LEGACY_IP_ADDRESSES =
[
  # Legacy GitHub Datacenter
  "207.97.227.245",
  "204.232.175.78",

  # Aug. 2016 Fastly datacenter deprecation
  "199.27.73.133",
  "199.27.76.133",

  # Feb. 2017 Fastly datacenter deprecation
  "185.31.17.133",
  "185.31.18.133",
  "185.31.19.133",
  "199.27.74.133",
  "199.27.75.133",
  "199.27.79.133",
  "23.235.39.133",
  "23.235.43.133",
  "23.235.44.133",
  "23.235.46.133",
  "23.235.47.133",
  "45.32.88.68"
].freeze
CURRENT_IP_ADDRESSES =
%w(
  192.30.252.153
  192.30.252.154
).freeze
HASH_METHODS =
[
  :host, :uri, :dns_resolves?, :proxied?, :cloudflare_ip?, :fastly_ip?,
  :old_ip_address?, :a_record?, :cname_record?, :mx_records_present?,
  :valid_domain?, :apex_domain?, :should_be_a_record?,
  :cname_to_github_user_domain?, :cname_to_pages_dot_github_dot_com?,
  :cname_to_fastly?, :pointed_to_github_pages_ip?, :pages_domain?,
  :served_by_pages?, :valid_domain?, :https?, :enforces_https?, :https_error
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Checkable

#reason, #to_hash, #to_json, #to_s, #to_s_pretty, #valid?

Constructor Details

#initialize(host) ⇒ Domain

Returns a new instance of Domain.



45
46
47
48
49
50
51
# File 'lib/github-pages-health-check/domain.rb', line 45

def initialize(host)
  unless host.is_a? String
    raise ArgumentError, "Expected string, got #{host.class}"
  end

  @host = normalize_host(host)
end

Instance Attribute Details

#hostObject (readonly)

Returns the value of attribute host.



5
6
7
# File 'lib/github-pages-health-check/domain.rb', line 5

def host
  @host
end

Instance Method Details

#a_record?Boolean

Is this domain’s first response an A record?

Returns:

  • (Boolean)


225
226
227
228
# File 'lib/github-pages-health-check/domain.rb', line 225

def a_record?
  return unless dns?
  dns.first.class == Net::DNS::RR::A
end

#apex_domain?Boolean

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

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/github-pages-health-check/domain.rb', line 93

def apex_domain?
  return @apex_domain if defined?(@apex_domain)
  return unless valid_domain?

  answers = begin
    Resolv::DNS.open do |dns|
      dns.timeouts = TIMEOUT
      dns.getresources(absolute_domain, Resolv::DNS::Resource::IN::NS)
    end
  rescue Timeout::Error, NoMethodError
    []
  end

  @apex_domain = answers.any?
end

#check!Object

Runs all checks, raises an error if invalid



54
55
56
57
58
59
60
61
62
63
# File 'lib/github-pages-health-check/domain.rb', line 54

def check!
  raise Errors::InvalidDomainError, :domain => self unless valid_domain?
  raise Errors::InvalidDNSError, :domain => self    unless dns_resolves?
  raise Errors::DeprecatedIPError, :domain => self if deprecated_ip?
  return true if proxied?
  raise Errors::InvalidARecordError, :domain => self    if invalid_a_record?
  raise Errors::InvalidCNAMEError, :domain => self      if invalid_cname?
  raise Errors::NotServedByPagesError, :domain => self  unless served_by_pages?
  true
end

#cloudflare_ip?Boolean

Does the domain resolve to a CloudFlare-owned IP

Returns:

  • (Boolean)


162
163
164
# File 'lib/github-pages-health-check/domain.rb', line 162

def cloudflare_ip?
  cdn_ip?(CloudFlare)
end

#cnameObject

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



240
241
242
243
# File 'lib/github-pages-health-check/domain.rb', line 240

def cname
  return unless dns.first.class == Net::DNS::RR::CNAME
  @cname ||= Domain.new(dns.first.cname.to_s)
end

#cname_record?Boolean Also known as: cname?

Is this domain’s first response a CNAME record?

Returns:

  • (Boolean)


231
232
233
234
235
# File 'lib/github-pages-health-check/domain.rb', line 231

def cname_record?
  return unless dns?
  return false unless cname
  cname.valid_domain?
end

#cname_to_fastly?Boolean

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

Returns:

  • (Boolean)


137
138
139
# File 'lib/github-pages-health-check/domain.rb', line 137

def cname_to_fastly?
  cname? && !pages_domain? && cname.fastly?
end

#cname_to_github_user_domain?Boolean

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

Returns:

  • (Boolean)


124
125
126
# File 'lib/github-pages-health-check/domain.rb', line 124

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?Boolean

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, generaly the target of a cname

Returns:

  • (Boolean)


132
133
134
# File 'lib/github-pages-health-check/domain.rb', line 132

def cname_to_pages_dot_github_dot_com?
  cname? && cname.pages_dot_github_dot_com?
end

#deprecated_ip?Boolean

Returns:

  • (Boolean)


65
66
67
68
# File 'lib/github-pages-health-check/domain.rb', line 65

def deprecated_ip?
  return @deprecated_ip if defined? @deprecated_ip
  @deprecated_ip = (valid_domain? && a_record? && old_ip_address?)
end

#dnsObject

Returns an array of DNS answers



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/github-pages-health-check/domain.rb', line 190

def dns
  return @dns if defined? @dns
  return unless valid_domain?
  @dns = Timeout.timeout(TIMEOUT) do
    GitHubPages::HealthCheck.without_warnings do
      unless host.nil?
        resolver.search(absolute_domain, Net::DNS::A).answer +
          resolver.search(absolute_domain, Net::DNS::MX).answer
      end
    end
  end
rescue StandardError
  @dns = nil
end

#dns?Boolean Also known as: dns_resolves?

Are we even able to get the DNS record?

Returns:

  • (Boolean)


210
211
212
# File 'lib/github-pages-health-check/domain.rb', line 210

def dns?
  !(dns.nil? || dns.empty?)
end

#enforces_https?Boolean

Does this domain redirect HTTP requests to HTTPS?

Returns:

  • (Boolean)


281
282
283
284
285
# File 'lib/github-pages-health-check/domain.rb', line 281

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?Boolean

Is the host our Fastly CNAME?

Returns:

  • (Boolean)


157
158
159
# File 'lib/github-pages-health-check/domain.rb', line 157

def fastly?
  !!host.match(/\Agithub\.map\.fastly\.net\.?\z/i)
end

#fastly_ip?Boolean

Does the domain resolve to a Fastly-owned IP

Returns:

  • (Boolean)


167
168
169
# File 'lib/github-pages-health-check/domain.rb', line 167

def fastly_ip?
  cdn_ip?(Fastly)
end

#github_domain?Boolean

Is this domain owned by GitHub?

Returns:

  • (Boolean)


152
153
154
# File 'lib/github-pages-health-check/domain.rb', line 152

def github_domain?
  !!host.downcase.end_with?("github.com")
end

#https?Boolean

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

Returns:

  • (Boolean)


270
271
272
# File 'lib/github-pages-health-check/domain.rb', line 270

def https?
  https_response.return_code == :ok
end

#https_errorObject

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



276
277
278
# File 'lib/github-pages-health-check/domain.rb', line 276

def https_error
  https_response.return_code unless https?
end

#invalid_a_record?Boolean

Returns:

  • (Boolean)


70
71
72
73
# File 'lib/github-pages-health-check/domain.rb', line 70

def invalid_a_record?
  return @invalid_a_record if defined? @invalid_a_record
  @invalid_a_record = (valid_domain? && a_record? && !should_be_a_record?)
end

#invalid_cname?Boolean

Returns:

  • (Boolean)


75
76
77
78
79
80
81
82
83
# File 'lib/github-pages-health-check/domain.rb', line 75

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?Boolean

Returns:

  • (Boolean)


245
246
247
248
# File 'lib/github-pages-health-check/domain.rb', line 245

def mx_records_present?
  return unless dns?
  dns.any? { |answer| answer.class == Net::DNS::RR::MX }
end

#old_ip_address?Boolean

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

Returns:

  • (Boolean)


216
217
218
219
220
221
222
# File 'lib/github-pages-health-check/domain.rb', line 216

def old_ip_address?
  return unless dns?

  dns.any? do |answer|
    answer.is_a?(Net::DNS::RR::A) && legacy_ip?(answer.address.to_s)
  end
end

#pages_domain?Boolean

Is the host a *.github.io domain?

Returns:

  • (Boolean)


142
143
144
# File 'lib/github-pages-health-check/domain.rb', line 142

def pages_domain?
  !!host.match(/\A[\w-]+\.github\.(io|com)\.?\z/i)
end

#pages_dot_github_dot_com?Boolean

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

Returns:

  • (Boolean)


147
148
149
# File 'lib/github-pages-health-check/domain.rb', line 147

def pages_dot_github_dot_com?
  !!host.match(/\Apages\.github\.(io|com)\.?\z/i)
end

#pointed_to_github_pages_ip?Boolean

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

Returns:

  • (Boolean)


119
120
121
# File 'lib/github-pages-health-check/domain.rb', line 119

def pointed_to_github_pages_ip?
  a_record? && CURRENT_IP_ADDRESSES.include?(dns.first.value)
end

#proxied?Boolean

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

Returns:

  • (Boolean)


179
180
181
182
183
184
185
186
187
# File 'lib/github-pages-health-check/domain.rb', line 179

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_pages_dot_github_dot_com?
  return false if cname_to_fastly? || fastly_ip?
  served_by_pages?
end

#resolverObject



205
206
207
# File 'lib/github-pages-health-check/domain.rb', line 205

def resolver
  @resolver ||= Net::DNS::Resolver.new
end

#served_by_pages?Boolean

Returns:

  • (Boolean)


250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/github-pages-health-check/domain.rb', line 250

def served_by_pages?
  return @served_by_pages if defined? @served_by_pages
  return unless dns_resolves?

  @served_by_pages = begin
    return false unless response.mock? || response.return_code == :ok
    return true if response.headers["Server"] == "GitHub.com"

    # Typhoeus mangles the case of the header, compare insensitively
    response.headers.any? { |k, _v| k =~ /X-GitHub-Request-Id/i }
  end
end

#should_be_a_record?Boolean

Should the domain be an apex record?

Returns:

  • (Boolean)


110
111
112
# File 'lib/github-pages-health-check/domain.rb', line 110

def should_be_a_record?
  !pages_domain? && (apex_domain? || mx_records_present?)
end

#should_be_cname_record?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/github-pages-health-check/domain.rb', line 114

def should_be_cname_record?
  !should_be_a_record?
end

#uri(overrides = {}) ⇒ Object



263
264
265
266
267
# File 'lib/github-pages-health-check/domain.rb', line 263

def uri(overrides = {})
  options = { :host => host, :scheme => scheme, :path => "/" }
  options = options.merge(overrides)
  Addressable::URI.new(options).normalize.to_s
end

#valid_domain?Boolean

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

Returns:

  • (Boolean)


87
88
89
90
# File 'lib/github-pages-health-check/domain.rb', line 87

def valid_domain?
  return @valid if defined? @valid
  @valid = PublicSuffix.valid?(host, :default_rule => nil)
end