amarillo 0.3.2 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2dbdaceaf0eac001e1d7cb1ba63058ce2cee3e81be2c61d08378c3db7db562ae
4
- data.tar.gz: b1afafa36c49e473a09bb56de32c354a668516e7fd71ec8770b38b681777c825
3
+ metadata.gz: 5229f8188b6086ad5c09326cc438d8375703602036e50534cbb69d684a2ec9e7
4
+ data.tar.gz: b1a7005a37527eb00fd44dbc45cfa0ea3e232f90bbd8ad004f712bb74d8be0a5
5
5
  SHA512:
6
- metadata.gz: d19987aa6a9c84b92411fc5f5fa99102e9bd0f3abb8fcfb3dcacd8f28bc39a7806a28e932d7e39463680ca1281b701ad30b9a44e54c786918795f12d909d4e10
7
- data.tar.gz: 2eef894b6c9ed756249684f31f2b4392ef2a79bd5c281858b64c9615f19bdabe91a46f43cf480fe9e9ff429c1cacc173f6d98b89916ffc21bc82f633906a675f
6
+ metadata.gz: b167194f5fbb58f945fb5674d9466473cf9777a04536cbb709bdf7e75e018666c7421beb28dfc300ad49c5e522f2250339f8b92c882273608525084708caa088
7
+ data.tar.gz: 1e40be3bb8babc91470dca9588c51467cd572e99dada1d9e716f4cbe57e669c0731956a3e36598c35a37a9a015369b88b01cb7ffe604442463d6b884dd1d907d
data/bin/amarillo CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright 2021 iAchieved.it LLC
3
+ # Copyright 2022 iAchieved.it LLC
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the "Software"), to deal
@@ -35,12 +35,16 @@ OptionParser.new do |opts|
35
35
  options[:list] = l
36
36
  end
37
37
 
38
- opts.on("-d", "--delete", "Delete certificate") do |d|
38
+ opts.on("-d", "--delete COMMONNAME", "Delete certificate specified by COMMONNAME") do |d|
39
39
  options[:delete] = d
40
40
  end
41
41
 
42
- opts.on("-r", "--renew", "Renew certificates") do |r|
43
- options[:renew] = r
42
+ opts.on("-r", "--renew [COMMONNAME]", "Renew specific or all certificates") do |r|
43
+ if r
44
+ options[:renew] = r
45
+ else
46
+ options[:renew] = 'all'
47
+ end
44
48
  end
45
49
 
46
50
  opts.on("-z", "--zone ZONE", "Hosted zone") do |z|
@@ -55,6 +59,14 @@ OptionParser.new do |opts|
55
59
  options[:name] = n
56
60
  end
57
61
 
62
+ opts.on("-k", "--keytype KEYTYPE", "Valid key types: ") do |k|
63
+ options[:keytype] = k
64
+ end
65
+
66
+ opts.on("-s", "--script SCRIPT", "A script or command to execute after a successful certificate creation or renewal") do |s|
67
+ options[:script] = s
68
+ end
69
+
58
70
  opts.on("-a", "--amarillo-home AMARILLO_HOME", "Home directory for configuration, keys, and certificates") do |o|
59
71
  options[:amarillo_home] = a
60
72
  end
@@ -103,8 +115,8 @@ else
103
115
  email = options[:email]
104
116
  end
105
117
 
106
- if options[:name].nil? and options[:renew].nil? and options[:list].nil? then
107
- puts "Usage: amarillo --name COMMONNAME [--zone ZONE] [--email EMAIL] [--amarillo-home AMARILLO_HOME]"
118
+ if options[:name].nil? and options[:renew].nil? and options[:list].nil? and options[:delete].nil? then
119
+ puts "Usage: amarillo [--name COMMONNAME|--renew|--renew COMMONNAME] [--zone ZONE] [--email EMAIL] [--amarillo-home AMARILLO_HOME]"
108
120
  exit -1
109
121
  else
110
122
  name = options[:name]
@@ -119,13 +131,26 @@ end
119
131
  y = Amarillo.new amarillo_home
120
132
 
121
133
  if options[:renew] then
122
- y.renewCertificates
134
+
135
+ if options[:renew] != 'all' then
136
+ y.renewCertificate options[:renew]
137
+ else
138
+ y.renewCertificates
139
+ end
140
+
123
141
  elsif options[:list] then
124
142
  y.listCertificates
125
143
  elsif options[:delete] then
