chef_fixie 0.1.0 → 0.5.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.
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