keeper_secrets_manager 17.2.1
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 +139 -0
- data/Gemfile +16 -0
- data/LICENSE +21 -0
- data/README.md +113 -0
- data/Rakefile +30 -0
- data/bin/console +47 -0
- data/keeper_secrets_manager.gemspec +36 -0
- data/lib/keeper_secrets_manager/cache.rb +139 -0
- data/lib/keeper_secrets_manager/config_keys.rb +29 -0
- data/lib/keeper_secrets_manager/core.rb +1781 -0
- data/lib/keeper_secrets_manager/crypto.rb +333 -0
- data/lib/keeper_secrets_manager/dto/payload.rb +153 -0
- data/lib/keeper_secrets_manager/dto.rb +557 -0
- data/lib/keeper_secrets_manager/errors.rb +90 -0
- data/lib/keeper_secrets_manager/field_types.rb +152 -0
- data/lib/keeper_secrets_manager/folder_manager.rb +110 -0
- data/lib/keeper_secrets_manager/keeper_globals.rb +53 -0
- data/lib/keeper_secrets_manager/notation.rb +463 -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 +263 -0
- data/lib/keeper_secrets_manager/version.rb +3 -0
- data/lib/keeper_secrets_manager.rb +46 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 18b1f0bf853bb2239551325dbe053cf29d7ba68d50dadba1a502eefcc175a044
|
|
4
|
+
data.tar.gz: 90a4d6f1bdc8dd99c9905d2a30717cef9bde0421c9778e49c1cfe2e3bdbdb59c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2038821bbaa4a18b683c45415a6a5f5357ac8a8f6cc9eb677b3ad1130c6a34095f63264704af55a18be8e988d8b79eac716e5ca492694c3b2197417ab38fb1c8
|
|
7
|
+
data.tar.gz: cf9d6aa23e02f7e0878023216c07c36f38fe8fe4ad9c69f2ffe9c8c0748f9a9aca6de03d05a51def7dc4c407dda9ec2e4448481552d756f08f853fd02ec97127
|
data/.rspec
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.3.8
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [17.2.1]
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
- **KSM-1193**: `create_secret` now encrypts the record key to the application owner's public key before sending it, matching the other SDKs. Previously the record key was placed in the create payload without that wrap, while the same request carried the record data encrypted under it. All earlier published versions are affected: 17.0.3, 17.0.4, 17.1.0 and 17.2.0; 17.0.0-17.0.2 were never published to RubyGems. A record key cannot be changed after creation, so upgrading protects newly created records only; records created by an earlier version must be re-created to place them under a new key. `create_secret` now also raises an error when the application configuration has no owner public key, instead of continuing without the encryption step.
|
|
7
|
+
|
|
8
|
+
## [17.2.0]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **KSM-1095**: `update_secret` now calls `complete_transaction` after staging the update, so changes are committed to the server rather than remaining in a staged state indefinitely; works for both `KeeperRecord` objects and plain hash inputs
|
|
12
|
+
- **KSM-1094**: `update_secret` no longer raises `NameError: undefined local variable 'record_uid'` when called with a `KeeperRecord` object; the revision refresh now correctly references `record.uid`
|
|
13
|
+
- **KSM-1096**: `download_thumbnail` no longer raises `NoMethodError` when passed a `KeeperFile` object; `KeeperFile` now exposes a `file_key` attribute and the method dispatches on type before attempting hash access
|
|
14
|
+
- **KSM-824**: `to_h` now always includes `custom` in the V3 API payload, even when the array is empty, matching Commander and Vault behavior
|
|
15
|
+
- **KSM-906**: Added IL5 region mapping (`IL5` → `il5.keepersecurity.us`) to `KEEPER_SERVERS`
|
|
16
|
+
- **KSM-987**: `url_safe_str_to_bytes` and `base64_to_bytes` in `Utils` now raise `Error` when passed `nil`; all Base64 decoding in `core.rb` routes through `Utils`
|
|
17
|
+
- **KSM-1090**: `base64` and `logger` are now declared as explicit runtime dependencies in the gemspec; Ruby 4.0 removed these from the default standard library, so any clean install on Ruby 4.0+ previously raised `LoadError` on `require 'keeper_secrets_manager'`. Also removed a dead `require 'ostruct'` from `dto.rb`.
|
|
18
|
+
- **KSM-1070**: Fixed silent AES-CBC fallback in `decrypt_aes_gcm`: an AES-GCM authentication-tag failure now raises `DecryptionError` immediately rather than retrying decryption as AES-CBC; tampered or wrong-key ciphertext previously could produce output without any error.
|
|
19
|
+
- **KSM-685**: `CreateOptions.subfolder_uid` parameter is now correctly sent to the API when creating records
|
|
20
|
+
- **KSM-686**: Disaster recovery caching with `CachingPostFunction` is now implemented to match other SDKs; API response caching works for both `get_secret` and `get_folders` endpoints, and unused `@cache` and `@cache_expiry` instance variables are removed from `SecretsManager`
|
|
21
|
+
- **KSM-696**: Config storage file is now written with `0600` permissions (owner read/write only), preventing other local users from reading stored credentials
|
|
22
|
+
- **KSM-734**: Fixed notation lookup to deduplicate record shortcuts via `uniq { |r| r.uid }` before the ambiguity check; preserves genuine duplicate-title detection while preventing false ambiguity errors when both an original record and its shortcut are in scope
|
|
23
|
+
- **KSM-1088**: `delete_secret` and `delete_folder` now log an error for each record/folder whose `responseCode` is not `"ok"`, surfacing partial-failure details that were previously silently discarded
|
|
24
|
+
- **KSM-1091**: Invalid `proxy_url` now raises `ArgumentError` at initialization with a descriptive message; a URL with no host previously silently bypassed the proxy, and a fully malformed URL raised `NetworkError` at request time instead
|
|
25
|
+
- **KSM-1098**: `get_value`, `get_totp_code`, and `download_file` in `notation_enhancements` no longer raise `NoMethodError` when passed `nil`, an empty string, or a non-String value; `parse` itself now checks `is_a?(String)` before `empty?` to avoid `NoMethodError` for non-String inputs. `notation_enhancements` is now properly required from the main entry point.
|
|
26
|
+
- Fixed example files to use correct SDK APIs: `09_totp.rb` corrects `Totp` to `TOTP` and `generate()` to `generate_code()`; `01_quick_start.rb` uses the `secret.login` dynamic getter instead of hash access; `10_custom_caching.rb` uses `Utils.bytes_to_base64`
|
|
27
|
+
- Fixed badly anchored regular expression in `test/integration/test_totp.rb` that could cause false positives in test validation
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **KSM-906**: `SecretsManager` now accepts the 4-part IL5 one-time token `IL5:clientKey:serverPublicKeyId:serverPublicKey`, registering the supplied EC P-256 server public key (keyId 20, outside the built-in 1–18 table) for ECIES transmission-key wrapping, and persists it as `serverPublicKey` in config so it survives restarts. Adds `server_public_key` / `server_public_key_id` constructor options (precedence: programmatic > token > config), a new `ConfigKeys::KEY_SERVER_PUBLIC_KEY`, malformed-OTT validation, and an actionable error when the backend rejects a configured custom key. Non-IL5 tokens are unchanged.
|
|
31
|
+
- **KSM-1013**: `KeeperRecordLink` (via `KeeperRecord#get_links`) wraps each raw `links` entry with never-raising typed accessors: permission booleans with an `allowedSettings` fallback (top-level wins), AES-256-GCM `get_decrypted_data`/`get_link_data`, and `meta`/`ai_settings`/`jit_settings` settings accessors. Adds a `request_links:` keyword to `get_secrets`. Purely additive; the raw `record.links` list is unchanged.
|
|
32
|
+
- **KSM-883**: On HTTP 403 `{"error":"throttled"}`, `post_query` now retries up to 5 times with exponentially increasing delays (11s, 22s, 44s, 88s, 176s) plus 0–25% jitter (one-sided), honoring `retry_after` from the response when present, and raises `ThrottledError` once retries are exhausted. Replaces the previous fixed 60-second sleep with no backoff, jitter, or retry cap.
|
|
33
|
+
- **KSM-1102**: Added `save(record, transaction_type: nil, links_to_remove: nil)` and `save_with_options(record, update_options)`: non-finalizing update aliases that use the stored record key without re-fetching and do not call `complete_transaction`
|
|
34
|
+
- **KSM-1101**: Added `inflate_field_value(uids, replace_fields)` and `get_inflate_ref_types(field_type)` for field-reference resolution; `addressRef` resolves to address fields, `cardRef` resolves to paymentCard/text/pinCode/address fields with recursive inflate
|
|
35
|
+
- **KSM-1100**: Added `get_notation_results` and `try_get_notation_results`: list-returning notation lookup that always returns `Array[String]`, returns all field values by default (no first-element shortcut), and JSON-serializes complex values
|
|
36
|
+
- **KSM-1099**: Added `create_secret_with_options(create_options, record_data, folders: nil)`: explicit options-based creation that accepts a pre-fetched folders list to avoid an extra `get_folders` network call; `create_secret` is unchanged
|
|
37
|
+
- **KSM-743**: Added transmission public key #18 for Gov Cloud Dev environment support
|
|
38
|
+
- **KSM-687**: Added DTO fields and PAM transaction support for complete SDK parity:
|
|
39
|
+
- `links` field to `KeeperRecord` for linked records support
|
|
40
|
+
- `is_editable` field to `KeeperRecord` to check edit permissions
|
|
41
|
+
- `inner_folder_uid` field to `KeeperRecord` for folder location tracking
|
|
42
|
+
- `thumbnail_url` and `last_modified` fields to `KeeperFile`
|
|
43
|
+
- `UpdateOptions` class with `transaction_type` and `links_to_remove` support
|
|
44
|
+
- `update_secret_with_options` method to support removing file links
|
|
45
|
+
- `request_links` option to `QueryOptions` for fetching linked records
|
|
46
|
+
- `download_thumbnail` method for downloading file thumbnails
|
|
47
|
+
- `expires_on` field to `SecretsManagerResponse`
|
|
48
|
+
- `complete_transaction(record_uid, rollback: false)` method for PAM rotation workflows
|
|
49
|
+
- `CompleteTransactionPayload` DTO class for transaction completion
|
|
50
|
+
- **KSM-692**: HTTP proxy support for enterprise environments:
|
|
51
|
+
- `proxy_url` initialization parameter for explicit proxy configuration
|
|
52
|
+
- `HTTPS_PROXY` / `https_proxy` environment variable support (automatic detection)
|
|
53
|
+
- Authenticated proxy support (username:password in URL)
|
|
54
|
+
- Proxy applies to all HTTP operations (API calls, file downloads, file uploads)
|
|
55
|
+
- **KSM-694**: Convenience methods for improved developer experience:
|
|
56
|
+
- `upload_file_from_path(owner_record_uid, file_path, file_title: nil)`: upload files directly from disk
|
|
57
|
+
- `try_get_notation(notation_uri)`: error-safe notation access (returns empty array on error)
|
|
58
|
+
- `KeeperSecretsManager.from_config(config_base64, options = {})`: convenience method for initializing from a base64 config string; complements `from_token()` and `from_file()` and provides parity with the .NET SDK's `GetVaultConfigFromConfigString()` pattern
|
|
59
|
+
- `KeeperSecretsManager::CachingPostFunction`: built-in disaster recovery caching
|
|
60
|
+
- `KeeperSecretsManager::Cache`: file-based cache management (save, load, clear); location configurable via `KSM_CACHE_DIR` environment variable
|
|
61
|
+
- Development console script (`bin/console`) for interactive SDK exploration using Pry REPL
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
- Documentation: Added Ruby SDK to root repository SDK comparison table
|
|
65
|
+
- **Test Coverage:**
|
|
66
|
+
- Added 5 new integration test files (test_pam_rotation.rb, test_proxy.rb, test_pam_linked_records.rb, test_caching.rb)
|
|
67
|
+
- Added 17 unit tests for caching functionality and 27 for new features (CompleteTransactionPayload, QueryOptions, proxy configuration, convenience methods)
|
|
68
|
+
- Enhanced test_file_operations.rb with thumbnail download and file link removal tests
|
|
69
|
+
- **Mock Infrastructure:** Implemented proper AES-256-GCM encryption in `mock_helper.rb`:
|
|
70
|
+
- Records now use proper AES-GCM encryption (was Base64 only)
|
|
71
|
+
- Folders use correct AES-CBC encryption for data
|
|
72
|
+
- Added transmission key encryption/decryption
|
|
73
|
+
- Added mock endpoints for transaction completion (finalize_secret_update, rollback_secret_update)
|
|
74
|
+
- Enabled complete offline testing without config.base64
|
|
75
|
+
- **Example Files:**
|
|
76
|
+
- Added `11_pam_linked_records.rb`: PAM resources with linked credentials and transaction workflow
|
|
77
|
+
- Added `12_proxy_usage.rb`: HTTP proxy configuration examples
|
|
78
|
+
- Updated `06_files.rb`: added `upload_file_from_path` convenience method example
|
|
79
|
+
- Updated `08_notation.rb`: added `try_get_notation` error-safe notation example
|
|
80
|
+
- Removed emojis from all example files for professional appearance
|
|
81
|
+
- **Dependencies:** Added base32 gem to test dependencies for TOTP support
|
|
82
|
+
|
|
83
|
+
## [17.1.0]
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
- **BREAKING**: Minimum Ruby version increased to 3.1.0 (from 2.6.0)
|
|
87
|
+
- Users on older Ruby versions should upgrade or pin to keeper_secrets_manager <= 17.0.4
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
- ECC key generation now correctly returns 32-byte raw private keys (was returning 121-byte DER format)
|
|
91
|
+
- Client version now dynamically uses VERSION constant instead of hardcoded value
|
|
92
|
+
- Fixed Tests
|
|
93
|
+
- `update_secret` now correctly encrypts record data before sending to server
|
|
94
|
+
- `update_secret` now calls `finalize_secret_update` endpoint to persist changes
|
|
95
|
+
- Local record's revision number is automatically refreshed after successful update
|
|
96
|
+
- Consecutive updates on the same record object now work without manual refetching
|
|
97
|
+
- `download_file` now properly respects SSL certificate verification settings and disables CRL checking
|
|
98
|
+
- `upload_file` now uses correct `add_file` endpoint and includes required `ownerRecordRevision` in payload
|
|
99
|
+
- `create_folder` now properly encrypts folder key with AES-CBC and sets correct parent_uid (nil for root-level folders)
|
|
100
|
+
- Fixed AES-CBC encryption to not double-pad data (OpenSSL handles padding automatically)
|
|
101
|
+
|
|
102
|
+
## [17.0.4]
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
- Maintenance release with internal improvements
|
|
106
|
+
|
|
107
|
+
## [17.0.3]
|
|
108
|
+
|
|
109
|
+
### Changed
|
|
110
|
+
- Cleaned up directory structure
|
|
111
|
+
- Removed development and debug files from distribution
|
|
112
|
+
|
|
113
|
+
## [17.0.2]
|
|
114
|
+
|
|
115
|
+
### Security
|
|
116
|
+
- Updated all examples to use environment variables or placeholders
|
|
117
|
+
|
|
118
|
+
## [17.0.1]
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
- Added missing files to gem package (folder_manager, notation_enhancements, totp)
|
|
122
|
+
|
|
123
|
+
## [17.0.0]
|
|
124
|
+
|
|
125
|
+
### Added
|
|
126
|
+
- Initial release of Keeper Secrets Manager Ruby SDK
|
|
127
|
+
- Ruby 2.6+ compatibility
|
|
128
|
+
|
|
129
|
+
### Security
|
|
130
|
+
- Zero-knowledge encryption using AES-GCM
|
|
131
|
+
- Secure key management
|
|
132
|
+
- SSL certificate verification
|
|
133
|
+
|
|
134
|
+
### Notes
|
|
135
|
+
- Version 17.0.0 to align with other Keeper SDKs
|
|
136
|
+
- No runtime dependencies (base32 is optional)
|
|
137
|
+
|
|
138
|
+
[17.2.1]: https://github.com/Keeper-Security/secrets-manager/compare/ruby-sdk-v17.2.0...ruby-sdk-v17.2.1
|
|
139
|
+
[17.2.0]: https://github.com/Keeper-Security/secrets-manager/compare/ruby-sdk-v17.1.0...ruby-sdk-v17.2.0
|
data/Gemfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
# Specify gem dependencies in keeper_secrets_manager.gemspec
|
|
4
|
+
gemspec
|
|
5
|
+
|
|
6
|
+
group :development, :test do
|
|
7
|
+
gem 'base32', '~> 0.3' # Required for TOTP support in tests
|
|
8
|
+
gem 'dotenv', '~> 2.8'
|
|
9
|
+
gem 'pry', '~> 0.14'
|
|
10
|
+
gem 'rake', '~> 13.0'
|
|
11
|
+
gem 'rspec', '~> 3.12'
|
|
12
|
+
gem 'rubocop', '~> 1.12.0'
|
|
13
|
+
gem 'simplecov', '~> 0.22'
|
|
14
|
+
gem 'webmock', '~> 3.18'
|
|
15
|
+
gem 'yard', '~> 0.9'
|
|
16
|
+
end
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Keeper Security, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
## Keeper Secrets Manager Ruby SDK
|
|
2
|
+
|
|
3
|
+
For more information see our official documentation page https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/ruby-sdk
|
|
4
|
+
|
|
5
|
+
# Change Log
|
|
6
|
+
|
|
7
|
+
## 17.2.1
|
|
8
|
+
- KSM-1193 - Security fix: `create_secret` now encrypts the record key to the application owner's public key before sending it, matching the other SDKs. Earlier versions placed the record key in the create payload without that wrap, while the same request carried the record data encrypted under it. All previously published versions are affected (17.0.3, 17.0.4, 17.1.0, 17.2.0). A record key cannot be changed after creation, so upgrading protects newly created records only; records created by an earlier version must be re-created to place them under a new key. `create_secret` now raises an error if the application configuration has no `appOwnerPublicKey`. This key is only persisted during binding, so an already-bound configuration cannot acquire it after the fact; re-bind with a fresh one-time token, or set the `KSM_APPOWNERPUBLICKEY` environment variable when using the read-only `EnvironmentStorage`.
|
|
9
|
+
|
|
10
|
+
## 17.2.0
|
|
11
|
+
- KSM-685 - Fixed `CreateOptions.subfolder_uid` parameter API transmission
|
|
12
|
+
- KSM-686 - Implemented disaster recovery caching with `CachingPostFunction`
|
|
13
|
+
- KSM-687 - Added missing DTO fields for complete SDK parity (links, is_editable, inner_folder_uid, thumbnail_url, last_modified, expires_on)
|
|
14
|
+
- KSM-1013 - Added typed linked-credential accessors (`KeeperRecordLink` via `record.get_links`) and a `request_links:` option on `get_secrets`
|
|
15
|
+
- KSM-692 - Added HTTP proxy support for enterprise environments
|
|
16
|
+
- KSM-694 - Added convenience methods (`upload_file_from_path`, `try_get_notation`)
|
|
17
|
+
- KSM-696 - Fixed file permissions for Ruby SDK config files
|
|
18
|
+
- KSM-697 - Comprehensive unit test coverage improvements (+358 tests, 63.3% coverage)
|
|
19
|
+
- KSM-734 - Fixed notation lookup to handle duplicate UIDs from record shortcuts
|
|
20
|
+
- KSM-743 - Added transmission public key #18 for Gov Cloud Dev environment support
|
|
21
|
+
- Added `from_config()` convenience method for base64 config initialization
|
|
22
|
+
- Added `update_secret_with_options()` method for removing file links
|
|
23
|
+
- Added `download_thumbnail()` method for file thumbnails
|
|
24
|
+
- Added development console (`bin/console`) for interactive SDK exploration
|
|
25
|
+
- Fixed example files to use correct SDK APIs
|
|
26
|
+
- Improved mock infrastructure with proper AES-256-GCM encryption
|
|
27
|
+
|
|
28
|
+
## 17.1.0
|
|
29
|
+
- **BREAKING**: Minimum Ruby version increased to 3.1.0 (from 2.6.0)
|
|
30
|
+
- Fixed ECC key generation to return 32-byte raw private keys
|
|
31
|
+
- Fixed `update_secret` to correctly encrypt and persist changes
|
|
32
|
+
- Fixed `download_file` SSL certificate verification
|
|
33
|
+
- Fixed `upload_file` to use correct endpoint
|
|
34
|
+
- Fixed `create_folder` encryption and parent_uid handling
|
|
35
|
+
|
|
36
|
+
For full version history, see [CHANGELOG.md](CHANGELOG.md)
|
|
37
|
+
|
|
38
|
+
# Quick Start
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
gem install keeper_secrets_manager
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Basic Usage
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require 'keeper_secrets_manager'
|
|
50
|
+
|
|
51
|
+
# Initialize from config file
|
|
52
|
+
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')
|
|
53
|
+
|
|
54
|
+
# Get all secrets
|
|
55
|
+
records = secrets_manager.get_secrets
|
|
56
|
+
|
|
57
|
+
# Access secret fields
|
|
58
|
+
record = records.first
|
|
59
|
+
puts "Password: #{record.password}"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Linked Credentials (PAM)
|
|
63
|
+
|
|
64
|
+
PAM records can carry linked credentials. Request them with `request_links: true`, then use the typed `KeeperRecordLink` accessors returned by `record.get_links` (the raw entries remain available on `record.links`):
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
records = secrets_manager.get_secrets(request_links: true)
|
|
68
|
+
|
|
69
|
+
records.each do |record|
|
|
70
|
+
record.get_links.each do |link|
|
|
71
|
+
puts "-> #{link.record_uid} (path: #{link.path.inspect})"
|
|
72
|
+
|
|
73
|
+
# Permission booleans read allowedSettings when nested (e.g. on "meta" links)
|
|
74
|
+
puts " rotation allowed: #{link.allows_rotation?}"
|
|
75
|
+
puts " admin user: #{link.admin_user?}"
|
|
76
|
+
|
|
77
|
+
# Encrypted ai_settings / jit_settings decrypt with the owning record's key
|
|
78
|
+
ai = link.get_ai_settings_data(record.record_key)
|
|
79
|
+
puts " ai settings: #{ai.inspect}" if ai
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Accessors never raise — decode or decryption failures return `nil`/`false`.
|
|
85
|
+
|
|
86
|
+
## Proxy Support
|
|
87
|
+
|
|
88
|
+
For enterprise environments behind HTTP proxies:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# Method 1: Explicit proxy_url parameter
|
|
92
|
+
secrets_manager = KeeperSecretsManager.from_file(
|
|
93
|
+
'keeper_config.json',
|
|
94
|
+
proxy_url: 'http://proxy.company.com:8080'
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Method 2: Authenticated proxy
|
|
98
|
+
secrets_manager = KeeperSecretsManager.from_file(
|
|
99
|
+
'keeper_config.json',
|
|
100
|
+
proxy_url: 'http://username:password@proxy.company.com:8080'
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Method 3: HTTPS_PROXY environment variable (recommended)
|
|
104
|
+
# export HTTPS_PROXY=http://proxy.company.com:8080
|
|
105
|
+
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')
|
|
106
|
+
# Proxy auto-detected from environment
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See `examples/ruby/12_proxy_usage.rb` for complete examples.
|
|
110
|
+
|
|
111
|
+
# Documentation
|
|
112
|
+
|
|
113
|
+
For complete documentation, see: https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/ruby-sdk
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
2
|
+
require 'rspec/core/rake_task'
|
|
3
|
+
|
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
5
|
+
|
|
6
|
+
task default: :spec
|
|
7
|
+
|
|
8
|
+
desc 'Run unit tests only'
|
|
9
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
|
10
|
+
t.pattern = 'spec/**/unit/*_spec.rb'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc 'Run integration tests only'
|
|
14
|
+
RSpec::Core::RakeTask.new(:integration) do |t|
|
|
15
|
+
t.pattern = 'spec/**/integration/*_spec.rb'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc 'Run all tests with coverage'
|
|
19
|
+
task :coverage do
|
|
20
|
+
ENV['COVERAGE'] = 'true'
|
|
21
|
+
Rake::Task['spec'].invoke
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'Run RuboCop'
|
|
25
|
+
task :rubocop do
|
|
26
|
+
sh 'bundle exec rubocop'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc 'Run all quality checks'
|
|
30
|
+
task ci: %i[rubocop spec]
|
data/bin/console
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Interactive console for Keeper Secrets Manager Ruby SDK development
|
|
5
|
+
|
|
6
|
+
require 'bundler/setup'
|
|
7
|
+
require 'pry'
|
|
8
|
+
|
|
9
|
+
# Load the SDK from local source
|
|
10
|
+
require_relative '../lib/keeper_secrets_manager'
|
|
11
|
+
|
|
12
|
+
# Load environment variables if .env exists
|
|
13
|
+
begin
|
|
14
|
+
require 'dotenv/load'
|
|
15
|
+
puts "✓ Loaded environment variables from .env"
|
|
16
|
+
rescue LoadError
|
|
17
|
+
# dotenv not available, skip
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Helper message
|
|
21
|
+
puts "Keeper Secrets Manager Ruby SDK Console"
|
|
22
|
+
puts "=" * 60
|
|
23
|
+
puts "SDK loaded from: #{File.expand_path('../lib/keeper_secrets_manager.rb', __dir__)}"
|
|
24
|
+
puts "Version: #{KeeperSecretsManager::VERSION}"
|
|
25
|
+
puts
|
|
26
|
+
|
|
27
|
+
if ENV['KSM_CONFIG']
|
|
28
|
+
puts "Quick start:"
|
|
29
|
+
puts " storage = KeeperSecretsManager::Storage::InMemoryStorage.new(ENV['KSM_CONFIG'])"
|
|
30
|
+
puts " sm = KeeperSecretsManager.new(config: storage)"
|
|
31
|
+
puts " secrets = sm.get_secrets"
|
|
32
|
+
puts
|
|
33
|
+
puts "With caching:"
|
|
34
|
+
puts " sm = KeeperSecretsManager.new(config: storage, custom_post_function: KeeperSecretsManager::CachingPostFunction)"
|
|
35
|
+
puts
|
|
36
|
+
else
|
|
37
|
+
puts "No KSM_CONFIG found in environment"
|
|
38
|
+
puts "Set it in .env or export it before running"
|
|
39
|
+
puts
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts "Type 'ls KeeperSecretsManager' to explore the SDK"
|
|
43
|
+
puts "=" * 60
|
|
44
|
+
puts
|
|
45
|
+
|
|
46
|
+
# Start Pry REPL
|
|
47
|
+
Pry.start
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require_relative 'lib/keeper_secrets_manager/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'keeper_secrets_manager'
|
|
5
|
+
spec.version = KeeperSecretsManager::VERSION
|
|
6
|
+
spec.authors = ['Keeper Security']
|
|
7
|
+
spec.email = ['sm@keepersecurity.com']
|
|
8
|
+
|
|
9
|
+
spec.summary = 'Keeper Secrets Manager SDK for Ruby'
|
|
10
|
+
spec.description = 'Ruby SDK for Keeper Secrets Manager - A zero-knowledge platform for managing and protecting infrastructure secrets'
|
|
11
|
+
spec.homepage = 'https://github.com/Keeper-Security/secrets-manager'
|
|
12
|
+
spec.license = 'MIT'
|
|
13
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
14
|
+
|
|
15
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Keeper-Security/secrets-manager'
|
|
18
|
+
spec.metadata['changelog_uri'] = 'https://github.com/Keeper-Security/secrets-manager/blob/master/sdk/ruby/CHANGELOG.md'
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
23
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
spec.bindir = 'exe'
|
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
|
|
30
|
+
# base64 and logger were default gems through Ruby 3.x; Ruby 4.0 unbundled them.
|
|
31
|
+
# Both are maintained by the Ruby core team — declaring them makes the implicit explicit.
|
|
32
|
+
spec.add_runtime_dependency 'base64'
|
|
33
|
+
spec.add_runtime_dependency 'logger'
|
|
34
|
+
|
|
35
|
+
# All development dependencies are in Gemfile
|
|
36
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module KeeperSecretsManager
|
|
4
|
+
# File-based caching for disaster recovery
|
|
5
|
+
# Stores encrypted API responses to allow offline access when network is unavailable
|
|
6
|
+
class Cache
|
|
7
|
+
# Default cache file location - can be overridden with KSM_CACHE_DIR environment variable
|
|
8
|
+
def self.cache_file_path
|
|
9
|
+
cache_dir = ENV['KSM_CACHE_DIR'] || '.'
|
|
10
|
+
File.join(cache_dir, 'ksm_cache.bin')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Save encrypted cache data (transmission key + encrypted response)
|
|
14
|
+
def self.save_cache(data)
|
|
15
|
+
File.open(cache_file_path, 'wb') do |file|
|
|
16
|
+
file.write(data)
|
|
17
|
+
end
|
|
18
|
+
rescue StandardError => e
|
|
19
|
+
# Silently fail on cache write errors (don't break the app)
|
|
20
|
+
warn "Failed to write cache: #{e.message}" if ENV['KSM_DEBUG']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Load encrypted cache data
|
|
24
|
+
def self.get_cached_data
|
|
25
|
+
return nil unless File.exist?(cache_file_path)
|
|
26
|
+
|
|
27
|
+
File.open(cache_file_path, 'rb', &:read)
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
# Silently fail on cache read errors
|
|
30
|
+
warn "Failed to read cache: #{e.message}" if ENV['KSM_DEBUG']
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Remove cache file
|
|
35
|
+
def self.clear_cache
|
|
36
|
+
File.delete(cache_file_path) if File.exist?(cache_file_path)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
warn "Failed to delete cache: #{e.message}" if ENV['KSM_DEBUG']
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if cache file exists
|
|
42
|
+
def self.cache_exists?
|
|
43
|
+
File.exist?(cache_file_path)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Caching post function for disaster recovery
|
|
48
|
+
# Wraps the normal post_function to save responses and fall back to cache on network failure
|
|
49
|
+
# Usage: KeeperSecretsManager.new(config: storage, custom_post_function: KeeperSecretsManager::CachingPostFunction)
|
|
50
|
+
module CachingPostFunction
|
|
51
|
+
# Post function that caches successful responses and falls back to cache on failure
|
|
52
|
+
# This matches the pattern used in Python, JavaScript, Java, and .NET SDKs
|
|
53
|
+
#
|
|
54
|
+
# @param url [String] The API endpoint URL
|
|
55
|
+
# @param transmission_key [Dto::TransmissionKey] The transmission key
|
|
56
|
+
# @param encrypted_payload [Dto::EncryptedPayload] The encrypted payload with signature
|
|
57
|
+
# @param verify_ssl_certs [Boolean] Whether to verify SSL certificates
|
|
58
|
+
# @return [Dto::KSMHttpResponse] Response object
|
|
59
|
+
def self.call(url, transmission_key, encrypted_payload, verify_ssl_certs = true)
|
|
60
|
+
# Try network request first
|
|
61
|
+
begin
|
|
62
|
+
# Call the static post_function
|
|
63
|
+
response = make_http_request(url, transmission_key, encrypted_payload, verify_ssl_certs)
|
|
64
|
+
|
|
65
|
+
# On success, save to cache (transmission key + encrypted response body)
|
|
66
|
+
if response.success? && response.data
|
|
67
|
+
cache_data = transmission_key.key + response.data
|
|
68
|
+
Cache.save_cache(cache_data)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
response
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
# Network failed - try to load from cache
|
|
74
|
+
cached_data = Cache.get_cached_data
|
|
75
|
+
|
|
76
|
+
if cached_data && cached_data.bytesize > 32
|
|
77
|
+
# Extract cached transmission key and response data
|
|
78
|
+
# First 32 bytes are the transmission key, rest is encrypted response
|
|
79
|
+
cached_transmission_key = cached_data[0...32]
|
|
80
|
+
cached_response_data = cached_data[32..-1]
|
|
81
|
+
|
|
82
|
+
# Update the transmission key to match cached version
|
|
83
|
+
transmission_key.key = cached_transmission_key
|
|
84
|
+
|
|
85
|
+
# Return cached response as if it came from network
|
|
86
|
+
Dto::KSMHttpResponse.new(
|
|
87
|
+
status_code: 200,
|
|
88
|
+
data: cached_response_data
|
|
89
|
+
)
|
|
90
|
+
else
|
|
91
|
+
# No cache available - re-raise the original error
|
|
92
|
+
raise e
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Make HTTP request - extracted to be testable
|
|
98
|
+
# This duplicates some logic from Core::SecretsManager#post_function
|
|
99
|
+
# because that method is an instance method
|
|
100
|
+
def self.make_http_request(url, transmission_key, encrypted_payload, verify_ssl_certs)
|
|
101
|
+
require 'net/http'
|
|
102
|
+
require 'uri'
|
|
103
|
+
|
|
104
|
+
uri = URI(url)
|
|
105
|
+
|
|
106
|
+
request = Net::HTTP::Post.new(uri)
|
|
107
|
+
request['Content-Type'] = 'application/octet-stream'
|
|
108
|
+
request['PublicKeyId'] = transmission_key.public_key_id.to_s
|
|
109
|
+
request['TransmissionKey'] = Utils.bytes_to_base64(transmission_key.encrypted_key)
|
|
110
|
+
request['Authorization'] = "Signature #{Utils.bytes_to_base64(encrypted_payload.signature)}"
|
|
111
|
+
request['Content-Length'] = encrypted_payload.encrypted_payload.bytesize.to_s
|
|
112
|
+
request.body = encrypted_payload.encrypted_payload
|
|
113
|
+
|
|
114
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
115
|
+
http.use_ssl = true
|
|
116
|
+
|
|
117
|
+
if verify_ssl_certs
|
|
118
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
119
|
+
|
|
120
|
+
# Set up certificate store with system defaults
|
|
121
|
+
store = OpenSSL::X509::Store.new
|
|
122
|
+
store.set_default_paths
|
|
123
|
+
http.cert_store = store
|
|
124
|
+
else
|
|
125
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
response = http.request(request)
|
|
129
|
+
|
|
130
|
+
Dto::KSMHttpResponse.new(
|
|
131
|
+
status_code: response.code.to_i,
|
|
132
|
+
data: response.body,
|
|
133
|
+
http_response: response
|
|
134
|
+
)
|
|
135
|
+
rescue StandardError => e
|
|
136
|
+
raise NetworkError, "HTTP request failed: #{e.message}"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
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_SERVER_PUBLIC_KEY = 'serverPublicKey'.freeze
|
|
10
|
+
KEY_PRIVATE_KEY = 'privateKey'.freeze
|
|
11
|
+
KEY_APP_KEY = 'appKey'.freeze
|
|
12
|
+
KEY_OWNER_PUBLIC_KEY = 'appOwnerPublicKey'.freeze
|
|
13
|
+
KEY_APP_UID = 'appUid'.freeze
|
|
14
|
+
|
|
15
|
+
# All valid keys
|
|
16
|
+
ALL_KEYS = [
|
|
17
|
+
KEY_URL,
|
|
18
|
+
KEY_CLIENT_ID,
|
|
19
|
+
KEY_CLIENT_KEY,
|
|
20
|
+
KEY_HOSTNAME,
|
|
21
|
+
KEY_SERVER_PUBLIC_KEY_ID,
|
|
22
|
+
KEY_SERVER_PUBLIC_KEY,
|
|
23
|
+
KEY_PRIVATE_KEY,
|
|
24
|
+
KEY_APP_KEY,
|
|
25
|
+
KEY_OWNER_PUBLIC_KEY,
|
|
26
|
+
KEY_APP_UID
|
|
27
|
+
].freeze
|
|
28
|
+
end
|
|
29
|
+
end
|