Skip to content

Commit

Permalink
Use libselinux for providers and directly parse text files
Browse files Browse the repository at this point in the history
  • Loading branch information
oranenj committed Jan 31, 2017
1 parent 0b4a314 commit 89172d1
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 118 deletions.
54 changes: 25 additions & 29 deletions lib/puppet/provider/selinux_fcontext/semanage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

@file_types = {
'all files' => 'a',
'directory' => 'd',
'character device' => 'c',
'block device' => 'b',
'symbolic link' => 'l',
'named pipe' => 'p',
'regular file' => 'f',
'socket' => 's'
'-d' => 'd',
'-c' => 'c',
'-b' => 'b',
'-l' => 'l',
'-p' => 'p',
'--' => 'f',
'-s' => 's'
}

def self.file_type_map(val)
Expand All @@ -33,33 +33,23 @@ def self.file_type_map(val)

def self.type_param(file_type)
return file_type unless @old_semanage
case file_type
when 'a'
'all files'
when 'f'
'--'
else
"-#{file_type}"
end
@file_types.invert[file_type]
end

def self.parse_semanage_lines(lines)
def self.parse_fcontext_lines(lines)
ret = []
lines.each do |line|
break if line =~ %r{^SELinux(.*)Equivalence(.*)}
next if line =~ %r{^SELinux}
next if line.strip.empty?
# This is a bit of a hack... split only if >1 whitespace to get the
# entirety of the middle field which can have a single space.
# The output should never be so tight that there's only one space
# between the first and the second fields...
split = line.split(%r{\s{2,}})
path_spec = split.shift.strip
file_type = split.shift.strip
context_spec = split.shift.strip
next if line =~ %r{^#}
split = line.split(%r{\s+})
if split.length == 2
path_spec, context_spec = split
file_type = 'all files'
else
path_spec, file_type, context_spec = split
end
user, role, type, range = context_spec.split(':')
if context_spec == '<<None>>'
# semanage is weird...
if context_spec == '<<none>>'
type = '<<none>>'
user = range = role = nil
end
Expand All @@ -80,7 +70,13 @@ def self.instances
# With fcontext, we only need to care about local customisations as they
# should never conflict with system policy
# Old semanage fails with --locallist, use -C
parse_semanage_lines(semanage('fcontext', '--list', '-C').split("\n"))
local_fcs = Selinux.selinux_file_context_local_path
if File.exist? local_fcs
parse_fcontext_lines(File.readlines(local_fcs))
else
# no file, no local contexts
[]
end
end

def self.prefetch(resources)
Expand Down
24 changes: 12 additions & 12 deletions lib/puppet/provider/selinux_fcontext_equivalence/semanage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@

mk_resource_methods

def self.parse_semanage_lines(lines)
def self.parse_fcontext_subs_lines(lines)
ret = []
found_eqs = false
lines.each do |line|
if line =~ %r{^SELinux(.*)Equivalence(.*)}
found_eqs = true
next
end
next unless found_eqs
next if line.strip.empty?
source, _eq, target = line.split(%r{\s+})
next if line =~ %r{^#}
source, target = line.split(%r{\s+})
ret.push(new(ensure: :present,
name: source,
target: target))
Expand All @@ -27,10 +22,15 @@ def self.parse_semanage_lines(lines)
end

def self.instances
# With fcontext, we only need to care about local customisations as they
# should never conflict with system policy
# --locallist does not work on older semanage, use -C
parse_semanage_lines(semanage('fcontext', '--list', '-C').split("\n"))
# Allow this to fail with an exception if it does not exist
path = Selinux.selinux_file_context_subs_path
if File.exist? path
lines = File.readlines(path)
parse_fcontext_subs_lines(lines)
else
# No file, no equivalences:
[]
end
end

def self.prefetch(resources)
Expand Down
132 changes: 68 additions & 64 deletions spec/unit/puppet/provider/selinux_fcontext/semanage_spec.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
require 'spec_helper'

# stub the selinux module for tests in travis
module Selinux
def selinux_file_context_local_path
'spec_dummy'
end
end

semanage_provider = Puppet::Type.type(:selinux_fcontext).provider(:semanage)
fcontext = Puppet::Type.type(:selinux_fcontext)

file_types = {
'all files' => 'a',
'directory' => 'd',
'character device' => 'c',
'block device' => 'b',
'symbolic link' => 'l',
'named pipe' => 'p',
'regular file' => 'f',
'socket' => 's'
}

semanage_output_template = <<-EOS
SELinux fcontext type Context
fcontexts_local = <<-EOS
# This file is auto-generated by libsemanage
# Do not edit directly.
/foobar THETYPE system_u:object_r:bin_t:s0
/something/else THETYPE <<None>>
SELinux Local fcontext Equivalence
/foobar = /var/lib/whatever
/foobar system_u:object_r:bin_t:s0
/tmp/foobar -d system_u:object_r:boot_t:s0
/something/else -s <<none>>
EOS

describe semanage_provider do
Expand All @@ -31,39 +25,47 @@
let(:facts) do
facts
end
file_types.each do |name, ft|
context "with a single #{name} fcontext" do
before do
semanage_output = semanage_output_template.gsub('THETYPE', name)
described_class.expects(:semanage).with('fcontext', '--list', '-C').returns(semanage_output)
end
it 'returns two resources' do
expect(described_class.instances.size).to eq(2)
end
it 'regular contexts get parsed properly' do
expect(described_class.instances[0].instance_variable_get('@property_hash')).to eq(
ensure: :present,
name: "/foobar_#{ft}",
pathspec: '/foobar',
file_type: ft,
seltype: 'bin_t',
selrole: 'object_r',
seluser: 'system_u',
selrange: 's0'
)
end
it '<<None>> contexts get parsed properly' do
expect(described_class.instances[1].instance_variable_get('@property_hash')).to eq(
ensure: :present,
name: "/something/else_#{ft}",
pathspec: '/something/else',
file_type: ft,
seltype: '<<none>>',
selrole: nil,
seluser: nil,
selrange: nil
)
end
context "with a single #{name} fcontext" do
before do
Selinux.expects(:selinux_file_context_local_path).returns('spec_dummy')
File.expects(:exist?).with('spec_dummy').returns(true)
File.expects(:readlines).with('spec_dummy').returns(fcontexts_local.split("\n"))
end
it 'returns three resources' do
expect(described_class.instances.size).to eq(3)
end
it 'regular contexts get parsed properly' do
expect(described_class.instances[0].instance_variable_get('@property_hash')).to eq(
ensure: :present,
name: '/foobar_a',
pathspec: '/foobar',
file_type: 'a',
seltype: 'bin_t',
selrole: 'object_r',
seluser: 'system_u',
selrange: 's0'
)
end
it '<<none>> contexts get parsed properly' do
expect(described_class.instances[2].instance_variable_get('@property_hash')).to eq(
ensure: :present,
name: '/something/else_s',
pathspec: '/something/else',
file_type: 's',
seltype: '<<none>>',
selrole: nil,
seluser: nil,
selrange: nil
)
end
end
context 'with no fcontexts defined, and no fcontexts.local file' do
before do
Selinux.expects(:selinux_file_context_local_path).returns('spec_dummy')
File.expects(:exist?).with('spec_dummy').returns(false)
end
it 'returns no resources' do
expect(described_class.instances.size).to eq(0)
end
end
context 'Creating with just seltype defined' do
Expand Down Expand Up @@ -94,39 +96,41 @@
file_type: 's',
seltype: 'some_type_t'
),
'/foobar_f' => fcontext.new(
name: '/foobar_f',
file_type: 'f',
'/foobar_a' => fcontext.new(
name: '/foobar_a',
file_type: 'a',
pathspec: '/foobar',
seltype: 'mytype_t',
seluser: 'myuser_u'
) }
end
before do
# prefetch should find the provider parsed from this:
semanage_output = semanage_output_template.gsub('THETYPE', 'regular file')
described_class.expects(:semanage).with('fcontext', '--list', '-C').returns(semanage_output)
Selinux.expects(:selinux_file_context_local_path).returns('spec_dummy')
File.expects(:exist?).with('spec_dummy').returns(true)
File.expects(:readlines).with('spec_dummy').returns(fcontexts_local.split("\n"))
semanage_provider.prefetch(resources)
end
it 'finds provider for /foobar' do
p = resources['/foobar_f'].provider
p = resources['/foobar_a'].provider
expect(p).not_to eq(nil)
end
context 'has the correct attributes' do
let(:p) { resources['/foobar_f'].provider }
it { expect(p.file_type).to eq('f') }
let(:p) { resources['/foobar_a'].provider }
it { expect(p.name).to eq('/foobar_a') }
it { expect(p.file_type).to eq('a') }
it { expect(p.seltype).to eq('bin_t') }
it { expect(p.selrole).to eq('object_r') }
it { expect(p.seluser).to eq('system_u') }
end
it 'can change seltype' do
p = resources['/foobar_f'].provider
described_class.expects(:semanage).with('fcontext', '-m', '-t', 'new_type_t', '-f', 'f', '/foobar')
p = resources['/foobar_a'].provider
described_class.expects(:semanage).with('fcontext', '-m', '-t', 'new_type_t', '-f', 'a', '/foobar')
p.seltype = 'new_type_t'
end
it 'can change seluser' do
p = resources['/foobar_f'].provider
described_class.expects(:semanage).with('fcontext', '-m', '-s', 'unconfined_u', '-t', 'bin_t', '-f', 'f', '/foobar')
p = resources['/foobar_a'].provider
described_class.expects(:semanage).with('fcontext', '-m', '-s', 'unconfined_u', '-t', 'bin_t', '-f', 'a', '/foobar')
p.seluser = 'unconfined_u'
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
require 'spec_helper'

# Provide a dummy Selinux module for the test
module Selinux
def selinux_file_context_subs_path
'spec_dummy'
end
end

semanage_provider = Puppet::Type.type(:selinux_fcontext_equivalence).provider(:semanage)
fc_equiv = Puppet::Type.type(:selinux_fcontext_equivalence)

semanage_output = <<-EOS
SELinux fcontext type Context
/foobar all files system_u:object_r:bin_t:s0
/something/else socket <<None>>
SELinux Local fcontext Equivalence
/foobar = /var/lib/whatever
/opt/my/other/app = /var/lib/whatever
/opt/foo = /usr/share/wordpress
fcontext_equivs = <<-EOS
/foobar /var/lib/whatever
/opt/my/other/app /var/lib/whatever
/opt/foo /usr/share/wordpress
EOS

describe semanage_provider do
Expand All @@ -24,7 +24,9 @@
end
context 'with three custom equivalences' do
before do
described_class.expects(:semanage).with('fcontext', '--list', '-C').returns(semanage_output)
Selinux.expects(:selinux_file_context_subs_path).returns('spec_dummy')
File.expects(:exist?).with('spec_dummy').returns(true)
File.expects(:readlines).with('spec_dummy').returns(fcontext_equivs.split("\n"))
end
it 'returns three resources' do
expect(described_class.instances.size).to eq(3)
Expand All @@ -37,6 +39,15 @@
)
end
end
context 'with no equivalences file' do
before do
Selinux.expects(:selinux_file_context_subs_path).returns('spec_dummy')
File.expects(:exist?).with('spec_dummy').returns(false)
end
it 'returns no resources' do
expect(described_class.instances.size).to eq(0)
end
end
context 'Creating' do
let(:resource) do
res = fc_equiv.new(name: '/foobar', ensure: :present, target: '/something')
Expand Down Expand Up @@ -70,7 +81,9 @@
end
before do
# prefetch should find the provider parsed from this:
described_class.expects(:semanage).with('fcontext', '--list', '-C').returns(semanage_output)
Selinux.expects(:selinux_file_context_subs_path).returns('spec_dummy')
File.expects(:exist?).with('spec_dummy').returns(true)
File.expects(:readlines).with('spec_dummy').returns(fcontext_equivs.split("\n"))
semanage_provider.prefetch(resources)
end
it 'finds provider for /foobar' do
Expand Down

0 comments on commit 89172d1

Please sign in to comment.