class sudo ( Optional[String[1]] $sudogroup = undef, ) { $include_directory = '/etc/sudoers.d' $file_defaults = { owner => "root", group => "root", mode => "0440", validate_cmd => "sh -c 'if ! visudo --check --file=%; then cat %; exit 1; fi'", } $default_target = '00-defaults' include sudo::install include sudo::files include sudo::defaults define option ( Optional[String[1]] $parameter = undef, Variant[String[1],Array[String[1]],Integer,Boolean] $value = true, Enum['generic','host','user','cmnd','runas'] $context = 'generic', Optional[Variant[Array[String[1]],String[1]]] $list = undef, String[1] $target = $sudo::default_target, Integer $order = 10, Optional[String[1]] $comment = undef, Boolean $newline_before = true, ) { $param = $parameter ? { undef => $name, default => $parameter } $_list = type($list) ? { list => $list, default => [$list] } sudo::internals::add_sudoers_fragment { "${name}": target => $target, content => template("sudo/option_line.erb"), order => $order, comment => $comment, } } define alias ( Enum['host','user','cmnd','runas'] $type, Optional[Variant[Array[String[1]],String[1]]] $list = undef, String[1] $target = $sudo::default_target, Integer $order = 10, Optional[String[1]] $comment = undef, Boolean $newline_before = true, ) { if $name !~ /^[[:upper:]][[:upper:]_[:digit:]]*$/ { fail("sudoers alias definition '$name' can only contain uppercase letter, numbers, and the underscore") } $_list = type($list) ? { list => $list, default => [$list] } sudo::internals::add_sudoers_fragment { "${name}": target => $target, content => template("sudo/alias_line.erb"), order => $order, comment => $comment, } } define rule ( Variant[Array[String[1]],String[1]] $who, Variant[Array[String[1]],String[1]] $where = 'ALL', Optional[Variant[Array[String[1]],String[1]]] $as_whom = 'ALL', Optional[Variant[Array[String[1]],String[1]]] $as_group = 'ALL', Variant[Array[String[1]],String[1]] $what, String[1] $target = $sudo::default_target, Integer $order = 10, Optional[String[1]] $comment = undef, Boolean $newline_before = true, ) { $_comment = $comment ? { undef => $name, default => $comment } sudo::internals::add_sudoers_fragment { "${name}": target => $target, content => template("sudo/rule_line.erb"), order => $order, comment => $comment, } } } class sudo::install { package { "sudo": ensure => latest, } } class sudo::files ( ) { $include_directory = $sudo::include_directory file { default: * => $sudo::file_defaults ; "/etc/sudoers": content => template("sudo/sudoers.erb"), validate_cmd => "visudo --check --strict --file=%", ; "$include_directory": mode => "0770", recurse => true, purge => true, ignore => ["*.local","*-local-*","local-*"], ; "$include_directory/README": content => @(EOT) # This directory is managed by Puppet # # Local files can be named any of: # - local-* # - *-local-* # - *.local | EOT ; } } class sudo::defaults ( Optional[String[1]] $sudogroup = undef, Boolean $root_may_sudo = true, Optional[Hash] $generic = undef, Optional[Hash] $user = undef, Optional[Hash] $host = undef, Optional[Hash] $runas = undef, Optional[Hash] $cmnd = undef, ) { $netfacts = $facts[networking] ? { undef => $facts, default => $facts[networking] } sudo::alias { "LOCALHOST": type => host, list => [ "localhost" , $netfacts[hostname] , $netfacts[fqdn] ], } if $sudogroup { $sudogroup_target = "00-sudogroup" group { "$sudogroup": ensure => present, system => true }-> sudo::rule { "sudogroup": who => "%$sudogroup", where => "LOCALHOST", require => Sudo::Alias["LOCALHOST"], what => "PASSWD: ALL", target => "$sudogroup_target", comment => "Members of the ${sudogroup} group can use sudo (with password)", } } if $root_may_sudo { $rootsudo_target = "00-root_may_sudo" sudo::option { "syslog": value => false, context => user, list => "root", target => "$rootsudo_target", comment => "No need to log root usage of sudo", }-> sudo::rule { "root_may_sudo": who => "root", where => "LOCALHOST", require => Sudo::Alias["LOCALHOST"], what => "NOPASSWD: ALL", target => "$rootsudo_target", comment => "root may inadvertedly run sudo, so let them:", } } if $generic { concat::fragment { "sudo::defaults::generic comment": target => "sudoers_file_$sudo::default_target", order => 14, content => "\n# Generated from the sudo::defaults::generic class parameter:\n", } $generic.each | $param, $value | { sudo::option { "$param": value => $value, order => 15, newline_before => false, require => Concat::Fragment["sudo::defaults::generic comment"], } } concat::fragment { "sudo::defaults::generic end": target => "sudoers_file_$sudo::default_target", order => 16, content => "# End sudo::defaults::generic class parameters\n", } } $context_hash = {"user"=>$user,"host"=>$host,"runas"=>$runas,"cmnd"=>$cmnd} $context_hash.keys.each | $index, $context | { $defaults = $context_hash[$context] if $defaults { concat::fragment { "sudo::defaults::${context} comment": target => "sudoers_$default_target", order => 17 + $index * 3, content => "\n# Generated from the sudo::defaults::${context} class parameter:\n", } $defaults.each | $list, $items | { $items.each | $param, $value | { sudo::option { "${context}_${list}_${param}": parameter => $param, context => $context, list => $list, value => $value, order => 18 + $index * 3, newline_before => false, } } } concat::fragment { "sudo::defaults::${context} end": target => "sudoers_$default_target", order => 19 + $index * 3, content => "# End sudo::defaults::${context} class parameters\n", } } } } class sudo::internals { define add_sudoers_fragment ( String[1] $target, String[1] $content, Integer $order, Optional[String[1]] $comment = undef, ) { sudo::internals::ensure_sudoers_file { "${name}": target => $target } $ts = strftime("%s.%N") # include the timestamp to preserve order in the output if execution # is ordered concat::fragment { "${ts}_sudoers_fragment_${target}_${name}": target => "sudoers_file_${target}", content => $content, order => $order, } } define ensure_sudoers_file( String[1] $target, ) { ensure_resource('concat', "sudoers_file_${target}", { tag => "${target}", path => "${sudo::include_directory}/$target", warn => "# THIS FILE IS MANAGED BY PUPPET; CHANGES WILL BE OVERWRITTEN\n", require => File[$sudo::include_directory], } + $sudo::file_defaults, ) } }