Project: Chef InSpec (Provisioning / Security & Compliance)
File: shadow.md

+++ title = "shadow resource" draft = false gh_repo = "inspec" platform = "linux" [menu] [menu.inspec] title = "shadow" identifier = "inspec/resources/os/shadow.md shadow resource" parent = "inspec/resources/os" +++ Use the `shadow` Chef InSpec audit resource to test the contents of `/etc/shadow`, which contains password details that are readable only by the `root` user. `shadow` is a . Like all plural resources, it functions by performing searches across multiple entries in the shadow file. The format for `/etc/shadow` includes: A username The hashed password for that user The last date a password was changed, as the number of days since Jan 1, 1970 The minimum number of days a password must exist before it may be changed The maximum number of days after which a password must be changed The number of days a user is warned about an expiring password The number of days a user must be inactive before the user account is disabled The date on which a user account was disabled, as the number of days since Jan 1, 1970 These entries are defined as a colon-delimited row in the file, one row per user: username:Gb7crrO5CDF.:10063:0:99999:7::: The `shadow` resource understands this format, allows you to search on the fields, and exposes the selected users' properties. {{< readfile file="content/inspec/reusable/md/inspec_installation.md" >}} This resource first became available in v1.0.0 of InSpec. The `shadow` resource takes one optional parameter: the path to the shadow file. If omitted, `/etc/shadow` is assumed. describe shadow do its('count') { should eq 32 } end describe shadow('/etc/my-custom-place/shadow') do its('count') { should eq 32 } end A `shadow` resource block uses `where` to filter entries from the shadow file. If `where` is omitted, all entries are selected. describe shadow do its('users') { shouldnot include 'forbiddenuser' } end describe shadow.where(user: 'root') do its('count') { should eq 1 } end Use `where` to match any of the supported . `where` has a method form for simple equality and a block form for more complex queries. describe shadow.where(user: 'root') do its ('count') { should eq 1 } end describe shadow.where(user: /^smb/) do its ('count') { should eq 2 } end describe shadow.where { expiry_date > 0 } do its('count') { should eq 0 } its('users') { should be_empty } end Use `where` with to show all users (that aren't disabled or locked) without SHA512 hashed passwords. bad_users = inspec.shadow.where { password !~ /^[!]$|^\$6\$./ }.users describe 'Password hashes in /etc/shadow' do it 'should only contain SHA512 hashes' do message = "Users without SHA512 hashes: #{bad_users.join(', ')}" expect(badusers).to beempty, message end end As a , all of `shadow`'s properties return lists (that is, Ruby Arrays). `include` and `be_empty` are two useful matchers when working with lists. You can also perform manipulation of the lists, such as calling `uniq`, `sort`, `count`, `first`, `last`, `min`, and `max`. A list of strings, representing the usernames matched by the filter. describe shadow its('users') { should include 'root' } end A list of strings, representing the encrypted password strings for entries matched by the `where` filter. Each string may not be an encrypted password, but rather a `` or similar which indicates that direct logins are not allowed. Different operating systems use different flags here (such as `LK*` to indicate the account is locked). describe shadow.where(user: /adm$/) do its('passwords.uniq.first') { should cmp '*' } its('passwords.uniq.count') { should eq 1 } end A list of integers, indicating the number of days since Jan 1, 1970 since the password for each matching entry was

describe shadow do its('last_changes.min') { should be < Date.today - 90 - Date.new(1970,1,1) } end A list of integers reflecting the minimum number of days a password must exist, before it may be changed, for the users that matched the filter. describe shadow do its('min_days.uniq') { should eq [0] } end A list of integers reflecting the maximum number of days after which the password must be changed for each user matching the filter. describe shadow do its('max_days.max') { should be < 90 } end A list of integers reflecting the number of days a user is warned about an expiring password for each user matching the filter. describe shadow do its('warn_days.uniq.count') { should eq 1 } its('warn_days.uniq.first') { should eq 7 } end A list of integers reflecting the number of days a user must be inactive before the user account is disabled for each user matching the filter. describe shadow.where { user !~ /adm$/ } do its('inactive_days.max') { should be <= 14 } end A list of integers reflecting the number of days since Jan 1, 1970 that a user account has been disabled, for each user matching the filter. Value is `nil` if the account has not expired. describe shadow do its('expirydates.compact') { should beempty } end The `count` property tests the number of records that the filter matched. describe shadow.user('root') do its('count') { should eq 1 } end You may use any of these filter criteria with the `where` function. They are named after the columns in the shadow file. Each has a related list . The string username of a user. Always present. Not required to be unique. describe shadow.where(user: /adm$/) do its('password.uniq') { should eq ['*'] } end The encrypted password strings, or an account status string. Each string may not be an encrypted password, but rather a `` or similar which indicates that direct logins are not allowed. Different operating systems use other flags here (such as `LK*` to indicate the account is locked). describe shadow.where(password: 'LK') do its('users') { should include 'nobody' } end An integer reflecting the number of days since Jan 1, 1970 since the user's password was changed. describe shadow.where { last_change > Date.today - 90 - Date.new(1970,1,1) } do its('users') { should be_empty } end An integer reflecting the minimum number of days a user is required to wait before changing their password again. describe shadow.where { min_days > 0 } do its('users') { should be_empty } end An integer reflecting the maximum number of days a user may go without changing their password. describe shadow.where { max_days != 30 } do its('users') { should be_empty } end An integer reflecting the number of days before a password expiration that a user receives an alert. describe shadow.where { warn_days != 7 } do its('users') { should be_empty } end An integer reflecting the number of days that must pass before a user who has not logged in will be disabled. describe shadow.where { inactivedays.nil? || inactivedays > 14 } do its('users') { should be_empty } end An integer reflecting the number of days since Jan 1, 1970 on which the user was disabled. The `expiry_date` criterion is `nil` for enabled users. describe shadow.where { !expiry_date.nil? } do its('users') { should be_empty } end describe shadow.where { !expirydate.nil? && expirydate - Date.new(1970,1,1) > 14} do its('users') { should be_empty } end This resource has no resource-specific matchers. For a full list of available matchers, please visit our .