right_infrastructure_agent 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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