acmesmith 2.3.1 → 2.4.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 +4 -4
- data/.github/stale.yml +17 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +17 -15
- data/docs/challenge_responders/route53.md +4 -0
- data/lib/acmesmith/authorization_service.rb +24 -3
- data/lib/acmesmith/challenge_responders/route53.rb +122 -6
- data/lib/acmesmith/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4089bdb36940e87e2f996a6c820a2f06505024f6ae9721c3da332999c2145998
|
4
|
+
data.tar.gz: 1abf984f4f5d82a0d266bb5aee3905708ba1392238439663af0bc8ac9aa700b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1147c0742b2bd14205b89ca76f4b011453d2eeadf0df2aaad094b7e3c32907de322aed48c0d4fce40a4120ddd782c5c67e6da1504644fce3db679b4452ef774
|
7
|
+
data.tar.gz: e8bc441a9cb9fff624f338fb33946bab80bf742848e695130b17bd807ba8da98d79e5999459ce824cf6ebcdac6206f156026a17adc754aa6bde8356cc3a915f1
|
data/.github/stale.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Number of days of inactivity before an issue becomes stale
|
2
|
+
daysUntilStale: 30
|
3
|
+
# Number of days of inactivity before a stale issue is closed
|
4
|
+
daysUntilClose: 7
|
5
|
+
# Issues with these labels will never be considered stale
|
6
|
+
exemptLabels:
|
7
|
+
- pinned
|
8
|
+
- security
|
9
|
+
# Label to use when marking an issue as stale
|
10
|
+
staleLabel: rotten
|
11
|
+
# Comment to post when marking an issue as stale. Set to `false` to disable
|
12
|
+
markComment: >
|
13
|
+
This issue has been automatically marked as stale because it has not had
|
14
|
+
recent activity. It will be closed if no further activity occurs. Thank you
|
15
|
+
for your contributions.
|
16
|
+
# Comment to post when closing a stale issue. Set to `false` to disable
|
17
|
+
closeComment: false
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## v2.4.0 (2020-05-12)
|
2
|
+
|
3
|
+
### Enhancement
|
4
|
+
|
5
|
+
- route53: Gains `restore_to_original_records` option. When enabled, existing record will be restored after authorizing domain names. Useful when other ACME tools or providers using ACME where requires a certain record to remain as long as possible for their renewal process (e.g. Fastly TLS).
|
6
|
+
|
1
7
|
## v2.3.1 (2020-05-12)
|
2
8
|
|
3
9
|
### Fixes
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
acmesmith (2.
|
4
|
+
acmesmith (2.4.0)
|
5
5
|
acme-client (~> 2)
|
6
6
|
aws-sdk-acm
|
7
7
|
aws-sdk-route53
|
@@ -11,33 +11,34 @@ PATH
|
|
11
11
|
GEM
|
12
12
|
remote: https://rubygems.org/
|
13
13
|
specs:
|
14
|
-
acme-client (2.0.
|
14
|
+
acme-client (2.0.7)
|
15
15
|
faraday (>= 0.17, < 2.0.0)
|
16
16
|
aws-eventstream (1.1.0)
|
17
|
-
aws-partitions (1.
|
18
|
-
aws-sdk-acm (1.
|
19
|
-
aws-sdk-core (~> 3, >= 3.
|
17
|
+
aws-partitions (1.402.0)
|
18
|
+
aws-sdk-acm (1.38.0)
|
19
|
+
aws-sdk-core (~> 3, >= 3.109.0)
|
20
20
|
aws-sigv4 (~> 1.1)
|
21
|
-
aws-sdk-core (3.
|
21
|
+
aws-sdk-core (3.109.3)
|
22
22
|
aws-eventstream (~> 1, >= 1.0.2)
|
23
23
|
aws-partitions (~> 1, >= 1.239.0)
|
24
24
|
aws-sigv4 (~> 1.1)
|
25
25
|
jmespath (~> 1.0)
|
26
|
-
aws-sdk-kms (1.
|
27
|
-
aws-sdk-core (~> 3, >= 3.
|
26
|
+
aws-sdk-kms (1.39.0)
|
27
|
+
aws-sdk-core (~> 3, >= 3.109.0)
|
28
28
|
aws-sigv4 (~> 1.1)
|
29
|
-
aws-sdk-route53 (1.
|
30
|
-
aws-sdk-core (~> 3, >= 3.
|
29
|
+
aws-sdk-route53 (1.44.0)
|
30
|
+
aws-sdk-core (~> 3, >= 3.109.0)
|
31
31
|
aws-sigv4 (~> 1.1)
|
32
|
-
aws-sdk-s3 (1.
|
33
|
-
aws-sdk-core (~> 3, >= 3.
|
32
|
+
aws-sdk-s3 (1.86.0)
|
33
|
+
aws-sdk-core (~> 3, >= 3.109.0)
|
34
34
|
aws-sdk-kms (~> 1)
|
35
35
|
aws-sigv4 (~> 1.1)
|
36
|
-
aws-sigv4 (1.
|
37
|
-
aws-eventstream (~> 1
|
36
|
+
aws-sigv4 (1.2.2)
|
37
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
38
38
|
diff-lcs (1.3)
|
39
|
-
faraday (1.0
|
39
|
+
faraday (1.1.0)
|
40
40
|
multipart-post (>= 1.2, < 3)
|
41
|
+
ruby2_keywords
|
41
42
|
jmespath (1.4.0)
|
42
43
|
mini_portile2 (2.4.0)
|
43
44
|
multipart-post (2.1.1)
|
@@ -57,6 +58,7 @@ GEM
|
|
57
58
|
diff-lcs (>= 1.2.0, < 2.0)
|
58
59
|
rspec-support (~> 3.9.0)
|
59
60
|
rspec-support (3.9.3)
|
61
|
+
ruby2_keywords (0.0.2)
|
60
62
|
thor (1.0.1)
|
61
63
|
|
62
64
|
PLATFORMS
|
@@ -21,6 +21,10 @@ challenge_responders:
|
|
21
21
|
## Required when you have multiple hosted zones for the same domain name.
|
22
22
|
hosted_zone_map:
|
23
23
|
"example.org.": "/hostedzone/DEADBEEF"
|
24
|
+
|
25
|
+
# Restore to original records on cleanup (after domain authorization). Default to false.
|
26
|
+
# Useful when you need to keep existing record as long as possible.
|
27
|
+
restore_to_original_records: true
|
24
28
|
```
|
25
29
|
|
26
30
|
## IAM Policy
|
@@ -69,9 +69,30 @@ module Acmesmith
|
|
69
69
|
puts "=> Requesting validations..."
|
70
70
|
puts
|
71
71
|
processes.each do |process|
|
72
|
-
|
73
|
-
process.challenge.
|
74
|
-
|
72
|
+
challenge = process.challenge
|
73
|
+
print " * #{process.domain} (#{challenge.challenge_type}) ..."
|
74
|
+
retried = false
|
75
|
+
begin
|
76
|
+
challenge.request_validation()
|
77
|
+
puts " [ ok ]"
|
78
|
+
rescue Acme::Client::Error::Malformed
|
79
|
+
# Rescue in case of requesting validation for a challenge which has already determined valid (asynchronously while we're receiving it).
|
80
|
+
# LE Boulder doesn't take this as an error, but pebble do.
|
81
|
+
# https://github.com/letsencrypt/boulder/blob/ebba443cad233111ee2b769ef09b32a13c3ba57e/wfe2/wfe.go#L1235
|
82
|
+
# https://github.com/letsencrypt/pebble/blob/b60b0b677c280ccbf63de55a26775591935c448b/wfe/wfe.go#L2166
|
83
|
+
challenge.reload
|
84
|
+
if process.valid?
|
85
|
+
puts " [ ok ] (turned valid in background)"
|
86
|
+
next
|
87
|
+
end
|
88
|
+
|
89
|
+
if retried
|
90
|
+
raise
|
91
|
+
else
|
92
|
+
retried = true
|
93
|
+
retry
|
94
|
+
end
|
95
|
+
end
|
75
96
|
end
|
76
97
|
puts
|
77
98
|
|
@@ -17,7 +17,7 @@ module Acmesmith
|
|
17
17
|
true
|
18
18
|
end
|
19
19
|
|
20
|
-
def initialize(aws_access_key: nil, assume_role: nil, hosted_zone_map: {})
|
20
|
+
def initialize(aws_access_key: nil, assume_role: nil, hosted_zone_map: {}, restore_to_original_records: false)
|
21
21
|
aws_options = {region: 'us-east-1'}.tap do |opt|
|
22
22
|
opt[:credentials] = Aws::Credentials.new(aws_access_key['access_key_id'], aws_access_key['secret_access_key'], aws_access_key['session_token']) if aws_access_key
|
23
23
|
end
|
@@ -34,13 +34,25 @@ module Acmesmith
|
|
34
34
|
|
35
35
|
@hosted_zone_map = hosted_zone_map
|
36
36
|
@hosted_zone_cache = {}
|
37
|
+
|
38
|
+
@restore_to_original_records = restore_to_original_records
|
39
|
+
@original_records = {}
|
37
40
|
end
|
38
41
|
|
39
42
|
def respond_all(*domain_and_challenges)
|
43
|
+
save_original_records(*domain_and_challenges) if @restore_to_original_records
|
44
|
+
|
40
45
|
challenges_by_hosted_zone = domain_and_challenges.group_by { |(domain, _)| find_hosted_zone(domain) }
|
41
46
|
|
42
47
|
zone_and_batches = challenges_by_hosted_zone.map do |zone_id, dcs|
|
43
|
-
[
|
48
|
+
[
|
49
|
+
zone_id,
|
50
|
+
change_batch_for_challenges(
|
51
|
+
dcs,
|
52
|
+
action: 'UPSERT',
|
53
|
+
pre_changes: changes_to_delete_original_cname(zone_id, *dcs),
|
54
|
+
),
|
55
|
+
]
|
44
56
|
end
|
45
57
|
|
46
58
|
change_ids = request_changing_rrset(zone_and_batches, comment: 'for challenge response')
|
@@ -51,7 +63,15 @@ module Acmesmith
|
|
51
63
|
challenges_by_hosted_zone = domain_and_challenges.group_by { |(domain, _)| find_hosted_zone(domain) }
|
52
64
|
|
53
65
|
zone_and_batches = challenges_by_hosted_zone.map do |zone_id, dcs|
|
54
|
-
[
|
66
|
+
[
|
67
|
+
zone_id,
|
68
|
+
change_batch_for_challenges(
|
69
|
+
dcs,
|
70
|
+
action: 'DELETE',
|
71
|
+
comment: '(cleanup)',
|
72
|
+
post_changes: changes_to_restore_original_records(zone_id, *dcs),
|
73
|
+
),
|
74
|
+
]
|
55
75
|
end
|
56
76
|
|
57
77
|
request_changing_rrset(zone_and_batches, comment: 'to remove challenge responses')
|
@@ -59,6 +79,69 @@ module Acmesmith
|
|
59
79
|
|
60
80
|
private
|
61
81
|
|
82
|
+
def save_original_records(*domain_and_challenges)
|
83
|
+
domain_and_challenges.each do |domain, challenge|
|
84
|
+
|
85
|
+
hosted_zone_id = find_hosted_zone(domain)
|
86
|
+
name = "#{challenge.record_name}.#{domain}."
|
87
|
+
|
88
|
+
rrsets = list_existing_rrsets(hosted_zone_id, name)
|
89
|
+
next if rrsets.empty?
|
90
|
+
|
91
|
+
@original_records[hosted_zone_id] ||= {}
|
92
|
+
@original_records[hosted_zone_id][name] = rrsets
|
93
|
+
puts " * original_record: #{domain}(#{hosted_zone_id}): #{rrsets.inspect}"
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def changes_to_delete_original_cname(zone_id, *domain_and_challenges)
|
99
|
+
@original_records[zone_id] ||= {}
|
100
|
+
domain_and_challenges.map do |domain, challenge|
|
101
|
+
name = "#{challenge.record_name}.#{domain}."
|
102
|
+
original_records = @original_records[zone_id][name]
|
103
|
+
next unless original_records
|
104
|
+
original_cname = original_records.find{ |_| _.type == 'CNAME' }
|
105
|
+
next unless original_cname
|
106
|
+
|
107
|
+
# FIXME: support set_identifier?
|
108
|
+
{
|
109
|
+
action: 'DELETE',
|
110
|
+
resource_record_set: {
|
111
|
+
name: original_cname.name,
|
112
|
+
ttl: original_cname.ttl,
|
113
|
+
type: original_cname.type,
|
114
|
+
resource_records: original_cname.resource_records.map(&:to_h),
|
115
|
+
alias_target: original_cname.alias_target&.to_h,
|
116
|
+
},
|
117
|
+
}
|
118
|
+
end.compact
|
119
|
+
end
|
120
|
+
|
121
|
+
def changes_to_restore_original_records(zone_id, *domain_and_challenges)
|
122
|
+
@original_records[zone_id] ||= {}
|
123
|
+
domain_and_challenges.flat_map do |domain, challenge|
|
124
|
+
name = "#{challenge.record_name}.#{domain}."
|
125
|
+
original_records = @original_records[zone_id][name]
|
126
|
+
next unless original_records
|
127
|
+
|
128
|
+
# FIXME: support set_identifier?
|
129
|
+
original_records.map do |original_record|
|
130
|
+
next if original_record.type != challenge.record_type && original_record.type != 'CNAME'
|
131
|
+
{
|
132
|
+
action: 'CREATE',
|
133
|
+
resource_record_set: {
|
134
|
+
name: original_record.name,
|
135
|
+
ttl: original_record.ttl,
|
136
|
+
type: original_record.type,
|
137
|
+
resource_records: original_record.resource_records.map(&:to_h),
|
138
|
+
alias_target: original_record.alias_target&.to_h,
|
139
|
+
},
|
140
|
+
}
|
141
|
+
end
|
142
|
+
end.compact
|
143
|
+
end
|
144
|
+
|
62
145
|
def request_changing_rrset(zone_and_batches, comment: nil)
|
63
146
|
puts "=> Requesting RRSet change #{comment}"
|
64
147
|
puts
|
@@ -107,10 +190,9 @@ module Acmesmith
|
|
107
190
|
end
|
108
191
|
end
|
109
192
|
puts
|
110
|
-
|
111
193
|
end
|
112
194
|
|
113
|
-
def change_batch_for_challenges(domain_and_challenges, comment: nil, action: 'UPSERT')
|
195
|
+
def change_batch_for_challenges(domain_and_challenges, comment: nil, action: 'UPSERT', pre_changes: [], post_changes: [])
|
114
196
|
changes = domain_and_challenges
|
115
197
|
.map do |d, c|
|
116
198
|
rrset_for_challenge(d, c)
|
@@ -133,7 +215,7 @@ module Acmesmith
|
|
133
215
|
|
134
216
|
{
|
135
217
|
comment: "ACME challenge response #{comment}",
|
136
|
-
changes: changes,
|
218
|
+
changes: pre_changes + changes + post_changes,
|
137
219
|
}
|
138
220
|
end
|
139
221
|
|
@@ -181,6 +263,40 @@ module Acmesmith
|
|
181
263
|
end.group_by(&:first).map { |domain, kvs| [domain, kvs.map(&:last)] }.to_h.merge(hosted_zone_map)
|
182
264
|
end
|
183
265
|
end
|
266
|
+
|
267
|
+
def list_existing_rrsets(hosted_zone_id, name)
|
268
|
+
rrsets = []
|
269
|
+
start_record_name = name
|
270
|
+
start_record_type = nil
|
271
|
+
start_record_identifier = nil
|
272
|
+
|
273
|
+
while start_record_name == name
|
274
|
+
begin
|
275
|
+
tries = 0
|
276
|
+
page = @route53.list_resource_record_sets(
|
277
|
+
hosted_zone_id: hosted_zone_id,
|
278
|
+
start_record_name: start_record_name,
|
279
|
+
start_record_type: start_record_type,
|
280
|
+
start_record_identifier: start_record_identifier,
|
281
|
+
max_items: 10,
|
282
|
+
)
|
283
|
+
page.resource_record_sets.each do |rrset|
|
284
|
+
rrsets << rrset if rrset.name == name
|
285
|
+
end
|
286
|
+
|
287
|
+
start_record_name = page.next_record_name
|
288
|
+
start_record_type = page.next_record_type
|
289
|
+
start_record_identifier = page.next_record_identifier
|
290
|
+
rescue Aws::Route53::Errors::Throttling => e
|
291
|
+
interval = (2**tries) * 0.1
|
292
|
+
$stderr.puts " ! #{e.class}: Sleeping #{interval} seconds (#{e.message})"
|
293
|
+
sleep interval
|
294
|
+
tries += 1
|
295
|
+
retry
|
296
|
+
end
|
297
|
+
end
|
298
|
+
rrsets
|
299
|
+
end
|
184
300
|
end
|
185
301
|
end
|
186
302
|
end
|
data/lib/acmesmith/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acmesmith
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sorah Fukumori
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acme-client
|
@@ -136,6 +136,7 @@ extensions: []
|
|
136
136
|
extra_rdoc_files: []
|
137
137
|
files:
|
138
138
|
- ".dockerignore"
|
139
|
+
- ".github/stale.yml"
|
139
140
|
- ".github/workflows/build.yml"
|
140
141
|
- ".gitignore"
|
141
142
|
- ".rspec"
|
@@ -190,7 +191,7 @@ homepage: https://github.com/sorah/acmesmith
|
|
190
191
|
licenses:
|
191
192
|
- MIT
|
192
193
|
metadata: {}
|
193
|
-
post_install_message:
|
194
|
+
post_install_message:
|
194
195
|
rdoc_options: []
|
195
196
|
require_paths:
|
196
197
|
- lib
|
@@ -206,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
207
|
version: '0'
|
207
208
|
requirements: []
|
208
209
|
rubygems_version: 3.1.2
|
209
|
-
signing_key:
|
210
|
+
signing_key:
|
210
211
|
specification_version: 4
|
211
212
|
summary: ACME client (Let's encrypt client) to manage certificate in multi server
|
212
213
|
environment with cloud services (e.g. AWS)
|