diff options
-rw-r--r-- | .travis.yml | 26 | ||||
-rw-r--r-- | CHANGELOG.md | 7 | ||||
-rw-r--r-- | Gemfile.lock | 14 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | chef-config/lib/chef-config/version.rb | 2 | ||||
-rw-r--r-- | kitchen-tests/cookbooks/base/attributes/default.rb | 8 | ||||
-rw-r--r-- | kitchen-tests/cookbooks/base/metadata.rb | 1 | ||||
-rw-r--r-- | kitchen-tests/cookbooks/base/recipes/default.rb | 12 | ||||
-rw-r--r-- | lib/chef/resource/sudo.rb | 227 | ||||
-rw-r--r-- | lib/chef/resource/support/sudoer.erb | 18 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 | ||||
-rw-r--r-- | lib/chef/version.rb | 2 | ||||
-rw-r--r-- | spec/unit/resource/sudo_spec.rb | 87 |
13 files changed, 371 insertions, 36 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/CHANGELOG.md b/CHANGELOG.md index ab2b77ae7e..6cdb77b5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,16 @@ <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ --> -<!-- latest_release 14.0.139 --> -## [v14.0.139](https://github.com/chef/chef/tree/v14.0.139) (2018-03-16) +<!-- latest_release 14.0.140 --> +## [v14.0.140](https://github.com/chef/chef/tree/v14.0.140) (2018-03-17) #### Merged Pull Requests -- Revert "Stripping out Authorization header on redirect to a different host [#6996](https://github.com/chef/chef/pull/6996) ([tas50](https://github.com/tas50)) +- Add the sudo resource from the sudo resource [#6979](https://github.com/chef/chef/pull/6979) ([tas50](https://github.com/tas50)) <!-- latest_release --> <!-- release_rollup since=13.7.16 --> ### Changes since 13.7.16 release #### Merged Pull Requests +- Add the sudo resource from the sudo resource [#6979](https://github.com/chef/chef/pull/6979) ([tas50](https://github.com/tas50)) <!-- 14.0.140 --> - Revert "Stripping out Authorization header on redirect to a different host [#6996](https://github.com/chef/chef/pull/6996) ([tas50](https://github.com/tas50)) <!-- 14.0.139 --> - Use Chef omnibus def that includes libarchive [#6993](https://github.com/chef/chef/pull/6993) ([tas50](https://github.com/tas50)) <!-- 14.0.138 --> - Yum refactor [#6540](https://github.com/chef/chef/pull/6540) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 14.0.137 --> diff --git a/Gemfile.lock b/Gemfile.lock index ec566e7c85..5a31985ae4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,10 +8,10 @@ GIT GIT remote: https://github.com/chef/ohai.git - revision: 65b2ee96ae77a7498b21663181b9c524e1235e26 + revision: 9a6842a19cc9d46ca9cc42af4bda5e9ca31a0dce branch: master specs: - ohai (14.0.26) + ohai (14.0.27) chef-config (>= 12.8, < 15) ffi (~> 1.9) ffi-yajl (~> 2.2) @@ -27,10 +27,10 @@ GIT PATH remote: . specs: - chef (14.0.139) + chef (14.0.140) addressable bundler (>= 1.10) - chef-config (= 14.0.139) + chef-config (= 14.0.140) chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -57,10 +57,10 @@ PATH specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef (14.0.139-universal-mingw32) + chef (14.0.140-universal-mingw32) addressable bundler (>= 1.10) - chef-config (= 14.0.139) + chef-config (= 14.0.140) chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -102,7 +102,7 @@ PATH PATH remote: chef-config specs: - chef-config (14.0.139) + chef-config (14.0.140) addressable fuzzyurl mixlib-config (~> 2.0) @@ -1 +1 @@ -14.0.139
\ No newline at end of file +14.0.140
\ No newline at end of file diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index 3451aa5a06..44b74e7ea9 100644 --- a/chef-config/lib/chef-config/version.rb +++ b/chef-config/lib/chef-config/version.rb @@ -21,7 +21,7 @@ module ChefConfig CHEFCONFIG_ROOT = File.expand_path("../..", __FILE__) - VERSION = "14.0.139" + VERSION = "14.0.140" end # 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..f1e517a275 --- /dev/null +++ b/lib/chef/resource/sudo.rb @@ -0,0 +1,227 @@ +# +# 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 + + action :remove do + Chef::Log.warn("The sudo :remove action has been renamed :delete. Please update your cookbook code for the new action") + action_delete + 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/lib/chef/version.rb b/lib/chef/version.rb index e0a24ff384..212f4679c7 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -23,7 +23,7 @@ require "chef/version_string" class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) - VERSION = Chef::VersionString.new("14.0.139") + VERSION = Chef::VersionString.new("14.0.140") end # 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 |