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",

  # Legacy Fastly Datacenter
  "199.27.73.133",
  "199.27.76.133"
].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?, :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?
].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.



31
32
33
34
35
36
37
# File 'lib/github-pages-health-check/domain.rb', line 31

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)


194
195
196
197
# File 'lib/github-pages-health-check/domain.rb', line 194

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)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/github-pages-health-check/domain.rb', line 79

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

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

  @apex_domain = answers.any?
end

#check!Object

Runs all checks, raises an error if invalid



40
41
42
43
44
45
46
47
48
49
# File 'lib/github-pages-health-check/domain.rb', line 40

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::NotServedByPagesError.new(domain: self)  unless served_by_pages?
  true
end

#cloudflare_ip?Boolean

Does the domain resolve to a CloudFlare-owned IP

Returns:

  • (Boolean)


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

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



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

def cname
  return unless 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)


200
201
202
203
# File 'lib/github-pages-health-check/domain.rb', line 200

def cname_record?
  return unless dns?
  dns.first.class == Net::DNS::RR::CNAME
end

#cname_to_fastly?Boolean

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

Returns:

  • (Boolean)


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

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)


106
107
108
# File 'lib/github-pages-health-check/domain.rb', line 106

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)


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

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

#deprecated_ip?Boolean

Returns:

  • (Boolean)


51
52
53
54
# File 'lib/github-pages-health-check/domain.rb', line 51

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



168
169
170
171
172
173
174
175
176
177
178
# File 'lib/github-pages-health-check/domain.rb', line 168

def dns
  return @dns if defined? @dns
  return unless valid_domain?
  @dns = Timeout.timeout(TIMEOUT) do
    GitHubPages::HealthCheck.without_warnings do
      Net::DNS::Resolver.start(absolute_domain).answer unless host.nil?
    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)


181
182
183
# File 'lib/github-pages-health-check/domain.rb', line 181

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

#fastly?Boolean

Is the host our Fastly CNAME?

Returns:

  • (Boolean)


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

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)


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

def fastly_ip?
  cdn_ip?(Fastly)
end

#github_domain?Boolean

Is this domain owned by GitHub?

Returns:

  • (Boolean)


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

def github_domain?
  !!host.match(/\.github\.com\z/)
end

#invalid_a_record?Boolean

Returns:

  • (Boolean)


56
57
58
59
# File 'lib/github-pages-health-check/domain.rb', line 56

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)


61
62
63
64
65
66
67
68
69
# File 'lib/github-pages-health-check/domain.rb', line 61

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

#old_ip_address?Boolean

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

Returns:

  • (Boolean)


187
188
189
190
191
# File 'lib/github-pages-health-check/domain.rb', line 187

def old_ip_address?
  dns.any? do |answer|
    answer.class == Net::DNS::RR::A && LEGACY_IP_ADDRESSES.include?(answer.address.to_s)
  end if dns?
end

#pages_domain?Boolean

Is the host a *.github.io domain?

Returns:

  • (Boolean)


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

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)


129
130
131
# File 'lib/github-pages-health-check/domain.rb', line 129

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)


101
102
103
# File 'lib/github-pages-health-check/domain.rb', line 101

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)


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

def proxied?
  return unless dns?
  return true if cloudflare_ip?
  return false if pointed_to_github_pages_ip? || cname_to_github_user_domain?
  return false if cname_to_pages_dot_github_dot_com? || cname_to_fastly? || fastly_ip?
  served_by_pages?
end

#served_by_pages?Boolean

Returns:

  • (Boolean)


213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/github-pages-health-check/domain.rb', line 213

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

  @served_by_pages = begin
    response = Typhoeus.head(uri, 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"], TYPHOEUS_OPTIONS)
    end

    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)


96
97
98
# File 'lib/github-pages-health-check/domain.rb', line 96

def should_be_a_record?
  !pages_domain? && apex_domain?
end

#uriObject



234
235
236
237
238
239
# File 'lib/github-pages-health-check/domain.rb', line 234

def uri
  @uri ||= begin
    options = { :host => host, :scheme => scheme, :path => "/" }
    Addressable::URI.new(options).normalize.to_s
  end
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)


73
74
75
76
# File 'lib/github-pages-health-check/domain.rb', line 73

def valid_domain?
  return @valid if defined? @valid
  @valid = PublicSuffix.valid?(host)
end