diff --git a/README.md b/README.md index f12a5f0..e89793c 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,223 @@ # puppet-sudo [![Build Status](https://secure.travis-ci.org/saz/puppet-sudo.png)](http://travis-ci.org/saz/puppet-sudo) https://github.com/saz/puppet-sudo Manage sudo configuration via Puppet ### Supported Puppet versions * Puppet >= 4 * Last version supporting Puppet 3: v4.2.0 ### Supported OS Some family and some specific os are supported by this module * debian osfamily (debian, ubuntu, kali, ...) * redhat osfamily (redhat, centos, fedora, ...) * suse osfamily (suse, opensuse, ...) * solaris osfamily (Solaris, OmniOS, SmartOS, ...) * freebsd osfamily * openbsd osfamily * aix osfamily * darwin osfamily * gentoo operating system * archlinux operating system * amazon operating system ### Gittip [![Support via Gittip](https://rawgithub.com/twolfson/gittip-badge/0.2.0/dist/gittip.png)](https://www.gittip.com/saz/) ## Usage ### WARNING **This module will purge your current sudo config** If this is not what you're expecting, set `purge` and/or `config_file_replace` to **false** ### Install sudo with default sudoers #### Purge current sudo config ```puppet class { 'sudo': } ``` #### Purge sudoers.d directory, but leave sudoers file as it is ```puppet class { 'sudo': config_file_replace => false, } ``` #### Leave current sudo config as it is ```puppet class { 'sudo': purge => false, config_file_replace => false, } ``` #### Use LDAP along with sudo Sudo do not always include by default the support for LDAP. On Debian and Ubuntu a special package sudo-ldap will be used. On Gentoo there is also the needing to include [puppet portage module by Gentoo](https://forge.puppetlabs.com/gentoo/portage). If not present, only a notification will be shown. ```puppet class { 'sudo': ldap_enable => true, } ``` ### Adding sudoers configuration #### Using Code ```puppet class { 'sudo': } sudo::conf { 'web': source => 'puppet:///files/etc/sudoers.d/web', } sudo::conf { 'admins': priority => 10, content => '%admins ALL=(ALL) NOPASSWD: ALL', } sudo::conf { 'joe': priority => 60, source => 'puppet:///files/etc/sudoers.d/users/joe', } ``` #### Using Hiera A hiera hash may be used to assemble the sudoers configuration. Hash merging is also enabled, which supports layering the configuration settings. Examples using: - YAML backend - an environment called __production__ - a __/etc/puppet/hiera.yaml__ hierarchy configuration: ```yaml :hierarchy: - "%{environment}" - "defaults" ``` ##### Load module -###### Using Puppet version 3+ - Load the module via Puppet Code or your ENC. ```puppet include sudo ``` -###### Using Puppet version 2.7+ - -After [Installing Hiera](http://docs.puppetlabs.com/hiera/1/installing.html): - -- Load the `sudo` and `sudo::configs` modules via Puppet Code or your ENC. - -```puppet - include sudo - include sudo::configs -``` - ##### Configure Hiera YAML __(defaults.yaml)__ These defaults will apply to all systems. ```yaml sudo::configs: 'web': 'source' : 'puppet:///files/etc/sudoers.d/web' 'admins': 'content' : '%admins ALL=(ALL) NOPASSWD: ALL' 'priority' : 10 'joe': 'priority' : 60 'source' : 'puppet:///files/etc/sudoers.d/users/joe' ``` ##### Configure Hiera YAML __(production.yaml)__ This will only apply to the production environment. In this example we are: - inheriting/preserving the __web__ configuration - overriding the __admins__ configuration - removing the __joe__ configuration - adding the __bill__ template ```yaml +lookup_options: + sudo::configs: + merge: + strategy: deep + merge_hash_arrays: true + sudo::configs: 'admins': 'content' : "%prodadmins ALL=(ALL) NOPASSWD: ALL" 'priority' : 10 'joe': 'ensure' : 'absent' 'source' : 'puppet:///files/etc/sudoers.d/users/joe' 'bill': 'template' : "mymodule/bill.erb" ``` -If you have Hiera version >= 1.2.0 and enable [Hiera Deeper Merging](http://docs.puppetlabs.com/hiera/1/lookup_types.html#deep-merging-in-hiera--120) you may conditionally override any setting. - In this example we are: - inheriting/preserving the __web__ configuration - overriding the __admins:content__ setting - inheriting/preserving the __admins:priority__ setting - inheriting/preserving the __joe:source__ and __joe:priority__ settings - removing the __joe__ configuration - adding the __bill__ template ```yaml +lookup_options: + sudo::configs: + merge: + strategy: deep + merge_hash_arrays: true + sudo::configs: 'admins': 'content' : "%prodadmins ALL=(ALL) NOPASSWD: ALL" 'joe': 'ensure' : 'absent' 'bill': 'template' : "mymodule/bill.erb" ``` ##### Set a custom name for the sudoers file In some edge cases, the automatically generated sudoers file name is insufficient. For example, when an application generates a sudoers file with a fixed file name, using this class with the purge option enabled will always delete the custom file and adding it manually will generate a file with the right content, but the wrong name. To solve this, you can use the ```sudo_file_name``` option to manually set the desired file name. ```puppet sudo::conf { "foreman-proxy": ensure => "present", source => "puppet:///modules/sudo/foreman-proxy", sudo_file_name => "foreman-proxy", } ``` ### sudo::conf / sudo::configs notes * One of content or source must be set. * Content may be an array, string will be added with return carriage after each element. * In order to properly pass a template() use template instead of content, as hiera would run template function otherwise. ## sudo class parameters | Parameter | Type | Default | Description | | :-------------- | :------ |:----------- | :---------- | | enable | boolean | true | Set this to remove or purge all sudoers configs | | package | string | OS specific | Set package name _(for unsupported platforms)_ | | package_ensure | string | present | latest, absent, or a specific package version | | package_source | string | OS specific | Set package source _(for unsupported platforms)_ | | purge | boolean | true | Purge unmanaged files from config_dir | | purge_ignore | string | undef | Files excluded from purging in config_dir | | config_file | string | OS specific | Set config_file _(for unsupported platforms)_ | | config_file_replace | boolean | true | Replace config file with module config file | | includedirsudoers | boolean | OS specific | Add #includedir /etc/sudoers.d with augeas | | config_dir | string | OS specific | Set config_dir _(for unsupported platforms)_ | | content | string | OS specific | Alternate content file location | | ldap_enable | boolean | false | Add support to LDAP | +| configs | hash | {} | A hash of sudo::conf's | ## sudo::conf class / sudo::configs hash parameters | Parameter | Type | Default | Description | | :-------------- | :----- |:----------- | :---------- | | ensure | string | present | present or absent | | priority | number | 10 | file name prefix | | content | string | undef | content of configuration snippet | | source | string | undef | source of configuration snippet | | template | string | undef | template of configuration snippet | | sudo_config_dir | string | OS Specific | configuration snippet directory _(for unsupported platforms)_ | | sudo_file_name | string | undef | custom file name for sudo file in sudoers directory | diff --git a/manifests/conf.pp b/manifests/conf.pp index 1e81813..5f7de97 100644 --- a/manifests/conf.pp +++ b/manifests/conf.pp @@ -1,141 +1,138 @@ # Define: sudo::conf # # This module manages sudo configurations # # Parameters: # [*ensure*] # Ensure if present or absent. # Default: present # # [*priority*] # Prefix file name with $priority # Default: 10 # # [*content*] # Content of configuration snippet. # Default: undef # # [*source*] # Source of configuration snippet. # Default: undef # # [*sudo_config_dir*] # Where to place configuration snippets. # Only set this, if your platform is not supported or # you know, what you're doing. # Default: auto-set, platform specific # # Actions: # Installs sudo configuration snippets # # Requires: # Class sudo # # Sample Usage: # sudo::conf { 'admins': # source => 'puppet:///files/etc/sudoers.d/admins', # } # # [Remember: No empty lines between comments and class definition] define sudo::conf( $ensure = present, $priority = 10, $content = undef, $source = undef, $template = undef, $sudo_config_dir = undef, $sudo_file_name = undef, $sudo_syntax_path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' ) { - include ::sudo + include ::sudo # Hack to allow the user to set the config_dir from the # sudo::config parameter, but default to $sudo::params::config_dir # if it is not provided. $sudo::params isn't included before # the parameters are loaded in. $sudo_config_dir_real = $sudo_config_dir ? { undef => $sudo::config_dir, $sudo_config_dir => $sudo_config_dir } # sudo skip file name that contain a "." $dname = regsubst($name, '\.', '-', 'G') if size("x${priority}") == 2 { $priority_real = "0${priority}" } else { $priority_real = $priority } # build current file name with path if $sudo_file_name != undef { $cur_file = "${sudo_config_dir_real}/${sudo_file_name}" } else { $cur_file = "${sudo_config_dir_real}/${priority_real}_${dname}" } # replace whitespace in file name $cur_file_real = regsubst($cur_file, '\s+', '_', 'G') - Class['sudo'] -> Sudo::Conf[$name] - if $::osfamily == 'RedHat' { if (versioncmp($::sudoversion, '1.7.2p1') < 0) { warning("Found sudo with version ${::sudoversion}, but at least version 1.7.2p1 is required!") } } if $content != undef { if $content =~ Array { $lines = join($content, "\n") $content_real = "# This file is managed by Puppet; changes may be overwritten\n${lines}\n" } else { $content_real = "# This file is managed by Puppet; changes may be overwritten\n${content}\n" } } elsif $template != undef { $content_real = template($template) } else { $content_real = undef } if $ensure == 'present' { if $sudo::validate_single { $validate_cmd_real = 'visudo -c -f %' } else { $validate_cmd_real = undef } if $sudo::delete_on_error { $notify_real = Exec["sudo-syntax-check for file ${cur_file}"] $delete_cmd = "( rm -f '${cur_file_real}' && exit 1)" } else { $notify_real = Exec["sudo-syntax-check for file ${cur_file}"] $errormsg = "Error on global-syntax-check with file ${cur_file_real}" $delete_cmd = "( echo '${errormsg}' && echo '#${errormsg}' >>${cur_file_real} && exit 1)" } } else { $delete_cmd = '' $notify_real = undef $validate_cmd_real = undef } file { "${priority_real}_${dname}": ensure => $ensure, path => $cur_file_real, owner => 'root', group => $sudo::params::config_file_group, mode => $sudo::params::config_file_mode, source => $source, content => $content_real, notify => $notify_real, + require => File[$sudo_config_dir_real], validate_cmd => $validate_cmd_real, } exec {"sudo-syntax-check for file ${cur_file}": command => "visudo -c || ${delete_cmd}", refreshonly => true, path => $sudo_syntax_path, } - - } diff --git a/manifests/configs.pp b/manifests/configs.pp deleted file mode 100644 index cbf2f82..0000000 --- a/manifests/configs.pp +++ /dev/null @@ -1,26 +0,0 @@ -# Class: sudo::configs -# -# This class enables support for a full hiera based sudoers configuration. -# Hiera functionality is auto enabled during the initial sudo module load; -# this class is not intended to be loaded directly. -# -# See the primary sudo module documentation for usage and examples. -# -class sudo::configs ( - Hash $configs_hash = {}, - ){ - - # NOTE: hiera_hash does not work as expected in a parameterized class - # definition; so we call it here. - # - # http://docs.puppetlabs.com/hiera/1/puppet.html#limitations - # https://tickets.puppetlabs.com/browse/HI-118 - # - $configs = lookup('sudo::configs', Hash, {'strategy' => 'deep', 'merge_hash_arrays' => true}, $configs_hash) - - if !empty($configs) { - create_resources('sudo::conf', $configs) - } - -} - diff --git a/manifests/init.pp b/manifests/init.pp index 71852d6..5b48be5 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,210 +1,207 @@ # Class: sudo # # This module manages sudo # # Parameters: # [*ensure*] # Ensure if present or absent. # Default: present # # [*package*] # Name of the package. # Only set this, if your platform is not supported or you know, # what you're doing. # Default: auto-set, platform specific # # [*package_ensure*] # Allows you to ensure a particular version of a package # Default: present / lastest for RHEL < 5.5 # # [*package_source*] # Where to find the package. Only set this on AIX (required) and # Solaris (required) or if your platform is not supported or you # know, what you're doing. # # The default for aix is the perzl sudo package. For solaris 10 we # use the official www.sudo.ws binary package. # # Default: AIX: perzl.org # Solaris: www.sudo.ws # # [*package_admin_file*] # Where to find a Solaris 10 package admin file for # an unattended installation. We do not supply a default file, so # this has to be staged separately # # Only set this on Solaris 10 (required) # Default: /var/sadm/install/admin/puppet # # [*purge*] # Whether or not to purge sudoers.d directory # Default: true # # [*purge_ignore*] # Files to exclude from purging in sudoers.d directory # Default: undef # # [*config_file*] # Main configuration file. # Only set this, if your platform is not supported or you know, # what you're doing. # Default: auto-set, platform specific # # [*config_dir*] # Main directory containing sudo snippets, imported via # includedir stanza in sudoers file # Default: auto-set, platform specific # # [*extra_include_dirs*] # Array of additional directories containing sudo snippets # Default: undef # # [*content*] # Alternate content file location # Only set this, if your platform is not supported or you know, # what you're doing. # Default: auto-set, platform specific # # [*ldap_enable*] # Enable ldap support on the package # Default: false # # [*delete_on_error*] # True if you want that the configuration is deleted on an error # during a complete visudo -c run. If false it will just return # an error and will add a comment to the sudoers configuration so # that the resource will be checked at the following run. # Default: true # # [*validate_single*] # Do a validate on the "single" file in the sudoers.d directory. # If the validate fail the file will not be saved or changed # if a file already exist. # Default: false # # [*use_sudoreplay*] # Boolean to enable the usage of sudoreplay. # Default: false # # [*sudoreplay_discard*] # Array of additional command to discard in sudo log. # Default: undef # +# [*configs*] +# A hash of sudo::conf's +# Default: {} +# # Actions: # Installs sudo package and checks the state of sudoers file and # sudoers.d directory. # # Requires: # Nothing # # Sample Usage: # class { 'sudo': } # # [Remember: No empty lines between comments and class definition] class sudo ( Boolean $enable = true, Optional[String] $package = $sudo::params::package, Optional[String] $package_ldap = $sudo::params::package_ldap, String $package_ensure = $sudo::params::package_ensure, Optional[String] $package_source = $sudo::params::package_source, Optional[String] $package_admin_file = $sudo::params::package_admin_file, Boolean $purge = true, Optional[Variant[String, Array[String]]] $purge_ignore = undef, String $config_file = $sudo::params::config_file, Boolean $config_file_replace = true, String $config_file_mode = $sudo::params::config_file_mode, String $config_dir = $sudo::params::config_dir, String $config_dir_mode = $sudo::params::config_dir_mode, Optional[Array[String]] $extra_include_dirs = undef, String $content = $sudo::params::content, Boolean $ldap_enable = false, Boolean $delete_on_error = true, Boolean $validate_single = false, Boolean $config_dir_keepme = $sudo::params::config_dir_keepme, Boolean $use_sudoreplay = false, Optional[Array[String]] $sudoreplay_discard = undef, + Hash $configs = {}, ) inherits sudo::params { case $enable { true: { $dir_ensure = 'directory' $file_ensure = 'present' } false: { $dir_ensure = 'absent' $file_ensure = 'absent' } default: { fail('no $enable is set') } } case $ldap_enable { true: { if $package_ldap == undef { fail('on your os ldap support for sudo is not yet supported') } $package_real = $package_ldap } false: { $package_real = $package } default: { fail('no $ldap_enable is set') } } if $package_real { class { '::sudo::package': package => $package_real, package_ensure => $package_ensure, package_source => $package_source, package_admin_file => $package_admin_file, ldap_enable => $ldap_enable, before => [ File[$config_file], File[$config_dir] ], } } file { $config_file: ensure => $file_ensure, owner => 'root', group => $sudo::params::config_file_group, mode => $config_file_mode, replace => $config_file_replace, content => template($content), } file { $config_dir: ensure => $dir_ensure, owner => 'root', group => $sudo::params::config_file_group, mode => $config_dir_mode, recurse => $purge, purge => $purge, ignore => $purge_ignore, } if $config_dir_keepme { file { "${config_dir}/.keep-me": ensure => file, owner => 'root', group => $sudo::params::config_file_group, } } - # Load the Hiera based sudoer configuration (if enabled and present) - # - # NOTE: We must use 'include' here to avoid circular dependencies with - # sudo::conf - # - # NOTE: There is no way to detect the existence of hiera. This automatic - # functionality is therefore made exclusive to Puppet 3+ (hiera is embedded) - # in order to preserve backwards compatibility. - # - # http://projects.puppetlabs.com/issues/12345 - # - if (versioncmp($::puppetversion, '3') != -1) { - include '::sudo::configs' + $configs.each |$config_name, $config| { + sudo::conf { $config_name: + * => $config, + } } + if $package_real { anchor { 'sudo::begin': } -> Class['sudo::package'] -> anchor { 'sudo::end': } } }