rs_user_policy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +20 -0
- data/README.rdoc +74 -0
- data/bin/rs_user_policy +188 -0
- metadata +89 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Ryan J. Geyer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
= rs_user_policy
|
2
|
+
|
3
|
+
A useful tool for managing many users across many child accounts in a RightScale Enterprise Edition
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
The binary contained in this gem accepts two files as inputs to determine it's behavior. The first, is a policy JSON file which specifies the permissions to be applied to users.
|
8
|
+
|
9
|
+
Policy JSON should be in the following format
|
10
|
+
|
11
|
+
{
|
12
|
+
"role_name": {
|
13
|
+
"default": [<permissions_here>],
|
14
|
+
"/api/accounts/12345": [<permissions_here>]
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
Here the "role_name" is what can be assigned to a user. The keys of the hash ("default" and "/api/accounts/12345") refer to the account(s) the role should have access to, and the value for those keys is an array of permissions that should be assigned for that role in that account.
|
19
|
+
|
20
|
+
The "default" account will apply to all accounts encountered.
|
21
|
+
|
22
|
+
The second input file is the user assignments JSON file which assigns users to roles using their email.
|
23
|
+
|
24
|
+
User Assignment JSON should be in the following format
|
25
|
+
|
26
|
+
{
|
27
|
+
"ryan.geyer@rightscale.com": "role_name"
|
28
|
+
}
|
29
|
+
|
30
|
+
There are two default roles which do not need to be defined in the policy file. "immutable" which indicates that no changes should be performed on the user, and "delete" which indicates that all permissions should be removed for the user in all accounts.
|
31
|
+
|
32
|
+
Many email:role pairs can be specified. in the user assignments JSON
|
33
|
+
|
34
|
+
So, given a policy file like;
|
35
|
+
|
36
|
+
{
|
37
|
+
"team1": {
|
38
|
+
"default": ['observer', 'actor'],
|
39
|
+
"/api/accounts/12345": ['observer', 'actor', 'server_login', 'admin']
|
40
|
+
},
|
41
|
+
"team2": {
|
42
|
+
"default": ['observer', 'lite_user'],
|
43
|
+
"/api/accounts/23456": ['observer', 'actor']
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
And a user assignments file like;
|
48
|
+
|
49
|
+
{
|
50
|
+
"user1@email.com": "team1",
|
51
|
+
"user2@email.com": "team2"
|
52
|
+
}
|
53
|
+
|
54
|
+
And operating on the accounts 12345 and 23456;
|
55
|
+
user1 will be assigned observer and actor rights on account 23456, and observer, actor, server_login, and admin rights on account 12345
|
56
|
+
user2 will be assigned observer and lite_user rights on account 12345, and observer and actor rights on account 23456
|
57
|
+
|
58
|
+
Got that? Cool!
|
59
|
+
|
60
|
+
== Output
|
61
|
+
|
62
|
+
When the script is run, it will produce two JSON files as output.
|
63
|
+
|
64
|
+
First is the audit_log-<timestamp>.json file. This will contain a history of all actions taken on all users. If --dry-run is specified, it will show the changes which *would* have been performed.
|
65
|
+
|
66
|
+
Second is the user_assignments-<timestamp>.json file. This will be a combination of the users read in from JSON in file specified by the --user-assignments option, plus any new users discovered in the accounts operated on. New users will be assigned the "immutable" role. This allows you to run rs_user_policy with the --dry-run option, or with no user assignments input to discover users, then assign roles to those users in the produced JSON, then use that file as the --user-assignments input for a subsequent run.
|
67
|
+
|
68
|
+
== TODO
|
69
|
+
|
70
|
+
* Allow a user to belong to more than one "role"
|
71
|
+
|
72
|
+
== Copyright
|
73
|
+
|
74
|
+
Copyright (c) 2012 Ryan J. Geyer. See LICENSE.txt for further details.
|
data/bin/rs_user_policy
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (c) 2012 Ryan J. Geyer
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'trollop'
|
25
|
+
require 'right_api_client'
|
26
|
+
require 'logger'
|
27
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'audit_log'))
|
28
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'utilities'))
|
29
|
+
|
30
|
+
opts = Trollop::options do
|
31
|
+
banner = "Manages users across many different child accounts of a RightScale Enterprise Master Account"
|
32
|
+
|
33
|
+
opt :rs_email, "You RightScale User Email Address", :type => :string, :required => true
|
34
|
+
opt :rs_pass, "Your RightScale User Password", :type => :string, :required => true
|
35
|
+
opt :rs_acct_num, "A RightScale Enterprise Master Account ID", :type => :string, :required => true
|
36
|
+
opt :policy, "The path to a JSON file containing the role to permissions policy to enforce", :type => :string, :required => true
|
37
|
+
opt :user_assignments, "The path to a JSON file containing email address => role pairs for user assignments", :type => :string
|
38
|
+
opt :dry_run, "A flag indicating that no changes should be made, only the user_assignments.json should be evaluated (or created) and the audit_log.json produced"
|
39
|
+
end
|
40
|
+
|
41
|
+
log = Logger.new(STDOUT)
|
42
|
+
timestamp = Time::now.to_i
|
43
|
+
deleted_users = []
|
44
|
+
accounts = []
|
45
|
+
user_href_resource_map = {}
|
46
|
+
user_email_resource_map = {}
|
47
|
+
permission_delete_order = [
|
48
|
+
'enterprise_admin',
|
49
|
+
'admin',
|
50
|
+
'security_manager',
|
51
|
+
'actor',
|
52
|
+
'billing',
|
53
|
+
'server_login',
|
54
|
+
'publisher',
|
55
|
+
'designer',
|
56
|
+
'library',
|
57
|
+
'lite_user',
|
58
|
+
'observer'
|
59
|
+
]
|
60
|
+
|
61
|
+
user_assignments_output = "user_assignments-#{timestamp}.json"
|
62
|
+
|
63
|
+
audit_log = AuditLog.new opts.merge(:timestamp => timestamp)
|
64
|
+
|
65
|
+
client = RightApi::Client.new(:email => opts[:rs_email], :password => opts[:rs_pass], :account_id => opts[:rs_acct_num])
|
66
|
+
master_account = client.accounts(:id => opts[:rs_acct_num]).show()
|
67
|
+
client.users().index.each do |user|
|
68
|
+
user_href_resource_map[user.href] = user
|
69
|
+
user_email_resource_map[user.email] = user
|
70
|
+
end
|
71
|
+
|
72
|
+
accounts << {:client => client, :href => master_account.href, :name => master_account.name}
|
73
|
+
|
74
|
+
log.info("The dry_run option was selected, no action will be taken, but the user_assignments output and audit_log files will be written reflecting the actions which would have been taken") if opts[:dry_run]
|
75
|
+
|
76
|
+
log.info("Operating on the Enterprise Master Account #{master_account.name}")
|
77
|
+
|
78
|
+
if File.exists? opts[:policy]
|
79
|
+
# TODO: Validate policy file format
|
80
|
+
policy = JSON.parse(File.read(opts[:policy]))
|
81
|
+
else
|
82
|
+
log.fatal("The policy file named #{opts[:policy]} was not found!")
|
83
|
+
exit 1
|
84
|
+
end
|
85
|
+
|
86
|
+
user_assignments = {}
|
87
|
+
if opts[:user_assignments]
|
88
|
+
if File.exists? opts[:user_assignments]
|
89
|
+
user_assignments = JSON.parse(File.read(opts[:user_assignments]))
|
90
|
+
else
|
91
|
+
log.warn("The user_assigments file named #{opts[:user_assignments]} was not found. All users will be treated as immutable and written to the user_assigments output file.")
|
92
|
+
end
|
93
|
+
else
|
94
|
+
log.warn("No user_assignments file was specified. All users will be treated as immutable and written to the user_assigments output file.")
|
95
|
+
end
|
96
|
+
|
97
|
+
begin
|
98
|
+
client.child_accounts().index.each do |child|
|
99
|
+
child_client = RightApi::Client.new(:email => opts[:rs_email], :password => opts[:rs_pass], :account_id => Utilities.id_from_href(child.href))
|
100
|
+
accounts << {:client => child_client, :href => child.href, :name => child.name}
|
101
|
+
|
102
|
+
child_client.users().index.each do |user|
|
103
|
+
user_href_resource_map[user.href] = user
|
104
|
+
user_email_resource_map[user.email] = user
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue RightApi::Exceptions::ApiException => e
|
108
|
+
if e.message =~ /Permission denied/
|
109
|
+
log.warn("#{master_account.name} is not an Enterprise Master, or you do not have the enterprise_manager permission. No child accounts will be operated on.")
|
110
|
+
else
|
111
|
+
raise e
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
accounts.each do |account|
|
116
|
+
child_client = account[:client]
|
117
|
+
users = {}
|
118
|
+
user_email_resource_map.each do |email,user|
|
119
|
+
users[email] = {}
|
120
|
+
end
|
121
|
+
|
122
|
+
log.info("#{account[:name]} - #{account[:href]}")
|
123
|
+
|
124
|
+
permissions = child_client.permissions().index
|
125
|
+
log.info("There are #{permissions.length} unique permissions")
|
126
|
+
permissions.each do |permission|
|
127
|
+
user_email = user_href_resource_map[permission.user.href].email || permission.user.href
|
128
|
+
users[user_email] = {} unless users.key?(user_email)
|
129
|
+
users[user_email][permission.role_title] = permission.href
|
130
|
+
end
|
131
|
+
log.info("There are #{users.length} unique Users")
|
132
|
+
|
133
|
+
users.each do |email,user|
|
134
|
+
unless user_assignments.key?(email)
|
135
|
+
user_assignments[email] = 'immutable'
|
136
|
+
end
|
137
|
+
# Allow some different options about possibly prompting the user etc
|
138
|
+
# TODO: Put this in a begin/rescue/end that allows stuff to continue and create an audit_log in the event of an error
|
139
|
+
case user_assignments[email]
|
140
|
+
when 'immutable'
|
141
|
+
# By design, do nothing
|
142
|
+
when 'delete'
|
143
|
+
# Note: This is an explicit delete across all master and child accounts
|
144
|
+
# a user can also be effectively deleted if they have an empty list of
|
145
|
+
# permissions for a particular account
|
146
|
+
unless opts[:dry_run]
|
147
|
+
log.debug("Gonna delete #{JSON.pretty_generate(user)}")
|
148
|
+
|
149
|
+
Utilities.yield_on_keys_in_order(permission_delete_order, user) do |role_title,perm_href|
|
150
|
+
log.debug("I KEELZ U PERMISSION! #{email} - #{role_title}")
|
151
|
+
child_client.permissions(:id => Utilities.id_from_href(perm_href)).destroy()
|
152
|
+
deleted_users << email
|
153
|
+
end
|
154
|
+
end
|
155
|
+
audit_log.add_entry(email, account[:name], 'deleted', 'deleted')
|
156
|
+
else
|
157
|
+
user_policy = []
|
158
|
+
user_policy = policy[user_assignments[email]][account[:href]] if policy[user_assignments[email]].key?(account[:href])
|
159
|
+
user_policy = policy[user_assignments[email]]['default'] if policy[user_assignments[email]].key?('default')
|
160
|
+
removed = user.keys - user_policy
|
161
|
+
added = user_policy - user.keys
|
162
|
+
changes = "-#{removed} +#{added}"
|
163
|
+
unless opts[:dry_run]
|
164
|
+
# Convert the role_title array into a hash with the hrefs
|
165
|
+
remove_hash = Hash[removed.map {|role| [role, user[role]]}]
|
166
|
+
|
167
|
+
Utilities.yield_on_keys_in_order(permission_delete_order, remove_hash) do |role_title, href|
|
168
|
+
child_client.permissions(:id => Utilities.id_from_href(href)).destroy()
|
169
|
+
end
|
170
|
+
|
171
|
+
if added.length > 0
|
172
|
+
add_hash = Hash[added.map {|x| [x, nil]}]
|
173
|
+
Utilities.yield_on_keys_in_order(['observer'], add_hash) do |role_title,foo|
|
174
|
+
child_client.permissions.create({'permission[user_href]' => user_email_resource_map[email].href, 'permission[role_title]' => role_title})
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
audit_log.add_entry(email, account[:name], 'update_permissions', changes) unless removed.length + added.length == 0
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
deleted_users.each do |email|
|
184
|
+
user_assignments.delete(email)
|
185
|
+
end
|
186
|
+
|
187
|
+
File.open(user_assignments_output, 'w') {|f| f.write(JSON.pretty_generate(user_assignments))}
|
188
|
+
audit_log.write_file
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rs_user_policy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan J. Geyer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: right_api_client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.5.9
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.5.9
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: trollop
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.16'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.16'
|
46
|
+
description: Manages users across many different child accounts of a RightScale Enterprise
|
47
|
+
Master Account
|
48
|
+
email: ryan.geyer@rightscale.com
|
49
|
+
executables:
|
50
|
+
- rs_user_policy
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- bin/rs_user_policy
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.rdoc
|
57
|
+
homepage: http://www.rightscale.com
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
hash: -1869146196787316308
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
hash: -1869146196787316308
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.23
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Manages users across many different child accounts of a RightScale Enterprise
|
88
|
+
Master Account
|
89
|
+
test_files: []
|