Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
AriaXLi committed May 31, 2024
1 parent 74811af commit c3b7af8
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 19 deletions.
8 changes: 8 additions & 0 deletions lib/puppet/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,14 @@ def <=>(other)
# @return [void]
# @api public

# @comment Document pre_resource_eval here as it does not exist anywhere else
# (called from transaction if implemented) @!method self.pre_resource_eval()
# @abstract A subclass may implement this - it is not implemented in the
# Provider class This method may be implemented by a provider in order to
# perform any setup actions needed, such as creating a handle. It will be
# called at the beginning of the transaction if the provider has implemented
# the method @return [void]

# @comment Document post_resource_eval here as it does not exist anywhere else (called from transaction if implemented)
# @!method self.post_resource_eval()
# @since 3.4.0
Expand Down
17 changes: 15 additions & 2 deletions lib/puppet/provider/file/posix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,21 @@
require 'etc'
require_relative '../../../puppet/util/selinux'

def self.post_resource_eval
Selinux.matchpathcon_fini if Puppet::Util::SELinux.selinux_support?
class << self
# @return [non-NULL handle value] A handle for selinux
attr_reader :selinux_handle

def pre_resource_eval
@selinux_handle = if Puppet::Util::SELinux.selinux_support?
Selinux.selabel_open(0, nil, 0)
else
nil
end
end

def post_resource_eval
Selinux.selinux_close(@selinux_handle) if @selinux_handle
end
end

def uid2name(id)
Expand Down
1 change: 1 addition & 0 deletions lib/puppet/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def evaluate(&block)
post_evalable_providers = Set.new
pre_process = lambda do |resource|
prov_class = resource.provider.class
prov_class.pre_resource_eval if prov_class.respond_to?(:pre_resource_eval)
post_evalable_providers << prov_class if prov_class.respond_to?(:post_resource_eval)

prefetch_if_necessary(resource)
Expand Down
14 changes: 8 additions & 6 deletions lib/puppet/type/file/selcontext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ def retrieve
end

def retrieve_default_context(property)
return nil if Puppet::Util::Platform.windows?

if @resource[:selinux_ignore_defaults] == :true
return nil
end

context = get_selinux_default_context(@resource[:path], @resource[:ensure])
context = get_selinux_default_context_with_handle(@resource[:path], @resource[:ensure], provider.class.selinux_handle)
unless context
return nil
end
Expand Down Expand Up @@ -85,7 +87,7 @@ def sync
end

Puppet::Type.type(:file).newparam(:selinux_ignore_defaults) do
desc "If this is set then Puppet will not ask SELinux (via matchpathcon) to
desc "If this is set then Puppet will not ask SELinux (via selabel_lookup) to
supply defaults for the SELinux attributes (seluser, selrole,
seltype, and selrange). In general, you should leave this set at its
default and only set it to true when you need Puppet to not try to fix
Expand All @@ -98,7 +100,7 @@ def sync
Puppet::Type.type(:file).newproperty(:seluser, :parent => Puppet::SELFileContext) do
desc "What the SELinux user component of the context of the file should be.
Any valid SELinux user component is accepted. For example `user_u`.
If not specified it defaults to the value returned by matchpathcon for
If not specified it defaults to the value returned by selabel_lookup for
the file, if any exists. Only valid on systems with SELinux support
enabled."

Expand All @@ -109,7 +111,7 @@ def sync
Puppet::Type.type(:file).newproperty(:selrole, :parent => Puppet::SELFileContext) do
desc "What the SELinux role component of the context of the file should be.
Any valid SELinux role component is accepted. For example `role_r`.
If not specified it defaults to the value returned by matchpathcon for
If not specified it defaults to the value returned by selabel_lookup for
the file, if any exists. Only valid on systems with SELinux support
enabled."

Expand All @@ -120,7 +122,7 @@ def sync
Puppet::Type.type(:file).newproperty(:seltype, :parent => Puppet::SELFileContext) do
desc "What the SELinux type component of the context of the file should be.
Any valid SELinux type component is accepted. For example `tmp_t`.
If not specified it defaults to the value returned by matchpathcon for
If not specified it defaults to the value returned by selabel_lookup for
the file, if any exists. Only valid on systems with SELinux support
enabled."

Expand All @@ -132,7 +134,7 @@ def sync
desc "What the SELinux range component of the context of the file should be.
Any valid SELinux range component is accepted. For example `s0` or
`SystemHigh`. If not specified it defaults to the value returned by
matchpathcon for the file, if any exists. Only valid on systems with
selabel_lookup for the file, if any exists. Only valid on systems with
SELinux support enabled and that have support for MCS (Multi-Category
Security)."

Expand Down
2 changes: 1 addition & 1 deletion lib/puppet/util/filetype.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def write(text)
FileUtils.cp(tf.path, @path)
tf.close
# If SELinux is present, we need to ensure the file has its expected context
set_selinux_default_context(@path)
set_selinux_default_context(@path) # TODO file another ticket to fix this and not call deprecated get
end
end

Expand Down
32 changes: 29 additions & 3 deletions lib/puppet/util/selinux.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def get_selinux_current_context(file)

# 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.
# @deprecated matchpathcon is a deprecated method, selabel_lookup is preferred
def get_selinux_default_context(file, resource_ensure = nil)
return nil unless selinux_support?
# If the filesystem has no support for SELinux labels, return a default of nil
Expand All @@ -68,11 +69,36 @@ def get_selinux_default_context(file, resource_ensure = nil)
end

retval = Selinux.matchpathcon(file, mode)
if retval == -1
return nil
retval == -1 ? nil : retval[1]
end

