class AcmeNsupdate::Strategies::Dns01

Constants

IDENTIFIER

Public Class Methods

new(client) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 12
def initialize client
  @client = client
end

Public Instance Methods

cleanup(challenges) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 39
def cleanup challenges
  @client.logger.info("Cleaning up challenges")
  challenges.each do |domain, challenge|
    nsupdate = @client.build_nsupdate
    nsupdate.del(*record(domain, challenge))
    nsupdate.send
  end
end
publish_challenges(order) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 16
def publish_challenges(order)
  challenges = map_authorizations(order) {|domain, authorization|
    challenge = authorization.dns01
    abort "Challenge dns-01 not supported by the given ACME server" unless challenge

    nsupdate = @client.build_nsupdate
    nsupdate.del(*record(domain, challenge, true)) unless @client.options[:keep]
    nsupdate.add(*record(domain, challenge), @client.options[:txt_ttl])
    nsupdate.send

    challenge
  }

  unless challenges.empty?
    @client.logger.info "Waiting up to 120 seconds for the DNS updates to go live"
    unless verify_live_challenges(@client.options[:master], challenges)
      raise AcmeNsupdate::Client::Error, "DNS challenges didn't appear on all nameservers within 120 seconds"
    end
  end

  challenges
end

Private Instance Methods

public_nameservers(primary, name) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 73
def public_nameservers(primary, name)
  # We have to hack into this because it gives us no way to fetch the SOA on a NXDOMAIN
  authority = nil
  Resolv::DNS.open(nameserver: [primary], search: [], ndots: 1) do |dns|
    dns.lazy_initialize
    message = Resolv::DNS::Message.new
    message.rd = 1
    message.add_question(name, Resolv::DNS::Resource::IN::SOA)
    requester = dns.make_udp_requester

    begin
      sender = requester.sender(message, name, primary, 53)
      reply, _ = requester.request(sender, 10)
      authority = !reply.authority.empty? ? reply.authority.first.first.to_s : reply.answer.first[2].to_s
    ensure
      requester.close
    end
  end

  return [] unless authority
  query(primary, authority, :NS).map {|record| record.name.to_s }.uniq
end
query(nameserver, name, qtype) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 96
def query(nameserver, name, qtype)
  Resolv::DNS.open(nameserver: [nameserver], search: [], ndots: 1) do |dns|
    return dns.getresources(name, Resolv::DNS::Resource::IN.const_get(qtype))
  end
end
record(domain, challenge, nodata=false) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 102
def record domain, challenge, nodata=false
  ["#{challenge.record_name}.#{domain}", challenge.record_type].tap do |record|
    record << %("#{challenge.record_content}") unless nodata
  end
end
verify_live_challenges(primary, challenges, timeout=120) click to toggle source
# File lib/acme_nsupdate/strategies/dns01.rb, line 50
def verify_live_challenges(primary, challenges, timeout=120)
  waited = 0
  public_nameservers(primary, challenges.first.first).all? {|nameserver|
    @client.logger.debug "Verifying DNS challenges are present on #{nameserver}"
    challenges.all? {|domain, challenge|
      name, type, content = record(domain, challenge)
      records = query(nameserver, name, type).map(&:strings).flatten.map {|content| %("#{content}") }
      @client.logger.debug "Got #{records.size} TXT records for #{name}: #{records.map(&:inspect).join(", ")}"
      if records.include? content
        true
      elsif waited >= timeout
        @client.logger.error "None matched, timeout reached, aborting"
        return false
      else
        @client.logger.debug "None matched, pausing for 5 seconds, already waited #{waited} seconds"
        sleep 5
        waited += 5
        redo
      end
    }
  }
end