chef_fixie_shahid 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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