From 24dc69a9a97e82a6e4207de68d6dcc664178249b Mon Sep 17 00:00:00 2001 From: Seth Chisamore Date: Tue, 30 Oct 2012 10:39:35 -0400 Subject: [OC-3564] move core Chef to the repo root \o/ \m/ The opscode/chef repository now only contains the core Chef library code used by chef-client, knife and chef-solo! --- lib/chef/file_access_control/unix.rb | 216 ++++++++++++++++++++++ lib/chef/file_access_control/windows.rb | 310 ++++++++++++++++++++++++++++++++ 2 files changed, 526 insertions(+) create mode 100644 lib/chef/file_access_control/unix.rb create mode 100644 lib/chef/file_access_control/windows.rb (limited to 'lib/chef/file_access_control') diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb new file mode 100644 index 0000000000..1dbfe40f2f --- /dev/null +++ b/lib/chef/file_access_control/unix.rb @@ -0,0 +1,216 @@ +# +# Author:: Adam Jacob () +# Author:: Daniel DeLeo () +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2008-2011 Opscode, 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/log' + +class Chef + class FileAccessControl + module Unix + UINT = (1 << 32) + UID_MAX = (1 << 32) - 10 + + def set_all! + set_owner! + set_group! + set_mode! + end + + def set_all + set_owner + set_group + set_mode + end + + # TODO factor this up + def requires_changes? + should_update_mode? || should_update_owner? || should_update_group? + end + + def describe_changes + changes = [] + changes << "change mode from '#{mode_to_s(current_mode)}' to '#{mode_to_s(target_mode)}'" if should_update_mode? + changes << "change owner from '#{current_resource.owner}' to '#{resource.owner}'" if should_update_owner? + changes << "change group from '#{current_resource.group}' to '#{resource.group}'" if should_update_group? + changes + end + + def target_uid + uid_from_resource(resource) + end + + def current_uid + uid_from_resource(current_resource) + end + + def should_update_owner? + !target_uid.nil? && target_uid != current_uid + end + + def set_owner! + unless target_uid.nil? + chown(target_uid, nil, file) + Chef::Log.info("#{log_string} owner changed to #{target_uid}") + modified + end + end + + def set_owner + set_owner! if should_update_owner? + end + + def target_gid + gid_from_resource(resource) + end + + def current_gid + gid_from_resource(current_resource) + end + + def gid_from_resource(resource) + return nil if resource == nil or resource.group.nil? + if resource.group.kind_of?(String) + diminished_radix_complement( Etc.getgrnam(resource.group).gid ) + elsif resource.group.kind_of?(Integer) + resource.group + else + Chef::Log.error("The `group` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})") + raise ArgumentError, "cannot resolve #{resource.group.inspect} to gid, group must be a string or integer" + end + rescue ArgumentError + provider.requirements.assert(:create, :create_if_missing, :touch) do |a| + a.assertion { false } + a.failure_message(Chef::Exceptions::GroupIDNotFound, "cannot determine group id for '#{resource.group}', does the group exist on this system?") + a.whyrun("Assuming group #{resource.group} would have been created") + end + return nil + end + + def should_update_group? + !target_gid.nil? && target_gid != current_gid + end + + def set_group! + unless target_gid.nil? + chown(nil, target_gid, file) + Chef::Log.info("#{log_string} group changed to #{target_gid}") + modified + end + end + + def set_group + set_group! if should_update_group? + end + + def mode_from_resource(res) + return nil if res == nil or res.mode.nil? + (res.mode.respond_to?(:oct) ? res.mode.oct : res.mode.to_i) & 007777 + end + + def target_mode + mode_from_resource(resource) + end + + def mode_to_s(mode) + mode.nil? ? "" : "0#{mode.to_s(8)}" + end + + def current_mode + mode_from_resource(current_resource) + end + + def should_update_mode? + !target_mode.nil? && current_mode != target_mode + end + + def set_mode! + unless target_mode.nil? + chmod(target_mode, file) + Chef::Log.info("#{log_string} mode changed to #{target_mode.to_s(8)}") + modified + end + end + + def set_mode + set_mode! if should_update_mode? + end + + def stat + if File.symlink?(file) + @stat ||= File.lstat(file) + else + @stat ||= File.stat(file) + end + end + + private + + def chmod(mode, file) + if File.symlink?(file) + begin + File.lchmod(mode, file) + rescue NotImplementedError + Chef::Log.warn("#{file} mode not changed: File.lchmod is unimplemented on this OS and Ruby version") + end + else + File.chmod(mode, file) + end + end + + def chown(uid, gid, file) + if ::File.symlink?(file) + File.lchown(uid, gid, file) + else + File.chown(uid, gid, file) + end + end + + # Workaround the fact that Ruby's Etc module doesn't believe in negative + # uids, so negative uids show up as the diminished radix complement of + # a uint. For example, a uid of -2 is reported as 4294967294 + def diminished_radix_complement(int) + if int > UID_MAX + int - UINT + else + int + end + end + + def uid_from_resource(resource) + return nil if resource == nil or resource.owner.nil? + if resource.owner.kind_of?(String) + diminished_radix_complement( Etc.getpwnam(resource.owner).uid ) + elsif resource.owner.kind_of?(Integer) + resource.owner + else + Chef::Log.error("The `owner` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})") + raise ArgumentError, "cannot resolve #{resource.owner.inspect} to uid, owner must be a string or integer" + end + rescue ArgumentError + provider.requirements.assert(:create, :create_if_missing, :touch) do |a| + a.assertion { false } + a.failure_message(Chef::Exceptions::UserIDNotFound, "cannot determine user id for '#{resource.owner}', does the user exist on this system?") + a.whyrun("Assuming user #{resource.owner} would have been created") + end + return nil + end + + end + end +end diff --git a/lib/chef/file_access_control/windows.rb b/lib/chef/file_access_control/windows.rb new file mode 100644 index 0000000000..c08752c87d --- /dev/null +++ b/lib/chef/file_access_control/windows.rb @@ -0,0 +1,310 @@ +# +# Author:: John Keiser () +# Author:: Seth Chisamore () +# Copyright:: Copyright 2011 Opscode, 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/win32/security' + +class Chef + class FileAccessControl + module Windows + include Chef::ReservedNames::Win32::API::Security + + Security = Chef::ReservedNames::Win32::Security + ACL = Security::ACL + ACE = Security::ACE + SID = Security::SID + + def set_all! + set_owner! + set_group! + set_dacl + end + + def set_all + set_owner + set_group + set_dacl + end + + def requires_changes? + should_update_dacl? || should_update_owner? || should_update_group? + end + + def describe_changes + # FIXME: describe what these are changing from and to + changes = [] + changes << "change dacl" if should_update_dacl? + changes << "change owner" if should_update_owner? + changes << "change group" if should_update_group? + changes + end + + private + + # Compare the actual ACL on a resource with the ACL we want. This + # ignores explicit ACLs on the target, and does mask prediction (if you + # set GENERIC_WRITE, Windows will flip on a whole bunch of other rights + # on the file when you save the ACL) + def acls_equal(target_acl, actual_acl) + if actual_acl.nil? + return target_acl.nil? + end + + actual_acl = actual_acl.select { |ace| !ace.inherited? } + # When ACLs apply to children, Windows splits them on the file system into two ACLs: + # one specific applying to this container, and one generic applying to children. + new_target_acl = [] + target_acl.each do |target_ace| + if target_ace.flags & INHERIT_ONLY_ACE == 0 + self_ace = target_ace.dup + self_ace.flags = 0 + self_ace.mask = securable_object.predict_rights_mask(target_ace.mask) + new_target_acl << self_ace + end + if target_ace.flags & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE) != 0 + children_ace = target_ace.dup + children_ace.flags |= INHERIT_ONLY_ACE + new_target_acl << children_ace + end + end + return actual_acl == new_target_acl + end + + def existing_descriptor + securable_object.security_descriptor + end + + def get_sid(value) + if value.kind_of?(String) + SID.from_account(value) + elsif value.kind_of?(SID) + value + else + raise "Must specify username, group or SID: #{value}" + end + end + + def securable_object + @securable_object ||= begin + if file.kind_of?(String) + so = Chef::ReservedNames::Win32::Security::SecurableObject.new(file.dup) + end + raise ArgumentError, "'file' must be a valid path or object of type 'Chef::ReservedNames::Win32::Security::SecurableObject'" unless so.kind_of? Chef::ReservedNames::Win32::Security::SecurableObject + so + end + end + + def should_update_dacl? + return true unless ::File.exists?(file) + dacl = target_dacl + existing_dacl = existing_descriptor.dacl + inherits = target_inherits + ( ! inherits.nil? && inherits != existing_descriptor.dacl_inherits? ) || ( dacl && !acls_equal(dacl, existing_dacl) ) + end + + def set_dacl! + set_dacl + end + + def set_dacl + dacl = target_dacl + existing_dacl = existing_descriptor.dacl + inherits = target_inherits + if ! inherits.nil? && inherits != existing_descriptor.dacl_inherits? + # We have to set DACL along with inherits. If rights were not + # specified, we need to change only inherited ACLs and leave + # explicit ACLs alone. + if dacl.nil? && !existing_dacl.nil? + dacl = ACL.create(existing_dacl.select { |ace| !ace.inherited? }) + end + securable_object.set_dacl(dacl, inherits) + Chef::Log.info("#{log_string} permissions changed to #{dacl} with inherits of #{inherits}") + modified + elsif dacl && !acls_equal(dacl, existing_dacl) + securable_object.dacl = dacl + Chef::Log.info("#{log_string} permissions changed to #{dacl}") + modified + end + end + + def should_update_group? + return true unless ::File.exists?(file) + (group = target_group) && (group != existing_descriptor.group) + end + + def set_group! + if (group = target_group) + Chef::Log.info("#{log_string} group changed to #{group}") + securable_object.group = group + modified + end + end + + def set_group + if (group = target_group) && (group != existing_descriptor.group) + set_group! + end + end + + def should_update_owner? + return true unless ::File.exists?(file) + (owner = target_owner) && (owner != existing_descriptor.owner) + end + + def set_owner! + if owner = target_owner + Chef::Log.info("#{log_string} owner changed to #{owner}") + securable_object.owner = owner + modified + end + end + + def set_owner + if (owner = target_owner) && (owner != existing_descriptor.owner) + set_owner! + end + end + + def mode_ace(sid, mode) + mask = 0 + mask |= GENERIC_READ if mode & 4 != 0 + mask |= (GENERIC_WRITE | DELETE) if mode & 2 != 0 + mask |= GENERIC_EXECUTE if mode & 1 != 0 + return [] if mask == 0 + [ ACE.access_allowed(sid, mask) ] + end + + def calculate_mask(permissions) + mask = 0 + [ permissions ].flatten.each do |permission| + case permission + when :full_control + mask |= GENERIC_ALL + when :modify + mask |= GENERIC_WRITE | GENERIC_READ | GENERIC_EXECUTE | DELETE + when :read + mask |= GENERIC_READ + when :read_execute + mask |= GENERIC_READ | GENERIC_EXECUTE + when :write + mask |= GENERIC_WRITE + else + # Otherwise, assume it's an integer specifying the actual flags + mask |= permission + end + end + mask + end + + def calculate_flags(rights) + # Handle inheritance flags + flags = 0 + case rights[:applies_to_children] + when :containers_only + flags |= CONTAINER_INHERIT_ACE + when :objects_only + flags |= OBJECT_INHERIT_ACE + when true + flags |= CONTAINER_INHERIT_ACE + flags |= OBJECT_INHERIT_ACE + when nil + flags |= CONTAINER_INHERIT_ACE + flags |= OBJECT_INHERIT_ACE + end + + if rights[:applies_to_self] == false + flags |= INHERIT_ONLY_ACE + end + + if rights[:one_level_deep] + flags |= NO_PROPAGATE_INHERIT_ACE + end + flags + end + + def target_dacl + return nil if resource.rights.nil? && resource.deny_rights.nil? && resource.mode.nil? + acls = nil + + if !resource.deny_rights.nil? + acls = [] if acls.nil? + + resource.deny_rights.each do |rights| + mask = calculate_mask(rights[:permissions]) + [ rights[:principals] ].flatten.each do |principal| + sid = get_sid(principal) + flags = calculate_flags(rights) + acls.push ACE.access_denied(sid, mask, flags) + end + end + end + + if !resource.rights.nil? + acls = [] if acls.nil? + + resource.rights.each do |rights| + mask = calculate_mask(rights[:permissions]) + [ rights[:principals] ].flatten.each do |principal| + sid = get_sid(principal) + flags = calculate_flags(rights) + acls.push ACE.access_allowed(sid, mask, flags) + end + end + end + + if !resource.mode.nil? + acls = [] if acls.nil? + + mode = (resource.mode.respond_to?(:oct) ? resource.mode.oct : resource.mode.to_i) & 0777 + + owner = target_owner + if owner + acls += mode_ace(owner, (mode & 0700) >> 6) + elsif mode & 0700 != 0 + Chef::Log.warn("Mode #{sprintf("%03o", mode)} includes bits for the owner, but owner is not specified") + end + + group = target_group + if group + acls += mode_ace(group, (mode & 070) >> 3) + elsif mode & 070 != 0 + Chef::Log.warn("Mode #{sprintf("%03o", mode)} includes bits for the group, but group is not specified") + end + + acls += mode_ace(SID.Everyone, (mode & 07)) + end + + acls.nil? ? nil : Chef::ReservedNames::Win32::Security::ACL.create(acls) + end + + def target_group + return nil if resource.group.nil? + sid = get_sid(resource.group) + end + + def target_inherits + resource.inherits + end + + def target_owner + return nil if resource.owner.nil? + sid = get_sid(resource.owner) + end + end + end +end -- cgit v1.2.1