File: //proc/self/root/usr/lib/ruby/site_ruby/1.8/puppet/indirector/ssl_file.rb
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
def path(name)
if name =~ Puppet::Indirector::BadNameRegexp then
Puppet.crit("directory traversal detected in #{self.class}: #{name.inspect}")
raise ArgumentError, "invalid key"
end
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 = Puppet::FileSystem.pathname(path(request.key))
return false unless Puppet::FileSystem.exist?(path)
Puppet.notice "Removing file #{model} #{request.key} at '#{path}'"
begin
Puppet::FileSystem.unlink(path)
rescue => detail
raise Puppet::Error, "Could not remove #{request.key}: #{detail}", detail.backtrace
end
end
# Find the file on disk, returning an instance of the model.
def find(request)
filename = rename_files_with_uppercase(path(request.key))
filename ? create_model(request.key, filename) : nil
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).
select { |file| file =~ /\.pem$/ }.
collect { |file| create_model(file.sub(/\.pem$/, ''), File.join(dir, file)) }.
compact
end
private
def create_model(name, path)
result = model.new(name)
result.read(path)
result
end
# 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)
return file if Puppet::FileSystem.exist?(file)
dir, short = File.split(file)
return nil unless Puppet::FileSystem.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.deprecation_warning "Automatic downcasing and renaming of ssl files is deprecated; please request the file using its correct case: #{full_file}"
File.rename(full_file, file)
file
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.setting(self.class.ca_setting).open('w') { |f| yield f }
elsif file_location
Puppet.settings.setting(self.class.file_setting).open('w') { |f| yield f }
elsif setting = self.class.directory_setting
begin
Puppet.settings.setting(setting).open_file(path, 'w') { |f| yield f }
rescue => detail
raise Puppet::Error, "Could not write #{path} to #{setting}: #{detail}", detail.backtrace
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'