chef_fixie_shahid 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +69 -0
- data/bin/chef_fixie +5 -0
- data/doc/AccessingSQL.md +68 -0
- data/doc/BulkFixup.md +33 -0
- data/doc/CommonTasks.md +31 -0
- data/doc/GETTING_STARTED.md +228 -0
- data/fixie.conf.example +8 -0
- data/lib/chef_fixie_shahid.rb +28 -0
- data/lib/chef_fixie_shahid/authz_mapper.rb +141 -0
- data/lib/chef_fixie_shahid/authz_objects.rb +295 -0
- data/lib/chef_fixie_shahid/bulk_edit_permissions.rb +161 -0
- data/lib/chef_fixie_shahid/check_org_associations.rb +239 -0
- data/lib/chef_fixie_shahid/config.rb +174 -0
- data/lib/chef_fixie_shahid/console.rb +96 -0
- data/lib/chef_fixie_shahid/context.rb +70 -0
- data/lib/chef_fixie_shahid/sql.rb +74 -0
- data/lib/chef_fixie_shahid/sql_objects.rb +573 -0
- data/lib/chef_fixie_shahid/utility_helpers.rb +63 -0
- data/lib/chef_fixie_shahid/version.rb +3 -0
- data/spec/chef_fixie/acl_spec.rb +81 -0
- data/spec/chef_fixie/assoc_invite_spec.rb +44 -0
- data/spec/chef_fixie/check_org_associations_spec.rb +137 -0
- data/spec/chef_fixie/groups_spec.rb +30 -0
- data/spec/chef_fixie/org_spec.rb +25 -0
- data/spec/chef_fixie/orgs_spec.rb +50 -0
- data/spec/spec_helper.rb +40 -0
- metadata +216 -0
data/fixie.conf.example
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
|
2
|
+
Fixie.configure do |mapper|
|
3
|
+
mapper.authz_uri = "http://localhost:9463"
|
4
|
+
mapper.superuser_id = "33d88ba14277f812a3ef3989869fb393"
|
5
|
+
mapper.sql_database = 'postgres://opscode_chef:6c6e1a140828be4ac406848e0b6a2ae1e9845e45ac4f48c790168c681c1b4217dc27ab99599130449a2437ab0d2a300bed5d@localhost/opscode_chef'
|
6
|
+
end
|
7
|
+
|
8
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014-2015 Chef Software Inc.
|
3
|
+
# License :: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
# Author: Mark Anderson <mark@chef.io>
|
18
|
+
|
19
|
+
require "sequel"
|
20
|
+
require_relative "chef_fixie/config"
|
21
|
+
require_relative "chef_fixie/sql"
|
22
|
+
require_relative "chef_fixie/sql_objects"
|
23
|
+
|
24
|
+
# This doesn't work because of initialization order, figure it out.
|
25
|
+
require_relative "chef_fixie/check_org_associations"
|
26
|
+
require_relative "chef_fixie/bulk_edit_permissions"
|
27
|
+
|
28
|
+
Sequel.extension :inflector
|
@@ -0,0 +1,141 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014-2015 Chef Software Inc.
|
3
|
+
# License :: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
# Author: Mark Anderson <mark@chef.io>
|
18
|
+
#
|
19
|
+
|
20
|
+
require "pp"
|
21
|
+
require_relative "config"
|
22
|
+
require_relative "authz_objects"
|
23
|
+
|
24
|
+
module ChefFixie
|
25
|
+
module AuthzMapper
|
26
|
+
|
27
|
+
#
|
28
|
+
# It would be really awesome if this was integrated with the
|
29
|
+
# AuthzObjectMixin so that when it was mixed in, we automatically
|
30
|
+
# added code to the reverse mapping
|
31
|
+
#
|
32
|
+
#
|
33
|
+
# Much of this might be better folded up into a sql stored procedure
|
34
|
+
#
|
35
|
+
|
36
|
+
def self.included(base)
|
37
|
+
base.extend(ClassMethods)
|
38
|
+
end
|
39
|
+
|
40
|
+
def authz_to_name(authz_id)
|
41
|
+
objects = by_authz_id(authz_id).all(1)
|
42
|
+
scope = :unknown
|
43
|
+
name = :unknown
|
44
|
+
if objects.count == 1
|
45
|
+
object = objects.first
|
46
|
+
name = object.name
|
47
|
+
scope =
|
48
|
+
if object.respond_to?(:org_id)
|
49
|
+
ChefFixie::Sql::Orgs.org_guid_to_name(object.org_id)
|
50
|
+
else
|
51
|
+
:global
|
52
|
+
end
|
53
|
+
[scope, name]
|
54
|
+
else
|
55
|
+
:unknown
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ReverseMapper
|
60
|
+
attr_reader :names, :by_type, :instance
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
# name of object map
|
64
|
+
@names ||= {}
|
65
|
+
@by_type ||= { :actor => {}, :container => {}, :group => {}, :object => {} }
|
66
|
+
# maps class to a pre-created instance for efficiency
|
67
|
+
@instance ||= {}
|
68
|
+
end
|
69
|
+
|
70
|
+
def class_cache(klass)
|
71
|
+
instance[klass] ||= klass.new
|
72
|
+
end
|
73
|
+
|
74
|
+
def register(klass, name, type)
|
75
|
+
names[name] = klass
|
76
|
+
by_type[type][name] = klass
|
77
|
+
end
|
78
|
+
|
79
|
+
def dump
|
80
|
+
pp names
|
81
|
+
end
|
82
|
+
|
83
|
+
def authz_to_name(authz_id, ctype = nil)
|
84
|
+
types = if ctype.nil?
|
85
|
+
AuthzUtils::TYPES
|
86
|
+
else
|
87
|
+
[ctype]
|
88
|
+
end
|
89
|
+
types.each do |type|
|
90
|
+
by_type[type].each_pair do |name, klass|
|
91
|
+
result = class_cache(klass).authz_to_name(authz_id)
|
92
|
+
return result if result != :unknown
|
93
|
+
end
|
94
|
+
end
|
95
|
+
:unknown
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.mapper
|
100
|
+
@mapper ||= ReverseMapper.new
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.register(klass, name, type)
|
104
|
+
mapper.register(klass, name, type)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Translates the json from authz for group membership and acls into a human readable form
|
108
|
+
# This makes some assumptions about the shape of the data structure, but works well enough to
|
109
|
+
# be quite useful
|
110
|
+
def self.struct_to_name(s)
|
111
|
+
mapper = AuthzMapper.mapper
|
112
|
+
if s.kind_of?(Hash)
|
113
|
+
s.keys.inject({}) do |h, k|
|
114
|
+
v = s[k]
|
115
|
+
if v.kind_of?(Array)
|
116
|
+
case k
|
117
|
+
when "actors"
|
118
|
+
h[k] = v.map { |a| mapper.authz_to_name(a, :actor) } #.sort We should sort these, but the way we're returning unknown causes sort
|
119
|
+
when "groups"
|
120
|
+
h[k] = v.map { |a| mapper.authz_to_name(a, :group) } #.sort to fail
|
121
|
+
else
|
122
|
+
h[k] = v
|
123
|
+
end
|
124
|
+
else
|
125
|
+
h[k] = struct_to_name(v)
|
126
|
+
end
|
127
|
+
h
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
module ClassMethods
|
133
|
+
# TODO: We should be able to automatically figure out the type somehow.
|
134
|
+
# At minimum should figure out a self check
|
135
|
+
def register_authz(name, type)
|
136
|
+
AuthzMapper.register(self, name, type)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,295 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014-2015 Chef Software Inc.
|
3
|
+
# License :: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
# Author: Mark Anderson <mark@chef.io>
|
18
|
+
#
|
19
|
+
|
20
|
+
require "pp"
|
21
|
+
require "ffi_yajl"
|
22
|
+
require "chef/http"
|
23
|
+
|
24
|
+
require_relative "config"
|
25
|
+
|
26
|
+
module ChefFixie
|
27
|
+
|
28
|
+
class AuthzApi
|
29
|
+
def initialize(user = nil)
|
30
|
+
@requestor_authz = user ? user : ChefFixie.configure { |x| x.superuser_id }
|
31
|
+
@auth_uri ||= ChefFixie.configure { |x| x.authz_uri }
|
32
|
+
@rest = Chef::HTTP.new(@auth_uri)
|
33
|
+
end
|
34
|
+
|
35
|
+
def json_helper(s)
|
36
|
+
if s.kind_of?(Hash)
|
37
|
+
FFI_Yajl::Encoder.encode(s)
|
38
|
+
else
|
39
|
+
s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(resource)
|
44
|
+
result = @rest.get(resource,
|
45
|
+
"Content-Type" => "application/json",
|
46
|
+
"Accept" => "application/json",
|
47
|
+
"X-Ops-Requesting-Actor-Id" => @requestor_authz)
|
48
|
+
FFI_Yajl::Parser.parse(result)
|
49
|
+
end
|
50
|
+
|
51
|
+
def put(resource, data)
|
52
|
+
result = @rest.put(resource, json_helper(data),
|
53
|
+
"Content-Type" => "application/json",
|
54
|
+
"Accept" => "application/json",
|
55
|
+
"X-Ops-Requesting-Actor-Id" => @requestor_authz)
|
56
|
+
FFI_Yajl::Parser.parse(result)
|
57
|
+
end
|
58
|
+
|
59
|
+
def post(resource, data)
|
60
|
+
result = @rest.post(resource, json_helper(data),
|
61
|
+
"Content-Type" => "application/json",
|
62
|
+
"Accept" => "application/json",
|
63
|
+
"X-Ops-Requesting-Actor-Id" => @requestor_authz)
|
64
|
+
FFI_Yajl::Parser.parse(result)
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete(resource)
|
68
|
+
result = @rest.delete(resource,
|
69
|
+
"Content-Type" => "application/json",
|
70
|
+
"Accept" => "application/json",
|
71
|
+
"X-Ops-Requesting-Actor-Id" => @requestor_authz)
|
72
|
+
FFI_Yajl::Parser.parse(result)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
module AuthzUtils
|
78
|
+
TYPES = [:object, :actor, :group, :container] # order is an attempt to optimize by most probable.
|
79
|
+
ACTIONS = [:create, :read, :update, :delete, :grant]
|
80
|
+
|
81
|
+
def to_resource(t)
|
82
|
+
# This is a rails thing... t.to_s.pluralize
|
83
|
+
t.to_s + "s" # hack
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_type(id)
|
87
|
+
TYPES.each do |t|
|
88
|
+
begin
|
89
|
+
r = AuthzApi.get("#{to_resource(t)}/#{id}")
|
90
|
+
return t
|
91
|
+
rescue RestClient::ResourceNotFound => e
|
92
|
+
# expected if not found
|
93
|
+
end
|
94
|
+
end
|
95
|
+
:none
|
96
|
+
end
|
97
|
+
|
98
|
+
def check_action(action)
|
99
|
+
# TODO Improve; stack trace isn't the best way to communicate with the user
|
100
|
+
raise "#{action} not one of #{ACTIONS.join(', ')} " if !ACTIONS.member?(action)
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_actor_or_group(a_or_g)
|
104
|
+
raise "#{a_or_g} not one of :actor or :group" if a_or_g != :actor && a_or_g != :group
|
105
|
+
end
|
106
|
+
|
107
|
+
def resourcify_actor_or_group(a_or_g)
|
108
|
+
return a_or_g if %w{actors groups}.member?(a_or_g)
|
109
|
+
check_actor_or_group(a_or_g)
|
110
|
+
to_resource(a_or_g)
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_authz_id(x)
|
114
|
+
return x.authz_id if x.respond_to?(:authz_id)
|
115
|
+
# if it quacks like an authz id
|
116
|
+
return x if x.is_a?(String) && x =~ /^[[:xdigit:]]{32}$/
|
117
|
+
raise "#{x} doesn't look like an authz_id"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
module AuthzObjectMixin
|
123
|
+
include AuthzUtils # reconsider this mixin; maybe better to refer to those routines explictly
|
124
|
+
|
125
|
+
def self.included(base)
|
126
|
+
# pp :note=>"Include", :base=>base, :super=>(base.superclass rescue :nil)
|
127
|
+
# block = lambda { :object }
|
128
|
+
# base.send(:define_method, :type_me, block )
|
129
|
+
# pp :methods=>(base.methods.sort - Object.methods)
|
130
|
+
end
|
131
|
+
|
132
|
+
def type
|
133
|
+
:object
|
134
|
+
end
|
135
|
+
|
136
|
+
def authz_api
|
137
|
+
@@authz_api_as_superuser ||= AuthzApi.new
|
138
|
+
end
|
139
|
+
|
140
|
+
# we expect to be mixed in with a class that has the authz_id method
|
141
|
+
def prefix
|
142
|
+
"#{to_resource(type)}/#{authz_id}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def is_authorized(action, actor)
|
146
|
+
result = authz_api.get("#{prefix}/acl/#{action}/ace/#{actor.authz_id}")
|
147
|
+
[:unparsed, result] # todo figure this out in more detail
|
148
|
+
end
|
149
|
+
|
150
|
+
def authz_delete
|
151
|
+
authz_api.delete(prefix)
|
152
|
+
end
|
153
|
+
|
154
|
+
def acl_raw
|
155
|
+
authz_api.get("#{prefix}/acl")
|
156
|
+
end
|
157
|
+
|
158
|
+
# Todo: filter this by scope and type
|
159
|
+
def acl
|
160
|
+
ChefFixie::AuthzMapper.struct_to_name(acl_raw)
|
161
|
+
end
|
162
|
+
|
163
|
+
def ace_get_util(action)
|
164
|
+
check_action(action)
|
165
|
+
|
166
|
+
resource = "#{prefix}/acl/#{action}"
|
167
|
+
ace = authz_api.get(resource)
|
168
|
+
[resource, ace]
|
169
|
+
end
|
170
|
+
|
171
|
+
def ace_raw(action)
|
172
|
+
resource, ace = ace_get_util(action)
|
173
|
+
ace
|
174
|
+
end
|
175
|
+
|
176
|
+
# Todo: filter this by scope and type
|
177
|
+
def ace(action)
|
178
|
+
ChefFixie::AuthzMapper.struct_to_name(ace_raw(action))
|
179
|
+
end
|
180
|
+
|
181
|
+
def expand_actions(action)
|
182
|
+
if action == :all
|
183
|
+
action = AuthzUtils::ACTIONS
|
184
|
+
end
|
185
|
+
action.is_a?(Array) ? action : [action]
|
186
|
+
end # add actor or group to acl
|
187
|
+
|
188
|
+
def ace_add_raw(action, actor_or_group, entity)
|
189
|
+
# groups or actors
|
190
|
+
a_or_g_resource = resourcify_actor_or_group(actor_or_group)
|
191
|
+
resource, ace = ace_get_util(action)
|
192
|
+
|
193
|
+
ace[a_or_g_resource] << get_authz_id(entity)
|
194
|
+
ace[a_or_g_resource].uniq!
|
195
|
+
authz_api.put("#{resource}", ace)
|
196
|
+
end
|
197
|
+
|
198
|
+
def ace_add(action, entity)
|
199
|
+
actions = expand_actions(action)
|
200
|
+
actions.each { |a| ace_add_raw(a, entity.type, entity) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def ace_delete_raw(action, actor_or_group, entity)
|
204
|
+
# groups or actors
|
205
|
+
a_or_g_resource = resourcify_actor_or_group(actor_or_group)
|
206
|
+
resource, ace = ace_get_util(action)
|
207
|
+
|
208
|
+
ace[a_or_g_resource] -= [get_authz_id(entity)]
|
209
|
+
ace[a_or_g_resource].uniq!
|
210
|
+
authz_api.put("#{resource}", ace)
|
211
|
+
end
|
212
|
+
|
213
|
+
def ace_delete(action, entity)
|
214
|
+
actions = expand_actions(action)
|
215
|
+
actions.each { |a| ace_delete_raw(a, entity.type, entity) }
|
216
|
+
end
|
217
|
+
|
218
|
+
def ace_member?(action, entity)
|
219
|
+
a_or_g_resource = resourcify_actor_or_group(entity.type)
|
220
|
+
resource, ace = ace_get_util(action)
|
221
|
+
ace[a_or_g_resource].member?(entity.authz_id)
|
222
|
+
end
|
223
|
+
|
224
|
+
def acl_add_from_object(object)
|
225
|
+
src = object.acl_raw
|
226
|
+
|
227
|
+
# this could be made more efficient by refactoring ace_add_raw to split fetch and update, but this works
|
228
|
+
src.each do |action, ace|
|
229
|
+
ace.each do |type, list|
|
230
|
+
list.each do |item|
|
231
|
+
ace_add_raw(action.to_sym, type, item)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
module AuthzActorMixin
|
240
|
+
include AuthzObjectMixin
|
241
|
+
def type
|
242
|
+
:actor
|
243
|
+
end
|
244
|
+
end
|
245
|
+
module AuthzContainerMixin
|
246
|
+
include AuthzObjectMixin
|
247
|
+
def type
|
248
|
+
:container
|
249
|
+
end
|
250
|
+
end
|
251
|
+
module AuthzGroupMixin
|
252
|
+
include AuthzObjectMixin
|
253
|
+
def type
|
254
|
+
:group
|
255
|
+
end
|
256
|
+
|
257
|
+
# Groups need a little more code to manage members.
|
258
|
+
def group_raw
|
259
|
+
authz_api.get("#{prefix}")
|
260
|
+
end
|
261
|
+
|
262
|
+
# Todo: filter this by scope and type
|
263
|
+
def group
|
264
|
+
ChefFixie::AuthzMapper.struct_to_name(group_raw)
|
265
|
+
end
|
266
|
+
|
267
|
+
def list
|
268
|
+
group
|
269
|
+
end
|
270
|
+
|
271
|
+
def group_add_raw(actor_or_group, entity)
|
272
|
+
entity_resource = to_resource(actor_or_group)
|
273
|
+
authz_api.put("#{prefix}/#{entity_resource}/#{entity.authz_id}", {})
|
274
|
+
end
|
275
|
+
|
276
|
+
def group_add(entity)
|
277
|
+
group_add_raw(entity.type, entity)
|
278
|
+
end
|
279
|
+
|
280
|
+
def group_delete_raw(actor_or_group, entity)
|
281
|
+
entity_resource = to_resource(actor_or_group)
|
282
|
+
authz_api.delete("#{prefix}/#{entity_resource}/#{entity.authz_id}")
|
283
|
+
end
|
284
|
+
|
285
|
+
def group_delete(entity)
|
286
|
+
group_delete_raw(entity.type, entity)
|
287
|
+
end
|
288
|
+
|
289
|
+
def member?(entity)
|
290
|
+
members = group_raw
|
291
|
+
members[resourcify_actor_or_group(entity.type)].member?(entity.authz_id)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|