acme-r53-cli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []