puppetserver-ca 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7cdc97b5346544e3f5eaa1b6e8258e7f8ad6356d
4
- data.tar.gz: fa5cafd9e26a01d03a0f9ffd73169b4ea98c77f0
3
+ metadata.gz: 675bbbde6ef2920bff8139cd7cbed1da317a26e4
4
+ data.tar.gz: 59856cf44f1a0f8629faadb2b69a4a9a66722225
5
5
  SHA512:
6
- metadata.gz: 96260c55c8b9a1499ce112e20300801306ab9e7ce7ebdaf2a8a0cff5cffb9f692e5b266dccdf1a2df14bb3ff4f6a57dc2b4bb17dc93ce9a2f092b57e6f61a002
7
- data.tar.gz: b9ffc226ca976e0359530d89a5c449f1ec089c9e75b560aa0dbb5b5db133900ebbd657d14a96884d55f9d098b963f207f57f5249c3df257e7715ad14ba55e4c2
6
+ metadata.gz: a4ffd3cbedefabf0ae8c5e33eb173addfea289fd984e9252ecc971d6eea1775a5d8bcfdf6446b1d1aaa1d8d69bb0d6c802afb1e797e50f7ace7304c26794f48f
7
+ data.tar.gz: 02ae98ec500429f7c4d589c5a2e0d975cb06bce415c09f27caf4f24d0b9cab707bc1bc40bca9ed33596229ea75fc0749252e77bc33e683e3054e32d1b30a28bb
@@ -20,7 +20,7 @@ module Puppetserver
20
20
  BANNER = <<-BANNER
21
21
  Usage:
22
22
  puppetserver ca create [--help]
23
- puppetserver ca create [--config] --certname CERTNAME[,ADDLCERTNAME]
23
+ puppetserver ca create [--config PATH] [--certname CERTNAME[,ADDLCERTNAME]]
24
24
 
25
25
  Description:
26
26
  Creates a new certificate signed by the intermediate CA
@@ -103,11 +103,6 @@ BANNER
103
103
  signer = SigningDigest.new
104
104
  return 1 if CliParsing.handle_errors(@logger, signer.errors)
105
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
106
  # Generate and save certs and associated keys
112
107
  all_passed = generate_certs(certnames, puppet.settings, signer.digest)
113
108
  return all_passed ? 0 : 1
@@ -118,6 +113,12 @@ BANNER
118
113
  # the signed certs and associated keys. Returns true if all certs were
119
114
  # successfully created and saved.
120
115
  def generate_certs(certnames, settings, digest)
116
+ # Make sure we have all the directories where we will be writing files
117
+ FileSystem.ensure_dirs([settings[:ssldir],
118
+ settings[:certdir],
119
+ settings[:privatekeydir],
120
+ settings[:publickeydir]])
121
+
121
122
  ca = Puppetserver::Ca::CertificateAuthority.new(@logger, settings)
122
123
 
123
124
  passed = certnames.map do |certname|
@@ -1,11 +1,9 @@
1
1
  require 'optparse'
2
- require 'openssl'
3
2
  require 'puppetserver/ca/utils/file_system'
4
- require 'puppetserver/ca/host'
3
+ require 'puppetserver/ca/local_certificate_authority'
5
4
  require 'puppetserver/ca/utils/cli_parsing'
6
5
  require 'puppetserver/ca/utils/signing_digest'
7
6
  require 'puppetserver/ca/config/puppet'
8
- require 'facter'
9
7
 
10
8
  module Puppetserver
11
9
  module Ca
@@ -13,33 +11,6 @@ module Puppetserver
13
11
  class Generate
14
12
  include Puppetserver::Ca::Utils
15
13
 
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
14
  SUMMARY = "Generate a root and intermediate signing CA for Puppet Server"
44
15
  BANNER = <<-BANNER
45
16
  Usage:
@@ -79,10 +50,10 @@ BANNER
79
50
  # Load, resolve, and validate puppet config settings
80
51
  settings_overrides = {}
