github-pages-health-check-1.16.1/0000755000004100000410000000000013440143231016520 5ustar www-datawww-datagithub-pages-health-check-1.16.1/.travis.yml0000644000004100000410000000025213440143231020630 0ustar www-datawww-datalanguage: ruby rvm: - 2.4 - 2.5 - 2.6 before_install: - gem install bundler script: "script/cibuild" notifications: email: false cache: bundler sudo: false github-pages-health-check-1.16.1/.rspec0000644000004100000410000000003613440143231017634 0ustar www-datawww-data--color --require spec_helper github-pages-health-check-1.16.1/README.md0000644000004100000410000000435313440143231020004 0ustar www-datawww-data# GitHub Pages Health Check *Checks your GitHub Pages site for common DNS configuration issues* [![Build Status](https://travis-ci.org/github/pages-health-check.svg)](https://travis-ci.org/github/pages-health-check) [![Gem Version](https://badge.fury.io/rb/github-pages-health-check.svg)](http://badge.fury.io/rb/github-pages-health-check) ## Installation `gem install github-pages-health-check` ## Usage ### Basic Usage ```ruby > check = GitHubPages::HealthCheck::Site.new("choosealicense.com") => # > check.valid? => true ``` ### An invalid domain ```ruby > check = GitHubPages::HealthCheck::Site.new("foo.github.com") > check.valid? => false > check.valid! raises GitHubPages::HealthCheck::Errors::InvalidCNAMEError ``` ### Retrieving specific checks ``` ruby > check.domain.should_be_a_record? => true > check.domain.a_record? => true ``` ### Getting checks in bulk ```ruby > check.to_hash => { :cloudflare_ip?=>false, :old_ip_address?=>false, :a_record?=>true, :cname_record?=>false, :valid_domain?=>true, :apex_domain?=>true, :should_be_a_record?=>true, :pointed_to_github_user_domain?=>false, :pointed_to_github_pages_ip?=>false, :pages_domain?=>false, :valid?=>true } > check.to_json => "{\"cloudflare_ip?\":false,\"old_ip_address?\":false,\"a_record?\":true,\"cname_record?\":false,\"valid_domain?\":true,\"apex_domain?\":true,\"should_be_a_record?\":true,\"pointed_to_github_user_domain?\":false,\"pointed_to_github_pages_ip?\":false,\"pages_domain?\":false,\"valid?\":true}" ``` ### Getting the reason a domain is invalid ```ruby > check = GitHubPages::HealthCheck::Site.new "developer.facebook.com" > check.valid? => false > check.reason => # > check.reason.message => "CNAME does not point to GitHub Pages" ``` ### Repository checks Repository checks require a personal access or OAuth token with `repo` or scope. This can be passed as the second argument to the Site or Repository constructors like so: ```ruby check = GitHubPages::HealthCheck::Site.new "github/pages-health-check", access_token: "1234 ``` You can also set `OCTOKIT_ACCESS_TOKEN` as an environmental variable, or via a `.env` file in your working directory. github-pages-health-check-1.16.1/LICENSE.md0000644000004100000410000000207613440143231020131 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2014 - 2017 GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. github-pages-health-check-1.16.1/.rubocop.yml0000644000004100000410000000721513440143231020777 0ustar www-datawww-data# Ruby linting configuration. # See https://github.com/styleguide/ruby for the Ruby style guide # We only worry about two kinds of issues: 'error' and anything less than that. # Error is not about severity, but about taste. Simple style choices that # never have a great excuse to be broken, such as 1.9 JSON-like hash syntax, # are errors. Choices that tend to have good exceptions in practice, such as # line length, are warnings. # If you'd like to make changes, a full list of available issues is at # https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml # # A list of configurable issues is at: # https://github.com/bbatsov/rubocop/blob/master/config/default.yml # # If you disable a check, document why. # AllCops: TargetRubyVersion: 2.2 Exclude: - 'bin/**/*' - 'script/**/*' - 'vendor/**/*' - 'test-site/**/*' Layout/EndAlignment: Severity: error Lint/UnreachableCode: Severity: error Style/StringLiterals: EnforcedStyle: double_quotes Severity: error Style/StringLiteralsInInterpolation: EnforcedStyle: double_quotes Style/HashSyntax: EnforcedStyle: hash_rockets Severity: error Layout/AlignHash: SupportedLastArgumentHashStyles: always_ignore Layout/AlignParameters: Enabled: false # This is usually true, but we often want to roll back to # the start of a line. Style/Attr: Enabled: false # We have no styleguide guidance here, and it seems to be # in frequent use. Style/ClassAndModuleChildren: Enabled: false # module X<\n>module Y is just as good as module X::Y. Style/PercentLiteralDelimiters: PreferredDelimiters: '%w': '{}' '%r': '{}' Metrics/LineLength: Max: 90 Severity: warning Exclude: - github-pages-health-check.gemspec - lib/github-pages-health-check/errors/*.rb Metrics/BlockLength: Enabled: false Style/MultilineTernaryOperator: Severity: error Style/AndOr: Severity: error Layout/IndentationWidth: Severity: error Metrics/MethodLength: CountComments: false # count full line comments? Max: 20 Severity: error Exclude: - lib/github-pages-health-check/printer.rb Style/Alias: Enabled: false # We have no guidance on alias vs alias_method Style/RedundantSelf: Enabled: false # Sometimes a self.field is a bit more clear Style/IfUnlessModifier: Enabled: false Naming/FileName: #Rubocop doesn't like the Git*H*ub namespace Enabled: false Metrics/ParameterLists: { Max: 4 } Metrics/AbcSize: { Max: 20 } Layout/IndentHash: { EnforcedStyle: consistent } Layout/MultilineMethodCallIndentation: { EnforcedStyle: indented } Layout/MultilineOperationIndentation: { EnforcedStyle: indented } Layout/FirstParameterIndentation: { EnforcedStyle: consistent } Layout/IndentArray: { EnforcedStyle: consistent } Layout/ExtraSpacing: { AllowForAlignment: true } Style/SignalException: { EnforcedStyle: only_raise } Style/StringLiterals: { EnforcedStyle: double_quotes } Style/PercentLiteralDelimiters: PreferredDelimiters: '%q': '{}' '%Q': '{}' '%r': '{}' '%s': '()' '%w': '()' '%W': '()' '%x': '()' Style/Documentation: Enabled: false Metrics/ClassLength: Exclude: - lib/github-pages-health-check/domain.rb Metrics/CyclomaticComplexity: Max: 9 Exclude: - lib/github-pages-health-check/printer.rb Metrics/PerceivedComplexity: Max: 9 Exclude: - lib/github-pages-health-check/printer.rb Metrics/AbcSize: Max: 17 Exclude: - lib/github-pages-health-check/printer.rb Style/DoubleNegation: Enabled: false Layout/EmptyLineAfterMagicComment: Exclude: - script/* Style/FrozenStringLiteralComment: Enabled: true Severity: error Gemspec/RequiredRubyVersion: Enabled: false github-pages-health-check-1.16.1/.gitignore0000644000004100000410000000007613440143231020513 0ustar www-datawww-data/*.gem *.lock .bundle vendor/gems /bin .env spec/examples.txt github-pages-health-check-1.16.1/script/0000755000004100000410000000000013440143231020024 5ustar www-datawww-datagithub-pages-health-check-1.16.1/script/test0000755000004100000410000000006613440143231020733 0ustar www-datawww-data#!/bin/sh set -e bundle exec rspec $@ script/fmt $@ github-pages-health-check-1.16.1/script/release0000755000004100000410000000153113440143231021372 0ustar www-datawww-data#!/bin/sh # Tag and push a release. set -e # Make sure we're in the project root. cd $(dirname "$0")/.. # Make sure the darn thing works bundle update # Build a new gem archive. rm -rf github-pages-health-check-*.gem gem build -q github-pages-health-check.gemspec # Make sure we're on the master branch. (git branch | grep -q '* master') || { echo "Only release from the master branch." exit 1 } # Figure out what version we're releasing. tag=v`ls github-pages-health-check-*.gem | sed 's/^github-pages-health-check-\(.*\)\.gem$/\1/'` # Make sure we haven't released this version before. git fetch -t origin (git tag -l | grep -q "$tag") && { echo "Whoops, there's already a '${tag}' tag." exit 1 } # Tag it and bag it. gem push github-pages-health-check-*.gem && git tag "$tag" && git push origin master && git push origin "$tag" github-pages-health-check-1.16.1/script/console0000755000004100000410000000011113440143231021405 0ustar www-datawww-data#!/bin/sh set -ex bundle exec pry -r "./lib/github-pages-health-check" github-pages-health-check-1.16.1/script/check0000755000004100000410000000037513440143231021034 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # # Usage: script/check [DOMAIN] require_relative "../lib/github-pages-health-check" if ARGV.count != 1 puts "Usage: script/check [DOMAIN]" exit 1 end puts GitHubPages::HealthCheck.check(ARGV[0]) github-pages-health-check-1.16.1/script/cibuild0000755000004100000410000000037713440143231021374 0ustar www-datawww-data#!/bin/sh set -ex script/bootstrap script/test script/check-cdn-ips bundle exec script/check www.parkermoore.de | grep 'valid?: true' bundle exec script/check ben.balter.com | grep 'valid?: true' bundle exec gem build github-pages-health-check.gemspec github-pages-health-check-1.16.1/script/check-cdn-ips0000755000004100000410000000075713440143231022373 0ustar www-datawww-data#!/bin/bash -e script/update-cdn-ips >/dev/null 2>&1 files=( cloudflare fastly) # `git diff --quiet` suppresses output and sets a return code # 0 - no changes # 1 - changes for file in "${files[@]}" do if git diff -w --quiet --cached "config/$file-ips.txt" then echo "$file IP list is up-to-date." else echo git reset "config/$file-ips.txt" git reset --quiet "config/$file-ips.txt" echo "*** $file IP list is out of date! Run script/update-cdn-ips!" exit 1 fi done github-pages-health-check-1.16.1/script/bootstrap0000755000004100000410000000004313440143231021764 0ustar www-datawww-data#!/bin/sh set -ex bundle install github-pages-health-check-1.16.1/script/update-cdn-ips0000755000004100000410000000105113440143231022564 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # # Usage script/update-ips # updates config/cloudflare-ips.txt and config/fastly-ips.txt require "open-uri" require "json" SOURCES = { :cloudflare => "https://www.cloudflare.com/ips-v4", :fastly => "https://api.fastly.com/public-ip-list" }.freeze SOURCES.each do |source, url| file = "config/#{source}-ips.txt" puts "Fetching #{url}..." data = open(url).read data = JSON.parse(data)["addresses"].join("\n") if source == :fastly File.write(file, data) `git add --verbose #{file}` end github-pages-health-check-1.16.1/script/fmt0000755000004100000410000000016013440143231020535 0ustar www-datawww-data#!/bin/bash #/ Usage: script/fmt [args] #/ Runs rubocop with the given arguments. bundle exec rubocop -D -S $@ github-pages-health-check-1.16.1/lib/0000755000004100000410000000000013440143231017266 5ustar www-datawww-datagithub-pages-health-check-1.16.1/lib/github-pages-health-check.rb0000644000004100000410000000420013440143231024504 0ustar www-datawww-data# frozen_string_literal: true require "dnsruby" require "addressable/idna" require "addressable/uri" require "ipaddr" require "public_suffix" require "singleton" require "net/http" require "typhoeus" require "resolv" require "timeout" require "octokit" require_relative "github-pages-health-check/version" if File.exist?(File.expand_path("../.env", File.dirname(__FILE__))) require "dotenv" Dotenv.load end module GitHubPages module HealthCheck autoload :CDN, "github-pages-health-check/cdn" autoload :CloudFlare, "github-pages-health-check/cdns/cloudflare" autoload :Fastly, "github-pages-health-check/cdns/fastly" autoload :Error, "github-pages-health-check/error" autoload :Errors, "github-pages-health-check/errors" autoload :CAA, "github-pages-health-check/caa" autoload :Checkable, "github-pages-health-check/checkable" autoload :Domain, "github-pages-health-check/domain" autoload :RedundantCheck, "github-pages-health-check/redundant_check" autoload :Repository, "github-pages-health-check/repository" autoload :Resolver, "github-pages-health-check/resolver" autoload :Site, "github-pages-health-check/site" autoload :Printer, "github-pages-health-check/printer" # DNS and HTTP timeout, in seconds TIMEOUT = 7 HUMAN_NAME = "GitHub Pages Health Check".freeze URL = "https://github.com/github/pages-health-check".freeze USER_AGENT = "Mozilla/5.0 (compatible; #{HUMAN_NAME}/#{VERSION}; +#{URL})".freeze TYPHOEUS_OPTIONS = { :followlocation => true, :timeout => TIMEOUT, :accept_encoding => "gzip", :method => :head, :headers => { "User-Agent" => USER_AGENT } }.freeze # surpress warn-level feedback due to unsupported record types def self.without_warnings(&block) warn_level = $VERBOSE $VERBOSE = nil result = block.call $VERBOSE = warn_level result end def self.check(repository_or_domain, access_token: nil) Site.new repository_or_domain, :access_token => access_token end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/0000755000004100000410000000000013440143231024163 5ustar www-datawww-datagithub-pages-health-check-1.16.1/lib/github-pages-health-check/version.rb0000644000004100000410000000015713440143231026200 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck VERSION = "1.16.1".freeze end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/0000755000004100000410000000000013440143231025477 5ustar www-datawww-datagithub-pages-health-check-1.16.1/lib/github-pages-health-check/errors/build_error.rb0000644000004100000410000000042713440143231030337 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class BuildError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/troubleshooting-jekyll-builds/".freeze LOCAL_ONLY = true end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/deprecated_ip_error.rb0000644000004100000410000000103713440143231032026 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class DeprecatedIPError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message <<-MSG The custom domain for your GitHub Pages site is pointed at an outdated IP address. You must update your site's DNS records if you'd like it to be available via your custom domain. MSG end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/invalid_a_record_error.rb0000644000004100000410000000106313440143231032521 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class InvalidARecordError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message <<-MSG Your site's DNS settings are using a custom subdomain, #{domain.host}, that's set up as an A record. We recommend you change this to a CNAME record pointing at #{username}.github.io. MSG end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/invalid_cname_error.rb0000644000004100000410000000107313440143231032027 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class InvalidCNAMEError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message <<-MSG Your site's DNS settings are using a custom subdomain, #{domain.host}, that's not set up with a correct CNAME record. We recommend you set this CNAME record to point at #{username}.github.io. MSG end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/invalid_domain_error.rb0000644000004100000410000000053613440143231032216 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class InvalidDomainError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message "Domain is not a valid domain" end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/invalid_repository_error.rb0000644000004100000410000000044513440143231033165 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class InvalidRepositoryError < GitHubPages::HealthCheck::Error LOCAL_ONLY = true def message "Repository is not a valid repository" end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/not_served_by_pages_error.rb0000644000004100000410000000056713440143231033266 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class NotServedByPagesError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message "Domain does not resolve to the GitHub Pages server" end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/missing_access_token_error.rb0000644000004100000410000000050213440143231033424 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class MissingAccessTokenError < GitHubPages::HealthCheck::Error LOCAL_ONLY = true def message "Cannot retrieve repository information with a valid access token" end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/invalid_aaaa_record_error.rb0000644000004100000410000000102713440143231033164 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class InvalidAAAARecordError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message <<-MSG Your site's DNS settings are using a custom subdomain, #{domain.host}, that's set up with an AAAA record. GitHub Pages currently does not support IPv6. MSG end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors/invalid_dns_error.rb0000644000004100000410000000055113440143231031530 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class InvalidDNSError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze def message "Domain's DNS record could not be retrieved" end end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/site.rb0000644000004100000410000000154513440143231025461 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Site < Checkable attr_reader :repository, :domain def initialize(repository_or_domain, access_token: nil) @repository = Repository.new(repository_or_domain, :access_token => access_token) @domain = @repository.domain rescue GitHubPages::HealthCheck::Errors::InvalidRepositoryError @repository = nil @domain = Domain.redundant(repository_or_domain) end def check! [domain, repository].compact.each(&:check!) true end def to_hash hash = (domain || {}).to_hash.dup hash = hash.merge(repository.to_hash) unless repository.nil? hash[:valid?] = valid? hash[:reason] = reason hash end alias to_h to_hash alias as_json to_hash end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/cdn.rb0000644000004100000410000000220513440143231025253 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class CDN include Singleton # Internal: The path of the config file. attr_reader :name, :path # Public: Does cloudflare control this address? def self.controls_ip?(address) instance.controls_ip?(address) end # Internal: Create a new CDN info instance. def initialize(options = {}) @name = options.fetch(:name) { self.class.name.split("::").last.downcase } @path = options.fetch(:path) { default_config_path } end # Internal: Does this CDN control this address? def controls_ip?(address) ranges.any? { |range| range.include?(address.to_s) } end private # Internal: The IP address ranges that cloudflare controls. def ranges @ranges ||= load_ranges end # Internal: Load IPAddr ranges from #path def load_ranges File.read(path).lines.map { |line| IPAddr.new(line.chomp) } end def default_config_path File.expand_path("../../config/#{name}-ips.txt", File.dirname(__FILE__)) end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/errors.rb0000644000004100000410000000036213440143231026025 0ustar www-datawww-data# frozen_string_literal: true Dir[File.expand_path("errors/*_error.rb", __dir__)].each do |f| require f end module GitHubPages module HealthCheck module Errors def self.all Error.subclasses end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/error.rb0000644000004100000410000000301513440143231025640 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Error < StandardError DOCUMENTATION_BASE = "https://help.github.com".freeze DOCUMENTATION_PATH = "/categories/github-pages-basics/".freeze LOCAL_ONLY = false # Error is only used when running locally attr_reader :repository, :domain def initialize(repository: nil, domain: nil) super @repository = repository @domain = domain end def self.inherited(base) subclasses << base end def self.subclasses @subclasses ||= [] end def message "Something's wrong with your GitHub Pages site." end # Error message, with get more info URL appended def message_with_url msg = message.gsub(/\s+/, " ").squeeze(" ").strip msg << "." unless msg.end_with?(".") # add trailing period if not there "#{msg} #{more_info}" end alias message_formatted message_with_url def to_s "#{message_with_url} (#{name})".tr("\n", " ").squeeze(" ").strip end private def name self.class.name.split("::").last end def username if repository.nil? "[YOUR USERNAME]" else repository.owner end end def more_info "For more information, see #{documentation_url}." end def documentation_url URI.join(Error::DOCUMENTATION_BASE, self.class::DOCUMENTATION_PATH).to_s end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/cdns/0000755000004100000410000000000013440143231025112 5ustar www-datawww-datagithub-pages-health-check-1.16.1/lib/github-pages-health-check/cdns/cloudflare.rb0000644000004100000410000000036013440143231027556 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck # Instance of the CloudFlare CDN for checking IP ownership # Specifically not namespaced to avoid a breaking change class CloudFlare < CDN end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/cdns/fastly.rb0000644000004100000410000000067113440143231026745 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck # Instance of the Fastly CDN for checking IP ownership # Specifically not namespaced to avoid a breaking change class Fastly < CDN # Fastly maps used by GitHub Pages. HOSTNAMES = %w( github.map.fastly.net github.map.fastly.net. sni.github.map.fastly.net sni.github.map.fastly.net. ).freeze end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/repository.rb0000644000004100000410000000343213440143231026731 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Repository < Checkable attr_reader :name, :owner REPO_REGEX = %r{\A[a-z0-9_\-]+/[a-z0-9_\-\.]+\z}i.freeze HASH_METHODS = %i[ name_with_owner built? last_built build_duration build_error ].freeze def initialize(name_with_owner, access_token: nil) unless name_with_owner.match(REPO_REGEX) raise Errors::InvalidRepositoryError end parts = name_with_owner.split("/") @owner = parts.first @name = parts.last @access_token = access_token || ENV["OCTOKIT_ACCESS_TOKEN"] end def name_with_owner @name_with_owner ||= [owner, name].join("/") end alias nwo name_with_owner def check! raise Errors::BuildError.new(:repository => self), build_error unless built? true end def last_build @last_build ||= client.latest_pages_build(name_with_owner) end def built? last_build && last_build.status == "built" end def build_error last_build.error["message"] unless built? end alias reason build_error def build_duration last_build && last_build.duration end def last_built last_build && last_build.updated_at end def domain return if cname.nil? @domain ||= GitHubPages::HealthCheck::Domain.redundant(cname) end private def client raise Errors::MissingAccessTokenError if @access_token.nil? @client ||= Octokit::Client.new(:access_token => @access_token) end def pages_info @pages_info ||= client.pages(name_with_owner) end def cname pages_info.cname end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/caa.rb0000644000004100000410000000321413440143231025234 0ustar www-datawww-data# frozen_string_literal: true require "dnsruby" require "public_suffix" require "github-pages-health-check/resolver" module GitHubPages module HealthCheck class CAA attr_reader :host, :error, :nameservers def initialize(host, nameservers: :default) raise ArgumentError, "host cannot be nil" if host.nil? @host = host @nameservers = nameservers end def errored? records # load the records first !error.nil? end def lets_encrypt_allowed? return false if errored? return true unless records_present? records.any? { |r| r.property_value == "letsencrypt.org" } end def records_present? return false if errored? records && !records.empty? end def records return @records if defined?(@records) @records = get_caa_records(host) @records = get_caa_records(parent_host) if @records.nil? || @records.empty? @records end private def get_caa_records(domain) return [] if domain.nil? query(domain).select { |r| issue_caa_record?(r) } end def issue_caa_record?(record) record.type == Dnsruby::Types::CAA && record.property_tag == "issue" end def query(domain) resolver(domain).query(Dnsruby::Types::CAA) rescue Dnsruby::ResolvError, Dnsruby::ResolvTimeout => e @error = e [] end def resolver(domain) GitHubPages::HealthCheck::Resolver.new(domain, :nameservers => nameservers) end def parent_host host.split(".").drop(1).join(".") end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/checkable.rb0000644000004100000410000000236213440143231026414 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Checkable # Array of symbolized methods to be included in the output hash HASH_METHODS = [].freeze def check! raise "Not implemented" end alias valid! check! # Runs all checks, returns true if valid, otherwise false def valid? check! true rescue GitHubPages::HealthCheck::Error false end # Returns the reason the check failed, if any def reason check! nil rescue GitHubPages::HealthCheck::Error => e e end def to_hash @to_hash ||= begin hash = {} self.class::HASH_METHODS.each do |method| hash[method] = public_send(method) end hash end end alias [] to_hash alias to_h to_hash def to_json(state = nil) require "json" to_hash.to_json(state) end def to_s printer.simple_string end def to_s_pretty printer.pretty_print end alias pretty_print to_s_pretty private def printer @printer ||= GitHubPages::HealthCheck::Printer.new(self) end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/printer.rb0000644000004100000410000000541113440143231026174 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Printer PRETTY_LEFT_WIDTH = 11 PRETTY_JOINER = " | ".freeze attr_reader :health_check def initialize(health_check) @health_check = health_check end def simple_string require "yaml" hash = health_check.to_hash hash[:reason] = hash[:reason].to_s if hash[:reason] hash.to_yaml.sub(/\A---\n/, "").gsub(/^:/, "") end def pretty_print values = health_check.to_hash output = StringIO.new # Header output.puts new_line "Domain", (values[:uri]).to_s output.puts "-" * (PRETTY_LEFT_WIDTH + 1) + "|" + "-" * 50 output.puts new_line "DNS", "does not resolve" unless values[:dns_resolves?] # Valid? output.write new_line "State", (values[:valid?] ? "valid" : "invalid").to_s output.puts " - is #{"NOT " unless values[:served_by_pages?]}served by Pages" # What's wrong? output.puts new_line "Reason", (values[:reason]).to_s unless values[:valid?] if values[:pointed_to_github_user_domain?] output.puts new_line nil, "pointed to user domain" end if values[:pointed_to_github_pages_ip?] output.puts new_line nil, "pointed to pages IP" end # DNS Record info record_type = if values[:a_record?] "A" elsif values[:cname_record?] "CNAME" else "other" end output.write new_line "Record Type", record_type should_be = values[:should_be_a_record?] ? "A record" : "CNAME" output.puts ", should be #{should_be}" ip_problems = [] ip_problems << "not apex domain" unless values[:apex_domain?] ip_problems << "invalid domain" unless values[:valid_domain?] ip_problems << "old ip address used" if values[:old_ip_address?] ip_problems_string = !ip_problems.empty? ? ip_problems.join(", ") : "none" output.puts new_line "IP Problems", ip_problems_string if values[:proxied?] proxy = values[:cloudflare_ip?] ? "CloudFlare" : "unknown" output.puts new_line "Proxied", "yes, through #{proxy}" end output.puts new_line "Domain", "*.github.com/io domain" if values[:pages_domain?] output.string end def new_line(left = nil, right = nil) if left && right ljust(left) + PRETTY_JOINER + right elsif left ljust(left) elsif right " " * (PRETTY_LEFT_WIDTH + PRETTY_JOINER.size) + right end end def ljust(line) line.ljust(PRETTY_LEFT_WIDTH) end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/redundant_check.rb0000644000004100000410000000175713440143231027643 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class RedundantCheck extend Forwardable TIMEOUT = 5 # seconds attr_reader :domain def initialize(domain) @domain = domain end def check @check ||= (checks.find(&:valid?) || check_with_default_nameservers) end def_delegator :check, :reason, :reason def_delegator :check, :valid?, :valid? def https_eligible? checks.any?(&:https_eligible?) end private def checks @checks ||= %i[default authoritative public].map do |ns| GitHubPages::HealthCheck::Domain.new(domain, :nameservers => ns) end end def check_with_default_nameservers @check_with_default_nameservers ||= checks.find { |c| c.nameservers == :default } end def check_with_public_nameservers @check_with_public_nameservers ||= checks.find { |c| c.nameservers == :public } end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/domain.rb0000644000004100000410000003662113440143231025767 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Domain < Checkable attr_reader :host, :resolver, :nameservers 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 "103.245.222.133", "103.245.223.133", "103.245.224.133", "104.156.81.133", "104.156.82.133", "104.156.83.133", "104.156.85.133", "104.156.87.133", "104.156.88.133", "104.156.89.133", "104.156.90.133", "104.156.91.133", "104.156.92.133", "104.156.93.133", "104.156.94.133", "104.156.95.133", "104.37.95.133", "157.52.64.133", "157.52.66.133", "157.52.67.133", "157.52.68.133", "157.52.69.133", "157.52.96.133", "172.111.64.133", "172.111.96.133", "185.31.16.133", "185.31.17.133", "185.31.18.133", "185.31.19.133", "199.27.74.133", "199.27.75.133", "199.27.76.133", "199.27.78.133", "199.27.79.133", "23.235.33.133", "23.235.37.133", "23.235.39.133", "23.235.40.133", "23.235.41.133", "23.235.43.133", "23.235.44.133", "23.235.45.133", "23.235.46.133", "23.235.47.133", "23.235.47.133", "43.249.72.133", "43.249.73.133", "43.249.74.133", "43.249.75.133", # 2018 Move to GitHub assigned IP space "192.30.252.153", "192.30.252.154" ].freeze CURRENT_IP_ADDRESSES = %w( 185.199.108.153 185.199.109.153 185.199.110.153 185.199.111.153 ).freeze HASH_METHODS = %i[ host uri nameservers 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? non_github_pages_ip_present? pages_domain? served_by_pages? valid? reason valid_domain? https? enforces_https? https_error https_eligible? caa_error ].freeze def self.redundant(host) GitHubPages::HealthCheck::RedundantCheck.new(host).check end 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 # Runs all checks, raises an error if invalid 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::InvalidAAAARecordError, :domain => self if invalid_aaaa_record? raise Errors::NotServedByPagesError, :domain => self unless served_by_pages? true end def deprecated_ip? return @deprecated_ip if defined? @deprecated_ip @deprecated_ip = (valid_domain? && a_record? && old_ip_address?) end def invalid_aaaa_record? return @invalid_aaaa_record if defined? @invalid_aaaa_record @invalid_aaaa_record = (valid_domain? && should_be_a_record? && aaaa_record_present?) end 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 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 # Is this a valid domain that PublicSuffix recognizes? # Used as an escape hatch to prevent false positives on DNS checkes 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 # Is this domain an apex domain, meaning a CNAME would be innapropriate def apex_domain? return @apex_domain if defined?(@apex_domain) return unless valid_domain? # 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 # Should the domain use an A record? def should_be_a_record? !pages_io_domain? && (apex_domain? || mx_records_present?) end def should_be_cname_record? !should_be_a_record? end # Is the domain's first response an A record to a valid GitHub Pages IP? def pointed_to_github_pages_ip? a_record? && CURRENT_IP_ADDRESSES.include?(dns.first.address.to_s) end # Are any of the domain's A records pointing elsewhere? def non_github_pages_ip_present? return unless dns? a_records = dns.select { |answer| answer.type == Dnsruby::Types::A } a_records.any? { |answer| !github_pages_ip?(answer.address.to_s) } false end # Is the domain's first response a CNAME to a pages domain? def cname_to_github_user_domain? cname? && !cname_to_pages_dot_github_dot_com? && cname.pages_domain? end # 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 def cname_to_pages_dot_github_dot_com? cname? && cname.pages_dot_github_dot_com? end # Is the given domain CNAME'd directly to our Fastly account? def cname_to_fastly? cname? && !pages_domain? && cname.fastly? end # Is the host a *.github.io domain? def pages_io_domain? !!host.match(/\A[\w-]+\.github\.(io)\.?\z/i) end # Is the host a *.github.(io|com) domain? def pages_domain? !!host.match(/\A[\w-]+\.github\.(io|com)\.?\z/i) end # Is the host pages.github.com or pages.github.io? def pages_dot_github_dot_com? !!host.match(/\Apages\.github\.(io|com)\.?\z/i) end # Is this domain owned by GitHub? def github_domain? !!host.downcase.end_with?("github.com") end # Is the host our Fastly CNAME? def fastly? !!host.match(/\A#{Regexp.union(Fastly::HOSTNAMES)}\z/i) end # Does the domain resolve to a CloudFlare-owned IP def cloudflare_ip? cdn_ip?(CloudFlare) end # Does the domain resolve to a Fastly-owned IP def fastly_ip? cdn_ip?(Fastly) end # 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 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 REQUESTED_RECORD_TYPES = [ Dnsruby::Types::A, Dnsruby::Types::AAAA, Dnsruby::Types::CNAME, Dnsruby::Types::MX ].freeze # Returns an array of DNS answers 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 # Are we even able to get the DNS record? def dns? !(dns.nil? || dns.empty?) end alias dns_resolves? dns? # Does this domain have *any* A record that points to the legacy IPs? def old_ip_address? return unless dns? dns.any? do |answer| answer.type == Dnsruby::Types::A && legacy_ip?(answer.address.to_s) end end # Is this domain's first response an A record? def a_record? return unless dns? dns.first.type == Dnsruby::Types::A end def aaaa_record_present? return unless dns? dns.any? { |answer| answer.type == Dnsruby::Types::AAAA } end # Is this domain's first response a CNAME record? def cname_record? return unless dns? return false unless cname cname.valid_domain? end alias cname? cname_record? # The domain to which this domain's CNAME resolves # Returns nil if the domain is not a CNAME def cname cnames = dns.take_while { |answer| answer.type == Dnsruby::Types::CNAME } return if cnames.empty? @cname ||= Domain.new(cnames.last.cname.to_s) end def mx_records_present? return unless dns? dns.any? { |answer| answer.type == Dnsruby::Types::MX } end 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 def uri(overrides = {}) options = { :host => host, :scheme => scheme, :path => "/" } options = options.merge(overrides) Addressable::URI.new(options).normalize.to_s end # Does this domain respond to HTTPS requests with a valid cert? def https? https_response.return_code == :ok end # The response code of the HTTPS request, if it failed. # Useful for diagnosing cert errors def https_error https_response.return_code unless https? end # Does this domain redirect HTTP requests to HTTPS? 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 # Can an HTTPS certificate be issued for this domain? 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 any AAAA records present return false if aaaa_record_present? # Must be a CNAME or point to our IPs. # Only check the one domain if a CNAME. Don't check the parent domain. return true if cname_to_github_user_domain? # Check CAA records for the full domain and its parent domain. pointed_to_github_pages_ip? && caa.lets_encrypt_allowed? end # Any errors querying CAA records def caa_error return nil unless caa.errored? caa.error.class.name end private def caa @caa ||= GitHubPages::HealthCheck::CAA.new(host, :nameservers => nameservers) end # The domain's response to HTTP(S) requests, following redirects def response return @response if defined? @response @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 @response end # The domain's response to HTTP requests, without following redirects def http_response options = TYPHOEUS_OPTIONS.merge(:followlocation => false) @http_response ||= Typhoeus.head(uri(:scheme => "http"), options) end # The domain's response to HTTPS requests, without following redirects def https_response options = TYPHOEUS_OPTIONS.merge(:followlocation => false) @https_response ||= Typhoeus.head(uri(:scheme => "https"), options) end # 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. 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 # Adjust `domain` so that it won't be searched for with /etc/resolv.conf # # GitHubPages::HealthCheck.new("anything.io").absolute_domain # => "anything.io." def absolute_domain host.end_with?(".") ? host : "#{host}." end def scheme @scheme ||= github_domain? ? "https" : "http" end # Does the domain resolve to a CDN-owned IP def cdn_ip?(cdn) return unless dns? a_records = dns.select { |answer| answer.type == Dnsruby::Types::A } return false if !a_records || a_records.empty? a_records.all? do |answer| cdn.controls_ip?(answer.address) end end def legacy_ip?(ip_addr) LEGACY_IP_ADDRESSES.include?(ip_addr) end def github_pages_ip?(ip_addr) CURRENT_IP_ADDRESSES.include?(ip_addr) end end end end github-pages-health-check-1.16.1/lib/github-pages-health-check/resolver.rb0000644000004100000410000000406013440143231026351 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Resolver DEFAULT_RESOLVER_OPTIONS = { :retry_times => 2, :query_timeout => 5, :dnssec => false, :do_caching => false }.freeze PUBLIC_NAMESERVERS = %w( 8.8.8.8 1.1.1.1 ).freeze class << self def default_resolver @default_resolver ||= Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS) end end attr_reader :domain, :nameservers # Create a new resolver. # # domain - the domain we're getting answers for # nameserver - (optional) a case def initialize(domain, nameservers: :default) @domain = domain @nameservers = nameservers end def query(type) resolver.query(Addressable::IDNA.to_ascii(domain), type).answer end private def resolver @resolver ||= case nameservers when :default self.class.default_resolver when :authoritative Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge( :nameservers => authoritative_nameservers )) when :public Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge( :nameservers => PUBLIC_NAMESERVERS )) when Array Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge( :nameservers => nameservers )) else raise "Invalid nameserver type: #{nameservers.inspect}" end end def authoritative_nameservers @authoritative_nameservers ||= begin self.class.default_resolver.query(domain, Dnsruby::Types::NS).answer.map do |rr| next rr.nsdname.to_s if rr.type == Dnsruby::Types::NS end.compact end end end end end github-pages-health-check-1.16.1/config/0000755000004100000410000000000013440143231017765 5ustar www-datawww-datagithub-pages-health-check-1.16.1/config/fastly-ips.txt0000644000004100000410000000027213440143231022622 0ustar www-datawww-data23.235.32.0/20 43.249.72.0/22 103.244.50.0/24 103.245.222.0/23 103.245.224.0/24 104.156.80.0/20 151.101.0.0/16 157.52.64.0/18 172.111.64.0/18 185.31.16.0/22 199.27.72.0/21 199.232.0.0/16github-pages-health-check-1.16.1/config/cloudflare-ips.txt0000644000004100000410000000033113440143231023434 0ustar www-datawww-data173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/12 172.64.0.0/13 131.0.72.0/22 github-pages-health-check-1.16.1/Gemfile0000644000004100000410000000010613440143231020010 0ustar www-datawww-data# frozen_string_literal: true source "https://rubygems.org" gemspec github-pages-health-check-1.16.1/github-pages-health-check.gemspec0000644000004100000410000000253613440143231024770 0ustar www-datawww-data# frozen_string_literal: true require File.expand_path("lib/github-pages-health-check/version", __dir__) Gem::Specification.new do |s| s.required_ruby_version = ">= 2.2.0" s.name = "github-pages-health-check" s.version = GitHubPages::HealthCheck::VERSION s.summary = "Checks your GitHub Pages site for commons DNS configuration issues" s.description = "Checks your GitHub Pages site for commons DNS configuration issues." s.authors = "GitHub, Inc." s.email = "support@github.com" s.homepage = "https://github.com/github/github-pages-health-check" s.license = "MIT" s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } s.require_paths = ["lib"] s.add_dependency("addressable", "~> 2.3") s.add_dependency("dnsruby", "~> 1.60") s.add_dependency("octokit", "~> 4.0") s.add_dependency("public_suffix", "~> 3.0") s.add_dependency("typhoeus", "~> 1.3") s.add_development_dependency("dotenv", "~> 1.0") s.add_development_dependency("gem-release", "~> 0.7") s.add_development_dependency("pry", "~> 0.10") s.add_development_dependency("rspec", "~> 3.0") s.add_development_dependency("rubocop", "~> 0.52") s.add_development_dependency("webmock", "~> 1.21") end