Class: GitHubPages::HealthCheck::Domain

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

Constant Summary collapse

LEGACY_IP_ADDRESSES =
%w[
  207.97.227.245
  204.232.175.78
  199.27.73.133
].freeze
CURRENT_IP_ADDRESSES =
%w[
  192.30.252.153
  192.30.252.154
].freeze
HASH_METHODS =
[
  :host, :uri, :dns_resolves?, :proxied?, :cloudflare_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.



27
28
29
30
31
32
33
# File 'lib/github-pages-health-check/domain.rb', line 27

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

  @host = host_from_uri(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)


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

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)


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

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

  answers = Resolv::DNS.open { |dns|
    dns.getresources(absolute_domain, Resolv::DNS::Resource::IN::NS)
  }

  @apex_domain = answers.any?
end

#check!Object

Runs all checks, raises an error if invalid



36
37
38
39
40
41
42
43
44
# File 'lib/github-pages-health-check/domain.rb', line 36

def check!
  raise Errors::InvalidDNSError unless dns_resolves?
  return true if proxied?
  raise Errors::DeprecatedIPError      if deprecated_ip?
  raise Errors::InvalidARecordError    if invalid_a_record?
  raise Errors::InvalidCNAMEError      if invalid_cname?
  raise Errors::NotServedByPagesError  unless served_by_pages?
  true
end

#cloudflare_ip?Boolean

Does the domain resolve to a CloudFlare-owned IP

Returns:

  • (Boolean)


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

def cloudflare_ip?
  return unless dns?
  dns.all? do |answer|
    answer.class == Net::DNS::RR::A && CloudFlare.controls_ip?(answer.address)
  end
end

#cnameObject

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



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

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)


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

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)


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

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)


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

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)


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

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

#deprecated_ip?Boolean

Returns:

  • (Boolean)


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

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

#dnsObject

Returns an array of DNS answers



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

def dns
  return @dns if defined? @dns
  @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)


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

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

#fastly?Boolean

Is the host our Fastly CNAME?

Returns:

  • (Boolean)


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

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

#github_domain?Boolean

Is this domain owned by GitHub?

Returns:

  • (Boolean)


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

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

#invalid_a_record?Boolean

Returns:

  • (Boolean)


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

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)


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

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)


173
174
175
176
177
# File 'lib/github-pages-health-check/domain.rb', line 173

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)


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

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)


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

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)


90
91
92
# File 'lib/github-pages-health-check/domain.rb', line 90

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)


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

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

#served_by_pages?Boolean

Returns:

  • (Boolean)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/github-pages-health-check/domain.rb', line 199

def served_by_pages?
  return @served_by_pages if defined? @served_by_pages

  @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)


85
86
87
# File 'lib/github-pages-health-check/domain.rb', line 85

def should_be_a_record?
  !pages_domain? && apex_domain?
end

#uriObject



219
220
221
222
223
224
# File 'lib/github-pages-health-check/domain.rb', line 219

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)


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

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