idrac 0.1.17 → 0.1.19
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.
- checksums.yaml +4 -4
- data/README.md +15 -0
- data/bin/idrac +3 -1
- data/lib/idrac/client.rb +197 -41
- data/lib/idrac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 287cc837870fd1a004f10303ac8a0b60d594f3b2f4561c322e9a56b8cfa3de78
|
4
|
+
data.tar.gz: a34978f43c1157eb3d66a7e3399f057b238d2de777e79c1bfc5a3533a178cc6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6af64f193514911149d8f902ee0f9bab6a380abc9ad740461f0903cd3dc627f45fdd13e16511da646413922e950aa9c853768acfd8c6692d27c47804a9b70d01
|
7
|
+
data.tar.gz: 8391f69048343693a387bf469f6b71de1322b994435ce3410df031f4171d2fc202607ff5ee3e75df399d5f2ffa630ba3349af798bb54eb561f5cb6c0584b26bb
|
data/README.md
CHANGED
@@ -105,6 +105,21 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
105
105
|
|
106
106
|
## Changelog
|
107
107
|
|
108
|
+
### Version 0.1.19
|
109
|
+
- **Basic Auth Session Clearing**: Implemented direct session management using Basic Authentication
|
110
|
+
- Added ability to list and delete all active sessions without requiring a session
|
111
|
+
- Improved session clearing by directly accessing the Redfish Sessions API with Basic Auth
|
112
|
+
- Enhanced force_clear_sessions to first try the direct Basic Auth approach before falling back to login/logout cycles
|
113
|
+
- Added detailed logging of session clearing operations
|
114
|
+
|
115
|
+
### Version 0.1.18
|
116
|
+
- **Direct Mode Implementation**: Added a fallback mechanism for environments with persistent session issues
|
117
|
+
- Implemented automatic switching to direct mode (Basic Auth) when session creation repeatedly fails
|
118
|
+
- Added detection of maximum sessions condition to avoid unnecessary session creation attempts
|
119
|
+
- Improved handling of authentication failures with graceful degradation to direct mode
|
120
|
+
- Added Basic Auth support for all request types when in direct mode
|
121
|
+
- Enhanced logging for better visibility into authentication mode changes
|
122
|
+
|
108
123
|
### Version 0.1.17
|
109
124
|
- **Enhanced Session Clearing**: Added aggressive session clearing functionality
|
110
125
|
- Implemented a force_clear_sessions method that attempts multiple login/logout cycles with both authentication methods
|
data/bin/idrac
CHANGED
@@ -19,6 +19,7 @@ module IDRAC
|
|
19
19
|
class_option :port, type: :numeric, default: 443, desc: "iDRAC port"
|
20
20
|
class_option :no_ssl, type: :boolean, default: false, desc: "Disable SSL"
|
21
21
|
class_option :verify_ssl, type: :boolean, default: false, desc: "Enable SSL verification (not recommended for iDRAC's self-signed certificates)"
|
22
|
+
class_option :direct_mode, type: :boolean, default: false, desc: "Use direct mode with Basic Auth instead of sessions (for environments with session limits)"
|
22
23
|
|
23
24
|
desc "firmware:update PATH", "Update firmware using the specified file"
|
24
25
|
method_option :wait, type: :boolean, default: true, desc: "Wait for the update to complete"
|
@@ -191,7 +192,8 @@ module IDRAC
|
|
191
192
|
password: options[:password],
|
192
193
|
port: options[:port],
|
193
194
|
use_ssl: !options[:no_ssl],
|
194
|
-
verify_ssl: options[:verify_ssl]
|
195
|
+
verify_ssl: options[:verify_ssl],
|
196
|
+
direct_mode: options[:direct_mode]
|
195
197
|
)
|
196
198
|
end
|
197
199
|
end
|
data/lib/idrac/client.rb
CHANGED
@@ -4,12 +4,14 @@ require 'nokogiri'
|
|
4
4
|
require 'base64'
|
5
5
|
require 'uri'
|
6
6
|
require 'httparty'
|
7
|
+
require 'json'
|
7
8
|
|
8
9
|
module IDRAC
|
9
10
|
class Client
|
10
11
|
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl
|
12
|
+
attr_accessor :direct_mode
|
11
13
|
|
12
|
-
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true)
|
14
|
+
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true, direct_mode: false)
|
13
15
|
@host = host
|
14
16
|
@username = username
|
15
17
|
@password = password
|
@@ -19,6 +21,9 @@ module IDRAC
|
|
19
21
|
@session_id = nil
|
20
22
|
@cookies = nil
|
21
23
|
@x_auth_token = nil
|
24
|
+
@direct_mode = direct_mode
|
25
|
+
@sessions_maxed = false
|
26
|
+
@tried_clearing_sessions = false
|
22
27
|
end
|
23
28
|
|
24
29
|
def connection
|
@@ -33,6 +38,15 @@ module IDRAC
|
|
33
38
|
def force_clear_sessions
|
34
39
|
puts "Attempting to force clear all sessions..."
|
35
40
|
|
41
|
+
# First try to delete sessions directly using Basic Auth
|
42
|
+
if delete_all_sessions_with_basic_auth
|
43
|
+
puts "Successfully cleared sessions using Basic Auth"
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
# If direct deletion fails, try the login/logout cycle approach
|
48
|
+
puts "Falling back to login/logout cycle approach for clearing sessions..."
|
49
|
+
|
36
50
|
# Try multiple login/logout cycles with both methods
|
37
51
|
3.times do |i|
|
38
52
|
begin
|
@@ -74,8 +88,84 @@ module IDRAC
|
|
74
88
|
puts "Completed session clearing attempts"
|
75
89
|
end
|
76
90
|
|
91
|
+
# Delete all sessions using Basic Authentication
|
92
|
+
def delete_all_sessions_with_basic_auth
|
93
|
+
puts "Attempting to delete all sessions using Basic Authentication..."
|
94
|
+
|
95
|
+
# First, get the list of sessions
|
96
|
+
sessions_url = '/redfish/v1/SessionService/Sessions'
|
97
|
+
|
98
|
+
# Create a connection with Basic Auth
|
99
|
+
basic_auth_headers = {
|
100
|
+
'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
|
101
|
+
'Content-Type' => 'application/json'
|
102
|
+
}
|
103
|
+
|
104
|
+
begin
|
105
|
+
# Get the list of sessions
|
106
|
+
response = connection.get(sessions_url) do |req|
|
107
|
+
req.headers.merge!(basic_auth_headers)
|
108
|
+
end
|
109
|
+
|
110
|
+
if response.status != 200
|
111
|
+
puts "Failed to get sessions list: #{response.status} - #{response.body}"
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
115
|
+
# Parse the response to get session IDs
|
116
|
+
begin
|
117
|
+
sessions_data = JSON.parse(response.body)
|
118
|
+
|
119
|
+
if sessions_data['Members'] && sessions_data['Members'].any?
|
120
|
+
puts "Found #{sessions_data['Members'].count} active sessions"
|
121
|
+
|
122
|
+
# Delete each session
|
123
|
+
success = true
|
124
|
+
sessions_data['Members'].each do |session|
|
125
|
+
session_url = session['@odata.id']
|
126
|
+
|
127
|
+
# Skip if no URL
|
128
|
+
next unless session_url
|
129
|
+
|
130
|
+
# Delete the session
|
131
|
+
delete_response = connection.delete(session_url) do |req|
|
132
|
+
req.headers.merge!(basic_auth_headers)
|
133
|
+
end
|
134
|
+
|
135
|
+
if delete_response.status == 200 || delete_response.status == 204
|
136
|
+
puts "Successfully deleted session: #{session_url}"
|
137
|
+
else
|
138
|
+
puts "Failed to delete session #{session_url}: #{delete_response.status}"
|
139
|
+
success = false
|
140
|
+
end
|
141
|
+
|
142
|
+
# Small delay between deletions
|
143
|
+
sleep(1)
|
144
|
+
end
|
145
|
+
|
146
|
+
return success
|
147
|
+
else
|
148
|
+
puts "No active sessions found"
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
rescue JSON::ParserError => e
|
152
|
+
puts "Error parsing sessions response: #{e.message}"
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
rescue => e
|
156
|
+
puts "Error during session deletion with Basic Auth: #{e.message}"
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
77
161
|
# Create a Redfish session (preferred method for all Redfish API calls)
|
78
162
|
def create_redfish_session
|
163
|
+
# Skip if we're in direct mode or we know sessions are maxed out
|
164
|
+
if @direct_mode || @sessions_maxed
|
165
|
+
puts "Skipping Redfish session creation (direct mode or sessions maxed)"
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
|
79
169
|
url = '/redfish/v1/SessionService/Sessions'
|
80
170
|
payload = { "UserName" => username, "Password" => password }
|
81
171
|
|
@@ -92,24 +182,38 @@ module IDRAC
|
|
92
182
|
@session_location = response.headers['Location']
|
93
183
|
|
94
184
|
puts "Redfish session created successfully"
|
185
|
+
@sessions_maxed = false
|
95
186
|
return true
|
96
187
|
elsif response.status == 400 && response.body.include?("maximum number of user sessions")
|
97
188
|
puts "Maximum sessions reached during Redfish session creation"
|
98
|
-
|
189
|
+
@sessions_maxed = true
|
99
190
|
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
191
|
+
# Only try to clear sessions if we haven't tried too many times
|
192
|
+
if !@tried_clearing_sessions
|
193
|
+
@tried_clearing_sessions = true
|
194
|
+
force_clear_sessions
|
195
|
+
|
196
|
+
# Try one more time after clearing
|
197
|
+
response = connection.post(url) do |req|
|
198
|
+
req.headers['Content-Type'] = 'application/json'
|
199
|
+
req.body = payload.to_json
|
200
|
+
end
|
201
|
+
|
202
|
+
if response.status == 201 || response.status == 200
|
203
|
+
@x_auth_token = response.headers['X-Auth-Token']
|
204
|
+
@session_location = response.headers['Location']
|
205
|
+
puts "Redfish session created successfully after clearing sessions"
|
206
|
+
@sessions_maxed = false
|
207
|
+
return true
|
208
|
+
else
|
209
|
+
puts "Failed to create Redfish session after clearing: #{response.status} - #{response.body}"
|
210
|
+
# If we still can't create a session, switch to direct mode
|
211
|
+
@direct_mode = true
|
212
|
+
return false
|
213
|
+
end
|
111
214
|
else
|
112
|
-
|
215
|
+
# If we've already tried clearing sessions, switch to direct mode
|
216
|
+
@direct_mode = true
|
113
217
|
return false
|
114
218
|
end
|
115
219
|
else
|
@@ -142,8 +246,16 @@ module IDRAC
|
|
142
246
|
|
143
247
|
# Legacy login method for screenshot functionality
|
144
248
|
def legacy_login(retry_count = 0)
|
249
|
+
# Skip if we're in direct mode
|
250
|
+
if @direct_mode
|
251
|
+
puts "Skipping legacy login (direct mode)"
|
252
|
+
return false
|
253
|
+
end
|
254
|
+
|
145
255
|
# Limit retries to prevent infinite loops
|
146
256
|
if retry_count >= 3
|
257
|
+
# If we've tried too many times, switch to direct mode
|
258
|
+
@direct_mode = true
|
147
259
|
raise Error, "Failed to login after multiple attempts due to maximum sessions limit"
|
148
260
|
end
|
149
261
|
|
@@ -216,20 +328,34 @@ module IDRAC
|
|
216
328
|
|
217
329
|
# Main login method that decides which approach to use
|
218
330
|
def login
|
331
|
+
# If we're in direct mode, skip login attempts
|
332
|
+
if @direct_mode
|
333
|
+
puts "Operating in direct mode (no session)"
|
334
|
+
return true
|
335
|
+
end
|
336
|
+
|
219
337
|
# First try to create a Redfish session
|
220
338
|
if create_redfish_session
|
221
339
|
return true
|
222
340
|
else
|
223
341
|
# If we failed to create a Redfish session, try to clear sessions first
|
224
|
-
|
342
|
+
if !@tried_clearing_sessions
|
343
|
+
@tried_clearing_sessions = true
|
344
|
+
force_clear_sessions
|
345
|
+
|
346
|
+
# Try Redfish again
|
347
|
+
if create_redfish_session
|
348
|
+
return true
|
349
|
+
end
|
350
|
+
end
|
225
351
|
|
226
|
-
#
|
227
|
-
if
|
228
|
-
return true
|
229
|
-
else
|
230
|
-
# Fall back to legacy login if Redfish session creation fails
|
352
|
+
# Fall back to legacy login if Redfish session creation fails
|
353
|
+
if !@direct_mode
|
231
354
|
puts "Falling back to legacy login method"
|
232
355
|
return legacy_login
|
356
|
+
else
|
357
|
+
puts "Operating in direct mode (no session)"
|
358
|
+
return true
|
233
359
|
end
|
234
360
|
end
|
235
361
|
end
|
@@ -250,22 +376,32 @@ module IDRAC
|
|
250
376
|
def authenticated_request(method, path, options = {}, retry_count = 0)
|
251
377
|
# Limit retries to prevent infinite loops
|
252
378
|
if retry_count >= 3
|
253
|
-
|
379
|
+
# If we've tried too many times, switch to direct mode
|
380
|
+
@direct_mode = true
|
381
|
+
puts "Switching to direct mode after multiple authentication failures"
|
254
382
|
end
|
255
383
|
|
256
|
-
# Ensure we have a valid session
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
384
|
+
# Ensure we have a valid session (unless in direct mode)
|
385
|
+
unless @direct_mode
|
386
|
+
begin
|
387
|
+
login unless @x_auth_token || @session_id
|
388
|
+
rescue Error => e
|
389
|
+
# If login fails with maximum sessions error, try to handle it
|
390
|
+
if e.message.include?("maximum number of user sessions") && retry_count < 2
|
391
|
+
puts "Maximum sessions reached during initial login, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
|
392
|
+
logout
|
393
|
+
sleep(5) # Longer delay
|
394
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
395
|
+
else
|
396
|
+
# If we've tried too many times, switch to direct mode
|
397
|
+
if retry_count >= 2
|
398
|
+
@direct_mode = true
|
399
|
+
puts "Switching to direct mode after multiple authentication failures"
|
400
|
+
else
|
401
|
+
# Re-raise other errors or if we've tried too many times
|
402
|
+
raise
|
403
|
+
end
|
404
|
+
end
|
269
405
|
end
|
270
406
|
end
|
271
407
|
|
@@ -277,6 +413,9 @@ module IDRAC
|
|
277
413
|
elsif @session_id
|
278
414
|
# Fall back to session cookie if needed
|
279
415
|
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
416
|
+
elsif @direct_mode
|
417
|
+
# In direct mode, use Basic Auth
|
418
|
+
options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
280
419
|
end
|
281
420
|
|
282
421
|
response = connection.send(method, path, options[:params]) do |req|
|
@@ -285,7 +424,7 @@ module IDRAC
|
|
285
424
|
end
|
286
425
|
|
287
426
|
# Handle authentication errors
|
288
|
-
if response.status == 401
|
427
|
+
if response.status == 401 && !@direct_mode
|
289
428
|
puts "Session expired, re-authenticating..."
|
290
429
|
logout
|
291
430
|
sleep(3)
|
@@ -297,12 +436,21 @@ module IDRAC
|
|
297
436
|
options[:headers]['X-Auth-Token'] = @x_auth_token
|
298
437
|
elsif @session_id
|
299
438
|
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
439
|
+
elsif @direct_mode
|
440
|
+
options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
300
441
|
end
|
301
442
|
|
302
443
|
response = connection.send(method, path, options[:params]) do |req|
|
303
444
|
req.headers.merge!(options[:headers])
|
304
445
|
req.body = options[:body] if options[:body]
|
305
446
|
end
|
447
|
+
|
448
|
+
# If we still get a 401, switch to direct mode
|
449
|
+
if response.status == 401 && retry_count < 2
|
450
|
+
@direct_mode = true
|
451
|
+
puts "Switching to direct mode after authentication failure"
|
452
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
453
|
+
end
|
306
454
|
end
|
307
455
|
|
308
456
|
# Check for maximum sessions error in the response
|
@@ -328,17 +476,25 @@ module IDRAC
|
|
328
476
|
|
329
477
|
def get(path:, headers: {})
|
330
478
|
# For screenshot functionality, we need to use the legacy cookies
|
331
|
-
if @cookies.nil?
|
479
|
+
if @cookies.nil? && !@direct_mode
|
332
480
|
legacy_login unless @session_id
|
333
481
|
end
|
334
482
|
|
483
|
+
headers_to_use = {
|
484
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
485
|
+
"Accept-Encoding" => "deflate, gzip"
|
486
|
+
}
|
487
|
+
|
488
|
+
if @cookies
|
489
|
+
headers_to_use["Cookie"] = @cookies
|
490
|
+
elsif @direct_mode
|
491
|
+
# In direct mode, use Basic Auth
|
492
|
+
headers_to_use["Authorization"] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
493
|
+
end
|
494
|
+
|
335
495
|
response = HTTParty.get(
|
336
496
|
"#{base_url}/#{path}",
|
337
|
-
headers:
|
338
|
-
"User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
339
|
-
"Accept-Encoding" => "deflate, gzip",
|
340
|
-
"Cookie" => @cookies
|
341
|
-
}.merge(headers),
|
497
|
+
headers: headers_to_use.merge(headers),
|
342
498
|
verify: false
|
343
499
|
)
|
344
500
|
end
|
data/lib/idrac/version.rb
CHANGED