Skip to content

Commit

Permalink
COOK-350, add sudo lwrp
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanwb committed Jan 9, 2012
1 parent 054d786 commit 4d15727
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 7 deletions.
75 changes: 75 additions & 0 deletions sudo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,20 @@ If you prefer to use passwordless sudo just set the following attribute to true:

node['authorization']['sudo']['passwordless']

This attribute controls whether or not to include the /etc/sudoers.d
directory, it default to false. If you set it to true, the default
recipe will create the directory /etc/sudoers.d and put the
placeholder file README there

node['authorization']['sudo']['include_sudoers_d']

USAGE
=====

You can create sudoer entries in two ways,
# populating the node['authorization']['sudo'] properties
# using the sudo lwrp

To use this cookbook, set the attributes above on the node via a role or the node object itself. In a role.rb:

"authorization" => {
Expand Down Expand Up @@ -54,9 +65,73 @@ In JSON (role.json or on the node object):

Note that the template for the sudoers file has the group "sysadmin" with ALL:ALL permission, though the group by default does not exist.

sudo LWRP
=============

This is a fairly complex LWRP for managing sudoers fragment files in
/etc/sudoers.d. It has two modes, "natural" mode which mimics the
sudoers file interface and "template" mode where you supply a regular
erb template and hash of variables. For "template" mode, the sudo lwrp
simply ensures that resulting sudo fragment passes validation and has
the proper filesystem permissions.

In either mode, the sudo lwrp will render a sudoers fragment in
/etc/sudoers.d/

In the case that the sudoers fragment does not pass validation, this
lwrp will fail the chef-client run before the fragment can be copied
to /etc/sudoers.d. This prevents the corruption of your sudoers configuration.

Example of the default mode, "natural" mode

sudo "tomcat" do
user "%tomcat" # or a username
runas "app_user" # or "app_user : tomcat"
commands ["/etc/init.d/tomcat restart"] # array of commands, will be .join(",")
host "ALL"
nopasswd false # true prepends the runas_spec with NOPASSWD
end


Example of template mode

sudo "tomcat"
# this template must exist in the calling cookbook
template "restart_tomcat.erb"
variables( :cmds => [ "/etc/init.d/tomcat restart" ] )
end

In either case, the following file would be generated in /etc/sudoers.d/tomcat

# this file was generated by chef
%tomcat ALL=(app_user) /etc/init.d/tomcat restart


Description of all attributes

* :name -- name of the file to be created in /etc/sudoers.d/ ,
defaults to the name you use for the resource. An exception will be
thrown if th
* :user -- user to provide sudo privileges to
* :group -- same as user except "%" is prepended to the name in
case it is not already
* :commands -- an array of commands that the user/group can execute using
sudo, must use the full path for each command, otherwise the resulting
fragment will fail validation
* :nopasswd -- whether or not a password must be supplied when
invoking sudo
* :template -- a template file in the current cookbook (not the sudo
cookbook), currently must be an erb template
* :variables -- variables to use with the template

If you use the template attribute, all other attributes will be
ignored except for the variables attribute.


LICENSE AND AUTHOR
==================

Author:: Bryan W. Berry <bryan.berry@gmail.com>
Author:: Adam Jacob <adam@opscode.com>
Author:: Seth Chisamore <schisamo@opscode.com>

Expand Down
1 change: 1 addition & 0 deletions sudo/attributes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
default['authorization']['sudo']['groups'] = Array.new
default['authorization']['sudo']['users'] = Array.new
default['authorization']['sudo']['passwordless'] = false
default['authorization']['sudo']['include_sudoers_d'] = false
4 changes: 4 additions & 0 deletions sudo/files/default/README.sudoers
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# If you use #includedir /etc/sudoers.d, the directory must contain at
# least one file. This file is that file
#
3 changes: 2 additions & 1 deletion sudo/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
maintainer_email "cookbooks@opscode.com"
license "Apache 2.0"
description "Installs sudo and configures /etc/sudoers"
version "1.0.0"
version "1.2.3"

recipe "sudo", "Installs sudo and configures /etc/sudoers"
recipe "sudo::sudoers_d", "Installs sudo and configures /etc/sudoers.d/ directory"

%w{redhat centos fedora ubuntu debian freebsd}.each do |os|
supports os
Expand Down
145 changes: 145 additions & 0 deletions sudo/providers/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#
# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
# Cookbook Name:: sudo
# Provider:: default
#
# Copyright 2011, Bryan w. Berry
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'fileutils'

def check_inputs user, group, foreign_template, foreign_vars
# if group, user, and template are nil, throw an exception
if user == nil and group == nil and foreign_template == nil
Chef::Application.fatal!("You must provide a user, group, or template")
elsif user != nil and group != nil and template != nil
Chef::Application.fatal!("You cannot specify user, group, and template")
end
end

def sudo_test tmpl_name
cmd = Chef::ShellOut.new(
%Q[ visudo -cf #{tmpl_name} ]
).run_command
unless cmd.exitstatus == 0
Chef::Log.debug('sudoers fragment failed validation. Here it is for your viewing pleasure')
Chef::Log.debug("\n" + ::File.open(tmpl_name).read + "\n")
Chef::Application.fatal!("sudoers template #{tmpl_name} failed parsing validation!")
end
end

def sudoers_updated?(tmpfile_path, sudoers_file)
require 'digest/sha1'
sudoers_path = "/etc/sudoers.d/#{sudoers_file}"

tmpfile_digest = Digest::SHA1.digest(::File.read(tmpfile_path))
if ::File.exist? sudoers_path
sudoers_file_digest = Digest::SHA1.digest(::File.read(sudoers_path))
else
# it doesn't already exist, so true
return true
end
tmpfile_digest != sudoers_file_digest ? true : false
end

def render_sudo_template new_resource
::Dir.mktmpdir do |tmpdir|
tmpfile_path = "#{tmpdir}/#{new_resource.name}"
tmpl = template tmpfile_path do
source new_resource.template
mode 0440
owner "root"
group "root"
variables new_resource.variables
action :nothing
end
tmpl.run_action(:create)
sudo_test tmpfile_path
# check if the sudoers file already exists, and only
# overwrite if the sudoers file has been changed
if sudoers_updated? tmpfile_path, new_resource.name
FileUtils.mv tmpfile_path, "/etc/sudoers.d/#{new_resource.name}"
new_resource.updated_by_last_action(true)
else
# resource not updated, do nothing
Chef::Log.debug("Sudo resource not updated, doing nothing")
FileUtils.rm_f tmpfile_path
end
end
end

def render_sudo_attributes new_resource
require 'tempfile'
sudo_user = new_resource.user
sudo_group = new_resource.group
commands = new_resource.commands
host = new_resource.host
runas = new_resource.runas
nopasswd = new_resource.nopasswd
sudo_entries = Array.new

if sudo_group
# prepend % to name if group name if it isn't already there
if sudo_group !~ /^%.*$/
sudo_name = "%#{sudo_group}"
else
sudo_name = sudo_group
end
else
sudo_name = sudo_user
end
commands.each do |cmd|
entry = ""
entry << sudo_name
entry << " ALL=(#{runas}) "
if nopasswd
entry << "NOPASSWD:"
end
entry << cmd
sudo_entries << entry + "\n"
end

tmpfile = Tempfile.new "d"
tmpfile_path = tmpfile.path
tmpfile.write sudo_entries.join
tmpfile.close
sudo_test tmpfile_path
FileUtils.chmod 0440, tmpfile_path

if sudoers_updated? tmpfile_path, new_resource.name
FileUtils.mv tmpfile_path, "/etc/sudoers.d/#{new_resource.name}"
new_resource.updated_by_last_action(true)
else
# resource not updated, do nothing
FileUtils.rm_f tmpfile_path
end

end

action :install do
if new_resource.template
Chef::Log.debug "template attribute provided to sudo lwrp, all other attributes ignored" +
" except for variables attribute"
render_sudo_template new_resource
else
render_sudo_attributes new_resource
end
end

action :remove do
sudoers_path = "/etc/sudoers.d/#{new_resource.name}"
require 'fileutils'
FileUtils.rm_f sudoers_path
new_resource.updated_by_last_action(true)
end
29 changes: 23 additions & 6 deletions sudo/recipes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,35 @@
#

package "sudo" do
action platform?("freebsd") ? :install : :upgrade
action :upgrade
end

if node['authorization']['sudo']['include_sudoers_d']
directory "/etc/sudoers.d" do
mode 0755
owner "root"
group "root"
action :create
end
cookbook_file "/etc/sudoers.d/README" do
cookbook "sudo"
source "README.sudoers"
mode 0440
owner "root"
group "root"
action :create
end
end

template "/etc/sudoers" do
path "/usr/local/etc/sudoers" if platform?("freebsd")
source "sudoers.erb"
mode 0440
owner "root"
group platform?("freebsd") ? "wheel" : "root"
group "root"
variables(
:sudoers_groups => node['authorization']['sudo']['groups'],
:sudoers_users => node['authorization']['sudo']['users'],
:passwordless => node['authorization']['sudo']['passwordless']
:sudoers_groups => node['authorization']['sudo']['groups'],
:sudoers_users => node['authorization']['sudo']['users'],
:passwordless => node['authorization']['sudo']['passwordless'],
:include_sudoers_d => node['authorization']['sudo']['include_sudoers_d']
)
end
37 changes: 37 additions & 0 deletions sudo/resources/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#
# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
# Cookbook Name:: sudo
# Resource:: default
#
# Copyright 2011, Bryan w. Berry
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

actions :install, :remove

attribute :user, :kind_of => String, :default => nil
attribute :group, :kind_of => String, :default => nil
attribute :commands, :kind_of => Array, :default => nil
attribute :host, :kind_of => String, :default => "ALL"
attribute :runas, :kind_of => String, :default => "ALL"
attribute :nopasswd, :equal_to => [true, false], :default => true
attribute :template, :regex => /^[a-z_]+.erb$/, :default => nil
attribute :variables, :kind_of => Hash, :default => nil

# we have to set default for the supports attribute
# in initializer since it is a 'reserved' attribute name
def initialize(*args)
super
@action = :install
@supports = {:report => true, :exception => true}
end
2 changes: 2 additions & 0 deletions sudo/templates/default/sudoers.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Defaults !lecture,tty_tickets,!fqdn
# User privilege specification
root ALL=(ALL) ALL

<%= '#includedir /etc/sudoers.d' if @include_sudoers_d -%>

This comment has been minimized.

Copy link
@fnichol

fnichol Mar 6, 2012

Any reason this is included mid-file rather than at the end? If a user is added in /etc/sudoers.d/jdoe with password-less sudo and they also belong to a group requiring a password, then the group line "wins" and trumps the /etc/sudoers.d/jdoe version.

<% @sudoers_users.each do |user| -%>
<%= user %> ALL=(ALL) <%= "NOPASSWD:" if @passwordless %>ALL
<% end -%>
Expand Down

0 comments on commit 4d15727

Please sign in to comment.