chef_fixie 0.1.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/bin/chef_fixie +2 -2
  3. data/doc/AccessingSQL.md +33 -1
  4. data/doc/BulkFixup.md +33 -0
  5. data/doc/CommonTasks.md +14 -3
  6. data/lib/chef_fixie.rb +7 -6
  7. data/lib/chef_fixie/authz_mapper.rb +26 -28
  8. data/lib/chef_fixie/authz_objects.rb +51 -41
  9. data/lib/chef_fixie/bulk_edit_permissions.rb +161 -0
  10. data/lib/chef_fixie/check_org_associations.rb +56 -59
  11. data/lib/chef_fixie/config.rb +58 -23
  12. data/lib/chef_fixie/console.rb +17 -12
  13. data/lib/chef_fixie/context.rb +2 -4
  14. data/lib/chef_fixie/sql.rb +12 -12
  15. data/lib/chef_fixie/sql_objects.rb +107 -31
  16. data/lib/chef_fixie/utility_helpers.rb +13 -9
  17. data/lib/chef_fixie/version.rb +1 -1
  18. data/spec/chef_fixie/acl_spec.rb +23 -25
  19. data/spec/chef_fixie/assoc_invite_spec.rb +5 -8
  20. data/spec/chef_fixie/check_org_associations_spec.rb +14 -17
  21. data/spec/chef_fixie/groups_spec.rb +7 -11
  22. data/spec/chef_fixie/org_spec.rb +4 -5
  23. data/spec/chef_fixie/orgs_spec.rb +6 -9
  24. data/spec/spec_helper.rb +5 -6
  25. metadata +13 -49
  26. data/bin/bundler +0 -16
  27. data/bin/chef-apply +0 -16
  28. data/bin/chef-client +0 -16
  29. data/bin/chef-shell +0 -16
  30. data/bin/chef-solo +0 -16
  31. data/bin/chef-zero +0 -16
  32. data/bin/coderay +0 -16
  33. data/bin/edit_json.rb +0 -16
  34. data/bin/erubis +0 -16
  35. data/bin/ffi-yajl-bench +0 -16
  36. data/bin/fixie~ +0 -231
  37. data/bin/htmldiff +0 -16
  38. data/bin/knife +0 -16
  39. data/bin/ldiff +0 -16
  40. data/bin/net-dhcp +0 -16
  41. data/bin/ohai +0 -16
  42. data/bin/prettify_json.rb +0 -16
  43. data/bin/pry +0 -16
  44. data/bin/rackup +0 -16
  45. data/bin/rake +0 -16
  46. data/bin/rdoc +0 -16
  47. data/bin/restclient +0 -16
  48. data/bin/ri +0 -16
  49. data/bin/rspec +0 -16
  50. data/bin/s3sh +0 -16
  51. data/bin/sequel +0 -16
  52. data/bin/serverspec-init +0 -16
  53. data/doc/AccessingSQL.md~ +0 -32
  54. data/doc/BulkFixup.md~ +0 -28
  55. data/doc/CommonTasks.md~ +0 -0
  56. data/doc/GETTING_STARTED.md~ +0 -6
  57. data/spec/chef_fixie/assoc_invite_spec.rb~ +0 -26
  58. data/spec/chef_fixie/check_org_associations_spec.rb~ +0 -34
  59. data/spec/chef_fixie/org_spec.rb~ +0 -53
@@ -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
@@ -18,46 +18,55 @@
18
18
  # Author: Mark Anderson <mark@chef.io>
19
19
  #
20
20
 
21
- require 'chef_fixie/config'
22
- require 'chef_fixie/authz_objects'
23
- require 'chef_fixie/authz_mapper'
24
-
25
- require 'chef_fixie/utility_helpers'
21
+ require_relative "config"
22
+ require_relative "authz_objects"
23
+ require_relative "authz_mapper"
24
+ require_relative "utility_helpers"
26
25
 
27
26
  module ChefFixie
28
27
  module CheckOrgAssociations
29
28
  def self.orgs
30
29
  @orgs ||= ChefFixie::Sql::Orgs.new
31
30
  end
