From 702516c76bcb84c9d02b2410d69a72be75580907 Mon Sep 17 00:00:00 2001 From: "martin f. krafft" Date: Tue, 24 Mar 2020 01:20:02 +0100 Subject: [PATCH] initial checkin --- data/defaults.yaml | 3 + data/osfamily/Debian.yaml | 6 + hiera.yaml | 8 ++ manifests/init.pp | 252 ++++++++++++++++++++++++++++++++++++++ templates/alias_line.erb | 16 +++ templates/option_line.erb | 26 ++++ templates/rule_line.erb | 22 ++++ templates/sudoers.erb | 6 + 8 files changed, 339 insertions(+) create mode 100644 data/defaults.yaml create mode 100644 data/osfamily/Debian.yaml create mode 100644 hiera.yaml create mode 100644 manifests/init.pp create mode 100644 templates/alias_line.erb create mode 100644 templates/option_line.erb create mode 100644 templates/rule_line.erb create mode 100644 templates/sudoers.erb diff --git a/data/defaults.yaml b/data/defaults.yaml new file mode 100644 index 0000000..0294874 --- /dev/null +++ b/data/defaults.yaml @@ -0,0 +1,3 @@ +lookup_options: + "^sudo::defaults::(generic|user|host|runas|cmnd)": + merge: hash diff --git a/data/osfamily/Debian.yaml b/data/osfamily/Debian.yaml new file mode 100644 index 0000000..0081eb0 --- /dev/null +++ b/data/osfamily/Debian.yaml @@ -0,0 +1,6 @@ +sudo::defaults::sudogroup: sudo + +sudo::defaults::generic: + env_reset: true + mail_badpass: true + secure_path: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" diff --git a/hiera.yaml b/hiera.yaml new file mode 100644 index 0000000..e6314fd --- /dev/null +++ b/hiera.yaml @@ -0,0 +1,8 @@ +version: 5 + +hierarchy: + - name: "OS-specific defaults" + path: "osfamily/%{::osfamily}.yaml" + + - name: "Defaults" + path: "defaults.yaml" diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 0000000..5e807d6 --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,252 @@ +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, + ) + } +} diff --git a/templates/alias_line.erb b/templates/alias_line.erb new file mode 100644 index 0000000..38d786c --- /dev/null +++ b/templates/alias_line.erb @@ -0,0 +1,16 @@ +<%- if @newline_before %> +<% end -%> +<%- if @comment -%> +<%= @comment.gsub(/(.{1,78})( +|$\n?)|(.{1,78})/, "# \\1\\3\n") -%> +<%- end -%> +<%- + def render_list(list) + return list.join(", \\\n ") + end + + def alias_type(type) + return "#{type.capitalize}_Alias" + end +-%> +<%= alias_type(@type) %> <%= @name %> = \ + <%= render_list(@_list) %> diff --git a/templates/option_line.erb b/templates/option_line.erb new file mode 100644 index 0000000..a05ecdd --- /dev/null +++ b/templates/option_line.erb @@ -0,0 +1,26 @@ +<%- if @newline_before %> +<% end -%> +<% if @comment -%> +<%= @comment.gsub(/(.{1,78})( +|$\n?)|(.{1,78})/, "# \\1\\3\n") -%> +<%- end -%> +<%- + def format_parameter(param, value) + if [true, false].include? value + return "#{value ? "" : "!"}#{param}" + elsif value.is_a?(String) + return "#{param}=\"#{@value}\"" + else + return "#{param}=#{@value}" + end + end + + def context_list(context, list) + if context == "global" + return "" + else + @CMAP = { "host" => '@', "user" => ':', "cmnd" => '!', "runas" => '>' } + return "#{@CMAP[context]}#{list.join(',')}" + end + end +-%> +Defaults<%= context_list(@context, @_list) %> <%= format_parameter(@param, @value) %> diff --git a/templates/rule_line.erb b/templates/rule_line.erb new file mode 100644 index 0000000..2f6a8c5 --- /dev/null +++ b/templates/rule_line.erb @@ -0,0 +1,22 @@ +<%- if @newline_before %> +<% end -%> +<%- if @_comment -%> +<%= @_comment.gsub(/(.{1,78})( +|$\n?)|(.{1,78})/, "# \\1\\3\n") -%> +<%- end -%> +<%- + def rl(list) + if list.is_a?(Array) + return list.join(",") + else + return list + end + end + def render_cmnd_list(list) + if list.is_a?(Array) + return "\\\n " + list.join(",\\\n ") + else + return list + end + end +-%> +<%= "#{rl(@who)} #{rl(@where)} = (#{rl(@as_whom)}:#{rl(@as_group)})" %> <%= render_cmnd_list(@what) %> diff --git a/templates/sudoers.erb b/templates/sudoers.erb new file mode 100644 index 0000000..cb3b6a2 --- /dev/null +++ b/templates/sudoers.erb @@ -0,0 +1,6 @@ +# THIS FILE IS MANAGED BY PUPPET; CHANGES WILL BE OVERWRITTEN! +<%- if @include_directory -%> + +# All includeiguration happens run-parts.d style in: +#includedir <%= @include_directory %> +<%- end -%> -- 2.39.5