def get_selinux_default_context_with_handle(file, handle, resource_ensure = nil)
return nil unless selinux_support?
# If the filesystem has no support for SELinux labels, return a default of nil
# instead of what selabel_lookup would return
return nil unless selinux_label_support?(file)
# If the file exists we should pass the mode to selabel_lookup for the most specific

# Handle is needed for selabel_lookup
raise ArgumentError, _("Cannot get default context with nil handle") unless handle

# If the file exists we should pass the mode to selabel_lookup for the most specific
# matching. If not, we can pass a mode of 0.
begin
filestat = file_lstat(file)
mode = filestat.mode
rescue Errno::EACCES
mode = 0
rescue Errno::ENOENT
if resource_ensure
mode = get_create_mode(resource_ensure)
else
mode = 0
end
end

retval[1]
retval = Selinux.selabel_lookup(handle, file, mode)
retval == -1 ? nil : retval[1]
end

# Take the full SELinux context returned from the tools and parse it
Expand Down
21 changes: 18 additions & 3 deletions spec/unit/transaction_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,19 @@ def generate
transaction.evaluate
end

it "should call ::pre_resource_eval on provider classes that support it" do
skip if Puppet::Util::Platform.windows?
selinux = double('selinux', is_selinux_enabled: true)
stub_const('Selinux', selinux)

resource = Puppet::Type.type(:file).new(:path => make_absolute("/tmp/foo"))
transaction = transaction_with_resource(resource)

expect(resource.provider.class).to receive(:pre_resource_eval)

transaction.evaluate
end

it "should abort the transaction on failure" do
expect(resource).to receive(:pre_run_check).and_raise(Puppet::Error, spec_exception)

Expand Down Expand Up @@ -758,14 +771,16 @@ def post_resource_eval
transaction.evaluate
end

it "should call Selinux.matchpathcon_fini in case Selinux is enabled ", :if => Puppet.features.posix? do
selinux = double('selinux', is_selinux_enabled: true, matchpathcon_fini: nil)
it "should call Selinux.selinux_close in case Selinux is enabled ", :if => Puppet.features.posix? do
selinux = double('selinux', is_selinux_enabled: true, selinux_close: nil)
stub_const('Selinux', selinux)

resource = Puppet::Type.type(:file).new(:path => make_absolute("/tmp/foo"))
transaction = transaction_with_resource(resource)

expect(Selinux).to receive(:matchpathcon_fini)
handle = double('selinux_handle')
allow(selinux).to receive(:selabel_open).and_return(handle)
expect(Selinux).to receive(:selinux_close).with(handle)
expect(Puppet::Util::SELinux).to receive(:selinux_support?).and_return(true)

transaction.evaluate
Expand Down
9 changes: 5 additions & 4 deletions spec/unit/type/file/selinux_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#skip if Puppet::Util::Platform.windows?
require 'spec_helper'

[:seluser, :selrole, :seltype, :selrange].each do |param|
property = Puppet::Type.type(:file).attrclass(param)
describe property do
describe property, :unless Puppet::Util::Platform.windows? do
include PuppetSpec::Files

before do
Expand Down Expand Up @@ -50,13 +51,13 @@
end

it "should handle no default gracefully" do
expect(@sel).to receive(:get_selinux_default_context).with(@path, :file).and_return(nil)
expect(@sel).to receive(:get_selinux_default_context_with_handle).with(@path, :file, nil).and_return(nil)
expect(@sel.default).to be_nil
end

it "should be able to detect matchpathcon defaults" do
it "should be able to detect default context" do
allow(@sel).to receive(:debug)
expect(@sel).to receive(:get_selinux_default_context).with(@path, :file).and_return("user_u:role_r:type_t:s0")
expect(@sel).to receive(:get_selinux_default_context_with_handle).with(@path, :file, nil).and_return("user_u:role_r:type_t:s0")
expectedresult = case param
when :seluser; "user_u"
when :selrole; "role_r"
Expand Down
31 changes: 31 additions & 0 deletions spec/unit/util/selinux_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,37 @@
end
end

describe "get_selinux_default_context_with_handle" do
it "should return a context if a default context exists" do
without_partial_double_verification do
expect(self).to receive(:selinux_support?).and_return(true)
fstat = double('File::Stat', :mode => 0)
expect(Puppet::FileSystem).to receive(:lstat).with('/foo').and_return(fstat)
expect(self).to receive(:find_fs).with("/foo").and_return("ext3")
hnd = double("SWIG::TYPE_p_selabel_handle")
expect(Selinux).to receive(:selabel_lookup).with(hnd, '/foo', 0).and_return([0, "user_u:role_r:type_t:s0"])
expect(get_selinux_default_context_with_handle("/foo", hnd)).to eq("user_u:role_r:type_t:s0")
end
end

it "should raise an ArgumentError when handle is nil" do
allow(self).to receive(:selinux_support?).and_return(true)
allow(self).to receive(:selinux_label_support?).and_return(true)
expect{get_selinux_default_context_with_handle("/foo", nil)}.to raise_error(ArgumentError, /Cannot get default context with nil handle/)
end

it "should return nil if there is no SELinux support" do
expect(self).to receive(:selinux_support?).and_return(false)
expect(get_selinux_default_context_with_handle("/foo", nil)).to be_nil
end

it "should return nil if selinux_label_support returns false" do
expect(self).to receive(:selinux_support?).and_return(true)
expect(self).to receive(:find_fs).with("/foo").and_return("nfs")
expect(get_selinux_default_context_with_handle("/foo", nil)).to be_nil
end
end

describe "parse_selinux_context" do
it "should return nil if no context is passed" do
expect(parse_selinux_context(:seluser, nil)).to be_nil
Expand Down

0 comments on commit c3b7af8

Please sign in to comment.