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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +69 -0
  4. data/bin/bundler +16 -0
  5. data/bin/chef-apply +16 -0
  6. data/bin/chef-client +16 -0
  7. data/bin/chef-shell +16 -0
  8. data/bin/chef-solo +16 -0
  9. data/bin/chef-zero +16 -0
  10. data/bin/chef_fixie +5 -0
  11. data/bin/coderay +16 -0
  12. data/bin/edit_json.rb +16 -0
  13. data/bin/erubis +16 -0
  14. data/bin/ffi-yajl-bench +16 -0
  15. data/bin/fixie~ +231 -0
  16. data/bin/htmldiff +16 -0
  17. data/bin/knife +16 -0
  18. data/bin/ldiff +16 -0
  19. data/bin/net-dhcp +16 -0
  20. data/bin/ohai +16 -0
  21. data/bin/prettify_json.rb +16 -0
  22. data/bin/pry +16 -0
  23. data/bin/rackup +16 -0
  24. data/bin/rake +16 -0
  25. data/bin/rdoc +16 -0
  26. data/bin/restclient +16 -0
  27. data/bin/ri +16 -0
  28. data/bin/rspec +16 -0
  29. data/bin/s3sh +16 -0
  30. data/bin/sequel +16 -0
  31. data/bin/serverspec-init +16 -0
  32. data/doc/AccessingSQL.md +36 -0
  33. data/doc/AccessingSQL.md~ +32 -0
  34. data/doc/BulkFixup.md~ +28 -0
  35. data/doc/CommonTasks.md +20 -0
  36. data/doc/CommonTasks.md~ +0 -0
  37. data/doc/GETTING_STARTED.md +228 -0
  38. data/doc/GETTING_STARTED.md~ +6 -0
  39. data/fixie.conf.example +8 -0
  40. data/lib/chef_fixie.rb +27 -0
  41. data/lib/chef_fixie/authz_mapper.rb +143 -0
  42. data/lib/chef_fixie/authz_objects.rb +285 -0
  43. data/lib/chef_fixie/check_org_associations.rb +242 -0
  44. data/lib/chef_fixie/config.rb +139 -0
  45. data/lib/chef_fixie/console.rb +91 -0
  46. data/lib/chef_fixie/context.rb +72 -0
  47. data/lib/chef_fixie/sql.rb +74 -0
  48. data/lib/chef_fixie/sql_objects.rb +497 -0
  49. data/lib/chef_fixie/utility_helpers.rb +59 -0
  50. data/lib/chef_fixie/version.rb +3 -0
  51. data/spec/chef_fixie/acl_spec.rb +83 -0
  52. data/spec/chef_fixie/assoc_invite_spec.rb +47 -0
  53. data/spec/chef_fixie/assoc_invite_spec.rb~ +26 -0
  54. data/spec/chef_fixie/check_org_associations_spec.rb +140 -0
  55. data/spec/chef_fixie/check_org_associations_spec.rb~ +34 -0
  56. data/spec/chef_fixie/groups_spec.rb +34 -0
  57. data/spec/chef_fixie/org_spec.rb +26 -0
  58. data/spec/chef_fixie/org_spec.rb~ +53 -0
  59. data/spec/chef_fixie/orgs_spec.rb +53 -0
  60. data/spec/spec_helper.rb +41 -0
  61. metadata +252 -0
