diff options
-rw-r--r-- | lib/puppet/provider/selboolean/getsetsebool.rb | 47 | ||||
-rw-r--r-- | lib/puppet/provider/selmodule/semodule.rb | 143 | ||||
-rw-r--r-- | lib/puppet/type/file.rb | 1 | ||||
-rw-r--r-- | lib/puppet/type/file/selcontext.rb | 96 | ||||
-rw-r--r-- | lib/puppet/type/selboolean.rb | 29 | ||||
-rw-r--r-- | lib/puppet/type/selmodule.rb | 50 | ||||
-rw-r--r-- | spec/unit/other/selinux.rb | 79 |
7 files changed, 445 insertions, 0 deletions
diff --git a/lib/puppet/provider/selboolean/getsetsebool.rb b/lib/puppet/provider/selboolean/getsetsebool.rb new file mode 100644 index 000000000..4614c6c38 --- /dev/null +++ b/lib/puppet/provider/selboolean/getsetsebool.rb @@ -0,0 +1,47 @@ +Puppet::Type.type(:selboolean).provide(:getsetsebool) do + desc "Manage SELinux booleans using the getsebool and setsebool binaries." + + commands :getsebool => "/usr/sbin/getsebool" + commands :setsebool => "/usr/sbin/setsebool" + + def value + self.debug "Retrieving value of selboolean #{@resource[:name]}" + + status = getsebool(@resource[:name]) + + if status =~ / off$/ then + return :off + elsif status =~ / on$/ then + return :on + else + status.chomp! + raise Puppet::Error, "Invalid response '%s' returned from getsebool" % [status] + end + end + + def value=(new) + persist = "" + if @resource[:persistent] == :true + self.debug "Enabling persistence" + persist = "-P" + end + execoutput("#{command(:setsebool)} #{persist} #{@resource[:name]} #{new}") + return :file_changed + end + + # Required workaround, since SELinux policy prevents setsebool + # from writing to any files, even tmp, preventing the standard + # 'setsebool("...")' construct from working. + + def execoutput (cmd) + output = '' + begin + execpipe(cmd) do |out| + output = out.readlines.join('').chomp! + end + rescue Puppet::ExecutionFailure + raise Puppet::ExecutionFailure, output.split("\n")[0] + end + return output + end +end diff --git a/lib/puppet/provider/selmodule/semodule.rb b/lib/puppet/provider/selmodule/semodule.rb new file mode 100644 index 000000000..498136691 --- /dev/null +++ b/lib/puppet/provider/selmodule/semodule.rb @@ -0,0 +1,143 @@ +Puppet::Type.type(:selmodule).provide(:semodule) do + desc "Manage SELinux policy modules using the semodule binary." + + commands :semodule => "/usr/sbin/semodule" + + def create + begin + execoutput("#{command(:semodule)} --install #{selmod_name_to_filename}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not load policy module: %s" % [detail]; + end + return :true + end + + def destroy + begin + execoutput("#{command(:semodule)} --remove #{@resource[:name]}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not remove policy module: %s" % [detail]; + end + end + + def exists? + self.debug "Checking for module #{@resource[:name]}" + execpipe("#{command(:semodule)} --list") do |out| + out.each do |line| + if line =~ /#{@resource[:name]}\b/ + return :true + end + end + end + return nil + end + + def syncversion + self.debug "Checking syncversion on #{@resource[:name]}" + + loadver = selmodversion_loaded + + if(loadver) then + filever = selmodversion_file + if (filever == loadver) then + return :true + end + end + return :false + end + + def syncversion= (dosync) + begin + execoutput("#{command(:semodule)} --upgrade #{selmod_name_to_filename}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not upgrade policy module: %s" % [detail]; + end + end + + # Helper functions + + def execoutput (cmd) + output = '' + begin + execpipe(cmd) do |out| + output = out.readlines.join('').chomp! + end + rescue Puppet::ExecutionFailure + raise Puppet::ExecutionFailure, output.split("\n")[0] + end + return output + end + + def selmod_name_to_filename + if @resource[:selmodulepath] + return @resource[:selmodulepath] + else + return "#{@resource[:selmoduledir]}/#{@resource[:name]}.pp" + end + end + + def selmod_readnext (handle) + len = handle.read(4).unpack('L')[0] + return handle.read(len) + end + + def selmodversion_file + magic = 0xF97CFF8F + + filename = selmod_name_to_filename + mod = File.new(filename, "r") + + (hdr, ver, numsec) = mod.read(12).unpack('LLL') + + if hdr != magic + raise Puppet::Error, "Found #{hdr} instead of magic #{magic} in #{filename}" + end + + if ver != 1 + raise Puppet::Error, "Unknown policy file version #{ver} in #{filename}" + end + + # Read through (and throw away) the file section offsets, and also + # the magic header for the first section. + + mod.read((numsec + 1) * 4) + + ## Section 1 should be "SE Linux Module" + + selmod_readnext(mod) + selmod_readnext(mod) + + # Skip past the section headers + mod.read(14) + + # Module name + selmod_readnext(mod) + + # At last! the version + + v = selmod_readnext(mod) + + self.debug "file version #{v}" + return v + end + + def selmodversion_loaded + lines = () + begin + execpipe("#{command(:semodule)} --list") do |output| + lines = output.readlines + lines.each do |line| + line.chomp! + bits = line.split + if bits[0] == @resource[:name] then + self.debug "load version #{bits[1]}" + return bits[1] + end + end + end + rescue Puppet::ExecutionFailure + raise Puppet::ExecutionFailure, "Could not list policy modules: %s" % [lines.join(' ').chomp!] + end + return nil + end +end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 2a5e61de8..371571ff3 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1157,4 +1157,5 @@ module Puppet require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' + require 'puppet/type/file/selcontext' # SELinux file context end diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb new file mode 100644 index 000000000..f36695075 --- /dev/null +++ b/lib/puppet/type/file/selcontext.rb @@ -0,0 +1,96 @@ +# Manage SELinux context of files. +# +# This code actually manages three pieces of data in the context. +# +# [root@delenn files]# ls -dZ / +# drwxr-xr-x root root system_u:object_r:root_t / +# +# The context of '/' here is 'system_u:object_r:root_t'. This is +# three seperate fields: +# +# system_u is the user context +# object_r is the role context +# root_t is the type context +# +# All three of these fields are returned in a single string by the +# output of the stat command, but set individually with the chcon +# command. This allows the user to specify a subset of the three +# values while leaving the others alone. +# +# See http://www.nsa.gov/selinux/ for complete docs on SELinux. + +module Puppet + class SELFileContext < Puppet::Property + + def retrieve + unless @resource.stat(false) + return :absent + end + context = `stat -c %C #{@resource[:path]}` + context.chomp! + if context == "unlabeled" + return nil + end + unless context =~ /^[a-z0-9_]+:[a-z0-9_]+:[a-z0-9_]+/ + raise Puppet::Error, "Invalid output from stat: #{context}" + end + bits = context.split(':') + ret = { + :seluser => bits[0], + :selrole => bits[1], + :seltype => bits[2] + } + return ret[name] + end + + def sync + unless @resource.stat(false) + stat = @resource.stat(true) + unless stat + return nil + end + end + + flag = '' + + case name + when :seluser + flag = "-u" + when :selrole + flag = "-r" + when :seltype + flag = "-t" + else + raise Puppet::Error, "Invalid SELinux file context component: #{name}" + end + + self.debug "Running chcon #{flag} #{@should} #{@resource[:path]}" + retval = system("chcon #{flag} #{@should} #{@resource[:path]}") + unless retval + error = Puppet::Error.new("failed to chcon %s" % [@resource[:path]]) + raise error + end + return :file_changed + end + end + + Puppet.type(:file).newproperty(:seluser, :parent => Puppet::SELFileContext) do + desc "What the SELinux User context of the file should be." + + @event = :file_changed + end + + Puppet.type(:file).newproperty(:selrole, :parent => Puppet::SELFileContext) do + desc "What the SELinux Role context of the file should be." + + @event = :file_changed + end + + Puppet.type(:file).newproperty(:seltype, :parent => Puppet::SELFileContext) do + desc "What the SELinux Type context of the file should be." + + @event = :file_changed + end + +end + diff --git a/lib/puppet/type/selboolean.rb b/lib/puppet/type/selboolean.rb new file mode 100644 index 000000000..d12dd3bcb --- /dev/null +++ b/lib/puppet/type/selboolean.rb @@ -0,0 +1,29 @@ +# +# Simple module for manageing SELinux booleans +# + +module Puppet + newtype(:selboolean) do + @doc = "Enable or disable SELinux booleans." + + newparam(:name) do + desc "The name of the SELinux boolean to be managed." + isnamevar + end + + newproperty(:value) do + desc "Whether the the SELinux boolean should be enabled or disabled. Possible values are ``on`` or ``off``." + newvalue(:on) + newvalue(:off) + end + + newparam(:persistent) do + desc "If set true, SELinux booleans will be written to disk and persist accross reboots." + + defaultto :false + newvalues(:true, :false) + end + + end +end + diff --git a/lib/puppet/type/selmodule.rb b/lib/puppet/type/selmodule.rb new file mode 100644 index 000000000..1f02912ad --- /dev/null +++ b/lib/puppet/type/selmodule.rb @@ -0,0 +1,50 @@ +# +# Simple module for manageing SELinux policy modules +# + +Puppet::Type.newtype(:selmodule) do + @doc = "Enable or disable SELinux policy modules." + + ensurable + + newparam(:name) do + desc "The name of the SELinux policy to be managed." + isnamevar + end + + newparam(:selmoduledir) do + + desc "The directory to look for the compiled pp module file in. + Currently defaults to /usr/share/selinux/targeted" + + defaultto "/usr/share/selinux/targeted" + end + + newparam(:selmodulepath) do + + desc "The full path in which to look for the compiled pp + module file in. You only need to use this if the module file + is not in the directory pointed at by selmoduledir." + + end + + newproperty(:syncversion) do + + desc "If set to 'true', the policy will be reloaded if the + version found in the on-disk file differs from the loaded + version. If set to 'false' (the default) the the only check + that will be made is if the policy is loaded at all or not." + + newvalue(:true) + newvalue(:false) + end + + autorequire(:file) do + if self[:selmodulepath] + [self[:selmodulepath]] + else + ["#{self[:selmoduledir]}/#{self[:name]}.pp"] + end + end +end + diff --git a/spec/unit/other/selinux.rb b/spec/unit/other/selinux.rb new file mode 100644 index 000000000..3a07e0933 --- /dev/null +++ b/spec/unit/other/selinux.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/type/selboolean' +require 'puppet/type/selmodule' + +describe Puppet.type(:file), " when manipulating file contexts" do + before :each do + @file = Puppet::Type::File.create( + :name => "/tmp/foo", + :ensure => "file", + :seluser => "user_u", + :selrole => "role_r", + :seltype => "type_t" ) + end + it "should use :seluser to get/set an SELinux user file context attribute" do + @file.property(:seluser).should == "user_u" + end + it "should use :selrole to get/set an SELinux role file context attribute" do + @file.property(:selrole).should == "role_r" + end + it "should use :seltype to get/set an SELinux user file context attribute" do + @file.property(:seltype).should == "type_t" + end +end + +describe Puppet.type(:selboolean), " when manipulating booleans" do + before :each do + @bool = Puppet::Type::Selboolean.create( + :name => "foo", + :value => "on", + :persistent => true ) + end + it "should be able to access :name" do + @bool[:name].should == "foo" + end + it "should be able to access :value" do + @bool.property(:value).should == :on + end + it "should set :value to off" do + @bool[:value] = :off + @bool.property(:value).should == :off + end + it "should be able to access :persistent" do + @bool[:persistent].should == :true + end + it "should set :persistent to false" do + @bool[:persistent] = false + @bool[:persistent].should == :false + end +end + +describe Puppet.type(:selmodule), " when checking policy modules" do + before :each do + @module = Puppet::Type::Selmodule.create( + :name => "foo", + :selmoduledir => "/some/path", + :selmodulepath => "/some/path/foo.pp", + :syncversion => true) + end + it "should be able to access :name" do + @module[:name].should == "foo" + end + it "should be able to access :selmoduledir" do + @module[:selmoduledir].should == "/some/path" + end + it "should be able to access :selmodulepath" do + @module[:selmodulepath].should == "/some/path/foo.pp" + end + it "should be able to access :syncversion" do + @module.property(:syncversion).should == :true + end + it "should set the syncversion value to false" do + @module[:syncversion] = :false + @module.property(:syncversion).should == :false + end +end + |