openvoxserver-ca 3.0.0.pre.rc1
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 +7 -0
- data/.github/dependabot.yml +17 -0
- data/.github/release.yml +41 -0
- data/.github/workflows/gem_release.yaml +106 -0
- data/.github/workflows/prepare_release.yml +28 -0
- data/.github/workflows/release.yml +28 -0
- data/.github/workflows/unit_tests.yaml +45 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +15 -0
- data/CODEOWNERS +4 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +20 -0
- data/LICENSE +202 -0
- data/README.md +118 -0
- data/Rakefile +30 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/puppetserver-ca +10 -0
- data/lib/puppetserver/ca/action/clean.rb +109 -0
- data/lib/puppetserver/ca/action/delete.rb +286 -0
- data/lib/puppetserver/ca/action/enable.rb +140 -0
- data/lib/puppetserver/ca/action/generate.rb +330 -0
- data/lib/puppetserver/ca/action/import.rb +196 -0
- data/lib/puppetserver/ca/action/list.rb +253 -0
- data/lib/puppetserver/ca/action/migrate.rb +97 -0
- data/lib/puppetserver/ca/action/prune.rb +289 -0
- data/lib/puppetserver/ca/action/revoke.rb +108 -0
- data/lib/puppetserver/ca/action/setup.rb +188 -0
- data/lib/puppetserver/ca/action/sign.rb +146 -0
- data/lib/puppetserver/ca/certificate_authority.rb +418 -0
- data/lib/puppetserver/ca/cli.rb +145 -0
- data/lib/puppetserver/ca/config/puppet.rb +309 -0
- data/lib/puppetserver/ca/config/puppetserver.rb +84 -0
- data/lib/puppetserver/ca/errors.rb +40 -0
- data/lib/puppetserver/ca/host.rb +176 -0
- data/lib/puppetserver/ca/local_certificate_authority.rb +304 -0
- data/lib/puppetserver/ca/logger.rb +49 -0
- data/lib/puppetserver/ca/stub.rb +17 -0
- data/lib/puppetserver/ca/utils/cli_parsing.rb +67 -0
- data/lib/puppetserver/ca/utils/config.rb +61 -0
- data/lib/puppetserver/ca/utils/file_system.rb +109 -0
- data/lib/puppetserver/ca/utils/http_client.rb +232 -0
- data/lib/puppetserver/ca/utils/inventory.rb +84 -0
- data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
- data/lib/puppetserver/ca/version.rb +5 -0
- data/lib/puppetserver/ca/x509_loader.rb +170 -0
- data/lib/puppetserver/ca.rb +7 -0
- data/openvoxserver-ca.gemspec +31 -0
- data/tasks/spec.rake +15 -0
- data/tasks/vox.rake +19 -0
- metadata +154 -0
@@ -0,0 +1,418 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'puppetserver/ca/utils/http_client'
|
4
|
+
|
5
|
+
module Puppetserver
|
6
|
+
module Ca
|
7
|
+
class CertificateAuthority
|
8
|
+
|
9
|
+
include Puppetserver::Ca::Utils
|
10
|
+
|
11
|
+
# Taken from puppet/lib/settings/duration_settings.rb
|
12
|
+
UNITMAP = {
|
13
|
+
# 365 days isn't technically a year, but is sufficient for most purposes
|
14
|
+
"y" => 365 * 24 * 60 * 60,
|
15
|
+
"d" => 24 * 60 * 60,
|
16
|
+
"h" => 60 * 60,
|
17
|
+
"m" => 60,
|
18
|
+
"s" => 1
|
19
|
+
}
|
20
|
+
|
21
|
+
REVOKE_BODY = JSON.dump({ desired_state: 'revoked' })
|
22
|
+
|
23
|
+
def initialize(logger, settings)
|
24
|
+
@logger = logger
|
25
|
+
@client = HttpClient.new(@logger, settings)
|
26
|
+
@ca_server = settings[:ca_server]
|
27
|
+
@ca_port = settings[:ca_port]
|
28
|
+
end
|
29
|
+
|
30
|
+
def server_has_bulk_signing_endpoints
|
31
|
+
url = HttpClient::URL.new('https', @ca_server, @ca_port, 'status', 'v1', 'services')
|
32
|
+
result = @client.with_connection(url) do |connection|
|
33
|
+
connection.get(url)
|
34
|
+
end
|
35
|
+
version = process_results(:server_version, nil, result)
|
36
|
+
return version >= Gem::Version.new('8.4.0')
|
37
|
+
end
|
38
|
+
|
39
|
+
def worst_result(previous_result, current_result)
|
40
|
+
%i{success invalid not_found error}.each do |state|
|
41
|
+
if previous_result == state
|
42
|
+
return current_result
|
43
|
+
elsif current_result == state
|
44
|
+
return previous_result
|
45
|
+
else
|
46
|
+
next
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a URI-like wrapper around CA specific urls
|
52
|
+
def make_ca_url(resource_type = nil, certname = nil, query = {})
|
53
|
+
HttpClient::URL.new('https', @ca_server, @ca_port, 'puppet-ca', 'v1', resource_type, certname, query)
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_ttl_input(ttl)
|
57
|
+
match = /^(\d+)(s|m|h|d|y)?$/.match(ttl)
|
58
|
+
if match
|
59
|
+
if match[2]
|
60
|
+
match[1].to_i * UNITMAP[match[2]].to_i
|
61
|
+
else
|
62
|
+
ttl
|
63
|
+
end
|
64
|
+
else
|
65
|
+
@logger.err "Error:"
|
66
|
+
@logger.err " '#{ttl}' is an invalid ttl value"
|
67
|
+
@logger.err "Value should match regex \"^(\d+)(s|m|h|d|y)?$\""
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def sign_all
|
73
|
+
return post(resource_type: 'sign',
|
74
|
+
resource_name: 'all',
|
75
|
+
body: '{}',
|
76
|
+
type: :sign_all)
|
77
|
+
end
|
78
|
+
|
79
|
+
def sign_bulk(certnames)
|
80
|
+
return post(resource_type: 'sign',
|
81
|
+
body: "{\"certnames\":#{certnames}}",
|
82
|
+
type: :sign_bulk
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def sign_certs(certnames, ttl=nil)
|
87
|
+
results = []
|
88
|
+
if ttl
|
89
|
+
lifetime = process_ttl_input(ttl)
|
90
|
+
return false if lifetime.nil?
|
91
|
+
body = JSON.dump({ desired_state: 'signed', cert_ttl: lifetime})
|
92
|
+
results = put(certnames,
|
93
|
+
resource_type: 'certificate_status',
|
94
|
+
body: body,
|
95
|
+
type: :sign)
|
96
|
+
else
|
97
|
+
results = put(certnames,
|
98
|
+
resource_type: 'certificate_status',
|
99
|
+
body: JSON.dump({ desired_state: 'signed' }),
|
100
|
+
type: :sign)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
results.all? { |result| result == :success }
|
105
|
+
end
|
106
|
+
|
107
|
+
def revoke_certs(certnames)
|
108
|
+
results = put(certnames,
|
109
|
+
resource_type: 'certificate_status',
|
110
|
+
body: REVOKE_BODY,
|
111
|
+
type: :revoke)
|
112
|
+
|
113
|
+
results.reduce { |prev, curr| worst_result(prev, curr) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def submit_certificate_request(certname, csr)
|
117
|
+
results = put([certname],
|
118
|
+
resource_type: 'certificate_request',
|
119
|
+
body: csr.to_pem,
|
120
|
+
headers: {'Content-Type' => 'text/plain'},
|
121
|
+
type: :submit)
|
122
|
+
|
123
|
+
results.all? { |result| result == :success }
|
124
|
+
end
|
125
|
+
|
126
|
+
# Make an HTTP PUT request to CA
|
127
|
+
# @param resource_type [String] the resource type of url
|
128
|
+
# @param certnames [Array] array of certnames
|
129
|
+
# @param body [JSON/String] body of the put request
|
130
|
+
# @param type [Symbol] type of error processing to perform on result
|
131
|
+
# @return [Boolean] whether all requests were successful
|
132
|
+
def put(certnames, resource_type:, body:, type:, headers: {})
|
133
|
+
url = make_ca_url(resource_type)
|
134
|
+
results = @client.with_connection(url) do |connection|
|
135
|
+
certnames.map do |certname|
|
136
|
+
url.resource_name = certname
|
137
|
+
result = connection.put(body, url, headers)
|
138
|
+
process_results(type, certname, result)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Make an HTTP POST request to CA
|
144
|
+
# @param endpoint [String] the endpoint to post to for the url
|
145
|
+
# @param body [JSON/String] body of the post request
|
146
|
+
# @param type [Symbol] type of error processing to perform on result
|
147
|
+
# @return [Boolean] whether all requests were successful
|
148
|
+
def post(resource_type:, resource_name: nil, body:, type:, headers: {})
|
149
|
+
url = make_ca_url(resource_type, resource_name)
|
150
|
+
results = @client.with_connection(url) do |connection|
|
151
|
+
result = connection.post(body, url, headers)
|
152
|
+
process_results(type, nil, result)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Handle the result data from the /sign and /sign/all endpoints
|
157
|
+
def process_bulk_sign_result_data(result)
|
158
|
+
data = JSON.parse(result.body)
|
159
|
+
signed = data.dig('signed') || []
|
160
|
+
no_csr = data.dig('no-csr') || []
|
161
|
+
signing_errors = data.dig('signing-errors') || []
|
162
|
+
|
163
|
+
if !signed.empty?
|
164
|
+
@logger.inform "Successfully signed the following certificate requests:"
|
165
|
+
signed.each { |s| @logger.inform " #{s}" }
|
166
|
+
end
|
167
|
+
|
168
|
+
@logger.err 'Error:' if !no_csr.empty? || !signing_errors.empty?
|
169
|
+
if !no_csr.empty?
|
170
|
+
@logger.err ' No certificate request found for the following nodes when attempting to sign:'
|
171
|
+
no_csr.each { |s| @logger.err " #{s}" }
|
172
|
+
end
|
173
|
+
if !signing_errors.empty?
|
174
|
+
@logger.err ' Error encountered when attempting to sign the certificate request for the following nodes:'
|
175
|
+
signing_errors.each { |s| @logger.err " #{s}" }
|
176
|
+
end
|
177
|
+
if no_csr.empty? && signing_errors.empty?
|
178
|
+
@logger.err 'No waiting certificate requests to sign.' if signed.empty?
|
179
|
+
return signed.empty? ? :no_requests : :success
|
180
|
+
else
|
181
|
+
return :error
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# logs the action and returns true/false for success
|
186
|
+
def process_results(type, certname, result)
|
187
|
+
case type
|
188
|
+
when :sign
|
189
|
+
case result.code
|
190
|
+
when '204'
|
191
|
+
@logger.inform "Successfully signed certificate request for #{certname}"
|
192
|
+
return :success
|
193
|
+
when '404'
|
194
|
+
@logger.err 'Error:'
|
195
|
+
@logger.err " Could not find certificate request for #{certname}"
|
196
|
+
return :not_found
|
197
|
+
else
|
198
|
+
@logger.err 'Error:'
|
199
|
+
@logger.err " When attempting to sign certificate request '#{certname}', received"
|
200
|
+
@logger.err " code: #{result.code}"
|
201
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
202
|
+
return :error
|
203
|
+
end
|
204
|
+
when :sign_all
|
205
|
+
if result.code == '200'
|
206
|
+
if !result.body
|
207
|
+
@logger.err 'Error:'
|
208
|
+
@logger.err ' Response from /sign/all endpoint did not include a body. Unable to verify certificate requests were signed.'
|
209
|
+
return :error
|
210
|
+
end
|
211
|
+
begin
|
212
|
+
return process_bulk_sign_result_data(result)
|
213
|
+
rescue JSON::ParserError
|
214
|
+
@logger.err 'Error:'
|
215
|
+
@logger.err ' Unable to parse the response from the /sign/all endpoint.'
|
216
|
+
@logger.err " body #{result.body.to_s}"
|
217
|
+
return :error
|
218
|
+
end
|
219
|
+
else
|
220
|
+
@logger.err 'Error:'
|
221
|
+
@logger.err ' When attempting to sign all certificate requests, received:'
|
222
|
+
@logger.err " code: #{result.code}"
|
223
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
224
|
+
return :error
|
225
|
+
end
|
226
|
+
when :sign_bulk
|
227
|
+
if result.code == '200'
|
228
|
+
if !result.body
|
229
|
+
@logger.err 'Error:'
|
230
|
+
@logger.err ' Response from /sign endpoint did not include a body. Unable to verify certificate requests were signed.'
|
231
|
+
return :error
|
232
|
+
end
|
233
|
+
begin
|
234
|
+
return process_bulk_sign_result_data(result)
|
235
|
+
rescue JSON::ParserError
|
236
|
+
@logger.err 'Error:'
|
237
|
+
@logger.err ' Unable to parse the response from the /sign endpoint.'
|
238
|
+
@logger.err " body #{result.body.to_s}"
|
239
|
+
return :error
|
240
|
+
end
|
241
|
+
else
|
242
|
+
@logger.err 'Error:'
|
243
|
+
@logger.err ' When attempting to sign certificate requests, received:'
|
244
|
+
@logger.err " code: #{result.code}"
|
245
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
246
|
+
return :error
|
247
|
+
end
|
248
|
+
when :revoke
|
249
|
+
case result.code
|
250
|
+
when '200', '204'
|
251
|
+
@logger.inform "Certificate for #{certname} has been revoked"
|
252
|
+
return :success
|
253
|
+
when '404'
|
254
|
+
@logger.err 'Error:'
|
255
|
+
@logger.err " Could not find certificate for #{certname}"
|
256
|
+
return :not_found
|
257
|
+
when '409'
|
258
|
+
@logger.err 'Error:'
|
259
|
+
@logger.err " Could not revoke unsigned csr for #{certname}"
|
260
|
+
return :invalid
|
261
|
+
else
|
262
|
+
@logger.err 'Error:'
|
263
|
+
@logger.err " When attempting to revoke certificate '#{certname}', received:"
|
264
|
+
@logger.err " code: #{result.code}"
|
265
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
266
|
+
return :error
|
267
|
+
end
|
268
|
+
when :submit
|
269
|
+
case result.code
|
270
|
+
when '200', '204'
|
271
|
+
@logger.inform "Successfully submitted certificate request for #{certname}"
|
272
|
+
return :success
|
273
|
+
else
|
274
|
+
@logger.err 'Error:'
|
275
|
+
@logger.err " When attempting to submit certificate request for '#{certname}', received:"
|
276
|
+
@logger.err " code: #{result.code}"
|
277
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
278
|
+
return :error
|
279
|
+
end
|
280
|
+
when :server_version
|
281
|
+
if result.code == '200' && result.body
|
282
|
+
begin
|
283
|
+
data = JSON.parse(result.body)
|
284
|
+
version_str = data.dig('ca','service_version')
|
285
|
+
return Gem::Version.new(version_str.match('^\d+\.\d+\.\d+')[0])
|
286
|
+
rescue JSON::ParserError, NoMethodError
|
287
|
+
# If we get bad JSON, version_str is nil, or the matcher doesn't match,
|
288
|
+
# fall through to returning a version of 0.
|
289
|
+
end
|
290
|
+
end
|
291
|
+
@logger.debug 'Could not detect server version. Defaulting to legacy signing endpoints.'
|
292
|
+
return Gem::Version.new(0)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Make an HTTP request to CA to clean the named certificates
|
297
|
+
# @param certnames [Array] the name of the certificate(s) to have cleaned
|
298
|
+
# @return [Boolean] whether all certificate cleaning and revocation was successful
|
299
|
+
def clean_certs(certnames)
|
300
|
+
url = make_ca_url('certificate_status')
|
301
|
+
|
302
|
+
results = @client.with_connection(url) do |connection|
|
303
|
+
certnames.map do |certname|
|
304
|
+
url.resource_name = certname
|
305
|
+
revoke_result = connection.put(REVOKE_BODY, url)
|
306
|
+
revoked = check_revocation(certname, revoke_result)
|
307
|
+
|
308
|
+
cleaned = nil
|
309
|
+
unless revoked == :error
|
310
|
+
clean_result = connection.delete(url)
|
311
|
+
cleaned = check_clean(certname, clean_result)
|
312
|
+
end
|
313
|
+
|
314
|
+
if revoked == :error || cleaned != :success
|
315
|
+
:error
|
316
|
+
|
317
|
+
# If we get passed the first conditional we know that
|
318
|
+
# cleaned must == :success and revoked must be one of
|
319
|
+
# :invalid, :not_found, or :success. We'll treat both
|
320
|
+
# :not_found and :success of revocation here as successes.
|
321
|
+
# However we'll treat invalid's specially.
|
322
|
+
elsif revoked == :invalid
|
323
|
+
:invalid
|
324
|
+
|
325
|
+
else
|
326
|
+
:success
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
return results.reduce {|prev, curr| worst_result(prev, curr) }
|
332
|
+
end
|
333
|
+
|
334
|
+
# possibly logs the action, always returns a status symbol 👑
|
335
|
+
def check_revocation(certname, result)
|
336
|
+
case result.code
|
337
|
+
when '200', '204'
|
338
|
+
@logger.inform "Certificate for #{certname} has been revoked"
|
339
|
+
return :success
|
340
|
+
when '409'
|
341
|
+
return :invalid
|
342
|
+
when '404'
|
343
|
+
return :not_found
|
344
|
+
else
|
345
|
+
@logger.err 'Error:'
|
346
|
+
@logger.err " When attempting to revoke certificate '#{certname}', received:"
|
347
|
+
@logger.err " code: #{result.code}"
|
348
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
349
|
+
return :error
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# logs the action and returns a status symbol 👑
|
354
|
+
def check_clean(certname, result)
|
355
|
+
case result.code
|
356
|
+
when '200', '204'
|
357
|
+
@logger.inform "Cleaned files related to #{certname}"
|
358
|
+
return :success
|
359
|
+
when '404'
|
360
|
+
@logger.err 'Error:'
|
361
|
+
@logger.err " Could not find files to clean for #{certname}"
|
362
|
+
return :not_found
|
363
|
+
else
|
364
|
+
@logger.err 'Error:'
|
365
|
+
@logger.err " When attempting to clean certificate '#{certname}', received:"
|
366
|
+
@logger.err " code: #{result.code}"
|
367
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
368
|
+
return :error
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# Returns nil for errors, else the result of the GET request
|
373
|
+
def get_certificate_statuses(query = {})
|
374
|
+
result = get('certificate_statuses', 'any_key', query)
|
375
|
+
|
376
|
+
unless result.code == '200'
|
377
|
+
@logger.err 'Error:'
|
378
|
+
@logger.err " code: #{result.code}"
|
379
|
+
@logger.err " body: #{result.body}" if result.body
|
380
|
+
return nil
|
381
|
+
end
|
382
|
+
|
383
|
+
result
|
384
|
+
end
|
385
|
+
|
386
|
+
# Returns nil for errors, else the result of the GET request
|
387
|
+
def get_certificate(certname)
|
388
|
+
result = get('certificate', certname)
|
389
|
+
|
390
|
+
case result.code
|
391
|
+
when '200'
|
392
|
+
return result
|
393
|
+
when '404'
|
394
|
+
@logger.err 'Error:'
|
395
|
+
@logger.err " Signed certificate #{certname} could not be found on the CA"
|
396
|
+
return nil
|
397
|
+
else
|
398
|
+
@logger.err 'Error:'
|
399
|
+
@logger.err " When attempting to download certificate '#{certname}', received:"
|
400
|
+
@logger.err " code: #{result.code}"
|
401
|
+
@logger.err " body: #{result.body.to_s}" if result.body
|
402
|
+
return nil
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# Make an HTTP GET request to CA
|
407
|
+
# @param resource_type [String] the resource type of url
|
408
|
+
# @param resource_name [String] the resource name of url
|
409
|
+
# @return [Struct] an instance of the Result struct with :code, :body
|
410
|
+
def get(resource_type, resource_name, query = {})
|
411
|
+
url = make_ca_url(resource_type, resource_name, query)
|
412
|
+
@client.with_connection(url) do |connection|
|
413
|
+
connection.get(url)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'puppetserver/ca/action/clean'
|
4
|
+
require 'puppetserver/ca/action/delete'
|
5
|
+
require 'puppetserver/ca/action/generate'
|
6
|
+
require 'puppetserver/ca/action/import'
|
7
|
+
require 'puppetserver/ca/action/enable'
|
8
|
+
require 'puppetserver/ca/action/list'
|
9
|
+
require 'puppetserver/ca/action/revoke'
|
10
|
+
require 'puppetserver/ca/action/setup'
|
11
|
+
require 'puppetserver/ca/action/sign'
|
12
|
+
require 'puppetserver/ca/action/prune'
|
13
|
+
require 'puppetserver/ca/action/migrate'
|
14
|
+
require 'puppetserver/ca/errors'
|
15
|
+
require 'puppetserver/ca/logger'
|
16
|
+
require 'puppetserver/ca/utils/cli_parsing'
|
17
|
+
require 'puppetserver/ca/version'
|
18
|
+
|
19
|
+
|
20
|
+
module Puppetserver
|
21
|
+
module Ca
|
22
|
+
class Cli
|
23
|
+
BANNER= <<-BANNER
|
24
|
+
Usage: puppetserver ca <action> [options]
|
25
|
+
|
26
|
+
Manage the Private Key Infrastructure for
|
27
|
+
Puppet Server's built-in Certificate Authority
|
28
|
+
BANNER
|
29
|
+
|
30
|
+
ADMIN_ACTIONS = {
|
31
|
+
'delete' => Action::Delete,
|
32
|
+
'import' => Action::Import,
|
33
|
+
'setup' => Action::Setup,
|
34
|
+
'enable' => Action::Enable,
|
35
|
+
'migrate' => Action::Migrate,
|
36
|
+
'prune' => Action::Prune
|
37
|
+
}
|
38
|
+
|
39
|
+
MAINT_ACTIONS = {
|
40
|
+
'clean' => Action::Clean,
|
41
|
+
'generate' => Action::Generate,
|
42
|
+
'list' => Action::List,
|
43
|
+
'revoke' => Action::Revoke,
|
44
|
+
'sign' => Action::Sign
|
45
|
+
}
|
46
|
+
|
47
|
+
VALID_ACTIONS = ADMIN_ACTIONS.merge(MAINT_ACTIONS).sort.to_h
|
48
|
+
|
49
|
+
ACTION_LIST = "\nAvailable Actions:\n\n" +
|
50
|
+
" Certificate Actions (requires a running Puppet Server):\n\n" +
|
51
|
+
MAINT_ACTIONS.map do |action, cls|
|
52
|
+
" #{action}\t#{cls::SUMMARY}"
|
53
|
+
end.join("\n") + "\n\n" +
|
54
|
+
" Administrative Actions (requires Puppet Server to be stopped):\n\n" +
|
55
|
+
ADMIN_ACTIONS.map do |action, cls|
|
56
|
+
" #{action}\t#{cls::SUMMARY}"
|
57
|
+
end.join("\n")
|
58
|
+
|
59
|
+
ACTION_OPTIONS = "\nAction Options:\n" +
|
60
|
+
VALID_ACTIONS.map do |action, cls|
|
61
|
+
action_summary = cls.parser.summarize.
|
62
|
+
select{|line| line =~ /^\s*--/ }.
|
63
|
+
reject{|line| line =~ /--help|--version/ }
|
64
|
+
summary = action_summary.empty? ? ' N/A' : action_summary.join('')
|
65
|
+
|
66
|
+
" #{action}:\n" + summary
|
67
|
+
end.join("\n")
|
68
|
+
|
69
|
+
|
70
|
+
def self.run(cli_args = ARGV, out = STDOUT, err = STDERR)
|
71
|
+
parser, general_options, unparsed = parse_general_inputs(cli_args)
|
72
|
+
level = general_options.delete('verbose') ? :debug : :info
|
73
|
+
|
74
|
+
logger = Puppetserver::Ca::Logger.new(level, out, err)
|
75
|
+
|
76
|
+
if general_options['version']
|
77
|
+
logger.inform Puppetserver::Ca::VERSION
|
78
|
+
return 0
|
79
|
+
end
|
80
|
+
|
81
|
+
action_argument = unparsed.shift
|
82
|
+
action_class = VALID_ACTIONS[action_argument]
|
83
|
+
|
84
|
+
if general_options['help']
|
85
|
+
if action_class
|
86
|
+
logger.inform action_class.parser.help
|
87
|
+
else
|
88
|
+
logger.inform parser.help
|
89
|
+
end
|
90
|
+
|
91
|
+
return 0
|
92
|
+
end
|
93
|
+
|
94
|
+
if action_class
|
95
|
+
action = action_class.new(logger)
|
96
|
+
input, exit_code = action.parse(unparsed)
|
97
|
+
|
98
|
+
if exit_code
|
99
|
+
return exit_code
|
100
|
+
else
|
101
|
+
begin
|
102
|
+
return action.run(input)
|
103
|
+
rescue Puppetserver::Ca::Error => e
|
104
|
+
logger.err "Fatal error when running action '#{action_argument}'"
|
105
|
+
logger.err " Error: " + e.message
|
106
|
+
|
107
|
+
return 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
else
|
111
|
+
logger.warn "Unknown action: #{action_argument}"
|
112
|
+
logger.warn parser.help
|
113
|
+
return 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.parse_general_inputs(inputs)
|
118
|
+
parsed = {}
|
119
|
+
general_parser = OptionParser.new do |opts|
|
120
|
+
opts.banner = BANNER
|
121
|
+
opts.separator ACTION_LIST
|
122
|
+
opts.separator "\nGeneral Options:"
|
123
|
+
|
124
|
+
opts.on('--help', 'Display this general help output') do |help|
|
125
|
+
parsed['help'] = true
|
126
|
+
end
|
127
|
+
opts.on('--version', 'Display the version') do |v|
|
128
|
+
parsed['version'] = true
|
129
|
+
end
|
130
|
+
opts.on('--verbose', 'Display low-level information') do |verbose|
|
131
|
+
parsed['verbose'] = true
|
132
|
+
end
|
133
|
+
|
134
|
+
opts.separator ACTION_OPTIONS
|
135
|
+
opts.separator "\nSee `puppetserver ca <action> --help` for detailed info"
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
all,_,_,_ = Utils::CliParsing.parse_without_raising(general_parser, inputs)
|
140
|
+
|
141
|
+
return general_parser, parsed, all
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|