puppet 2.7.5 → 2.7.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/CHANGELOG +121 -0
- data/conf/redhat/puppet.spec +16 -7
- data/lib/puppet.rb +1 -1
- data/lib/puppet/application/cert.rb +17 -3
- data/lib/puppet/application/device.rb +1 -0
- data/lib/puppet/application/kick.rb +0 -2
- data/lib/puppet/application/resource.rb +73 -66
- data/lib/puppet/configurer/plugin_handler.rb +6 -2
- data/lib/puppet/defaults.rb +60 -5
- data/lib/puppet/face/ca.rb +11 -2
- data/lib/puppet/face/certificate.rb +33 -4
- data/lib/puppet/file_serving/fileset.rb +1 -1
- data/lib/puppet/file_serving/indirection_hooks.rb +2 -2
- data/lib/puppet/file_serving/metadata.rb +43 -4
- data/lib/puppet/indirector.rb +0 -1
- data/lib/puppet/indirector/request.rb +3 -4
- data/lib/puppet/indirector/resource/active_record.rb +3 -10
- data/lib/puppet/indirector/resource/ral.rb +2 -2
- data/lib/puppet/indirector/rest.rb +1 -1
- data/lib/puppet/network/handler/ca.rb +16 -106
- data/lib/puppet/network/handler/master.rb +0 -3
- data/lib/puppet/network/handler/runner.rb +1 -0
- data/lib/puppet/parser/scope.rb +10 -0
- data/lib/puppet/provider/file/posix.rb +72 -34
- data/lib/puppet/provider/file/windows.rb +100 -0
- data/lib/puppet/provider/group/windows_adsi.rb +2 -2
- data/lib/puppet/provider/user/windows_adsi.rb +19 -4
- data/lib/puppet/resource.rb +16 -0
- data/lib/puppet/resource/catalog.rb +1 -1
- data/lib/puppet/ssl/certificate.rb +2 -2
- data/lib/puppet/ssl/certificate_authority.rb +86 -10
- data/lib/puppet/ssl/certificate_authority/interface.rb +64 -19
- data/lib/puppet/ssl/certificate_factory.rb +112 -91
- data/lib/puppet/ssl/certificate_request.rb +88 -1
- data/lib/puppet/ssl/host.rb +20 -3
- data/lib/puppet/type/file.rb +15 -34
- data/lib/puppet/type/file/group.rb +11 -91
- data/lib/puppet/type/file/mode.rb +11 -41
- data/lib/puppet/type/file/owner.rb +18 -34
- data/lib/puppet/type/file/source.rb +22 -7
- data/lib/puppet/type/group.rb +4 -3
- data/lib/puppet/type/user.rb +4 -1
- data/lib/puppet/util.rb +59 -6
- data/lib/puppet/util/adsi.rb +11 -0
- data/lib/puppet/util/log.rb +4 -0
- data/lib/puppet/util/log/destinations.rb +7 -1
- data/lib/puppet/util/monkey_patches.rb +19 -0
- data/lib/puppet/util/network_device/config.rb +4 -5
- data/lib/puppet/util/settings.rb +5 -0
- data/lib/puppet/util/suidmanager.rb +0 -1
- data/lib/puppet/util/windows.rb +4 -0
- data/lib/puppet/util/windows/error.rb +16 -0
- data/lib/puppet/util/windows/security.rb +593 -0
- data/spec/integration/defaults_spec.rb +27 -0
- data/spec/integration/network/handler_spec.rb +1 -1
- data/spec/integration/type/file_spec.rb +382 -145
- data/spec/integration/util/windows/security_spec.rb +468 -0
- data/spec/shared_behaviours/file_serving.rb +4 -3
- data/spec/unit/application/agent_spec.rb +1 -0
- data/spec/unit/application/device_spec.rb +5 -0
- data/spec/unit/application/resource_spec.rb +62 -101
- data/spec/unit/configurer/downloader_spec.rb +2 -2
- data/spec/unit/configurer/plugin_handler_spec.rb +15 -8
- data/spec/unit/configurer_spec.rb +2 -2
- data/spec/unit/face/ca_spec.rb +34 -0
- data/spec/unit/face/certificate_spec.rb +168 -1
- data/spec/unit/file_serving/fileset_spec.rb +1 -1
- data/spec/unit/file_serving/indirection_hooks_spec.rb +1 -1
- data/spec/unit/file_serving/metadata_spec.rb +151 -107
- data/spec/unit/indirector/certificate_request/ca_spec.rb +0 -3
- data/spec/unit/indirector/direct_file_server_spec.rb +10 -9
- data/spec/unit/indirector/file_metadata/file_spec.rb +6 -4
- data/spec/unit/indirector/request_spec.rb +13 -3
- data/spec/unit/indirector/resource/active_record_spec.rb +4 -10
- data/spec/unit/indirector/resource/ral_spec.rb +6 -4
- data/spec/unit/indirector/rest_spec.rb +5 -6
- data/spec/unit/network/handler/ca_spec.rb +86 -0
- data/spec/unit/parser/collector_spec.rb +7 -7
- data/spec/unit/parser/scope_spec.rb +20 -0
- data/spec/unit/provider/file/posix_spec.rb +226 -0
- data/spec/unit/provider/file/windows_spec.rb +136 -0
- data/spec/unit/provider/group/windows_adsi_spec.rb +7 -2
- data/spec/unit/provider/user/windows_adsi_spec.rb +36 -3
- data/spec/unit/resource/catalog_spec.rb +20 -10
- data/spec/unit/resource_spec.rb +55 -8
- data/spec/unit/ssl/certificate_authority/interface_spec.rb +97 -54
- data/spec/unit/ssl/certificate_authority_spec.rb +133 -23
- data/spec/unit/ssl/certificate_factory_spec.rb +90 -70
- data/spec/unit/ssl/certificate_request_spec.rb +62 -1
- data/spec/unit/ssl/certificate_spec.rb +20 -14
- data/spec/unit/ssl/host_spec.rb +52 -6
- data/spec/unit/type/file/content_spec.rb +4 -4
- data/spec/unit/type/file/group_spec.rb +34 -96
- data/spec/unit/type/file/mode_spec.rb +88 -0
- data/spec/unit/type/file/owner_spec.rb +32 -123
- data/spec/unit/type/file/source_spec.rb +120 -41
- data/spec/unit/type/file_spec.rb +1033 -753
- data/spec/unit/type_spec.rb +19 -1
- data/spec/unit/util/adsi_spec.rb +19 -0
- data/spec/unit/util/log/destinations_spec.rb +75 -0
- data/spec/unit/util/log_spec.rb +15 -0
- data/spec/unit/util/network_device/config_spec.rb +7 -0
- data/spec/unit/util/settings_spec.rb +10 -0
- data/spec/unit/util_spec.rb +126 -13
- data/test/language/functions.rb +0 -1
- data/test/language/snippets.rb +0 -9
- data/test/lib/puppettest/exetest.rb +1 -1
- data/test/lib/puppettest/servertest.rb +0 -1
- data/test/rails/rails.rb +0 -1
- data/test/ral/type/filesources.rb +0 -60
- metadata +13 -33
- data/lib/puppet/network/client.rb +0 -174
- data/lib/puppet/network/client/ca.rb +0 -56
- data/lib/puppet/network/client/file.rb +0 -6
- data/lib/puppet/network/client/proxy.rb +0 -27
- data/lib/puppet/network/client/report.rb +0 -26
- data/lib/puppet/network/client/runner.rb +0 -10
- data/lib/puppet/network/client/status.rb +0 -4
- data/lib/puppet/network/http_server.rb +0 -3
- data/lib/puppet/network/http_server/mongrel.rb +0 -130
- data/lib/puppet/network/http_server/webrick.rb +0 -155
- data/lib/puppet/network/xmlrpc/client.rb +0 -211
- data/lib/puppet/provider/file/win32.rb +0 -72
- data/lib/puppet/sslcertificates.rb +0 -146
- data/lib/puppet/sslcertificates/ca.rb +0 -375
- data/lib/puppet/sslcertificates/certificate.rb +0 -255
- data/lib/puppet/sslcertificates/inventory.rb +0 -38
- data/lib/puppet/sslcertificates/support.rb +0 -146
- data/spec/integration/network/client_spec.rb +0 -18
- data/spec/unit/network/xmlrpc/client_spec.rb +0 -172
- data/spec/unit/sslcertificates/ca_spec.rb +0 -106
- data/test/certmgr/certmgr.rb +0 -308
- data/test/certmgr/inventory.rb +0 -69
- data/test/certmgr/support.rb +0 -105
- data/test/network/client/ca.rb +0 -69
- data/test/network/client/dipper.rb +0 -34
- data/test/network/handler/ca.rb +0 -273
- data/test/network/server/mongrel_test.rb +0 -99
- data/test/network/server/webrick.rb +0 -111
- data/test/network/xmlrpc/client.rb +0 -45
@@ -9,7 +9,7 @@ module Puppet
|
|
9
9
|
|
10
10
|
class InterfaceError < ArgumentError; end
|
11
11
|
|
12
|
-
attr_reader :method, :subjects, :digest
|
12
|
+
attr_reader :method, :subjects, :digest, :options
|
13
13
|
|
14
14
|
# Actually perform the work.
|
15
15
|
def apply(ca)
|
@@ -35,49 +35,94 @@ module Puppet
|
|
35
35
|
raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all
|
36
36
|
|
37
37
|
subjects.each do |host|
|
38
|
-
ca.generate(host)
|
38
|
+
ca.generate(host, options)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
def initialize(method, options)
|
43
43
|
self.method = method
|
44
|
-
self.subjects = options
|
45
|
-
@digest = options
|
44
|
+
self.subjects = options.delete(:to)
|
45
|
+
@digest = options.delete(:digest) || :MD5
|
46
|
+
@options = options
|
46
47
|
end
|
47
48
|
|
48
49
|
# List the hosts.
|
49
50
|
def list(ca)
|
50
|
-
unless subjects
|
51
|
-
puts ca.waiting?.join("\n")
|
52
|
-
return nil
|
53
|
-
end
|
54
|
-
|
55
51
|
signed = ca.list
|
56
52
|
requests = ca.waiting?
|
57
53
|
|
58
|
-
|
54
|
+
case subjects
|
55
|
+
when :all
|
59
56
|
hosts = [signed, requests].flatten
|
60
|
-
|
57
|
+
when :signed
|
61
58
|
hosts = signed.flatten
|
59
|
+
when nil
|
60
|
+
hosts = requests
|
62
61
|
else
|
63
62
|
hosts = subjects
|
64
63
|
end
|
65
64
|
|
65
|
+
certs = {:signed => {}, :invalid => {}, :request => {}}
|
66
|
+
|
67
|
+
return if hosts.empty?
|
68
|
+
|
66
69
|
hosts.uniq.sort.each do |host|
|
67
|
-
invalid = false
|
68
70
|
begin
|
69
71
|
ca.verify(host) unless requests.include?(host)
|
70
72
|
rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details
|
71
|
-
|
73
|
+
verify_error = details.to_s
|
72
74
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
|
76
|
+
if verify_error
|
77
|
+
cert = Puppet::SSL::Certificate.indirection.find(host)
|
78
|
+
certs[:invalid][host] = [cert, verify_error]
|
79
|
+
elsif signed.include?(host)
|
80
|
+
cert = Puppet::SSL::Certificate.indirection.find(host)
|
81
|
+
certs[:signed][host] = cert
|
77
82
|
else
|
78
|
-
|
83
|
+
req = Puppet::SSL::CertificateRequest.indirection.find(host)
|
84
|
+
certs[:request][host] = req
|
79
85
|
end
|
80
86
|
end
|
87
|
+
|
88
|
+
names = certs.values.map(&:keys).flatten
|
89
|
+
|
90
|
+
name_width = names.sort_by(&:length).last.length rescue 0
|
91
|
+
|
92
|
+
output = [:request, :signed, :invalid].map do |type|
|
93
|
+
next if certs[type].empty?
|
94
|
+
|
95
|
+
certs[type].map do |host,info|
|
96
|
+
format_host(ca, host, type, info, name_width)
|
97
|
+
end
|
98
|
+
end.flatten.compact.sort.join("\n")
|
99
|
+
|
100
|
+
puts output
|
101
|
+
end
|
102
|
+
|
103
|
+
def format_host(ca, host, type, info, width)
|
104
|
+
certish, verify_error = info
|
105
|
+
alt_names = case type
|
106
|
+
when :signed
|
107
|
+
certish.subject_alt_names
|
108
|
+
when :request
|
109
|
+
certish.subject_alt_names
|
110
|
+
else
|
111
|
+
[]
|
112
|
+
end
|
113
|
+
|
114
|
+
alt_names.delete(host)
|
115
|
+
|
116
|
+
alt_str = "(alt names: #{alt_names.join(', ')})" unless alt_names.empty?
|
117
|
+
|
118
|
+
glyph = {:signed => '+', :request => ' ', :invalid => '-'}[type]
|
119
|
+
|
120
|
+
name = host.ljust(width)
|
121
|
+
fingerprint = "(#{ca.fingerprint(host, @digest)})"
|
122
|
+
|
123
|
+
explanation = "(#{verify_error})" if verify_error
|
124
|
+
|
125
|
+
[glyph, name, fingerprint, alt_str, explanation].compact.join(' ')
|
81
126
|
end
|
82
127
|
|
83
128
|
# Set the method to apply.
|
@@ -113,7 +158,7 @@ module Puppet
|
|
113
158
|
list = subjects == :all ? ca.waiting? : subjects
|
114
159
|
raise InterfaceError, "No waiting certificate requests to sign" if list.empty?
|
115
160
|
list.each do |host|
|
116
|
-
ca.sign(host)
|
161
|
+
ca.sign(host, options[:allow_dns_alt_names])
|
117
162
|
end
|
118
163
|
end
|
119
164
|
|
@@ -2,7 +2,7 @@ require 'puppet/ssl'
|
|
2
2
|
|
3
3
|
# The tedious class that does all the manipulations to the
|
4
4
|
# certificate to correctly sign it. Yay.
|
5
|
-
|
5
|
+
module Puppet::SSL::CertificateFactory
|
6
6
|
# How we convert from various units to the required seconds.
|
7
7
|
UNITMAP = {
|
8
8
|
"y" => 365 * 24 * 60 * 60,
|
@@ -11,75 +11,84 @@ class Puppet::SSL::CertificateFactory
|
|
11
11
|
"s" => 1
|
12
12
|
}
|
13
13
|
|
14
|
-
|
14
|
+
def self.build(cert_type, csr, issuer, serial)
|
15
|
+
# Work out if we can even build the requested type of certificate.
|
16
|
+
build_extensions = "build_#{cert_type.to_s}_extensions"
|
17
|
+
respond_to?(build_extensions) or
|
18
|
+
raise ArgumentError, "#{cert_type.to_s} is an invalid certificate type!"
|
15
19
|
|
16
|
-
|
17
|
-
|
20
|
+
# set up the certificate, and start building the content.
|
21
|
+
cert = OpenSSL::X509::Certificate.new
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@cert = OpenSSL::X509::Certificate.new
|
23
|
+
cert.version = 2 # X509v3
|
24
|
+
cert.subject = csr.content.subject
|
25
|
+
cert.issuer = issuer.subject
|
26
|
+
cert.public_key = csr.content.public_key
|
27
|
+
cert.serial = serial
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
# Make the certificate valid as of yesterday, because so many people's
|
30
|
+
# clocks are out of sync. This gives one more day of validity than people
|
31
|
+
# might expect, but is better than making every person who has a messed up
|
32
|
+
# clock fail, and better than having every cert we generate expire a day
|
33
|
+
# before the user expected it to when they asked for "one year".
|
34
|
+
cert.not_before = Time.now - (60*60*24)
|
35
|
+
cert.not_after = Time.now + ttl
|
31
36
|
|
32
|
-
build_extensions
|
37
|
+
add_extensions_to(cert, csr, issuer, send(build_extensions))
|
33
38
|
|
34
|
-
|
35
|
-
|
36
|
-
@cert
|
39
|
+
return cert
|
37
40
|
end
|
38
41
|
|
39
42
|
private
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@ef = OpenSSL::X509::ExtensionFactory.new
|
45
|
-
|
46
|
-
@ef.subject_certificate = @cert
|
44
|
+
def self.add_extensions_to(cert, csr, issuer, extensions)
|
45
|
+
ef = OpenSSL::X509::ExtensionFactory.
|
46
|
+
new(cert, issuer.is_a?(OpenSSL::X509::Request) ? cert : issuer)
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
# Extract the requested extensions from the CSR.
|
49
|
+
requested_exts = csr.request_extensions.inject({}) do |hash, re|
|
50
|
+
hash[re["oid"]] = [re["value"], re["critical"]]
|
51
|
+
hash
|
52
52
|
end
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
54
|
+
# Produce our final set of extensions. We deliberately order these to
|
55
|
+
# build the way we want:
|
56
|
+
# 1. "safe" default values, like the comment, that no one cares about.
|
57
|
+
# 2. request extensions, from the CSR
|
58
|
+
# 3. extensions based on the type we are generating
|
59
|
+
# 4. overrides, which we always want to have in their form
|
60
|
+
#
|
61
|
+
# This ordering *is* security-critical, but we want to allow the user
|
62
|
+
# enough rope to shoot themselves in the foot, if they want to ignore our
|
63
|
+
# advice and externally approve a CSR that sets the basicConstraints.
|
64
|
+
#
|
65
|
+
# Swapping the order of 2 and 3 would ensure that you couldn't slip a
|
66
|
+
# certificate through where the CA constraint was true, though, if
|
67
|
+
# something went wrong up there. --daniel 2011-10-11
|
68
|
+
defaults = { "nsComment" => "Puppet Ruby/OpenSSL Internal Certificate" }
|
69
|
+
override = { "subjectKeyIdentifier" => "hash" }
|
70
|
+
|
71
|
+
exts = [defaults, requested_exts, extensions, override].
|
72
|
+
inject({}) {|ret, val| ret.merge(val) }
|
73
|
+
|
74
|
+
cert.extensions = exts.map do |oid, val|
|
75
|
+
val, crit = *val
|
76
|
+
val = val.join(', ') unless val.is_a? String
|
77
|
+
|
78
|
+
# Enforce the X509v3 rules about subjectAltName being critical:
|
79
|
+
# specifically, it SHOULD NOT be critical if we have a subject, which we
|
80
|
+
# always do. --daniel 2011-10-18
|
81
|
+
crit = false if oid == "subjectAltName"
|
82
|
+
|
83
|
+
# val can be either a string, or [string, critical], and this does the
|
84
|
+
# right thing regardless of what we get passed.
|
85
|
+
ef.create_ext(oid, val, crit)
|
65
86
|
end
|
66
|
-
|
67
|
-
@extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate")
|
68
|
-
@extensions << @ef.create_extension("basicConstraints", @basic_constraint, true)
|
69
|
-
@extensions << @ef.create_extension("subjectKeyIdentifier", "hash")
|
70
|
-
@extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage
|
71
|
-
@extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage
|
72
|
-
@extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty?
|
73
|
-
|
74
|
-
@cert.extensions = @extensions
|
75
|
-
|
76
|
-
# for some reason this _must_ be the last extension added
|
77
|
-
@extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca
|
78
87
|
end
|
79
88
|
|
80
89
|
# TTL for new certificates in seconds. If config param :ca_ttl is set,
|
81
90
|
# use that, otherwise use :ca_days for backwards compatibility
|
82
|
-
def ttl
|
91
|
+
def self.ttl
|
83
92
|
ttl = Puppet.settings[:ca_ttl]
|
84
93
|
|
85
94
|
return ttl unless ttl.is_a?(String)
|
@@ -89,57 +98,69 @@ class Puppet::SSL::CertificateFactory
|
|
89
98
|
$1.to_i * UNITMAP[$2]
|
90
99
|
end
|
91
100
|
|
92
|
-
def set_ttl
|
93
|
-
# Make the certificate valid as of yesterday, because
|
94
|
-
# so many people's clocks are out of sync.
|
95
|
-
from = Time.now - (60*60*24)
|
96
|
-
@cert.not_before = from
|
97
|
-
@cert.not_after = from + ttl
|
98
|
-
end
|
99
|
-
|
100
101
|
# Woot! We're a CA.
|
101
|
-
def
|
102
|
-
|
103
|
-
|
102
|
+
def self.build_ca_extensions
|
103
|
+
{
|
104
|
+
# This was accidentally omitted in the previous version of this code: an
|
105
|
+
# effort was made to add it last, but that actually managed to avoid
|
106
|
+
# adding it to the certificate at all.
|
107
|
+
#
|
108
|
+
# We have some sort of bug, which means that when we add it we get a
|
109
|
+
# complaint that the issuer keyid can't be fetched, which breaks all
|
110
|
+
# sorts of things in our test suite and, e.g., bootstrapping the CA.
|
111
|
+
#
|
112
|
+
# http://tools.ietf.org/html/rfc5280#section-4.2.1.1 says that, to be a
|
113
|
+
# conforming CA we MAY omit the field if we are self-signed, which I
|
114
|
+
# think gives us a pass in the specific case.
|
115
|
+
#
|
116
|
+
# It also notes that we MAY derive the ID from the subject and serial
|
117
|
+
# number of the issuer, or from the key ID, and we definitely have the
|
118
|
+
# former data, should we want to restore this...
|
119
|
+
#
|
120
|
+
# Anyway, preserving this bug means we don't risk breaking anything in
|
121
|
+
# the field, even though it would be nice to have. --daniel 2011-10-11
|
122
|
+
#
|
123
|
+
# "authorityKeyIdentifier" => "keyid:always,issuer:always",
|
124
|
+
"keyUsage" => [%w{cRLSign keyCertSign}, true],
|
125
|
+
"basicConstraints" => ["CA:TRUE", true],
|
126
|
+
}
|
104
127
|
end
|
105
128
|
|
106
129
|
# We're a terminal CA, probably not self-signed.
|
107
|
-
def
|
108
|
-
|
109
|
-
|
130
|
+
def self.build_terminalsubca_extensions
|
131
|
+
{
|
132
|
+
"keyUsage" => [%w{cRLSign keyCertSign}, true],
|
133
|
+
"basicConstraints" => ["CA:TRUE,pathlen:0", true],
|
134
|
+
}
|
110
135
|
end
|
111
136
|
|
112
137
|
# We're a normal server.
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
@subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
|
120
|
-
elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server
|
121
|
-
@subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias
|
122
|
-
@subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
|
123
|
-
@subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias
|
124
|
-
end
|
125
|
-
@key_usage = %w{digitalSignature keyEncipherment}
|
126
|
-
@ext_key_usage = %w{serverAuth clientAuth emailProtection}
|
138
|
+
def self.build_server_extensions
|
139
|
+
{
|
140
|
+
"keyUsage" => [%w{digitalSignature keyEncipherment}, true],
|
141
|
+
"extendedKeyUsage" => [%w{serverAuth clientAuth}, true],
|
142
|
+
"basicConstraints" => ["CA:FALSE", true],
|
143
|
+
}
|
127
144
|
end
|
128
145
|
|
129
146
|
# Um, no idea.
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
147
|
+
def self.build_ocsp_extensions
|
148
|
+
{
|
149
|
+
"keyUsage" => [%w{nonRepudiation digitalSignature}, true],
|
150
|
+
"extendedKeyUsage" => [%w{serverAuth OCSPSigning}, true],
|
151
|
+
"basicConstraints" => ["CA:FALSE", true],
|
152
|
+
}
|
134
153
|
end
|
135
154
|
|
136
155
|
# Normal client.
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
156
|
+
def self.build_client_extensions
|
157
|
+
{
|
158
|
+
"keyUsage" => [%w{nonRepudiation digitalSignature keyEncipherment}, true],
|
159
|
+
# We don't seem to use this, but that seems much more reasonable here...
|
160
|
+
"extendedKeyUsage" => [%w{clientAuth emailProtection}, true],
|
161
|
+
"basicConstraints" => ["CA:FALSE", true],
|
162
|
+
"nsCertType" => "client,email",
|
163
|
+
}
|
143
164
|
end
|
144
165
|
end
|
145
166
|
|
@@ -35,8 +35,12 @@ class Puppet::SSL::CertificateRequest < Puppet::SSL::Base
|
|
35
35
|
[:s]
|
36
36
|
end
|
37
37
|
|
38
|
+
def extension_factory
|
39
|
+
@ef ||= OpenSSL::X509::ExtensionFactory.new
|
40
|
+
end
|
41
|
+
|
38
42
|
# How to create a certificate request with our system defaults.
|
39
|
-
def generate(key)
|
43
|
+
def generate(key, options = {})
|
40
44
|
Puppet.info "Creating a new SSL certificate request for #{name}"
|
41
45
|
|
42
46
|
# Support either an actual SSL key, or a Puppet key.
|
@@ -51,6 +55,19 @@ class Puppet::SSL::CertificateRequest < Puppet::SSL::Base
|
|
51
55
|
csr.version = 0
|
52
56
|
csr.subject = OpenSSL::X509::Name.new([["CN", common_name]])
|
53
57
|
csr.public_key = key.public_key
|
58
|
+
|
59
|
+
if options[:dns_alt_names] then
|
60
|
+
names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name]
|
61
|
+
names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ")
|
62
|
+
names = extension_factory.create_extension("subjectAltName", names, false)
|
63
|
+
|
64
|
+
extReq = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([names])])
|
65
|
+
|
66
|
+
# We only support the standard request extensions. If you really need
|
67
|
+
# msExtReq support, let us know and we can restore them. --daniel 2011-10-10
|
68
|
+
csr.add_attribute(OpenSSL::X509::Attribute.new("extReq", extReq))
|
69
|
+
end
|
70
|
+
|
54
71
|
csr.sign(key, OpenSSL::Digest::MD5.new)
|
55
72
|
|
56
73
|
raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key)
|
@@ -59,4 +76,74 @@ class Puppet::SSL::CertificateRequest < Puppet::SSL::Base
|
|
59
76
|
Puppet.info "Certificate Request fingerprint (md5): #{fingerprint}"
|
60
77
|
@content
|
61
78
|
end
|
79
|
+
|
80
|
+
# Return the set of extensions requested on this CSR, in a form designed to
|
81
|
+
# be useful to Ruby: a hash. Which, not coincidentally, you can pass
|
82
|
+
# successfully to the OpenSSL constructor later, if you want.
|
83
|
+
def request_extensions
|
84
|
+
raise Puppet::Error, "CSR needs content to extract fields" unless @content
|
85
|
+
|
86
|
+
# Prefer the standard extReq, but accept the Microsoft specific version as
|
87
|
+
# a fallback, if the standard version isn't found.
|
88
|
+
ext = @content.attributes.find {|x| x.oid == "extReq" } or
|
89
|
+
@content.attributes.find {|x| x.oid == "msExtReq" }
|
90
|
+
return [] unless ext
|
91
|
+
|
92
|
+
# Assert the structure and extract the names into an array of arrays.
|
93
|
+
unless ext.value.is_a? OpenSSL::ASN1::Set
|
94
|
+
raise Puppet::Error, "In #{ext.oid}, expected Set but found #{ext.value.class}"
|
95
|
+
end
|
96
|
+
|
97
|
+
unless ext.value.value.is_a? Array
|
98
|
+
raise Puppet::Error, "In #{ext.oid}, expected Set[Array] but found #{ext.value.value.class}"
|
99
|
+
end
|
100
|
+
|
101
|
+
unless ext.value.value.length == 1
|
102
|
+
raise Puppet::Error, "In #{ext.oid}, expected Set[Array[...]], but found #{ext.value.value.length} items in the array"
|
103
|
+
end
|
104
|
+
|
105
|
+
san = ext.value.value.first
|
106
|
+
unless san.is_a? OpenSSL::ASN1::Sequence
|
107
|
+
raise Puppet::Error, "In #{ext.oid}, expected Set[Array[Sequence[...]]], but found #{san.class}"
|
108
|
+
end
|
109
|
+
san = san.value
|
110
|
+
|
111
|
+
# OK, now san should be the array of items, validate that...
|
112
|
+
index = -1
|
113
|
+
san.map do |name|
|
114
|
+
index += 1
|
115
|
+
|
116
|
+
unless name.is_a? OpenSSL::ASN1::Sequence
|
117
|
+
raise Puppet::Error, "In #{ext.oid}, expected request extension record #{index} to be a Sequence, but found #{name.class}"
|
118
|
+
end
|
119
|
+
name = name.value
|
120
|
+
|
121
|
+
# OK, turn that into an extension, to unpack the content. Lovely that
|
122
|
+
# we have to swap the order of arguments to the underlying method, or
|
123
|
+
# perhaps that the ASN.1 representation chose to pack them in a
|
124
|
+
# strange order where the optional component comes *earlier* than the
|
125
|
+
# fixed component in the sequence.
|
126
|
+
case name.length
|
127
|
+
when 2
|
128
|
+
ev = OpenSSL::X509::Extension.new(name[0].value, name[1].value)
|
129
|
+
{ "oid" => ev.oid, "value" => ev.value }
|
130
|
+
|
131
|
+
when 3
|
132
|
+
ev = OpenSSL::X509::Extension.new(name[0].value, name[2].value, name[1].value)
|
133
|
+
{ "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? }
|
134
|
+
|
135
|
+
else
|
136
|
+
raise Puppet::Error, "In #{ext.oid}, expected extension record #{index} to have two or three items, but found #{name.length}"
|
137
|
+
end
|
138
|
+
end.flatten
|
139
|
+
end
|
140
|
+
|
141
|
+
def subject_alt_names
|
142
|
+
@subject_alt_names ||= request_extensions.
|
143
|
+
select {|x| x["oid"] = "subjectAltName" }.
|
144
|
+
map {|x| x["value"].split(/\s*,\s*/) }.
|
145
|
+
flatten.
|
146
|
+
sort.
|
147
|
+
uniq
|
148
|
+
end
|
62
149
|
end
|