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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +17 -0
  3. data/.github/release.yml +41 -0
  4. data/.github/workflows/gem_release.yaml +106 -0
  5. data/.github/workflows/prepare_release.yml +28 -0
  6. data/.github/workflows/release.yml +28 -0
  7. data/.github/workflows/unit_tests.yaml +45 -0
  8. data/.gitignore +14 -0
  9. data/.rspec +2 -0
  10. data/.travis.yml +16 -0
  11. data/CHANGELOG.md +15 -0
  12. data/CODEOWNERS +4 -0
  13. data/CODE_OF_CONDUCT.md +74 -0
  14. data/CONTRIBUTING.md +15 -0
  15. data/Gemfile +20 -0
  16. data/LICENSE +202 -0
  17. data/README.md +118 -0
  18. data/Rakefile +30 -0
  19. data/bin/console +14 -0
  20. data/bin/setup +8 -0
  21. data/exe/puppetserver-ca +10 -0
  22. data/lib/puppetserver/ca/action/clean.rb +109 -0
  23. data/lib/puppetserver/ca/action/delete.rb +286 -0
  24. data/lib/puppetserver/ca/action/enable.rb +140 -0
  25. data/lib/puppetserver/ca/action/generate.rb +330 -0
  26. data/lib/puppetserver/ca/action/import.rb +196 -0
  27. data/lib/puppetserver/ca/action/list.rb +253 -0
  28. data/lib/puppetserver/ca/action/migrate.rb +97 -0
  29. data/lib/puppetserver/ca/action/prune.rb +289 -0
  30. data/lib/puppetserver/ca/action/revoke.rb +108 -0
  31. data/lib/puppetserver/ca/action/setup.rb +188 -0
  32. data/lib/puppetserver/ca/action/sign.rb +146 -0
  33. data/lib/puppetserver/ca/certificate_authority.rb +418 -0
  34. data/lib/puppetserver/ca/cli.rb +145 -0
  35. data/lib/puppetserver/ca/config/puppet.rb +309 -0
  36. data/lib/puppetserver/ca/config/puppetserver.rb +84 -0
  37. data/lib/puppetserver/ca/errors.rb +40 -0
  38. data/lib/puppetserver/ca/host.rb +176 -0
  39. data/lib/puppetserver/ca/local_certificate_authority.rb +304 -0
  40. data/lib/puppetserver/ca/logger.rb +49 -0
  41. data/lib/puppetserver/ca/stub.rb +17 -0
  42. data/lib/puppetserver/ca/utils/cli_parsing.rb +67 -0
  43. data/lib/puppetserver/ca/utils/config.rb +61 -0
  44. data/lib/puppetserver/ca/utils/file_system.rb +109 -0
  45. data/lib/puppetserver/ca/utils/http_client.rb +232 -0
  46. data/lib/puppetserver/ca/utils/inventory.rb +84 -0
  47. data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
  48. data/lib/puppetserver/ca/version.rb +5 -0
  49. data/lib/puppetserver/ca/x509_loader.rb +170 -0
  50. data/lib/puppetserver/ca.rb +7 -0
  51. data/openvoxserver-ca.gemspec +31 -0
  52. data/tasks/spec.rake +15 -0
  53. data/tasks/vox.rake +19 -0
  54. metadata +154 -0