31
+
32
32
  def self.users
33
33
  @users ||= ChefFixie::Sql::Users.new
34
34
  end
35
+
35
36
  def self.assocs
36
37
  @assocs ||= ChefFixie::Sql::Associations.new
37
38
  end
39
+
38
40
  def self.invites
39
41
  invites ||= ChefFixie::Sql::Invites.new
40
42
  end
41
43
 
42
44
  def self.make_user(user)
43
45
  if user.is_a?(String)
44
- return users[user]
46
+ users[user]
45
47
  elsif user.is_a?(ChefFixie::Sql::User)
46
- return user
48
+ user
47
49
  else
48
50
  raise "Expected a user, got a #{user.class}"
49
51
  end
50
52
  end
53
+
51
54
  def self.make_org(org)
52
55
  if org.is_a?(String)
53
- return orgs[org]
56
+ orgs[org]
54
57
  elsif org.is_a?(ChefFixie::Sql::Org)
55
- return org
58
+ org
56
59
  else
57
60
  raise "Expected an org, got a #{org.class}"
58
61
  end
59
62
  end
60
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
+
61
70
  def self.check_association(org, user, global_admins = nil)
62
71
  # magic to make usage easier
63
72
  org = make_org(org)
@@ -79,7 +88,7 @@ module ChefFixie
79
88
  return :user_not_in_usag
80
89
  end
81
90
 
82
- if !org.groups['users'].member?(usag)
91
+ if !org.groups["users"].member?(usag)
83
92
  return :usag_not_in_users
84
93
  end
85
94
 
@@ -90,7 +99,7 @@ module ChefFixie
90
99
  if invites.by_org_id_user_id(org.id, user.id)
91
100
  return :zombie_invite
92
101
  end
93
- return true
102
+ true
94
103
  end
95
104
 
96
105
  def self.fix_association(org, user, global_admins = nil)
@@ -99,7 +108,7 @@ module ChefFixie
99
108
  user = users[user] if user.is_a?(String)
100
109
  global_admins ||= org.global_admins
101
110
 
102
- failure = check_association(org,user,global_admins)
111
+ failure = check_association(org, user, global_admins)
103
112
 
104
113
  case failure
105
114
  when true
@@ -109,14 +118,14 @@ module ChefFixie
109
118
  usag.group_add(user)
110
119
  when :usag_not_in_users
111
120
  usag = org.groups[user.id]
112
- org.groups['users'].group_add(usag)
121
+ org.groups["users"].group_add(usag)
113
122
  when :global_admins_lacks_read
114
123
  user.ace_add(:read, global_admins)
115
124
  else
116
125
  puts "#{org.name} #{user.name} can't fix problem #{failure} yet"
117
126
  return false
118
127
  end
119
- return true
128
+ true
120
129
  end
121
130
 
122
131
  def self.check_associations(org)
@@ -134,56 +143,54 @@ module ChefFixie
134
143
  users_assoc = assocs.by_org_id(org.id).all(:all)
135
144
  users_invite = invites.by_org_id(org.id).all(:all)
136
145
 
137
- user_ids = users_assoc.map {|a| a.user_id }
138
- users_in_org = user_ids.map {|i| users.by_id(i).all.first }
139
- usernames = users_in_org.map {|u| u.name }
140
-
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 }
141
149
 
142
150
  # check that users aren't both invited and associated
143
- invited_ids = users_invite.map {|a| a.user_id }
151
+ invited_ids = users_invite.map { |a| a.user_id }
144
152
  overlap_ids = user_ids & invited_ids
145
153
 
146
154
  if !overlap_ids.empty?
147
- overlap_names = overlap_ids.map {|i| users.by_id(i).all.first.name rescue "#{i}" }
148
- puts "#{orgname} users both associated and invited: #{overlap_names.join(', ') }"
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(', ')}"
149
157
  success = false
150
158
  end
151
159
 
152
160
  # Check that we don't have zombie USAGs left around (not 100% reliable)
153
161
  # because someone could create a group that looks like a USAG
154
162
  possible_usags = org.groups.list(:all) - user_ids
