module Puppet::Util::SELinux
Constants
- S_IFDIR
- S_IFLNK
- S_IFREG
Public Class Methods
# 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
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
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
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 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
# File lib/puppet/util/selinux.rb 29 def selinux_support? 30 Puppet::Util::SELinux.selinux_support? 31 end
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
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 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
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 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
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
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