github-pages-health-check-1.19.0/0000755000004100000410000000000015024551160016526 5ustar www-datawww-datagithub-pages-health-check-1.19.0/.gitignore0000644000004100000410000000007615024551160020521 0ustar www-datawww-data/*.gem *.lock .bundle vendor/gems /bin .env spec/examples.txt github-pages-health-check-1.19.0/Dockerfile0000644000004100000410000000076015024551160020523 0ustar www-datawww-dataARG RUBY_VERSION FROM ruby:$RUBY_VERSION-slim RUN set -ex \ && gem update --system --silent --quiet \ && apt-get update -y \ && apt-get upgrade -y \ && apt-get install -y \ build-essential \ git \ libcurl4-openssl-dev \ && apt-get clean WORKDIR /app/github-pages-health-check COPY Gemfile . COPY github-pages-health-check.gemspec . COPY lib/github-pages-health-check/version.rb lib/github-pages-health-check/version.rb RUN bundle install COPY . . ENTRYPOINT [ "/bin/bash" ] github-pages-health-check-1.19.0/.github/0000755000004100000410000000000015024551160020066 5ustar www-datawww-datagithub-pages-health-check-1.19.0/.github/dependabot.yml0000644000004100000410000000067415024551160022725 0ustar www-datawww-dataversion: 2 updates: - package-ecosystem: bundler directory: "/" schedule: interval: daily time: "10:00" timezone: Europe/Vienna pull-request-branch-name: separator: "-" open-pull-requests-limit: 99 allow: - dependency-type: direct - dependency-type: indirect rebase-strategy: disabled - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly github-pages-health-check-1.19.0/.github/CODEOWNERS0000644000004100000410000000013115024551160021454 0ustar www-datawww-data# Automatically add our team to each pull request in this repo * @github/pages-reviewers github-pages-health-check-1.19.0/.github/workflows/0000755000004100000410000000000015024551160022123 5ustar www-datawww-datagithub-pages-health-check-1.19.0/.github/workflows/push-cibuild.yml0000644000004100000410000000070215024551160025235 0ustar www-datawww-dataon: push name: "GitHub Pages Health Check Tests" permissions: contents: read jobs: build: name: "GitHub Pages Health Check Tests" runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - '3.1' - '3.2' - '3.3' steps: - uses: actions/checkout@master - name: script/cibuild-docker run: script/cibuild-docker env: RUBY_VERSION: ${{ matrix.ruby }} github-pages-health-check-1.19.0/.github/workflows/pages-gem.yml0000644000004100000410000000114515024551160024514 0ustar www-datawww-dataname: Publish Gem on: release: types: [released] permissions: contents: read jobs: release: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: '3.3' - name: Build gem run: | gem build github-pages-health-check.gemspec - name: Publish gem env: GEM_HOST_API_KEY: ${{ secrets.PAGES_GEM_PUBLISHING }} run: | gem push github-pages-health-check-*.gem github-pages-health-check-1.19.0/lib/0000755000004100000410000000000015024551160017274 5ustar www-datawww-datagithub-pages-health-check-1.19.0/lib/github-pages-health-check.rb0000644000004100000410000000513115024551160024516 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" URL = "https://github.com/github/pages-health-check" USER_AGENT = "Mozilla/5.0 (compatible; #{HUMAN_NAME}/#{VERSION}; +#{URL})" # suppress 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 # rubocop:disable Naming/AccessorMethodName (this is not an accessor method) def self.set_proxy(proxy_url) @typhoeus_options = typhoeus_options.merge(:proxy => proxy_url).freeze nil end # rubocop:enable Naming/AccessorMethodName def self.typhoeus_options return @typhoeus_options if defined?(@typhoeus_options) @typhoeus_options = { :followlocation => true, :redir_protocols => %i[http https], # don't allow non-http protocols on redirections :timeout => TIMEOUT, :accept_encoding => "gzip", :method => :head, :headers => { "User-Agent" => USER_AGENT }, :proxy => nil }.freeze end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/0000755000004100000410000000000015024551160024171 5ustar www-datawww-datagithub-pages-health-check-1.19.0/lib/github-pages-health-check/error.rb0000644000004100000410000000277715024551160025664 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Error < StandardError DOCUMENTATION_BASE = "https://help.github.com" DOCUMENTATION_PATH = "/categories/github-pages-basics/" 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.19.0/lib/github-pages-health-check/cdn.rb0000644000004100000410000000220515024551160025261 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.19.0/lib/github-pages-health-check/repository.rb0000644000004100000410000000340015024551160026732 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&.duration end def last_built 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.19.0/lib/github-pages-health-check/cdns/0000755000004100000410000000000015024551160025120 5ustar www-datawww-datagithub-pages-health-check-1.19.0/lib/github-pages-health-check/cdns/fastly.rb0000644000004100000410000000067115024551160026753 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.19.0/lib/github-pages-health-check/cdns/cloudflare.rb0000644000004100000410000000036015024551160027564 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.19.0/lib/github-pages-health-check/domain.rb0000644000004100000410000004704215024551160025774 0ustar www-datawww-data# frozen_string_literal: true require "securerandom" 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 CURRENT_IPV6_ADDRESSES = %w( 2606:50c0:8000::153 2606:50c0:8001::153 2606:50c0:8002::153 2606:50c0:8003::153 ).freeze CURRENT_IP_ADDRESSES_ALL = (CURRENT_IP_ADDRESSES + CURRENT_IPV6_ADDRESSES).freeze HASH_METHODS = %i[ host uri nameservers dns_resolves? proxied? cloudflare_ip? fastly_ip? old_ip_address? a_record? aaaa_record? a_record_present? aaaa_record_present? cname_record? mx_records_present? valid_domain? apex_domain? should_be_a_record? cname_to_github_user_domain? cname_to_domain_to_pages? 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 dns_zone_soa? dns_zone_ns? ].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 # rubocop:disable Metrics/AbcSize 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::InvalidAAAARecordError.new :domain => self if invalid_aaaa_record? raise Errors::NotServedByPagesError.new :domain => self unless served_by_pages? true end # rubocop:enable Metrics/AbcSize 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? && aaaa_record_present? && !should_be_a_record?) end def invalid_a_record? return @invalid_a_record if defined? @invalid_a_record @invalid_a_record = (valid_domain? && a_record_present? && !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 checks 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 inappropriate def apex_domain? return @apex_domain if defined?(@apex_domain) return false unless valid_domain? return true if dns_zone_soa? && dns_zone_ns? # 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 # # Does the domain have an associated SOA record? # def dns_zone_soa? return @soa_records if defined?(@soa_records) return false unless dns? @soa_records = dns.any? do |answer| answer.type == Dnsruby::Types::SOA && answer.name.to_s == host end end # # Does the domain have associated NS records? # def dns_zone_ns? return @ns_records if defined?(@ns_records) return false unless dns? @ns_records = dns.any? do |answer| answer.type == Dnsruby::Types::NS && answer.name.to_s == host end 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 or AAAA record to a valid GitHub Pages IP? def pointed_to_github_pages_ip? return false unless address_record? CURRENT_IP_ADDRESSES_ALL.include?(dns.first.address.to_s.downcase) end # Are any of the domain's A or AAAA records pointing elsewhere? def non_github_pages_ip_present? return unless dns? dns .select { |a| Dnsruby::Types::A == a.type || Dnsruby::Types::AAAA == a.type } .any? { |a| !github_pages_ip?(a.address.to_s) } 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 # Check if the CNAME points to a Domain that points to pages # e.g. CNAME -> Domain -> Pages # rubocop:disable Metrics/AbcSize def cname_to_domain_to_pages? return false unless dns? a_record_to_pages = dns.select { |d| d.type == Dnsruby::Types::A && d.name.to_s == host }.first return false unless a_record_to_pages && cname? && !cname_to_pages_dot_github_dot_com? && @www_cname CURRENT_IP_ADDRESSES.include?(a_record_to_pages.address.to_s.downcase) end # rubocop:enable Metrics/AbcSize # 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, generally 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.eql?("github.com") || 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_domain_to_pages? 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, Dnsruby::Types::NS, Dnsruby::Types::SOA ].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 @is_a_record if defined?(@is_a_record) return unless dns? @is_a_record = Dnsruby::Types::A == dns.first.type end # Is this domain's first response an AAAA record? def aaaa_record? return @is_aaaa_record if defined?(@is_aaaa_record) return unless dns? @is_aaaa_record = Dnsruby::Types::AAAA == dns.first.type end # Does this domain has an A record setup (not necessarily as the first record)? def a_record_present? return unless dns? dns.any? { |answer| answer.type == Dnsruby::Types::A && answer.name.to_s == host } end # Does this domain has an AAAA record setup (not necessarily as the first record)? def aaaa_record_present? return unless dns? dns.any? { |answer| answer.type == Dnsruby::Types::AAAA && answer.name.to_s == host } 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 return unless dns? cnames = dns.take_while { |answer| answer.type == Dnsruby::Types::CNAME } return if cnames.empty? www_cname(cnames.last) @cname ||= Domain.new(cnames.last.cname.to_s) end # Check if we have a 'www.' CNAME that matches the domain def www_cname(cname) @www_cname ||= cname.name.to_s.start_with?("www.") && cname.name.to_s.end_with?(cname.domainname.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 true if response.headers["Server"] == "GitHub.com" # Typhoeus mangles the case of the header, compare insensitively response.headers.any? { |k, _v| k.downcase == "x-github-request-id" } end end def parent_domain parsed = PublicSuffix.parse(host) parent = host.split(".", 2).last if parent == parsed.tld return nil end parent rescue PublicSuffix::DomainNotAllowed nil end def maybe_wildcard? return @maybe_wildcard if defined? @maybe_wildcard return false unless dns_resolves? return false unless parent_domain sibling_domain = SecureRandom.alphanumeric(20) + "." + parent_domain @maybe_wildcard = begin wildcard_resolver = GitHubPages::HealthCheck::Resolver.new(sibling_domain, :nameservers => nameservers) [Dnsruby::Types::A, Dnsruby::Types::AAAA].any? do |record_type| wildcard_resolver.query(record_type).any? do |record| record.respond_to?(:address) && github_pages_ip?(record.address) end end end end def wildcard_warning Errors::WildcardRecordError.new :domain => self, :parent_domain => parent_domain if maybe_wildcard? 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 underscores in the domain name (Let's Encrypt does not allow it) return false if host.include?("_") # Must be a CNAME or point to our IPs. return true if cname_to_github_user_domain? || cname_to_domain_to_pages? # 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 address_record? a_record? || aaaa_record? end def caa @caa ||= GitHubPages::HealthCheck::CAA.new( :host => cname&.host || 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, GitHubPages::HealthCheck.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"], GitHubPages::HealthCheck.typhoeus_options) end @response end # The domain's response to HTTP requests, without following redirects def http_response options = GitHubPages::HealthCheck.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 = GitHubPages::HealthCheck.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? address_records = dns.select do |answer| Dnsruby::Types::A == answer.type || Dnsruby::Types::AAAA == answer.type end return false if !address_records || address_records.empty? address_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_ALL.include?(ip_addr&.to_s&.downcase) end end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/version.rb0000644000004100000410000000015015024551160026177 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck VERSION = "1.19.0" end end github-pages-health-check-1.19.0/lib/github-pages-health-check/redundant_check.rb0000644000004100000410000000175715024551160027651 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.19.0/lib/github-pages-health-check/checkable.rb0000644000004100000410000000236215024551160026422 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.19.0/lib/github-pages-health-check/errors/0000755000004100000410000000000015024551160025505 5ustar www-datawww-datagithub-pages-health-check-1.19.0/lib/github-pages-health-check/errors/invalid_aaaa_record_error.rb0000644000004100000410000000105115024551160033167 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/" def message <<-MSG Your site's DNS settings are using a custom subdomain, #{domain.host}, that's set up as an AAAA 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.19.0/lib/github-pages-health-check/errors/wildcard_record_error.rb0000644000004100000410000000161215024551160032372 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck module Errors class WildcardRecordError < GitHubPages::HealthCheck::Error DOCUMENTATION_PATH = "/pages/configuring-a-custom-domain-for-your-github-pages-site/verifying-your-custom-domain-for-github-pages/" attr_reader :parent_domain def initialize(repository: nil, domain: nil, parent_domain: nil) super(:repository => repository, :domain => domain) @parent_domain = parent_domain end def message <<-MSG The DNS record for your domain appears to be *.#{parent_domain}, a wildcard record. Your GitHub Pages site will still work, but unless you verify ownership of #{parent_domain}, any GitHub Pages user can serve their content from an arbitrary subdomain of it. MSG end end end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/errors/not_served_by_pages_error.rb0000644000004100000410000000056015024551160033265 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/" def message "Domain does not resolve to the GitHub Pages server" end end end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/errors/invalid_a_record_error.rb0000644000004100000410000000105415024551160032527 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/" 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.19.0/lib/github-pages-health-check/errors/build_error.rb0000644000004100000410000000042015024551160030336 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/" LOCAL_ONLY = true end end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/errors/invalid_dns_error.rb0000644000004100000410000000054215024551160031536 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/" def message "Domain's DNS record could not be retrieved" end end end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/errors/invalid_repository_error.rb0000644000004100000410000000044515024551160033173 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.19.0/lib/github-pages-health-check/errors/deprecated_ip_error.rb0000644000004100000410000000103015024551160032025 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/" 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.19.0/lib/github-pages-health-check/errors/invalid_cname_error.rb0000644000004100000410000000106415024551160032035 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/" 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.19.0/lib/github-pages-health-check/errors/invalid_domain_error.rb0000644000004100000410000000052715024551160032224 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/" def message "Domain is not a valid domain" end end end end end github-pages-health-check-1.19.0/lib/github-pages-health-check/errors/missing_access_token_error.rb0000644000004100000410000000050215024551160033432 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.19.0/lib/github-pages-health-check/errors.rb0000644000004100000410000000036715024551160026040 0ustar www-datawww-data# frozen_string_literal: true Dir[File.expand_path("errors/*_error.rb", __dir__)].sort.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.19.0/lib/github-pages-health-check/site.rb0000644000004100000410000000154515024551160025467 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.19.0/lib/github-pages-health-check/resolver.rb0000644000004100000410000000426115024551160026362 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( :nameserver => authoritative_nameservers )) when :public Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge( :nameserver => PUBLIC_NAMESERVERS )) when Array Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge( :nameserver => 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.19.0/lib/github-pages-health-check/printer.rb0000644000004100000410000000540215024551160026202 0ustar www-datawww-data# frozen_string_literal: true module GitHubPages module HealthCheck class Printer PRETTY_LEFT_WIDTH = 11 PRETTY_JOINER = " | " 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.19.0/lib/github-pages-health-check/caa.rb0000644000004100000410000000321515024551160025243 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.19.0/github-pages-health-check.gemspec0000644000004100000410000000206615024551160024774 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.8.7") s.add_dependency("dnsruby", "~> 1.60") s.add_dependency("octokit", ">= 4", "< 10") s.add_dependency("public_suffix", ">= 3.0", "< 7.0") s.add_dependency("typhoeus", "~> 1.3") end github-pages-health-check-1.19.0/.rspec0000644000004100000410000000003615024551160017642 0ustar www-datawww-data--color --require spec_helper github-pages-health-check-1.19.0/LICENSE.md0000644000004100000410000000207615024551160020137 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.19.0/Gemfile0000644000004100000410000000045515024551160020025 0ustar www-datawww-data# frozen_string_literal: true source "https://rubygems.org" group :development do gem "dotenv", "~> 2.7" gem "gem-release", "~> 2.1" gem "pry", "~> 0.10" gem "pry-byebug" gem "rspec", "~> 3.0" gem "rspec-retry", "~> 0.6" gem "rubocop", "~> 0.52" gem "webmock", "~> 3.8" end gemspec github-pages-health-check-1.19.0/README.md0000644000004100000410000000600515024551160020006 0ustar www-datawww-data# GitHub Pages Health Check *Checks your GitHub Pages site for common DNS configuration issues* [![Build Status](https://github.com/github/pages-health-check/actions/workflows/push-cibuild.yml/badge.svg)](https://github.com/github/pages-health-check/actions/workflows/push-cibuild.yml) [![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. ### Command Line ``` ./script/check pages.github.com host: pages.github.com uri: https://pages.github.com/ nameservers: :default dns_resolves?: true proxied?: false cloudflare_ip?: false fastly_ip?: false old_ip_address?: false a_record?: false cname_record?: true mx_records_present?: false valid_domain?: true apex_domain?: false should_be_a_record?: false cname_to_github_user_domain?: true cname_to_pages_dot_github_dot_com?: false cname_to_fastly?: false pointed_to_github_pages_ip?: false non_github_pages_ip_present?: false pages_domain?: true served_by_pages?: true valid?: true reason: https?: true enforces_https?: true https_error: https_eligible?: true caa_error: dns_zone_soa?: false dns_zone_ns?: false ``` github-pages-health-check-1.19.0/config/0000755000004100000410000000000015024551160017773 5ustar www-datawww-datagithub-pages-health-check-1.19.0/config/fastly-ips.txt0000644000004100000410000000050515024551160022627 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 140.248.64.0/18 140.248.128.0/17 146.75.0.0/17 151.101.0.0/16 157.52.64.0/18 167.82.0.0/17 167.82.128.0/20 167.82.160.0/20 167.82.224.0/20 172.111.64.0/18 185.31.16.0/22 199.27.72.0/21 199.232.0.0/16 2a04:4e40::/32 2a04:4e42::/32github-pages-health-check-1.19.0/config/cloudflare-ips.txt0000644000004100000410000000051715024551160023450 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/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32github-pages-health-check-1.19.0/.dockerignore0000644000004100000410000000001515024551160021176 0ustar www-datawww-dataGemfile.lock github-pages-health-check-1.19.0/.ruby-version0000644000004100000410000000000415024551160021165 0ustar www-datawww-data3.3 github-pages-health-check-1.19.0/.rubocop.yml0000644000004100000410000000717215024551160021007 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: 3.0 Exclude: - 'bin/**/*' - 'script/**/*' - 'vendor/**/*' - 'test-site/**/*' Layout/EndAlignment: Severity: error Lint/UnreachableCode: Severity: error Style/StringLiteralsInInterpolation: EnforcedStyle: double_quotes Style/HashSyntax: EnforcedStyle: hash_rockets Severity: error Layout/HashAlignment: SupportedLastArgumentHashStyles: always_ignore Layout/ParameterAlignment: 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. Layout/LineLength: Max: 120 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 } Layout/FirstHashElementIndentation: { EnforcedStyle: consistent } Layout/MultilineMethodCallIndentation: { EnforcedStyle: indented } Layout/MultilineOperationIndentation: { EnforcedStyle: indented } Layout/FirstParameterIndentation: { EnforcedStyle: consistent } Layout/FirstArrayElementIndentation: { 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 Style/HashEachMethods: Enabled: false Style/HashTransformKeys: Enabled: false Style/HashTransformValues: Enabled: false github-pages-health-check-1.19.0/script/0000755000004100000410000000000015024551160020032 5ustar www-datawww-datagithub-pages-health-check-1.19.0/script/release0000755000004100000410000000153115024551160021400 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.19.0/script/cibuild-docker0000755000004100000410000000054015024551160022637 0ustar www-datawww-data#!/bin/bash : ${RUBY_VERSION:="3.2"} docker build -t github-pages-health-check --build-arg RUBY_VERSION=$RUBY_VERSION . if [ -n "$DEBUG" ]; then # Run a shell. docker run -it --rm -v $(pwd):/app/github-pages-health-check github-pages-health-check else # Run CI docker run --rm github-pages-health-check script/cibuild --profile --fail-fast fi github-pages-health-check-1.19.0/script/update-cdn-ips0000755000004100000410000000213015024551160022571 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", "https://www.cloudflare.com/ips-v6"], :fastly => ["https://api.fastly.com/public-ip-list"] }.freeze def parse_fastly(data) json_data = JSON.parse(data) (json_data["addresses"] + json_data["ipv6_addresses"]).join("\n") end def parse_cloudflare(data) data end def fetch_ips_from_cdn(urls) urls.map do |url| puts "Fetching #{url}..." URI.parse(url).open.read end.join("\n") end def update_cdn_file(source, data) file = "config/#{source}-ips.txt" File.write(file, data) puts "Writing contents to #{file} and staging changes." `git add --verbose #{file}` end def parse_cdn_response(source, ips) send("parse_#{source}", ips) end def update_cdn_ips(source, urls) ips = fetch_ips_from_cdn(urls) data = parse_cdn_response(source, ips) update_cdn_file(source, data) end SOURCES.each do |source, urls| update_cdn_ips(source, urls) end github-pages-health-check-1.19.0/script/bootstrap0000755000004100000410000000004315024551160021772 0ustar www-datawww-data#!/bin/sh set -ex bundle install github-pages-health-check-1.19.0/script/test0000755000004100000410000000006315024551160020736 0ustar www-datawww-data#!/bin/sh set -e bundle exec rspec $@ script/fmt github-pages-health-check-1.19.0/script/check-cdn-ips0000755000004100000410000000075715024551160022401 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.19.0/script/console0000755000004100000410000000011115024551160021413 0ustar www-datawww-data#!/bin/sh set -ex bundle exec pry -r "./lib/github-pages-health-check" github-pages-health-check-1.19.0/script/check0000755000004100000410000000045015024551160021034 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # # Usage: script/check [DOMAIN] require "rubygems" require "bundler/setup" 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.19.0/script/fmt0000755000004100000410000000016015024551160020543 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.19.0/script/cibuild0000755000004100000410000000040215024551160021367 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