puppetserver-ca 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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