module Puppet::Util::SELinux

Constants

S_IFDIR
S_IFLNK
S_IFREG

Public Class Methods

selinux_support?() click to toggle source
   # File lib/puppet/util/selinux.rb
20 def self.selinux_support?
21   return false unless defined?(Selinux)
22   if Selinux.is_selinux_enabled == 1
23     return true
24   end
25   false
26 end

Public Instance Methods

get_selinux_current_context(file) click to toggle source

Retrieve and return the full context of the file. If we don't have SELinux support or if the SELinux call fails then return nil.

   # File lib/puppet/util/selinux.rb
34 def get_selinux_current_context(file)
35   return nil unless selinux_support?
36   retval = Selinux.lgetfilecon(file)
37   if retval == -1
38     return nil
39   end
40   retval[1]
41 end
get_selinux_default_context(file, resource_ensure=nil) click to toggle source

Retrieve and return the default context of the file. If we don't have SELinux support or if the SELinux call fails to file a default then return nil.

   # File lib/puppet/util/selinux.rb
45 def get_selinux_default_context(file, resource_ensure=nil)
46   return nil unless selinux_support?
47   # If the filesystem has no support for SELinux labels, return a default of nil
48   # instead of what matchpathcon would return
49   return nil unless selinux_label_support?(file)
50   # If the file exists we should pass the mode to matchpathcon for the most specific
51   # matching.  If not, we can pass a mode of 0.
52   begin
53     filestat = file_lstat(file)
54     mode = filestat.mode
55   rescue Errno::EACCES
56     mode = 0
57   rescue Errno::ENOENT
58     if resource_ensure
59       mode = get_create_mode(resource_ensure)
60     else
61       mode = 0
62     end
63   end
64 
65   retval = Selinux.matchpathcon(file, mode)
66   if retval == -1
67     return nil
68   end
69   retval[1]
70 end
parse_selinux_context(component, context) click to toggle source

Take the full SELinux context returned from the tools and parse it out to the three (or four) component parts. Supports :seluser, :selrole, :seltype, and on systems with range support, :selrange.

   # File lib/puppet/util/selinux.rb
75 def parse_selinux_context(component, context)
76   if context.nil? or context == "unlabeled"
77     return nil
78   end
79   components = /^([^\s:]+):([^\s:]+):([^\s:]+)(?::([\sa-zA-Z0-9:,._-]+))?$/.match(context)
80   unless components
81     raise Puppet::Error, _("Invalid context to parse: %{context}") % { context: context }
82   end
83   case component
84   when :seluser
85     components[1]
86   when :selrole
87     components[2]
88   when :seltype
89     components[3]
90   when :selrange
91     components[4]
92   else
93     raise Puppet::Error, _("Invalid SELinux parameter type")
94   end
95 end
selinux_category_to_label(category) click to toggle source

selinux_category_to_label is an internal method that converts all selinux categories to their internal representation, avoiding potential issues when mcstransd is not functional.

It is not marked private because it is needed by File's selcontext.rb, but it is not intended for use outside of Puppet's code.

@param category [String] An selinux category, such as “s0” or “SystemLow”

@return [String] the numeric category name, such as “s0”

    # File lib/puppet/util/selinux.rb
172 def selinux_category_to_label(category)
173   # We don't cache this, but there's already a ton of duplicate work
174   # in the selinux handling code.
175 
176   path = Selinux.selinux_translations_path
177   begin
178     File.open(path).each do |line|
179       line.strip!
180       next if line.empty?
181       next if line[0] == "#" # skip comments
182       line.gsub!(/[[:space:]]+/m, '')
183       mapping = line.split("=", 2)
184       if category == mapping[1]
185         return mapping[0]
186       end
187     end
188   rescue SystemCallError => ex
189     log_exception(ex)
190     raise Puppet::Error, _("Could not open SELinux category translation file %{path}.") % { context: context }
191   end
192 
193   category
194 end
selinux_support?() click to toggle source
   # File lib/puppet/util/selinux.rb
28 def selinux_support?
29   Puppet::Util::SELinux.selinux_support?
30 end
set_selinux_context(file, value, component = false) click to toggle source

This updates the actual SELinux label on the file. You can update only a single component or update the entire context. The caveat is that since setting a partial context makes no sense the file has to already exist. Puppet (via the File resource) will always just try to set components, even if all values are specified by the manifest. I believe that the OS should always provide at least a fall-through context though on any well-running system.

    # File lib/puppet/util/selinux.rb
