keeper_secrets_manager 17.0.3
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 +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +305 -0
- data/Rakefile +30 -0
- data/examples/basic_usage.rb +139 -0
- data/examples/config_string_example.rb +99 -0
- data/examples/debug_secrets.rb +84 -0
- data/examples/demo_list_secrets.rb +182 -0
- data/examples/download_files.rb +100 -0
- data/examples/flexible_records_example.rb +94 -0
- data/examples/folder_hierarchy_demo.rb +109 -0
- data/examples/full_demo.rb +176 -0
- data/examples/my_test_standalone.rb +176 -0
- data/examples/simple_test.rb +162 -0
- data/examples/storage_examples.rb +126 -0
- data/lib/keeper_secrets_manager/config_keys.rb +27 -0
- data/lib/keeper_secrets_manager/core.rb +1231 -0
- data/lib/keeper_secrets_manager/crypto.rb +348 -0
- data/lib/keeper_secrets_manager/dto/payload.rb +152 -0
- data/lib/keeper_secrets_manager/dto.rb +221 -0
- data/lib/keeper_secrets_manager/errors.rb +79 -0
- data/lib/keeper_secrets_manager/field_types.rb +152 -0
- data/lib/keeper_secrets_manager/folder_manager.rb +114 -0
- data/lib/keeper_secrets_manager/keeper_globals.rb +59 -0
- data/lib/keeper_secrets_manager/notation.rb +354 -0
- data/lib/keeper_secrets_manager/notation_enhancements.rb +67 -0
- data/lib/keeper_secrets_manager/storage.rb +254 -0
- data/lib/keeper_secrets_manager/totp.rb +140 -0
- data/lib/keeper_secrets_manager/utils.rb +196 -0
- data/lib/keeper_secrets_manager/version.rb +3 -0
- data/lib/keeper_secrets_manager.rb +38 -0
- metadata +82 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'keeper_secrets_manager'
|
4
|
+
require 'base64'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
puts "=== Keeper Secrets Manager Ruby SDK Full Demo ==="
|
8
|
+
puts "This demonstrates all working features of the Ruby SDK"
|
9
|
+
puts "=" * 60
|
10
|
+
|
11
|
+
# Load configuration
|
12
|
+
config_file = File.join(File.dirname(__FILE__), '..', 'config.base64')
|
13
|
+
unless File.exist?(config_file)
|
14
|
+
puts "ERROR: config.base64 not found"
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
config_base64 = File.read(config_file).strip
|
19
|
+
config_json = Base64.decode64(config_base64)
|
20
|
+
config_data = JSON.parse(config_json)
|
21
|
+
|
22
|
+
# Initialize SDK
|
23
|
+
storage = KeeperSecretsManager::Storage::InMemoryStorage.new(config_data)
|
24
|
+
secrets_manager = KeeperSecretsManager.new(config: storage)
|
25
|
+
|
26
|
+
# 1. Authentication Test
|
27
|
+
puts "\n1. AUTHENTICATION"
|
28
|
+
puts " ✅ Successfully authenticated with Keeper servers"
|
29
|
+
puts " ✅ Using ECDSA signatures"
|
30
|
+
puts " ✅ AES-GCM encryption active"
|
31
|
+
|
32
|
+
# 2. List existing records
|
33
|
+
puts "\n2. READING RECORDS"
|
34
|
+
records = secrets_manager.get_secrets
|
35
|
+
puts " Found #{records.length} Secrets Manager compatible record(s)"
|
36
|
+
|
37
|
+
if records.any?
|
38
|
+
records.each_with_index do |record, i|
|
39
|
+
puts "\n Record ##{i + 1}:"
|
40
|
+
puts " - Title: #{record.title}"
|
41
|
+
puts " - UID: #{record.uid}"
|
42
|
+
puts " - Type: #{record.type}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# 3. List folders
|
47
|
+
puts "\n3. LISTING FOLDERS"
|
48
|
+
folders = secrets_manager.get_folders
|
49
|
+
puts " Found #{folders.length} folder(s)"
|
50
|
+
|
51
|
+
if folders.any?
|
52
|
+
puts "\n Available folders:"
|
53
|
+
folders.first(5).each do |folder|
|
54
|
+
puts " - #{folder.name} (#{folder.uid})"
|
55
|
+
end
|
56
|
+
puts " ... and #{folders.length - 5} more" if folders.length > 5
|
57
|
+
end
|
58
|
+
|
59
|
+
# 4. Create a test record
|
60
|
+
puts "\n4. CREATING RECORDS"
|
61
|
+
target_folder = folders.find { |f| f.uid == 'khq76ez6vkTRj3MqUiEGRg' }
|
62
|
+
|
63
|
+
if target_folder
|
64
|
+
puts " Using folder: #{target_folder.name}"
|
65
|
+
|
66
|
+
test_record = {
|
67
|
+
'type' => 'login',
|
68
|
+
'title' => "Ruby SDK Demo #{Time.now.strftime('%Y-%m-%d %H:%M')}",
|
69
|
+
'fields' => [
|
70
|
+
{ 'type' => 'login', 'value' => ['demo@example.com'] },
|
71
|
+
{ 'type' => 'password', 'value' => ['DemoPass123!'] },
|
72
|
+
{ 'type' => 'url', 'value' => ['https://example.com'] },
|
73
|
+
{ 'type' => 'text', 'label' => 'Custom Field', 'value' => ['Custom Value'] }
|
74
|
+
],
|
75
|
+
'notes' => "Created by Ruby SDK demo at #{Time.now}",
|
76
|
+
'custom' => [
|
77
|
+
{ 'type' => 'text', 'label' => 'Environment', 'value' => ['Production'] },
|
78
|
+
{ 'type' => 'text', 'label' => 'API Key', 'value' => ['demo-api-key-123'] }
|
79
|
+
]
|
80
|
+
}
|
81
|
+
|
82
|
+
options = KeeperSecretsManager::Dto::CreateOptions.new
|
83
|
+
options.folder_uid = target_folder.uid
|
84
|
+
|
85
|
+
begin
|
86
|
+
record_uid = secrets_manager.create_secret(test_record, options)
|
87
|
+
puts " ✅ Successfully created record: #{record_uid}"
|
88
|
+
|
89
|
+
# 5. Notation examples
|
90
|
+
puts "\n5. KEEPER NOTATION"
|
91
|
+
|
92
|
+
# Try with existing records
|
93
|
+
if records.any?
|
94
|
+
first_record = records.first
|
95
|
+
puts " Testing with existing record: #{first_record.title}"
|
96
|
+
|
97
|
+
title = secrets_manager.get_notation("keeper://#{first_record.uid}/title")
|
98
|
+
puts " ✅ Title via notation: #{title}"
|
99
|
+
|
100
|
+
type = secrets_manager.get_notation("keeper://#{first_record.uid}/type")
|
101
|
+
puts " ✅ Type via notation: #{type}"
|
102
|
+
else
|
103
|
+
puts " ⚠️ No existing records to test notation with"
|
104
|
+
end
|
105
|
+
|
106
|
+
rescue => e
|
107
|
+
puts " ❌ Error creating record: #{e.message}"
|
108
|
+
end
|
109
|
+
else
|
110
|
+
puts " ❌ Target folder not found"
|
111
|
+
end
|
112
|
+
|
113
|
+
# 6. Dynamic field access
|
114
|
+
puts "\n6. DYNAMIC FIELD ACCESS (JavaScript-style)"
|
115
|
+
if records.any?
|
116
|
+
record = records.first
|
117
|
+
puts " Record: #{record.title}"
|
118
|
+
|
119
|
+
# Try common field accessors
|
120
|
+
if record.respond_to?(:login)
|
121
|
+
puts " ✅ record.login = #{record.login}"
|
122
|
+
end
|
123
|
+
|
124
|
+
if record.respond_to?(:password)
|
125
|
+
puts " ✅ record.password = [hidden]"
|
126
|
+
end
|
127
|
+
|
128
|
+
if record.respond_to?(:url)
|
129
|
+
puts " ✅ record.url = #{record.url}"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Custom fields
|
133
|
+
puts " ✅ Dynamic field access via method_missing"
|
134
|
+
else
|
135
|
+
puts " Creating example record to demonstrate..."
|
136
|
+
example = KeeperSecretsManager::Dto::KeeperRecord.new(
|
137
|
+
'title' => 'Example Record',
|
138
|
+
'fields' => [
|
139
|
+
{ 'type' => 'login', 'value' => ['user@example.com'] },
|
140
|
+
{ 'type' => 'password', 'value' => ['secret123'] }
|
141
|
+
]
|
142
|
+
)
|
143
|
+
|
144
|
+
puts " ✅ example.login = #{example.login}"
|
145
|
+
puts " ✅ example.password = [hidden]"
|
146
|
+
|
147
|
+
# Set new field using set_field method
|
148
|
+
example.set_field('api_key', 'new-api-key-456')
|
149
|
+
puts " ✅ example.get_field_value_single('api_key') = new-api-key-456 (dynamically added)"
|
150
|
+
end
|
151
|
+
|
152
|
+
# 7. Storage options
|
153
|
+
puts "\n7. STORAGE OPTIONS"
|
154
|
+
puts " ✅ InMemoryStorage - Currently in use"
|
155
|
+
puts " ✅ FileStorage - Save config to disk"
|
156
|
+
puts " ✅ EnvironmentStorage - Use environment variables"
|
157
|
+
|
158
|
+
# 8. Summary
|
159
|
+
puts "\n" + "=" * 60
|
160
|
+
puts "SUMMARY: Ruby SDK Features"
|
161
|
+
puts "=" * 60
|
162
|
+
puts "✅ Authentication & Encryption working correctly"
|
163
|
+
puts "✅ Read operations fully functional"
|
164
|
+
puts "✅ Folder listing from dedicated endpoint"
|
165
|
+
puts "✅ Record creation working"
|
166
|
+
puts "✅ Dynamic, JavaScript-style DTOs"
|
167
|
+
puts "✅ Keeper notation support"
|
168
|
+
puts "✅ Multiple storage backends"
|
169
|
+
puts "✅ Ruby 2.7+ compatible"
|
170
|
+
|
171
|
+
puts "\n📝 Notes:"
|
172
|
+
puts "- Client version currently using 'mr' prefix (temporary)"
|
173
|
+
puts "- Will change to 'mb' after registration with Keeper"
|
174
|
+
puts "- Some folders may show decryption errors (expected for certain folder types)"
|
175
|
+
|
176
|
+
puts "\n🎉 The Ruby SDK is ready for use!"
|
@@ -0,0 +1,176 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# First, we need to add the SDK to Ruby's load path
|
4
|
+
sdk_path = '/Users/mustinov/Source/secrets-manager/sdk/ruby/lib'
|
5
|
+
$LOAD_PATH.unshift(sdk_path) unless $LOAD_PATH.include?(sdk_path)
|
6
|
+
|
7
|
+
# Now we can require the SDK
|
8
|
+
require 'keeper_secrets_manager'
|
9
|
+
|
10
|
+
# Your token from Keeper
|
11
|
+
TOKEN = 'US:3cJU7J7Wea97u4BauFmT2Aeeib3jciyYnqAyNryDDvE'
|
12
|
+
|
13
|
+
# Initialize the SDK
|
14
|
+
puts "Initializing Keeper SDK..."
|
15
|
+
sm = KeeperSecretsManager.new(token: TOKEN)
|
16
|
+
|
17
|
+
# Alternative: Use in-memory storage to reuse credentials
|
18
|
+
# storage = KeeperSecretsManager::Storage::InMemoryStorage.new
|
19
|
+
# sm = KeeperSecretsManager.new(token: TOKEN, config: storage)
|
20
|
+
#
|
21
|
+
# After first use, you can connect without token:
|
22
|
+
# sm = KeeperSecretsManager.new(config: storage)
|
23
|
+
|
24
|
+
puts "\n=== Step 1: List All Secrets ==="
|
25
|
+
begin
|
26
|
+
secrets = sm.get_secrets()
|
27
|
+
puts "Found #{secrets.length} secrets:"
|
28
|
+
secrets.each do |secret|
|
29
|
+
puts " - #{secret.title} (UID: #{secret.uid})"
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
puts "Error: #{e.message}"
|
33
|
+
puts "Make sure to replace TOKEN with your actual Keeper token!"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
# Save a secret UID for later tests
|
38
|
+
if secrets.any?
|
39
|
+
test_secret_uid = secrets.first.uid
|
40
|
+
puts "\nUsing secret '#{secrets.first.title}' for testing"
|
41
|
+
end
|
42
|
+
|
43
|
+
puts "\n=== Step 2: Get Secret Details ==="
|
44
|
+
if test_secret_uid
|
45
|
+
secret = sm.get_secrets([test_secret_uid]).first
|
46
|
+
puts "Title: #{secret.title}"
|
47
|
+
puts "Type: #{secret.type}"
|
48
|
+
puts "Fields:"
|
49
|
+
secret.fields.each do |field|
|
50
|
+
puts " - #{field['type']}: #{field['value'].first rescue 'N/A'}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
puts "\n=== Step 3: Test Notation ==="
|
55
|
+
if test_secret_uid
|
56
|
+
# Test notation to get password
|
57
|
+
notation = "keeper://#{test_secret_uid}/field/password"
|
58
|
+
begin
|
59
|
+
password = sm.get_notation(notation)
|
60
|
+
puts "Password via notation: [HIDDEN]"
|
61
|
+
rescue => e
|
62
|
+
puts "No password field found"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "\n=== Step 4: List Folders ==="
|
67
|
+
folders = sm.get_folders()
|
68
|
+
puts "Found #{folders.length} folders:"
|
69
|
+
folders.each do |folder|
|
70
|
+
puts " - #{folder.name} (UID: #{folder.uid})"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Save folder UID for record creation
|
74
|
+
if folders.any?
|
75
|
+
test_folder_uid = folders.first.uid
|
76
|
+
puts "\nWill use folder '#{folders.first.name}' for new records"
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "\n=== Step 5: Create New Secret ==="
|
80
|
+
if test_folder_uid
|
81
|
+
new_record = KeeperSecretsManager::Dto::KeeperRecord.new(
|
82
|
+
title: "Test Record - #{Time.now.strftime('%Y-%m-%d %H:%M')}",
|
83
|
+
type: 'login',
|
84
|
+
fields: [
|
85
|
+
{ 'type' => 'login', 'value' => ['testuser@example.com'] },
|
86
|
+
{ 'type' => 'password', 'value' => ['MySecurePassword123!'] },
|
87
|
+
{ 'type' => 'url', 'value' => ['https://example.com'] }
|
88
|
+
],
|
89
|
+
notes: 'Created by Ruby SDK test'
|
90
|
+
)
|
91
|
+
|
92
|
+
options = KeeperSecretsManager::Dto::CreateOptions.new
|
93
|
+
options.folder_uid = test_folder_uid
|
94
|
+
|
95
|
+
begin
|
96
|
+
new_uid = sm.create_secret(new_record, options)
|
97
|
+
puts "Created new secret with UID: #{new_uid}"
|
98
|
+
created_uid = new_uid
|
99
|
+
rescue => e
|
100
|
+
puts "Could not create record: #{e.message}"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
puts "No folders found - skipping record creation"
|
104
|
+
end
|
105
|
+
|
106
|
+
puts "\n=== Step 6: Update Secret ==="
|
107
|
+
if defined?(created_uid) && created_uid
|
108
|
+
# Fetch the created record
|
109
|
+
record = sm.get_secrets([created_uid]).first
|
110
|
+
|
111
|
+
# Update password
|
112
|
+
record.set_field('password', 'UpdatedPassword456!')
|
113
|
+
record.notes = "Updated at #{Time.now}"
|
114
|
+
|
115
|
+
sm.update_secret(record)
|
116
|
+
puts "Updated secret successfully"
|
117
|
+
end
|
118
|
+
|
119
|
+
puts "\n=== Step 7: File Operations ==="
|
120
|
+
if defined?(created_uid) && created_uid
|
121
|
+
# Create a test file
|
122
|
+
test_content = "This is a test file created at #{Time.now}"
|
123
|
+
File.write('test_upload.txt', test_content)
|
124
|
+
|
125
|
+
# Upload file
|
126
|
+
file_data = File.read('test_upload.txt', mode: 'rb')
|
127
|
+
file_uid = sm.upload_file(created_uid, 'test_upload.txt', file_data)
|
128
|
+
puts "Uploaded file with UID: #{file_uid}"
|
129
|
+
|
130
|
+
# Download file
|
131
|
+
downloaded = sm.download_file(file_uid)
|
132
|
+
puts "Downloaded file: #{downloaded['name']} (#{downloaded['size']} bytes)"
|
133
|
+
|
134
|
+
# Save downloaded file
|
135
|
+
File.write('test_download.txt', downloaded['data'], mode: 'wb')
|
136
|
+
puts "Saved to test_download.txt"
|
137
|
+
|
138
|
+
# Cleanup
|
139
|
+
File.delete('test_upload.txt') if File.exist?('test_upload.txt')
|
140
|
+
File.delete('test_download.txt') if File.exist?('test_download.txt')
|
141
|
+
end
|
142
|
+
|
143
|
+
puts "\n=== Step 8: Test TOTP ==="
|
144
|
+
# Look for a record with TOTP
|
145
|
+
totp_found = false
|
146
|
+
secrets.each do |secret|
|
147
|
+
begin
|
148
|
+
totp_url = sm.get_notation("keeper://#{secret.uid}/field/oneTimeCode")
|
149
|
+
if totp_url && totp_url.start_with?('otpauth://')
|
150
|
+
puts "Found TOTP in '#{secret.title}'"
|
151
|
+
params = KeeperSecretsManager::TOTP.parse_url(totp_url)
|
152
|
+
code = KeeperSecretsManager::TOTP.generate_code(params['secret'])
|
153
|
+
puts "Current TOTP code: #{code}"
|
154
|
+
totp_found = true
|
155
|
+
break
|
156
|
+
end
|
157
|
+
rescue
|
158
|
+
# No TOTP in this record
|
159
|
+
end
|
160
|
+
end
|
161
|
+
puts "No TOTP fields found in any records" unless totp_found
|
162
|
+
|
163
|
+
puts "\n=== Step 9: Cleanup ==="
|
164
|
+
if defined?(created_uid) && created_uid
|
165
|
+
print "Delete test record? (y/n): "
|
166
|
+
response = gets.chomp.downcase
|
167
|
+
if response == 'y'
|
168
|
+
sm.delete_secret([created_uid])
|
169
|
+
puts "Deleted test record"
|
170
|
+
else
|
171
|
+
puts "Kept test record"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
puts "\n=== Testing Complete! ==="
|
176
|
+
puts "All basic operations tested successfully"
|
@@ -0,0 +1,162 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Add the Ruby SDK to the load path
|
4
|
+
$LOAD_PATH.unshift('/Users/mustinov/Source/secrets-manager/sdk/ruby/lib')
|
5
|
+
|
6
|
+
# Load the SDK
|
7
|
+
require 'keeper_secrets_manager'
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
# Enable debug logging
|
11
|
+
logger = Logger.new(STDOUT)
|
12
|
+
logger.level = Logger::DEBUG
|
13
|
+
|
14
|
+
begin
|
15
|
+
# Connect to Keeper
|
16
|
+
puts "Connecting to Keeper..."
|
17
|
+
# IMPORTANT: Replace with your own configuration
|
18
|
+
# You can get a one-time token from: https://keepersecurity.com/secrets-manager
|
19
|
+
#
|
20
|
+
# Option 1: Use one-time token
|
21
|
+
# token = 'US:YOUR_ONE_TIME_TOKEN_HERE'
|
22
|
+
# sm = KeeperSecretsManager.from_token(token)
|
23
|
+
#
|
24
|
+
# Option 2: Use base64 config from environment or file
|
25
|
+
config_base64 = ENV['KSM_CONFIG_BASE64'] || 'YOUR_BASE64_CONFIG_HERE'
|
26
|
+
storage_from_b64 = KeeperSecretsManager::Storage::InMemoryStorage.new(config_base64)
|
27
|
+
sm = KeeperSecretsManager.new(config: storage_from_b64, logger: logger)
|
28
|
+
|
29
|
+
# Alternative: Use in-memory storage to save credentials after first use
|
30
|
+
# storage = KeeperSecretsManager::Storage::InMemoryStorage.new
|
31
|
+
# sm = KeeperSecretsManager.new(token: TOKEN, config: storage)
|
32
|
+
#
|
33
|
+
# For subsequent connections (no token needed):
|
34
|
+
# sm = KeeperSecretsManager.new(config: storage)
|
35
|
+
|
36
|
+
# Get all secrets
|
37
|
+
puts "\nFetching secrets..."
|
38
|
+
response = sm.get_secrets(full_response: true)
|
39
|
+
secrets = response.records
|
40
|
+
|
41
|
+
# Display results
|
42
|
+
puts "\nFound #{secrets.length} secrets:"
|
43
|
+
puts "Found #{response.folders.length} folders:"
|
44
|
+
|
45
|
+
# Show folder structure
|
46
|
+
if response.folders.any?
|
47
|
+
puts "\nFolder structure:"
|
48
|
+
response.folders.each do |folder|
|
49
|
+
puts " - #{folder.name} (#{folder.uid})"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Show secrets with their folders and all fields
|
54
|
+
puts "\nShowing first 5 secrets with all fields:"
|
55
|
+
puts "=" * 80
|
56
|
+
secrets.first(500).each_with_index do |secret, i|
|
57
|
+
folder_info = secret.folder_uid && !secret.folder_uid.empty? ? " [in folder: #{secret.folder_uid}]" : " [in root]"
|
58
|
+
puts "\n#{i+1}. #{secret.title}#{folder_info}"
|
59
|
+
puts " Type: #{secret.type}"
|
60
|
+
puts " UID: #{secret.uid}"
|
61
|
+
|
62
|
+
# Show all fields
|
63
|
+
if secret.fields && secret.fields.any?
|
64
|
+
puts " Fields:"
|
65
|
+
secret.fields.each do |field|
|
66
|
+
field_type = field['type'] || 'unknown'
|
67
|
+
field_label = field['label'] || field_type
|
68
|
+
|
69
|
+
# Get the value(s)
|
70
|
+
values = field['value'] || []
|
71
|
+
values = [values] unless values.is_a?(Array)
|
72
|
+
|
73
|
+
# Format based on field type
|
74
|
+
case field_type
|
75
|
+
when 'password', 'secret', 'pinCode', 'securityQuestion'
|
76
|
+
# Hide sensitive data
|
77
|
+
display_values = values.map { |v| '********' }
|
78
|
+
when 'privateKey'
|
79
|
+
# Show first/last few characters of private keys
|
80
|
+
display_values = values.map do |v|
|
81
|
+
if v && v.length > 20
|
82
|
+
"#{v[0..10]}...#{v[-10..-1]}"
|
83
|
+
else
|
84
|
+
v
|
85
|
+
end
|
86
|
+
end
|
87
|
+
when 'fileRef'
|
88
|
+
# Show file reference info
|
89
|
+
display_values = values.map { |v| "File: #{v}" }
|
90
|
+
else
|
91
|
+
# Show full value for non-sensitive fields
|
92
|
+
display_values = values
|
93
|
+
end
|
94
|
+
|
95
|
+
# Print field info
|
96
|
+
if display_values.length == 1
|
97
|
+
puts " - #{field_label} (#{field_type}): #{display_values.first}"
|
98
|
+
elsif display_values.length > 1
|
99
|
+
puts " - #{field_label} (#{field_type}):"
|
100
|
+
display_values.each { |v| puts " • #{v}" }
|
101
|
+
else
|
102
|
+
puts " - #{field_label} (#{field_type}): [empty]"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
puts " No fields"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Show custom fields if any
|
110
|
+
if secret.custom && secret.custom.any?
|
111
|
+
puts " Custom fields:"
|
112
|
+
secret.custom.each do |custom_field|
|
113
|
+
puts " - #{custom_field['label']}: #{custom_field['value']}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Show files if any
|
118
|
+
if secret.files && secret.files.any?
|
119
|
+
puts " Files:"
|
120
|
+
secret.files.each do |file|
|
121
|
+
file_name = file['title'] || file['name'] || 'unnamed'
|
122
|
+
file_size = file['size'] || 0
|
123
|
+
file_uid = file['fileUid'] || file['uid']
|
124
|
+
puts " - #{file_name} (#{file_size} bytes) [UID: #{file_uid}]"
|
125
|
+
|
126
|
+
# Download the file if it's small (under 1MB for demo)
|
127
|
+
if file_size < 1_000_000 && file_uid
|
128
|
+
begin
|
129
|
+
puts " Downloading..."
|
130
|
+
downloaded_file = sm.download_file(file)
|
131
|
+
|
132
|
+
# Save to downloads folder
|
133
|
+
download_dir = "downloads"
|
134
|
+
Dir.mkdir(download_dir) unless Dir.exist?(download_dir)
|
135
|
+
|
136
|
+
safe_filename = File.basename(downloaded_file['name'] || file_name)
|
137
|
+
file_path = File.join(download_dir, safe_filename)
|
138
|
+
File.binwrite(file_path, downloaded_file['data'])
|
139
|
+
|
140
|
+
puts " ✅ Saved to: #{file_path}"
|
141
|
+
rescue => e
|
142
|
+
puts " ❌ Download failed: #{e.message}"
|
143
|
+
end
|
144
|
+
elsif file_size >= 1_000_000
|
145
|
+
puts " ⚠️ File too large for demo (>1MB)"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
puts "\n" + "=" * 80
|
152
|
+
puts "\nTotal: #{secrets.length} secrets (showing first #{[secrets.length, 500].min})"
|
153
|
+
puts "\nSuccess! The Ruby SDK is working correctly."
|
154
|
+
|
155
|
+
rescue => e
|
156
|
+
puts "\nError: #{e.message}"
|
157
|
+
puts "\nTroubleshooting:"
|
158
|
+
puts "1. Make sure you replaced TOKEN with your actual Keeper token"
|
159
|
+
puts "2. Token format should be: US:xxxxxxxxxxxxx (or EU:xxxxx for Europe)"
|
160
|
+
puts "3. Tokens are one-time use - get a new one if this fails"
|
161
|
+
puts "4. Check that the SDK path is correct: /Users/mustinov/Source/secrets-manager/sdk/ruby/lib"
|
162
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Add the Ruby SDK to the load path
|
4
|
+
$LOAD_PATH.unshift('/Users/mustinov/Source/secrets-manager/sdk/ruby/lib')
|
5
|
+
|
6
|
+
require 'keeper_secrets_manager'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
puts "=== Keeper Secrets Manager Storage Examples ==="
|
10
|
+
|
11
|
+
# Your one-time token (replace with a fresh one)
|
12
|
+
TOKEN = 'US:YOUR_ONE_TIME_TOKEN_HERE'
|
13
|
+
|
14
|
+
puts "\n1. One-time token (no storage) - Token used every time"
|
15
|
+
puts "=" * 50
|
16
|
+
begin
|
17
|
+
# This uses the token directly and doesn't save credentials
|
18
|
+
sm = KeeperSecretsManager.new(token: TOKEN)
|
19
|
+
secrets = sm.get_secrets()
|
20
|
+
puts "✓ Connected and found #{secrets.length} secrets"
|
21
|
+
rescue => e
|
22
|
+
puts "✗ Error: #{e.message}"
|
23
|
+
end
|
24
|
+
|
25
|
+
puts "\n2. In-Memory Storage - Credentials stored in memory"
|
26
|
+
puts "=" * 50
|
27
|
+
begin
|
28
|
+
# Create in-memory storage
|
29
|
+
storage = KeeperSecretsManager::Storage::InMemoryStorage.new
|
30
|
+
|
31
|
+
# First connection with token
|
32
|
+
puts "First connection (with token)..."
|
33
|
+
sm1 = KeeperSecretsManager.new(token: TOKEN, config: storage)
|
34
|
+
secrets1 = sm1.get_secrets()
|
35
|
+
puts "✓ Found #{secrets1.length} secrets"
|
36
|
+
|
37
|
+
# Get the config as JSON for later use
|
38
|
+
config_json = storage.to_json
|
39
|
+
puts "✓ Config saved to memory (length: #{config_json.length} chars)"
|
40
|
+
|
41
|
+
# Subsequent connections without token - just pass the config!
|
42
|
+
puts "\nSecond connection (no token needed)..."
|
43
|
+
new_storage = KeeperSecretsManager::Storage::InMemoryStorage.new(config_json)
|
44
|
+
sm2 = KeeperSecretsManager.new(config: new_storage)
|
45
|
+
secrets2 = sm2.get_secrets()
|
46
|
+
puts "✓ Found #{secrets2.length} secrets using stored credentials"
|
47
|
+
|
48
|
+
# Or use base64 encoded config
|
49
|
+
puts "\nThird connection (using base64 config)..."
|
50
|
+
config_base64 = Base64.strict_encode64(config_json)
|
51
|
+
storage_from_b64 = KeeperSecretsManager::Storage::InMemoryStorage.new(config_base64)
|
52
|
+
sm3 = KeeperSecretsManager.new(config: storage_from_b64)
|
53
|
+
secrets3 = sm3.get_secrets()
|
54
|
+
puts "✓ Found #{secrets3.length} secrets using base64 config"
|
55
|
+
|
56
|
+
rescue => e
|
57
|
+
puts "✗ Error: #{e.message}"
|
58
|
+
end
|
59
|
+
|
60
|
+
puts "\n3. File Storage - Credentials saved to disk"
|
61
|
+
puts "=" * 50
|
62
|
+
begin
|
63
|
+
# Create file storage
|
64
|
+
config_file = 'keeper_config.json'
|
65
|
+
file_storage = KeeperSecretsManager::Storage::FileStorage.new(config_file)
|
66
|
+
|
67
|
+
# First connection with token
|
68
|
+
puts "First connection (with token)..."
|
69
|
+
sm1 = KeeperSecretsManager.new(token: TOKEN, config: file_storage)
|
70
|
+
secrets1 = sm1.get_secrets()
|
71
|
+
puts "✓ Found #{secrets1.length} secrets"
|
72
|
+
puts "✓ Credentials saved to #{config_file}"
|
73
|
+
|
74
|
+
# Later, in a different script/session
|
75
|
+
puts "\nSimulating new session..."
|
76
|
+
file_storage2 = KeeperSecretsManager::Storage::FileStorage.new(config_file)
|
77
|
+
sm2 = KeeperSecretsManager.new(config: file_storage2)
|
78
|
+
secrets2 = sm2.get_secrets()
|
79
|
+
puts "✓ Found #{secrets2.length} secrets using saved credentials"
|
80
|
+
|
81
|
+
# Clean up
|
82
|
+
File.delete(config_file) if File.exist?(config_file)
|
83
|
+
puts "✓ Cleaned up config file"
|
84
|
+
|
85
|
+
rescue => e
|
86
|
+
puts "✗ Error: #{e.message}"
|
87
|
+
end
|
88
|
+
|
89
|
+
puts "\n4. Pre-populated Storage - Manual configuration"
|
90
|
+
puts "=" * 50
|
91
|
+
begin
|
92
|
+
# You can manually populate storage with known values
|
93
|
+
manual_storage = KeeperSecretsManager::Storage::InMemoryStorage.new
|
94
|
+
|
95
|
+
# These would come from your secure configuration
|
96
|
+
manual_storage.save_string('hostname', 'keepersecurity.com')
|
97
|
+
manual_storage.save_string('clientId', 'YOUR_CLIENT_ID')
|
98
|
+
manual_storage.save_bytes('appKey', Base64.decode64('YOUR_APP_KEY_BASE64'))
|
99
|
+
manual_storage.save_bytes('privateKey', Base64.decode64('YOUR_PRIVATE_KEY_BASE64'))
|
100
|
+
# ... other required keys
|
101
|
+
|
102
|
+
# Connect using pre-configured storage
|
103
|
+
# sm = KeeperSecretsManager.new(config: manual_storage)
|
104
|
+
puts "✓ Manual storage example (commented out - needs real credentials)"
|
105
|
+
|
106
|
+
rescue => e
|
107
|
+
puts "✗ Error: #{e.message}"
|
108
|
+
end
|
109
|
+
|
110
|
+
puts "\n5. Environment Variable Storage"
|
111
|
+
puts "=" * 50
|
112
|
+
puts "You can also load from environment variables:"
|
113
|
+
puts <<-EOF
|
114
|
+
# Set environment variable with base64 config
|
115
|
+
export KSM_CONFIG="eyJjbGllbnRJZCI6Ii4uLiJ9..."
|
116
|
+
|
117
|
+
# SDK will automatically use it
|
118
|
+
sm = KeeperSecretsManager.new()
|
119
|
+
EOF
|
120
|
+
|
121
|
+
puts "\nKey Points:"
|
122
|
+
puts "- One-time tokens can only be used once"
|
123
|
+
puts "- In-memory storage is lost when program exits"
|
124
|
+
puts "- File storage persists credentials between runs"
|
125
|
+
puts "- Always protect stored credentials appropriately"
|
126
|
+
puts "- Use file permissions to secure config files"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module KeeperSecretsManager
|
2
|
+
module ConfigKeys
|
3
|
+
# Configuration key constants (matching other SDKs)
|
4
|
+
KEY_URL = 'url'.freeze
|
5
|
+
KEY_CLIENT_ID = 'clientId'.freeze
|
6
|
+
KEY_CLIENT_KEY = 'clientKey'.freeze
|
7
|
+
KEY_HOSTNAME = 'hostname'.freeze
|
8
|
+
KEY_SERVER_PUBLIC_KEY_ID = 'serverPublicKeyId'.freeze
|
9
|
+
KEY_PRIVATE_KEY = 'privateKey'.freeze
|
10
|
+
KEY_APP_KEY = 'appKey'.freeze
|
11
|
+
KEY_OWNER_PUBLIC_KEY = 'appOwnerPublicKey'.freeze
|
12
|
+
KEY_APP_UID = 'appUid'.freeze
|
13
|
+
|
14
|
+
# All valid keys
|
15
|
+
ALL_KEYS = [
|
16
|
+
KEY_URL,
|
17
|
+
KEY_CLIENT_ID,
|
18
|
+
KEY_CLIENT_KEY,
|
19
|
+
KEY_HOSTNAME,
|
20
|
+
KEY_SERVER_PUBLIC_KEY_ID,
|
21
|
+
KEY_PRIVATE_KEY,
|
22
|
+
KEY_APP_KEY,
|
23
|
+
KEY_OWNER_PUBLIC_KEY,
|
24
|
+
KEY_APP_UID
|
25
|
+
].freeze
|
26
|
+
end
|
27
|
+
end
|