Skip to content

Commit 3c1674f

Browse files
committed
Merge pull request #41 from github/check-cname
Check if domain is CNAME to pages.github.com or directly to Fastly
2 parents 7f402ee + 21c684d commit 3c1674f

File tree

3 files changed

+129
-22
lines changed

3 files changed

+129
-22
lines changed

lib/github-pages-health-check/domain.rb

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class Domain < Checkable
1818
HASH_METHODS = [
1919
:host, :uri, :dns_resolves?, :proxied?, :cloudflare_ip?,
2020
:old_ip_address?, :a_record?, :cname_record?, :valid_domain?,
21-
:apex_domain?, :should_be_a_record?, :pointed_to_github_user_domain?,
21+
:apex_domain?, :should_be_a_record?, :cname_to_github_user_domain?,
22+
:cname_to_pages_dot_github_dot_com?, :cname_to_fastly?,
2223
:pointed_to_github_pages_ip?, :pages_domain?, :served_by_pages?,
2324
:valid_domain?
2425
].freeze
@@ -27,7 +28,7 @@ def initialize(host)
2728
unless host.is_a? String
2829
raise ArgumentError, "Expected string, got #{host.class}"
2930
end
30-
31+
3132
@host = host_from_uri(host)
3233
end
3334

@@ -54,8 +55,12 @@ def invalid_a_record?
5455

5556
def invalid_cname?
5657
return @invalid_cname if defined? @invalid_cname
57-
@invalid_cname = !valid_domain? ||
58-
!(github_domain? || apex_domain? || pointed_to_github_user_domain?)
58+
@invalid_cname = begin
59+
return false unless valid_domain?
60+
return false if github_domain? || apex_domain?
61+
return true if cname_to_pages_dot_github_dot_com? || cname_to_fastly?
62+
!cname_to_github_user_domain?
63+
end
5964
end
6065

6166
# Is this a valid domain that PublicSuffix recognizes?
@@ -81,29 +86,50 @@ def should_be_a_record?
8186
!pages_domain? && apex_domain?
8287
end
8388

84-
# Is the domain's first response a CNAME to a pages domain?
85-
def pointed_to_github_user_domain?
86-
dns.first.class == Net::DNS::RR::CNAME && pages_domain?(dns.first.cname.to_s) if dns?
87-
end
88-
8989
# Is the domain's first response an A record to a valid GitHub Pages IP?
9090
def pointed_to_github_pages_ip?
91-
dns.first.class == Net::DNS::RR::A && CURRENT_IP_ADDRESSES.include?(dns.first.value) if dns?
91+
a_record? && CURRENT_IP_ADDRESSES.include?(dns.first.value)
9292
end
9393

94-
# Is the given cname a pages domain?
94+
# Is the domain's first response a CNAME to a pages domain?
95+
def cname_to_github_user_domain?
96+
cname? && !cname_to_pages_dot_github_dot_com? && cname.pages_domain?
97+
end
98+
99+
# Is the given domain a CNAME to pages.github.(io|com)
100+
# instead of being CNAME'd to the user's subdomain?
95101
#
96102
# domain - the domain to check, generaly the target of a cname
97-
def pages_domain?(domain = nil)
98-
domain ||= host
99-
!!domain.match(/^[\w-]+\.github\.(io|com)\.?$/i)
103+
def cname_to_pages_dot_github_dot_com?
104+
cname? && cname.pages_dot_github_dot_com?
105+
end
106+
107+
# Is the given domain CNAME'd directly to our Fastly account?
108+
def cname_to_fastly?
109+
cname? && !pages_domain? && cname.fastly?
110+
end
111+
112+
# Is the host a *.github.io domain?
113+
def pages_domain?
114+
!!host.match(/\A[\w-]+\.github\.(io|com)\.?\z/i)
115+
end
116+
117+
# Is the host pages.github.com or pages.github.io?
118+
def pages_dot_github_dot_com?
119+
!!host.match(/\Apages\.github\.(io|com)\.?\z/i)
100120
end
101121