@@ -0,0 +1,140 @@
1
+ require 'optparse'
2
+
3
+ require 'puppetserver/ca/config/puppet'
4
+ require 'puppetserver/ca/errors'
5
+ require 'puppetserver/ca/local_certificate_authority'
6
+ require 'puppetserver/ca/utils/cli_parsing'
7
+ require 'puppetserver/ca/utils/file_system'
8
+ require 'puppetserver/ca/utils/signing_digest'
9
+
10
+ module Puppetserver
11
+ module Ca
12
+ module Action
13
+ class Enable
14
+ include Puppetserver::Ca::Utils
15
+
16
+ SUMMARY = "Setup infrastructure CRL based on a node inventory."
17
+ BANNER = <<-BANNER
18
+ Usage:
19
+ puppetserver ca enable [--help]
20
+ puppetserver ca enable [--infracrl]
21
+
22
+ Description:
23
+ Performs actions necessary to enable certain CA modes.
24
+
25
+ --infracrl
26
+ Creates auxiliary files necessary to use the infrastructure-only CRL.
27
+ Assumes the existence of an `infra_inventory.txt` file in the CA
28
+ directory listing the certnames of the infrastructure nodes in the
29
+ Puppet installation. Generates the the empty CRL to be populated with
30
+ revoked infrastructure nodes.
31
+
32
+ Options:
33
+ BANNER
34
+
35
+ def initialize(logger)
36
+ @logger = logger
37
+ end
38
+
39
+ def run(input)
40
+ # Validate config_path provided
41
+ config_path = input['config']
42
+ if config_path
43
+ errors = FileSystem.validate_file_paths(config_path)
44
+ return 1 if Errors.handle_with_usage(@logger, errors)
45
+ end
46
+
47
+ puppet = Config::Puppet.new(config_path)
48
+ puppet.load(logger: @logger)
49
+ settings = puppet.settings
50
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
51
+
52
+ if input['infracrl']
53
+ errors = enable_infra_crl(settings)
54
+ return 1 if Errors.handle_with_usage(@logger, errors)
55
+ end
56
+
57
+ return 0
58
+ end
59
+
60
+ def enable_infra_crl(settings)
61
+ inventory_file = File.join(settings[:cadir], 'infra_inventory.txt')
62
+ if !File.exist?(inventory_file)
63
+ error = <<-ERR
64
+ Please create an inventory file at '#{inventory_file}' with the certnames of your
65
+ infrastructure nodes before proceeding with infra CRL setup!"
66
+ ERR
67
+ return [error]
68
+ end
69
+
70
+ infra_crl = File.join(settings[:cadir], 'infra_crl.pem')
71
+
72
+ file_errors = check_for_existing_infra_files(infra_crl)
73
+ return file_errors if !file_errors.empty?
74
+
75
+ errors = create_infra_crl_chain(settings)
76
+ return errors if !errors.empty?
77
+
78
+ @logger.inform "Infra CRL files created."
79
+ return []
80
+ end
81
+
82
+ def check_for_existing_infra_files(files)
83
+ file_errors = FileSystem.check_for_existing_files(files)
84
+ if !file_errors.empty?
85
+ notice = <<-MSG
86
+ If you would really like to reinitialize your infrastructure CRL, please delete
87
+ the existing files and run this command again.
88
+ MSG
89
+ file_errors << notice
90
+ end
91
+ return file_errors
92
+ end
93
+
94
+ def create_infra_crl_chain(settings)
95
+ # Load most secure signing digest we can for cers/crl/csr signing.
96
+ signer = SigningDigest.new
97
+ return signer.errors if signer.errors.any?
98
+
99
+ ca = LocalCertificateAuthority.new(signer.digest, settings)
100
+ return ca.errors if ca.errors.any?
101
+
102
+ infra_crl = ca.create_crl_for(ca.cert, ca.key)
103
+
104
+ # Drop the full leaf CRL from the chain
105
+ crl_chain = ca.crl_chain.drop(1)
106
+ # Add the new clean CRL, that will be populated with infra nodes only
107
+ # as they are revoked
108
+ crl_chain.unshift(infra_crl)
109
+ FileSystem.write_file(File.join(settings[:cadir], 'infra_crl.pem'), crl_chain, 0644)
110
+
111
+ []
112
+ end
113
+
114
+ def parse(cli_args)
115
+ results = {}
116
+ parser = self.class.parser(results)
117
+ errors = CliParsing.parse_with_errors(parser, cli_args)
118
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
119
+ exit_code = errors_were_handled ? 1 : nil
120
+ return results, exit_code
121
+ end
122
+
123
+ def self.parser(parsed = {})
124
+ OptionParser.new do |opts|
125
+ opts.banner = BANNER
126
+ opts.on('--help', 'Display this command-specific help output') do |help|
127
+ parsed['help'] = true
128
+ end
129
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
130
+ parsed['config'] = conf
131
+ end
132
+ opts.on('--infracrl', "Create auxiliary files for the infrastructure-only CRL.") do |infracrl|
133
+ parsed['infracrl'] = true
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,330 @@
1
+ require 'puppetserver/ca/certificate_authority'
2
+ require 'puppetserver/ca/config/puppet'
3
+ require 'puppetserver/ca/errors'
4
+ require 'puppetserver/ca/host'
5
+ require 'puppetserver/ca/local_certificate_authority'
6
+ require 'puppetserver/ca/utils/cli_parsing'
7
+ require 'puppetserver/ca/utils/config'
8
+ require 'puppetserver/ca/utils/file_system'
9
+ require 'puppetserver/ca/utils/signing_digest'
10
+ require 'puppetserver/ca/x509_loader'
11
+
12
+ module Puppetserver
13
+ module Ca
14
+ module Action
15
+ class Generate
16
+
17
+ include Puppetserver::Ca::Utils
18
+
19
+ # Only allow printing ascii characters, excluding /
20
+ VALID_CERTNAME = /\A[ -.0-~]+\Z/
21
+ CERTNAME_BLOCKLIST = %w{--all --config}
22
+
23
+ SUMMARY = "Generate a new certificate signed by the CA"
24
+ BANNER = <<-BANNER
25
+ Usage:
26
+ puppetserver ca generate [--help]
27
+ puppetserver ca generate --certname NAME[,NAME] [--config PATH]
28
+ [--subject-alt-names NAME[,NAME]]
29
+ [--ca-client [--force]]
30
+
31
+ Description:
32
+ Generates a new certificate signed by the intermediate CA
33
+ and stores generated keys and certs on disk.
34
+
35
+ If the `--ca-client` flag is passed, the cert will be generated
36
+ offline, without using Puppet Server's signing code, and will add
37
+ a special extension authorizing it to talk to the CA API. This can
38
+ be used for regenerating the server's host cert, or for manually
39
+ setting up other nodes to be CA clients. Do not distribute certs
40
+ generated this way to any node that you do not intend to have
41
+ administrative access to the CA (e.g. the ability to sign a cert).
42
+
43
+ Since the `--ca-client` causes a cert to be generated offline, it
44
+ should ONLY be used when Puppet Server is NOT running, to avoid
45
+ conflicting with the actions of the CA service. This will be
46
+ mandatory in a future release.
47
+
48
+ Options:
49
+ BANNER
50
+ def initialize(logger)
51
+ @logger = logger
52
+ end
53
+
54
+ def self.parser(parsed = {})
55
+ parsed['certnames'] = []
56
+ parsed['subject-alt-names'] = ''
57
+ OptionParser.new do |opts|
58
+ opts.banner = BANNER
59
+ opts.on('--certname NAME[,NAME]', Array,
60
+ 'One or more comma separated certnames') do |certs|
61
+ parsed['certnames'] += certs
62
+ end
63
+ opts.on('--help', 'Display this command-specific help output') do |help|
64
+ parsed['help'] = true
65
+ end
66
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
67
+ parsed['config'] = conf
68
+ end
69
+ opts.on('--subject-alt-names NAME[,NAME]',
70
+ 'Subject alternative names for the generated cert') do |sans|
71
+ parsed['subject-alt-names'] = sans
72
+ end
73
+ opts.on('--ca-client',
74
+ 'Whether this cert will be used to request CA actions.',
75
+ 'Causes the cert to be generated offline.') do |ca_client|
76
+ parsed['ca-client'] = true
77
+ end
78
+ opts.on('--force', 'Suppress errors when signing cert offline.',
79
+ "To be used with '--ca-client'") do |force|
80
+ parsed['force'] = true
81
+ end
82
+ opts.on('--ttl TTL', 'The time-to-live for each cert generated and signed') do |ttl|
83
+ parsed['ttl'] = ttl
84
+ end
85
+ end
86
+ end
87
+
88
+ def parse(args)
89
+ results = {}
90
+ parser = self.class.parser(results)
91
+
92
+ errors = CliParsing.parse_with_errors(parser, args)
93
+
94
+ if results['certnames'].empty?
95
+ errors << ' At least one certname is required to generate'
96
+ else
97
+ results['certnames'].each do |certname|
98
+ if CERTNAME_BLOCKLIST.include?(certname)
99
+ errors << " Cannot manage cert named `#{certname}` from " +
100
+ "the CLI, if needed use the HTTP API directly"
101
+ end
102
+
103
+ if certname.match(/\p{Upper}/)
104
+ errors << " Certificate names must be lower case"
105
+ end
106
+
107
+ unless certname =~ VALID_CERTNAME
108
+ errors << " Certname #{certname} must not contain unprintable or non-ASCII characters"
109
+ end
110
+ end
111
+ end
112
+
113
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
114
+
115
+ exit_code = errors_were_handled ? 1 : nil
116
+
117
+ return results, exit_code
118
+ end
119
+
120
+ def run(input)
121
+ certnames = input['certnames']
122
+ config_path = input['config']
123
+
124
+ # Validate config_path provided
125
+ if config_path
126
+ errors = FileSystem.validate_file_paths(config_path)
127
+ return 1 if Errors.handle_with_usage(@logger, errors)
128
+ end
129
+
130
+ # Load, resolve, and validate puppet config settings
131
+ settings_overrides = {}
132
+ puppet = Config::Puppet.new(config_path)
133
+ puppet.load(cli_overrides: settings_overrides, logger: @logger)
134
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
135
+
136
+ # We don't want generate to respect the alt names setting, since it is usually
137
+ # used to generate certs for other nodes
138
+ alt_names = input['subject-alt-names']
139
+
140
+ # Load most secure signing digest we can for csr signing.
141
+ signer = SigningDigest.new
142
+ return 1 if Errors.handle_with_usage(@logger, signer.errors)
143
+
144
+ # Generate and save certs and associated keys
145
+ if input['ca-client']
146
+ # Refuse to generate certs offline if the CA service is running
147
+ begin
148
+ return 1 if HttpClient.check_server_online(puppet.settings, @logger)
149
+ rescue Puppetserver::Ca::ConnectionFailed => e
150
+ base_message = "Could not determine whether Puppet Server is online."
151
+ if input['force']
152
+ @logger.inform("#{base_message} Connection check failed with " \
153
+ "error: #{e.wrapped}\nContinuing with certificate signing.")
154
+ else
155
+ @logger.inform("#{base_message} If you are certain that the " \
156
+ "Puppetserver service is stopped, run this command again " \
157
+ "with the '--force' flag.")
158
+ raise e
159
+ end
160
+ end
161
+ all_passed = generate_authorized_certs(certnames, alt_names, puppet.settings, signer.digest)
162
+ else
163
+ all_passed = generate_certs(certnames, alt_names, puppet.settings, signer.digest, input['ttl'])
164
+ end
165
+ return all_passed ? 0 : 1
166
+ end
167
+
168
+ # Certs authorized to talk to the CA API need to be signed offline,
169
+ # in order to securely add the special auth extension.
170
+ def generate_authorized_certs(certnames, alt_names, settings, digest)
171
+ # Make sure we have all the directories where we will be writing files
172
+ FileSystem.ensure_dirs([settings[:ssldir],
173
+ settings[:certdir],
174
+ settings[:privatekeydir],
175
+ settings[:publickeydir]])
176
+
177
+ ca = Puppetserver::Ca::LocalCertificateAuthority.new(digest, settings)
178
+ return false if Errors.handle_with_usage(@logger, ca.errors)
179
+
180
+ passed = certnames.map do |certname|
181
+ errors = check_for_existing_ssl_files(certname, settings)
182
+ next false if Errors.handle_with_usage(@logger, errors)
183
+
184
+ current_alt_names = process_alt_names(alt_names, certname)
185
+
186
+ # For certs signed offline, any alt names are added directly to the cert,
187
+ # rather than to the CSR.
188
+ key, csr = generate_key_csr(certname, settings, digest)
189
+ next false unless csr
190
+
191
+ cert = ca.sign_authorized_cert(csr, current_alt_names)
192
+ next false unless save_file(cert.to_pem, certname, settings[:certdir], "Certificate")
193
+ next false unless save_file(cert.to_pem, certname, settings[:signeddir], "Certificate")
194
+ next false unless save_keys(certname, settings, key)
195
+ ca.update_serial_file(cert.serial + 1)
196
+ true
197
+ end
198
+ passed.all?
199
+ end
200
+
201
+ # Generate csrs and keys, then submit them to CA, request for the CA to sign
202
+ # them, download the signed certificates from the CA, and finally save
203
+ # the signed certs and associated keys. Returns true if all certs were
204
+ # successfully created and saved. Takes a ttl to use if certificates
205
+ # are signed by this CLI, not autosigned by the CA. if ttl is nil, uses
206
+ # the CA's settings.
207
+ def generate_certs(certnames, alt_names, settings, digest, ttl)
208
+ # Make sure we have all the directories where we will be writing files
209
+ FileSystem.ensure_dirs([settings[:ssldir],
210
+ settings[:certdir],
211
+ settings[:privatekeydir],
212
+ settings[:publickeydir]])
213
+
214
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
215
+
216
+ passed = certnames.map do |certname|
217
+ errors = check_for_existing_ssl_files(certname, settings)
218
+ next false if Errors.handle_with_usage(@logger, errors)
219
+
220
+ current_alt_names = process_alt_names(alt_names, certname)
221
+
222
+ next false unless submit_csr(certname, ca, settings, digest, current_alt_names)
223
+
224
+ # Check if the CA autosigned the cert
225
+ next acquire_signed_cert(ca, certname, settings, ttl)
226
+ end
227
+ passed.all?
228
+ end
229
+
230
+ # Try to download a signed certificate; sign the cert with the given ttl if it needs
231
+ # signing before download.
232
+ def acquire_signed_cert(ca, certname, settings, ttl)
233
+ if download_cert(ca, certname, settings)
234
+ @logger.inform "Certificate for #{certname} was autosigned."
235
+ if ttl
236
+ @logger.warn "ttl was specified, but the CA autosigned the CSR. Unable to specify #{ttl} for #{certname}"
237
+ end
238
+ true
239
+ else
240
+ false unless ca.sign_certs([certname], ttl)
241
+ download_cert(ca, certname, settings)
242
+ end
243
+ end
244
+
245
+ def submit_csr(certname, ca, settings, digest, alt_names)
246
+ key, csr = generate_key_csr(certname, settings, digest, alt_names)
247
+ return false unless csr
248
+ # Always save the keys, since soemtimes the server saves the CSR
249
+ # even when it returns a 400 (e.g. when the CSR contains alt names
250
+ # but the server isn't configured to sign such certs)
251
+ return false unless save_keys(certname, settings, key)
252
+ return false unless ca.submit_certificate_request(certname, csr)
253
+ true
254
+ end
255
+
256
+ def download_cert(ca, certname, settings)
257
+ if result = ca.get_certificate(certname)
258
+ return false unless save_file(result.body, certname, settings[:certdir], "Certificate")
259
+ true
260
+ end
261
+ end
262
+
263
+ # For certs signed offline, any alt names are added directly to the cert,
264
+ # rather than to the CSR.
265
+ def generate_key_csr(certname, settings, digest, alt_names = '')
266
+ host = Puppetserver::Ca::Host.new(digest)
267
+ private_key = host.create_private_key(settings[:keylength])
268
+ extensions = []
269
+ if !alt_names.empty?
270
+ ef = OpenSSL::X509::ExtensionFactory.new
271
+ extensions << ef.create_extension("subjectAltName",
272
+ alt_names,
273
+ false)
274
+ end
275
+ csr = host.create_csr(name: certname,
276
+ key: private_key,
277
+ cli_extensions: extensions,
278
+ csr_attributes_path: settings[:csr_attributes])
279
+ return if Errors.handle_with_usage(@logger, host.errors)
280
+
281
+ return private_key, csr
282
+ end
283
+
284
+ def save_keys(certname, settings, key)
285
+ public_key = key.public_key
286
+ return false unless save_file(key, certname, settings[:privatekeydir], "Private key")
287
+ return false unless save_file(public_key, certname, settings[:publickeydir], "Public key")
288
+ true
289
+ end
290
+
291
+ def save_file(content, certname, dir, type)
292
+ location = File.join(dir, "#{certname}.pem")
293
+ if File.exist?(location)
294
+ @logger.err "#{type} #{certname}.pem already exists. Please delete it if you really want to regenerate it."
295
+ false
296
+ else
297
+ FileSystem.write_file(location, content, 0640)
298
+ @logger.inform "Successfully saved #{type.downcase} for #{certname} to #{location}"
299
+ true
300
+ end
301
+ end
302
+
303
+ def check_for_existing_ssl_files(certname, settings)
304
+ files = [ File.join(settings[:certdir], "#{certname}.pem"),
305
+ File.join(settings[:privatekeydir], "#{certname}.pem"),
306
+ File.join(settings[:publickeydir], "#{certname}.pem"),
307
+ File.join(settings[:signeddir], "#{certname}.pem"), ]
308
+ errors = Puppetserver::Ca::Utils::FileSystem.check_for_existing_files(files)
309
+ if !errors.empty?
310
+ errors << "Please delete these files if you really want to generate a new cert for #{certname}."
311
+ end
312
+ errors
313
+ end
314
+
315
+ def process_alt_names(alt_names, certname)
316
+ # It is recommended (and sometimes enforced) to always include
317
+ # the certname as a SAN, see RFC 2818 https://tools.ietf.org/html/rfc2818#section-3.1.
318
+ return "DNS:#{certname}" if alt_names.empty?
319
+
320
+ current_alt_names = alt_names.dup
321
+ # When validating the cert, OpenSSL will ignore the CN field if
322
+ # altnames are present, so we need to ensure that the certname is
323
+ # also listed among the alt names.
324
+ current_alt_names += ",DNS:#{certname}"
325
+ current_alt_names = Puppetserver::Ca::Utils::Config.munge_alt_names(current_alt_names)
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,196 @@
1
+ require 'optparse'
2
+
3
+ require 'puppetserver/ca/config/puppet'
4
+ require 'puppetserver/ca/errors'
5
+ require 'puppetserver/ca/local_certificate_authority'
6
+ require 'puppetserver/ca/utils/cli_parsing'
7
+ require 'puppetserver/ca/utils/config'
8
+ require 'puppetserver/ca/utils/file_system'
9
+ require 'puppetserver/ca/utils/signing_digest'
10
+ require 'puppetserver/ca/x509_loader'
11
+
12
+ module Puppetserver
13
+ module Ca
14
+ module Action
15
+ class Import
16
+ include Puppetserver::Ca::Utils
17
+
18
+ SUMMARY = "Import an external CA chain and generate server PKI"
19
+ BANNER = <<-BANNER
20
+ Usage:
21
+ puppetserver ca import [--help]
22
+ puppetserver ca import [--config PATH] [--certname NAME]
23
+ [--subject-alt-names NAME[,NAME]]
24
+ --private-key PATH --cert-bundle PATH --crl-chain PATH
25
+
26
+ Description:
27
+ Given a private key, cert bundle, and a crl chain,
28
+ validate and import to the Puppet Server CA.
29
+
30
+ Note that the cert and crl provided for the leaf CA must not
31
+ have already issued or revoked any certificates.
32
+
33
+ Options:
34
+ BANNER
35
+
36
+ def initialize(logger)
37
+ @logger = logger
38
+ end
39
+
40
+ def run(input)
41
+ bundle_path = input['cert-bundle']
42
+ key_path = input['private-key']
43
+ chain_path = input['crl-chain']
44
+ config_path = input['config']
45
+
46
+ files = [bundle_path, key_path, chain_path, config_path].compact
47
+
48
+ errors = FileSystem.validate_file_paths(files)
49
+ return 1 if Errors.handle_with_usage(@logger, errors)
50
+
51
+ loader = X509Loader.new(bundle_path, key_path, chain_path)
52
+ return 1 if Errors.handle_with_usage(@logger, loader.errors)
53
+
54
+ settings_overrides = {}
55
+ settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
56
+ settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
57
+
58
+ puppet = Config::Puppet.new(config_path)
59
+ puppet.load(cli_overrides: settings_overrides, logger: @logger)
60
+ return 1 if Errors.handle_with_usage(@logger, puppet.errors)
61
+
62
+ # Load most secure signing digest we can for cers/crl/csr signing.
63
+ signer = SigningDigest.new
64
+ return 1 if Errors.handle_with_usage(@logger, signer.errors)
65
+
66
+ errors = import(loader, puppet.settings, signer.digest)
67
+ return 1 if Errors.handle_with_usage(@logger, errors)
68
+
69
+ @logger.inform "Import succeeded. Find your files in #{puppet.settings[:cadir]}"
70
+ return 0
71
+ end
72
+
73
+ def import(loader, settings, signing_digest)
74
+ ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
75
+ ca.initialize_ssl_components(loader)
76
+ server_key, server_cert = ca.create_server_cert
77
+ return ca.errors if ca.errors.any?
78
+
79
+ FileSystem.ensure_dirs([settings[:ssldir],
80
+ settings[:cadir],
81
+ settings[:certdir],
82
+ settings[:privatekeydir],
83
+ settings[:publickeydir],
84
+ settings[:signeddir]])
85
+
86
+ public_files = [
87
+ [settings[:cacert], loader.certs],
88
+ [settings[:cacrl], loader.crls],
89
+ [settings[:cadir] + '/infra_crl.pem', loader.crls],
90
+ [settings[:localcacert], loader.certs],
91
+ [settings[:hostcrl], loader.crls],
92
+ [settings[:hostpubkey], server_key.public_key],
93
+ [settings[:hostcert], server_cert],
94
+ [settings[:cert_inventory], ca.inventory_entry(server_cert)],
95
+ [settings[:capub], loader.key.public_key],
96
+ [settings[:cadir] + '/infra_inventory.txt', ''],
97
+ [settings[:cadir] + '/infra_serials', ''],
98
+ [settings[:serial], "002"],
99
+ [File.join(settings[:signeddir], "#{settings[:certname]}.pem"), server_cert]
100
+ ]
101
+
102
+ private_files = [
103
+ [settings[:hostprivkey], server_key],
104
+ [settings[:cakey], loader.key],
105
+ ]
106
+
107
+ files_to_check = public_files + private_files
108
+ # We don't want to error if server's keys exist. Certain workflows
109
+ # allow the agent to have already be installed with keys and then
110
+ # upgraded to be a server. The host class will honor keys, if both
111
+ # public and private exist, and error if only one exists - as is
112
+ # previous behavior.
113
+ files_to_check = files_to_check.map(&:first) - [settings[:hostpubkey], settings[:hostprivkey]]
114
+ errors = FileSystem.check_for_existing_files(files_to_check)
115
+
116
+ if !errors.empty?
117
+ instructions = <<-ERR
118
+ If you would really like to replace your CA, please delete the existing files first.
119
+ Note that any certificates that were issued by this CA will become invalid if you
120
+ replace it!
121
+ ERR
122
+ errors << instructions
123
+ return errors
124
+ end
125
+
126
+ public_files.each do |location, content|
127
+ FileSystem.write_file(location, content, 0644)
128
+ end
129
+
130
+ private_files.each do |location, content|
131
+ FileSystem.write_file(location, content, 0640)
132
+ end
133
+
134
+ Puppetserver::Ca::Utils::Config.symlink_to_old_cadir(settings[:cadir], settings[:confdir])
135
+
136
+ return []
137
+ end
138
+
139
+ def check_flag_usage(results)
140
+ if results['cert-bundle'].nil? || results['private-key'].nil? || results['crl-chain'].nil?
141
+ ' Missing required argument' + "\n" +
142
+ ' --cert-bundle, --private-key, --crl-chain are required'
143
+ end
144
+ end
145
+
146
+ def parse(args)
147
+ results = {}
148
+ parser = self.class.parser(results)
149
+
150
+ errors = CliParsing.parse_with_errors(parser, args)
151
+
152
+ if err = check_flag_usage(results)
153
+ errors << err
154
+ end
155
+
156
+ errors_were_handled = Errors.handle_with_usage(@logger, errors, parser.help)
157
+
158
+ exit_code = errors_were_handled ? 1 : nil
159
+
160
+ return results, exit_code
161
+ end
162
+
163
+ def self.parser(parsed = {})
164
+ parsed['certname'] = ''
165
+ parsed['subject-alt-names'] = ''
166
+ OptionParser.new do |opts|
167
+ opts.banner = BANNER
168
+ opts.on('--help', 'Display this command-specific help output') do |help|
169
+ parsed['help'] = true
170
+ end
171
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
172
+ parsed['config'] = conf
173
+ end
174
+ opts.on('--private-key KEY', 'Path to PEM encoded key') do |key|
175
+ parsed['private-key'] = key
176
+ end
177
+ opts.on('--cert-bundle BUNDLE', 'Path to PEM encoded bundle') do |bundle|
178
+ parsed['cert-bundle'] = bundle
179
+ end
180
+ opts.on('--crl-chain CHAIN', 'Path to PEM encoded chain') do |chain|
181
+ parsed['crl-chain'] = chain
182
+ end
183
+ opts.on('--certname NAME',
184
+ 'Common name to use for the server cert') do |name|
185
+ parsed['certname'] = name
186
+ end
187
+ opts.on('--subject-alt-names NAME[,NAME]',
188
+ 'Subject alternative names for the server cert') do |sans|
189
+ parsed['subject-alt-names'] = sans
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end