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