acme-r53-cli 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0bffb5e1041e5b53ca07b2bfc74e50f54f72d52ac4887d064bb9d1c2fa819a3a
4
+ data.tar.gz: a537081d73193d5a501dc84f3c969e4163536169cbb3cd976d5346d4be2d85ef
5
+ SHA512:
6
+ metadata.gz: a10e69c4040799553083bbbb5c07f6a3ef1a6606a3400aa291174124acd8d48092ef07da6715c4fab730a55d178c714b1f12457ad2de467dd38a512a2f98c769
7
+ data.tar.gz: 780afcc3f216d9beb8d66f596c6a4e12b2e7dd11058154134972264b400214184ba1f19cbdf19b0d1ed26beb465d607e787da179a1515944f1fb4b03c1aab9b8
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "openssl"
4
+
5
+ require "docopt"
6
+ require "acme-client"
7
+ require "aws-sdk-route53"
8
+ require "pry"
9
+
10
+ require "acme-cli/version"
11
+
12
+ doc = <<DOCOPT
13
+ acme.rb
14
+
15
+ Usage:
16
+ acme.rb sign [options] <domain_name> [<alt_name>...]
17
+ acme.rb register [options] --email <email> [--agree-terms]
18
+ acme.rb -h | --help
19
+ acme.rb -v | --version
20
+
21
+ Options:
22
+ -h --help Show this message
23
+ -v --version Show the version
24
+ --account <account.pem> Provide existing account key
25
+ --domain <domain.pem> Provide domain private key
26
+ --staging Use LE Staging directory
27
+
28
+ DOCOPT
29
+
30
+ def load_key(passed_in, passed_in_path, default_path)
31
+ if passed_in
32
+ STDERR.puts "Loading #{passed_in_path}"
33
+ if File.file?(passed_in_path)
34
+ STDERR.puts "[Error]: Cannot load #{passed_in_path}"
35
+ exit 1
36
+ end
37
+ OpenSSL::PKey::RSA.new(File.read(passed_in_path))
38
+ else
39
+ if File.file?(default_path)
40
+ STDERR.puts "Loading #{default_path}"
41
+ OpenSSL::PKey::RSA.new(File.read(default_path))
42
+ else
43
+ STDERR.puts "Creating key #{default_path}"
44
+ key = OpenSSL::PKey::RSA.new(4096)
45
+ File.write(default_path, key)
46
+ key
47
+ end
48
+ end
49
+ end
50
+
51
+ def get_test_name(entry, challenge)
52
+ if entry.include?("*")
53
+ "#{challenge.record_name}.#{entry.gsub("*.", "")}"
54
+ else
55
+ "#{challenge.record_name}.#{entry}"
56
+ end
57
+ end
58
+
59
+ def get_csr_names(identifier)
60
+ if identifier.include?("*")
61
+ [
62
+ identifier
63
+ ]
64
+ else
65
+ [
66
+ identifier
67
+ ]
68
+ end
69
+ end
70
+
71
+ def get_zone_name(record)
72
+ parts = record.split(/\./)
73
+ parts.slice(parts.length - 2, parts.length - 1).join(".") + "."
74
+ end
75
+
76
+ def update_dns(entry, challenge)
77
+ r53 = Aws::Route53::Client.new
78
+ top_domain = get_zone_name(entry)
79
+ zone = r53.list_hosted_zones.hosted_zones.select do |zone|
80
+ zone.name == top_domain
81
+ end.first
82
+
83
+ record = get_test_name(entry, challenge)
84
+
85
+ change = r53.change_resource_record_sets({
86
+ change_batch: {
87
+ changes: [{
88
+ action: "CREATE",
89
+ resource_record_set: {
90
+ resource_records: [
91
+ {
92
+ value: "\"#{challenge.record_content}\"",
93
+ },
94
+ ],
95
+ name: record,
96
+ ttl: 10,
97
+ type: challenge.record_type
98
+ }
99
+ }]
100
+ },
101
+ hosted_zone_id: zone.id
102
+ })
103
+
104
+ r53.wait_until(:resource_record_sets_changed, id: change.change_info.id)
105
+
106
+ [zone.id, record, challenge.record_type, challenge.record_content]
107
+ end
108
+
109
+ def delete_dns_record(zone_id, record, type, value)
110
+ r53 = Aws::Route53::Client.new
111
+
112
+ r53.change_resource_record_sets({
113
+ change_batch: {
114
+ changes: [{
115
+ action: "DELETE",
116
+ resource_record_set: {
117
+ resource_records: [
118
+ {
119
+ value: "\"#{value}\"",
120
+ },
121
+ ],
122
+ name: record,
123
+ ttl: 10,
124
+ type: type
125
+ }
126
+ }]
127
+ },
128
+ hosted_zone_id: zone_id
129
+ })
130
+ end
131
+
132
+ options = nil
133
+
134
+ begin
135
+ options = Docopt::docopt(doc)
136
+ rescue Docopt::Exit => e
137
+ STDERR.puts e.message
138
+ end
139
+
140
+ exit 1 if options == nil
141
+
142
+ if options["--version"]
143
+ STDERR.puts AcmeCli::Version::VERSION
144
+ exit 0
145
+ end
146
+
147
+ ## Load account key, or create on if missing
148
+ account_key = load_key(
149
+ !!options["--account"],
150
+ options["--account"],
151
+ "./account.pem"
152
+ )
153
+
154
+ directory = if options["--staging"]
155
+ "https://acme-staging-v02.api.letsencrypt.org/directory"
156
+ else
157
+ "https://acme-v02.api.letsencrypt.org/directory"
158
+ end
159
+
160
+ client = Acme::Client.new(
161
+ private_key: account_key,
162
+ directory: directory
163
+ )
164
+
165
+ if options["register"]
166
+ account = client.new_account(
167
+ contact: "mailto:#{options["<email>"]}",
168
+ terms_of_service_agreed: options["--agree-terms"]
169
+ )
170
+ STDERR.puts "Created account #{account.kid}"
171
+ elsif options["sign"]
172
+ identifiers = if options["<alt_name>"]
173
+ options["<alt_name>"].unshift(options["<domain_name>"])
174
+ else
175
+ [options["<domain_name>"]]
176
+ end
177
+ STDERR.puts "Creating order for: #{identifiers.join(", ")}"
178
+ order = client.new_order(identifiers: identifiers)
179
+
180
+ order.authorizations.each_with_index do |auth, idx|
181
+ challenge = auth.dns
182
+
183
+ STDERR.puts "Creating dns record for: #{identifiers[idx]}"
184
+ record_created = update_dns(identifiers[idx], challenge)
185
+
186
+ STDERR.puts "Validating dns record: #{identifiers[idx]}"
187
+ challenge.request_validation
188
+ while challenge.status == 'pending'
189
+ sleep(2)
190
+ challenge.reload
191
+ end
192
+
193
+ STDERR.puts "Validated dns record: #{identifiers[idx]}" if challenge.status == "valid"
194
+
195
+ STDERR.puts "Delete dns record for: #{identifiers.first}"
196
+ delete_dns_record(*record_created)
197
+
198
+ if challenge.status != 'valid'
199
+ STDERR.puts "failed challenge for #{identifiers[idx]}: #{challenge.status}"
200
+ exit 1
201
+ end
202
+ end
203
+
204
+ domain_key = load_key(
205
+ !!options["--domain"],
206
+ options["--domain"],
207
+ "./domain.pem"
208
+ )
209
+
210
+ STDERR.puts "Signing cert for: #{identifiers.join(", ")}"
211
+
212
+ csr = Acme::Client::CertificateRequest.new(
213
+ private_key: domain_key,
214
+ common_name: identifiers.first,
215
+ names: identifiers[1..-1]
216
+ )
217
+ order.finalize(csr: csr)
218
+ sleep(1) while order.status == 'processing'
219
+
220
+ puts order.certificate
221
+ end
@@ -0,0 +1,3 @@
1
+ module AcmeCli
2
+
3
+ end
@@ -0,0 +1,5 @@
1
+ module AcmeCli
2
+ module Version
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acme-r53-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ethan Apocaca
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: docopt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-route53
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: acme-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ description: A cli interface for ACMEv2 DNS challenges with Route53
56
+ email: papodaca@gmail.com
57
+ executables:
58
+ - acme-r53.rb
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - bin/acme-r53.rb
63
+ - lib/acme-cli/cli.rb
64
+ - lib/acme-cli/version.rb
65
+ homepage: https://github.com/papodaca/acme-cli
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.7.6
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: A cli interface for ACMEv2 DNS challenges with Route53
89
+ test_files: []