@@ -0,0 +1,242 @@
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 'chef_fixie/config'
22
+ require 'chef_fixie/authz_objects'
23
+ require 'chef_fixie/authz_mapper'
24
+
25
+ require 'chef_fixie/utility_helpers'
26
+
27
+ module ChefFixie
28
+ module CheckOrgAssociations
29
+ def self.orgs
30
+ @orgs ||= ChefFixie::Sql::Orgs.new
31
+ end
32
+ def self.users
33
+ @users ||= ChefFixie::Sql::Users.new
34
+ end
35
+ def self.assocs
36
+ @assocs ||= ChefFixie::Sql::Associations.new
37
+ end
38
+ def self.invites
39
+ invites ||= ChefFixie::Sql::Invites.new
40
+ end
41
+
42
+ def self.make_user(user)
43
+ if user.is_a?(String)
44
+ return users[user]
45
+ elsif user.is_a?(ChefFixie::Sql::User)
46
+ return user
47
+ else
48
+ raise "Expected a user, got a #{user.class}"
49
+ end
50
+ end
51
+ def self.make_org(org)
52
+ if org.is_a?(String)
53
+ return orgs[org]
54
+ elsif org.is_a?(ChefFixie::Sql::Org)
55
+ return org
56
+ else
57
+ raise "Expected an org, got a #{org.class}"
58
+ end
59
+ end
60
+
61
+ def self.check_association(org, user, global_admins = nil)
62
+ # magic to make usage easier
63
+ org = make_org(org)
64
+ user = make_user(user)
65
+ global_admins ||= org.global_admins
66
+
67
+ # check that the user is associated
68
+ if !assocs.by_org_id_user_id(org.id, user.id)
69
+ return :not_associated
70
+ end
71
+
72
+ usag = org.groups[user.id]
73
+ # check that user has USAG
74
+ if usag.nil?
75
+ return :missing_usag
76
+ end
77
+
78
+ if !usag.member?(user)
79
+ return :user_not_in_usag
80
+ end
81
+
82
+ if !org.groups['users'].member?(usag)
83
+ return :usag_not_in_users
84
+ end
85
+
86
+ if !user.ace_member?(:read, global_admins)
87
+ return :global_admins_lacks_read
88
+ end
89
+
90
+ if invites.by_org_id_user_id(org.id, user.id)
91
+ return :zombie_invite
92
+ end
93
+ return true
94
+ end
95
+
96
+ def self.fix_association(org, user, global_admins = nil)
97
+ # magic to make usage easier
98
+ org = orgs[org] if org.is_a?(String)
99
+ user = users[user] if user.is_a?(String)
100
+ global_admins ||= org.global_admins
101
+
102
+ failure = check_association(org,user,global_admins)
103
+
104
+ case failure
105
+ when true
106
+ puts "#{org.name} #{user.name} doesn't need repair"
107
+ when :user_not_in_usag
108
+ usag = org.groups[user.id]
109
+ usag.group_add(user)
110
+ when :usag_not_in_users
111
+ usag = org.groups[user.id]
112
+ org.groups['users'].group_add(usag)
113
+ when :global_admins_lacks_read
114
+ user.ace_add(:read, global_admins)
115
+ else
116
+ puts "#{org.name} #{user.name} can't fix problem #{failure} yet"
117
+ return false
118
+ end
119
+ return true
120
+ end
121
+
122
+ def self.check_associations(org)
123
+ success = true
124
+ org = make_org(org)
125
+ orgname = org.name
126
+
127
+ # check that global_admins exists:
128
+ global_admins = org.global_admins
129
+ if !global_admins || !global_admins.is_a?(ChefFixie::Sql::Group)
130
+ puts "#{orgname} Missing global admins group"
131
+ success = false
132
+ end
133
+
134
+ users_assoc = assocs.by_org_id(org.id).all(:all)
135
+ users_invite = invites.by_org_id(org.id).all(:all)
136
+
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
+
141
+
142
+ # check that users aren't both invited and associated
143
+ invited_ids = users_invite.map {|a| a.user_id }
144
+ overlap_ids = user_ids & invited_ids
145
+
146
+ 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(', ') }"
149
+ success = false
150
+ end
151
+
152
+ # Check that we don't have zombie USAGs left around (not 100% reliable)
153
+ # because someone could create a group that looks like a USAG
154
+ possible_usags = org.groups.list(:all) - user_ids
155
+ usags = possible_usags.select {|n| n =~ /^\h+{20}$/ }
156
+ if !usags.empty?
157
+ puts "#{orgname} Suspicious USAGS without associated user #{usags.join(', ') }"
158
+ end
159
+
160
+ # Check group membership for sanity
161
+ success &= check_group(org, 'billing-admins', usernames)
162
+ success &= check_group(org, 'admins', usernames)
163
+
164
+ # TODO check for non-usags in users!
165
+ users_members = org.groups['users'].group
166
+ users_actors = users_members['actors'] - [[:global, "pivotal"]]
167
+ if !users_actors.empty?
168
+ puts "#{orgname} has actors in it's users group #{users_actors}"
169
+ end
170
+ non_usags = users_members['groups'].map {|g| g[1] } - user_ids
171
+ if !non_usags.empty?
172
+ puts "#{orgname} warning: has non usags in it's users group #{non_usags.join(', ')}"
173
+ end
174
+
175
+
176
+ # Check individual associations
177
+ users_in_org.each do |user|
178
+ result = self.check_association(org,user,global_admins)
179
+ if (result != true)
180
+ puts "Org #{orgname} Association check failed for #{user.name} #{result}"
181
+ success = false
182
+ end
183
+ end
184
+
185
+ puts "Org #{orgname} is #{success ? 'ok' : 'bad'} (#{users_in_org.count} users)"
186
+ return success
187
+ end
188
+
189
+ # expect at least one current user to be in admins and billing admins
190
+ def self.check_group(org, groupname, users)
191
+ g = org.groups[groupname]
192
+ if g.nil?
193
+ puts "#{orgname} Missing group #{groupname}"
194
+ return :no_such_group
195
+ end
196
+ actors = g.group['actors'].map {|x| x[1] }
197
+ live = actors & users
198
+
199
+ if live.count == 0
200
+ puts "Org #{org.name} has no active users in #{groupname}"
201
+ return false
202
+ end
203
+ return true
204
+ end
205
+
206
+
207
+ ## TODO: Port this
208
+ def self.remove_association(org, user)
209
+ # magic to make usage easier
210
+ org = orgs[org] if org.is_a?(String)
211
+ user = users[user] if user.is_a?(String)
212
+
213
+ # remove USAG
214
+ delete_usag_for_user(orgname,username)
215
+
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"])
220
+
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)
224
+
225
+ # remove association record
226
+
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
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,139 @@
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
+
25
+ module ChefFixie
26
+ def self.configure
27
+ yield Config.instance
28
+ end
29
+
30
+ def self.load_config(config_file = nil)
31
+ if config_file
32
+ puts "loading config: #{config_file}..."
33
+ Kernel.load(config_file)
34
+ else
35
+ path = "/etc/opscode"
36
+ puts "loading config from #{path}"
37
+ ChefFixie::Config.instance.load_from_pc(path)
38
+ end
39
+ end
40
+
41
+ def self.setup
42
+ # TODO: do we have to polute global object with this to make it available to the irb instance?
43
+ Object.const_set(:ORGS, ChefFixie::Sql::Orgs.new)
44
+ Object.const_set(:USERS, ChefFixie::Sql::Users.new)
45
+ Object.const_set(:ASSOCS, ChefFixie::Sql::Associations.new)
46
+ Object.const_set(:INVITES, ChefFixie::Sql::Invites.new)
47
+
48
+ # scope this by the global org id?
49
+ Object.const_set(:GLOBAL_GROUPS, ChefFixie::Sql::Groups.new.by_org_id(ChefFixie::Sql::Orgs::GlobalOrg))
50
+ Object.const_set(:GLOBAL_CONTAINERS, ChefFixie::Sql::Containers.new.by_org_id(ChefFixie::Sql::Orgs::GlobalOrg))
51
+ end
52
+
53
+ ##
54
+ # = ChefFixie::Config
55
+ # configuration for the fixie command.
56
+ #
57
+ # ==Example Config File:
58
+ #
59
+ # Fixie.configure do |mapper|
60
+ # mapper.authz_uri = 'http://authz.example.com:5959'
61
+ # end
62
+ #
63
+ class Config
64
+ include Singleton
65
+ KEYS = [:authz_uri, :sql_database, :superuser_id, :pivotal_key]
66
+ KEYS.each { |k| attr_accessor k }
67
+
68
+ def merge_opts(opts={})
69
+ opts.each do |key, value|
70
+ send("#{key}=".to_sym, value)
71
+ end
72
+ end
73
+
74
+ # this is waaay tightly coupled to ::Backend's initialize method
75
+ def to_ary
76
+ [couchdb_uri, database, auth_uri, authz_couch, sql_database, superuser_id].compact
77
+ end
78
+
79
+ def to_text
80
+ txt = ["### ChefFixie::Config"]
81
+ max_key_len = KEYS.inject(0) do |max, k|
82
+ key_len = k.to_s.length
83
+ key_len > max ? key_len : max
84
+ end
85
+ KEYS.each do |key|
86
+ value = send(key) || 'default'
87
+ txt << "# %#{max_key_len}s: %s" % [key.to_s, value]
88
+ end
89
+ txt.join("\n")
90
+ end
91
+
92
+ def example_config
93
+ txt = ["Fixie.configure do |mapper|"]
94
+ KEYS.each do |key|
95
+ txt << " mapper.%s = %s" % [key.to_s, '"something"']
96
+ end
97
+ txt << "end"
98
+ txt.join("\n")
99
+ end
100
+
101
+ def load_from_pc(dir = "/etc/opscode")
102
+ configdir = Pathname.new(dir)
103
+
104
+ config_files = %w(chef-server-running.json)
105
+ config = load_json_from_path([configdir], config_files)
106
+
107
+ authz_config = config['private_chef']['oc_bifrost']
108
+ authz_vip = authz_config['vip']
109
+ authz_port = authz_config['port']
110
+ @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
+
121
+ @sql_database = "postgres://#{sql_user}:#{sql_pw}@#{sql_vip}/opscode_chef"
122
+
123
+ @pivotal_key = configdir + "pivotal.pem"
124
+ end
125
+
126
+ def load_json_from_path(pathlist, filelist)
127
+ parser = FFI_Yajl::Parser.new
128
+ pathlist.each do |path|
129
+ filelist.each do |file|
130
+ configfile = path + file
131
+ if configfile.file?
132
+ data = File.read(configfile)
133
+ return parser.parse(data)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,91 @@
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
+ # Much of this code was orginally derived from the orgmapper tool, which had many varied authors.
20
+
21
+ require 'optparse'
22
+ require 'pp'
23
+ require 'pry'
24
+
25
+ require 'chef_fixie'
26
+ require 'chef_fixie/context'
27
+
28
+ module ChefFixie
29
+ module Console
30
+ extend self
31
+
32
+ def start
33
+ configure
34
+ Fixie.setup
35
+ configure_pry
36
+ Pry.start
37
+ end
38
+
39
+ def configure
40
+ config_file = nil
41
+ if ARGV.first && ARGV[0].chars.first != "-" && config_file = ARGV.shift
42
+ config_file = File.expand_path(config_file)
43
+ end
44
+ Fixie.load_config(config_file)
45
+
46
+ options = {}
47
+ OptionParser.new do |opt|
48
+ opt.banner = "Usage: fixie [config] [options]"
49
+ opt.on('--authz_uri AUTH_URI', "The URI of the opscode authz service") { |v| options[:authz_uri] =v }
50
+ opt.on("--sql_database DATABASE", 'The URI of the opscode_chef database') { |v| options[:sql_database] = v }
51
+ opt.on_tail('-h', '--help', 'Show this message') do
52
+ puts opt
53
+ puts "\nExample configuration file:\n\n"
54
+ puts ChefFixie::Config.instance.example_config
55
+ puts "\n"
56
+ exit(1)
57
+ end
58
+ opt.parse!(ARGV)
59
+ end
60
+ pp :cli_opts => options if ENV["DEBUG"]
61
+
62
+ ChefFixie::Config.instance.merge_opts(options)
63
+ puts ChefFixie::Config.instance.to_text
64
+ end
65
+
66
+ def configure_pry
67
+ Pry.config.history.file = "~/.fixie_history"
68
+ Pry.config.prompt_name = "fixie"
69
+ Pry::Commands.block_command("fixie-help", "Show fixie's help") do
70
+ output.puts(<<-HALP)
71
+ ** ORGS **
72
+ * access with ORGS or ORGS
73
+ * access a specific org: ORGS['orgname']
74
+
75
+ ** USERS **
76
+ * users.find('clownco-org-admin')
77
+ * users.grep :clownco
78
+ * users.usernames
79
+
80
+ ** RAW SQL ACCESS**
81
+ * sql[:users].select(:column, :column).where(:column => "condition").all
82
+
83
+ ** irb Help **
84
+ irb_help
85
+
86
+ HALP
87
+ end
88
+ end
89
+
90
+ end
91
+ end