summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/selinux.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/util/selinux.rb')
-rw-r--r--lib/puppet/util/selinux.rb198
1 files changed, 135 insertions, 63 deletions
diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb
index 148748950..70f244507 100644
--- a/lib/puppet/util/selinux.rb
+++ b/lib/puppet/util/selinux.rb
@@ -1,74 +1,67 @@
# Provides utility functions to help interfaces Puppet to SELinux.
#
-# Currently this is implemented via the command line tools. At some
-# point support should be added to use the new SELinux ruby bindings
-# as that will be faster and more reliable then shelling out when they
-# are available. At this time (2008-09-26) these bindings aren't bundled on
-# any SELinux-using distribution I know of.
+# This requires the very new SELinux Ruby bindings. These bindings closely
+# mirror the SELinux C library interface.
+#
+# Support for the command line tools is not provided because the performance
+# was abysmal. At this time (2008-11-02) the only distribution providing
+# these Ruby SELinux bindings which I am aware of is Fedora (in libselinux-ruby).
-require 'puppet/util'
+begin
+ require 'selinux'
+rescue LoadError
+ # Nothing
+end
module Puppet::Util::SELinux
- include Puppet::Util
-
def selinux_support?
- FileTest.exists?("/selinux/enforce")
+ unless defined? Selinux
+ return false
+ end
+ if Selinux.is_selinux_enabled == 1
+ return true
+ end
+ return false
end
# Retrieve and return the full context of the file. If we don't have
- # SELinux support or if the stat call fails then return nil.
+ # SELinux support or if the SELinux call fails then return nil.
def get_selinux_current_context(file)
unless selinux_support?
return nil
end
- context = ""
- begin
- execpipe("/usr/bin/stat -c %C #{file}") do |out|
- out.each do |line|
- context << line
- end
- end
- rescue Puppet::ExecutionFailure
- return nil
- end
- context.chomp!
- # Handle the case that the system seems to have SELinux support but
- # stat finds unlabled files.
- if context == "(null)"
+ retval = Selinux.lgetfilecon(file)
+ if retval == -1
return nil
end
- return context
+ return retval[1]
end
- # Use the matchpathcon command, if present, to return the SELinux context
- # which the SELinux policy on the system expects the file to have. We can
- # use this to obtain a good default context. If the command does not
- # exist or the call fails return nil.
- #
- # Note: For this command to work a full, non-relative, filesystem path
- # should be given.
+ # 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.
def get_selinux_default_context(file)
unless selinux_support?
return nil
end
- unless FileTest.executable?("/usr/sbin/matchpathcon")
+ # If the filesystem has no support for SELinux labels, return a default of nil
+ # instead of what matchpathcon would return
+ unless selinux_label_support?(file)
return nil
end
- context = ""
+ # If the file exists we should pass the mode to matchpathcon for the most specific
+ # matching. If not, we can pass a mode of 0.
begin
- execpipe("/usr/sbin/matchpathcon #{file}") do |out|
- out.each do |line|
- context << line
- end
- end
- rescue Puppet::ExecutionFailure
+ filestat = File.lstat(file)
+ mode = filestat.mode
+ rescue Errno::ENOENT
+ mode = 0
+ end
+ retval = Selinux.matchpathcon(file, mode)
+ if retval == -1
return nil
end
- # For a successful match, matchpathcon returns two fields separated by
- # a variable amount of whitespace. The second field is the full context.
- context = context.split(/\s/)[1]
- return context
+ return retval[1]
end
# Take the full SELinux context returned from the tools and parse it
@@ -91,32 +84,52 @@ module Puppet::Util::SELinux
end
# This updates the actual SELinux label on the file. You can update
- # only a single component or update the entire context. It is just a
- # wrapper around the chcon command.
+ # 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.
def set_selinux_context(file, value, component = false)
unless selinux_support?
return nil
end
- case component
- when :seluser
- flag = "-u"
- when :selrole
- flag = "-r"
- when :seltype
- flag = "-t"
- when :selrange
- flag = "-l"
- else
- flag = nil
- end
- if flag.nil?
- cmd = ["/usr/bin/chcon","-h",value,file]
+ if component
+ # Must first get existing context to replace a single component
+ context = Selinux.lgetfilecon(file)[1]
+ if context == -1
+ # We can't set partial context components when no context exists
+ # unless/until we can find a way to make Puppet call this method
+ # once for all selinux file label attributes.
+ Puppet.warning "Can't set SELinux context on file unless the file already has some kind of context"
+ return nil
+ end
+ context = context.split(':')
+ case component
+ when :seluser
+ context[0] = value
+ when :selrole
+ context[1] = value
+ when :seltype
+ context[2] = value
+ when :selrange
+ context[3] = value
+ else
+ raise ArguementError, "set_selinux_context component must be one of :seluser, :selrole, :seltype, or :selrange"
+ end
+ context = context.join(':')
+ else
+ context = value
+ end
+
+ retval = Selinux.lsetfilecon(file, context)
+ if retval == 0
+ return true
else
- cmd = ["/usr/bin/chcon","-h",flag,value,file]
+ Puppet.warning "Failed to set SELinux context %s on %s" % [context, file]
+ return false
end
- execute(cmd)
- return true
end
# Since this call relies on get_selinux_default_context it also needs a
@@ -136,4 +149,63 @@ module Puppet::Util::SELinux
end
return nil
end
+
+ # Internal helper function to read and parse /proc/mounts
+ def read_mounts
+ begin
+ mounts = File.read("/proc/mounts")
+ rescue
+ return nil
+ end
+
+ mntpoint = {}
+
+ # Read all entries in /proc/mounts. The second column is the
+ # mountpoint and the third column is the filesystem type.
+ # We skip rootfs because it is always mounted at /
+ mounts.collect do |line|
+ params = line.split(' ')
+ next if params[2] == 'rootfs'
+ mntpoint[params[1]] = params[2]
+ end
+ return mntpoint
+ end
+
+ # Internal helper function to return which type of filesystem a
+ # given file path resides on
+ def find_fs(file)
+ unless mnts = read_mounts()
+ return nil
+ end
+
+ # For a given file:
+ # Check if the filename is in the data structure;
+ # return the fstype if it is.
+ # Just in case: return something if you're down to "/" or ""
+ # Remove the last slash and everything after it,
+ # and repeat with that as the file for the next loop through.
+ ary = file.split('/')
+ while not ary.empty? do
+ path = ary.join('/')
+ if mnts.has_key?(path)
+ return mnts[path]
+ end
+ ary.pop
+ end
+ return mnts['/']
+ end
+
+ # 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.
+ def selinux_label_support?(file)
+ fstype = find_fs(file)
+ if fstype.nil?
+ return false
+ end
+ filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs']
+ return filesystems.include?(fstype)
+ end
+
end