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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d38771d98568288f7d80b1f3686c511eed0f4d44cd7e66658cbbee897a0ab49b
4
- data.tar.gz: ddf5029c40b5b3a57f138fe89e24d1dd8c055b9a07025e7be4fb49038a60abd9
3
+ metadata.gz: 287cc837870fd1a004f10303ac8a0b60d594f3b2f4561c322e9a56b8cfa3de78
4
+ data.tar.gz: a34978f43c1157eb3d66a7e3399f057b238d2de777e79c1bfc5a3533a178cc6c
5
5
  SHA512:
6
- metadata.gz: 0516e7373204bfcc8b71f45ae2291f031a23a01135e956bf971663912a92833d66b0c802ea01e38813bdffbcd1a4a9463036a6ad8d3aa127fb2c8726920d1c39
7
- data.tar.gz: 0bd081077d25c963c1a15b2f327916fa0d86774ca994607acbda2278d3326a677c7348e74b3cfea1ab6ccfe0c19f31dfdc1b462efd0e30aaafb8c3548c36514a
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
- force_clear_sessions
189
+ @sessions_maxed = true
99
190
 
100
- # Try one more time after clearing
101
- response = connection.post(url) do |req|
102
- req.headers['Content-Type'] = 'application/json'
103
- req.body = payload.to_json
104
- end
105
-
106
- if response.status == 201 || response.status == 200
107
- @x_auth_token = response.headers['X-Auth-Token']
108
- @session_location = response.headers['Location']
109
- puts "Redfish session created successfully after clearing sessions"
110
- return true
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
- puts "Failed to create Redfish session after clearing: #{response.status} - #{response.body}"
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
- force_clear_sessions
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
- # Try Redfish again
227
- if create_redfish_session
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
- raise Error, "Failed to complete request after multiple attempts due to session issues"
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
- begin
258
- login unless @x_auth_token || @session_id
259
- rescue Error => e
260
- # If login fails with maximum sessions error, try to handle it
261
- if e.message.include?("maximum number of user sessions") && retry_count < 2
262
- puts "Maximum sessions reached during initial login, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
263
- logout
264
- sleep(5) # Longer delay
265
- return authenticated_request(method, path, options, retry_count + 1)
266
- else
267
- # Re-raise other errors or if we've tried too many times
268
- raise
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IDRAC
4
- VERSION = "0.1.17"
4
+ VERSION = "0.1.19"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.17
4
+ version: 0.1.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel