stratus 1.0.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.
- data/History.txt +2 -0
- data/LICENSE +20 -0
- data/README.markdown +102 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/iamsh +27 -0
- data/bin/iamsh-setup +9 -0
- data/lib/stratus.rb +4 -0
- data/lib/stratus/aws.rb +4 -0
- data/lib/stratus/aws/iam.rb +599 -0
- data/lib/stratus/aws/iam/client.rb +18 -0
- data/lib/stratus/aws/iam/group.rb +12 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/stratus/aws/iam/client_spec.rb +43 -0
- data/spec/stratus/aws/iam/group_spec.rb +21 -0
- data/spec/stratus/aws/iam_spec.rb +1224 -0
- data/spec/stratus_spec.rb +4 -0
- metadata +146 -0
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Serverworks Co.,Ltd.
|
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
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
Stratus
|
2
|
+
====
|
3
|
+
|
4
|
+
Stratus is a client interface for the [AWS Identity and Access Management (IAM)](http://aws.amazon.com/documentation/iam/) Services.
|
5
|
+
|
6
|
+
It was developed to for usage in the Japanese AWS management console service [Cloudworks](http://www.cloudworks.jp/).
|
7
|
+
|
8
|
+
REQUIREMENTS:
|
9
|
+
----
|
10
|
+
|
11
|
+
* Ruby 1.8.7 or 1.9.2
|
12
|
+
* xml-simple and rest-client gems
|
13
|
+
* json_pure or json gem (optionally)
|
14
|
+
|
15
|
+
INSTALL:
|
16
|
+
----
|
17
|
+
|
18
|
+
gem install stratus
|
19
|
+
|
20
|
+
USAGE EXAMPLE
|
21
|
+
----
|
22
|
+
|
23
|
+
### As interactive shell
|
24
|
+
|
25
|
+
You can run interactive shell `iamsh' and call IAM API.
|
26
|
+
|
27
|
+
$ export AMAZON_ACCESS_KEY_ID=XXXXX
|
28
|
+
$ export AMAZON_SECRET_ACCESS_KEY=XXXXX
|
29
|
+
$ iamsh
|
30
|
+
|
31
|
+
@iam defined.
|
32
|
+
|
33
|
+
Examples to try:
|
34
|
+
|
35
|
+
returns : all iam public methods
|
36
|
+
>> @iam.methods.sort
|
37
|
+
|
38
|
+
returns : get all Amazon IAM groups.
|
39
|
+
>> @iam.list_groups
|
40
|
+
|
41
|
+
Welcome to IRB.
|
42
|
+
>>
|
43
|
+
|
44
|
+
Create a new IAM user by CreateUser API.
|
45
|
+
|
46
|
+
>> @iam.create_user :user_name => 'john'
|
47
|
+
>> result = @iam.list_users
|
48
|
+
>> puts result['ListUsersResult']['Users']['member'].inspect
|
49
|
+
[{"UserName"=>"john", "Arn"=>"arn:aws:iam::000000000000:user/john", "Path"=>"/", "UserId"=>"XXXXXXXXXXXXXXXXXXXX"}]
|
50
|
+
|
51
|
+
Then create an user policy JSON string.
|
52
|
+
|
53
|
+
>> policy = {}
|
54
|
+
>> policy['Statement'] = [{
|
55
|
+
'Effect' => 'Allow',
|
56
|
+
'Action' => 'ec2:Describe*',
|
57
|
+
'Resource' => '*'
|
58
|
+
}]
|
59
|
+
>> require 'json'
|
60
|
+
>> policy = policy.to_json
|
61
|
+
|
62
|
+
And put it by PutUserPolicy API.
|
63
|
+
|
64
|
+
>> @iam.put_user_policy :user_name => 'john', :policy_name => 'AllowDescribeEC2', :policy_document => policy
|
65
|
+
>> result = @iam.get_user_policy :user_name => 'john', :policy_name => 'AllowDescribeEC2'
|
66
|
+
>> result['GetUserPolicyResult']['PolicyDocument']
|
67
|
+
"{\"Statement\":[{\"Action\":\"ec2:Describe*\",\"Resource\":\"*\",\"Effect\":\"Allow\"}]}"
|
68
|
+
|
69
|
+
Delete an user policy and user.
|
70
|
+
|
71
|
+
>> @iam.delete_user_policy :user_name => 'john', :policy_name => 'AllowDescribeEC2'
|
72
|
+
>> @iam.delete_user :user_name => 'john'
|
73
|
+
|
74
|
+
### As library
|
75
|
+
|
76
|
+
You can require the library and call IAM API from any ruby script.
|
77
|
+
|
78
|
+
require 'rubygems'
|
79
|
+
require 'stratus'
|
80
|
+
|
81
|
+
iam = Stratus::AWS::IAM::Base.new('YOUR_ACCESS_KEY_ID', 'YOUR_SECRET_ACCESS_KEY')
|
82
|
+
result = iam.create_group :group_name => 'Developers'
|
83
|
+
group = result['CreateGroupResult']['Group']
|
84
|
+
puts "Group ARN is #{group['Arn']}"
|
85
|
+
|
86
|
+
Read the [IAM API Reference](http://docs.amazonwebservices.com/IAM/latest/APIReference/) for further information.
|
87
|
+
|
88
|
+
REFERENCES:
|
89
|
+
----
|
90
|
+
|
91
|
+
* [Using AWS Identity and Access Management](http://docs.amazonwebservices.com/IAM/latest/UserGuide/)
|
92
|
+
* [AWS Identity and Access Management API Reference](http://docs.amazonwebservices.com/IAM/latest/APIReference/)
|
93
|
+
|
94
|
+
LICENSE:
|
95
|
+
----
|
96
|
+
|
97
|
+
This plugin is licensed under the MIT licenses.
|
98
|
+
|
99
|
+
COPYRIGHT:
|
100
|
+
----
|
101
|
+
|
102
|
+
Copyright (c) 2010 Serverworks Co.,Ltd. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.name = "stratus"
|
10
|
+
gem.summary = %Q{Interface classes for the AWS Identity and Access Management (IAM)}
|
11
|
+
gem.description = %Q{Interface classes for the AWS Identity and Access Management (IAM)}
|
12
|
+
gem.email = "support@serverworks.co.jp"
|
13
|
+
gem.homepage = "http://github.com/serverworks/stratus"
|
14
|
+
gem.authors = ["Serverworks Co.,Ltd."]
|
15
|
+
gem.add_dependency('xml-simple', '>= 1.0.12')
|
16
|
+
gem.add_dependency('rest-client', '>= 1.6.1')
|
17
|
+
gem.add_development_dependency('rcov', '>= 0.9.6')
|
18
|
+
gem.add_development_dependency('rspec', '>= 1.2.9')
|
19
|
+
gem.files = FileList['bin/iamsh', 'lib/**/*.rb', '[A-Z]*', 'spec/**/*'].to_a
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'spec/rake/spectask'
|
26
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
29
|
+
end
|
30
|
+
|
31
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
32
|
+
spec.libs << 'lib' << 'spec'
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.rcov = true
|
35
|
+
end
|
36
|
+
|
37
|
+
task :spec => :check_dependencies
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
begin
|
42
|
+
require 'yard'
|
43
|
+
YARD::Rake::YardocTask.new do |t|
|
44
|
+
#t.files = ['lib/**/*.rb']
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
puts "YARD (or a dependency) not available. Install it with: [sudo] gem install yard"
|
48
|
+
end
|
49
|
+
|
50
|
+
# vim: syntax=Ruby
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/iamsh
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
iam_lib = File.dirname(__FILE__) + '/../lib/stratus/aws/iam.rb'
|
4
|
+
setup = File.dirname(__FILE__) + '/iamsh-setup'
|
5
|
+
irb_name = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
|
6
|
+
|
7
|
+
if ( ENV['AMAZON_ACCESS_KEY_ID'] && ENV['AMAZON_SECRET_ACCESS_KEY'] )
|
8
|
+
|
9
|
+
welcome_message = <<-MESSAGE
|
10
|
+
|
11
|
+
@iam defined.
|
12
|
+
|
13
|
+
Examples to try:
|
14
|
+
|
15
|
+
returns : all iam public methods
|
16
|
+
>> @iam.methods.sort
|
17
|
+
|
18
|
+
returns : get all Amazon IAM groups.
|
19
|
+
>> @iam.list_groups
|
20
|
+
|
21
|
+
MESSAGE
|
22
|
+
|
23
|
+
puts welcome_message
|
24
|
+
exec "#{irb_name} -rubygems -r #{iam_lib} -r #{setup} --simple-prompt"
|
25
|
+
else
|
26
|
+
puts "You must define AMAZON_ACCESS_KEY_ID and AMAZON_SECRET_ACCESS_KEY as shell environment variables before running #{$0}!"
|
27
|
+
end
|
data/bin/iamsh-setup
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if ENV['AMAZON_ACCESS_KEY_ID'] && ENV['AMAZON_SECRET_ACCESS_KEY']
|
4
|
+
opts = {
|
5
|
+
:access_key_id => ENV['AMAZON_ACCESS_KEY_ID'],
|
6
|
+
:secret_access_key => ENV['AMAZON_SECRET_ACCESS_KEY']
|
7
|
+
}
|
8
|
+
@iam = Stratus::AWS::IAM::Base.new(opts[:access_key_id], opts[:secret_access_key])
|
9
|
+
end
|
data/lib/stratus.rb
ADDED
data/lib/stratus/aws.rb
ADDED
@@ -0,0 +1,599 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'time'
|
3
|
+
require 'rest_client'
|
4
|
+
require 'rexml/document'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'xmlsimple' unless defined? XmlSimple
|
8
|
+
rescue Exception => e
|
9
|
+
require 'xml-simple' unless defined? XmlSimple
|
10
|
+
end
|
11
|
+
|
12
|
+
module Stratus
|
13
|
+
module AWS
|
14
|
+
|
15
|
+
class Response
|
16
|
+
# Parse the XML response from AWS
|
17
|
+
#
|
18
|
+
# @option options [String] :xml The XML response from AWS that we want to parse
|
19
|
+
# @option options [Hash] :parse_options Override the options for XmlSimple.
|
20
|
+
# @return [Hash] the input :xml converted to a custom Ruby Hash by XmlSimple.
|
21
|
+
def self.parse(options = {})
|
22
|
+
options = {
|
23
|
+
:xml => '',
|
24
|
+
:parse_options => { 'forcearray' => ['item', 'member'], 'suppressempty' => nil, 'keeproot' => false }
|
25
|
+
}.merge(options)
|
26
|
+
response = XmlSimple.xml_in(options[:xml], options[:parse_options])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module IAM
|
31
|
+
DEFAULT_HOST = 'iam.amazonaws.com'
|
32
|
+
API_VERSION = '2010-05-08'
|
33
|
+
|
34
|
+
class Base
|
35
|
+
VALID_ACCESS_KEY_STATUSES = [:active, :inactive].freeze
|
36
|
+
VALID_HTTP_METHODS = [:get, :post].freeze
|
37
|
+
|
38
|
+
# @param [String] access_key_id
|
39
|
+
# @param [String] secret_access_key
|
40
|
+
def initialize(access_key_id, secret_access_key)
|
41
|
+
@access_key_id = access_key_id
|
42
|
+
@secret_access_key = secret_access_key
|
43
|
+
@endpoint = "https://#{DEFAULT_HOST}/"
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [String]
|
47
|
+
def api_version
|
48
|
+
API_VERSION
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def default_host
|
53
|
+
DEFAULT_HOST
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String]
|
57
|
+
def endpoint
|
58
|
+
@endpoint
|
59
|
+
end
|
60
|
+
|
61
|
+
# Calls GetGropup API
|
62
|
+
#
|
63
|
+
# @param [Hash] :group_name parameter is required
|
64
|
+
# @return [Hash]
|
65
|
+
def get_group(options = {})
|
66
|
+
check_group_name(options)
|
67
|
+
params = make_pagination_params(options)
|
68
|
+
params['GroupName'] = options[:group_name]
|
69
|
+
response = call_api('GetGroup', params)
|
70
|
+
Response.parse(:xml => response.to_str)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Calls CreateGropup API
|
74
|
+
#
|
75
|
+
# @param [Hash] :group_name parameter is required
|
76
|
+
# @return [Hash]
|
77
|
+
def create_group(options = {})
|
78
|
+
check_group_name(options)
|
79
|
+
params = { 'GroupName' => options[:group_name] }
|
80
|
+
params['Path'] = options[:path] if options[:path]
|
81
|
+
response = call_api('CreateGroup', params)
|
82
|
+
Response.parse(:xml => response.to_str)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Calls DeleteGropup API
|
86
|
+
#
|
87
|
+
# @param [Hash] :group_name parameter is required
|
88
|
+
# @return [Hash]
|
89
|
+
def delete_group(options = {})
|
90
|
+
check_group_name(options)
|
91
|
+
params = { 'GroupName' => options[:group_name] }
|
92
|
+
response = call_api('DeleteGroup', params)
|
93
|
+
Response.parse(:xml => response.to_str)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Calls ListGroups API
|
97
|
+
#
|
98
|
+
# @return [Hash]
|
99
|
+
def list_groups(options = {})
|
100
|
+
params = make_pagination_params(options)
|
101
|
+
params['PathPrefix'] = options[:path_prefix] if options[:path_prefix]
|
102
|
+
response = call_api('ListGroups', params)
|
103
|
+
Response.parse(:xml => response.to_str)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Calls UpdateGroup API
|
107
|
+
#
|
108
|
+
# @param [Hash] :group_name parameter is required
|
109
|
+
# @return [Hash]
|
110
|
+
def update_group(options = {})
|
111
|
+
check_group_name(options)
|
112
|
+
params = { 'GroupName' => options[:group_name] }
|
113
|
+
params['NewGroupName'] = options[:new_group_name] if options[:new_group_name]
|
114
|
+
params['NewPathName'] = options[:new_path_name] if options[:new_path_name]
|
115
|
+
response = call_api('UpdateGroup', params)
|
116
|
+
Response.parse(:xml => response.to_str)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Calls AddUserToGroup API
|
120
|
+
#
|
121
|
+
# @param [Hash] :group_name and :user_name parameters is required
|
122
|
+
# @return [Hash]
|
123
|
+
def add_user_to_group(options = {})
|
124
|
+
check_group_name(options)
|
125
|
+
check_user_name(options)
|
126
|
+
params = { 'GroupName' => options[:group_name], 'UserName' => options[:user_name] }
|
127
|
+
response = call_api('AddUserToGroup', params)
|
128
|
+
Response.parse(:xml => response.to_str)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Calls RemoveUserFromGroup API
|
132
|
+
#
|
133
|
+
# @param [Hash] :group_name and :user_name parameters is required
|
134
|
+
# @return [Hash]
|
135
|
+
def remove_user_from_group(options = {})
|
136
|
+
check_group_name(options)
|
137
|
+
check_user_name(options)
|
138
|
+
params = { 'GroupName' => options[:group_name], 'UserName' => options[:user_name] }
|
139
|
+
response = call_api('RemoveUserFromGroup', params)
|
140
|
+
Response.parse(:xml => response.to_str)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Calls GetUser API
|
144
|
+
#
|
145
|
+
# @param [Hash]
|
146
|
+
# @return [Hash]
|
147
|
+
def get_user(options = {})
|
148
|
+
params = {}
|
149
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
150
|
+
response = call_api('GetUser', params)
|
151
|
+
Response.parse(:xml => response.to_str)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Calls CreateUser API
|
155
|
+
#
|
156
|
+
# @param [Hash] :user_name parameter is required
|
157
|
+
# @return [Hash]
|
158
|
+
def create_user(options = {})
|
159
|
+
check_user_name(options)
|
160
|
+
params = { 'UserName' => options[:user_name] }
|
161
|
+
params['Path'] = options[:path] if options[:path]
|
162
|
+
response = call_api('CreateUser', params)
|
163
|
+
Response.parse(:xml => response.to_str)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Calls DeleteUser API
|
167
|
+
#
|
168
|
+
# @param [Hash] :user_name parameter is required
|
169
|
+
# @return [Hash]
|
170
|
+
def delete_user(options = {})
|
171
|
+
check_user_name(options)
|
172
|
+
params = { 'UserName' => options[:user_name] }
|
173
|
+
response = call_api('DeleteUser', params)
|
174
|
+
Response.parse(:xml => response.to_str)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Calls ListUsers API
|
178
|
+
#
|
179
|
+
# @param [Hash]
|
180
|
+
# @return [Hash]
|
181
|
+
def list_users(options = {})
|
182
|
+
params = make_pagination_params(options)
|
183
|
+
params['PathPrefix'] = options[:path_prefix] if options[:path_prefix]
|
184
|
+
response = call_api('ListUsers', params)
|
185
|
+
Response.parse(:xml => response.to_str)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Calls ListGroupsForUser API
|
189
|
+
#
|
190
|
+
# @param [Hash] :user_name parameter is required
|
191
|
+
# @return [Hash]
|
192
|
+
def list_groups_for_user(options = {})
|
193
|
+
check_user_name(options)
|
194
|
+
params = make_pagination_params(options)
|
195
|
+
params['UserName'] = options[:user_name]
|
196
|
+
response = call_api('ListGroupsForUser', params)
|
197
|
+
Response.parse(:xml => response.to_str)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Calls UpdateUser API
|
201
|
+
#
|
202
|
+
# @param [Hash] :user_name parameter is required
|
203
|
+
# @return [Hash]
|
204
|
+
def update_user(options = {})
|
205
|
+
check_user_name(options)
|
206
|
+
params = {}
|
207
|
+
params['UserName'] = options[:user_name]
|
208
|
+
params['NewPath'] = options[:new_path] if options[:new_path]
|
209
|
+
params['NewUserName'] = options[:new_user_name] if options[:new_user_name]
|
210
|
+
response = call_api('UpdateUser', params)
|
211
|
+
Response.parse(:xml => response.to_str)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Calls CreateAccessKey API
|
215
|
+
#
|
216
|
+
# @param [Hash] options
|
217
|
+
# @return [Hash]
|
218
|
+
def create_access_key(options = {})
|
219
|
+
params = {}
|
220
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
221
|
+
response = call_api('CreateAccessKey', params)
|
222
|
+
Response.parse(:xml => response.to_str)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Calls DeleteAccessKey API
|
226
|
+
#
|
227
|
+
# @param [Hash] options :access_key_id option is required.
|
228
|
+
# @return [Hash]
|
229
|
+
def delete_access_key(options = {})
|
230
|
+
check_access_key_id(options)
|
231
|
+
params = { 'AccessKeyId' => options[:access_key_id] }
|
232
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
233
|
+
response = call_api('DeleteAccessKey', params)
|
234
|
+
Response.parse(:xml => response.to_str)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Calls UpdateAccessKey API
|
238
|
+
#
|
239
|
+
# @param [Hash] options :access_key_id and :status options is required.
|
240
|
+
# @return [Hash]
|
241
|
+
def update_access_key(options = {})
|
242
|
+
check_access_key_id(options)
|
243
|
+
check_activity_status(options)
|
244
|
+
params = { 'AccessKeyId' => options[:access_key_id], 'Status' => options[:status].to_s.capitalize }
|
245
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
246
|
+
response = call_api('UpdateAccessKey', params)
|
247
|
+
Response.parse(:xml => response.to_str)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Calls ListAccessKeys API
|
251
|
+
#
|
252
|
+
# @param [Hash] options
|
253
|
+
# @return [Hash]
|
254
|
+
def list_access_keys(options = {})
|
255
|
+
params = make_pagination_params(options)
|
256
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
257
|
+
response = call_api('ListAccessKeys', params)
|
258
|
+
Response.parse(:xml => response.to_str)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Calls GetGroupPolicy API
|
262
|
+
#
|
263
|
+
# @param [Hash] options :group_name and :policy_name options is required.
|
264
|
+
# @return [Hash]
|
265
|
+
def get_group_policy(options = {})
|
266
|
+
check_group_name(options)
|
267
|
+
check_policy_name(options)
|
268
|
+
params = { 'GroupName' => options[:group_name], 'PolicyName' => options[:policy_name] }
|
269
|
+
response = call_api('GetGroupPolicy', params)
|
270
|
+
result = Response.parse(:xml => response.to_str)
|
271
|
+
if result && result['GetGroupPolicyResult'] && result['GetGroupPolicyResult']['PolicyDocument']
|
272
|
+
result['GetGroupPolicyResult']['PolicyDocument'] = decode_uri(result['GetGroupPolicyResult']['PolicyDocument'])
|
273
|
+
end
|
274
|
+
result
|
275
|
+
end
|
276
|
+
|
277
|
+
# Calls PutGroupPolicy API
|
278
|
+
#
|
279
|
+
# @param [Hash] options
|
280
|
+
# @return [Hash]
|
281
|
+
def put_group_policy(options = {})
|
282
|
+
check_group_name(options)
|
283
|
+
check_policy_name(options)
|
284
|
+
check_policy_document(options)
|
285
|
+
params = {
|
286
|
+
'GroupName' => options[:group_name],
|
287
|
+
'PolicyName' => options[:policy_name],
|
288
|
+
'PolicyDocument' => options[:policy_document]
|
289
|
+
}
|
290
|
+
response = call_api('PutGroupPolicy', params)
|
291
|
+
Response.parse(:xml => response.to_str)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Calls DeleteGroupPolicy API
|
295
|
+
#
|
296
|
+
# @param [Hash] options :group_name and :policy_name options is required.
|
297
|
+
# @return [Hash]
|
298
|
+
def delete_group_policy(options = {})
|
299
|
+
check_group_name(options)
|
300
|
+
check_policy_name(options)
|
301
|
+
params = { 'GroupName' => options[:group_name], 'PolicyName' => options[:policy_name] }
|
302
|
+
response = call_api('DeleteGroupPolicy', params)
|
303
|
+
Response.parse(:xml => response.to_str)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Calls ListGroupPolicies API
|
307
|
+
#
|
308
|
+
# @param [Hash] options :group_name options is required.
|
309
|
+
# @return [Hash]
|
310
|
+
def list_group_policies(options = {})
|
311
|
+
check_group_name(options)
|
312
|
+
params = make_pagination_params(options)
|
313
|
+
params['GroupName'] = options[:group_name]
|
314
|
+
response = call_api('ListGroupPolicies', params)
|
315
|
+
Response.parse(:xml => response.to_str)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Calls GetUserPolicy API
|
319
|
+
#
|
320
|
+
# @param [Hash] options :user_name and :policy_name options is required.
|
321
|
+
# @return [Hash]
|
322
|
+
def get_user_policy(options = {})
|
323
|
+
check_user_name(options)
|
324
|
+
check_policy_name(options)
|
325
|
+
params = { 'UserName' => options[:user_name], 'PolicyName' => options[:policy_name] }
|
326
|
+
response = call_api('GetUserPolicy', params)
|
327
|
+
result = Response.parse(:xml => response.to_str)
|
328
|
+
if result && result['GetUserPolicyResult'] && result['GetUserPolicyResult']['PolicyDocument']
|
329
|
+
result['GetUserPolicyResult']['PolicyDocument'] = decode_uri(result['GetUserPolicyResult']['PolicyDocument'])
|
330
|
+
end
|
331
|
+
result
|
332
|
+
end
|
333
|
+
|
334
|
+
# Calls PutUserPolicy API
|
335
|
+
#
|
336
|
+
# @param [Hash] options
|
337
|
+
# @return [Hash]
|
338
|
+
def put_user_policy(options = {})
|
339
|
+
check_user_name(options)
|
340
|
+
check_policy_name(options)
|
341
|
+
check_policy_document(options)
|
342
|
+
params = {
|
343
|
+
'UserName' => options[:user_name],
|
344
|
+
'PolicyName' => options[:policy_name],
|
345
|
+
'PolicyDocument' => options[:policy_document]
|
346
|
+
}
|
347
|
+
response = call_api('PutUserPolicy', params)
|
348
|
+
Response.parse(:xml => response.to_str)
|
349
|
+
end
|
350
|
+
|
351
|
+
# Calls DeleteUserPolicy API
|
352
|
+
#
|
353
|
+
# @param [Hash] options :user_name and :policy_name options is required.
|
354
|
+
# @return [Hash]
|
355
|
+
def delete_user_policy(options = {})
|
356
|
+
check_user_name(options)
|
357
|
+
check_policy_name(options)
|
358
|
+
params = { 'UserName' => options[:user_name], 'PolicyName' => options[:policy_name] }
|
359
|
+
response = call_api('DeleteUserPolicy', params)
|
360
|
+
Response.parse(:xml => response.to_str)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Calls ListUserPolicies API
|
364
|
+
#
|
365
|
+
# @param [Hash] options :user_name options is required.
|
366
|
+
# @return [Hash]
|
367
|
+
def list_user_policies(options = {})
|
368
|
+
check_user_name(options)
|
369
|
+
params = make_pagination_params(options)
|
370
|
+
params['UserName'] = options[:user_name]
|
371
|
+
response = call_api('ListUserPolicies', params)
|
372
|
+
Response.parse(:xml => response.to_str)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Calls UploadSigningCertificate API
|
376
|
+
#
|
377
|
+
# @param [Hash] options :certificate_body options is required.
|
378
|
+
# @return [Hash]
|
379
|
+
def upload_signing_certificate(options = {})
|
380
|
+
check_certificate_body(options)
|
381
|
+
params = { 'CertificateBody' => options[:certificate_body] }
|
382
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
383
|
+
response = call_api('UploadSigningCertificate', params)
|
384
|
+
Response.parse(:xml => response.to_str)
|
385
|
+
end
|
386
|
+
|
387
|
+
# Calls UpdateSigningCertificate API
|
388
|
+
#
|
389
|
+
# @param [Hash] options :certificate_id and :status options is required.
|
390
|
+
# @return [Hash]
|
391
|
+
def update_signing_certificate(options = {})
|
392
|
+
check_certificate_id(options)
|
393
|
+
check_activity_status(options)
|
394
|
+
params = { 'CertificateId' => options[:certificate_id], 'Status' => options[:status].to_s.capitalize }
|
395
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
396
|
+
response = call_api('UpdateSigningCertificate', params)
|
397
|
+
Response.parse(:xml => response.to_str)
|
398
|
+
end
|
399
|
+
|
400
|
+
# Calls DeleteSigningCertificate API
|
401
|
+
#
|
402
|
+
# @param [Hash] options :certificate_id options is required.
|
403
|
+
# @return [Hash]
|
404
|
+
def delete_signing_certificate(options = {})
|
405
|
+
check_certificate_id(options)
|
406
|
+
params = { 'CertificateId' => options[:certificate_id] }
|
407
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
408
|
+
response = call_api('DeleteSigningCertificate', params)
|
409
|
+
Response.parse(:xml => response.to_str)
|
410
|
+
end
|
411
|
+
|
412
|
+
# Calls list_signing_certificates API
|
413
|
+
#
|
414
|
+
# @param [Hash] options
|
415
|
+
# @return [Hash]
|
416
|
+
def list_signing_certificates(options = {})
|
417
|
+
params = make_pagination_params(options)
|
418
|
+
params['UserName'] = options[:user_name] if options[:user_name]
|
419
|
+
response = call_api('ListSigningCertificates', params)
|
420
|
+
Response.parse(:xml => response.to_str)
|
421
|
+
end
|
422
|
+
|
423
|
+
# @param [String] action AWS IAM API action name
|
424
|
+
# @param [Hash] params
|
425
|
+
# @return RestClient::Response
|
426
|
+
def call_api(action, params)
|
427
|
+
params['Action'] = action.to_s
|
428
|
+
if params['Action'] == 'UploadSigningCertificate'
|
429
|
+
return request(params, :method => :post)
|
430
|
+
end
|
431
|
+
request(params)
|
432
|
+
end
|
433
|
+
|
434
|
+
protected
|
435
|
+
|
436
|
+
# @param [Hash] params
|
437
|
+
# @return [RestClient::Response]
|
438
|
+
def request(params, options = {})
|
439
|
+
options = { :method => :get, :api_version => '2010-05-08' }.merge(options)
|
440
|
+
auth_params = {
|
441
|
+
'AWSAccessKeyId' => @access_key_id,
|
442
|
+
'SignatureMethod' => 'HmacSHA1',
|
443
|
+
'SignatureVersion' => '2',
|
444
|
+
'Timestamp' => Time.now.utc.iso8601,
|
445
|
+
'Version' => options[:api_version]
|
446
|
+
}
|
447
|
+
signed_params = sign_to_params(auth_params.merge(params), options[:method])
|
448
|
+
if (options[:method] == :post)
|
449
|
+
return RestClient.post self.endpoint, signed_params
|
450
|
+
end
|
451
|
+
RestClient.get self.endpoint, { :params => signed_params }
|
452
|
+
end
|
453
|
+
|
454
|
+
# @param [Hash] params
|
455
|
+
# @return [Hash]
|
456
|
+
def sign_to_params(params, http_method = :get)
|
457
|
+
unless (VALID_HTTP_METHODS.include?(http_method))
|
458
|
+
raise ArgumentError, 'Invalid HTTP method proviced. method must be :get or :post'
|
459
|
+
end
|
460
|
+
tmp = []
|
461
|
+
sorted_params = params.sort { |a, b| a[0] <=> b[0] }
|
462
|
+
encoded_params = sorted_params.collect do |p|
|
463
|
+
encoded = (CGI::escape(p[0].to_s) + '=' + CGI::escape(p[1].to_s))
|
464
|
+
# Ensure spaces are encoded as '%20', not '+'
|
465
|
+
encoded = encoded.gsub('+', '%20')
|
466
|
+
# According to RFC3986 (the scheme for values expected by signing requests), '~' should not be encoded
|
467
|
+
encoded = encoded.gsub('%7E', '~')
|
468
|
+
end
|
469
|
+
sigquery = encoded_params.join('&')
|
470
|
+
|
471
|
+
# Generate the request description string
|
472
|
+
method = http_method.to_s.upcase
|
473
|
+
request_uri = '/'
|
474
|
+
req_desc = method + "\n" + default_host + "\n" + request_uri + "\n" + sigquery
|
475
|
+
|
476
|
+
# create sig
|
477
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
478
|
+
sig = Base64.encode64(OpenSSL::HMAC.digest(digest, @secret_access_key, req_desc)).gsub("\n", '')
|
479
|
+
|
480
|
+
params.merge({ 'Signature' => sig })
|
481
|
+
end
|
482
|
+
|
483
|
+
private
|
484
|
+
|
485
|
+
# Check to be sure the :user_name option exist
|
486
|
+
# @param [Hash] options
|
487
|
+
# @return [Hash]
|
488
|
+
# @raise [ArgumentError] throw if the option[:user_name] is nil or empty.
|
489
|
+
def check_user_name(options)
|
490
|
+
raise ArgumentError, 'No user name provided' if options[:user_name].nil? || options[:user_name].empty?
|
491
|
+
options
|
492
|
+
end
|
493
|
+
|
494
|
+
# Check to be sure the :group_name option exist
|
495
|
+
#
|
496
|
+
# @param [Hash] options
|
497
|
+
# @return [Hash]
|
498
|
+
# @raise [ArgumentError] throw if the option[:group_name] is nil or empty.
|
499
|
+
def check_group_name(options)
|
500
|
+
raise ArgumentError, 'No group name provided' if options[:group_name].nil? || options[:group_name].empty?
|
501
|
+
options
|
502
|
+
end
|
503
|
+
|
504
|
+
# Check to be sure the :access_key option exist
|
505
|
+
#
|
506
|
+
# @param [Hash] options
|
507
|
+
# @return [Hash]
|
508
|
+
# @raise [ArgumentError] throw if the option[:group_name] is nil or empty.
|
509
|
+
def check_access_key_id(options)
|
510
|
+
raise ArgumentError, 'No access key id provided' if options[:access_key_id].nil? || options[:access_key_id].empty?
|
511
|
+
options
|
512
|
+
end
|
513
|
+
|
514
|
+
# Check to be sure the :status option and validate the :status option format
|
515
|
+
#
|
516
|
+
# @param [Hash] options
|
517
|
+
# @return [Hash]
|
518
|
+
# @raise [ArgumentError] throw if the option[:group_name] is nil or empty.
|
519
|
+
def check_activity_status(options)
|
520
|
+
status = options[:status].to_s
|
521
|
+
raise ArgumentError, 'No status provided' if status.empty?
|
522
|
+
unless VALID_ACCESS_KEY_STATUSES.include?(status.downcase.to_sym)
|
523
|
+
raise ArgumentError, 'status option value must be "Active" or "Inactive"'
|
524
|
+
end
|
525
|
+
options
|
526
|
+
end
|
527
|
+
|
528
|
+
# Check to be sure the :policy_name option exist
|
529
|
+
#
|
530
|
+
# @param [Hash] options
|
531
|
+
# @return [Hash]
|
532
|
+
# @raise [ArgumentError] throw if the option[:policy_name] is nil or empty.
|
533
|
+
def check_policy_name(options)
|
534
|
+
raise ArgumentError, 'No policy name provided' if options[:policy_name].nil? || options[:policy_name].empty?
|
535
|
+
options
|
536
|
+
end
|
537
|
+
|
538
|
+
# Check to be sure the :policy_document option exist
|
539
|
+
#
|
540
|
+
# @param [Hash] options
|
541
|
+
# @return [Hash]
|
542
|
+
# @raise [ArgumentError] throw if the option[:policy_document] is nil or empty.
|
543
|
+
def check_policy_document(options)
|
544
|
+
raise ArgumentError, 'No policy document provided' if options[:policy_document].nil? || options[:policy_document].empty?
|
545
|
+
options
|
546
|
+
end
|
547
|
+
|
548
|
+
# Check to be sure the :certificate_id option exist
|
549
|
+
#
|
550
|
+
# @param [Hash] options
|
551
|
+
# @return [Hash]
|
552
|
+
# @raise [ArgumentError] throw if the option[:policy_document] is nil or empty.
|
553
|
+
def check_certificate_id(options)
|
554
|
+
raise ArgumentError, 'No certificate body provided' if options[:certificate_id].nil? || options[:certificate_id].empty?
|
555
|
+
options
|
556
|
+
end
|
557
|
+
|
558
|
+
# Check to be sure the :certificate_body option exist
|
559
|
+
#
|
560
|
+
# @param [Hash] options
|
561
|
+
# @return [Hash]
|
562
|
+
# @raise [ArgumentError] throw if the option[:policy_document] is nil or empty.
|
563
|
+
def check_certificate_body(options)
|
564
|
+
raise ArgumentError, 'No certificate body provided' if options[:certificate_body].nil? || options[:certificate_body].empty?
|
565
|
+
options
|
566
|
+
end
|
567
|
+
|
568
|
+
# Make a parameters hash for ***List methos from options
|
569
|
+
#
|
570
|
+
# ex. ListAccessKeys, ListUsers, ListGroups, etc...
|
571
|
+
#
|
572
|
+
# @param [Hash] options that passed from argument
|
573
|
+
# @return [Hash]
|
574
|
+
def make_pagination_params(options)
|
575
|
+
params = {}
|
576
|
+
params['Marker'] = options[:marker] if options[:marker]
|
577
|
+
params['MaxItems'] = options[:max_items] if options[:max_items]
|
578
|
+
params
|
579
|
+
end
|
580
|
+
|
581
|
+
# Decode a URI according to RFC 3986
|
582
|
+
#
|
583
|
+
# Notice: CGI.escape is not accoding to RFC 3986,
|
584
|
+
# but CGI.unescape seems according to RFC 3986.
|
585
|
+
# ex. CGI.unescape('~') -> '~'
|
586
|
+
# CGI.unescape('%2B') -> '+'
|
587
|
+
# CGI.unescape('%20') -> ' '
|
588
|
+
#
|
589
|
+
# @param [String] uri A string encoded by RFC 3986
|
590
|
+
# @return [String] Decoded string
|
591
|
+
def decode_uri(uri)
|
592
|
+
return uri unless uri
|
593
|
+
CGI::unescape(uri)
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|