puppetserver-ca 0.3.1 → 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.
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