puppetserver-ca 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/puppetserver/ca/action/clean.rb +102 -0
  3. data/lib/puppetserver/ca/action/create.rb +161 -0
  4. data/lib/puppetserver/ca/action/generate.rb +313 -0
  5. data/lib/puppetserver/ca/action/import.rb +132 -0
  6. data/lib/puppetserver/ca/action/list.rb +132 -0
  7. data/lib/puppetserver/ca/action/revoke.rb +101 -0
  8. data/lib/puppetserver/ca/action/sign.rb +126 -0
  9. data/lib/puppetserver/ca/certificate_authority.rb +224 -0
  10. data/lib/puppetserver/ca/cli.rb +17 -16
  11. data/lib/puppetserver/ca/config/puppet.rb +242 -0
  12. data/lib/puppetserver/ca/config/puppetserver.rb +85 -0
  13. data/lib/puppetserver/ca/utils/cli_parsing.rb +82 -0
  14. data/lib/puppetserver/ca/utils/config.rb +13 -0
  15. data/lib/puppetserver/ca/utils/file_system.rb +90 -0
  16. data/lib/puppetserver/ca/utils/http_client.rb +129 -0
  17. data/lib/puppetserver/ca/utils/signing_digest.rb +27 -0
  18. data/lib/puppetserver/ca/version.rb +1 -1
  19. metadata +17 -17
  20. data/lib/puppetserver/ca/clean_action.rb +0 -157
  21. data/lib/puppetserver/ca/config_utils.rb +0 -11
  22. data/lib/puppetserver/ca/create_action.rb +0 -265
  23. data/lib/puppetserver/ca/generate_action.rb +0 -227
  24. data/lib/puppetserver/ca/import_action.rb +0 -153
  25. data/lib/puppetserver/ca/list_action.rb +0 -153
  26. data/lib/puppetserver/ca/puppet_config.rb +0 -197
  27. data/lib/puppetserver/ca/puppetserver_config.rb +0 -83
  28. data/lib/puppetserver/ca/revoke_action.rb +0 -136
  29. data/lib/puppetserver/ca/sign_action.rb +0 -190
  30. data/lib/puppetserver/ca/utils.rb +0 -80
  31. data/lib/puppetserver/settings/ttl_setting.rb +0 -48
  32. data/lib/puppetserver/utils/file_utilities.rb +0 -78
  33. data/lib/puppetserver/utils/http_client.rb +0 -129
  34. data/lib/puppetserver/utils/signing_digest.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e4ce165ce36009a209f855a3be43baf46d9f3149
4
- data.tar.gz: 1dff6f4475def4e3dcbd069e48bfcd1d9f7401b0
3
+ metadata.gz: 98b359182e9c882ea8a66b4315b873f9d87185de
4
+ data.tar.gz: 3de8823031139ae413aa7a151dc6bbd0c10176b0
5
5
  SHA512:
