1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
require 'facter'
require 'facter/util/plist'
require 'puppet'
require 'tempfile'
Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppet::Provider do
desc "Manage Mac OS X authorization database rules and rights.
"
commands :security => "/usr/bin/security"
commands :sw_vers => "/usr/bin/sw_vers"
confine :operatingsystem => :darwin
# This should be confined based on macosx_productversion once
# http://projects.reductivelabs.com/issues/show/1796
# is resolved.
if FileTest.exists?("/usr/bin/sw_vers")
product_version = sw_vers "-productVersion"
confine :true => if /^10.5/.match(product_version) or /^10.6/.match(product_version)
true
end
end
defaultfor :operatingsystem => :darwin
AuthDB = "/etc/authorization"
@rights = {}
@rules = {}
@parsed_auth_db = {}
@comment = "" # Not implemented yet. Is there any real need to?
# This map exists due to the use of hyphens and reserved words in
# the authorization schema.
PuppetToNativeAttributeMap = { :allow_root => "allow-root",
:authenticate_user => "authenticate-user",
:auth_class => "class",
:k_of_n => "k-of-n",
:session_owner => "session-owner", }
class << self
attr_accessor :parsed_auth_db
attr_accessor :rights
attr_accessor :rules
attr_accessor :comments # Not implemented yet.
def prefetch(resources)
self.populate_rules_rights
end
def instances
if self.parsed_auth_db == {}
self.prefetch(nil)
end
self.parsed_auth_db.collect do |k,v|
new(:name => k)
end
end
def populate_rules_rights
auth_plist = Plist::parse_xml(AuthDB)
if not auth_plist
raise Puppet::Error.new("Cannot parse: #{AuthDB}")
end
self.rights = auth_plist["rights"].dup
self.rules = auth_plist["rules"].dup
self.parsed_auth_db = self.rights.dup
self.parsed_auth_db.merge!(self.rules.dup)
end
end
# standard required provider instance methods
def initialize(resource)
if self.class.parsed_auth_db == {}
self.class.prefetch(resource)
end
super
end
def create
# we just fill the @property_hash in here and let the flush method
# deal with it rather than repeating code.
new_values = {}
validprops = Puppet::Type.type(resource.class.name).validproperties
validprops.each do |prop|
next if prop == :ensure
if value = resource.should(prop) and value != ""
new_values[prop] = value
end
end
@property_hash = new_values.dup
end
def destroy
# We explicitly delete here rather than in the flush method.
case resource[:auth_type]
when :right
destroy_right
when :rule
destroy_rule
else
raise Puppet::Error.new("Must specify auth_type when destroying.")
end
end
def exists?
if self.class.parsed_auth_db.has_key?(resource[:name])
return true
else
return false
end
end
def flush
# deletion happens in the destroy methods
if resource[:ensure] != :absent
case resource[:auth_type]
when :right
flush_right
when :rule
flush_rule
else
raise Puppet::Error.new("flush requested for unknown type.")
end
@property_hash.clear
end
end
# utility methods below
def destroy_right
security "authorizationdb", :remove, resource[:name]
end
def destroy_rule
authdb = Plist::parse_xml(AuthDB)
authdb_rules = authdb["rules"].dup
if authdb_rules[resource[:name]]
begin
authdb["rules"].delete(resource[:name])
Plist::Emit.save_plist(authdb, AuthDB)
rescue Errno::EACCES => e
raise Puppet::Error.new("Error saving #{AuthDB}: #{e}")
end
end
end
def flush_right
# first we re-read the right just to make sure we're in sync for
# values that weren't specified in the manifest. As we're supplying
# the whole plist when specifying the right it seems safest to be
# paranoid given the low cost of quering the db once more.
cmds = []
cmds << :security << "authorizationdb" << "read" << resource[:name]
output = execute(cmds, :combine => false)
current_values = Plist::parse_xml(output)
if current_values.nil?
current_values = {}
end
specified_values = convert_plist_to_native_attributes(@property_hash)
# take the current values, merge the specified values to obtain a
# complete description of the new values.
new_values = current_values.merge(specified_values)
set_right(resource[:name], new_values)
end
def flush_rule
authdb = Plist::parse_xml(AuthDB)
authdb_rules = authdb["rules"].dup
current_values = {}
if authdb_rules[resource[:name]]
current_values = authdb_rules[resource[:name]]
end
specified_values = convert_plist_to_native_attributes(@property_hash)
new_values = current_values.merge(specified_values)
set_rule(resource[:name], new_values)
end
def set_right(name, values)
# Both creates and modifies rights as it simply overwrites them.
# The security binary only allows for writes using stdin, so we
# dump the values to a tempfile.
values = convert_plist_to_native_attributes(values)
tmp = Tempfile.new('puppet_macauthorization')
begin
Plist::Emit.save_plist(values, tmp.path)
cmds = []
cmds << :security << "authorizationdb" << "write" << name
output = execute(cmds, :combine => false,
:stdinfile => tmp.path.to_s)
rescue Errno::EACCES => e
raise Puppet::Error.new("Cannot save right to #{tmp.path}: #{e}")
ensure
tmp.close
tmp.unlink
end
end
def set_rule(name, values)
# Both creates and modifies rules as it overwrites the entry in the
# rules dictionary. Unfortunately the security binary doesn't
# support modifying rules at all so we have to twiddle the whole
# plist... :( See Apple Bug #6386000
values = convert_plist_to_native_attributes(values)
authdb = Plist::parse_xml(AuthDB)
authdb["rules"][name] = values
begin
Plist::Emit.save_plist(authdb, AuthDB)
rescue
raise Puppet::Error.new("Error writing to: #{AuthDB}")
end
end
def convert_plist_to_native_attributes(propertylist)
# This mainly converts the keys from the puppet attributes to the
# 'native' ones, but also enforces that the keys are all Strings
# rather than Symbols so that any merges of the resultant Hash are
# sane. The exception is booleans, where we coerce to a proper bool
# if they come in as a symbol.
newplist = {}
propertylist.each_pair do |key, value|
next if key == :ensure # not part of the auth db schema.
next if key == :auth_type # not part of the auth db schema.
case value
when true, :true
value = true
when false, :false
value = false
end
new_key = key
if PuppetToNativeAttributeMap.has_key?(key)
new_key = PuppetToNativeAttributeMap[key].to_s
elsif not key.is_a?(String)
new_key = key.to_s
end
newplist[new_key] = value
end
newplist
end
def retrieve_value(resource_name, attribute)
# We set boolean values to symbols when retrieving values
if not self.class.parsed_auth_db.has_key?(resource_name)
raise Puppet::Error.new("Cannot find #{resource_name} in auth db")
end
if PuppetToNativeAttributeMap.has_key?(attribute)
native_attribute = PuppetToNativeAttributeMap[attribute]
else
native_attribute = attribute.to_s
end
if self.class.parsed_auth_db[resource_name].has_key?(native_attribute)
value = self.class.parsed_auth_db[resource_name][native_attribute]
case value
when true, :true
value = :true
when false, :false
value = :false
end
@property_hash[attribute] = value
return value
else
@property_hash.delete(attribute)
return "" # so ralsh doesn't display it.
end
end
# property methods below
#
# We define them all dynamically apart from auth_type which is a special
# case due to not being in the actual authorization db schema.
properties = [ :allow_root, :authenticate_user, :auth_class, :comment,
:group, :k_of_n, :mechanisms, :rule, :session_owner,
:shared, :timeout, :tries ]
properties.each do |field|
define_method(field.to_s) do
retrieve_value(resource[:name], field)
end
define_method(field.to_s + "=") do |value|
@property_hash[field] = value
end
end
def auth_type
if resource.should(:auth_type) != nil
return resource.should(:auth_type)
elsif self.exists?
# this is here just for ralsh, so it can work out what type it is.
if self.class.rights.has_key?(resource[:name])
return :right
elsif self.class.rules.has_key?(resource[:name])
return :rule
else
raise Puppet::Error.new("#{resource[:name]} is unknown type.")
end
else
raise Puppet::Error.new("auth_type required for new resources.")
end
end
def auth_type=(value)
@property_hash[:auth_type] = value
end
end
|