rs_user_policy 0.0.5 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -8
- data/bin/rs_user_policy +46 -15
- data/lib/rs_user_policy/audit_log.rb +2 -2
- data/lib/rs_user_policy/user.rb +17 -6
- data/lib/rs_user_policy/user_collection.rb +6 -6
- data/lib/rs_user_policy/utilities.rb +13 -7
- metadata +4 -4
data/README.rdoc
CHANGED
@@ -10,13 +10,15 @@ While the tests are not exhaustive, the current build status is..
|
|
10
10
|
You must pass in your RightScale authentication information, and a policy file. You can also specify one or many RightScale accounts using the --rs-acct-num or -a parameters. If an account that you specify is an Enterprise Master account, all Enterprise Children accounts will automatically be included.
|
11
11
|
|
12
12
|
Options:
|
13
|
-
|
13
|
+
--rs-email, -r <s>: You RightScale User Email Address
|
14
14
|
--rs-pass, -s <s>: Your RightScale User Password
|
15
15
|
--rs-acct-num, -a <s>: A RightScale Enterprise Master Account ID
|
16
16
|
--policy, -p <s>: The path to a JSON file containing the role to permissions policy to enforce
|
17
17
|
--user-assignments, -u <s>: The path to a JSON file containing email address => role pairs for user assignments
|
18
18
|
--dry-run, -d: A flag indicating that no changes should be made, only the user_assignments.json should be evaluated (or created) and
|
19
19
|
the audit_log.json produced
|
20
|
+
--authority, -t: A flag indicating that all users in the user_assignments file "MUST" exist, and will always be created. Effectively
|
21
|
+
asserting that the user_assignments is your canonical authority for users.
|
20
22
|
--help, -h: Show this message
|
21
23
|
|
22
24
|
Example (One account)
|
@@ -57,8 +59,6 @@ User Assignment JSON should be in the following format
|
|
57
59
|
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.
|
58
60
|
Both "immutable" and "delete", if present will take precedence over any other roles assigned to the user.
|
59
61
|
|
60
|
-
Many email:role pairs can be specified. in the user assignments JSON
|
61
|
-
|
62
62
|
So, given a policy file like;
|
63
63
|
|
64
64
|
{
|
@@ -103,14 +103,24 @@ This tool can also be used to create net-new users who have either never used Ri
|
|
103
103
|
"company": "RightScale",
|
104
104
|
"first_name": "Net",
|
105
105
|
"last_name": "New",
|
106
|
-
"phone": "9999999999"
|
106
|
+
"phone": "9999999999",
|
107
|
+
"create": "yes"
|
107
108
|
}
|
108
109
|
}
|
109
110
|
|
110
111
|
The order of the additional parameters does not matter. The properties list can also include "identity_provider_href" and "principal_uid" or "password" to specify the users authentication details. If no authentication details are supplied a random secure password will be generated, and written to the output user_assignments.json file.
|
111
112
|
|
113
|
+
*NOTE:* See the Authority section below for details on the "create" property
|
114
|
+
|
112
115
|
If a user with the specified email already exists, but that user does not have any permissions in the account(s) targetted by the tool, these additional properties are still required, but will be ignored.
|
113
116
|
|
117
|
+
==== Authority
|
118
|
+
|
119
|
+
By default, rs_user_policy assumes that RightScale is the authority for the existence of users. Meaning, if a user exists in the user_assigments, but does not exist in RightScale, the user will not be created by rs_user_policy. In order to override this there are two options.
|
120
|
+
|
121
|
+
1. Specifying the --authority commandline option implies that ALL users who are in the user_assignments should be created with the provided parameters
|
122
|
+
2. For individual users in the user_assignments, you can add a property named "create" with any value. The user will be created, and the "create" property will be removed.
|
123
|
+
|
114
124
|
== Output
|
115
125
|
|
116
126
|
When the script is run, it will produce two JSON files as output.
|
@@ -123,10 +133,9 @@ Second is the user_assignments-<timestamp>.json file. This will be a combinatio
|
|
123
133
|
|
124
134
|
* In absence of a policy.json, create a new policy.json with base roles for each account discovered (I.E. Admin, Observer, Designer, etc)
|
125
135
|
* Perhaps allow a role to inherit from another, or be a concatenation of several?
|
126
|
-
*
|
127
|
-
*
|
128
|
-
* 2012/12/18 - This does not appear to be an issue any longer. Or at least I am not encountering it anymore. Perhaps this was a transient issue?
|
136
|
+
* Provide a mechanism for "temporary" users with an expiration date
|
137
|
+
* Perhaps allow the user to enter a different role after the expiration date, rather than being removed completely?
|
129
138
|
|
130
139
|
== Copyright
|
131
140
|
|
132
|
-
Copyright (c) 2012 Ryan J. Geyer. See LICENSE.txt for further details.
|
141
|
+
Copyright (c) 2012-2013 Ryan J. Geyer. See LICENSE.txt for further details.
|
data/bin/rs_user_policy
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
# Copyright (c) 2012 Ryan J. Geyer
|
3
|
+
# Copyright (c) 2012-2013 Ryan J. Geyer
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining
|
6
6
|
# a copy of this software and associated documentation files (the
|
@@ -27,6 +27,15 @@ require 'logger'
|
|
27
27
|
require 'digest/md5'
|
28
28
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rs_user_policy'))
|
29
29
|
|
30
|
+
class UserResourceDetailMock
|
31
|
+
attr_reader :email, :href
|
32
|
+
|
33
|
+
def initialize(email, href)
|
34
|
+
@email = email
|
35
|
+
@href = href
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
30
39
|
opts = Trollop::options do
|
31
40
|
banner = "Manages users across many different child accounts of a RightScale Enterprise Master Account"
|
32
41
|
|
@@ -36,6 +45,7 @@ opts = Trollop::options do
|
|
36
45
|
opt :policy, "The path to a JSON file containing the role to permissions policy to enforce", :type => :string, :required => true
|
37
46
|
opt :user_assignments, "The path to a JSON file containing email address => role pairs for user assignments", :type => :string
|
38
47
|
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"
|
48
|
+
opt :authority, "A flag indicating that all users in the user_assignments file \"MUST\" exist, and will always be created. Effectively asserting that the user_assignments is your canonical authority for users."
|
39
49
|
end
|
40
50
|
|
41
51
|
log = Logger.new(STDOUT)
|
@@ -72,33 +82,54 @@ multi_client.each do |account_id, account|
|
|
72
82
|
child_client = account[:client]
|
73
83
|
child_account = child_client.accounts(:id => account_id).show()
|
74
84
|
account_href = child_account.href
|
75
|
-
|
76
|
-
user_collection.add_users(users_hash)
|
85
|
+
user_collection.add_users(child_client.users.index)
|
77
86
|
user_collection.add_permissions(account_href, child_client.permissions.index)
|
78
87
|
end
|
79
88
|
|
80
|
-
# Populate the user_assignments with
|
89
|
+
# Populate the user_assignments with extra bits..
|
81
90
|
user_collection.users.each do |user|
|
82
|
-
|
91
|
+
user_hash = user.to_hash
|
92
|
+
user_hash.delete(:permissions)
|
93
|
+
user_assignments[user.email].merge!(user_hash)
|
83
94
|
end
|
84
95
|
|
85
|
-
|
96
|
+
user_assignments_without_delete = user_assignments.list.select do |assn|
|
97
|
+
roles = user_assignments.get_roles(assn)
|
98
|
+
!roles.include?("delete")
|
99
|
+
end
|
100
|
+
|
101
|
+
net_new_users = user_assignments_without_delete - user_collection.users.map{|u| u.email }
|
86
102
|
|
87
103
|
net_new_users.each do |net_new_user_email|
|
88
104
|
client = multi_client[opts[:rs_acct_num].first()][:client]
|
89
105
|
user_create_params = user_assignments[net_new_user_email]
|
90
106
|
user_create_params = {:email => net_new_user_email}.merge(user_create_params)
|
91
|
-
unless user_create_params.key?("password") || (user_create_params.key?("identity_provider_href") && user_create_params.key?("principal_uid"))
|
92
|
-
pass = RsUserPolicy::Utilities.generate_compliant_password
|
93
|
-
user_create_params["password"] = pass
|
94
|
-
user_assignments[net_new_user_email]["password"] = pass
|
95
|
-
end
|
96
107
|
net_new_user_href = Digest::MD5.hexdigest((Time.now.to_i + rand(256)).to_s)
|
108
|
+
|
109
|
+
unless opts[:authority] || user_create_params.key?("create")
|
110
|
+
log.info("User (#{net_new_user_email}) was not found in any of the accounts or child accounts being operated on. rs_user_policy was not executed with --authority, nor did (#{net_new_user_email}) have a \"create\" property, so no action is being taken.")
|
111
|
+
next
|
112
|
+
end
|
113
|
+
|
114
|
+
net_new_user = UserResourceDetailMock.new(net_new_user_email, net_new_user_href)
|
97
115
|
unless opts[:dry_run]
|
98
|
-
|
99
|
-
|
116
|
+
unless user_create_params.key?("password") || (user_create_params.key?("identity_provider_href") && user_create_params.key?("principal_uid"))
|
117
|
+
pass = RsUserPolicy::Utilities.generate_compliant_password
|
118
|
+
user_create_params["password"] = pass
|
119
|
+
user_assignments[net_new_user_email]["password"] = pass
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
net_new_user = client.users.create(:user => user_create_params)
|
124
|
+
net_new_user_href = net_new_user.href
|
125
|
+
user_assignments[net_new_user_email].delete("create")
|
126
|
+
rescue RightApi::Exceptions::ApiException => e
|
127
|
+
log.error("Failed to create a user with the following properties.\n Properties: #{JSON.pretty_generate(user_create_params)}\n Error: #{e}")
|
128
|
+
next
|
129
|
+
end
|
100
130
|
end
|
101
|
-
|
131
|
+
audit_log.add_entry(net_new_user_email, net_new_user_href, "created", "created")
|
132
|
+
user_collection.add_users([net_new_user])
|
102
133
|
user_assignments[net_new_user_email]["href"] = net_new_user_href
|
103
134
|
end
|
104
135
|
|
@@ -145,4 +176,4 @@ user_collection.users.each do |user|
|
|
145
176
|
end unless opts[:dry_run]
|
146
177
|
|
147
178
|
user_assignments.serialize(:filename => user_assignments_output)
|
148
|
-
audit_log.write_file
|
179
|
+
audit_log.write_file
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2012 Ryan J. Geyer
|
1
|
+
# Copyright (c) 2012-2013 Ryan J. Geyer
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -39,7 +39,7 @@ module RsUserPolicy
|
|
39
39
|
#
|
40
40
|
# @param [String] email The email address of the user impacted by the change
|
41
41
|
# @param [String] account The account name impacted by the change
|
42
|
-
# @param [String] action The action performed. Expected options are ['update_permissions', 'deleted']
|
42
|
+
# @param [String] action The action performed. Expected options are ['update_permissions', 'created', 'deleted']
|
43
43
|
# @param [String] changes A free form description of the changes
|
44
44
|
def add_entry(email, account, action, changes)
|
45
45
|
@audit_log[email] = [] unless audit_log[email]
|
data/lib/rs_user_policy/user.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2012 Ryan J. Geyer
|
1
|
+
# Copyright (c) 2012-2013 Ryan J. Geyer
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -25,14 +25,25 @@ module RsUserPolicy
|
|
25
25
|
|
26
26
|
# Initializes read only attributes for an RsUserPolicy::User
|
27
27
|
#
|
28
|
-
# @param [
|
29
|
-
|
30
|
-
|
31
|
-
@
|
32
|
-
@
|
28
|
+
# @param [RightApi::ResourceDetail] user The user detail returned by RightApi::Client
|
29
|
+
def initialize(user)
|
30
|
+
@email = user.email
|
31
|
+
@href = user.href
|
32
|
+
@user = user
|
33
33
|
@permissions = {}
|
34
34
|
end
|
35
35
|
|
36
|
+
# Converts this object to a hash which can be serialized
|
37
|
+
def to_hash()
|
38
|
+
rethash = {
|
39
|
+
:permissions => @permissions
|
40
|
+
}
|
41
|
+
(@user.attributes - [:links]).each do |attr_sym|
|
42
|
+
rethash[attr_sym.to_sym] = @user.send(attr_sym.to_s)
|
43
|
+
end
|
44
|
+
rethash
|
45
|
+
end
|
46
|
+
|
36
47
|
# Adds a single permission for a single RightScale account
|
37
48
|
#
|
38
49
|
# @param [String] account_href The RightScale API href of the account
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2012 Ryan J. Geyer
|
1
|
+
# Copyright (c) 2012-2013 Ryan J. Geyer
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -37,11 +37,11 @@ module RsUserPolicy
|
|
37
37
|
# include the specified users. The users RightScale API href is used
|
38
38
|
# as the unique identifier for deduplication
|
39
39
|
#
|
40
|
-
# @param [
|
40
|
+
# @param [Array<RightApi::ResourceDetail>] users An array of RightAPI::ResourceDetail for the resource type "user"
|
41
41
|
def add_users(users)
|
42
|
-
users.each do |
|
43
|
-
unless @users_by_href.has_key?(href)
|
44
|
-
@users_by_href[href] = RsUserPolicy::User.new(
|
42
|
+
users.each do |user|
|
43
|
+
unless @users_by_href.has_key?(user.href)
|
44
|
+
@users_by_href[user.href] = RsUserPolicy::User.new(user)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -55,7 +55,7 @@ module RsUserPolicy
|
|
55
55
|
user_href = permission.user.href
|
56
56
|
unless @users_by_href.has_key?(user_href)
|
57
57
|
user = permission.user.show()
|
58
|
-
@users_by_href[user.href] = RsUserPolicy::User.new(user
|
58
|
+
@users_by_href[user.href] = RsUserPolicy::User.new(user)
|
59
59
|
end
|
60
60
|
@users_by_href[user_href].add_permission(account_href, permission)
|
61
61
|
end
|
@@ -57,13 +57,19 @@ module RsUserPolicy
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def self.generate_compliant_password(size=12)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
(
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
compliant = nil
|
61
|
+
password = ''
|
62
|
+
until compliant
|
63
|
+
chars = (
|
64
|
+
('a'..'z').to_a +
|
65
|
+
('A'..'Z').to_a +
|
66
|
+
('0'..'9').to_a +
|
67
|
+
["!","@","#","$","%","^","&","*","(",")","-","_","=","+"]
|
68
|
+
) - %w(i o 0 1 l 0)
|
69
|
+
password = (1..size).collect{|a| chars[rand(chars.size)] }.join
|
70
|
+
compliant = password =~ /^(?=.*[^a-zA-Z])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@\#\$%\^&\*\(\)\-_=\+]).+$/
|
71
|
+
end
|
72
|
+
password
|
67
73
|
end
|
68
74
|
|
69
75
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rs_user_policy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: right_api_client
|
@@ -80,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
80
|
version: '0'
|
81
81
|
segments:
|
82
82
|
- 0
|
83
|
-
hash:
|
83
|
+
hash: -2197135940528599574
|
84
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
85
|
none: false
|
86
86
|
requirements:
|
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
89
|
version: '0'
|
90
90
|
segments:
|
91
91
|
- 0
|
92
|
-
hash:
|
92
|
+
hash: -2197135940528599574
|
93
93
|
requirements: []
|
94
94
|
rubyforge_project:
|
95
95
|
rubygems_version: 1.8.24
|