126
- y.deleteCertificate name
144
+ y.deleteCertificate options[:delete]
127
145
  else
128
- y.requestCertificate zone, name, email, nil
146
+ config = {
147
+ "zone" => zone,
148
+ "commonName" => name,
149
+ "email" => email,
150
+ "key_type" => options[:keytype],
151
+ "script" => options[:script]
152
+ }
153
+ y.requestCertificate certConfig: config
129
154
  end
130
155
 
131
156
 
@@ -105,7 +105,10 @@ HEREDOC
105
105
  "email" => email,
106
106
  "zone" => zone,
107
107
  "nameservers" => ['208.67.222.222', '9.9.9.9'],
108
- "key_type" => 'ec,secp384r1'
108
+ "key_type" => 'ec,secp384r1',
109
+ "owner" => 'root',
110
+ "group" => 'root',
111
+ "key_mode" => 0660
109
112
  }}
110
113
  File.write(@configFile, config.to_yaml)
111
114
  else
data/lib/amarillo.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # Inspired by Pete Keen's (pete@petekeen.net) post
4
4
  # https://www.petekeen.net/lets-encrypt-without-certbot
5
5
  #
6
- # Copyright 2021 iAchieved.it LLC
6
+ # Copyright 2022 iAchieved.it LLC
7
7
  #
8
8
  # Permission is hereby granted, free of charge, to any person obtaining a copy
9
9
  # of this software and associated documentation files (the "Software"), to deal
@@ -86,7 +86,17 @@ class Amarillo
86
86
 
87
87
  end
88
88
 
89
- def requestCertificate(zone, commonName, email, key_type)
89
+ def requestCertificate(configPath: nil, certConfig: nil)
90
+
91
+ if configPath
92
+ certConfig = YAML.load(File.read(configPath))
93
+ end
94
+
95
+ commonName = certConfig["commonName"]
96
+ email = certConfig["email"]
97
+ zone = certConfig["zone"]
98
+ key_type = certConfig["key_type"]
99
+ script = certConfig["script"]
90
100
 
91
101
  @zone = zone
92
102
 
@@ -139,11 +149,16 @@ class Amarillo
139
149
 
140
150
  @route53.change_resource_record_sets(options)
141
151
 
152
+ at_exit do
153
+ self.cleanup label, record_type, challengeValue
154
+ end
155
+
156
+
142
157
  nameservers = @environment.get_zone_nameservers
143
158
 
144
159
  @logger.info "Waiting for DNS record to propagate"
145
160
  while !check_dns(commonName, nameservers, challengeValue)
146
- sleep 2
161
+ sleep 5
147
162
  @logger.info "Still waiting..."
148
163
  end
149
164
 
@@ -152,20 +167,13 @@ class Amarillo
152
167
  @logger.info "Requesting validation..."
153
168
  authorization.dns.reload
154
169
  while authorization.dns.status == 'pending'
155
- sleep 2
170
+ sleep 5
156
171
  @logger.info "DNS status: #{authorization.dns.status}"
157
172
  authorization.dns.reload
158
173
  end
159
174
 
160
175
  @logger.info "Generating key"
161
176
 
162
- # Create certificate yml
163
- certConfig = {
164
- "commonName" => commonName,
165
- "email" => email,
166
- "zone" => zone
167
- }
168
-
169
177
  if key_type
170
178
  certConfig["key_type"] = key_type
171
179
  else
@@ -178,16 +186,22 @@ class Amarillo
178
186
  if type == 'ec' then
179
187
  certPrivateKey = OpenSSL::PKey::EC.new(args).generate_key
180
188
  elsif type == 'rsa' then
181
- certPrivateKey = OpenSSL::PKey::RSA.new(args)
189
+ if args.to_i > 0
190
+ certPrivateKey = OpenSSL::PKey::RSA.new(args.to_i)
191
+ else
192
+ @logger.error("Invalid RSA key size: #{args}")
193
+ end
182
194
  end
183
195
 
184
196
  @logger.info "Requesting certificate..."
185
197
  csr = Acme::Client::CertificateRequest.new private_key: certPrivateKey,
186
198
  names: [commonName]
187
199
 
188
- while order.status == 'processing'
200
+ while order.status != 'ready'
189
201
  sleep(1)
202
+ @logger.info "Order status: #{order.status}"
190
203
  order.reload
204
+ raise if order.status == 'invalid'
191
205
  end
192
206
 
193
207
  @logger.info "Order status: #{order.status}"
