summaryrefslogtreecommitdiffstats
path: root/lib/puppet/indirector/ssl_file.rb
blob: 531180f5bdc6253f504cc9ad58f6f159c8a1b876 (plain)
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'