104 def set_selinux_context(file, value, component = false)
105   return nil unless selinux_support? && selinux_label_support?(file)
106 
107   if component
108     # Must first get existing context to replace a single component
109     context = Selinux.lgetfilecon(file)[1]
110     if context == -1
111       # We can't set partial context components when no context exists
112       # unless/until we can find a way to make Puppet call this method
113       # once for all selinux file label attributes.
114       Puppet.warning _("Can't set SELinux context on file unless the file already has some kind of context")
115       return nil
116     end
117     context = context.split(':')
118     case component
119       when :seluser
120         context[0] = value
121       when :selrole
122         context[1] = value
123       when :seltype
124         context[2] = value
125       when :selrange
126         context[3] = value
127       else
128         raise ArgumentError, _("set_selinux_context component must be one of :seluser, :selrole, :seltype, or :selrange")
129     end
130     context = context.join(':')
131   else
132     context = value
133   end
134 
135   retval = Selinux.lsetfilecon(file, context)
136   if retval == 0
137     return true
138   else
139     Puppet.warning _("Failed to set SELinux context %{context} on %{file}") % { context: context, file: file }
140     return false
141   end
142 end
set_selinux_default_context(file, resource_ensure=nil) click to toggle source

Since this call relies on get_selinux_default_context it also needs a full non-relative path to the file. Fortunately, that seems to be all Puppet uses. This will set the file's SELinux context to the policy's default context (if any) if it differs from the context currently on the file.

    # File lib/puppet/util/selinux.rb
149 def set_selinux_default_context(file, resource_ensure=nil)
150   new_context = get_selinux_default_context(file, resource_ensure)
151   return nil unless new_context
152   cur_context = get_selinux_current_context(file)
153   if new_context != cur_context
154     set_selinux_context(file, new_context)
155     return new_context
156   end
157   nil
158 end

Private Instance Methods

file_lstat(path) click to toggle source

file_lstat is an internal, private method to allow precise stubbing and mocking without affecting the rest of the system.

@return [File::Stat] File.lstat result

    # File lib/puppet/util/selinux.rb
293 def file_lstat(path)
294   Puppet::FileSystem.lstat(path)
295 end
find_fs(path) click to toggle source

Internal helper function to return which type of filesystem a given file path resides on

    # File lib/puppet/util/selinux.rb
266 def find_fs(path)
267   mounts = read_mounts
268   return nil unless mounts
269 
270   # cleanpath eliminates useless parts of the path (like '.', or '..', or
271   # multiple slashes), without touching the filesystem, and without
272   # following symbolic links.  This gives the right (logical) tree to follow
273   # while we try and figure out what file-system the target lives on.
274   path = Pathname(path).cleanpath
275   unless path.absolute?
276     raise Puppet::DevError, _("got a relative path in SELinux find_fs: %{path}") % { path: path }
277   end
278 
279   # Now, walk up the tree until we find a match for that path in the hash.
280   path.ascend do |segment|
281     return mounts[segment.to_s] if mounts.has_key?(segment.to_s)
282   end
283 
284   # Should never be reached...
285   return mounts['/']
286 end
get_create_mode(resource_ensure) click to toggle source

Get mode file type bits set based on ensure on the file resource. This helps SELinux determine what context a new resource being created should have.

    # File lib/puppet/util/selinux.rb
214 def get_create_mode(resource_ensure)
215   mode = 0
216   case resource_ensure
217   when :present, :file
218     mode |= S_IFREG
219   when :directory
220     mode |= S_IFDIR
221   when :link
222     mode |= S_IFLNK
223   end
224   mode
225 end
read_mounts() click to toggle source

Internal helper function to read and parse /proc/mounts

    # File lib/puppet/util/selinux.rb
228 def read_mounts
229   mounts = ""
230   begin
231     if File.method_defined? "read_nonblock"
232       # If possible we use read_nonblock in a loop rather than read to work-
233       # a linux kernel bug.  See ticket #1963 for details.
234       mountfh = File.open("/proc/mounts")
235       loop do
236         mounts += mountfh.read_nonblock(1024)
237       end
238     else
239       # Otherwise we shell out and let cat do it for us
240       mountfh = IO.popen("/bin/cat /proc/mounts")
241       mounts = mountfh.read
242     end
243   rescue EOFError
244     # that's expected
245   rescue
246     return nil
247   ensure
248     mountfh.close if mountfh
249   end
250 
251   mntpoint = {}
252 
253   # Read all entries in /proc/mounts.  The second column is the
254   # mountpoint and the third column is the filesystem type.
255   # We skip rootfs because it is always mounted at /
256   mounts.each_line do |line|
257     params = line.split(' ')
258     next if params[2] == 'rootfs'
259     mntpoint[params[1]] = params[2]
260   end
261   mntpoint
262 end
selinux_label_support?(file) click to toggle source

Check filesystem a path resides on for SELinux support against whitelist of known-good filesystems. Returns true if the filesystem can support SELinux labels and false if not.

    # File lib/puppet/util/selinux.rb
204 def selinux_label_support?(file)
205   fstype = find_fs(file)
206   return false if fstype.nil?
207   filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs', 'btrfs', 'tmpfs']
208   filesystems.include?(fstype)
209 end