4 Puppet::Type.newtype(:posix_acl) do
6 Ensures that a set of ACL permissions are applied to a given file
11 posix_acl { '/var/www/html':
19 'default:user:www-data:r-x',
28 In this example, Puppet will ensure that the user and group
29 permissions are set recursively on /var/www/html as well as add
30 default permissions that will apply to new directories and files
31 created under /var/www/html
33 Setting an ACL can change a file's mode bits, so if the file is
34 managed by a File resource, that resource needs to set the mode
35 bits according to what the calculated mode bits will be, for
36 example, the File resource for the ACL above should be:
38 file { '/var/www/html':
44 desc 'What do we do with this list of ACLs? Options are set, unset, exact, and purge'
45 newvalues(:set, :unset, :exact, :purge)
50 desc 'The file or directory to which the ACL applies.'
53 path = Pathname.new(value)
55 raise ArgumentError, "Path must be absolute: #{path}"
60 newparam(:recursemode) do
61 desc "Should Puppet apply the ACL recursively with the -R option or
62 apply it to individual files?
65 deep means apply to every file"
67 newvalues(:lazy, :deep)
71 # Credits to @itdoesntwork
72 # http://stackoverflow.com/questions/26878341/how-do-i-tell-if-one-path-is-an-ancestor-of-another
73 def self.descendant?(a, b)
74 a_list = File.expand_path(a).split('/')
75 b_list = File.expand_path(b).split('/')
77 b_list[0..a_list.size - 1] == a_list && b_list != a_list
80 # Snippet based on upstream Puppet (ASL 2.0)
81 [:posix_acl, :file].each do |autorequire_type|
82 autorequire(autorequire_type) do
84 path = Pathname.new(self[:path])
85 # rubocop:disable Style/MultilineBlockChain
86 if autorequire_type != :posix_acl
87 if self[:recursive] == :true
88 catalog.resources.select do |r|
89 r.is_a?(Puppet::Type.type(autorequire_type)) && self.class.descendant?(self[:path], r[:path])
97 # Start at our parent, to avoid autorequiring ourself
98 parents = path.parent.enum_for(:ascend)
99 # should this be = or == ? I don't know
100 if found = parents.find { |p| catalog.resource(autorequire_type, p.to_s) } # rubocop:disable Lint/AssignmentInCondition
106 # rubocop:enable Style/MultilineBlockChain
110 autorequire(:package) do
114 newproperty(:permission, array_matching: :all) do
115 desc 'ACL permission(s).'
117 def is_to_s(value) # rubocop:disable Style/PredicateName
118 if value == :absent || value.include?(:absent)
125 def should_to_s(value)
126 if value == :absent || value.include?(:absent)
137 # Remove permission bits from an ACL line, eg:
138 # 'user:root:rwx' becomes 'user:root:'
140 Puppet.debug 'permission.strip_perms'
143 unless perm =~ %r{^(((u(ser)?)|(g(roup)?)|(m(ask)?)|(o(ther)?)):):}
144 perm = perm.split(':', -1)[0..-2].join(':')
151 # in unset_insync and set_insync the test_should has been added as a work around
152 # to prevent puppet-posix_acl from interpreting recursive permission notation (e.g. rwX)
153 # from causing a false mismatch. A better solution needs to be implemented to
154 # recursively check permissions, not rely upon getfacl
155 def unset_insync(cur_perm)
156 # Puppet.debug "permission.unset_insync"
158 @should.each { |x| test_should << x.downcase }
159 cp = strip_perms(cur_perm)
160 sp = strip_perms(test_should)
164 def set_insync(cur_perm) # rubocop:disable Style/AccessorMethodName
165 should = @should.uniq.sort
166 (cur_perm.sort == should) || (provider.check_set && (should - cur_perm).empty?)
169 def purge_insync(cur_perm)
170 # Puppet.debug "permission.purge_insync"
171 cur_perm.each do |perm|
172 # If anything other than the mode bits are set, we're not in sync
173 return false unless perm =~ %r{^(((u(ser)?)|(g(roup)?)|(o(ther)?)):):}
179 Puppet.debug "permission.insync? is: #{is.inspect} @should: #{@should.inspect}"
180 return purge_insync(is) if provider.check_purge
181 return unset_insync(is) if provider.check_unset
185 # Munge into normalised form
188 a = acl.split ':', -1 # -1 keeps trailing empty fields.
189 raise ArgumentError, "Too few fields. At least 3 required, got #{a.length}." if a.length < 3
190 raise ArgumentError, "Too many fields. At most 4 allowed, got #{a.length}." if a.length > 4
193 raise ArgumentError, %(First field of 4 must be "d" or "default", got "#{d}".) unless %w[d default].include?(d)
196 t = a.shift # Copy the type.
207 raise ArgumentError, %(Unknown type "#{t}", expected "user", "group", "other" or "mask".)
209 r << "#{a.shift}:" # Copy the "who".
213 r << (p | 4 ? 'r' : '-')
214 r << (p | 2 ? 'w' : '-')
215 r << (p | 1 ? 'x' : '-')
217 # Not the most efficient but checks for multiple and invalid chars.
219 r << (s.sub!('r', '') ? 'r' : '-')
220 r << (s.sub!('w', '') ? 'w' : '-')
221 r << (s.sub!('x', '') ? 'x' : '-')
222 raise ArgumentError, %(Invalid permission set "#{p}".) unless s.empty?
228 newparam(:recursive) do
229 desc 'Apply ACLs recursively.'
230 newvalues(:true, :false)
234 def self.pick_default_perms(acl)
235 acl.reject { |a| a.split(':', -1).length == 4 }
239 options = @original_parameters.merge(name: path).reject { |_param, value| value.nil? }
240 unless File.directory?(options[:name])
241 options[:permission] = self.class.pick_default_perms(options[:permission]) if options.include?(:permission)
243 [:recursive, :recursemode, :path].each do |param|
244 options.delete(param) if options.include?(param)
246 self.class.new(options)
250 return [] unless self[:recursive] == :true && self[:recursemode] == :deep
253 if File.directory?(self[:path])
254 Dir.chdir(self[:path]) do
255 Dir['**/*'].each do |path|
256 paths << ::File.join(self[:path], path)
260 # At the time we generate extra resources, all the files might now be present yet.
261 # In prediction to that we also create ACL resources for child file resources that
262 # might not have been applied yet.
263 catalog.resources.select do |r|
264 r.is_a?(Puppet::Type.type(:file)) && self.class.descendant?(self[:path], r[:path])
265 end.each do |found| # rubocop:disable Style/MultilineBlockChain
266 paths << found[:path]
269 results << newchild(path)
275 unless self[:permission]
276 raise(Puppet::Error, 'permission is a required property.')