81
52
  settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
82
- settings_overrides[:ca_name] = input['ca_name'] unless input['ca_name'].empty?
53
+ settings_overrides[:ca_name] = input['ca-name'] unless input['ca-name'].empty?
83
54
  # Since puppet expects the key to be called 'dns_alt_names', we need to use that here
84
55
  # to ensure that the overriding works correctly.
85
- settings_overrides[:dns_alt_names] = input['subject_alt_names'] unless input['subject_alt_names'].empty?
56
+ settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
86
57
 
87
58
  puppet = Config::Puppet.new(config_path)
88
59
  puppet.load(settings_overrides)
@@ -102,40 +73,28 @@ BANNER
102
73
  end
103
74
 
104
75
  def generate_pki(settings, signing_digest)
105
- valid_until = Time.now + settings[:ca_ttl]
106
- host = Puppetserver::Ca::Host.new(signing_digest)
107
- subject_alt_names = munge_alt_names(settings[:subject_alt_names])
108
-
109
- root_key = host.create_private_key(settings[:keylength])
110
- root_cert = self_signed_ca(root_key, settings[:root_ca_name], valid_until, signing_digest)
111
- root_crl = create_crl_for(root_cert, root_key, valid_until, signing_digest)
112
-
113
- int_key = host.create_private_key(settings[:keylength])
114
- int_csr = host.create_csr(settings[:ca_name], int_key)
115
- int_cert = sign_intermediate(root_key, root_cert, int_csr, valid_until, signing_digest)
116
- int_crl = create_crl_for(int_cert, int_key, valid_until, signing_digest)
76
+ ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
117
77
 
118
- master_key = host.create_private_key(settings[:keylength])
119
- master_csr = host.create_csr(settings[:certname], master_key)
120
- master_cert = sign_master_cert(int_key, int_cert, master_csr,
121
- valid_until, signing_digest,
122
- subject_alt_names)
78
+ root_key, root_cert, root_crl = ca.create_root_cert
79
+ int_key, int_cert, int_crl = ca.create_intermediate_cert(root_key, root_cert)
80
+ master_key, master_cert = ca.create_master_cert(int_key, int_cert)
123
81
 
124
- FileSystem.ensure_dir(settings[:cadir])
125
- FileSystem.ensure_dir(settings[:certdir])
126
- FileSystem.ensure_dir(settings[:privatekeydir])
127
- FileSystem.ensure_dir(settings[:publickeydir])
128
- FileSystem.ensure_dir(settings[:signeddir])
82
+ FileSystem.ensure_dirs([settings[:ssldir],
83
+ settings[:cadir],
84
+ settings[:certdir],
85
+ settings[:privatekeydir],
86
+ settings[:publickeydir],
87
+ settings[:signeddir]])
129
88
 
130
89
  public_files = [
131
90
  [settings[:cacert], [int_cert, root_cert]],
132
91
  [settings[:cacrl], [int_crl, root_crl]],
133
92
  [settings[:hostcert], master_cert],
134
93
  [settings[:localcacert], [int_cert, root_cert]],
135
- [settings[:localcacrl], [int_crl, root_crl]],
94
+ [settings[:hostcrl], [int_crl, root_crl]],
136
95
  [settings[:hostpubkey], master_key.public_key],
137
96
  [settings[:capub], int_key.public_key],
138
- [settings[:cert_inventory], inventory_entry(master_cert)],
97
+ [settings[:cert_inventory], ca.inventory_entry(master_cert)],
139
98
  [settings[:serial], "002"],
140
99
  [File.join(settings[:signeddir], "#{settings[:certname]}.pem"), master_cert],
141
100
  ]
@@ -146,8 +105,7 @@ BANNER
146
105
  [settings[:cakey], int_key],
147
106
  ]
148
107
 
149
- errors = FileSystem.check_for_existing_files(public_files.map(&:first))
150
- errors += FileSystem.check_for_existing_files(private_files.map(&:first))
108
+ errors = FileSystem.check_for_existing_files(public_files.map(&:first) + private_files.map(&:first))
151
109
 
