mirror of
https://github.com/inspec/inspec
synced 2024-11-24 13:43:09 +00:00
Merge pull request #467 from chef/dr/passwd-filter
add filters to passwd resource + deprecate old accessors
This commit is contained in:
commit
89b12577c5
6 changed files with 170 additions and 101 deletions
|
@ -3102,19 +3102,22 @@ A ``passwd`` |inspec resource| block declares one (or more) users and associated
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
describe passwd do
|
describe passwd do
|
||||||
its('matcher') { should eq 0 }
|
its(:users) { should_not include 'forbidden_user' }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe passwd.uid(filter) do
|
describe passwd.uid(0) do
|
||||||
its(:username) { should eq 'root' }
|
its(:users) { should cmp 'root' }
|
||||||
its(:count) { should eq 1 }
|
its(:count) { should eq 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
where
|
where
|
||||||
|
|
||||||
* ``gids``, ``passwords``, ``uids``, and ``usernames`` are valid matchers for ``passwd``
|
* ``users``, ``uids``, ``gids``, ``passwords``, ``homes``, and ``shells`` are valid accessors for ``passwd``
|
||||||
* ``filter`` is a filter for a specific uid
|
* All of these matchers can be given an argument to filter by, for example: ``passwd.users(/name/)``
|
||||||
* ``count``, ``uid``, ``username`` are valid matchers for ``passwd.uid(userid)``
|
* There is an explicit method to filter by (``filter``) which can take multiple arguments at once
|
||||||
|
* ``count`` retrieves the number of entries
|
||||||
|
* ``lines`` provides raw passwd lines
|
||||||
|
* ``params`` returns an array of maps for all entries
|
||||||
|
|
||||||
|
|
||||||
Matchers for ``passwd``
|
Matchers for ``passwd``
|
||||||
|
@ -3127,7 +3130,8 @@ The ``gids`` matcher tests if the group indentifiers in the test match group ide
|
||||||
|
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
its('gids') { should eq 1234 }
|
its('gids') { should include 1234 }
|
||||||
|
its('gids') { should cmp 0 }
|
||||||
|
|
||||||
passwords
|
passwords
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
@ -3141,7 +3145,8 @@ For example:
|
||||||
|
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
its('passwords') { should eq 'x' }
|
its('passwords') { should eq ['x'] }
|
||||||
|
its('passwords') { should cmp '*' }
|
||||||
|
|
||||||
uids
|
uids
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
@ -3151,42 +3156,25 @@ The ``uids`` matcher tests if the user indentifiers in the test match user ident
|
||||||
|
|
||||||
its('uids') { should eq ['1234', '1235'] }
|
its('uids') { should eq ['1234', '1235'] }
|
||||||
|
|
||||||
usernames
|
users
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
The ``usernames`` matcher tests if the usernames in the test match usernames in ``/etc/passwd``:
|
The ``users`` matcher tests if the usernames in the test match usernames in ``/etc/passwd``:
|
||||||
|
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
its('usernames') { should eq ['root', 'www-data'] }
|
its('users') { should_not include 'www-data' }
|
||||||
|
|
||||||
|
|
||||||
Matchers for ``passwd.uid(userid)``
|
|
||||||
-----------------------------------------------------
|
|
||||||
This InSpec audit resource has the following matchers.
|
|
||||||
|
|
||||||
count
|
count
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
The ``count`` matcher tests the number of times the named user appears in ``/etc/passwd``:
|
The ``count`` matcher tests the number of entries in ``/etc/passwd``. It becomes especially useful in conjunction combination with filters:
|
||||||
|
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
|
describe passwd.users('highlander') do
|
||||||
its('count') { should eq 1 }
|
its('count') { should eq 1 }
|
||||||
|
end
|
||||||
|
|
||||||
uid
|
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
The ``uid`` matcher tests if the user identifier in the test matches a user identifier in ``/etc/passwd``:
|
|
||||||
|
|
||||||
.. code-block:: ruby
|
|
||||||
|
|
||||||
its('uid') { should eq 1234 }
|
|
||||||
|
|
||||||
username
|
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
The ``username`` matcher tests if the user name in the test matches a user name in ``/etc/passwd``:
|
|
||||||
|
|
||||||
.. code-block:: ruby
|
|
||||||
|
|
||||||
its('username') { should eq 'root' }
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
-----------------------------------------------------
|
-----------------------------------------------------
|
||||||
|
@ -3197,7 +3185,7 @@ The following examples show how to use this InSpec audit resource.
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
describe passwd do
|
describe passwd do
|
||||||
its('usernames') { should eq ['root', 'www-data'] }
|
its('users') { should eq ['root', 'www-data'] }
|
||||||
its('uids') { should eq [0, 33] }
|
its('uids') { should eq [0, 33] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3205,13 +3193,13 @@ The following examples show how to use this InSpec audit resource.
|
||||||
|
|
||||||
.. code-block:: ruby
|
.. code-block:: ruby
|
||||||
|
|
||||||
describe passwd.uid(0) do
|
describe passwd.uids(0) do
|
||||||
its('username') { should eq 'root' }
|
its('users') { should cmp 'root' }
|
||||||
its('count') { should eq 1 }
|
its('count') { should eq 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe passwd.uid(33) do
|
describe passwd.filter(user: 'www-data') do
|
||||||
its('username') { should eq 'www-data' }
|
its('uids') { should cmp 33 }
|
||||||
its('count') { should eq 1 }
|
its('count') { should eq 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,88 +13,114 @@
|
||||||
# - home directory
|
# - home directory
|
||||||
# - command
|
# - command
|
||||||
|
|
||||||
# usage:
|
|
||||||
#
|
|
||||||
# describe passwd do
|
|
||||||
# its(:usernames) { should eq ['root'] }
|
|
||||||
# its(:uids) { should eq [0] }
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# describe passwd.uid(0) do
|
|
||||||
# its(:username) { should eq 'root' }
|
|
||||||
# its(:count) { should eq 1 }
|
|
||||||
# end
|
|
||||||
|
|
||||||
require 'utils/parser'
|
require 'utils/parser'
|
||||||
|
|
||||||
class Passwd < Inspec.resource(1)
|
class Passwd < Inspec.resource(1)
|
||||||
name 'passwd'
|
name 'passwd'
|
||||||
desc 'Use the passwd InSpec audit resource to test the contents of /etc/passwd, which contains the following information for users that may log into the system and/or as users that own running processes.'
|
desc 'Use the passwd InSpec audit resource to test the contents of /etc/passwd, which contains the following information for users that may log into the system and/or as users that own running processes.'
|
||||||
example "
|
example "
|
||||||
describe passwd.uid(0) do
|
describe passwd do
|
||||||
its('username') { should eq 'root' }
|
its('users') { should_not include 'forbidden_user' }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe passwd.uids(0) do
|
||||||
|
its('users') { should cmp 'root' }
|
||||||
its('count') { should eq 1 }
|
its('count') { should eq 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe passwd.shells(/nologin/) do
|
||||||
|
# find all users with a nologin shell
|
||||||
|
its('users') { should_not include 'my_login_user' }
|
||||||
|
end
|
||||||
"
|
"
|
||||||
|
|
||||||
include PasswdParser
|
include PasswdParser
|
||||||
|
|
||||||
attr_reader :uid
|
attr_reader :uid
|
||||||
attr_reader :parsed
|
attr_reader :params
|
||||||
|
attr_reader :content
|
||||||
|
attr_reader :lines
|
||||||
|
|
||||||
def initialize(path = nil)
|
def initialize(path = nil, opts = nil)
|
||||||
|
opts ||= {}
|
||||||
@path = path || '/etc/passwd'
|
@path = path || '/etc/passwd'
|
||||||
@content = inspec.file(@path).content
|
@content = opts[:content] || inspec.file(@path).content
|
||||||
@parsed = parse_passwd(@content)
|
@lines = @content.to_s.split("\n")
|
||||||
|
@filters = opts[:filters] || ''
|
||||||
|
@params = parse_passwd(@content)
|
||||||
end
|
end
|
||||||
|
|
||||||
# call passwd().uid(0)
|
def filter(hm = {})
|
||||||
# returns a seperate object with reference to this object
|
return self if hm.nil? || hm.empty?
|
||||||
def uid(uid)
|
res = @params
|
||||||
PasswdUid.new(self, uid)
|
filters = ''
|
||||||
|
hm.each do |attr, condition|
|
||||||
|
condition = condition.to_s if condition.is_a? Integer
|
||||||
|
filters += " #{attr} = #{condition.inspect}"
|
||||||
|
res = res.find_all do |line|
|
||||||
|
case line[attr.to_s]
|
||||||
|
when condition
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
content = res.map { |x| x.values.join(':') }.join("\n")
|
||||||
|
Passwd.new(@path, content: content, filters: @filters + filters)
|
||||||
end
|
end
|
||||||
|
|
||||||
def usernames
|
def usernames
|
||||||
map_data('name')
|
warn '[DEPRECATION] `passwd.usernames` is deprecated. Please use `passwd.users` instead. It will be removed in version 1.0.0.'
|
||||||
|
users
|
||||||
end
|
end
|
||||||
|
|
||||||
def passwords
|
def username
|
||||||
map_data('password')
|
warn '[DEPRECATION] `passwd.user` is deprecated. Please use `passwd.users` instead. It will be removed in version 1.0.0.'
|
||||||
|
users[0]
|
||||||
end
|
end
|
||||||
|
|
||||||
def uids
|
def uid(x)
|
||||||
map_data('uid')
|
warn '[DEPRECATION] `passwd.uid(arg)` is deprecated. Please use `passwd.uids(arg)` instead. It will be removed in version 1.0.0.'
|
||||||
|
uids(x)
|
||||||
end
|
end
|
||||||
|
|
||||||
def gids
|
def users(name = nil)
|
||||||
map_data('gid')
|
name.nil? ? map_data('user') : filter(user: name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def passwords(password = nil)
|
||||||
|
password.nil? ? map_data('password') : filter(password: password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def uids(uid = nil)
|
||||||
|
uid.nil? ? map_data('uid') : filter(uid: uid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def gids(gid = nil)
|
||||||
|
gid.nil? ? map_data('gid') : filter(gid: gid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def homes(home = nil)
|
||||||
|
home.nil? ? map_data('home') : filter(home: home)
|
||||||
|
end
|
||||||
|
|
||||||
|
def shells(shell = nil)
|
||||||
|
shell.nil? ? map_data('shell') : filter(shell: shell)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
'/etc/passwd'
|
f = @filters.empty? ? '' : ' with'+@filters
|
||||||
|
"/etc/passwd#{f}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def count
|
||||||
|
@params.length
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def map_data(id)
|
def map_data(id)
|
||||||
@parsed.map {|x|
|
@params.map { |x| x[id] }
|
||||||
x[id]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# object that hold a specifc uid view on passwd
|
|
||||||
class PasswdUid
|
|
||||||
def initialize(passwd, uid)
|
|
||||||
@passwd = passwd
|
|
||||||
@users = @passwd.parsed.select { |x| x['uid'] == uid.to_s }
|
|
||||||
end
|
|
||||||
|
|
||||||
def username
|
|
||||||
@users.at(0)['name']
|
|
||||||
end
|
|
||||||
|
|
||||||
def count
|
|
||||||
@users.size
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@ module PasswdParser
|
||||||
def parse_passwd_line(line)
|
def parse_passwd_line(line)
|
||||||
x = line.split(':')
|
x = line.split(':')
|
||||||
{
|
{
|
||||||
'name' => x.at(0),
|
'user' => x.at(0),
|
||||||
'password' => x.at(1),
|
'password' => x.at(1),
|
||||||
'uid' => x.at(2),
|
'uid' => x.at(2),
|
||||||
'gid' => x.at(3),
|
'gid' => x.at(3),
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
root:x:0:0:root:/root:/bin/bash
|
root:x:0:0:root:/root:/bin/bash
|
||||||
www-data:x:33:33:www-data:/var/www:/bin/sh
|
www-data:x:33:133:www-data:/var/www:/bin/sh
|
||||||
|
|
|
@ -6,19 +6,74 @@ require 'helper'
|
||||||
require 'inspec/resource'
|
require 'inspec/resource'
|
||||||
|
|
||||||
describe 'Inspec::Resources::Passwd' do
|
describe 'Inspec::Resources::Passwd' do
|
||||||
it 'verify passwd parsing' do
|
let(:passwd) { load_resource('passwd') }
|
||||||
resource = load_resource('passwd')
|
it 'retrieve users via field' do
|
||||||
_(resource.usernames).must_equal %w{root www-data}
|
_(passwd.users).must_equal %w{root www-data}
|
||||||
_(resource.uids).must_equal %w{0 33}
|
end
|
||||||
|
|
||||||
# verify root passwd resource
|
it 'retrieve uids via field' do
|
||||||
root = resource.uid(0)
|
_(passwd.uids).must_equal %w{0 33}
|
||||||
_(root.username).must_equal 'root'
|
end
|
||||||
_(root.count).must_equal 1
|
|
||||||
|
|
||||||
# verify www-data resource
|
it 'retrieve gids via field' do
|
||||||
www = resource.uid(33)
|
_(passwd.gids).must_equal %w{0 133}
|
||||||
_(www.username).must_equal 'www-data'
|
end
|
||||||
_(www.count).must_equal 1
|
|
||||||
|
it 'retrieve passwords via field' do
|
||||||
|
_(passwd.passwords).must_equal %w{x x}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'retrieve login-shells via field' do
|
||||||
|
_(passwd.shells).must_equal %w{/bin/bash /bin/sh}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'access all lines of the file' do
|
||||||
|
_(passwd.lines).must_equal %w{root:x:0:0:root:/root:/bin/bash www-data:x:33:133:www-data:/var/www:/bin/sh}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'access all params of the file' do
|
||||||
|
_(passwd.params[1]).must_equal({"user"=>"www-data", "password"=>"x", "uid"=>"33", "gid"=>"133", "desc"=>"www-data", "home"=>"/var/www", "shell"=>"/bin/sh"})
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'filter by uid == 0' do
|
||||||
|
let(:child) { passwd.uids(0) }
|
||||||
|
it 'creates a new passwd instance' do
|
||||||
|
_(child.content).must_equal 'root:x:0:0:root:/root:/bin/bash'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prints a nice to_s string' do
|
||||||
|
_(child.to_s).must_equal '/etc/passwd with uid = "0"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'retrieves singular elements instead of arrays when filter has only one entry' do
|
||||||
|
_(child.users).must_equal ['root']
|
||||||
|
_(child.count).must_equal 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'filter via name =~ /^www/' do
|
||||||
|
let(:child) { passwd.users(/^www/) }
|
||||||
|
it 'filters by user via name (regex)' do
|
||||||
|
_(child.users).must_equal ['www-data']
|
||||||
|
_(child.count).must_equal 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prints a nice to_s string' do
|
||||||
|
_(child.to_s).must_equal '/etc/passwd with user = /^www/'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'deprecated calls' do
|
||||||
|
it 'retrieves a username via uid' do
|
||||||
|
_(passwd.uid(0).username).must_equal 'root'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'retrieves a usercount via uid' do
|
||||||
|
_(passwd.uid(0).count).must_equal 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'retrieves usernames' do
|
||||||
|
_(passwd.usernames).must_equal ['root', 'www-data']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe PasswdParser do
|
||||||
|
|
||||||
it 'parses a valid passwd line' do
|
it 'parses a valid passwd line' do
|
||||||
info = [{
|
info = [{
|
||||||
"name"=>"root",
|
"user"=>"root",
|
||||||
"password"=>"x",
|
"password"=>"x",
|
||||||
"uid"=>"0",
|
"uid"=>"0",
|
||||||
"gid"=>"0",
|
"gid"=>"0",
|
||||||
|
|
Loading…
Reference in a new issue