1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
require 'puppet/sslcertificates'
# A module to handle reading of certificates.
module Puppet::SSLCertificates::Support
class MissingCertificate < Puppet::Error; end
class InvalidCertificate < Puppet::Error; end
attr_reader :cacert
# Some metaprogramming to create methods for retrieving and creating keys.
# This probably isn't fewer lines than defining each separately...
def self.keytype(name, options, &block)
var = "@#{name}"
maker = "mk_#{name}"
reader = "read_#{name}"
unless param = options[:param]
raise ArgumentError, "You must specify the parameter for the key"
end
unless klass = options[:class]
raise ArgumentError, "You must specify the class for the key"
end
# Define the method that creates it.
define_method(maker, &block)
# Define the reading method.
define_method(reader) do
return nil unless FileTest.exists?(Puppet[param]) or rename_files_with_uppercase(Puppet[param])
begin
instance_variable_set(var, klass.new(File.read(Puppet[param])))
rescue => detail
raise InvalidCertificate, "Could not read #{param}: #{detail}"
end
end
# Define the overall method, which just calls the reader and maker
# as appropriate.
define_method(name) do
unless cert = instance_variable_get(var)
unless cert = send(reader)
cert = send(maker)
Puppet.settings.write(param) { |f| f.puts cert.to_pem }
end
instance_variable_set(var, cert)
end
cert
end
end
# The key pair.
keytype :key, :param => :hostprivkey, :class => OpenSSL::PKey::RSA do
Puppet.info "Creating a new SSL key at #{Puppet[:hostprivkey]}"
key = OpenSSL::PKey::RSA.new(Puppet[:keylength])
# Our key meta programming can only handle one file, so we have
# to separately write out the public key.
Puppet.settings.write(:hostpubkey) do |f|
f.print key.public_key.to_pem
end
return key
end
# Our certificate request
keytype :csr, :param => :hostcsr, :class => OpenSSL::X509::Request do
Puppet.info "Creating a new certificate request for #{Puppet[:certname]}"
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = OpenSSL::X509::Name.new([["CN", Puppet[:certname]]])
csr.public_key = key.public_key
csr.sign(key, OpenSSL::Digest::MD5.new)
return csr
end
keytype :cert, :param => :hostcert, :class => OpenSSL::X509::Certificate do
raise MissingCertificate, "No host certificate"
end
keytype :ca_cert, :param => :localcacert, :class => OpenSSL::X509::Certificate do
raise MissingCertificate, "No CA certificate"
end
# Request a certificate from the remote system. This does all of the work
# of creating the cert request, contacting the remote system, and
# storing the cert locally.
def requestcert
begin
cert, cacert = caclient.getcert(@csr.to_pem)
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::Error.new("Certificate retrieval failed: #{detail}")
end
if cert.nil? or cert == ""
return nil
end
Puppet.settings.write(:hostcert) do |f| f.print cert end
Puppet.settings.write(:localcacert) do |f| f.print cacert end
#File.open(@certfile, "w", 0644) { |f| f.print cert }
#File.open(@cacertfile, "w", 0644) { |f| f.print cacert }
begin
@cert = OpenSSL::X509::Certificate.new(cert)
@cacert = OpenSSL::X509::Certificate.new(cacert)
retrieved = true
rescue => detail
raise Puppet::Error.new(
"Invalid certificate: #{detail}"
)
end
raise Puppet::DevError, "Received invalid certificate" unless @cert.check_private_key(@key)
retrieved
end
# A hack method to deal with files that exist with a different case.
# Just renames it; doesn't read it in or anything.
def rename_files_with_uppercase(file)
dir = File.dirname(file)
short = File.basename(file)
# If the dir isn't present, we clearly don't have the file.
#return nil unless FileTest.directory?(dir)
raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short
return false unless File.directory?(dir)
real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other|
other.downcase == short
end
return nil unless real_file
full_file = File.join(dir, real_file)
Puppet.notice "Fixing case in #{full_file}; renaming to #{file}"
File.rename(full_file, file)
true
end
end
|