102122
# Is this domain owned by GitHub?
103123
def github_domain?
104-
!!host.match(/\.github\.com$/)
124+
!!host.match(/\.github\.com\z/)
125+
end
126+
127+
# Is the host our Fastly CNAME?
128+
def fastly?
129+
!!host.match(/\Agithub\.map\.fastly\.net\.?\z/i)
105130
end
106131

132+
# Does the domain resolve to a CloudFlare-owned IP
107133
def cloudflare_ip?
108134
return unless dns?
109135
dns.all? do |answer|
@@ -120,7 +146,8 @@ def cloudflare_ip?
120146
def proxied?
121147
return unless dns?
122148
return true if cloudflare_ip?
123-
return false if pointed_to_github_pages_ip? || pointed_to_github_user_domain?
149+
return false if pointed_to_github_pages_ip? || cname_to_github_user_domain?
150+
return false if cname_to_pages_dot_github_dot_com? || cname_to_fastly?
124151
served_by_pages?
125152
end
126153

@@ -151,12 +178,22 @@ def old_ip_address?
151178

152179
# Is this domain's first response an A record?
153180
def a_record?
154-
dns.first.class == Net::DNS::RR::A if dns?
181+
return unless dns?
182+
dns.first.class == Net::DNS::RR::A
155183
end
156184

157185
# Is this domain's first response a CNAME record?
158186
def cname_record?
159-
dns.first.class == Net::DNS::RR::CNAME if dns?
187+
return unless dns?
188+
dns.first.class == Net::DNS::RR::CNAME
189+
end
190+
alias cname? cname_record?
191+
192+
# The domain to which this domain's CNAME resolves
193+
# Returns nil if the domain is not a CNAME
194+
def cname
195+
return unless cname?
196+
@cname ||= Domain.new(dns.first.cname.to_s)
160197
end
161198

162199
def served_by_pages?

script/check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ if ARGV.count != 1
88
exit 1
99
end
1010

11-
puts GitHubPages::HealthCheck.new(ARGV[0])
11+
puts GitHubPages::HealthCheck.check(ARGV[0])

spec/github_pages_health_check_domain_spec.rb

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ def cname_packet(domain)
7373
expect(domain_check.a_record?).to be(false)
7474
end
7575

76+
it "returns the cname" do
77+
domain_check = make_domain_check
78+
allow(domain_check).to receive(:dns) { [cname_packet("pages.github.com")] }
79+
expect(domain_check.cname.host).to eql("pages.github.com")
80+
end
81+
7682
it "knows a subdomain is not an apex domain" do
7783
domain_check = make_domain_check "blog.parkermoore.de"
7884
expect(domain_check.apex_domain?).to be(false)
@@ -105,12 +111,12 @@ def cname_packet(domain)
105111
["parkr.github.io", "mattr-.github.com"].each do |domain|
106112
domain_check = make_domain_check
107113
allow(domain_check).to receive(:dns) { [cname_packet(domain)] }
108-
expect(domain_check.pointed_to_github_user_domain?).to be(true)
114+
expect(domain_check.cname_to_github_user_domain?).to be(true)
109115
end
110116
["github.com", "ben.balter.com"].each do |domain|
111117
domain_check = make_domain_check
112118
allow(domain_check).to receive(:dns) { [cname_packet(domain)] }
113-
expect(domain_check.pointed_to_github_user_domain?).to be(false)
119+
expect(domain_check.cname_to_github_user_domain?).to be(false)
114120
end
115121
end
116122