155
- usags = possible_usags.select {|n| n =~ /^\h+{20}$/ }
163
+ usags = possible_usags.select { |n| n =~ /^\h+{20}$/ }
156
164
  if !usags.empty?
157
- puts "#{orgname} Suspicious USAGS without associated user #{usags.join(', ') }"
165
+ puts "#{orgname} Suspicious USAGS without associated user #{usags.join(', ')}"
158
166
  end
159
167
 
160
168
  # Check group membership for sanity
161
- success &= check_group(org, 'billing-admins', usernames)
162
- success &= check_group(org, 'admins', usernames)
169
+ success &= check_group(org, "billing-admins", usernames)
170
+ success &= check_group(org, "admins", usernames)
163
171
 
164
172
  # TODO check for non-usags in users!
165
- users_members = org.groups['users'].group
166
- users_actors = users_members['actors'] - [[:global, "pivotal"]]
173
+ users_members = org.groups["users"].group
174
+ users_actors = users_members["actors"] - [[:global, "pivotal"]]
167
175
  if !users_actors.empty?
168
176
  puts "#{orgname} has actors in it's users group #{users_actors}"
169
177
  end
170
- non_usags = users_members['groups'].map {|g| g[1] } - user_ids
178
+ non_usags = users_members["groups"].map { |g| g[1] } - user_ids
171
179
  if !non_usags.empty?
172
180
  puts "#{orgname} warning: has non usags in it's users group #{non_usags.join(', ')}"
173
181
  end
174
182
 
175
-
176
183
  # Check individual associations
177
184
  users_in_org.each do |user|
178
- result = self.check_association(org,user,global_admins)
179
- if (result != true)
185
+ result = check_association(org, user, global_admins)
186
+ if result != true
180
187
  puts "Org #{orgname} Association check failed for #{user.name} #{result}"
181
188
  success = false
182
189
  end
183
190
  end
184
191
 
185
192
  puts "Org #{orgname} is #{success ? 'ok' : 'bad'} (#{users_in_org.count} users)"
186
- return success
193
+ success
187
194
  end
188
195
 
189
196
  # expect at least one current user to be in admins and billing admins
@@ -193,50 +200,40 @@ module ChefFixie
193
200
  puts "#{orgname} Missing group #{groupname}"
194
201
  return :no_such_group
195
202
  end
196
- actors = g.group['actors'].map {|x| x[1] }
203
+ actors = g.group["actors"].map { |x| x[1] }
197
204
  live = actors & users
198
205
 
199
206
  if live.count == 0
200
207
  puts "Org #{org.name} has no active users in #{groupname}"
201
208
  return false
202
209
  end
203
- return true
210
+ true
204
211
  end
205
212
 
206
-
207
- ## TODO: Port this
208
213
  def self.remove_association(org, user)
209
214
  # magic to make usage easier
210
- org = orgs[org] if org.is_a?(String)
211
- user = users[user] if user.is_a?(String)
215
+ org = make_org(org)
216
+ user = make_user(user)
212
217
 
213
218
  # remove USAG
214
- delete_usag_for_user(orgname,username)
219
+ usag = org.groups[user.id]
220
+ usag.delete if usag
215
221
 
216
- # remove read ACE
217
- u_aid = user.authz_id
218
- ga = OrgMapper::CouchSupport.doc_from_view("#{orgname}_global_admins", "#{Group}/by_groupname")
219
- ga_aid = user_to_authz(ga["_id"])
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
220
226
 
221
- read_ace = OrgMapper::RawAuth.get("actors/#{u_aid}/acl/read")
222
- read_ace["groups"] -= [ga_aid]
223
- OrgMapper::RawAuth.put("actors/#{u_aid}/acl/read", read_ace)
227
+ # remove read ACE
228
+ user.ace_delete(:read, org.global_admins)
224
229
 
225
230
  # remove association record
231
+ assoc = assocs.by_org_id_user_id(org.id, user.id)
232
+ assoc.delete if assoc
226
233
 
