right_infrastructure_agent 1.1.2

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.
@@ -0,0 +1,140 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'right_infrastructure_agent', 'infrastructure_auth_client'))
26
+
27
+ describe RightScale::InfrastructureAuthClient do
28
+
29
+ include FlexMock::ArgumentTypes
30
+
31
+ class GlobalSessionTestDirectory
32
+ def initialize(configuration, authorities)
33
+ end
34
+
35
+ def configuration
36
+ {"cookie" => {"name" => "yum"}}
37
+ end
38
+ end
39
+
40
+ before(:each) do
41
+ @log = flexmock(RightScale::Log)
42
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
43
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
44
+
45
+ @config_dir = "/tmp"
46
+ @global_session_timeout = 3600
47
+ @global_session_client_timeout = (@global_session_timeout * 8) / 10
48
+ @global_session_config = flexmock("global_session configuration")
49
+ @global_session_config.should_receive(:[]).with("directory").and_return("GlobalSessionTestDirectory").by_default
50
+ @global_session_config.should_receive(:[]).with("timeout").and_return(@global_session_timeout).by_default
51
+ flexmock(GlobalSession::Configuration).should_receive(:new).and_return(@global_session_config).by_default
52
+ @global_session_dir = GlobalSessionTestDirectory.new(@global_session_config, "authorities")
53
+ @global_session = flexmock("global session")
54
+ @global_session.should_receive(:[]=).by_default
55
+ @global_session.should_receive(:directory).and_return(@global_session_dir).by_default
56
+ @global_session.should_receive(:to_s).and_return("session").by_default
57
+ flexmock(GlobalSession::Session).should_receive(:new).and_return(@global_session).by_default
58
+
59
+ @client_name = "agent"
60
+ @router_url = "http://router.com"
61
+ @agent_id = "rs-agent-1-1"
62
+ @options = {:agent_id => @agent_id}
63
+ end
64
+
65
+ context :initialize do
66
+ it "creates global session directory" do
67
+ flexmock(GlobalSession::Configuration).should_receive(:new).and_return(@global_session_config).once
68
+ client = RightScale::InfrastructureAuthClient.new(@client_name, @router_url, @config_dir, @options)
69
+ client.instance_variable_get(:@global_session_dir).is_a?(GlobalSessionTestDirectory).should be_true
70
+ end
71
+
72
+ it "sets timeout at 80% of actual global session timeout" do
73
+ @global_session_config.should_receive(:[]).with("directory").and_return("GlobalSessionTestDirectory").once
74
+ @global_session_config.should_receive(:[]).with("timeout").and_return(@global_session_timeout).once
75
+ client = RightScale::InfrastructureAuthClient.new(@client_name, @router_url, @config_dir, @options)
76
+ client.instance_variable_get(:@global_session_timeout).should == @global_session_client_timeout
77
+ end
78
+ end
79
+
80
+ context :headers do
81
+ before(:each) do
82
+ @client = RightScale::InfrastructureAuthClient.new(@client_name, @router_url, @config_dir, @options)
83
+ end
84
+
85
+ it "raises if not authorized" do
86
+ @client.send(:state=, :unauthorized)
87
+ lambda { @client.headers }.should raise_error(RightScale::Exceptions::Unauthorized)
88
+ end
89
+
90
+ it "returns headers" do
91
+ @client.headers.should == {"Authorization" => "Bearer session"}
92
+ end
93
+ end
94
+
95
+ context :auth_header do
96
+ before(:each) do
97
+ @client = RightScale::InfrastructureAuthClient.new(@client_name, @router_url, @config_dir, @options)
98
+ end
99
+
100
+ it "raises if not authorized" do
101
+ @client.send(:state=, :unauthorized)
102
+ lambda { @client.auth_header }.should raise_error(RightScale::Exceptions::Unauthorized)
103
+ end
104
+
105
+ it "returns authorization header" do
106
+ @client.auth_header.should == {"Authorization" => "Bearer session"}
107
+ end
108
+ end
109
+
110
+ context :infrastructure_session do
111
+ before(:each) do
112
+ @client = RightScale::InfrastructureAuthClient.new(@client_name, @router_url, @config_dir, @options)
113
+ end
114
+
115
+ it "creates session and stores in cache" do
116
+ now = Time.now
117
+ flexmock(Time).should_receive(:now).and_return(now)
118
+ flexmock(GlobalSession::Session).should_receive(:new).and_return(@global_session).once
119
+ @client.send(:infrastructure_session).should == "session"
120
+ @client.instance_variable_get(:@cached_infrastructure_session).should == {:session => "session", :created_at => now}
121
+ end
122
+
123
+ it "retrieves existing session from cache" do
124
+ flexmock(GlobalSession::Session).should_receive(:new).and_return(@global_session).once
125
+ @client.send(:infrastructure_session).should == "session"
126
+ @client.send(:infrastructure_session).should == "session"
127
+ end
128
+
129
+ it "creates a new session if session timed out" do
130
+ now = Time.now
131
+ later = now + @global_session_client_timeout
132
+ flexmock(Time).should_receive(:now).and_return(now, later)
133
+ flexmock(GlobalSession::Session).should_receive(:new).and_return(@global_session).twice
134
+ @client.send(:infrastructure_session).should == "session"
135
+ @client.instance_variable_get(:@cached_infrastructure_session).should == {:session => "session", :created_at => now}
136
+ @client.send(:infrastructure_session).should == "session"
137
+ @client.instance_variable_get(:@cached_infrastructure_session).should == {:session => "session", :created_at => later}
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,80 @@
1
+ #--
2
+ # Copyright (c) 2011-2013 RightScale, Inc, All Rights Reserved Worldwide.
3
+ #
4
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
5
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
6
+ # reproduction, modification, or disclosure of this program is
7
+ # strictly prohibited. Any use of this program by an authorized
8
+ # licensee is strictly subject to the terms and conditions,
9
+ # including confidentiality obligations, set forth in the applicable
10
+ # License Agreement between RightScale.com, Inc. and the licensee.
11
+ #++
12
+
13
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
14
+
15
+ describe RightScale::InfrastructureHelpers do
16
+
17
+ include RightScale::InfrastructureHelpers
18
+
19
+ context :render_nothing do
20
+
21
+ it "renders nothing" do
22
+ flexmock(self).should_receive(:render).with(:nothing => true, :status => :no_content).once
23
+ render_nothing.should be_true
24
+ end
25
+ end
26
+
27
+ context :format_error do
28
+
29
+ it "formats exception" do
30
+ format_error("This is", ArgumentError.new("bad"), :no_trace).should == "This is (ArgumentError: bad)"
31
+ end
32
+
33
+ it "formats error message" do
34
+ format_error("This is", "bad").should == "This is (bad)"
35
+ end
36
+ end
37
+
38
+ context :to_int_or_nil do
39
+
40
+ class ExtendedString < String
41
+ def blank?
42
+ self.empty?
43
+ end
44
+ end
45
+
46
+ it "converts string to integer" do
47
+ to_int_or_nil(ExtendedString.new("123")).should == 123
48
+ end
49
+
50
+ it "converts empty string to nil" do
51
+ to_int_or_nil(ExtendedString.new("")).should == nil
52
+ end
53
+
54
+ it "converts nil to nil" do
55
+ flexmock(NilClass).should_receive(:blank?).and_return(true)
56
+ to_int_or_nil(nil).should == nil
57
+ end
58
+ end
59
+
60
+ context :to_bool do
61
+
62
+ it "converts nil to false" do
63
+ to_bool(nil).should == false
64
+ end
65
+
66
+ it "converts false to false" do
67
+ to_bool(false).should == false
68
+ end
69
+
70
+ it "converts 'false' to false" do
71
+ to_bool("false").should == false
72
+ end
73
+
74
+ it "converts anything else to true" do
75
+ to_bool("anything else").should == true
76
+ to_bool("true").should == true
77
+ to_bool(true).should == true
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,279 @@
1
+ # Copyright (c) 2009-2014 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and the licensee.
10
+
11
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
12
+ require 'right_agent/core_payload_types'
13
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'right_infrastructure_agent', 'login_policy_factory'))
14
+
15
+ # LoginPolicyFactory is shared between library and right_api, which is why it lives here.
16
+ # By creating a mock world of RightScale models, we allow this spec to run independently
17
+ # of the Rails web apps.
18
+ class Account; end unless defined?(Account)
19
+ class User; end unless defined?(User)
20
+ class Ec2Instance ; end unless defined?(Ec2Instance)
21
+ class Role
22
+ def self.[](role)
23
+ role.hash
24
+ end
25
+ end unless defined?(Role)
26
+ module Biz
27
+ module ResourceUuidMixin
28
+ def self.obfuscate_id(object)
29
+ object.to_s
30
+ end
31
+ end
32
+ end
33
+
34
+ module MockHelper
35
+ def months_ago(months)
36
+ Time.now - (24 * 60 * 60 * 30 * months)
37
+ end
38
+
39
+ def mock_account_and_users(num_users)
40
+ account = flexmock(:model, Account)
41
+ account.should_receive(:setting).with('managed_login_mandatory').and_return(false).by_default
42
+
43
+ users = []
44
+ (0...num_users).each do
45
+ user = flexmock(:model, User,
46
+ :id=>rand(2**16),
47
+ :account=>account,
48
+ :email=>"user#{rand(2**16)}@rightscale.com")
49
+ users << user
50
+ end
51
+
52
+ flexmock(User).should_receive(:all).and_return(users)
53
+ return [account] + users
54
+ end
55
+
56
+ def mock_instance(account)
57
+ instance = flexmock(:model, Ec2Instance, :account=>account)
58
+ instance.should_receive(:managed_login_allowed?).and_return(false).by_default
59
+ return instance
60
+ end
61
+
62
+ def mock_login_permission(account, user, expires_at = nil, updated_at = nil)
63
+ updated_at ||= months_ago(1)
64
+
65
+ user.should_receive(:perm_updated_at).and_return(updated_at.to_s)
66
+ user.should_receive(:perm_deleted_at).and_return((expires_at.to_s unless expires_at.nil?))
67
+ end
68
+
69
+ def mock_credential(user, public_key = nil, public_key_fingerprint = nil, updated_at = nil)
70
+ updated_at ||= months_ago(1)
71
+
72
+ user.should_receive(:cred_updated_at).and_return(updated_at.to_s)
73
+ user.should_receive(:cred_public_value).and_return(public_key)
74
+ user.should_receive(:cred_public_fingerprint).and_return(public_key_fingerprint)
75
+ end
76
+ end
77
+
78
+ describe RightScale::LoginPolicyFactory do
79
+ include MockHelper
80
+
81
+ def hours_ago(hours)
82
+ Time.now - (60 * 60 * hours)
83
+ end
84
+
85
+ def days_ago(days)
86
+ Time.now - (24 * 60 * 60 * days)
87
+ end
88
+
89
+ context :policy_for_instance do
90
+ context 'handling oddly-formatted keys' do
91
+ before(:each) do
92
+ @account, @user = mock_account_and_users(1)
93
+ @instance = mock_instance(@account)
94
+ @instance.should_receive(:managed_login_allowed?).with(@user, @account).and_return(true)
95
+ end
96
+
97
+ it 'should add a comment for keys that are missing it' do
98
+ @user_credential = mock_credential(@user, 'ssh-rsa abcd1234')
99
+ @permission = mock_login_permission(@account, @user, nil)
100
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
101
+ @policy.users.size.should == 1
102
+ components = RightScale::LoginPolicy.parse_public_key(@policy.users[0].public_keys.first)
103
+ components[3].should_not be_nil # email should be substituted in
104
+ end
105
+ end
106
+
107
+ context 'calculating policy for a given user' do
108
+ before(:each) do
109
+ @account, @user = mock_account_and_users(1)
110
+ @instance = mock_instance(@account)
111
+ end
112
+
113
+ context "for all users" do
114
+ before(:each) do
115
+ @user_credential = mock_credential(@user, 'ssh-rsa abcd1234 joe@joebob.com', 'sha1')
116
+ @instance.should_receive(:managed_login_allowed?).with(@user, @account).and_return(true)
117
+ @permission = mock_login_permission(@account, @user, nil)
118
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
119
+ @policy.users.size.should == 1
120
+ end
121
+
122
+ it "should specify the user's UUID" do
123
+ @policy.users[0].uuid.should == @user.id.to_s
124
+ end
125
+
126
+ it "should specify a preferred username" do
127
+ @policy.users[0].username.should_not be_nil
128
+ end
129
+
130
+ it "should include a single public key for pre-5.5 instances" do
131
+ @policy.users[0].public_key.should == 'ssh-rsa abcd1234 joe@joebob.com'
132
+ end
133
+
134
+ it "should include a collection of public keys for modern instances" do
135
+ @policy.users[0].public_keys.should == ['ssh-rsa abcd1234 joe@joebob.com']
136
+ end
137
+
138
+ it "should include a collection of public key fingerprints for modern instances" do
139
+ @policy.users[0].public_key_fingerprints.should == ['sha1']
140
+ end
141
+
142
+ it "should specify a common name" do
143
+ @policy.users[0].username.should_not be_nil
144
+ end
145
+ end
146
+
147
+ context "for users not recently updated" do
148
+ before(:each) do
149
+ @permission = mock_login_permission(@account, @user, nil)
150
+ end
151
+
152
+ it "should not be terse by default" do
153
+ @t = days_ago(1)
154
+ @user_credential = mock_credential(@user, 'ssh-rsa abcd1234 joe@joebob.com', 'sha1', @t)
155
+ @instance.should_receive(:managed_login_allowed?).with(@user, @account).and_return(true)
156
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
157
+ @policy.users[0].public_keys.should == ['ssh-rsa abcd1234 joe@joebob.com']
158
+ @policy.users[0].public_key_fingerprints.should == ['sha1']
159
+ end
160
+
161
+ it "should not omit public keys if credential is considered new when requested to be terse" do
162
+ @t = hours_ago(23)
163
+ @user_credential = mock_credential(@user, 'ssh-rsa abcd1234 joe@joebob.com', 'sha1', @t)
164
+ @instance.should_receive(:managed_login_allowed?).with(@user, @account).and_return(true)
165
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234, true)
166
+ @policy.users[0].public_keys.should == ['ssh-rsa abcd1234 joe@joebob.com']
167
+ @policy.users[0].public_key_fingerprints.should == ['sha1']
168
+ end
169
+
170
+ it "should omit public keys that are considered old if requested to be terse" do
171
+ @t = days_ago(1)
172
+ @user_credential = mock_credential(@user, 'ssh-rsa abcd1234 joe@joebob.com', 'sha1', @t)
173
+ @instance.should_receive(:managed_login_allowed?).with(@user, @account).and_return(true)
174
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234, true)
175
+ @policy.users[0].public_keys.should == [nil]
176
+ @policy.users[0].public_key_fingerprints.should == ['sha1']
177
+ end
178
+ end
179
+
180
+ context "for users with temporary access" do
181
+ before(:each) do
182
+ @user_credential = mock_credential(@user, 'ssh-rsa abcd1234 joe@joebob.com', 'sha1')
183
+ @instance.should_receive(:managed_login_allowed?).with(@user, @account).and_return(true)
184
+ @t = Time.at((Time.now + 24 * 60 * 60).to_i)
185
+ @permission = mock_login_permission(@account, @user, @t)
186
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
187
+ @policy.users.size.should == 1
188
+ end
189
+
190
+ it "should specify the expiry time" do
191
+ @policy.users[0].expires_at.to_i.should == @t.to_i
192
+ end
193
+ end
194
+
195
+ end
196
+
197
+ context 'calculating users' do
198
+ before(:each) do
199
+ @account, @u_ok = mock_account_and_users(1)
200
+ mock_login_permission(@account, @u_ok)
201
+ mock_credential(@u_ok, 'moo')
202
+
203
+ @instance = mock_instance(@account)
204
+ @instance.should_receive(:managed_login_allowed?).with(@u_ok, @account).and_return(true)
205
+
206
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
207
+ @policy.users.size.should == 1
208
+ @policy.users.detect { |u| u.common_name == @u_ok.email }.should_not be_nil
209
+ end
210
+
211
+ it 'should include users that satisfy all criteria' do
212
+ @policy.users.detect { |u| u.common_name == @u_ok.email }.should_not be_nil
213
+ end
214
+ end
215
+
216
+ context 'calculating created_at' do
217
+ before(:each) do
218
+ @account, @user1, @user2 = mock_account_and_users(2)
219
+
220
+ @instance = mock_instance(@account)
221
+ @instance.should_receive(:managed_login_allowed?).with(@user1, @account).and_return(true)
222
+ @instance.should_receive(:managed_login_allowed?).with(@user2, @account).and_return(true)
223
+ end
224
+
225
+ it 'should match a UserCredential.updated_at that is most recent within scope' do
226
+ @t = days_ago(1)
227
+ mock_login_permission(@account, @user1)
228
+ mock_login_permission(@account, @user2)
229
+ mock_credential(@user1, 'moo', 'sha', days_ago(2))
230
+ mock_credential(@user2, 'moo', 'sha', @t)
231
+
232
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
233
+ @policy.created_at.to_s.should == @t.to_s
234
+ end
235
+
236
+ it 'should match a Permission.updated_at that is most recent within scope' do
237
+ @t = days_ago(1)
238
+ mock_login_permission(@account, @user1, nil, days_ago(2))
239
+ mock_login_permission(@account, @user2, nil, @t)
240
+ mock_credential(@user1, 'moo')
241
+ mock_credential(@user2, 'moo')
242
+
243
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
244
+ @policy.created_at.to_s.should == @t.to_s
245
+ end
246
+ end
247
+
248
+ context 'calculating exclusive' do
249
+ before(:each) do
250
+ @account, @user = mock_account_and_users(1)
251
+ mock_credential(@user, 'moo')
252
+ mock_login_permission(@account, @user, nil, days_ago(2))
253
+ @instance = mock_instance(@account)
254
+ end
255
+
256
+ it 'should be exclusive if Managed SSH is mandatory for the account' do
257
+ @account.should_receive(:setting).with('managed_login_mandatory').and_return(true)
258
+
259
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
260
+ @policy.exclusive.should == true
261
+ end
262
+
263
+ it 'should not be exclusive if Managed SSH is optional for the account' do
264
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
265
+ @policy.exclusive.should == false
266
+ end
267
+ end
268
+
269
+ it 'should use the audit_id passed in by the caller' do
270
+ @account, @user = mock_account_and_users(1)
271
+ @instance = mock_instance(@account)
272
+ mock_credential(@user, 'moo')
273
+ mock_login_permission(@account, @user, nil, days_ago(2))
274
+
275
+ @policy = RightScale::LoginPolicyFactory.policy_for_instance(@instance, 1234)
276
+ @policy.audit_id.should == 1234
277
+ end
278
+ end
279
+ end