acme_nsupdate 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32d57c19eaf5a6c23f23af08da0f6aa8f4089855
4
- data.tar.gz: 8cacaab5401445f956efa5b001a339559f64b14d
3
+ metadata.gz: 7e22b3776fb2331b29ccdbded623176832232282
4
+ data.tar.gz: 395303634131286629fd3092f102651d4e4901da
5
5
  SHA512:
6
- metadata.gz: c7ba48ba36dfd57d13726be92ff5a40028626118442dd57dc9e7659ab81207be33f9d8f2713c41e2f6d7ae0a250820dc9e3c87aed6312a9a2e391444ca83ee18
7
- data.tar.gz: 6c0e2cc6bae8556affad2e617c9a2ea4ec9f0d463c2a5b3c11be1d24e1b56f9ad3d2e129c665e68ea036e8a3c798c42c2ac9644d2baa024b944c9e9beb330b06
6
+ metadata.gz: b227c653a1414547d8317d4ada1091cbdf36c71f79be39ac4de08a6406a2b06bd0ea89f73a52076b9a7eeabadb34286c46b609f953c2d2cc4a384a5c63c5cffc
7
+ data.tar.gz: 8df03352e20f02af82cc2ccf2ac8fe9b1997976e10ddc86160e7a14fbc9ed601c0d92431f15e08953f2d318ad8f8e86be246f718b3802bda2971ba824a209f13
@@ -1,3 +1,5 @@
1
+ require "resolv"
2
+
1
3
  require "acme_nsupdate/strategy"
2
4
 
3
5
  module AcmeNsupdate
@@ -12,23 +14,24 @@ module AcmeNsupdate
12
14
  end
13
15
 
14
16
  def publish_challenges
15
- @client.logger.debug "Publishing challenges for #{@client.options[:domains].join(", ")}"
16
-
17
- challenges = @client.options[:domains].map {|domain|
18
- nsupdate = @client.build_nsupdate
19
-
20
- authorization = @client.client.authorize domain: domain
17
+ challenges = map_authorizations {|domain, authorization|
21
18
  challenge = authorization.dns01
22
19
  abort "Challenge dns-01 not supported by the given ACME server" unless challenge
20
+
21
+ nsupdate = @client.build_nsupdate
23
22
  nsupdate.del(*record(domain, challenge, true)) unless @client.options[:keep]
24
23
  nsupdate.add(*record(domain, challenge), @client.options[:txt_ttl])
25
24
  nsupdate.send
26
25
 
27
- [domain, challenge]
28
- }.to_h
26
+ challenge
27
+ }
29
28
 
30
- @client.logger.info "Waiting 120 seconds for the DNS updates to go live"
31
- sleep 120 # We wait some time to give the slaves time to update
29
+ unless challenges.empty?
30
+ @client.logger.info "Waiting up to 120 seconds for the DNS updates to go live"
31
+ unless verify_live_challenges(@client.options[:master], challenges)
32
+ raise AcmeNsupdate::Client::Error, "DNS challenges didn't appear on all nameservers within 120 seconds"
33
+ end
34
+ end
32
35
 
33
36
  challenges
34
37
  end
@@ -44,6 +47,58 @@ module AcmeNsupdate
44
47
 
45
48
  private
46
49
 
