amarillo 0.3.2 → 0.4.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
  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