acmesmith-google-cloud-dns 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 610078ba72b04e675580cd85ca616d4be0de3097
4
- data.tar.gz: 73845b42a9a51dc1f39ce62d3c23b8a2754650fa
3
+ metadata.gz: c7d3669d3a965183f22b88bfaf0237dd96bdddb7
4
+ data.tar.gz: d792b2845d8f1f4865141e609d17e389974db7d1
5
5
  SHA512:
6
- metadata.gz: b72db3c4ba993de12218c9cc185e6402c4704b93333663fd3d25b12a9f27f3c477ed5fa15d2d49babaf113046484e6d56eeacddfbfe51d2d8aa73a382584270b
7
- data.tar.gz: 5ae01f49eae13b09d159e9ac940ae67a18c71d75a3daa42976f448ff595e062e1f8ffa4075cb2730cdebb601abe7b9f925539c5591e0bb68cd966a5c83839616
6
+ metadata.gz: b56650c70ad3329026efeccaaebe9713878806ae3096170792afc1c11ef0f9eec1f78a1c527516b4625685c04a651f31116550d5a428d5f9af220fdec22641dc
7
+ data.tar.gz: e13876bccf6c72ac5f6d84b7ef528ac382c1ff8be210e96da039e8cc20fe64d728e078207f3707a36442dc2f64f961101b0aeaf2fc05df007c24dcd428ab4a23
data/README.md CHANGED
@@ -22,7 +22,7 @@ Use `google-cloud-dns` challenge responder in your `acmesmith.yml`. General inst
22
22
  Write your `tenant_name`, `username`, `password` and `auth_url` in `acmesmith.yml`, or if you don't want to write them down into the file, export these values as the corresponding environment variables `OS_TENANT_NAME`, `OS_USERNAME`, `OS_PASSWORD` and `OS_AUTH_URL`.
23
23
 
