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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
require 'puppet/ssl'
class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus
# Specify the directory in which multiple files are stored.
def self.store_in(setting)
@directory_setting = setting
end
# Specify a single file location for storing just one file.
# This is used for things like the CRL.
def self.store_at(setting)
@file_setting = setting
end
# Specify where a specific ca file should be stored.
def self.store_ca_at(setting)
@ca_setting = setting
end
class << self
attr_reader :directory_setting, :file_setting, :ca_setting
end
# The full path to where we should store our files.
def self.collection_directory
return nil unless directory_setting
Puppet.settings[directory_setting]
end
# The full path to an individual file we would be managing.
def self.file_location
return nil unless file_setting
Puppet.settings[file_setting]
end
# The full path to a ca file we would be managing.
def self.ca_location
return nil unless ca_setting
Puppet.settings[ca_setting]
end
# We assume that all files named 'ca' are pointing to individual ca files,
# rather than normal host files. It's a bit hackish, but all the other
# solutions seemed even more hackish.
def ca?(name)
name == Puppet::SSL::Host.ca_name
end
def initialize
Puppet.settings.use(:main, :ssl)
(collection_directory || file_location) or raise Puppet::DevError, "No file or directory setting provided; terminus #{self.class.name} cannot function"
end
# Use a setting to determine our path.
def path(name)
if ca?(name) and ca_location
ca_location
elsif collection_directory
File.join(collection_directory, name.to_s + ".pem")
else
file_location
end
end
# Remove our file.
def destroy(request)
path = path(request.key)
return false unless FileTest.exist?(path)
Puppet.notice "Removing file #{model} #{request.key} at '#{path}'"
begin
File.unlink(path)
rescue => detail
raise Puppet::Error, "Could not remove #{request.key}: #{detail}"
end
end
# Find the file on disk, returning an instance of the model.
def find(request)
path = path(request.key)
return nil unless FileTest.exist?(path) or rename_files_with_uppercase(path)
result = model.new(request.key)
result.read(path)
result
end
# Save our file to disk.
def save(request)
path = path(request.key)
dir = File.dirname(path)
raise Puppet::Error.new("Cannot save #{request.key}; parent directory #{dir} does not exist") unless FileTest.directory?(dir)
raise Puppet::Error.new("Cannot save #{request.key}; parent directory #{dir} is not writable") unless FileTest.writable?(dir)
write(request.key, path) { |f| f.print request.instance.to_s }
end
# Search for more than one file. At this point, it just returns
# an instance for every file in the directory.
def search(request)
dir = collection_directory
Dir.entries(dir).reject { |file| file !~ /\.pem$/ }.collect do |file|
name = file.sub(/\.pem$/, '')
result = model.new(name)
result.read(File.join(dir, file))
result
end
end
private
# Demeterish pointers to class info.
def collection_directory
self.class.collection_directory
end
def file_location
self.class.file_location
end
def ca_location
self.class.ca_location
end
# A hack method to deal with files that exist with a different case.
# Just renames it; doesn't read it in or anything.
# LAK:NOTE This is a copy of the method in sslcertificates/support.rb,
# which we'll be EOL'ing at some point. This method was added at 20080702
# and should be removed at some point.
def rename_files_with_uppercase(file)
dir, short = File.split(file)
return nil unless FileTest.exist?(dir)
raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short
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
# Yield a filehandle set up appropriately, either with our settings doing
# the work or opening a filehandle manually.
def write(name, path)
if ca?(name) and ca_location
Puppet.settings.write(self.class.ca_setting) { |f| yield f }
elsif file_location
Puppet.settings.write(self.class.file_setting) { |f| yield f }
elsif setting = self.class.directory_setting
begin
Puppet.settings.writesub(setting, path) { |f| yield f }
rescue => detail
raise Puppet::Error, "Could not write #{path} to #{setting}: #{detail}"
end
else
raise Puppet::DevError, "You must provide a setting to determine where the files are stored"
end
end
end
# LAK:NOTE This has to be at the end, because classes like SSL::Key use this
# class, and this require statement loads those, which results in a load loop
# and lots of failures.
require 'puppet/ssl/host'
|