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
21 def self.selinux_support?
22   return false unless defined?(Selinux)
23   if Selinux.is_selinux_enabled == 1
24     return true
25   end
26   false
27 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
35 def get_selinux_current_context(file)
36   return nil unless selinux_support?
37   retval = Selinux.lgetfilecon(file)
38   if retval == -1
39     return nil
40   end
41   retval[1]
42 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
46 def get_selinux_default_context(file, resource_ensure=nil)
47   return nil unless selinux_support?
48   # If the filesystem has no support for SELinux labels, return a default of nil
49   # instead of what matchpathcon would return
50   return nil unless selinux_label_support?(file)
51   # If the file exists we should pass the mode to matchpathcon for the most specific
52   # matching.  If not, we can pass a mode of 0.
53   begin
54     filestat = file_lstat(file)
55     mode = filestat.mode
56   rescue Errno::EACCES
57     mode = 0
58   rescue Errno::ENOENT
59     if resource_ensure
60       mode = get_create_mode(resource_ensure)
61     else
62       mode = 0
63     end
64   end
65 
66   retval = Selinux.matchpathcon(file, mode)
67   if retval == -1
68     return nil
69   end
70   retval[1]
71 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
76 def parse_selinux_context(component, context)
77   if context.nil? or context == "unlabeled"
78     return nil
79   end
80   components = /^([^\s:]+):([^\s:]+):([^\s:]+)(?::([\sa-zA-Z0-9:,._-]+))?$/.match(context)
81   unless components
82     raise Puppet::Error, _("Invalid context to parse: %{context}") % { context: context }
83   end
84   case component
85   when :seluser
86     components[1]
87   when :selrole
88     components[2]
89   when :seltype
90     components[3]
91   when :selrange
92     components[4]
93   else
94     raise Puppet::Error, _("Invalid SELinux parameter type")
95   end
96 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
173 def selinux_category_to_label(category)
174   # We don't cache this, but there's already a ton of duplicate work
175   # in the selinux handling code.
176 
177   path = Selinux.selinux_translations_path
178   begin
179     File.open(path).each do |line|
180       line.strip!
181       next if line.empty?
182       next if line[0] == "#" # skip comments
183       line.gsub!(/[[:space:]]+/m, '')
184       mapping = line.split("=", 2)
185       if category == mapping[1]
186         return mapping[0]
187       end
188     end
189   rescue SystemCallError => ex
190     log_exception(ex)
191     raise Puppet::Error, _("Could not open SELinux category translation file %{path}.") % { context: context }
192   end
193 
194   category
195 end
selinux_support?() click to toggle source
   # File lib/puppet/util/selinux.rb
29 def selinux_support?
30   Puppet::Util::SELinux.selinux_support?
31 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
105 def set_selinux_context(file, value, component = false)
106   return nil unless selinux_support? && selinux_label_support?(file)
107 
108   if component
109     # Must first get existing context to replace a single component
110     context = Selinux.lgetfilecon(file)[1]
111     if context == -1
112       # We can't set partial context components when no context exists
113       # unless/until we can find a way to make Puppet call this method
114       # once for all selinux file label attributes.
115       Puppet.warning _("Can't set SELinux context on file unless the file already has some kind of context")
116       return nil
117     end
118     context = context.split(':')
119     case component
120       when :seluser
121         context[0] = value
122       when :selrole
123         context[1] = value
124       when :seltype
125         context[2] = value
126       when :selrange
127         context[3] = value
128       else
129         raise ArgumentError, _("set_selinux_context component must be one of :seluser, :selrole, :seltype, or :selrange")
130     end
131     context = context.join(':')
132   else
133     context = value
134   end
135 
136   retval = Selinux.lsetfilecon(file, context)
137   if retval == 0
138     return true
139   else
140     Puppet.warning _("Failed to set SELinux context %{context} on %{file}") % { context: context, file: file }
141     return false
142   end
143 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
150 def set_selinux_default_context(file, resource_ensure=nil)
151   new_context = get_selinux_default_context(file, resource_ensure)
152   return nil unless new_context
153   cur_context = get_selinux_current_context(file)
154   if new_context != cur_context
155     set_selinux_context(file, new_context)
156     return new_context
157   end
158   nil
159 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
294 def file_lstat(path)
295   Puppet::FileSystem.lstat(path)
296 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
267 def find_fs(path)
268   mounts = read_mounts
269   return nil unless mounts
270 
271   # cleanpath eliminates useless parts of the path (like '.', or '..', or
272   # multiple slashes), without touching the filesystem, and without
273   # following symbolic links.  This gives the right (logical) tree to follow
274   # while we try and figure out what file-system the target lives on.
275   path = Pathname(path).cleanpath
276   unless path.absolute?
277     raise Puppet::DevError, _("got a relative path in SELinux find_fs: %{path}") % { path: path }
278   end
279 
280   # Now, walk up the tree until we find a match for that path in the hash.
281   path.ascend do |segment|
282     return mounts[segment.to_s] if mounts.has_key?(segment.to_s)
283   end
284 
285   # Should never be reached...
286   return mounts['/']
287 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
215 def get_create_mode(resource_ensure)
216   mode = 0
217   case resource_ensure
218   when :present, :file
219     mode |= S_IFREG
220   when :directory
221     mode |= S_IFDIR
222   when :link
223     mode |= S_IFLNK
224   end
225   mode
226 end
read_mounts() click to toggle source

Internal helper function to read and parse /proc/mounts

    # File lib/puppet/util/selinux.rb
229 def read_mounts
230   mounts = String.new
231   begin
232     if File.method_defined? "read_nonblock"
233       # If possible we use read_nonblock in a loop rather than read to work-
234       # a linux kernel bug.  See ticket #1963 for details.
235       mountfh = File.open("/proc/mounts")
236       loop do
237         mounts += mountfh.read_nonblock(1024)
238       end
239     else
240       # Otherwise we shell out and let cat do it for us
241       mountfh = IO.popen("/bin/cat /proc/mounts")
242       mounts = mountfh.read
243     end
244   rescue EOFError
245     # that's expected
246   rescue
247     return nil
248   ensure
249     mountfh.close if mountfh
250   end
251 
252   mntpoint = {}
253 
254   # Read all entries in /proc/mounts.  The second column is the
255   # mountpoint and the third column is the filesystem type.
256   # We skip rootfs because it is always mounted at /
257   mounts.each_line do |line|
258     params = line.split(' ')
259     next if params[2] == 'rootfs'
260     mntpoint[params[1]] = params[2]
261   end
262   mntpoint
263 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
205 def selinux_label_support?(file)
206   fstype = find_fs(file)
207   return false if fstype.nil?
208   filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs', 'btrfs', 'tmpfs', 'zfs']
209   filesystems.include?(fstype)
210 end