Module: Puppet::Util::SELinux

Included in:
SELFileContext, FileType
Defined in:
lib/puppet/util/selinux.rb

Instance Method Summary collapse

Instance Method Details

#find_fs(path) ⇒ Object

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



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/puppet/util/selinux.rb', line 186

def find_fs(path)
  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.
  path = realpath(path)
  while not path.empty?
    return mnts[path] if mnts.has_key?(path)
    path = parent_directory(path)
  end
  mnts['/']
end

#get_selinux_current_context(file) ⇒ Object

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.



26
27
28
29
30
31
32
33
# File 'lib/puppet/util/selinux.rb', line 26

def get_selinux_current_context(file)
  return nil unless selinux_support?
  retval = Selinux.lgetfilecon(file)
  if retval == -1
    return nil
  end
  retval[1]
end

#get_selinux_default_context(file) ⇒ Object

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.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/puppet/util/selinux.rb', line 37

def get_selinux_default_context(file)
  return nil unless selinux_support?
  # If the filesystem has no support for SELinux labels, return a default of nil
  # instead of what matchpathcon would return
  return nil unless selinux_label_support?(file)
  # 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
    filestat = File.lstat(file)
    mode = filestat.mode
  rescue Errno::ENOENT
    mode = 0
  end
  retval = Selinux.matchpathcon(file, mode)
  if retval == -1
    return nil
  end
  retval[1]
end

#parent_directory(path) ⇒ Object



180
181
182
# File 'lib/puppet/util/selinux.rb', line 180

def parent_directory(path)
  Pathname.new(path).dirname.to_s
end

#parse_selinux_context(component, context) ⇒ Object

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.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/puppet/util/selinux.rb', line 60

def parse_selinux_context(component, context)
  if context.nil? or context == "unlabeled"
    return nil
  end
  unless context =~ /^([a-z0-9_]+):([a-z0-9_]+):([a-zA-Z0-9_]+)(?::([a-zA-Z0-9:,._-]+))?/
    raise Puppet::Error, "Invalid context to parse: #{context}"
  end
  ret = {
    :seluser => $1,
    :selrole => $2,
    :seltype => $3,
    :selrange => $4,
  }
  ret[component]
end

#read_mountsObject

Internal helper function to read and parse /proc/mounts



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/puppet/util/selinux.rb', line 140

def read_mounts
  mounts = ""
  begin
    if File.instance_methods.include? "read_nonblock"
      # If possible we use read_nonblock in a loop rather than read to work-
      # a linux kernel bug.  See ticket #1963 for details.
      mountfh = File.open("/proc/mounts")
      mounts += mountfh.read_nonblock(1024) while true
    else
      # Otherwise we shell out and let cat do it for us
      mountfh = IO.popen("/bin/cat /proc/mounts")
      mounts = mountfh.read
    end
  rescue EOFError
    # that's expected
  rescue
    return nil
  ensure
    mountfh.close if mountfh
  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
  mntpoint
end

#realpath(path) ⇒ Object



174
175
176
177
178
# File 'lib/puppet/util/selinux.rb', line 174

def realpath(path)
  path, rest = Pathname.new(path), []
  path, rest = path.dirname, [path.basename] + rest while ! path.exist?
  File.join( path.realpath, *rest )
end

#selinux_label_support?(file) ⇒ Boolean

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.

Returns:

  • (Boolean)


209
210
211
212
213
214
# File 'lib/puppet/util/selinux.rb', line 209

def selinux_label_support?(file)
  fstype = find_fs(file)
  return false if fstype.nil?
  filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs']
  filesystems.include?(fstype)
end

#selinux_support?Boolean

Returns:

  • (Boolean)


16
17
18
19
20
21
22
# File 'lib/puppet/util/selinux.rb', line 16

def selinux_support?
  return false unless defined?(Selinux)
  if Selinux.is_selinux_enabled == 1
    return true
  end
  false
end

#set_selinux_context(file, value, component = false) ⇒ Object

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.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/puppet/util/selinux.rb', line 83

def set_selinux_context(file, value, component = false)
  return nil unless selinux_support? && selinux_label_support?(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
    Puppet.warning "Failed to set SELinux context #{context} on #{file}"
    return false
  end
end

#set_selinux_default_context(file) ⇒ Object

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.



128
129
130
131
132
133
134
135
136
137
# File 'lib/puppet/util/selinux.rb', line 128

def set_selinux_default_context(file)
  new_context = get_selinux_default_context(file)
  return nil unless new_context
  cur_context = get_selinux_current_context(file)
  if new_context != cur_context
    set_selinux_context(file, new_context)
    return new_context
  end
  nil
end