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 +4 -4
- data/lib/puppetserver/ca/action/create.rb +7 -6
- data/lib/puppetserver/ca/action/generate.rb +20 -180
- data/lib/puppetserver/ca/action/import.rb +63 -20
- data/lib/puppetserver/ca/config/puppet.rb +16 -5
- data/lib/puppetserver/ca/local_certificate_authority.rb +177 -0
- data/lib/puppetserver/ca/utils/file_system.rb +17 -11
- data/lib/puppetserver/ca/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 675bbbde6ef2920bff8139cd7cbed1da317a26e4
|
4
|
+
data.tar.gz: 59856cf44f1a0f8629faadb2b69a4a9a66722225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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['
|
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['
|
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
|
-
|
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
|
-
|
119
|
-
|
120
|
-
master_cert =
|
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.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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[:
|
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['
|
302
|
-
parsed['
|
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['
|
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['
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
69
|
-
return 1
|
111
|
+
return errors
|
70
112
|
end
|
71
113
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
118
|
+
private_files.each do |location, content|
|
119
|
+
FileSystem.write_file(location, content, 0640)
|
120
|
+
end
|
83
121
|
|
84
|
-
|
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.
|
18
|
-
|
19
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
FileUtils.
|
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
|
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.
|
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-
|
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
|