summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/selinux.rb
blob: 8c1aecf3b93227b4aa519e3a788d15d6d72b6908 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# 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.

module Puppet::Util::SELinux

    def selinux_support?
        FileTest.exists?("/selinux/enforce")
    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.
    def get_selinux_current_context(file)
        unless selinux_support?
            return nil
        end
        context = ""
        begin
            execpipe("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)"
            return nil
        end
        return context
    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.
    def get_selinux_default_context(file)
        unless selinux_support?
            return nil
        end
        unless FileTest.executable?("/usr/sbin/matchpathcon")
            return nil
        end
        context = ""
        begin
            execpipe("/usr/sbin/matchpathcon #{file}") do |out|
                out.each do |line|
                    context << line
                end
            end
        rescue Puppet::ExecutionFailure
            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
    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.
    def parse_selinux_context(component, context)
        if context.nil? or context == "unlabeled"
            return nil
        end
        unless context =~ /^([a-z0-9_]+):([a-z0-9_]+):([a-z0-9_]+)(?::([a-zA-Z0-9:,._-]+))?/
            raise Puppet::Error, "Invalid context to parse: #{context}"
        end
        ret = {
            :seluser => $1,
            :selrole => $2,
            :seltype => $3,
            :selrange => $4,
        }
        return ret[component]
    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.
    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 = ""
        end

        Puppet.debug "Running chcon #{flag} #{value} #{file}"
        retval = system("chcon #{flag} #{value} #{file}")
        unless retval
            error = Puppet::Error.new("failed to chcon %s" % [@resource[:path]])
            raise error
            return false
        end
        return true
    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.
    def set_selinux_default_context(file)
        new_context = get_selinux_default_context(file)
        unless new_context
            return nil
        end
        cur_context = get_selinux_current_context(file)
        if new_context != cur_context
            set_selinux_context(file, new_context)
            return new_context
        end
        return nil
    end
end