6
- metadata.gz: c667523644d19b2d835507b473c8a8e33c5ac397faa43ad272283289c5fb228ebb35f32bd62fb31ce93842a6065b6aae7f579685e1addcfb07257771eb3d2bff
7
- data.tar.gz: 8e37ef678e3fef24eb89b7e6ca51662323e3014f656582d54a05751f2de440e8bc8304b970bef3fac250538e567de5cee7abfbc2e7effaebfa1e0dcd86918165
6
+ metadata.gz: 327b9b653764428a428f9531dcec525fde0dd67913be851fef86f25a9993d041624dd6f70cec1357687af50f22cd77f690ee1f7bc570bd79a255f53105e81b4a
7
+ data.tar.gz: baa44b1f602863f5323911446b1896ec9145eeb4360c1fcaa85fb0c63ad173b5acd59b81f5fd6d1fcd183161526154f4d9599ece1c3a0b7b5241d3b6fb372e82
@@ -0,0 +1,102 @@
1
+ require 'puppetserver/ca/utils/cli_parsing'
2
+ require 'puppetserver/ca/utils/file_system'
3
+ require 'puppetserver/ca/config/puppet'
4
+ require 'puppetserver/ca/action/revoke'
5
+ require 'puppetserver/ca/certificate_authority'
6
+
7
+ require 'optparse'
8
+
9
+ module Puppetserver
10
+ module Ca
11
+ module Action
12
+ class Clean
13
+
14
+ include Puppetserver::Ca::Utils
15
+
16
+ CERTNAME_BLACKLIST = %w{--all --config}
17
+
18
+ SUMMARY = 'Clean files from the CA for certificate(s)'
19
+ BANNER = <<-BANNER
20
+ Usage:
21
+ puppetserver ca clean [--help]
22
+ puppetserver ca clean [--config] --certname CERTNAME[,ADDLCERTNAME]
23
+
24
+ Description:
25
+ Given one or more valid certnames, instructs the CA to revoke certificates
26
+ matching the given certnames if they exist, and then remove files pertaining
27
+ to them (keys, cert, and certificate request) over HTTPS using the local
28
+ agent's PKI
29
+
30
+ Options:
31
+ BANNER
32
+
33
+ def self.parser(parsed = {})
34
+ parsed['certnames'] = []
35
+ OptionParser.new do |o|
36
+ o.banner = BANNER
37
+ o.on('--certname foo,bar', Array,
38
+ 'One or more comma separated certnames') do |certs|
39
+ parsed['certnames'] += certs
40
+ end
41
+ o.on('--config PUPPET.CONF', 'Custom path to puppet.conf') do |conf|
42
+ parsed['config'] = conf
43
+ end
44
+ o.on('--help', 'Display this clean specific help output') do |help|
45
+ parsed['help'] = true
46
+ end
47
+ end
48
+ end
49
+
50
+ def initialize(logger)
51
+ @logger = logger
52
+ end
53
+
54
+ def parse(args)
55
+ results = {}
56
+ parser = self.class.parser(results)
57
+
58
+ errors = CliParsing.parse_with_errors(parser, args)
59
+
60
+ results['certnames'].each do |certname|
61
+ if CERTNAME_BLACKLIST.include?(certname)
62
+ errors << " Cannot manage cert named `#{certname}` from " +
63
+ "the CLI, if needed use the HTTP API directly"
64
+ end
65
+ end
66
+
67
+ if results['certnames'].empty?
68
+ errors << ' At least one certname is required to clean'
69
+ end
70
+
71
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
72
+
73
+ exit_code = errors_were_handled ? 1 : nil
74
+
75
+ return results, exit_code
76
+ end
77
+
78
+ def run(args)
79
+ certnames = args['certnames']
80
+ config = args['config']
81
+
82
+ if config
83
+ errors = FileSystem.validate_file_paths(config)
84
+ return 1 if CliParsing.handle_errors(@logger, errors)
85
+ end
86
+
87
+ puppet = Config::Puppet.parse(config)
88
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
89
+
90
+ passed = clean_certs(certnames, puppet.settings)
91
+
92
+ return passed ? 0 : 1
93
+ end
94
+
95
+ def clean_certs(certnames, settings)
96
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
97
+ ca.clean_certs(certnames)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,161 @@
1
+ require 'puppetserver/ca/utils/cli_parsing'
2
+ require 'puppetserver/ca/host'
3
+ require 'puppetserver/ca/certificate_authority'
4
+ require 'puppetserver/ca/config/puppet'
5
+ require 'puppetserver/ca/utils/file_system'
6
+ require 'puppetserver/ca/utils/signing_digest'
7
+
8
+ module Puppetserver
9
+ module Ca
10
+ module Action
11
+ class Create
12
+
13
+ include Puppetserver::Ca::Utils
14
+
15
+ # Only allow printing ascii characters, excluding /
16
+ VALID_CERTNAME = /\A[ -.0-~]+\Z/
17
+ CERTNAME_BLACKLIST = %w{--all --config}
18
+
19
+ SUMMARY = "Create a new certificate signed by the CA"
20
+ BANNER = <<-BANNER
21
+ Usage:
22
+ puppetserver ca create [--help]
23
+ puppetserver ca create [--config] --certname CERTNAME[,ADDLCERTNAME]
24
+
25
+ Description:
26
+ Creates a new certificate signed by the intermediate CA
27
+ and stores generated keys and certs on disk.
28
+
29
+ To determine the target location, the default puppet.conf
30
+ is consulted for custom values. If using a custom puppet.conf
31
+ provide it with the --config flag
32
+
33
+ Options:
34
+ BANNER
35
+ def initialize(logger)
36
+ @logger = logger
37
+ end
38
+
39
+ def self.parser(parsed = {})
40
+ parsed['certnames'] = []
41
+ OptionParser.new do |opts|
42
+ opts.banner = BANNER
43
+ opts.on('--certname FOO,BAR', Array,
44
+ 'One or more comma separated certnames') do |certs|
45
+ parsed['certnames'] += certs
46
+ end
47
+ opts.on('--help', 'Display this create specific help output') do |help|
48
+ parsed['help'] = true
49
+ end
50
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
51
+ parsed['config'] = conf
52
+ end
53
+ end
54
+ end
55
+
56
+ def parse(args)
57
+ results = {}
58
+ parser = self.class.parser(results)
59
+
60
+ errors = CliParsing.parse_with_errors(parser, args)
61
+
62
+ if results['certnames'].empty?
63
+ errors << ' At least one certname is required to create'
64
+ else
65
+ results['certnames'].each do |certname|
66
+ if CERTNAME_BLACKLIST.include?(certname)
67
+ errors << " Cannot manage cert named `#{certname}` from " +
68
+ "the CLI, if needed use the HTTP API directly"
69
+ end
70
+
71
+ if certname.match(/\p{Upper}/)
72
+ errors << " Certificate names must be lower case"
73
+ end
74
+
75
+ unless certname =~ VALID_CERTNAME
76
+ errors << " Certname #{certname} must not contain unprintable or non-ASCII characters"
77
+ end
78
+ end
79
+ end
80
+
81
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
82
+
83
+ exit_code = errors_were_handled ? 1 : nil
84
+
85
+ return results, exit_code
86
+ end
87
+
88
+ def run(input)
89
+ certnames = input['certnames']
90
+ config_path = input['config']
91
+
92
+ # Validate config_path provided
93
+ if config_path
94
+ errors = FileSystem.validate_file_paths(config_path)
95
+ return 1 if CliParsing.handle_errors(@logger, errors)
96
+ end
97
+
98
+ # Load, resolve, and validate puppet config settings
99
+ puppet = Config::Puppet.parse(config_path)
100
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
101
+
102
+ # Load most secure signing digest we can for csr signing.
103
+ signer = SigningDigest.new
104
+ return 1 if CliParsing.handle_errors(@logger, signer.errors)
105
+
106
+ # Make sure we have all the directories where we will be writing files
107
+ FileSystem.ensure_dir(puppet.settings[:certdir])
108
+ FileSystem.ensure_dir(puppet.settings[:privatekeydir])
109
+ FileSystem.ensure_dir(puppet.settings[:publickeydir])
110
+
111
+ # Generate and save certs and associated keys
112
+ all_passed = generate_certs(certnames, puppet.settings, signer.digest)
113
+ return all_passed ? 0 : 1
114
+ end
115
+
116
+ # Create csrs and keys, then submit them to CA, request for the CA to sign
117
+ # them, download the signed certificates from the CA, and finally save
118
+ # the signed certs and associated keys. Returns true if all certs were
119
+ # successfully created and saved.
120
+ def generate_certs(certnames, settings, digest)
121
+ ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
122
+
123
+ passed = certnames.map do |certname|
124
+ key, csr = generate_key_csr(certname, settings, digest)
125
+ return false unless ca.submit_certificate_request(certname, csr)
126
+ return false unless ca.sign_certs([certname])
127
+ if result = ca.get_certificate(certname)
128
+ save_file(result.body, certname, settings[:certdir], "Certificate")
129
+ save_keys(certname, settings, key)
130
+ true
131
+ else
132
+ false
133
+ end
134
+ end
135
+ passed.all?
136
+ end
137
+
138
+ def generate_key_csr(certname, settings, digest)
139
+ host = Puppetserver::Ca::Host.new(digest)
140
+ private_key = host.create_private_key(settings[:keylength])
141
+ csr = host.create_csr(certname, private_key)
142
+
143
+ return private_key, csr
144
+ end
145
+
146
+ def save_keys(certname, settings, key)
147
+ public_key = key.public_key
148
+ save_file(key, certname, settings[:privatekeydir], "Private key")
149
+ save_file(public_key, certname, settings[:publickeydir], "Public key")
150
+ end
151
+
152
+ def save_file(content, certname, dir, type)
153
+ location = File.join(dir, "#{certname}.pem")
154
+ @logger.warn "#{type} #{certname}.pem already exists, overwriting" if File.exist?(location)
155
+ FileSystem.write_file(location, content, 0640)
156
+ @logger.inform "Successfully saved #{type.downcase} for #{certname} to #{location}"
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,313 @@
1
+ require 'optparse'
2
+ require 'openssl'
3
+ require 'puppetserver/ca/utils/file_system'
4
+ require 'puppetserver/ca/host'
5
+ require 'puppetserver/ca/utils/cli_parsing'
6
+ require 'puppetserver/ca/utils/signing_digest'
7
+ require 'puppetserver/ca/config/puppet'
8
+ require 'facter'
9
+
10
+ module Puppetserver
11
+ module Ca
12
+ module Action
13
+ class Generate
14
+ include Puppetserver::Ca::Utils
15
+
16
+ CA_EXTENSIONS = [
17
+ ["basicConstraints", "CA:TRUE", true],
18
+ ["keyUsage", "keyCertSign, cRLSign", true],
19
+ ["subjectKeyIdentifier", "hash", false],
20
+ ["nsComment", "Puppet Server Internal Certificate", false],
21
+ ["authorityKeyIdentifier", "keyid:always", false]
22
+ ].freeze
23
+
24
+ SSL_SERVER_CERT = "1.3.6.1.5.5.7.3.1"
25
+ SSL_CLIENT_CERT = "1.3.6.1.5.5.7.3.2"
26
+
27
+ MASTER_EXTENSIONS = [
28
+ ["basicConstraints", "CA:FALSE", true],
29
+ ["nsComment", "Puppet Server Internal Certificate", false],
30
+ ["authorityKeyIdentifier", "keyid:always", false],
31
+ ["extendedKeyUsage", "#{SSL_SERVER_CERT}, #{SSL_CLIENT_CERT}", true],
32
+ ["keyUsage", "keyEncipherment, digitalSignature", true],
33
+ ["subjectKeyIdentifier", "hash", false]
34
+ ].freeze
35
+
36
+ # Make the certificate valid as of yesterday, because so many people's
37
+ # clocks are out of sync. This gives one more day of validity than people
38
+ # might expect, but is better than making every person who has a messed up
39
+ # clock fail, and better than having every cert we generate expire a day
40
+ # before the user expected it to when they asked for "one year".
41
+ CERT_VALID_FROM = (Time.now - (60*60*24)).freeze
42
+
43
+ SUMMARY = "Generate a root and intermediate signing CA for Puppet Server"
44
+ BANNER = <<-BANNER
45
+ Usage:
46
+ puppetserver ca generate [--help]
47
+ puppetserver ca generate [--config PATH]
48
+ puppetserver ca generate [--subject-alt-names ALTNAME1[,ALTNAME2...]]
49
+
50
+ Description:
51
+ Generate a root and intermediate signing CA for Puppet Server
52
+ and store generated CA keys, certs, and crls on disk.
53
+
54
+ The `--subject-alt-names` flag can be used to add SANs to the
55
+ certificate generated for the Puppet master. Multiple names can be
56
+ listed as a comma separated string. These can be either DNS names or
57
+ IP addresses, differentiated by prefixes: `DNS:foo.bar.com,IP:123.456.789`.
58
+ Names with no prefix will be treated as DNS names.
59
+
60
+ To determine the target location, the default puppet.conf
61
+ is consulted for custom values. If using a custom puppet.conf
62
+ provide it with the --config flag
63
+
64
+ Options:
65
+ BANNER
66
+
67
+ def initialize(logger)
68
+ @logger = logger
69
+ end
70
+
71
+ def run(input)
72
+ # Validate config_path provided
73
+ config_path = input['config']
74
+ if config_path
75
+ errors = FileSystem.validate_file_paths(config_path)
76
+ return 1 if CliParsing.handle_errors(@logger, errors)
77
+ end
78
+
79
+ # Load, resolve, and validate puppet config settings
80
+ puppet = Config::Puppet.parse(config_path)
81
+ return 1 if CliParsing.handle_errors(@logger, puppet.errors)
82
+
83
+ # Load most secure signing digest we can for cers/crl/csr signing.
84
+ signer = SigningDigest.new
85
+ return 1 if CliParsing.handle_errors(@logger, signer.errors)
86
+
87
+ if input['subject_alt_names'].empty?
88
+ subject_alt_names = munge_alt_names(puppet.settings[:subject_alt_names])
89
+ else
90
+ subject_alt_names = munge_alt_names(input['subject_alt_names'])
91
+ end
92
+
93
+ # Generate root and intermediate ca and put all the certificates, crls,
94
+ # and keys where they should go.
95
+ errors = generate_pki(puppet.settings, signer.digest, subject_alt_names)
96
+ return 1 if CliParsing.handle_errors(@logger, errors)
97
+
98
+ @logger.inform "Generation succeeded. Find your files in #{puppet.settings[:cadir]}"
99
+ return 0
100
+ end
101
+
102
+ def generate_pki(settings, signing_digest, subject_alt_names = '')
103
+ valid_until = Time.now + settings[:ca_ttl]
104
+ host = Puppetserver::Ca::Host.new(signing_digest)
105
+
106
+ root_key = host.create_private_key(settings[:keylength])
107
+ root_cert = self_signed_ca(root_key, settings[:root_ca_name], valid_until, signing_digest)
108
+ root_crl = create_crl_for(root_cert, root_key, valid_until, signing_digest)
109
+
110
+ int_key = host.create_private_key(settings[:keylength])
111
+ int_csr = host.create_csr(settings[:ca_name], int_key)
112
+ int_cert = sign_intermediate(root_key, root_cert, int_csr, valid_until, signing_digest)
113
+ int_crl = create_crl_for(int_cert, int_key, valid_until, signing_digest)
114
+
115
+ master_key = host.create_private_key(settings[:keylength])
116
+ master_csr = host.create_csr(settings[:certname], master_key)
117
+ master_cert = sign_master_cert(int_key, int_cert, master_csr,
118
+ valid_until, signing_digest, subject_alt_names)
119
+
120
+ FileSystem.ensure_dir(settings[:cadir])
121
+ FileSystem.ensure_dir(settings[:certdir])
122
+ FileSystem.ensure_dir(settings[:privatekeydir])
123
+ FileSystem.ensure_dir(settings[:publickeydir])
124
+
125
+ public_files = [
126
+ [settings[:cacert], [int_cert, root_cert]],
127
+ [settings[:cacrl], [int_crl, root_crl]],
128
+ [settings[:hostcert], master_cert],
129
+ [settings[:localcacert], [int_cert, root_cert]],
130
+ [settings[:localcacrl], [int_crl, root_crl]],
131
+ [settings[:hostpubkey], master_key.public_key],
132
+ [settings[:capub], int_key.public_key],
133
+ [settings[:cert_inventory], inventory_entry(master_cert)],
134
+ [settings[:serial], "0x0002"],
135
+ ]
136
+
137
+ private_files = [
138
+ [settings[:hostprivkey], master_key],
139
+ [settings[:rootkey], root_key],
140
+ [settings[:cakey], int_key],
141
+ ]
142
+
143
+ errors = FileSystem.check_for_existing_files(public_files.map(&:first))
144
+ errors += FileSystem.check_for_existing_files(private_files.map(&:first))
145
+
146
+ if !errors.empty?
147
+ instructions = <<-ERR
148
+ If you would really like to replace your CA, please delete the existing files first.
149
+ Note that any certificates that were issued by this CA will become invalid if you
150
+ replace it!
151
+ ERR
152
+ errors << instructions
153
+ return errors
154
+ end
155
+
156
+ public_files.each do |location, content|
157
+ FileSystem.write_file(location, content, 0644)
158
+ end
159
+
160
+ private_files.each do |location, content|
161
+ FileSystem.write_file(location, content, 0640)
162
+ end
163
+
164
+ return []
165
+ end
166
+
167
+ def self_signed_ca(key, name, valid_until, signing_digest)
168
+ cert = OpenSSL::X509::Certificate.new
169
+
170
+ cert.public_key = key.public_key
171
+ cert.subject = OpenSSL::X509::Name.new([["CN", name]])
172
+ cert.issuer = cert.subject
173
+ cert.version = 2
174
+ cert.serial = 1
175
+
176
+ cert.not_before = CERT_VALID_FROM
177
+ cert.not_after = valid_until
178
+
179
+ ef = extension_factory_for(cert, cert)
180
+ CA_EXTENSIONS.each do |ext|
181
+ extension = ef.create_extension(*ext)
182
+ cert.add_extension(extension)
183
+ end
184
+
185
+ cert.sign(key, signing_digest)
186
+
187
+ cert
188
+ end
189
+
190
+ def inventory_entry(cert)
191
+ "0x%04x %s %s %s" % [cert.serial, format_time(cert.not_before),
192
+ format_time(cert.not_after), cert.subject]
193
+ end
194
+
195
+ def format_time(time)
196
+ time.strftime('%Y-%m-%dT%H:%M:%S%Z')
197
+ end
198
+
199
+ def extension_factory_for(ca, cert = nil)
200
+ ef = OpenSSL::X509::ExtensionFactory.new
201
+ ef.issuer_certificate = ca
202
+ ef.subject_certificate = cert if cert
203
+
204
+ ef
205
+ end
206
+
207
+ def create_crl_for(ca_cert, ca_key, valid_until, signing_digest)
208
+ crl = OpenSSL::X509::CRL.new
209
+ crl.version = 1
210
+ crl.issuer = ca_cert.subject
211
+
212
+ ef = extension_factory_for(ca_cert)
213
+ crl.add_extension(
214
+ ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
215
+ crl.add_extension(
216
+ OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
217
+
218
+ crl.last_update = CERT_VALID_FROM
219
+ crl.next_update = valid_until
220
+ crl.sign(ca_key, signing_digest)
221
+
222
+ crl
223
+ end
224
+
225
+ def sign_intermediate(ca_key, ca_cert, csr, valid_until, signing_digest)
226
+ cert = OpenSSL::X509::Certificate.new
227
+
228
+ cert.public_key = csr.public_key
229
+ cert.subject = csr.subject
230
+ cert.issuer = ca_cert.subject
231
+ cert.version = 2
232
+ cert.serial = 2
233
+
234
+ cert.not_before = CERT_VALID_FROM
235
+ cert.not_after = valid_until
236
+
237
+ ef = extension_factory_for(ca_cert, cert)
238
+ CA_EXTENSIONS.each do |ext|
239
+ extension = ef.create_extension(*ext)
240
+ cert.add_extension(extension)
241
+ end
242
+
243
+ cert.sign(ca_key, signing_digest)
244
+
245
+ cert
246
+ end
247
+
248
+ def sign_master_cert(int_key, int_cert, csr, valid_until, signing_digest, subject_alt_names)
249
+ cert = OpenSSL::X509::Certificate.new
250
+ cert.public_key = csr.public_key
251
+ cert.subject = csr.subject
252
+ cert.issuer = int_cert.subject
253
+ cert.version = 2
254
+ cert.serial = 1
255
+ cert.not_before = CERT_VALID_FROM
256
+ cert.not_after = valid_until
257
+
258
+ ef = extension_factory_for(int_cert, cert)
259
+ MASTER_EXTENSIONS.each do |ext|
260
+ extension = ef.create_extension(*ext)
261
+ cert.add_extension(extension)
262
+ end
263
+
264
+ if !subject_alt_names.empty?
265
+ alt_names_ext = ef.create_extension("subjectAltName", subject_alt_names, false)
266
+ cert.add_extension(alt_names_ext)
267
+ end
268
+
269
+ cert.sign(int_key, signing_digest)
270
+ cert
271
+ end
272
+
273
+ def munge_alt_names(names)
274
+ raw_names = names.split(/\s*,\s*/).map(&:strip)
275
+ munged_names = raw_names.map do |name|
276
+ # Prepend the DNS tag if no tag was specified
277
+ if !name.start_with?("IP:") && !name.start_with?("DNS:")
278
+ "DNS:#{name}"
279
+ else
280
+ name
281
+ end
282
+ end.sort.uniq.join(", ")
283
+ end
284
+
285
+ def parse(cli_args)
286
+ results = {}
287
+ parser = self.class.parser(results)
288
+ errors = CliParsing.parse_with_errors(parser, cli_args)
289
+ errors_were_handled = CliParsing.handle_errors(@logger, errors, parser.help)
290
+ exit_code = errors_were_handled ? 1 : nil
291
+ return results, exit_code
292
+ end
293
+
294
+ def self.parser(parsed = {})
295
+ parsed['subject_alt_names'] = ''
296
+ OptionParser.new do |opts|
297
+ opts.banner = BANNER
298
+ opts.on('--help', 'Display this generate specific help output') do |help|
299
+ parsed['help'] = true
300
+ end
301
+ opts.on('--config CONF', 'Path to puppet.conf') do |conf|
302
+ parsed['config'] = conf
303
+ end
304
+ opts.on('--subject-alt-names NAME1[,NAME2]',
305
+ 'Subject alternative names for the CA signing cert') do |sans|
306
+ parsed['subject_alt_names'] = sans
307
+ end
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end