Module: KeeperSecretsManager::CachingPostFunction

Defined in:
lib/keeper_secrets_manager/cache.rb

Overview

Caching post function for disaster recovery Wraps the normal post_function to save responses and fall back to cache on network failure Usage: KeeperSecretsManager.new(config: storage, custom_post_function: KeeperSecretsManager::CachingPostFunction)

Class Method Summary collapse

Class Method Details

.call(url, transmission_key, encrypted_payload, verify_ssl_certs = true) ⇒ Dto::KSMHttpResponse

Post function that caches successful responses and falls back to cache on failure This matches the pattern used in Python, JavaScript, Java, and .NET SDKs

Parameters:

  • url (String)

    The API endpoint URL

  • transmission_key (Dto::TransmissionKey)

    The transmission key

  • encrypted_payload (Dto::EncryptedPayload)

    The encrypted payload with signature

  • verify_ssl_certs (Boolean) (defaults to: true)

    Whether to verify SSL certificates

Returns:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/keeper_secrets_manager/cache.rb', line 59

def self.call(url, transmission_key, encrypted_payload, verify_ssl_certs = true)
  # Try network request first
  begin
    # Call the static post_function
    response = make_http_request(url, transmission_key, encrypted_payload, verify_ssl_certs)

    # On success, save to cache (transmission key + encrypted response body)
    if response.success? && response.data
      cache_data = transmission_key.key + response.data
      Cache.save_cache(cache_data)
    end

    response
  rescue StandardError => e
    # Network failed - try to load from cache
    cached_data = Cache.get_cached_data

    if cached_data && cached_data.bytesize > 32
      # Extract cached transmission key and response data
      # First 32 bytes are the transmission key, rest is encrypted response
      cached_transmission_key = cached_data[0...32]
      cached_response_data = cached_data[32..-1]

      # Update the transmission key to match cached version
      transmission_key.key = cached_transmission_key

      # Return cached response as if it came from network
      Dto::KSMHttpResponse.new(
        status_code: 200,
        data: cached_response_data
      )
    else
      # No cache available - re-raise the original error
      raise e
    end
  end
end

.make_http_request(url, transmission_key, encrypted_payload, verify_ssl_certs) ⇒ Object

Make HTTP request - extracted to be testable This duplicates some logic from Core::SecretsManager#post_function because that method is an instance method



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/keeper_secrets_manager/cache.rb', line 100

def self.make_http_request(url, transmission_key, encrypted_payload, verify_ssl_certs)
  require 'net/http'
  require 'uri'

  uri = URI(url)

  request = Net::HTTP::Post.new(uri)
  request['Content-Type'] = 'application/octet-stream'
  request['PublicKeyId'] = transmission_key.public_key_id.to_s
  request['TransmissionKey'] = Utils.bytes_to_base64(transmission_key.encrypted_key)
  request['Authorization'] = "Signature #{Utils.bytes_to_base64(encrypted_payload.signature)}"
  request['Content-Length'] = encrypted_payload.encrypted_payload.bytesize.to_s
  request.body = encrypted_payload.encrypted_payload

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  if verify_ssl_certs
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER

    # Set up certificate store with system defaults
    store = OpenSSL::X509::Store.new
    store.set_default_paths
    http.cert_store = store
  else
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  response = http.request(request)

  Dto::KSMHttpResponse.new(
    status_code: response.code.to_i,
    data: response.body,
    http_response: response
  )
rescue StandardError => e
  raise NetworkError, "HTTP request failed: #{e.message}"
end