chef_fixie 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +69 -0
- data/bin/bundler +16 -0
- data/bin/chef-apply +16 -0
- data/bin/chef-client +16 -0
- data/bin/chef-shell +16 -0
- data/bin/chef-solo +16 -0
- data/bin/chef-zero +16 -0
- data/bin/chef_fixie +5 -0
- data/bin/coderay +16 -0
- data/bin/edit_json.rb +16 -0
- data/bin/erubis +16 -0
- data/bin/ffi-yajl-bench +16 -0
- data/bin/fixie~ +231 -0
- data/bin/htmldiff +16 -0
- data/bin/knife +16 -0
- data/bin/ldiff +16 -0
- data/bin/net-dhcp +16 -0
- data/bin/ohai +16 -0
- data/bin/prettify_json.rb +16 -0
- data/bin/pry +16 -0
- data/bin/rackup +16 -0
- data/bin/rake +16 -0
- data/bin/rdoc +16 -0
- data/bin/restclient +16 -0
- data/bin/ri +16 -0
- data/bin/rspec +16 -0
- data/bin/s3sh +16 -0
- data/bin/sequel +16 -0
- data/bin/serverspec-init +16 -0
- data/doc/AccessingSQL.md +36 -0
- data/doc/AccessingSQL.md~ +32 -0
- data/doc/BulkFixup.md~ +28 -0
- data/doc/CommonTasks.md +20 -0
- data/doc/CommonTasks.md~ +0 -0
- data/doc/GETTING_STARTED.md +228 -0
- data/doc/GETTING_STARTED.md~ +6 -0
- data/fixie.conf.example +8 -0
- data/lib/chef_fixie.rb +27 -0
- data/lib/chef_fixie/authz_mapper.rb +143 -0
- data/lib/chef_fixie/authz_objects.rb +285 -0
- data/lib/chef_fixie/check_org_associations.rb +242 -0
- data/lib/chef_fixie/config.rb +139 -0
- data/lib/chef_fixie/console.rb +91 -0
- data/lib/chef_fixie/context.rb +72 -0
- data/lib/chef_fixie/sql.rb +74 -0
- data/lib/chef_fixie/sql_objects.rb +497 -0
- data/lib/chef_fixie/utility_helpers.rb +59 -0
- data/lib/chef_fixie/version.rb +3 -0
- data/spec/chef_fixie/acl_spec.rb +83 -0
- data/spec/chef_fixie/assoc_invite_spec.rb +47 -0
- data/spec/chef_fixie/assoc_invite_spec.rb~ +26 -0
- data/spec/chef_fixie/check_org_associations_spec.rb +140 -0
- data/spec/chef_fixie/check_org_associations_spec.rb~ +34 -0
- data/spec/chef_fixie/groups_spec.rb +34 -0
- data/spec/chef_fixie/org_spec.rb +26 -0
- data/spec/chef_fixie/org_spec.rb~ +53 -0
- data/spec/chef_fixie/orgs_spec.rb +53 -0
- data/spec/spec_helper.rb +41 -0
- metadata +252 -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
|
+
|
data/lib/chef_fixie.rb
ADDED
@@ -0,0 +1,27 @@
|
|
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 'chef_fixie/config'
|
21
|
+
require 'chef_fixie/sql'
|
22
|
+
require 'chef_fixie/sql_objects'
|
23
|
+
|
24
|
+
# This doesn't work because of initialization order, figure it out.
|
25
|
+
require 'chef_fixie/check_org_associations'
|
26
|
+
|
27
|
+
Sequel.extension :inflector
|
@@ -0,0 +1,143 @@
|
|
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 'chef_fixie/config'
|
22
|
+
require 'chef_fixie/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
|
+
return :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
|
+
self.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] = self.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
|
142
|
+
|
143
|
+
|
@@ -0,0 +1,285 @@
|
|
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 'chef_fixie/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
|
+
def put(resource, data)
|
51
|
+
result = @rest.put(resource, self.json_helper(data),
|
52
|
+
'Content-Type'=>'application/json',
|
53
|
+
'Accept'=>'application/json',
|
54
|
+
'X-Ops-Requesting-Actor-Id'=>@requestor_authz)
|
55
|
+
FFI_Yajl::Parser.parse(result)
|
56
|
+
end
|
57
|
+
def post(resource, data)
|
58
|
+
result = @rest.post(resource, self.json_helper(data),
|
59
|
+
'Content-Type'=>'application/json',
|
60
|
+
'Accept'=>'application/json',
|
61
|
+
'X-Ops-Requesting-Actor-Id'=>@requestor_authz)
|
62
|
+
FFI_Yajl::Parser.parse(result)
|
63
|
+
end
|
64
|
+
def delete(resource)
|
65
|
+
result = @rest.delete(resource,
|
66
|
+
'Content-Type'=>'application/json',
|
67
|
+
'Accept'=>'application/json',
|
68
|
+
'X-Ops-Requesting-Actor-Id'=>@requestor_authz)
|
69
|
+
FFI_Yajl::Parser.parse(result)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
module AuthzUtils
|
75
|
+
Types = [:object,:actor,:group,:container] # order is an attempt to optimize by most probable.
|
76
|
+
Actions = [:create, :read, :update, :delete, :grant]
|
77
|
+
|
78
|
+
def to_resource(t)
|
79
|
+
# This is a rails thing... t.to_s.pluralize
|
80
|
+
t.to_s + "s" # hack
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_type(id)
|
84
|
+
Types.each do |t|
|
85
|
+
begin
|
86
|
+
r = AuthzApi.get("#{self.to_resource(t)}/#{id}")
|
87
|
+
return t
|
88
|
+
rescue RestClient::ResourceNotFound=>e
|
89
|
+
# expected if not found
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return :none
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_action(action)
|
96
|
+
# TODO Improve; stack trace isn't the best way to communicate with the user
|
97
|
+
raise "#{action} not one of #{Actions.join(', ')} " if !Actions.member?(action)
|
98
|
+
end
|
99
|
+
|
100
|
+
def check_actor_or_group(a_or_g)
|
101
|
+
raise "#{a_or_g} not one of :actor or :group" if a_or_g != :actor && a_or_g != :group
|
102
|
+
end
|
103
|
+
|
104
|
+
def resourcify_actor_or_group(a_or_g)
|
105
|
+
return a_or_g if ["actors", "groups"].member?(a_or_g)
|
106
|
+
check_actor_or_group(a_or_g)
|
107
|
+
to_resource(a_or_g)
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_authz_id(x)
|
111
|
+
return x.authz_id if x.respond_to?(:authz_id)
|
112
|
+
# if it quacks like an authz id
|
113
|
+
return x if x.is_a?(String) && x =~ /^[[:xdigit:]]{32}$/
|
114
|
+
raise "#{x} doesn't look like an authz_id"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
module AuthzObjectMixin
|
120
|
+
include AuthzUtils # reconsider this mixin; maybe better to refer to those routines explictly
|
121
|
+
|
122
|
+
def self.included(base)
|
123
|
+
# pp :note=>"Include", :base=>base, :super=>(base.superclass rescue :nil)
|
124
|
+
# block = lambda { :object }
|
125
|
+
# base.send(:define_method, :type_me, block )
|
126
|
+
# pp :methods=>(base.methods.sort - Object.methods)
|
127
|
+
end
|
128
|
+
|
129
|
+
def type
|
130
|
+
:object
|
131
|
+
end
|
132
|
+
|
133
|
+
def authz_api
|
134
|
+
@@authz_apiAsSuperUser ||= AuthzApi.new
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
# we expect to be mixed in with a class that has the authz_id method
|
139
|
+
def prefix
|
140
|
+
"#{to_resource(type)}/#{authz_id}"
|
141
|
+
end
|
142
|
+
|
143
|
+
def is_authorized(action, actor)
|
144
|
+
result = authz_api.get("#{prefix}/acl/#{action}/ace/#{actor.authz_id}")
|
145
|
+
[:unparsed, result] # todo figure this out in more detail
|
146
|
+
end
|
147
|
+
|
148
|
+
def acl_raw
|
149
|
+
authz_api.get("#{prefix}/acl")
|
150
|
+
end
|
151
|
+
# Todo: filter this by scope and type
|
152
|
+
def acl
|
153
|
+
ChefFixie::AuthzMapper.struct_to_name(acl_raw)
|
154
|
+
end
|
155
|
+
|
156
|
+
def ace_get_util(action)
|
157
|
+
check_action(action)
|
158
|
+
|
159
|
+
resource = "#{prefix}/acl/#{action}"
|
160
|
+
ace = authz_api.get(resource)
|
161
|
+
[resource, ace]
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def ace_raw(action)
|
166
|
+
resource,ace = ace_get_util(action)
|
167
|
+
ace
|
168
|
+
end
|
169
|
+
# Todo: filter this by scope and type
|
170
|
+
def ace(action)
|
171
|
+
ChefFixie::AuthzMapper.struct_to_name(ace_raw(action))
|
172
|
+
end
|
173
|
+
|
174
|
+
def expand_actions(action)
|
175
|
+
if action == :all
|
176
|
+
action = AuthzUtils::Actions
|
177
|
+
end
|
178
|
+
action.is_a?(Array) ? action : [action]
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
# add actor or group to acl
|
184
|
+
def ace_add_raw(action, actor_or_group, entity)
|
185
|
+
# groups or actors
|
186
|
+
a_or_g_resource = resourcify_actor_or_group(actor_or_group)
|
187
|
+
resource, ace = ace_get_util(action)
|
188
|
+
|
189
|
+
ace[a_or_g_resource] << get_authz_id(entity)
|
190
|
+
ace[a_or_g_resource].uniq!
|
191
|
+
authz_api.put("#{resource}", ace)
|
192
|
+
end
|
193
|
+
def ace_add(action, entity)
|
194
|
+
actions = expand_actions(action)
|
195
|
+
actions.each {|a| ace_add_raw(a, entity.type, entity) }
|
196
|
+
end
|
197
|
+
|
198
|
+
def ace_delete_raw(action, actor_or_group, entity)
|
199
|
+
# groups or actors
|
200
|
+
a_or_g_resource = resourcify_actor_or_group(actor_or_group)
|
201
|
+
resource, ace = ace_get_util(action)
|
202
|
+
|
203
|
+
ace[a_or_g_resource] -= [get_authz_id(entity)]
|
204
|
+
ace[a_or_g_resource].uniq!
|
205
|
+
authz_api.put("#{resource}", ace)
|
206
|
+
end
|
207
|
+
|
208
|
+
def ace_delete(action, entity)
|
209
|
+
actions = expand_actions(action)
|
210
|
+
actions.each {|a| ace_delete_raw(a, entity.type, entity) }
|
211
|
+
end
|
212
|
+
|
213
|
+
def ace_member?(action, entity)
|
214
|
+
a_or_g_resource = resourcify_actor_or_group(entity.type)
|
215
|
+
resource, ace = ace_get_util(action)
|
216
|
+
ace[a_or_g_resource].member?(entity.authz_id)
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
def acl_add_from_object(object)
|
221
|
+
src = object.acl_raw
|
222
|
+
|
223
|
+
# this could be made more efficient by refactoring ace_add_raw to split fetch and update, but this works
|
224
|
+
src.each do |action, ace|
|
225
|
+
ace.each do |type, list|
|
226
|
+
list.each do |item|
|
227
|
+
ace_add_raw(action.to_sym, type, item)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
module AuthzActorMixin
|
236
|
+
include AuthzObjectMixin
|
237
|
+
def type
|
238
|
+
:actor
|
239
|
+
end
|
240
|
+
end
|
241
|
+
module AuthzContainerMixin
|
242
|
+
include AuthzObjectMixin
|
243
|
+
def type
|
244
|
+
:container
|
245
|
+
end
|
246
|
+
end
|
247
|
+
module AuthzGroupMixin
|
248
|
+
include AuthzObjectMixin
|
249
|
+
def type
|
250
|
+
:group
|
251
|
+
end
|
252
|
+
|
253
|
+
# Groups need a little more code to manage members.
|
254
|
+
def group_raw
|
255
|
+
authz_api.get("#{prefix}")
|
256
|
+
end
|
257
|
+
# Todo: filter this by scope and type
|
258
|
+
def group
|
259
|
+
ChefFixie::AuthzMapper.struct_to_name(group_raw)
|
260
|
+
end
|
261
|
+
|
262
|
+
def group_add_raw(actor_or_group, entity)
|
263
|
+
entity_resource = to_resource(actor_or_group)
|
264
|
+
authz_api.put("#{prefix}/#{entity_resource}/#{entity.authz_id}",{})
|
265
|
+
end
|
266
|
+
def group_add(entity)
|
267
|
+
group_add_raw(entity.type, entity)
|
268
|
+
end
|
269
|
+
|
270
|
+
def group_delete_raw(actor_or_group, entity)
|
271
|
+
entity_resource = to_resource(actor_or_group)
|
272
|
+
authz_api.delete("#{prefix}/#{entity_resource}/#{entity.authz_id}")
|
273
|
+
end
|
274
|
+
|
275
|
+
def group_delete(entity)
|
276
|
+
group_delete_raw(entity.type, entity)
|
277
|
+
end
|
278
|
+
|
279
|
+
def member?(entity)
|
280
|
+
members = group_raw
|
281
|
+
return members[resourcify_actor_or_group(entity.type)].member?(entity.authz_id)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|