152
110
  if !errors.empty?
153
111
  instructions = <<-ERR
@@ -170,124 +128,6 @@ ERR
170
128
  return []
171
129
  end
172
130
 
173
- def self_signed_ca(key, name, valid_until, signing_digest)
174
- cert = OpenSSL::X509::Certificate.new
175
-
176
- cert.public_key = key.public_key
177
- cert.subject = OpenSSL::X509::Name.new([["CN", name]])
178
- cert.issuer = cert.subject
179
- cert.version = 2
180
- cert.serial = 1
181
-
182
- cert.not_before = CERT_VALID_FROM
183
- cert.not_after = valid_until
184
-
185
- ef = extension_factory_for(cert, cert)
186
- CA_EXTENSIONS.each do |ext|
187
- extension = ef.create_extension(*ext)
188
- cert.add_extension(extension)
189
- end
190
-
191
- cert.sign(key, signing_digest)
192
-
193
- cert
194
- end
195
-
196
- def inventory_entry(cert)
197
- "0x%04x %s %s %s" % [cert.serial, format_time(cert.not_before),
198
- format_time(cert.not_after), cert.subject]
199
- end
200
-
201
- def format_time(time)
202
- time.strftime('%Y-%m-%dT%H:%M:%S%Z')
203
- end
204
-
205
- def extension_factory_for(ca, cert = nil)
206
- ef = OpenSSL::X509::ExtensionFactory.new
207
- ef.issuer_certificate = ca
208
- ef.subject_certificate = cert if cert
209
-
210
- ef
211
- end
212
-
213
- def create_crl_for(ca_cert, ca_key, valid_until, signing_digest)
214
- crl = OpenSSL::X509::CRL.new
215
- crl.version = 1
216
- crl.issuer = ca_cert.subject
217
-
218
- ef = extension_factory_for(ca_cert)
219
- crl.add_extension(
220
- ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
221
- crl.add_extension(
222
- OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
223
-
224
- crl.last_update = CERT_VALID_FROM
225
- crl.next_update = valid_until
226
- crl.sign(ca_key, signing_digest)
227
-
228
- crl
229
- end
230
-
231
- def sign_intermediate(ca_key, ca_cert, csr, valid_until, signing_digest)
232
- cert = OpenSSL::X509::Certificate.new
233
-
234
- cert.public_key = csr.public_key
235
- cert.subject = csr.subject
236
- cert.issuer = ca_cert.subject
237
- cert.version = 2
238
- cert.serial = 2
239
-
240
- cert.not_before = CERT_VALID_FROM
241
- cert.not_after = valid_until
242
-
243
- ef = extension_factory_for(ca_cert, cert)
244
- CA_EXTENSIONS.each do |ext|
245
- extension = ef.create_extension(*ext)
246
- cert.add_extension(extension)
247
- end
248
-
249
- cert.sign(ca_key, signing_digest)
250
-
251
- cert
252
- end
253
-
254
- def sign_master_cert(int_key, int_cert, csr, valid_until, signing_digest, subject_alt_names)
255
- cert = OpenSSL::X509::Certificate.new
256
- cert.public_key = csr.public_key
257
- cert.subject = csr.subject
258
- cert.issuer = int_cert.subject
259
- cert.version = 2
260
- cert.serial = 1
261
- cert.not_before = CERT_VALID_FROM
262
- cert.not_after = valid_until
263
-
264
- ef = extension_factory_for(int_cert, cert)
265
- MASTER_EXTENSIONS.each do |ext|
266
- extension = ef.create_extension(*ext)
267
- cert.add_extension(extension)
268
- end
269
-
270
- if !subject_alt_names.empty?
271
- alt_names_ext = ef.create_extension("subjectAltName", subject_alt_names, false)
272
- cert.add_extension(alt_names_ext)
273
- end
274
-
275
- cert.sign(int_key, signing_digest)
276
- cert
277
- end
278
-
279
- def munge_alt_names(names)
280
- raw_names = names.split(/\s*,\s*/).map(&:strip)
281
- munged_names = raw_names.map do |name|
282
- # Prepend the DNS tag if no tag was specified
283
- if !name.start_with?("IP:") && !name.start_with?("DNS:")
284
- "DNS:#{name}"
285
- else
286
- name
287
- end
288
- end.sort.uniq.join(", ")
289
- end
290
-
291
131
  def parse(cli_args)
292
132
  results = {}
293
133
  parser = self.class.parser(results)
@@ -298,8 +138,8 @@ ERR
298
138
  end
299
139
 
300
140
  def self.parser(parsed = {})
301
- parsed['subject_alt_names'] = ''
302
- parsed['ca_name'] = ''
141
+ parsed['subject-alt-names'] = ''
142
+ parsed['ca-name'] = ''
303
143
  parsed['certname'] = ''
304
144
  OptionParser.new do |opts|
305
145
  opts.banner = BANNER
@@ -311,11 +151,11 @@ ERR
311
151
  end
312
152
  opts.on('--subject-alt-names NAME1[,NAME2]',
313
153
  'Subject alternative names for the master cert') do |sans|
314
- parsed['subject_alt_names'] = sans
154
+ parsed['subject-alt-names'] = sans
315
155
  end
316
156
  opts.on('--ca-name NAME',
317
157
  'Common name to use for the CA signing cert') do |name|
318
- parsed['ca_name'] = name
158
+ parsed['ca-name'] = name
319
159
  end
320
160
  opts.on('--certname NAME',
321
161
  'Common name to use for the master cert') do |name|
@@ -2,7 +2,9 @@ require 'optparse'
2
2
  require 'puppetserver/ca/utils/file_system'
3
3
  require 'puppetserver/ca/x509_loader'
4
4
  require 'puppetserver/ca/config/puppet'
5
+ require 'puppetserver/ca/local_certificate_authority'
5
6
  require 'puppetserver/ca/utils/cli_parsing'
7
+ require 'puppetserver/ca/utils/signing_digest'
6
8
 
7
9
  module Puppetserver
8
10
  module Ca
@@ -15,12 +17,16 @@ module Puppetserver
15
17
  Usage:
16
18
  puppetserver ca import [--help]
17
19
  puppetserver ca import [--config PATH] [--certname NAME]
20
+ [--subject-alt-names ALTNAME1[,ALTNAME2...]]
18
21
  --private-key PATH --cert-bundle PATH --crl-chain PATH
19
22
 
20
23
  Description:
21
24
  Given a private key, cert bundle, and a crl chain,
22
25
  validate and import to the Puppet Server CA.
23
26
 
27
+ Note that the cert and crl provided for the leaf CA must not
28
+ have already issued or revoked any certificates.
29
+
24
30
  To determine the target location the default puppet.conf
25
31
  is consulted for custom values. If using a custom puppet.conf
26
32
  provide it with the --config flag
@@ -48,16 +54,53 @@ BANNER
48
54
 
49
55
  settings_overrides = {}
50
56
  settings_overrides[:certname] = input['certname'] unless input['certname'].empty?
57
+ settings_overrides[:dns_alt_names] = input['subject-alt-names'] unless input['subject-alt-names'].empty?
58
+
51
59
  puppet = Config::Puppet.new(config_path)
52
60
  puppet.load(settings_overrides)
53
61
  return 1 if CliParsing.handle_errors(@logger, puppet.errors)
54
62
 
55
- target_locations = [puppet.settings[:cacert],
56
- puppet.settings[:cakey],
57
- puppet.settings[:cacrl],
58
- puppet.settings[:serial],
59
- puppet.settings[:cert_inventory]]
60
- errors = FileSystem.check_for_existing_files(target_locations)
63
+ # Load most secure signing digest we can for cers/crl/csr signing.
64
+ signer = SigningDigest.new
65
+ return 1 if CliParsing.handle_errors(@logger, signer.errors)
66
+
67
+ errors = import(loader, puppet.settings, signer.digest)
68
+ return 1 if CliParsing.handle_errors(@logger, errors)
69
+
70
+ @logger.inform "Import succeeded. Find your files in #{puppet.settings[:cadir]}"
71
+ return 0
72
+ end
73
+
74
+ def import(loader, settings, signing_digest)
75
+ ca = Puppetserver::Ca::LocalCertificateAuthority.new(signing_digest, settings)
76
+ master_key, master_cert = ca.create_master_cert(loader.key, loader.certs.first)
77
+
78
+ FileSystem.ensure_dirs([settings[:ssldir],
79
+ settings[:cadir],
80
+ settings[:certdir],
81
+ settings[:privatekeydir],
82
+ settings[:publickeydir],
83
+ settings[:signeddir]])
84
+
85
+ public_files = [
86
+ [settings[:cacert], loader.certs],
87
+ [settings[:cacrl], loader.crls],
88
+ [settings[:localcacert], loader.certs],
89
+ [settings[:hostcrl], loader.crls],
90
+ [settings[:hostpubkey], master_key.public_key],
91
+ [settings[:hostcert], master_cert],
92
+ [settings[:cert_inventory], ca.inventory_entry(master_cert)],
93
+ [settings[:serial], "0x0002"],
94
+ [File.join(settings[:signeddir], "#{settings[:certname]}.pem"), master_cert]
95
+ ]
96
+
97
+ private_files = [
98
+ [settings[:hostprivkey], master_key],
99
+ [settings[:cakey], loader.key],
100
+ ]
101
+
102
+ errors = FileSystem.check_for_existing_files(public_files.map(&:first) + private_files.map(&:first))
103
+
61
104
  if !errors.empty?
62
105
  instructions = <<-ERR
63
106
  If you would really like to replace your CA, please delete the existing files first.
@@ -65,24 +108,18 @@ Note that any certificates that were issued by this CA will become invalid if yo
65
108
  replace it!
66
109
  ERR
67
110
  errors << instructions
68
- CliParsing.handle_errors(@logger, errors)
69
- return 1
111
+ return errors
70
112
  end
71
113
 
72
- FileSystem.ensure_dir(puppet.settings[:cadir])
73
-
74
- FileSystem.write_file(puppet.settings[:cacert], loader.certs, 0640)
75
-
76
- FileSystem.write_file(puppet.settings[:cakey], loader.key, 0640)
77
-
78
- FileSystem.write_file(puppet.settings[:cacrl], loader.crls, 0640)
114
+ public_files.each do |location, content|
115
+ FileSystem.write_file(location, content, 0644)
116
+ end
79
117
 
80
- # Puppet's internal CA expects these file to exist.
81
- FileSystem.ensure_file(puppet.settings[:serial], "001", 0640)
82
- FileSystem.ensure_file(puppet.settings[:cert_inventory], "", 0640)
118
+ private_files.each do |location, content|
119
+ FileSystem.write_file(location, content, 0640)
120
+ end
83
121
 
84
- @logger.inform "Import succeeded. Find your files in #{puppet.settings[:cadir]}"
85
- return 0
122
+ return []
86
123
  end
87
124
 
88
125
  def check_flag_usage(results)
@@ -110,6 +147,8 @@ ERR
110
147
  end
111
148
 
112
149
  def self.parser(parsed = {})
150
+ parsed['certname'] = ''
151
+ parsed['subject-alt-names'] = ''
113
152
  OptionParser.new do |opts|
114
153
  opts.banner = BANNER
115
154
  opts.on('--help', 'Display this import specific help output') do |help|
@@ -131,6 +170,10 @@ ERR
131
170
  'Common name to use for the master cert') do |name|
132
171
  parsed['certname'] = name
133
172
  end
173
+ opts.on('--subject-alt-names NAME1[,NAME2]',
174
+ 'Subject alternative names for the master cert') do |sans|
175
+ parsed['subject-alt-names'] = sans
176
+ end
134
177
  end
135
178
  end
136
179
  end
@@ -126,9 +126,8 @@ module Puppetserver
126
126
  :ca_server => '$server',
127
127
  :ca_port => '$masterport',
128
128
  :localcacert => '$certdir/ca.pem',
129
- :localcacrl => '$ssldir/crl.pem',
130
- :hostcert => '$certdir/$certname.pem',
131
129
  :hostcrl => '$ssldir/crl.pem',
130
+ :hostcert => '$certdir/$certname.pem',
132
131
  :hostprivkey => '$privatekeydir/$certname.pem',
133
132
  :hostpubkey => '$publickeydir/$certname.pem',
134
133
  :publickeydir => '$ssldir/public_keys',
@@ -153,12 +152,13 @@ module Puppetserver
153
152
  settings[setting_name] = setting_value
154
153
  end
155
154
 
155
+ # rename dns_alt_names to subject_alt_names now that we support IP alt names
156
+ settings[:subject_alt_names] = overrides.fetch(:dns_alt_names, "puppet,$certname")
157
+
156
158
  # Some special cases where we need to manipulate config settings:
157
159
  settings[:ca_ttl] = munge_ttl_setting(settings[:ca_ttl])
158
160
  settings[:certificate_revocation] = parse_crl_usage(settings[:certificate_revocation])
159
-
160
- # rename dns_alt_names to subject_alt_names now that we support IP alt names
161
- settings[:subject_alt_names] = overrides.fetch(:dns_alt_names, "puppet,$certname")
161
+ settings[:subject_alt_names] = munge_alt_names(settings[:subject_alt_names])
162
162
 
163
163
  settings.each do |key, value|
164
164
  next unless value.is_a? String
@@ -227,6 +227,17 @@ module Puppetserver
227
227
  end
228
228
  end
229
229
 
230
+ def munge_alt_names(names)
231
+ raw_names = names.split(/\s*,\s*/).map(&:strip)
232
+ munged_names = raw_names.map do |name|
233
+ # Prepend the DNS tag if no tag was specified
234
+ if !name.start_with?("IP:") && !name.start_with?("DNS:")
235
+ "DNS:#{name}"
236
+ else
237
+ name
238
+ end
239
+ end.sort.uniq.join(", ")
240
+ end
230
241
 
231
242
  def parse_crl_usage(setting)
232
243
  case setting.to_s
@@ -0,0 +1,177 @@
1
+ require 'puppetserver/ca/host'
2
+
3
+ require 'openssl'
4
+
5
+ module Puppetserver
6
+ module Ca
7
+ class LocalCertificateAuthority
8
+
9
+ # Make the certificate valid as of yesterday, because so many people's
10
+ # clocks are out of sync. This gives one more day of validity than people
11
+ # might expect, but is better than making every person who has a messed up
12
+ # clock fail, and better than having every cert we generate expire a day
13
+ # before the user expected it to when they asked for "one year".
14
+ CERT_VALID_FROM = (Time.now - (60*60*24)).freeze
15
+
16
+ SSL_SERVER_CERT = "1.3.6.1.5.5.7.3.1"
17
+ SSL_CLIENT_CERT = "1.3.6.1.5.5.7.3.2"
18
+
19
+ MASTER_EXTENSIONS = [
20
+ ["basicConstraints", "CA:FALSE", true],
21
+ ["nsComment", "Puppet Server Internal Certificate", false],
22
+ ["authorityKeyIdentifier", "keyid:always", false],
23
+ ["extendedKeyUsage", "#{SSL_SERVER_CERT}, #{SSL_CLIENT_CERT}", true],
24
+ ["keyUsage", "keyEncipherment, digitalSignature", true],
25
+ ["subjectKeyIdentifier", "hash", false]
26
+ ].freeze
27
+
28
+ CA_EXTENSIONS = [
29
+ ["basicConstraints", "CA:TRUE", true],
30
+ ["keyUsage", "keyCertSign, cRLSign", true],
31
+ ["subjectKeyIdentifier", "hash", false],
32
+ ["nsComment", "Puppet Server Internal Certificate", false],
33
+ ["authorityKeyIdentifier", "keyid:always", false]
34
+ ].freeze
35
+
36
+ def initialize(digest, settings)
37
+ @digest = digest
38
+ @host = Host.new(digest)
39
+ @settings = settings
40
+ end
41
+
42
+ def valid_until
43
+ Time.now + @settings[:ca_ttl]
44
+ end
45
+
46
+ def extension_factory_for(ca, cert = nil)
47
+ ef = OpenSSL::X509::ExtensionFactory.new
48
+ ef.issuer_certificate = ca
49
+ ef.subject_certificate = cert if cert
50
+
51
+ ef
52
+ end
53
+
54
+ def inventory_entry(cert)
55
+ "0x%04x %s %s %s" % [cert.serial, format_time(cert.not_before),
56
+ format_time(cert.not_after), cert.subject]
57
+ end
58
+
59
+ def format_time(time)
60
+ time.strftime('%Y-%m-%dT%H:%M:%S%Z')
61
+ end
62
+
63
+ def create_master_cert(ca_key, ca_cert)
64
+ master_key = @host.create_private_key(@settings[:keylength])
65
+ master_csr = @host.create_csr(@settings[:certname], master_key)
66
+ master_cert = sign_master_cert(ca_key, ca_cert, master_csr)
67
+ return master_key, master_cert
68
+ end
69
+
70
+ def sign_master_cert(int_key, int_cert, csr)
71
+ cert = OpenSSL::X509::Certificate.new
72
+ cert.public_key = csr.public_key
73
+ cert.subject = csr.subject
74
+ cert.issuer = int_cert.subject
75
+ cert.version = 2
76
+ cert.serial = 1
77
+ cert.not_before = CERT_VALID_FROM
78
+ cert.not_after = valid_until
79
+
80
+ ef = extension_factory_for(int_cert, cert)
81
+ MASTER_EXTENSIONS.each do |ext|
82
+ extension = ef.create_extension(*ext)
83
+ cert.add_extension(extension)
84
+ end
85
+
86
+ if !@settings[:subject_alt_names].empty?
87
+ alt_names_ext = ef.create_extension("subjectAltName", @settings[:subject_alt_names], false)
88
+ cert.add_extension(alt_names_ext)
89
+ end
90
+
91
+ cert.sign(int_key, @digest)
92
+ cert
93
+ end
94
+
95
+ def create_root_cert
96
+ root_key = @host.create_private_key(@settings[:keylength])
97
+ root_cert = self_signed_ca(root_key)
98
+ root_crl = create_crl_for(root_cert, root_key)
99
+
100
+ return root_key, root_cert, root_crl
101
+ end
102
+
103
+ def self_signed_ca(key)
104
+ cert = OpenSSL::X509::Certificate.new
105
+
106
+ cert.public_key = key.public_key
107
+ cert.subject = OpenSSL::X509::Name.new([["CN", @settings[:root_ca_name]]])
108
+ cert.issuer = cert.subject
109
+ cert.version = 2
110
+ cert.serial = 1
111
+
112
+ cert.not_before = CERT_VALID_FROM
113
+ cert.not_after = valid_until
114
+
115
+ ef = extension_factory_for(cert, cert)
116
+ CA_EXTENSIONS.each do |ext|
117
+ extension = ef.create_extension(*ext)
118
+ cert.add_extension(extension)
119
+ end
120
+
121
+ cert.sign(key, @digest)
122
+
123
+ cert
124
+ end
125
+
126
+ def create_crl_for(cert, key)
127
+ crl = OpenSSL::X509::CRL.new
128
+ crl.version = 1
129
+ crl.issuer = cert.subject
130
+
131
+ ef = extension_factory_for(cert)
132
+ crl.add_extension(
133
+ ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
134
+ crl.add_extension(
135
+ OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))
136
+
137
+ crl.last_update = CERT_VALID_FROM
138
+ crl.next_update = valid_until
139
+ crl.sign(key, @digest)
140
+
141
+ crl
142
+ end
143
+
144
+ def create_intermediate_cert(root_key, root_cert)
145
+ int_key = @host.create_private_key(@settings[:keylength])
146
+ int_csr = @host.create_csr(@settings[:ca_name], int_key)
147
+ int_cert = sign_intermediate(root_key, root_cert, int_csr)
148
+ int_crl = create_crl_for(int_cert, int_key)
149
+
150
+ return int_key, int_cert, int_crl
151
+ end
152
+
153
+ def sign_intermediate(ca_key, ca_cert, csr)
154
+ cert = OpenSSL::X509::Certificate.new
155
+
156
+ cert.public_key = csr.public_key
157
+ cert.subject = csr.subject
158
+ cert.issuer = ca_cert.subject
159
+ cert.version = 2
160
+ cert.serial = 2
161
+
162
+ cert.not_before = CERT_VALID_FROM
163
+ cert.not_after = valid_until
164
+
165
+ ef = extension_factory_for(ca_cert, cert)
166
+ CA_EXTENSIONS.each do |ext|
167
+ extension = ef.create_extension(*ext)
168
+ cert.add_extension(extension)
169
+ end
170
+
171
+ cert.sign(ca_key, @digest)
172
+
173
+ cert
174
+ end
175
+ end
176
+ end
177
+ end
@@ -6,6 +6,15 @@ module Puppetserver
6
6
  module Utils
7
7
  class FileSystem
8
8
 
9
+ DIR_MODES = {
10
+ :ssldir => 0771,
11
+ :cadir => 0755,
12
+ :certdir => 0755,
13
+ :privatekeydir => 0750,
14
+ :publickeydir => 0755,
15
+ :signeddir => 0755
16
+ }
17
+
9
18
  def self.instance
10
19
  @instance ||= new
11
20
  end
@@ -14,13 +23,9 @@ module Puppetserver
14
23
  instance.write_file(*args)
15
24
  end
16
25
 
17
- def self.ensure_dir(setting)
18
- instance.ensure_dir(setting)
19
- end
20
-
21
- def self.ensure_file(location, content, mode)
22
- if !File.exist?(location)
23
- instance.write_file(location, content, mode)
26
+ def self.ensure_dirs(one_or_more_dirs)
27
+ Array(one_or_more_dirs).each do |directory|
28
+ instance.ensure_dir(directory)
24
29
  end
25
30
  end
26
31
 
@@ -78,10 +83,11 @@ module Puppetserver
78
83
  FileUtils.chown(@user, @group, path)
79
84
  end
80
85
 
81
- def ensure_dir(setting)
82
- if !File.exist?(setting)
83
- FileUtils.mkdir_p(setting, mode: 0750)
84
- FileUtils.chown(@user, @group, setting)
86
+ # Warning: directory mode should be specified in DIR_MODES above
87
+ def ensure_dir(directory)
88
+ if !File.exist?(directory)
89
+ FileUtils.mkdir_p(directory, mode: DIR_MODES[directory])
90
+ FileUtils.chown(@user, @group, directory)
85
91
  end
86
92
  end
87
93
  end
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "0.4.1"
3
+ VERSION = "0.4.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppetserver-ca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-24 00:00:00.000000000 Z
11
+ date: 2018-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: facter
@@ -105,6 +105,7 @@ files:
105
105
  - lib/puppetserver/ca/config/puppet.rb
106
106
  - lib/puppetserver/ca/config/puppetserver.rb
107
107
  - lib/puppetserver/ca/host.rb
108
+ - lib/puppetserver/ca/local_certificate_authority.rb
108
109
  - lib/puppetserver/ca/logger.rb
109
110
  - lib/puppetserver/ca/stub.rb
110
111
  - lib/puppetserver/ca/utils/cli_parsing.rb