puppet 2.6.11 → 2.6.12
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 +33 -0
- data/conf/redhat/puppet.spec +7 -4
- data/lib/puppet.rb +1 -1
- data/lib/puppet/application/cert.rb +17 -3
- data/lib/puppet/application/kick.rb +0 -2
- data/lib/puppet/defaults.rb +52 -3
- 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/ssl/certificate.rb +6 -0
- data/lib/puppet/ssl/certificate_authority.rb +86 -11
- 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 +16 -3
- data/lib/puppet/type/file.rb +0 -1
- data/lib/puppet/util/command_line/puppetca +23 -2
- data/lib/puppet/util/monkey_patches.rb +69 -0
- data/lib/puppet/util/settings.rb +5 -0
- data/spec/integration/defaults_spec.rb +11 -0
- data/spec/integration/network/handler_spec.rb +1 -1
- data/spec/unit/configurer_spec.rb +2 -2
- data/spec/unit/network/handler/ca_spec.rb +86 -0
- data/spec/unit/ssl/certificate_authority/interface_spec.rb +92 -53
- 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 +31 -0
- data/spec/unit/ssl/host_spec.rb +44 -2
- data/spec/unit/util/settings_spec.rb +10 -0
- 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 +5 -34
- data/lib/puppet/network/client.rb +0 -179
- 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 -150
- data/lib/puppet/network/http_server/webrick.rb +0 -155
- data/lib/puppet/network/xmlrpc/client.rb +0 -211
- 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/monkey_patch.rb +0 -6
- data/lib/puppet/sslcertificates/support.rb +0 -146
- data/spec/integration/network/client_spec.rb +0 -19
- data/spec/unit/network/client_spec.rb +0 -45
- data/spec/unit/network/xmlrpc/client_spec.rb +0 -172
- data/spec/unit/sslcertificates/ca_spec.rb +0 -110
- 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 -128
- data/test/network/xmlrpc/client.rb +0 -45
@@ -1,211 +0,0 @@
|
|
1
|
-
require 'puppet/sslcertificates'
|
2
|
-
require 'puppet/network/http_pool'
|
3
|
-
require 'openssl'
|
4
|
-
require 'puppet/external/base64'
|
5
|
-
|
6
|
-
require 'xmlrpc/client'
|
7
|
-
require 'net/https'
|
8
|
-
require 'yaml'
|
9
|
-
|
10
|
-
module Puppet::Network
|
11
|
-
class ClientError < Puppet::Error; end
|
12
|
-
class XMLRPCClientError < Puppet::Error; end
|
13
|
-
class XMLRPCClient < ::XMLRPC::Client
|
14
|
-
|
15
|
-
attr_accessor :puppet_server, :puppet_port
|
16
|
-
@clients = {}
|
17
|
-
|
18
|
-
class << self
|
19
|
-
include Puppet::Util
|
20
|
-
include Puppet::Util::ClassGen
|
21
|
-
end
|
22
|
-
|
23
|
-
# Create a netclient for each handler
|
24
|
-
def self.mkclient(handler)
|
25
|
-
interface = handler.interface
|
26
|
-
namespace = interface.prefix
|
27
|
-
|
28
|
-
# Create a subclass for every client type. This is
|
29
|
-
# so that all of the methods are on their own class,
|
30
|
-
# so that their namespaces can define the same methods if
|
31
|
-
# they want.
|
32
|
-
constant = handler.name.to_s.capitalize
|
33
|
-
name = namespace.downcase
|
34
|
-
newclient = genclass(name, :hash => @clients, :constant => constant)
|
35
|
-
|
36
|
-
interface.methods.each { |ary|
|
37
|
-
method = ary[0]
|
38
|
-
newclient.send(:define_method,method) { |*args|
|
39
|
-
make_rpc_call(namespace, method, *args)
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
newclient
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.handler_class(handler)
|
47
|
-
@clients[handler] || self.mkclient(handler)
|
48
|
-
end
|
49
|
-
|
50
|
-
class ErrorHandler
|
51
|
-
def initialize(&block)
|
52
|
-
singleton_class.define_method(:execute, &block)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Use a class variable so all subclasses have access to it.
|
57
|
-
@@error_handlers = {}
|
58
|
-
|
59
|
-
def self.error_handler(exception)
|
60
|
-
if handler = @@error_handlers[exception.class]
|
61
|
-
return handler
|
62
|
-
else
|
63
|
-
return @@error_handlers[:default]
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.handle_error(*exceptions, &block)
|
68
|
-
handler = ErrorHandler.new(&block)
|
69
|
-
|
70
|
-
exceptions.each do |exception|
|
71
|
-
@@error_handlers[exception] = handler
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method|
|
76
|
-
if detail.message =~ /bad write retry/
|
77
|
-
Puppet.warning "Transient SSL write error; restarting connection and retrying"
|
78
|
-
client.recycle_connection
|
79
|
-
return :retry
|
80
|
-
end
|
81
|
-
["certificate verify failed", "hostname was not match", "hostname not match"].each do |str|
|
82
|
-
Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str)
|
83
|
-
end
|
84
|
-
raise XMLRPCClientError, "Certificates were not trusted: #{detail}"
|
85
|
-
end
|
86
|
-
|
87
|
-
handle_error(:default) do |client, detail, namespace, method|
|
88
|
-
if detail.message.to_s =~ /^Wrong size\. Was \d+, should be \d+$/
|
89
|
-
Puppet.warning "XMLRPC returned wrong size. Retrying."
|
90
|
-
return :retry
|
91
|
-
end
|
92
|
-
Puppet.err "Could not call #{namespace}.#{method}: #{detail.inspect}"
|
93
|
-
error = XMLRPCClientError.new(detail.to_s)
|
94
|
-
error.set_backtrace detail.backtrace
|
95
|
-
raise error
|
96
|
-
end
|
97
|
-
|
98
|
-
handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method|
|
99
|
-
if detail.message =~ /bad write retry/
|
100
|
-
Puppet.warning "Transient SSL write error; restarting connection and retrying"
|
101
|
-
client.recycle_connection
|
102
|
-
return :retry
|
103
|
-
end
|
104
|
-
["certificate verify failed", "hostname was not match", "hostname not match"].each do |str|
|
105
|
-
Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str)
|
106
|
-
end
|
107
|
-
raise XMLRPCClientError, "Certificates were not trusted: #{detail}"
|
108
|
-
end
|
109
|
-
|
110
|
-
handle_error(::XMLRPC::FaultException) do |client, detail, namespace, method|
|
111
|
-
raise XMLRPCClientError, detail.faultString
|
112
|
-
end
|
113
|
-
|
114
|
-
handle_error(Errno::ECONNREFUSED) do |client, detail, namespace, method|
|
115
|
-
msg = "Could not connect to #{client.host} on port #{client.port}"
|
116
|
-
raise XMLRPCClientError, msg
|
117
|
-
end
|
118
|
-
|
119
|
-
handle_error(SocketError) do |client, detail, namespace, method|
|
120
|
-
Puppet.err "Could not find server #{@host}: #{detail}"
|
121
|
-
error = XMLRPCClientError.new("Could not find server #{client.host}")
|
122
|
-
error.set_backtrace detail.backtrace
|
123
|
-
raise error
|
124
|
-
end
|
125
|
-
|
126
|
-
handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method|
|
127
|
-
Puppet.info "Other end went away; restarting connection and retrying"
|
128
|
-
client.recycle_connection
|
129
|
-
return :retry
|
130
|
-
end
|
131
|
-
|
132
|
-
handle_error(Timeout::Error) do |client, detail, namespace, method|
|
133
|
-
Puppet.err "Connection timeout calling #{namespace}.#{method}: #{detail}"
|
134
|
-
error = XMLRPCClientError.new("Connection Timeout")
|
135
|
-
error.set_backtrace(detail.backtrace)
|
136
|
-
raise error
|
137
|
-
end
|
138
|
-
|
139
|
-
def make_rpc_call(namespace, method, *args)
|
140
|
-
Puppet.debug "Calling #{namespace}.#{method}"
|
141
|
-
begin
|
142
|
-
call("#{namespace}.#{method}",*args)
|
143
|
-
rescue SystemExit,NoMemoryError
|
144
|
-
raise
|
145
|
-
rescue Exception => detail
|
146
|
-
retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry
|
147
|
-
end
|
148
|
-
ensure
|
149
|
-
http.finish if http.started?
|
150
|
-
end
|
151
|
-
|
152
|
-
def http
|
153
|
-
@http ||= Puppet::Network::HttpPool.http_instance(host, port, true)
|
154
|
-
end
|
155
|
-
|
156
|
-
attr_reader :host, :port
|
157
|
-
|
158
|
-
def initialize(hash = {})
|
159
|
-
hash[:Path] ||= "/RPC2"
|
160
|
-
hash[:Server] ||= Puppet[:server]
|
161
|
-
hash[:Port] ||= Puppet[:masterport]
|
162
|
-
hash[:HTTPProxyHost] ||= Puppet[:http_proxy_host]
|
163
|
-
hash[:HTTPProxyPort] ||= Puppet[:http_proxy_port]
|
164
|
-
|
165
|
-
if "none" == hash[:HTTPProxyHost]
|
166
|
-
hash[:HTTPProxyHost] = nil
|
167
|
-
hash[:HTTPProxyPort] = nil
|
168
|
-
end
|
169
|
-
|
170
|
-
|
171
|
-
super(
|
172
|
-
|
173
|
-
hash[:Server],
|
174
|
-
hash[:Path],
|
175
|
-
hash[:Port],
|
176
|
-
hash[:HTTPProxyHost],
|
177
|
-
hash[:HTTPProxyPort],
|
178
|
-
|
179
|
-
nil, # user
|
180
|
-
nil, # password
|
181
|
-
true, # use_ssl
|
182
|
-
Puppet[:configtimeout] # use configured timeout (#1176)
|
183
|
-
)
|
184
|
-
@http = Puppet::Network::HttpPool.http_instance(@host, @port)
|
185
|
-
end
|
186
|
-
|
187
|
-
# Get rid of our existing connection, replacing it with a new one.
|
188
|
-
# This should only happen if we lose our connection somehow (e.g., an EPIPE)
|
189
|
-
# or we've just downloaded certs and we need to create new http instances
|
190
|
-
# with the certs added.
|
191
|
-
def recycle_connection
|
192
|
-
http.finish if http.started?
|
193
|
-
@http = nil
|
194
|
-
self.http # force a new one
|
195
|
-
end
|
196
|
-
|
197
|
-
def start
|
198
|
-
@http.start unless @http.started?
|
199
|
-
rescue => detail
|
200
|
-
Puppet.err "Could not connect to server: #{detail}"
|
201
|
-
end
|
202
|
-
|
203
|
-
def local
|
204
|
-
false
|
205
|
-
end
|
206
|
-
|
207
|
-
def local?
|
208
|
-
false
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
@@ -1,146 +0,0 @@
|
|
1
|
-
# The library for manipulating SSL certs.
|
2
|
-
|
3
|
-
require 'puppet'
|
4
|
-
|
5
|
-
raise Puppet::Error, "You must have the Ruby openssl library installed" unless Puppet.features.openssl?
|
6
|
-
|
7
|
-
module Puppet::SSLCertificates
|
8
|
-
#def self.mkcert(type, name, dnsnames, ttl, issuercert, issuername, serial, publickey)
|
9
|
-
def self.mkcert(hash)
|
10
|
-
[:type, :name, :ttl, :issuer, :serial, :publickey].each { |param|
|
11
|
-
raise ArgumentError, "mkcert called without #{param}" unless hash.include?(param)
|
12
|
-
}
|
13
|
-
|
14
|
-
cert = OpenSSL::X509::Certificate.new
|
15
|
-
# Make the certificate valid as of yesterday, because
|
16
|
-
# so many people's clocks are out of sync.
|
17
|
-
from = Time.now - (60*60*24)
|
18
|
-
|
19
|
-
cert.subject = hash[:name]
|
20
|
-
if hash[:issuer]
|
21
|
-
cert.issuer = hash[:issuer].subject
|
22
|
-
else
|
23
|
-
# we're a self-signed cert
|
24
|
-
cert.issuer = hash[:name]
|
25
|
-
end
|
26
|
-
cert.not_before = from
|
27
|
-
cert.not_after = from + hash[:ttl]
|
28
|
-
cert.version = 2 # X509v3
|
29
|
-
|
30
|
-
cert.public_key = hash[:publickey]
|
31
|
-
cert.serial = hash[:serial]
|
32
|
-
|
33
|
-
basic_constraint = nil
|
34
|
-
key_usage = nil
|
35
|
-
ext_key_usage = nil
|
36
|
-
subject_alt_name = []
|
37
|
-
|
38
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
39
|
-
|
40
|
-
ef.subject_certificate = cert
|
41
|
-
|
42
|
-
if hash[:issuer]
|
43
|
-
ef.issuer_certificate = hash[:issuer]
|
44
|
-
else
|
45
|
-
ef.issuer_certificate = cert
|
46
|
-
end
|
47
|
-
|
48
|
-
ex = []
|
49
|
-
case hash[:type]
|
50
|
-
when :ca
|
51
|
-
basic_constraint = "CA:TRUE"
|
52
|
-
key_usage = %w{cRLSign keyCertSign}
|
53
|
-
when :terminalsubca
|
54
|
-
basic_constraint = "CA:TRUE,pathlen:0"
|
55
|
-
key_usage = %w{cRLSign keyCertSign}
|
56
|
-
when :server
|
57
|
-
basic_constraint = "CA:FALSE"
|
58
|
-
dnsnames = Puppet[:certdnsnames]
|
59
|
-
name = hash[:name].to_s.sub(%r{/CN=},'')
|
60
|
-
if dnsnames != ""
|
61
|
-
dnsnames.split(':').each { |d| subject_alt_name << 'DNS:' + d }
|
62
|
-
subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
|
63
|
-
elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server
|
64
|
-
subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias
|
65
|
-
subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
|
66
|
-
subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias
|
67
|
-
end
|
68
|
-
key_usage = %w{digitalSignature keyEncipherment}
|
69
|
-
ext_key_usage = %w{serverAuth clientAuth emailProtection}
|
70
|
-
when :ocsp
|
71
|
-
basic_constraint = "CA:FALSE"
|
72
|
-
key_usage = %w{nonRepudiation digitalSignature}
|
73
|
-
ext_key_usage = %w{serverAuth OCSPSigning}
|
74
|
-
when :client
|
75
|
-
basic_constraint = "CA:FALSE"
|
76
|
-
key_usage = %w{nonRepudiation digitalSignature keyEncipherment}
|
77
|
-
ext_key_usage = %w{clientAuth emailProtection}
|
78
|
-
ex << ef.create_extension("nsCertType", "client,email")
|
79
|
-
else
|
80
|
-
raise Puppet::Error, "unknown cert type '#{hash[:type]}'"
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
ex << ef.create_extension(
|
85
|
-
"nsComment",
|
86
|
-
|
87
|
-
"Puppet Ruby/OpenSSL Generated Certificate")
|
88
|
-
ex << ef.create_extension("basicConstraints", basic_constraint, true)
|
89
|
-
ex << ef.create_extension("subjectKeyIdentifier", "hash")
|
90
|
-
|
91
|
-
ex << ef.create_extension("keyUsage", key_usage.join(",")) if key_usage
|
92
|
-
ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(",")) if ext_key_usage
|
93
|
-
ex << ef.create_extension("subjectAltName", subject_alt_name.join(",")) if ! subject_alt_name.empty?
|
94
|
-
|
95
|
-
#if @ca_config[:cdp_location] then
|
96
|
-
# ex << ef.create_extension("crlDistributionPoints",
|
97
|
-
# @ca_config[:cdp_location])
|
98
|
-
#end
|
99
|
-
|
100
|
-
#if @ca_config[:ocsp_location] then
|
101
|
-
# ex << ef.create_extension("authorityInfoAccess",
|
102
|
-
# "OCSP;" << @ca_config[:ocsp_location])
|
103
|
-
#end
|
104
|
-
cert.extensions = ex
|
105
|
-
|
106
|
-
# for some reason this _must_ be the last extension added
|
107
|
-
ex << ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if hash[:type] == :ca
|
108
|
-
|
109
|
-
cert
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.mkhash(dir, cert, certfile)
|
113
|
-
# Make sure the hash is zero-padded to 8 chars
|
114
|
-
hash = "%08x" % cert.issuer.hash
|
115
|
-
hashpath = nil
|
116
|
-
10.times { |i|
|
117
|
-
path = File.join(dir, "#{hash}.#{i}")
|
118
|
-
if FileTest.exists?(path)
|
119
|
-
if FileTest.symlink?(path)
|
120
|
-
dest = File.readlink(path)
|
121
|
-
if dest == certfile
|
122
|
-
# the correct link already exists
|
123
|
-
hashpath = path
|
124
|
-
break
|
125
|
-
else
|
126
|
-
next
|
127
|
-
end
|
128
|
-
else
|
129
|
-
next
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
File.symlink(certfile, path)
|
134
|
-
|
135
|
-
hashpath = path
|
136
|
-
break
|
137
|
-
}
|
138
|
-
|
139
|
-
|
140
|
-
hashpath
|
141
|
-
end
|
142
|
-
require 'puppet/sslcertificates/certificate'
|
143
|
-
require 'puppet/sslcertificates/inventory'
|
144
|
-
require 'puppet/sslcertificates/ca'
|
145
|
-
end
|
146
|
-
|
@@ -1,375 +0,0 @@
|
|
1
|
-
require 'sync'
|
2
|
-
|
3
|
-
class Puppet::SSLCertificates::CA
|
4
|
-
include Puppet::Util::Warnings
|
5
|
-
|
6
|
-
Certificate = Puppet::SSLCertificates::Certificate
|
7
|
-
attr_accessor :keyfile, :file, :config, :dir, :cert, :crl
|
8
|
-
|
9
|
-
def certfile
|
10
|
-
@config[:cacert]
|
11
|
-
end
|
12
|
-
|
13
|
-
# Remove all traces of a given host. This is kind of hackish, but, eh.
|
14
|
-
def clean(host)
|
15
|
-
host = host.downcase
|
16
|
-
[:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir].each do |name|
|
17
|
-
dir = Puppet[name]
|
18
|
-
|
19
|
-
file = File.join(dir, host + ".pem")
|
20
|
-
|
21
|
-
if FileTest.exists?(file)
|
22
|
-
begin
|
23
|
-
if Puppet[:name] == "cert"
|
24
|
-
puts "Removing #{file}"
|
25
|
-
else
|
26
|
-
Puppet.info "Removing #{file}"
|
27
|
-
end
|
28
|
-
File.unlink(file)
|
29
|
-
rescue => detail
|
30
|
-
raise Puppet::Error, "Could not delete #{file}: #{detail}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def host2csrfile(hostname)
|
38
|
-
File.join(Puppet[:csrdir], [hostname.downcase, "pem"].join("."))
|
39
|
-
end
|
40
|
-
|
41
|
-
# this stores signed certs in a directory unrelated to
|
42
|
-
# normal client certs
|
43
|
-
def host2certfile(hostname)
|
44
|
-
File.join(Puppet[:signeddir], [hostname.downcase, "pem"].join("."))
|
45
|
-
end
|
46
|
-
|
47
|
-
# Turn our hostname into a Name object
|
48
|
-
def thing2name(thing)
|
49
|
-
thing.subject.to_a.find { |ary|
|
50
|
-
ary[0] == "CN"
|
51
|
-
}[1]
|
52
|
-
end
|
53
|
-
|
54
|
-
def initialize(hash = {})
|
55
|
-
Puppet.settings.use(:main, :ca, :ssl)
|
56
|
-
self.setconfig(hash)
|
57
|
-
|
58
|
-
if Puppet[:capass]
|
59
|
-
if FileTest.exists?(Puppet[:capass])
|
60
|
-
#puts "Reading #{Puppet[:capass]}"
|
61
|
-
#system "ls -al #{Puppet[:capass]}"
|
62
|
-
#File.read Puppet[:capass]
|
63
|
-
@config[:password] = self.getpass
|
64
|
-
else
|
65
|
-
# Don't create a password if the cert already exists
|
66
|
-
@config[:password] = self.genpass unless FileTest.exists?(@config[:cacert])
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
self.getcert
|
71
|
-
init_crl
|
72
|
-
unless FileTest.exists?(@config[:serial])
|
73
|
-
Puppet.settings.write(:serial) do |f|
|
74
|
-
f << "%04X" % 1
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# Generate a new password for the CA.
|
80
|
-
def genpass
|
81
|
-
pass = ""
|
82
|
-
20.times { pass += (rand(74) + 48).chr }
|
83
|
-
|
84
|
-
begin
|
85
|
-
Puppet.settings.write(:capass) { |f| f.print pass }
|
86
|
-
rescue Errno::EACCES => detail
|
87
|
-
raise Puppet::Error, detail.to_s
|
88
|
-
end
|
89
|
-
pass
|
90
|
-
end
|
91
|
-
|
92
|
-
# Get the CA password.
|
93
|
-
def getpass
|
94
|
-
if @config[:capass] and File.readable?(@config[:capass])
|
95
|
-
return File.read(@config[:capass])
|
96
|
-
else
|
97
|
-
raise Puppet::Error, "Could not decrypt CA key with password: #{detail}"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Get the CA cert.
|
102
|
-
def getcert
|
103
|
-
if FileTest.exists?(@config[:cacert])
|
104
|
-
@cert = OpenSSL::X509::Certificate.new(
|
105
|
-
File.read(@config[:cacert])
|
106
|
-
)
|
107
|
-
else
|
108
|
-
self.mkrootcert
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Retrieve a client's CSR.
|
113
|
-
def getclientcsr(host)
|
114
|
-
csrfile = host2csrfile(host)
|
115
|
-
return nil unless File.exists?(csrfile)
|
116
|
-
|
117
|
-
OpenSSL::X509::Request.new(File.read(csrfile))
|
118
|
-
end
|
119
|
-
|
120
|
-
# Retrieve a client's certificate.
|
121
|
-
def getclientcert(host)
|
122
|
-
certfile = host2certfile(host)
|
123
|
-
return [nil, nil] unless File.exists?(certfile)
|
124
|
-
|
125
|
-
[OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
|
126
|
-
end
|
127
|
-
|
128
|
-
# List certificates waiting to be signed. This returns a list of hostnames, not actual
|
129
|
-
# files -- the names can be converted to full paths with host2csrfile.
|
130
|
-
def list(dummy_argument=:work_arround_for_ruby_GC_bug)
|
131
|
-
return Dir.entries(Puppet[:csrdir]).find_all { |file|
|
132
|
-
file =~ /\.pem$/
|
133
|
-
}.collect { |file|
|
134
|
-
file.sub(/\.pem$/, '')
|
135
|
-
}
|
136
|
-
end
|
137
|
-
|
138
|
-
# List signed certificates. This returns a list of hostnames, not actual
|
139
|
-
# files -- the names can be converted to full paths with host2csrfile.
|
140
|
-
def list_signed(dummy_argument=:work_arround_for_ruby_GC_bug)
|
141
|
-
return Dir.entries(Puppet[:signeddir]).find_all { |file|
|
142
|
-
file =~ /\.pem$/
|
143
|
-
}.collect { |file|
|
144
|
-
file.sub(/\.pem$/, '')
|
145
|
-
}
|
146
|
-
end
|
147
|
-
|
148
|
-
# Create the root certificate.
|
149
|
-
def mkrootcert
|
150
|
-
# Make the root cert's name "Puppet CA: " plus the FQDN of the host running the CA.
|
151
|
-
name = "Puppet CA: #{Facter["hostname"].value}"
|
152
|
-
if domain = Facter["domain"].value
|
153
|
-
name += ".#{domain}"
|
154
|
-
end
|
155
|
-
|
156
|
-
cert = Certificate.new(
|
157
|
-
:name => name,
|
158
|
-
:cert => @config[:cacert],
|
159
|
-
:encrypt => @config[:capass],
|
160
|
-
:key => @config[:cakey],
|
161
|
-
:selfsign => true,
|
162
|
-
:ttl => ttl,
|
163
|
-
:type => :ca
|
164
|
-
)
|
165
|
-
|
166
|
-
# This creates the cakey file
|
167
|
-
Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do
|
168
|
-
@cert = cert.mkselfsigned
|
169
|
-
end
|
170
|
-
Puppet.settings.write(:cacert) do |f|
|
171
|
-
f.puts @cert.to_pem
|
172
|
-
end
|
173
|
-
Puppet.settings.write(:capub) do |f|
|
174
|
-
f.puts @cert.public_key
|
175
|
-
end
|
176
|
-
cert
|
177
|
-
end
|
178
|
-
|
179
|
-
def removeclientcsr(host)
|
180
|
-
csrfile = host2csrfile(host)
|
181
|
-
raise Puppet::Error, "No certificate request for #{host}" unless File.exists?(csrfile)
|
182
|
-
|
183
|
-
File.unlink(csrfile)
|
184
|
-
end
|
185
|
-
|
186
|
-
# Revoke the certificate with serial number SERIAL issued by this
|
187
|
-
# CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons
|
188
|
-
def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
|
189
|
-
time = Time.now
|
190
|
-
revoked = OpenSSL::X509::Revoked.new
|
191
|
-
revoked.serial = serial
|
192
|
-
revoked.time = time
|
193
|
-
enum = OpenSSL::ASN1::Enumerated(reason)
|
194
|
-
ext = OpenSSL::X509::Extension.new("CRLReason", enum)
|
195
|
-
revoked.add_extension(ext)
|
196
|
-
@crl.add_revoked(revoked)
|
197
|
-
store_crl
|
198
|
-
end
|
199
|
-
|
200
|
-
# Take the Puppet config and store it locally.
|
201
|
-
def setconfig(hash)
|
202
|
-
@config = {}
|
203
|
-
Puppet.settings.params("ca").each { |param|
|
204
|
-
param = param.intern if param.is_a? String
|
205
|
-
if hash.include?(param)
|
206
|
-
@config[param] = hash[param]
|
207
|
-
Puppet[param] = hash[param]
|
208
|
-
hash.delete(param)
|
209
|
-
else
|
210
|
-
@config[param] = Puppet[param]
|
211
|
-
end
|
212
|
-
}
|
213
|
-
|
214
|
-
if hash.include?(:password)
|
215
|
-
@config[:password] = hash[:password]
|
216
|
-
hash.delete(:password)
|
217
|
-
end
|
218
|
-
|
219
|
-
raise ArgumentError, "Unknown parameters #{hash.keys.join(",")}" if hash.length > 0
|
220
|
-
|
221
|
-
[:cadir, :csrdir, :signeddir].each { |dir|
|
222
|
-
raise Puppet::DevError, "#{dir} is undefined" unless @config[dir]
|
223
|
-
}
|
224
|
-
end
|
225
|
-
|
226
|
-
# Sign a given certificate request.
|
227
|
-
def sign(csr)
|
228
|
-
unless csr.is_a?(OpenSSL::X509::Request)
|
229
|
-
raise Puppet::Error,
|
230
|
-
"CA#sign only accepts OpenSSL::X509::Request objects, not #{csr.class}"
|
231
|
-
end
|
232
|
-
|
233
|
-
raise Puppet::Error, "CSR sign verification failed" unless csr.verify(csr.public_key)
|
234
|
-
|
235
|
-
serial = nil
|
236
|
-
Puppet.settings.readwritelock(:serial) { |f|
|
237
|
-
serial = File.read(@config[:serial]).chomp.hex
|
238
|
-
# increment the serial
|
239
|
-
f << "%04X" % (serial + 1)
|
240
|
-
}
|
241
|
-
|
242
|
-
newcert = Puppet::SSLCertificates.mkcert(
|
243
|
-
:type => :server,
|
244
|
-
:name => csr.subject,
|
245
|
-
:ttl => ttl,
|
246
|
-
:issuer => @cert,
|
247
|
-
:serial => serial,
|
248
|
-
:publickey => csr.public_key
|
249
|
-
)
|
250
|
-
|
251
|
-
sign_with_key(newcert)
|
252
|
-
|
253
|
-
self.storeclientcert(newcert)
|
254
|
-
|
255
|
-
[newcert, @cert]
|
256
|
-
end
|
257
|
-
|
258
|
-
# Store the client's CSR for later signing. This is called from
|
259
|
-
# server/ca.rb, and the CSRs are deleted once the certificate is actually
|
260
|
-
# signed.
|
261
|
-
def storeclientcsr(csr)
|
262
|
-
host = thing2name(csr)
|
263
|
-
|
264
|
-
csrfile = host2csrfile(host)
|
265
|
-
raise Puppet::Error, "Certificate request for #{host} already exists" if File.exists?(csrfile)
|
266
|
-
|
267
|
-
Puppet.settings.writesub(:csrdir, csrfile) do |f|
|
268
|
-
f.print csr.to_pem
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
# Store the certificate that we generate.
|
273
|
-
def storeclientcert(cert)
|
274
|
-
host = thing2name(cert)
|
275
|
-
|
276
|
-
certfile = host2certfile(host)
|
277
|
-
Puppet.notice "Overwriting signed certificate #{certfile} for #{host}" if File.exists?(certfile)
|
278
|
-
|
279
|
-
Puppet::SSLCertificates::Inventory::add(cert)
|
280
|
-
Puppet.settings.writesub(:signeddir, certfile) do |f|
|
281
|
-
f.print cert.to_pem
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
# TTL for new certificates in seconds. If config param :ca_ttl is set,
|
286
|
-
# use that, otherwise use :ca_days for backwards compatibility
|
287
|
-
def ttl
|
288
|
-
days = @config[:ca_days]
|
289
|
-
if days && days.size > 0
|
290
|
-
warnonce "Parameter ca_ttl is not set. Using depecated ca_days instead."
|
291
|
-
return @config[:ca_days] * 24 * 60 * 60
|
292
|
-
else
|
293
|
-
ttl = @config[:ca_ttl]
|
294
|
-
if ttl.is_a?(String)
|
295
|
-
unless ttl =~ /^(\d+)(y|d|h|s)$/
|
296
|
-
raise ArgumentError, "Invalid ca_ttl #{ttl}"
|
297
|
-
end
|
298
|
-
case $2
|
299
|
-
when 'y'
|
300
|
-
unit = 365 * 24 * 60 * 60
|
301
|
-
when 'd'
|
302
|
-
unit = 24 * 60 * 60
|
303
|
-
when 'h'
|
304
|
-
unit = 60 * 60
|
305
|
-
when 's'
|
306
|
-
unit = 1
|
307
|
-
else
|
308
|
-
raise ArgumentError, "Invalid unit for ca_ttl #{ttl}"
|
309
|
-
end
|
310
|
-
return $1.to_i * unit
|
311
|
-
else
|
312
|
-
return ttl
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
private
|
318
|
-
def init_crl
|
319
|
-
if FileTest.exists?(@config[:cacrl])
|
320
|
-
@crl = OpenSSL::X509::CRL.new(
|
321
|
-
File.read(@config[:cacrl])
|
322
|
-
)
|
323
|
-
else
|
324
|
-
# Create new CRL
|
325
|
-
@crl = OpenSSL::X509::CRL.new
|
326
|
-
@crl.issuer = @cert.subject
|
327
|
-
@crl.version = 1
|
328
|
-
store_crl
|
329
|
-
@crl
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
def store_crl
|
334
|
-
# Increment the crlNumber
|
335
|
-
e = @crl.extensions.find { |e| e.oid == 'crlNumber' }
|
336
|
-
ext = @crl.extensions.reject { |e| e.oid == 'crlNumber' }
|
337
|
-
crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0)
|
338
|
-
ext << OpenSSL::X509::Extension.new("crlNumber", crlNum)
|
339
|
-
@crl.extensions = ext
|
340
|
-
|
341
|
-
# Set last/next update
|
342
|
-
now = Time.now
|
343
|
-
@crl.last_update = now
|
344
|
-
# Keep CRL valid for 5 years
|
345
|
-
@crl.next_update = now + 5 * 365*24*60*60
|
346
|
-
|
347
|
-
sign_with_key(@crl)
|
348
|
-
Puppet.settings.write(:cacrl) do |f|
|
349
|
-
f.puts @crl.to_pem
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def sign_with_key(signable, digest = OpenSSL::Digest::SHA1.new)
|
354
|
-
cakey = nil
|
355
|
-
if @config[:password]
|
356
|
-
begin
|
357
|
-
cakey = OpenSSL::PKey::RSA.new(
|
358
|
-
File.read(@config[:cakey]), @config[:password]
|
359
|
-
)
|
360
|
-
rescue
|
361
|
-
raise Puppet::Error,
|
362
|
-
"Decrypt of CA private key with password stored in @config[:capass] not possible"
|
363
|
-
end
|
364
|
-
else
|
365
|
-
cakey = OpenSSL::PKey::RSA.new(
|
366
|
-
File.read(@config[:cakey])
|
367
|
-
)
|
368
|
-
end
|
369
|
-
|
370
|
-
raise Puppet::Error, "CA Certificate is invalid" unless @cert.check_private_key(cakey)
|
371
|
-
|
372
|
-
signable.sign(cakey, digest)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|