diff options
author | Sean E. Millichamp <sean@bruenor.org> | 2008-11-02 20:05:57 -0500 |
---|---|---|
committer | James Turnbull <james@lovedthanlost.net> | 2008-11-14 09:52:18 +1100 |
commit | 3a5dcab28682a1bbf1b71b2d1de39008468b1ca6 (patch) | |
tree | e1e5f49647a7fecb8bb54bbb7f058ef19be91683 | |
parent | da9b02c6c074b5f60e83db9389faf216e7653ddc (diff) | |
download | puppet-3a5dcab28682a1bbf1b71b2d1de39008468b1ca6.tar.gz puppet-3a5dcab28682a1bbf1b71b2d1de39008468b1ca6.tar.xz puppet-3a5dcab28682a1bbf1b71b2d1de39008468b1ca6.zip |
Refactoring of SELinux functions to use native Ruby SELinux interface
-rw-r--r-- | lib/puppet/util/selinux.rb | 133 | ||||
-rw-r--r-- | spec/unit/util/selinux.rb | 66 |
2 files changed, 100 insertions, 99 deletions
diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index 148748950..b181b3556 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -1,74 +1,55 @@ # 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") + filestat = File.lstat(file) + retval = Selinux.matchpathcon(file, filestat.mode) + if retval == -1 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 + return retval[1] end # Take the full SELinux context returned from the tools and parse it @@ -91,32 +72,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 diff --git a/spec/unit/util/selinux.rb b/spec/unit/util/selinux.rb index 7a56f914a..076ebd293 100644 --- a/spec/unit/util/selinux.rb +++ b/spec/unit/util/selinux.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/util/selinux' include Puppet::Util::SELinux @@ -8,13 +8,19 @@ include Puppet::Util::SELinux describe Puppet::Util::SELinux do describe "selinux_support?" do + before :all do + if not defined? Selinux + Selinux = mock() + end + end + it "should return :true if this system has SELinux enabled" do - FileTest.expects(:exists?).with("/selinux/enforce").returns true + Selinux.expects(:is_selinux_enabled).returns 1 selinux_support?.should be_true end it "should return :false if this system lacks SELinux" do - FileTest.expects(:exists?).with("/selinux/enforce").returns false + Selinux.expects(:is_selinux_enabled).returns 0 selinux_support?.should be_false end end @@ -27,19 +33,13 @@ describe Puppet::Util::SELinux do it "should return a context" do self.expects(:selinux_support?).returns true - self.expects(:execpipe).with("/usr/bin/stat -c %C /foo").yields ["user_u:role_r:type_t:s0\n"] + Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:type_t:s0"] get_selinux_current_context("/foo").should == "user_u:role_r:type_t:s0" end - it "should return nil if an exception is raised calling stat" do + it "should return nil if lgetfilecon fails" do self.expects(:selinux_support?).returns true - self.expects(:execpipe).with("/usr/bin/stat -c %C /foo").raises(Puppet::ExecutionFailure, 'error') - get_selinux_current_context("/foo").should be_nil - end - - it "should return nil if stat finds an unlabeled file" do - self.expects(:selinux_support?).returns true - self.expects(:execpipe).with("/usr/bin/stat -c %C /foo").yields ["(null)\n"] + Selinux.expects(:lgetfilecon).with("/foo").returns -1 get_selinux_current_context("/foo").should be_nil end end @@ -50,23 +50,19 @@ describe Puppet::Util::SELinux do get_selinux_default_context("/foo").should be_nil end - it "should return nil if matchpathcon is not executable" do - self.expects(:selinux_support?).returns true - FileTest.expects(:executable?).with("/usr/sbin/matchpathcon").returns false - get_selinux_default_context("/foo").should be_nil - end - it "should return a context if a default context exists" do self.expects(:selinux_support?).returns true - FileTest.expects(:executable?).with("/usr/sbin/matchpathcon").returns true - self.expects(:execpipe).with("/usr/sbin/matchpathcon /foo").yields ["/foo\tuser_u:role_r:type_t:s0\n"] + fstat = stub 'File::Stat', :mode => 0 + File.expects(:lstat).with("/foo").returns fstat + Selinux.expects(:matchpathcon).with("/foo", 0).returns [0, "user_u:role_r:type_t:s0"] get_selinux_default_context("/foo").should == "user_u:role_r:type_t:s0" end - it "should return nil if an exception is raised calling matchpathcon" do + it "should return nil if matchpathcon returns failure" do self.expects(:selinux_support?).returns true - FileTest.expects(:executable?).with("/usr/sbin/matchpathcon").returns true - self.expects(:execpipe).with("/usr/sbin/matchpathcon /foo").raises(Puppet::ExecutionFailure, 'error') + fstat = stub 'File::Stat', :mode => 0 + File.expects(:lstat).with("/foo").returns fstat + Selinux.expects(:matchpathcon).with("/foo", 0).returns -1 get_selinux_default_context("/foo").should be_nil end end @@ -115,33 +111,37 @@ describe Puppet::Util::SELinux do set_selinux_context("/foo", "user_u:role_r:type_t:s0").should be_nil end - it "should use chcon to set a context" do + it "should use lsetfilecon to set a context" do self.expects(:selinux_support?).returns true - self.expects(:execute).with(["/usr/bin/chcon","-h","user_u:role_r:type_t:s0","/foo"]).returns 0 + Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 set_selinux_context("/foo", "user_u:role_r:type_t:s0").should be_true end - it "should use chcon to set user_u user context" do + it "should use lsetfilecon to set user_u user context" do self.expects(:selinux_support?).returns true - self.expects(:execute).with(["/usr/bin/chcon","-h","-u","user_u","/foo"]).returns 0 + Selinux.expects(:lgetfilecon).with("/foo").returns [0, "foo:role_r:type_t:s0"] + Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 set_selinux_context("/foo", "user_u", :seluser).should be_true end - it "should use chcon to set role_r role context" do + it "should use lsetfilecon to set role_r role context" do self.expects(:selinux_support?).returns true - self.expects(:execute).with(["/usr/bin/chcon","-h","-r","role_r","/foo"]).returns 0 + Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:foo:type_t:s0"] + Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 set_selinux_context("/foo", "role_r", :selrole).should be_true end - it "should use chcon to set type_t type context" do + it "should use lsetfilecon to set type_t type context" do self.expects(:selinux_support?).returns true - self.expects(:execute).with(["/usr/bin/chcon","-h","-t","type_t","/foo"]).returns 0 + Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:foo:s0"] + Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 set_selinux_context("/foo", "type_t", :seltype).should be_true end - it "should use chcon to set s0:c3,c5 range context" do + it "should use lsetfilecon to set s0:c3,c5 range context" do self.expects(:selinux_support?).returns true - self.expects(:execute).with(["/usr/bin/chcon","-h","-l","s0:c3,c5","/foo"]).returns 0 + Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:type_t:s0"] + Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0:c3,c5").returns 0 set_selinux_context("/foo", "s0:c3,c5", :selrange).should be_true end end |