chef_fixie_shahid 0.5.2

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.
@@ -0,0 +1,161 @@
1
+ #
2
+ # Copyright (c) 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
+
21
+ require_relative "config.rb"
22
+ require_relative "authz_objects.rb"
23
+ require_relative "authz_mapper.rb"
24
+
25
+ require "pp"
26
+
27
+ module ChefFixie
28
+ module BulkEditPermissions
29
+ def self.orgs
30
+ @orgs ||= ChefFixie::Sql::Orgs.new
31
+ end
32
+
33
+ def self.users
34
+ @users ||= ChefFixie::Sql::Users.new
35
+ end
36
+
37
+ def self.assocs
38
+ @assocs ||= ChefFixie::Sql::Associations.new
39
+ end
40
+
41
+ def self.invites
42
+ invites ||= ChefFixie::Sql::Invites.new
43
+ end
44
+
45
+ def self.check_permissions(org)
46
+ org = orgs[org] if org.is_a?(String)
47
+ admins = org.groups["admins"].authz_id
48
+ pivotal = users["pivotal"].authz_id
49
+ errors = Hash.new({})
50
+ org.each_authz_object do |object|
51
+ begin
52
+ acl = object.acl_raw
53
+ rescue RestClient::ResourceNotFound => e
54
+ puts "#{object.class} '#{object.name}' id '#{object.id}' missing authz info"
55
+ # pp :object=>object, :e=>e
56
+ next
57
+ end
58
+ broken_acl = {}
59
+ # the one special case
60
+ acl.each do |k, v|
61
+ list = []
62
+ list << "pivotal" if !v["actors"].member?(pivotal)
63
+ # admins doesn't belong to the billing admins group
64
+ if object.class != ChefFixie::Sql::Group || object.name != "billing-admins"
65
+ list << "admins" if !v["groups"].member?(admins)
66
+ end
67
+ broken_acl[k] = list if !list.empty?
68
+ end
69
+ if !broken_acl.empty?
70
+ classname = object.class
71
+ errors[classname] = {} if !errors.has_key?(classname)
72
+ errors[classname][object.name] = broken_acl
73
+ end
74
+ end
75
+ errors
76
+ end
77
+
78
+ def self.ace_add(list, ace_type, entity)
79
+ list.each do |item|
80
+ if item.respond_to?(:ace_add)
81
+ item.ace_add(ace_type, entity)
82
+ else
83
+ puts "item.class is not a native authz type"
84
+ return nil
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.ace_delete(list, ace_type, entity)
90
+ list.each do |item|
91
+ if item.respond_to?(:ace_delete)
92
+ item.ace_delete(ace_type, entity)
93
+ else
94
+ puts "item.class is not a native authz type"
95
+ return nil
96
+ end
97
+ end
98
+ end
99
+
100
+ def self.do_all_objects(org)
101
+ org = orgs[org] if org.is_a?(String)
102
+
103
+ containers = org.containers.all(:all)
104
+ # Maybe we should fix up containers first?
105
+ # fix up objects in containers
106
+ containers.each do |container|
107
+ # TODO Write some tests to validate that this stuff
108
+ # works, since it depends on a lot of name magic...
109
+ object_type = container.name.to_sym
110
+ # raise Exception "No such object_type #{object_type}" unless org.respond_to?(object_type)
111
+ objects = org.send(object_type).all(:all)
112
+ if block_given?
113
+ yield objects
114
+ end
115
+ end
116
+ end
117
+
118
+ def self.ace_add_all(org, ace_type, entity)
119
+ org = orgs[org] if org.is_a?(String)
120
+ org.each_authz_object_by_class do |objects|
121
+ ace_add(objects, ace_type, entity)
122
+ end
123
+ end
124
+
125
+ def self.ace_delete_all(org, ace_type, entity)
126
+ org = orgs[org] if org.is_a?(String)
127
+ org.each_authz_object_by_class do |objects|
128
+ ace_delete(objects, ace_type, entity)
129
+ end
130
+ end
131
+
132
+ def self.add_admin_permissions(org)
133
+ org = orgs[org] if org.is_a?(String)
134
+ # rework when ace add takes multiple items...
135
+ admins = org.groups["admins"]
136
+ pivotal = users["pivotal"]
137
+ org.each_authz_object do |object|
138
+ object.ace_add(:all, pivotal)
139
+ if object.class != ChefFixie::Sql::Group || object.name != "billing-admins"
140
+ object.ace_add(:all, admins)
141
+ end
142
+ end
143
+ end
144
+
145
+ def self.copy_from_containers(org)
146
+ org = orgs[org] if org.is_a?(String)
147
+
148
+ containers = org.containers.all(:all)
149
+ containers.each do |c|
150
+ # don't mess with containers and groups, they are special
151
+ next if c.name == "containers" || c.name == "groups"
152
+ org.objects_by_container_type(c.name).each do |obj|
153
+ obj.acl_add_from_object(c)
154
+ puts "#{obj.name} from #{c.name}"
155
+ end
156
+ end
157
+ nil
158
+ end
159
+
160
+ end
161
+ end
@@ -0,0 +1,239 @@
1
+ # -*- indent-tabs-mode: nil; fill-column: 110 -*-
2
+ #
3
+ # Copyright (c) 2015 Chef Software Inc.
4
+ # License :: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # Author: Mark Anderson <mark@chef.io>
19
+ #
20
+
21
+ require_relative "config"
22
+ require_relative "authz_objects"
23
+ require_relative "authz_mapper"
24
+ require_relative "utility_helpers"
25
+
26
+ module ChefFixie
27
+ module CheckOrgAssociations
28
+ def self.orgs
29
+ @orgs ||= ChefFixie::Sql::Orgs.new
30
+ end
31
+
32
+ def self.users
33
+ @users ||= ChefFixie::Sql::Users.new
34
+ end
35
+
36
+ def self.assocs
37
+ @assocs ||= ChefFixie::Sql::Associations.new
38
+ end
39
+
40
+ def self.invites
41
+ invites ||= ChefFixie::Sql::Invites.new
42
+ end
43
+
44
+ def self.make_user(user)
45
+ if user.is_a?(String)
46
+ users[user]
47
+ elsif user.is_a?(ChefFixie::Sql::User)
48
+ user
49
+ else
50
+ raise "Expected a user, got a #{user.class}"
51
+ end
52
+ end
53
+
54
+ def self.make_org(org)
55
+ if org.is_a?(String)
56
+ orgs[org]
57
+ elsif org.is_a?(ChefFixie::Sql::Org)
58
+ org
59
+ else
60
+ raise "Expected an org, got a #{org.class}"
61
+ end
62
+ end
63
+
64
+ def usag_for_user(org, user)
65
+ user = make_user(user)
66
+ org = make_org(org)
67
+ org.groups[user.id]
68
+ end
69
+
70
+ def self.check_association(org, user, global_admins = nil)
71
+ # magic to make usage easier
72
+ org = make_org(org)
73
+ user = make_user(user)
74
+ global_admins ||= org.global_admins
75
+
76
+ # check that the user is associated
77
+ if !assocs.by_org_id_user_id(org.id, user.id)
78
+ return :not_associated
79
+ end
80
+
81
+ usag = org.groups[user.id]
82
+ # check that user has USAG
83
+ if usag.nil?
84
+ return :missing_usag
85
+ end
86
+
87
+ if !usag.member?(user)
88
+ return :user_not_in_usag
89
+ end
90
+
91
+ if !org.groups["users"].member?(usag)
92
+ return :usag_not_in_users
93
+ end
94
+
95
+ if !user.ace_member?(:read, global_admins)
96
+ return :global_admins_lacks_read
97
+ end
98
+
99
+ if invites.by_org_id_user_id(org.id, user.id)
100
+ return :zombie_invite
101
+ end
102
+ true
103
+ end
104
+
105
+ def self.fix_association(org, user, global_admins = nil)
106
+ # magic to make usage easier
107
+ org = orgs[org] if org.is_a?(String)
108
+ user = users[user] if user.is_a?(String)
109
+ global_admins ||= org.global_admins
110
+
111
+ failure = check_association(org, user, global_admins)
112
+
113
+ case failure
114
+ when true
115
+ puts "#{org.name} #{user.name} doesn't need repair"
116
+ when :user_not_in_usag
117
+ usag = org.groups[user.id]
118
+ usag.group_add(user)
119
+ when :usag_not_in_users
120
+ usag = org.groups[user.id]
121
+ org.groups["users"].group_add(usag)
122
+ when :global_admins_lacks_read
123
+ user.ace_add(:read, global_admins)
124
+ else
125
+ puts "#{org.name} #{user.name} can't fix problem #{failure} yet"
126
+ return false
127
+ end
128
+ true
129
+ end
130
+
131
+ def self.check_associations(org)
132
+ success = true
133
+ org = make_org(org)
134
+ orgname = org.name
135
+
136
+ # check that global_admins exists:
137
+ global_admins = org.global_admins
138
+ if !global_admins || !global_admins.is_a?(ChefFixie::Sql::Group)
139
+ puts "#{orgname} Missing global admins group"
140
+ success = false
141
+ end
142
+
143
+ users_assoc = assocs.by_org_id(org.id).all(:all)
144
+ users_invite = invites.by_org_id(org.id).all(:all)
145
+
146
+ user_ids = users_assoc.map { |a| a.user_id }
147
+ users_in_org = user_ids.map { |i| users.by_id(i).all.first }
148
+ usernames = users_in_org.map { |u| u.name }
149
+
150
+ # check that users aren't both invited and associated
151
+ invited_ids = users_invite.map { |a| a.user_id }
152
+ overlap_ids = user_ids & invited_ids
153
+
154
+ if !overlap_ids.empty?
155
+ overlap_names = overlap_ids.map { |i| users.by_id(i).all.first.name rescue "#{i}" }
156
+ puts "#{orgname} users both associated and invited: #{overlap_names.join(', ')}"
157
+ success = false
158
+ end
159
+
160
+ # Check that we don't have zombie USAGs left around (not 100% reliable)
161
+ # because someone could create a group that looks like a USAG
162
+ possible_usags = org.groups.list(:all) - user_ids
163
+ usags = possible_usags.select { |n| n =~ /^\h+{20}$/ }
164
+ if !usags.empty?
165
+ puts "#{orgname} Suspicious USAGS without associated user #{usags.join(', ')}"
166
+ end
167
+
168
+ # Check group membership for sanity
169
+ success &= check_group(org, "billing-admins", usernames)
170
+ success &= check_group(org, "admins", usernames)
171
+
172
+ # TODO check for non-usags in users!
173
+ users_members = org.groups["users"].group
174
+ users_actors = users_members["actors"] - [[:global, "pivotal"]]
175
+ if !users_actors.empty?
176
+ puts "#{orgname} has actors in it's users group #{users_actors}"
177
+ end
178
+ non_usags = users_members["groups"].map { |g| g[1] } - user_ids
179
+ if !non_usags.empty?
180
+ puts "#{orgname} warning: has non usags in it's users group #{non_usags.join(', ')}"
181
+ end
182
+
183
+ # Check individual associations
184
+ users_in_org.each do |user|
185
+ result = check_association(org, user, global_admins)
186
+ if result != true
187
+ puts "Org #{orgname} Association check failed for #{user.name} #{result}"
188
+ success = false
189
+ end
190
+ end
191
+
192
+ puts "Org #{orgname} is #{success ? 'ok' : 'bad'} (#{users_in_org.count} users)"
193
+ success
194
+ end
195
+
196
+ # expect at least one current user to be in admins and billing admins
197
+ def self.check_group(org, groupname, users)
198
+ g = org.groups[groupname]
199
+ if g.nil?
200
+ puts "#{orgname} Missing group #{groupname}"
201
+ return :no_such_group
202
+ end
203
+ actors = g.group["actors"].map { |x| x[1] }
204
+ live = actors & users
205
+
206
+ if live.count == 0
207
+ puts "Org #{org.name} has no active users in #{groupname}"
208
+ return false
209
+ end
210
+ true
211
+ end
212
+
213
+ def self.remove_association(org, user)
214
+ # magic to make usage easier
215
+ org = make_org(org)
216
+ user = make_user(user)
217
+
218
+ # remove USAG
219
+ usag = org.groups[user.id]
220
+ usag.delete if usag
221
+
222
+ # remove from any groups they are in
223
+ org.groups.all(:all).each do |g|
224
+ g.group_delete(user) if g.member?(user)
225
+ end
226
+
227
+ # remove read ACE
228
+ user.ace_delete(:read, org.global_admins)
229
+
230
+ # remove association record
231
+ assoc = assocs.by_org_id_user_id(org.id, user.id)
232
+ assoc.delete if assoc
233
+
234
+ # remove any invites
235
+ invite = invites.by_org_id_user_id(org.id, user.id)
236
+ invite.delete if invite
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,174 @@
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
+ # Much of this code was orginally derived from the orgmapper tool, which had many varied authors.
20
+
21
+ require "singleton"
22
+ require "ffi_yajl"
23
+ require "pathname"
24
+ require "veil"
25
+
26
+ module ChefFixie
27
+ def self.configure
28
+ yield Config.instance
29
+ end
30
+
31
+ def self.load_config(config_file = nil)
32
+ if config_file
33
+ puts "loading config: #{config_file}..." if ChefFixie::Console.started_from_command_line?
34
+ Kernel.load(config_file)
35
+ else
36
+ path = "/etc/opscode"
37
+ puts "loading config from #{path}" if ChefFixie::Console.started_from_command_line?
38
+ ChefFixie::Config.instance.load_from_pc(path)
39
+ end
40
+ end
41
+
42
+ def self.setup
43
+ # TODO: do we have to polute global object with this to make it available to the irb instance?
44
+ Object.const_set(:ORGS, ChefFixie::Sql::Orgs.new)
45
+ Object.const_set(:USERS, ChefFixie::Sql::Users.new)
46
+ Object.const_set(:ASSOCS, ChefFixie::Sql::Associations.new)
47
+ Object.const_set(:INVITES, ChefFixie::Sql::Invites.new)
48
+
49
+ # scope this by the global org id?
50
+ Object.const_set(:GLOBAL_GROUPS, ChefFixie::Sql::Groups.new.by_org_id(ChefFixie::Sql::Orgs::GlobalOrg))
51
+ Object.const_set(:GLOBAL_CONTAINERS, ChefFixie::Sql::Containers.new.by_org_id(ChefFixie::Sql::Orgs::GlobalOrg))
52
+ end
53
+
54
+ ##
55
+ # = ChefFixie::Config
56
+ # configuration for the fixie command.
57
+ #
58
+ # ==Example Config File:
59
+ #
60
+ # Fixie.configure do |mapper|
61
+ # mapper.authz_uri = 'http://authz.example.com:5959'
62
+ # end
63
+ #
64
+ class Config
65
+ include Singleton
66
+ KEYS = [:authz_uri, :sql_database, :superuser_id, :pivotal_key]
67
+ KEYS.each { |k| attr_accessor k }
68
+
69
+ def merge_opts(opts = {})
70
+ opts.each do |key, value|
71
+ send("#{key}=".to_sym, value)
72
+ end
73
+ end
74
+
75
+ # this is waaay tightly coupled to ::Backend's initialize method
76
+ def to_ary
77
+ [couchdb_uri, database, auth_uri, authz_couch, sql_database, superuser_id].compact
78
+ end
79
+
80
+ def to_text
81
+ txt = ["### ChefFixie::Config"]
82
+ max_key_len = KEYS.inject(0) do |max, k|
83
+ key_len = k.to_s.length
84
+ key_len > max ? key_len : max
85
+ end
86
+ KEYS.each do |key|
87
+ value = send(key) || "default"
88
+ txt << "# %#{max_key_len}s: %s" % [key.to_s, value]
89
+ end
90
+ txt.join("\n")
91
+ end
92
+
93
+ def example_config
94
+ txt = ["Fixie.configure do |mapper|"]
95
+ KEYS.each do |key|
96
+ txt << " mapper.%s = %s" % [key.to_s, '"something"']
97
+ end
98
+ txt << "end"
99
+ txt.join("\n")
100
+ end
101
+
102
+ def load_from_pc(dir = "/etc/opscode")
103
+ configdir = Pathname.new(dir)
104
+
105
+ config_files = %w{chef-server-running.json}
106
+ config = load_json_from_path([configdir], config_files)
107
+
108
+ secrets = load_secrets_from_path([configdir], %w{private-chef-secrets.json} )
109
+
110
+ authz_config = config["private_chef"]["oc_bifrost"]
111
+ authz_vip = authz_config["vip"]
112
+ authz_port = authz_config["port"]
113
+ @authz_uri = "http://#{authz_vip}:#{authz_port}"
114
+
115
+ @superuser_id = dig(secrets, %w{oc_bifrost superuser_id}) || authz_config["superuser_id"]
116
+
117
+ sql_config = config["private_chef"]["postgresql"]
118
+ erchef_config = config["private_chef"]["opscode-erchef"]
119
+
120
+ sql_user = sql_config["sql_user"] || erchef_config["sql_user"]
121
+ sql_pw = dig(secrets, %w{opscode_erchef sql_password}) || sql_config["sql_password"] || erchef_config["sql_password"]
122
+ sql_vip = sql_config["vip"]
123
+ sql_port = sql_config["port"]
124
+
125
+ @sql_database = "postgres://#{sql_user}:#{sql_pw}@#{sql_vip}/opscode_chef"
126
+
127
+ @pivotal_key = configdir + "pivotal.pem"
128
+ end
129
+
130
+ def load_json_from_path(pathlist, filelist)
131
+ parser = FFI_Yajl::Parser.new
132
+ pathlist.each do |path|
133
+ filelist.each do |file|
134
+ configfile = path + file
135
+ if configfile.file?
136
+ data = File.read(configfile)
137
+ return parser.parse(data)
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ def load_secrets_from_path(pathlist, filelist)
144
+ pathlist.each do |path|
145
+ filelist.each do |file|
146
+ configfile = path + file
147
+ if configfile.file?
148
+ data = Veil::CredentialCollection::ChefSecretsFile.from_file(configfile)
149
+ return data
150
+ end
151
+ end
152
+ end
153
+ nil
154
+ end
155
+
156
+ def dig(hash, list)
157
+ if hash.respond_to?(:get)
158
+ hash.get(*list)
159
+ elsif hash.nil?
160
+ nil
161
+ elsif list.empty?
162
+ hash
163
+ else
164
+ element = list.shift
165
+ if hash.has_key?(element)
166
+ dig(hash[element], list)
167
+ else
168
+ nil
169
+ end
170
+ end
171
+ end
172
+
173
+ end
174
+ end