24
24
  ```yaml
25
- endpoint: https://acme-v01.api.letsencrypt.org/
25
+ directory: https://acme-v02.api.letsencrypt.org/directory
26
26
 
27
27
  storage:
28
28
  type: filesystem
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "acmesmith"
22
+ spec.add_dependency "acmesmith", "~> 2.0"
23
23
  spec.add_dependency "google-api-client"
24
24
 
25
25
  spec.add_development_dependency "bundler", "~> 1.10"
@@ -1,3 +1,3 @@
1
1
  module AcmesmithGoogleCloudDns
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -3,6 +3,7 @@ require "acmesmith/challenge_responders/base"
3
3
  require "json"
4
4
  require "google/apis/dns_v1"
5
5
  require "resolv"
6
+ require "set"
6
7
 
7
8
  module Acmesmith
8
9
  module ChallengeResponders
@@ -11,6 +12,10 @@ module Acmesmith
11
12
  type == 'dns-01'
12
13
  end
13
14
 
15
+ def cap_respond_all?
16
+ true
17
+ end
18
+
14
19
  def initialize(config)
15
20
  @config = config
16
21
  @scope = "https://www.googleapis.com/auth/ndev.clouddns.readwrite"
@@ -32,21 +37,44 @@ module Acmesmith
32
37
  @project_id = @config[:project_id]
33
38
  end
34
39
 
35
- def respond(domain, challenge)
36
- puts "=> Responding challenge dns-01 for #{domain} in #{self.class.name}"
40
+ def respond_all(*domain_and_challenges)
41
+ challenges_by_zone_names = domain_and_challenges.group_by{ |domain, challenge|
42
+ domain = canonicalize(domain)
43
+ find_managed_zone(domain).name
44
+ }
37
45
 
38
- domain = canonicalize(domain)
39
- zone_name = find_managed_zone(domain).name
46
+ challenges_by_zone_names.each do |zone_name, dcs|
47
+ change = change_for_challenges(zone_name, dcs)
40
48
 
41
- puts " * create_change: #{challenge.record_type} #{[challenge.record_name, domain].join('.').inspect}, #{challenge.record_content.inspect}"
42
- change = Google::Apis::DnsV1::Change.new
43
- change.additions = [
44
- resource_record_set(domain, challenge)
45
- ]
46
- resp = @api.create_change(@project_id, zone_name, change)
49
+ resp = @api.create_change(@project_id, zone_name, change)
50
+ change_id = resp.id
47
51
 
48
- change_id = resp.id
52
+ wait_for_sync_by_api(zone_name, change_id)
53
+ wait_for_sync_by_dns(zone_name, change)
54
+ end
55
+ end
56
+
57
+ def cleanup_all(*domain_and_challenges)
58
+ challenges_by_zone_names = domain_and_challenges.group_by{ |domain, challenge|
59
+ domain = canonicalize(domain)
60
+ find_managed_zone(domain).name
61
+ }
62
+
63
+ challenges_by_zone_names.each do |zone_name, dcs|
64
+ change = change_for_challenges(zone_name, dcs, for_cleanup: true)
65
+
66
+ resp = @api.create_change(@project_id, zone_name, change)
67
+ change_id = resp.id
68
+
69
+ wait_for_sync_by_api(zone_name, change_id)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def wait_for_sync_by_api(zone_name, change_id)
49
76
  puts " * requested change: #{change_id}"
77
+ resp = @api.get_change(@project_id, zone_name, change_id)
50
78
 
51
79
  while resp.status != 'done'
52
80
  puts " * change #{change_id.inspect} is still #{resp.status.inspect}"
@@ -55,38 +83,40 @@ module Acmesmith
55
83
  end
56
84
 
57
85
  puts " * synced!"
86
+ end
58
87
 
88
+ def wait_for_sync_by_dns(zone_name, change)
59
89
  puts "=> Checking DNS resource record"
60
90
  nameservers = @api.get_managed_zone(@project_id, zone_name).name_servers
61
91
  puts " * nameservers: #{nameservers.inspect}"
62
92
  nameservers.each do |ns|
63
93
  Resolv::DNS.open(:nameserver => Resolv.getaddresses(ns)) do |dns|
64
94
  dns.timeouts = 5
65
- begin
66
- ret = dns.getresource([challenge.record_name, domain].join('.'), Resolv::DNS::Resource::IN::TXT)
67
- rescue Resolv::ResolvError => e
68
- puts " * [#{ns}] failed: #{e.to_s}"
69
- sleep 5
70
- retry
95
+ change.additions.each do |rrset|
96
+ required_rrdatas = Set.new(rrset.rrdatas.map{|rrdata| rrdata.gsub(/(\A"|"\z)/, '') })
97
+
98
+ deletion = change.deletions.find{|_deletion| _deletion.name == rrset.name && _deletion.type == rrset.type }
99
+ if deletion
100
+ required_rrdatas -= Set.new(deletion.rrdatas)
101
+ end
102
+
103
+ loop do
104
+ resources = dns.getresources(rrset.name, Resolv::DNS::Resource::IN::TXT)
105
+ actual_rrdatas = resources.map(&:data)
106
+ if required_rrdatas.subset?(Set.new(actual_rrdatas))
107
+ puts " * [#{ns} -> #{rrset.name}] success. (actual=#{actual_rrdatas.inspect})"
108
+ sleep 1
109
+ break
110
+ else
111
+ puts " * [#{ns} -> #{rrset.name}] failed. (required=#{required_rrdatas.to_a.inspect}, but actual=#{actual_rrdatas.inspect})"
112
+ sleep 5
113
+ end
114
+ end
71
115
  end
72
- puts " * [#{ns}] success: ttl=#{ret.ttl.inspect}, data=#{ret.data.inspect}"
73
- sleep 1
74
116
  end
75
117
  end
76
118
  end
77
119
 
78
- def cleanup(domain, challenge)
79
- domain = canonicalize(domain)
80
- zone_name = find_managed_zone(domain).name
81
- change = Google::Apis::DnsV1::Change.new
82
- change.deletions = [
83
- resource_record_set(domain, challenge)
84
- ]
85
- @api.create_change(@project_id, zone_name, change)
86
- end
87
-
88
- private
89
-
90
120
  def load_json_key(filepath)
91
121
  obj = JSON.parse(File.read(filepath))
92
122
  {
@@ -109,13 +139,55 @@ module Acmesmith
109
139
  managed_zone
110
140
  end
111
141
 
112
- def resource_record_set(domain, challenge)
113
- Google::Apis::DnsV1::ResourceRecordSet.new(
114
- name: [challenge.record_name, domain].join("."),
115
- type: challenge.record_type,
116
- rrdatas: [challenge.record_content],
117
- ttl: @config[:ttl] || 5
118
- )
142
+ def change_for_challenges(zone_name, domain_and_challenges, for_cleanup: false)
143
+ current_rrsets = @api.fetch_all(items: :rrsets) do |token|
144
+ @api.list_resource_record_sets(@project_id, zone_name, page_token: token)
145
+ end
146
+
147
+ change = Google::Apis::DnsV1::Change.new
148
+
149
+ change.deletions = domain_and_challenges.map{ |domain, challenge|
150
+ domain = canonicalize(domain)
151
+ name = [challenge.record_name, domain].join('.')
152
+ type = challenge.record_type
153
+
154
+ current_rrsets.find{ |rrset| rrset.type == type && rrset.name == name }
155
+ }.uniq.compact
156
+
157
+ change.additions = domain_and_challenges.map{ |domain, challenge|
158
+ domain = canonicalize(domain)
159
+ name = [challenge.record_name, domain].join('.')
160
+ type = challenge.record_type
161
+ data = "\"#{challenge.record_content}\""
162
+
163
+ {
164
+ name: name,
165
+ type: type,
166
+ rrdatas: [data],
167
+ }
168
+ }.group_by{ |rrset_param|
169
+ [ rrset_param[:name], rrset_param[:type] ]
170
+ }.map{ |(name, type), rrset_params|
171
+ current_rrset = current_rrsets.find{ |rrset| rrset.type == type && rrset.name == name }
172
+
173
+ new_rrset = Google::Apis::DnsV1::ResourceRecordSet.new(
174
+ name: name,
175
+ type: type,
176
+ rrdatas: current_rrset ? current_rrset.rrdatas : [],
177
+ ttl: @config[:ttl] || 5,
178
+ )
179
+
180
+ if for_cleanup
181
+ new_rrset.rrdatas -= rrset_params.map{|rrset| rrset[:rrdatas] }.flatten
182
+ else
183
+ new_rrset.rrdatas += rrset_params.map{|rrset| rrset[:rrdatas] }.flatten
184
+ end
185
+ new_rrset
186
+ }.select{ |rrset|
187
+ rrset.rrdatas != []
188
+ }
189
+
190
+ change
119
191
  end
120
192
  end
121
193
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acmesmith-google-cloud-dns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chikanaga Tomoyuki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-20 00:00:00.000000000 Z
11
+ date: 2018-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acmesmith
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: google-api-client
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -119,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
119
  version: '0'
120
120
  requirements: []
121
121
  rubyforge_project:
122
- rubygems_version: 2.6.13
122
+ rubygems_version: 2.6.14.1
123
123
  signing_key:
124
124
  specification_version: 4
125
125
  summary: acmesmith plugin implementing dns-01 using Google Cloud DNS