50
+ def verify_live_challenges(primary, challenges, timeout=120)
51
+ waited = 0
52
+ public_nameservers(primary, challenges.first.first).all? {|nameserver|
53
+ @client.logger.debug "Verifying DNS challenges are present on #{nameserver}"
54
+ challenges.all? {|domain, challenge|
55
+ name, type, content = record(domain, challenge)
56
+ records = query(nameserver, name, type).map(&:strings).flatten.map {|content| %("#{content}") }
57
+ @client.logger.debug "Got #{records.size} TXT records for #{name}: #{records.map(&:inspect).join(", ")}"
58
+ if records.include? content
59
+ true
60
+ elsif waited == timeout
61
+ @client.logger.error "None matched, timeout reached, aborting"
62
+ return false
63
+ else
64
+ @client.logger.debug "None matched, pausing for 5 seconds, already waited #{waited} seconds"
65
+ sleep 5
66
+ waited += 5
67
+ redo
68
+ end
69
+ }
70
+ }
71
+ end
72
+
73
+ def public_nameservers(primary, name)
74
+ # We have to hack into this because it gives us no way to fetch the SOA on a NXDOMAIN
75
+ authority = nil
76
+ Resolv::DNS.open(nameserver: [primary], search: [], ndots: 1) do |dns|
77
+ dns.lazy_initialize
78
+ message = Resolv::DNS::Message.new
79
+ message.rd = 1
80
+ message.add_question(name, Resolv::DNS::Resource::IN::SOA)
81
+ requester = dns.make_udp_requester
82
+
83
+ begin
84
+ sender = requester.sender(message, name, primary, 53)
85
+ reply, _ = requester.request(sender, 10)
86
+ authority = reply.authority.first.first.to_s
87
+ ensure
88
+ requester.close
89
+ end
90
+ end
91
+
92
+ return [] unless authority
93
+ query(primary, authority, :NS).map {|record| record.name.to_s }.uniq
94
+ end
95
+
96
+ def query(nameserver, name, qtype)
97
+ Resolv::DNS.open(nameserver: [nameserver], search: [], ndots: 1) do |dns|
98
+ return dns.getresources(name, Resolv::DNS::Resource::IN.const_get(qtype))
99
+ end
100
+ end
101
+
47
102
  def record domain, challenge, nodata=false
48
103
  ["#{challenge.record_name}.#{domain}", challenge.record_type].tap do |record|
49
104
  record << %("#{challenge.record_content}") unless nodata
@@ -14,17 +14,17 @@ module AcmeNsupdate
14
14
  end
15
15
 
16
16
  def publish_challenges
17
- @client.logger.debug "Publishing challenges for #{@client.options[:domains].join(", ")}"
18
- @client.options[:domains].map {|domain|
19
- authorization = @client.client.authorize domain: domain
17
+ map_authoriations {|domain, authorization|
20
18
  challenge = authorization.http01
21
19
  abort "Challenge http-01 not supported by this ACME server" unless challenge
20
+
22
21
  path = path challenge
23
22
  @client.logger.debug "Writing #{path} for #{domain}"
24
23
  FileUtils.mkdir_p File.dirname path
25
24
  File.write path, challenge.file_content
25
+
26
26
  [domain, challenge]
27
- }.to_h
27
+ }.compact.to_h
28
28
  end
29
29
 
30
30
  def cleanup challenges
@@ -21,8 +21,29 @@ module AcmeNsupdate
21
21
  end
22
22
  end
23
23
 
24
+
24
25
  private
25
26
 
27
+ def map_authorizations
28
+ @client.logger.debug "Publishing challenges for #{@client.options[:domains].join(", ")}"
29
+
30
+ challenges = @client.options[:domains].map {|domain|
31
+ authorization = @client.client.authorize domain: domain
32
+ if authorization.status == "valid"
33
+ @client.logger.debug("Skipping challenge for #{domain}, already valid.")
34
+ next
35
+ end
36
+
37
+ challenge = yield domain, authorization
38
+ unless challenge
39
+ @client.logger.debug("Skipping challenge for #{domain}, not solvable.")
40
+ next
41
+ end
42
+
43
+ [domain, challenge]
44
+ }.compact.to_h
45
+ end
46
+
26
47
  def wait_for_verification challenges
27
48
  @client.logger.debug("Requesting verification")
28
49
  challenges.each_value(&:request_verification)
@@ -1,3 +1,3 @@
1
1
  module AcmeNsupdate
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acme_nsupdate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonne Haß
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-10 00:00:00.000000000 Z
11
+ date: 2016-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slop