Skip to content

Commit

Permalink
Add of pam type/provider with doc and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gregswift authored and raphink committed Apr 22, 2014
1 parent 5da239d commit 3907cf9
Show file tree
Hide file tree
Showing 8 changed files with 538 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -34,6 +34,7 @@ The module adds the following new types:
* `apache_setenv` for updating SetEnv entries in Apache HTTP Server configs
* `kernel_parameter` for adding kernel parameters to GRUB Legacy or GRUB 2 configs
* `nrpe_command` for setting command entries in Nagios NRPE's `nrpe.cfg`
* `pam` for files inside /etc/pam.d/
* `pg_hba` for PostgreSQL's `pg_hba.conf` entries
* `puppet_auth` for authentication rules in Puppet's `auth.conf`
* `shellvar` for shell variables in `/etc/sysconfig` or `/etc/default` etc.
Expand Down
65 changes: 65 additions & 0 deletions docs/examples/pam.md
@@ -0,0 +1,65 @@
## pam provider

This is a custom type and provider supplied by `augeasproviders`.

### manage simple entry

pam { "Set sss entry to system-auth auth":
ensure => present,
service => 'system-auth',
type => 'auth',
control => 'sufficient',
module => 'pam_sss.so',
arguments => 'use_first_pass',
position => 'before module pam_deny.so',
}

### manage same entry but with Augeas xpath

pam { "Set sss entry to system-auth auth":
ensure => present,
service => 'system-auth',
type => 'auth',
control => 'sufficient',
module => 'pam_sss.so',
arguments => 'use_first_pass',
position => 'before *[type="auth" and module="pam_deny.so"]',
}

### delete entry

pam { "Remove sss auth entry from system-auth":
ensure => absent,
service => 'system-auth',
type => 'auth',
module => 'pam_sss.so',
}

### delete all references to module in file

pam { "Remove all pam_sss.so from system-auth":
ensure => absent,
service => 'system-auth',
module => 'pam_sss.so',
}

### manage entry in another pam service

pam { "Set cracklib limits in password-auth":
ensure => present,
service => 'password-auth',
type => 'password',
module => 'pam_cracklib.so',
arguments => ['try_first_pass','retry=3', 'minlen=10'],
}

### manage entry like previous but in classic pam.conf

pam { "Set cracklib limits in password-auth":
ensure => present,
service => 'password-auth',
type => 'password',
module => 'pam_cracklib.so',
arguments => ['try_first_pass','retry=3', 'minlen=10'],
target => '/etc/pam.conf',
}
129 changes: 129 additions & 0 deletions lib/puppet/provider/pam/augeas.rb
@@ -0,0 +1,129 @@
# Alternative Augeas-based providers for Puppet
#
# Copyright (c) 2012 Greg Swift
# Licensed under the Apache License, Version 2.0

require File.dirname(__FILE__) + '/../../../augeasproviders/provider'

Puppet::Type.type(:pam).provide(:augeas) do
desc "Uses Augeas API to update an pam parameter"

include AugeasProviders::Provider

# Boolean is the key because they either do or do not provide a
# value for control to work against. Module doesn't work against
# control
PAM_POSITION_ALIASES = {
true => { 'first' => "*[type='%s' and control='%s'][1]",
'last' => "*[type='%s' and control='%s'][last()]",
'module' => "*[type='%s' and module='%s'][1]", },
false => { 'first' => "*[type='%s'][1]",
'last' => "*[type='%s'][last()]", },
}

confine :feature => :augeas

default_file { '/etc/pam.d/system-auth' }

def target(resource = nil)
if resource and resource[:service]
"/etc/pam.d/#{resource[:service]}".chomp('/')
else
super
end
end

lens do |resource|
target(resource) == '/etc/pam.conf' ? 'pamconf.lns' : 'pam.lns'
end

resource_path do |resource|
service = resource[:service]
type = resource[:type]
mod = resource[:module]
if target == '/etc/pam.conf'
"$target/*[service='#{service}' and type='#{type}' and module='#{mod}']"
else
"$target/*[type='#{type}' and module='#{mod}']"
end
end

def self.instances
augopen do |aug|
resources = []
aug.match("$target/*[label()!='#comment']").each do |spath|
optional = aug.match("#{spath}/optional").empty?.to_s.to_sym
type = aug.get("#{spath}/type")
control = aug.get("#{spath}/control")
mod = aug.get("#{spath}/module")
arguments = aug.match("#{spath}/argument").map { |p| aug.get(p) }
entry = {:ensure => :present,
:optional => optional,
:type => type,
:control => control,
:module => mod,
:arguments => arguments}
if target == '/etc/pam.conf'
entry[:service] = aug.get("#{spath}/service")
end
resources << new(entry)
end
resources
end
end

