summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2018-03-12 17:59:59 -0700
committerTim Smith <tsmith@chef.io>2018-03-15 11:06:09 -0700
commitd88972f463fb7251872cece16d5eedfbc2faa315 (patch)
tree071d7cd25f59ea47b555ef1854c8d4647c3985e1
parentfb61966bbc7a2d8920d772ff7f3f3fc8543b107d (diff)
downloadchef-d88972f463fb7251872cece16d5eedfbc2faa315.tar.gz
Add the sudo resource from the sudo resource
Copied as is with the new description fields added. Signed-off-by: Tim Smith <tsmith@chef.io>
-rw-r--r--.travis.yml26
-rw-r--r--kitchen-tests/cookbooks/base/attributes/default.rb8
-rw-r--r--kitchen-tests/cookbooks/base/metadata.rb1
-rw-r--r--kitchen-tests/cookbooks/base/recipes/default.rb12
-rw-r--r--lib/chef/resource/sudo.rb222
-rw-r--r--lib/chef/resource/support/sudoer.erb18
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/unit/resource/sudo_spec.rb87
8 files changed, 352 insertions, 23 deletions
diff --git a/.travis.yml b/.travis.yml
index 30d8ef45a1..ddfb6ff8e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -269,32 +269,32 @@ matrix:
sudo: required
gemfile: kitchen-tests/Gemfile
before_install:
- - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2)
- - gem install bundler -v $(grep :bundler omnibus_overrides.rb | cut -d'"' -f2)
+ - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2)
+ - gem install bundler -v $(grep :bundler omnibus_overrides.rb | cut -d'"' -f2)
before_script:
- - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
- - cd kitchen-tests
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
script:
- - bundle exec kitchen test base-fedora-latest
+ - bundle exec kitchen test base-fedora-latest
after_failure:
- - cat .kitchen/logs/kitchen.log
+ - cat .kitchen/logs/kitchen.log
env:
- - FEDORA=latest
- - KITCHEN_YAML=.kitchen.travis.yml
+ - FEDORA=latest
+ - KITCHEN_YAML=.kitchen.travis.yml
- rvm: 2.4.3
services: docker
sudo: required
gemfile: kitchen-tests/Gemfile
before_install:
- - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2)
- - gem install bundler -v $(grep :bundler omnibus_overrides.rb | cut -d'"' -f2)
+ - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2)
+ - gem install bundler -v $(grep :bundler omnibus_overrides.rb | cut -d'"' -f2)
before_script:
- - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
- - cd kitchen-tests
+ - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+ - cd kitchen-tests
script:
- bundle exec kitchen test base-opensuse-leap
after_failure:
- - cat .kitchen/logs/kitchen.log
+ - cat .kitchen/logs/kitchen.log
env:
- OPENSUSELEAP=42
- KITCHEN_YAML=.kitchen.travis.yml
diff --git a/kitchen-tests/cookbooks/base/attributes/default.rb b/kitchen-tests/cookbooks/base/attributes/default.rb
index c952f0887e..5e8cee6736 100644
--- a/kitchen-tests/cookbooks/base/attributes/default.rb
+++ b/kitchen-tests/cookbooks/base/attributes/default.rb
@@ -70,14 +70,6 @@ default["resolver"]["nameservers"] = [ "8.8.8.8", "8.8.4.4" ]
default["resolver"]["search"] = "chef.io"
#
-# sudo cookbook overrides
-#
-
-default["authorization"]["sudo"]["passwordless"] = true
-default["authorization"]["sudo"]["groups"] = %w{sysadmin}
-default["authorization"]["sudo"]["users"] = %w{vagrant centos ubuntu}
-
-#
# nscd cookbook overrides
#
diff --git a/kitchen-tests/cookbooks/base/metadata.rb b/kitchen-tests/cookbooks/base/metadata.rb
index 87efeb710d..f526318bb7 100644
--- a/kitchen-tests/cookbooks/base/metadata.rb
+++ b/kitchen-tests/cookbooks/base/metadata.rb
@@ -16,7 +16,6 @@ depends "ntp"
depends "openssh"
depends "resolver"
depends "selinux"
-depends "sudo"
depends "ubuntu"
depends "users"
depends "cron"
diff --git a/kitchen-tests/cookbooks/base/recipes/default.rb b/kitchen-tests/cookbooks/base/recipes/default.rb
index 350b5637e9..dac989cc81 100644
--- a/kitchen-tests/cookbooks/base/recipes/default.rb
+++ b/kitchen-tests/cookbooks/base/recipes/default.rb
@@ -38,7 +38,17 @@ users_manage "sysadmin" do
action [:create]
end
-include_recipe "sudo"
+sudo "sysadmins" do
+ group ["sysadmin", "%superadmin"]
+ nopasswd true
+end
+
+sudo "some_person" do
+ nopasswd true
+ user "some_person"
+ commands ["/opt/chef/bin/chef-client"]
+ env_keep_add %w{PATH RBENV_ROOT RBENV_VERSION}
+end
include_recipe "chef-client::delete_validation"
include_recipe "chef-client::config"
diff --git a/lib/chef/resource/sudo.rb b/lib/chef/resource/sudo.rb
new file mode 100644
index 0000000000..202e904a35
--- /dev/null
+++ b/lib/chef/resource/sudo.rb
@@ -0,0 +1,222 @@
+#
+# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
+# Author:: Seth Vargo (<sethvargo@gmail.com>)
+#
+# Copyright:: 2011-2018, Bryan w. Berry
+# Copyright:: 2012-2018, Seth Vargo
+# Copyright:: 2015-2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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 "chef/resource"
+
+class Chef
+ class Resource
+ class Sudo < Chef::Resource
+ resource_name "sudo"
+ provides "sudo"
+
+ description "Use the sudo resource to add or remove individual sudo entries using sudoers.d files."\
+ " Sudo version 1.7.2 or newer is required to use the sudo resource as it relies on the"\
+ " '#includedir' directive introduced in version 1.7.2. The resource does not enforce"\
+ " installing the version. Supported releases of Ubuntu, Debian and RHEL (6+) all support"\
+ " this feature."
+
+ introduced "14.0"
+
+ # acording to the sudo man pages sudo will ignore files in an include dir that have a `.` or `~`
+ # We convert either to `__`
+ property :filename, String,
+ description: "The name of the sudoers.d file",
+ name_property: true,
+ coerce: proc { |x| x.gsub(/[\.~]/, "__") }
+
+ property :users, [String, Array],
+ description: "User(s) to provide sudo privileges to. This accepts either an array or a comma separated.",
+ default: [],
+ coerce: proc { |x| x.is_a?(Array) ? x : x.split(/\s*,\s*/) }
+
+ property :groups, [String, Array],
+ description: "Group(s) to provide sudo privileges to. This accepts either an array or a comma separated list. Leading % on group names is optional.",
+ default: [],
+ coerce: proc { |x| coerce_groups(x) }
+
+ property :commands, Array,
+ description: "An array of commands this sudoer can execute.",
+ default: ["ALL"]
+
+ property :host, String,
+ description: "The host to set in the sudo config.",
+ default: "ALL"
+
+ property :runas, String,
+ description: "User the command(s) can be run as",
+ default: "ALL"
+
+ property :nopasswd, [TrueClass, FalseClass],
+ description: "Allow running sudo without specifying a password sudo",
+ default: false
+
+ property :noexec, [TrueClass, FalseClass],
+ description: "Prevent commands from shelling out.",
+ default: false
+
+ property :template, String,
+ description: "The name of the erb template in your cookbook if you wish to supply your own template."
+
+ property :variables, [Hash, nil],
+ description: "The variables to pass to the custom template. Ignored if not using a custom template.",
+ default: nil
+
+ property :defaults, Array,
+ description: "An array of defaults for the user/group.",
+ default: []
+
+ property :command_aliases, Array,
+ description: "Command aliases that can be used as allowed commands later in the config",
+ default: []
+
+ property :setenv, [TrueClass, FalseClass],
+ description: "Whether to permit the preserving of environment with sudo -E.",
+ default: false
+
+ property :env_keep_add, Array,
+ description: "An array of strings to add to env_keep.",
+ default: []
+
+ property :env_keep_subtract, Array,
+ description: "An array of strings to remove from env_keep.",
+ default: []
+
+ property :visudo_path, String,
+ description: "The path to visudo for config verification.",
+ default: "/usr/sbin/visudo"
+
+ property :config_prefix, String,
+ description: "The directory containing the sudoers config file.",
+ default: lazy { "config_prefix" }
+
+ alias_method :user, :users
+ alias_method :group, :groups
+
+ # make sure each group starts with a %
+ def coerce_groups(x)
+ # split strings on the commas with optional spaces on either side
+ groups = x.is_a?(Array) ? x : x.split(/\s*,\s*/)
+
+ # make sure all the groups start with %
+ groups.map { |g| g[0] == "%" ? g : "%#{g}" }
+ end
+
+ # default config prefix paths based on platform
+ def config_prefix
+ case node["platform_family"]
+ when "smartos"
+ "/opt/local/etc"
+ when "mac_os_x"
+ "/private/etc"
+ else
+ "/etc"
+ end
+ end
+
+ action :create do
+ description "Create a single sudoers config in the sudoers.d directory"
+
+ validate_platform
+ validate_properties
+
+ if docker? # don't even put this into resource collection unless we're in docker
+ declare_resource(:package, "sudo") do
+ action :nothing
+ not_if "which sudo"
+ end.run_action(:install)
+ end
+
+ target = "#{new_resource.config_prefix}/sudoers.d/"
+ declare_resource(:directory, target) unless ::File.exist?(target)
+
+ Chef::Log.warn("#{new_resource.filename} will be rendered, but will not take effect because the #{new_resource.config_prefix}/sudoers config lacks the includedir directive that loads configs from #{new_resource.config_prefix}/sudoers.d/!") if ::File.readlines("#{new_resource.config_prefix}/sudoers").grep(/includedir/).empty?
+
+ if new_resource.template
+ Chef::Log.debug("Template property provided, all other properties ignored.")
+
+ declare_resource(:template, "#{target}#{new_resource.filename}") do
+ source new_resource.template
+ mode "0440"
+ variables new_resource.variables
+ verify "#{new_resource.visudo_path} -cf %{path}" if visudo_present?
+ action :create
+ end
+ else
+ declare_resource(:template, "#{target}#{new_resource.filename}") do
+ source "sudoer.erb"
+ source ::File.expand_path("../support/sudoer.erb", __FILE__)
+ local true
+ mode "0440"
+ variables sudoer: (new_resource.groups + new_resource.users).join(","),
+ host: new_resource.host,
+ runas: new_resource.runas,
+ nopasswd: new_resource.nopasswd,
+ noexec: new_resource.noexec,
+ commands: new_resource.commands,
+ command_aliases: new_resource.command_aliases,
+ defaults: new_resource.defaults,
+ setenv: new_resource.setenv,
+ env_keep_add: new_resource.env_keep_add,
+ env_keep_subtract: new_resource.env_keep_subtract
+ verify "#{new_resource.visudo_path} -cf %{path}" if visudo_present?
+ action :create
+ end
+ end
+ end
+
+ action :install do
+ Chef::Log.warn("The sudo :install action has been renamed :create. Please update your cookbook code for the new action")
+ action_create
+ end
+
+ # Removes a user from the sudoers group
+ action :delete do
+ description "Remove a sudoers config from the sudoers.d directory"
+
+ file "#{new_resource.config_prefix}/sudoers.d/#{new_resource.filename}" do
+ action :delete
+ end
+ end
+
+ action_class do
+ # Make sure we fail on FreeBSD
+ def validate_platform
+ return unless platform_family?("freebsd")
+ raise "The sudo resource cannot run on FreeBSD as FreeBSD does not support using a sudoers.d config directory."
+ end
+
+ # Ensure that the inputs are valid (we cannot just use the resource for this)
+ def validate_properties
+ # if group, user, env_keep_add, env_keep_subtract and template are nil, throw an exception
+ raise "You must specify users, groups, env_keep_add, env_keep_subtract, or template properties!" if new_resource.users.empty? && new_resource.groups.empty? && new_resource.template.nil? && new_resource.env_keep_add.empty? && new_resource.env_keep_subtract.empty?
+
+ # if specifying user or group and template at the same time fail
+ raise "You cannot specify users or groups properties and also specify a template. To use your own template pass in all template variables using the variables property." if (!new_resource.users.empty? || !new_resource.groups.empty?) && !new_resource.template.nil?
+ end
+
+ def visudo_present?
+ return if ::File.exist?(new_resource.visudo_path)
+ Chef::Log.warn("The visudo binary cannot be found at '#{new_resource.visudo_path}'. Skipping sudoer file validation. If visudo is on this system you can specify the path using the 'visudo_path' property.")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/support/sudoer.erb b/lib/chef/resource/support/sudoer.erb
new file mode 100644
index 0000000000..c54507535b
--- /dev/null
+++ b/lib/chef/resource/support/sudoer.erb
@@ -0,0 +1,18 @@
+# This file is managed by Chef.
+# Do NOT modify this file directly.
+
+<% @command_aliases.each do |a| -%>
+Cmnd_Alias <%= a[:name].upcase %> = <%= a[:command_list].join(', ') %>
+<% end -%>
+<% @env_keep_add.each do |env_keep| -%>
+Defaults env_keep += "<%= env_keep %>"
+<% end -%>
+<% @env_keep_subtract.each do |env_keep| -%>
+Defaults env_keep -= "<%= env_keep %>"
+<% end -%>
+<% @commands.each do |command| -%>
+<% if @sudoer %><%= @sudoer %> <%= @host %>=(<%= @runas %>) <%= 'NOEXEC:' if @noexec %><%= 'NOPASSWD:' if @nopasswd.to_s == 'true' %><%= 'SETENV:' if @setenv.to_s == 'true' %><%= command %><% end -%>
+<% end -%>
+<% unless @defaults.empty? %>
+Defaults:<%= @sudoer %> <%= @defaults.join(',') %>
+<% end -%>
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 42150ffc82..dc235deb06 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -85,6 +85,7 @@ require "chef/resource/ruby_block"
require "chef/resource/scm"
require "chef/resource/script"
require "chef/resource/service"
+require "chef/resource/sudo"
require "chef/resource/systemd_unit"
require "chef/resource/windows_service"
require "chef/resource/subversion"
diff --git a/spec/unit/resource/sudo_spec.rb b/spec/unit/resource/sudo_spec.rb
new file mode 100644
index 0000000000..660eb285da
--- /dev/null
+++ b/spec/unit/resource/sudo_spec.rb
@@ -0,0 +1,87 @@
+#
+# Copyright:: Copyright 2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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 "spec_helper"
+
+describe Chef::Resource::Sudo do
+
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:resource) { Chef::Resource::Sudo.new("someone", run_context) }
+
+ it "has a resource name of :sudo" do
+ expect(resource.resource_name).to eql(:sudo)
+ end
+
+ it "has a default action of create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "the filename property is the name property" do
+ expect(resource.filename).to eql("someone")
+ end
+
+ it "coerces filename property values . & ~ to __" do
+ resource.filename "something.something~"
+ expect(resource.filename).to eql("something__something__")
+ end
+
+ it "supports the legacy 'user' property" do
+ resource.user ["foo"]
+ expect(resource.users).to eql(["foo"])
+ end
+
+ it "supports the legacy 'groups' property" do
+ resource.group ["%foo"]
+ expect(resource.groups).to eql(["%foo"])
+ end
+
+ it "coerces users & groups String vals to Arrays" do
+ resource.users "something"
+ resource.groups "%something"
+ expect(resource.users).to eql(["something"])
+ expect(resource.groups).to eql(["%something"])
+ end
+
+ it "coerces users & group String vals no matter the spacing" do
+ resource.users "user1, user2 , user3 ,user4"
+ resource.groups "group1, group2 , group3 ,group4"
+ expect(resource.users).to eql(%w{user1 user2 user3 user4})
+ expect(resource.groups).to eql(["%group1", "%group2", "%group3", "%group4"])
+ end
+
+ it "coerces groups values to properly start with %" do
+ resource.groups ["foo", "%bar"]
+ expect(resource.groups).to eql(["%foo", "%bar"])
+ end
+
+ it "it sets the config prefix to /etc on linux" do
+ node.automatic[:platform_family] = "debian"
+ expect(resource.config_prefix).to eql("/etc")
+ end
+
+ it "it sets the config prefix to /private/etc on macOS" do
+ node.automatic[:platform_family] = "mac_os_x"
+ expect(resource.config_prefix).to eql("/private/etc")
+ end
+
+ it "it sets the config prefix to /opt/local/etc on smartos" do
+ node.automatic[:platform_family] = "smartos"
+ expect(resource.config_prefix).to eql("/opt/local/etc")
+ end
+end