@@ -120,9 +126,61 @@ def cname_packet(domain)
120126
expect(domain_check.valid_domain?).to be(true)
121127
expect(domain_check.github_domain?).to be(false)
122128
expect(domain_check.apex_domain?).to be(false)
123-
expect(domain_check.pointed_to_github_user_domain?).to eql(false)
129+
expect(domain_check.cname_to_github_user_domain?).to eql(false)
130+
expect(domain_check.invalid_cname?).to eql(true)
131+
end
132+
133+
it "flags CNAMEs to pages.github.com as invalid" do
134+
domain_check = make_domain_check("foo.github.biz")
135+
allow(domain_check).to receive(:dns) { [cname_packet("pages.github.com")] }
136+
expect(domain_check.invalid_cname?).to eql(true)
137+
end
138+
139+
it "flags CNAMEs directly to fastly as invalid" do
140+
domain_check = make_domain_check("foo.github.biz")
141+
allow(domain_check).to receive(:dns) { [cname_packet("github.map.fastly.net")] }
124142
expect(domain_check.invalid_cname?).to eql(true)
125143
end
144+
145+
it "knows CNAMEs to user subdomains are valid" do
146+
domain_check = make_domain_check("foo.github.biz")
147+
allow(domain_check).to receive(:dns) { [cname_packet("foo.github.io")] }
148+
expect(domain_check.invalid_cname?).to eql(false)
149+
end
150+
151+
it "knows when the domain is CNAME'd to a user domain" do
152+
domain_check = make_domain_check("foo.github.biz")
153+
allow(domain_check).to receive(:dns) { [cname_packet("foo.github.io")] }
154+
expect(domain_check.cname_to_github_user_domain?).to eql(true)
155+
end
156+
157+
it "knows when the domain is CNAME'd to pages.github.com" do
158+
domain_check = make_domain_check("foo.github.biz")
159+
allow(domain_check).to receive(:dns) { [cname_packet("pages.github.com")] }
160+
expect(domain_check.cname_to_pages_dot_github_dot_com?).to eql(true)
161+
end
162+
163+
it "knows when the domain is CNAME'd to pages.github.io" do
164+
domain_check = make_domain_check("foo.github.biz")
165+
allow(domain_check).to receive(:dns) { [cname_packet("pages.github.io")] }
166+
expect(domain_check.cname_to_pages_dot_github_dot_com?).to eql(true)
167+
end
168+
169+
it "knows when the domain is CNAME'd to fastly" do
170+
domain_check = make_domain_check("foo.github.biz")
171+
allow(domain_check).to receive(:dns) { [cname_packet("github.map.fastly.net")] }
172+
expect(domain_check.cname_to_fastly?).to eql(true)
173+
end
174+
end
175+
176+
it "knows if the domain is a github domain" do
177+
domain_check = make_domain_check("government.github.com")
178+
expect(domain_check.github_domain?).to eql(true)
179+
end
180+
181+
it "knows if the domain is a fastly domain" do
182+
domain_check = make_domain_check("github.map.fastly.net")
183+
expect(domain_check.fastly?).to eql(true)
126184
end
127185

128186
context "apex domains" do
@@ -288,11 +346,23 @@ def cname_packet(domain)
288346
end
289347

290348
it "knows a site pointed to a Pages domain isn't proxied" do
349+
domain_check = make_domain_check
350+
allow(domain_check).to receive(:dns) { [cname_packet("foo.github.io")] }
351+
expect(domain_check.proxied?).to be(false)
352+
end
353+
354+
it "knows a site CNAMEd to pages.github.com isn't proxied" do
291355
domain_check = make_domain_check
292356
allow(domain_check).to receive(:dns) { [cname_packet("pages.github.com")] }
293357
expect(domain_check.proxied?).to be(false)
294358
end
295359

360+
it "knows a site CNAME'd directly to Fastly isn't proxied" do
361+
domain_check = make_domain_check('foo.github.biz')
362+
allow(domain_check).to receive(:dns) { [cname_packet("github.map.fastly.net")] }
363+
expect(domain_check.proxied?).to be(false)
364+
end
365+
296366
it "detects proxied sites" do
297367
stub_request(:head, "http://management.cio.gov").
298368
to_return(:status => 200, :headers => {:server => "GitHub.com"})

0 commit comments

Comments
 (0)