define_aug_method!(:create) do |aug, resource|
path = '01'
entry_path = "$target/#{path}"
# we pull type, control, and position out because we actually
# work with those values, not just reference them in the set section
# type comes to us as a symbol, so needs to be converted to a string
type = resource[:type].to_s
control = resource[:control]
position = resource[:position]
placement, identifier, value = position.split(/ /)
key = !!value
if PAM_POSITION_ALIASES[key].has_key?(identifier)
expr = PAM_POSITION_ALIASES[key][identifier]
expr = key ? expr % [type, value] : expr % [type]
else
# if the identifier is not in the mapping
# we assume that its an xpath and so
# join everything after the placement
identifier = position.split(/ /)[1..-1].join(" ")
expr = identifier
end
aug.insert("$target/#{expr}", path, placement == 'before')
if resource[:optional] == :true
aug.touch("#{entry_path}/optional")
end
if target == '/etc/pam.conf'
aug.set("#{entry_path}/service", resource[:service])
end
aug.set("#{entry_path}/type", type)
aug.set("#{entry_path}/control", control)
aug.set("#{entry_path}/module", resource[:module])
resource[:arguments].each do |argument|
aug.set("#{entry_path}/argument[last()+1]", argument)
end
end

define_aug_method(:optional) do |aug, resource|
aug.match("$resource/optional").empty?.to_s.to_sym
end

define_aug_method!(:optional=) do |aug, resource, value|
if resource[:optional] == :true
if aug.match("$resource/optional").empty?
aug.clear("$resource/optional")
end
else
aug.rm("$resource/optional")
end
end

attr_aug_accessor(:control)

attr_aug_accessor(:arguments, :type => :array, :label => 'argument')

end
99 changes: 99 additions & 0 deletions lib/puppet/type/pam.rb
@@ -0,0 +1,99 @@
# Manages settings in PAM service files
#
# Copyright (c) 2012 Greg Swift
# Licensed under the Apache License, Version 2.0

require File.dirname(__FILE__) + '/../../augeasproviders/type'

Puppet::Type.newtype(:pam) do
@doc = "Manages settings in an PAM service files.
The resource name is a descriptive string only due to the non-uniqueness of any single paramter."

extend AugeasProviders::Type

positionable

def munge_boolean(value)
case value
when true, "true", :true
:true
when false, "false", :false
:false
else
fail("munge_boolean only takes booleans")
end
end

newparam(:name) do
desc "The name of the resource, has no bearing on anything"
isnamevar
end

newparam(:service) do
desc "The PAM service this entry will be placed in. Typically this is the same as the
filename under /etc/pam.d"
end

newparam(:type) do
desc "The PAM service type of the setting: account, auth, password, session."
newvalues(:account, :auth, :password, :session)
end

newparam(:module) do
desc "The name of the specific PAM module to load."
end

newproperty(:optional, :boolean => true) do
desc "Whether failure to load the module will break things"

newvalue(:true)
newvalue(:false)

munge do |value|
@resource.munge_boolean(value)
end
end

newproperty(:arguments, :array_matching => :all) do
desc "Arguments to assign for the module."
end

newproperty(:control) do
desc "Simple or complex definition of the module's behavior on failure."
end

newparam(:position) do
desc "A three part text field that providers the placement position of an entry.
The field consists of `placement identifier value`
Placement can be either `before` or `after`
Identifier can be either `first`, `last`, `module`, or an Augeas xpath
Value is matched as follows:
With `first` and `last` match `value` to the `control` field, can be blank for absolute positioning.
With `module` matches the `module` field of the associated line, can not be blank.
With an Augeas xpath this field will be ignored, and should be blank.
"
defaultto('before last')
validate do |value|
placement, identifier, val = value.split(/ /)
unless ['before', 'after'].include? placement
raise ArgumentError, "%s is not a valid placement in position" % placement
end
# Don't do validation of the second field because we are supporting xpath
# and thats hard to validate
# unless ['first', 'last', 'module'].include? identifier or identifier =~ //
# raise ArgumentError, "%s is not a valid identifier in position" % indentifier
# end
if val.nil? and identifier == 'module'
raise ArgumentError, "Value must be set if you are matching on module"
end
end
end

newparam(:target) do
desc "The file in which to store the settings, defaults to `/etc/pam.d/{service}`."
end

end
27 changes: 27 additions & 0 deletions spec/fixtures/unit/puppet/provider/pam/augeas/broken
@@ -0,0 +1,27 @@
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth sufficient pam_sss.so use_first_pass
auth required pam_deny.so

account required pam_unix.so broken_shadow
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account [default=bad success=ok user_unknown=ignore] pam_sss.so
account required pam_permit.so

password requisite pam_pwquality.so try_first_pass retry=3 type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password sufficient pam_sss.so use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session optional pam_mkhomedir.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
session optional pam_sss.so
Empty file.
27 changes: 27 additions & 0 deletions spec/fixtures/unit/puppet/provider/pam/augeas/full
@@ -0,0 +1,27 @@
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required pam_env.so
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth sufficient pam_sss.so use_first_pass
auth required pam_deny.so

account required pam_unix.so broken_shadow
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account [default=bad success=ok user_unknown=ignore] pam_sss.so
account required pam_permit.so

password requisite pam_pwquality.so try_first_pass retry=3 type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password sufficient pam_sss.so use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session optional pam_mkhomedir.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
session optional pam_sss.so

0 comments on commit 3907cf9

Please sign in to comment.