227
- org_assoc = OrgMapper::CouchSupport.associations_for_org(orgname)
228
- doc = org_assoc.select {|x| x[:uid] == user.id}.first
229
-
230
- OrgMapper::CouchSupport.delete_account_doc(doc[:id])
231
-
232
- # clean up excess invites
233
- invites = OrgMapper::CouchSupport.invites_for_org(orgname)
234
- invite_map = invites.inject({}) {|a,e| a[e[:name]] = e; a}
235
-
236
- if invite_map.has_key?(username)
237
- invite = invite_map[username]
238
- OrgMapper::CouchSupport.delete_account_doc(invite[:id])
239
- end
234
+ # remove any invites
235
+ invite = invites.by_org_id_user_id(org.id, user.id)
236
+ invite.delete if invite
240
237
  end
241
238
  end
242
239
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014-2015 Chef Software Inc.
2
+ # Copyright (c) 2014-2015 Chef Software Inc.
3
3
  # License :: Apache License, Version 2.0
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,9 +18,10 @@
18
18
  #
19
19
  # Much of this code was orginally derived from the orgmapper tool, which had many varied authors.
20
20
 
21
- require 'singleton'
22
- require 'ffi_yajl'
23
- require 'pathname'
21
+ require "singleton"
22
+ require "ffi_yajl"
23
+ require "pathname"
24
+ require "veil"
24
25
 
25
26
  module ChefFixie
26
27
  def self.configure
@@ -29,11 +30,11 @@ module ChefFixie
29
30
 
30
31
  def self.load_config(config_file = nil)
31
32
  if config_file
32
- puts "loading config: #{config_file}..."
33
+ puts "loading config: #{config_file}..." if ChefFixie::Console.started_from_command_line?
33
34
  Kernel.load(config_file)
34
35
  else
35
36
  path = "/etc/opscode"
36
- puts "loading config from #{path}"
37
+ puts "loading config from #{path}" if ChefFixie::Console.started_from_command_line?
37
38
  ChefFixie::Config.instance.load_from_pc(path)
38
39
  end
39
40
  end
@@ -65,7 +66,7 @@ module ChefFixie
65
66
  KEYS = [:authz_uri, :sql_database, :superuser_id, :pivotal_key]
66
67
  KEYS.each { |k| attr_accessor k }
67
68
 
68
- def merge_opts(opts={})
69
+ def merge_opts(opts = {})
69
70
  opts.each do |key, value|
70
71
  send("#{key}=".to_sym, value)
71
72
  end
@@ -83,7 +84,7 @@ module ChefFixie
83
84
  key_len > max ? key_len : max
84
85
  end
85
86
  KEYS.each do |key|
86
- value = send(key) || 'default'
87
+ value = send(key) || "default"
87
88
  txt << "# %#{max_key_len}s: %s" % [key.to_s, value]
88
89
  end
89
90
  txt.join("\n")
@@ -101,25 +102,28 @@ module ChefFixie
101
102
  def load_from_pc(dir = "/etc/opscode")
102
103
  configdir = Pathname.new(dir)
103
104
 
104
- config_files = %w(chef-server-running.json)
105
+ config_files = %w{chef-server-running.json}
105
106
  config = load_json_from_path([configdir], config_files)
106
107
 
107
- authz_config = config['private_chef']['oc_bifrost']
108
- authz_vip = authz_config['vip']
109
- authz_port = authz_config['port']
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"]
110
113
  @authz_uri = "http://#{authz_vip}:#{authz_port}"
111
-
112
- @superuser_id = authz_config['superuser_id']
113
-
114
- sql_config = config['private_chef']['postgresql']
115
-
116
- sql_user = sql_config['sql_user']
117
- sql_pw = sql_config['sql_password']
118
- sql_vip = sql_config['vip']
119
- sql_port = sql_config['port']
120
-
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
+
121
125
  @sql_database = "postgres://#{sql_user}:#{sql_pw}@#{sql_vip}/opscode_chef"
122
-
126
+
123
127
  @pivotal_key = configdir + "pivotal.pem"
124
128
  end
125
129
 
@@ -135,5 +139,36 @@ module ChefFixie
135
139
  end
136
140
  end
137
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
+
138
173
  end
139
174
  end