@@ -196,7 +210,7 @@ class Amarillo
196
210
  order.finalize(csr: csr)
197
211
  rescue
198
212
  @logger.error("Error finalizing certificate order")
199
- self.cleanup label, record_type, challengeValue
213
+ raise
200
214
  end
201
215
 
202
216
  keyOutputPath = "#{@keyPath}/#{commonName}.key"
@@ -207,7 +221,13 @@ class Amarillo
207
221
  File.open(keyOutputPath, "w") do |f|
208
222
  f.puts certPrivateKey.to_pem.to_s
209
223
  end
210
- File.chmod(0600, keyOutputPath)
224
+
225
+ keyMode = @config["defaults"]["key_mode"] || 0600
226
+ if keyMode
227
+ File.chmod(keyMode, keyOutputPath)
228
+ else
229
+ File.chmod(0600, keyOutputPath)
230
+ end
211
231
 
212
232
  @logger.info "Saving certificate to #{certOutputPath}"
213
233
 
@@ -215,10 +235,20 @@ class Amarillo
215
235
  f.puts order.certificate
216
236
  end
217
237
 
238
+ owner = certConfig["owner"] || @config["defaults"]["owner"] || "root"
239
+ group = certConfig["group"] || @config["defaults"]["group"] || "root"
240
+ begin
241
+ FileUtils.chown(owner, group, keyOutputPath)
242
+ rescue
243
+ @logger.info "Unable to change ownership of key file #{keyOutputPath}"
244
+ end
245
+
218
246
  certConfigFile = "#{@configsPath}/#{commonName}.yml"
247
+
248
+ @logger.info "Saving certificate configuration to #{certConfigFile}"
219
249
  File.write(certConfigFile, certConfig.to_yaml)
220
250
 
221
- self.cleanup label, record_type, challengeValue
251
+ self.doRenewalAction script
222
252
 
223
253
  end
224
254
 
@@ -247,10 +277,6 @@ class Amarillo
247
277
  @route53.change_resource_record_sets(options)
248
278
  end
249
279
 
250
- def renewCertificate(zone, commonName, email)
251
-
252
- end
253
-
254
280
  def listCertificates
255
281
 
256
282
  rows = []
@@ -284,6 +310,7 @@ class Amarillo
284
310
 
285
311
  end
286
312
 
313
+ # Renew all certificates
287
314
  def renewCertificates
288
315
  t = Time.now
289
316
  @logger.info "Renewing certificates"
@@ -291,10 +318,7 @@ class Amarillo
291
318
  Dir["#{@configsPath}/*.yml"].each do |c|
292
319
  config = YAML.load(File.read(c))
293
320
 
294
- cn = config["commonName"]
295
- email = config["email"]
296
- zone = config["zone"]
297
- key_type = config["key_type"]
321
+ cn = config["commonName"]
298
322
 
299
323
  certificatePath = "#{@certificatePath}/#{cn}.crt"
300
324
  raw = File.read certificatePath
@@ -303,12 +327,39 @@ class Amarillo
303
327
 
304
328
  if daysToExpiration < 30 then
305
329
  @logger.info "#{cn} certificate needs to be renewed"
306
- self.requestCertificate zone, cn, email, key_type
330
+ self.requestCertificate configPath: c
307
331
  else
308
332
  @logger.info "#{cn} certificate does not need to be renewed"
309
333
  end
310
334
  end
311
335
  end
336
+
337
+ # Renew specific certificate, implied force
338
+ def renewCertificate(commonName)
339
+
340
+ configPath = "#{@configsPath}/#{commonName}.yml"
341
+ self.requestCertificate configPath: configPath
342
+
343
+ end
344
+
345
+ def doRenewalAction(renewal_action)
346
+
347
+ if renewal_action
348
+
349
+ @logger.info "Executing certificate renewal action: #{renewal_action}"
350
+
351
+ begin
352
+ %x( #{renewal_action} )
353
+ @logger.info "Renewal action returned: #{$?}"
354
+ rescue
355
+ @logger.error("Error executing certificate renewal action")
356
+ raise
357
+ end
358
+
359
+ end
360
+
361
+ end
362
+
312
363
  end
313
364
 
314
365
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amarillo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - iAchieved.it LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-06 00:00:00.000000000 Z
11
+ date: 2022-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acme-client
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.2'
33
+ version: '3.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.2'
40
+ version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: aws-sdk-core
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 3.3.3
112
+ rubygems_version: 3.2.33
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: Amarillo