Class: WorkOS::Vault

Inherits:
Object
  • Object
show all
Defined in:
lib/workos/vault.rb

Overview

WorkOS Vault: KV secret storage, server-managed key wrapping, and client-side AES-GCM encrypt/decrypt.

Examples:

Store and retrieve a secret

client.vault.create_object(name: "api-key", value: "sk_...", key_context: { "tenant" => "t1" })
obj = client.vault.read_object_by_name(name: "api-key")
obj.value # => "sk_..."

Client-side encrypt/decrypt

encrypted = client.vault.encrypt(data: "plaintext", key_context: { "tenant" => "t1" })
client.vault.decrypt(encrypted_data: encrypted)

Defined Under Namespace

Classes: DataKey, DataKeyPair, ObjectDigest, ObjectMetadata, ObjectUpdateBy, ObjectVersion, VaultObject

Constant Summary collapse

DEFAULT_RESPONSE_LIMIT =
10

Instance Method Summary collapse

Constructor Details

#initialize(client) ⇒ Vault

Returns a new instance of Vault.



88
89
90
# File 'lib/workos/vault.rb', line 88

def initialize(client)
  @client = client
end

Instance Method Details

#create_data_key(key_context:, request_options: {}) ⇒ DataKeyPair

Generate a data key for local encryption.

Returns:



153
154
155
156
157
# File 'lib/workos/vault.rb', line 153

def create_data_key(key_context:, request_options: {})
  body = {"context" => key_context}
  response = @client.request(method: :post, path: "/vault/v1/keys/data-key", auth: true, body: body, request_options: request_options)
  DataKeyPair.from_response(JSON.parse(response.body))
end

#create_object(name:, value:, key_context:, request_options: {}) ⇒ Object

Create a new Vault encrypted object.



130
131
132
133
134
# File 'lib/workos/vault.rb', line 130

def create_object(name:, value:, key_context:, request_options: {})
  body = {"name" => name, "value" => value, "key_context" => key_context}
  response = @client.request(method: :post, path: "/vault/v1/kv", auth: true, body: body, request_options: request_options)
  ObjectMetadata.from_hash(JSON.parse(response.body))
end

#decrypt(encrypted_data:, associated_data: nil) ⇒ Object

Decrypt data previously encrypted by ‘encrypt`.



181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/workos/vault.rb', line 181

def decrypt(encrypted_data:, associated_data: nil)
  payload = Base64.decode64(encrypted_data)
  iv = payload.byteslice(0, 12)
  tag = payload.byteslice(12, 16)
  key_len, leb_len = decode_u32_leb128(payload.byteslice(28, payload.bytesize - 28))
  keys_index = 28 + leb_len
  key_blob = payload.byteslice(keys_index, key_len)
  ciphertext = payload.byteslice(keys_index + key_len, payload.bytesize - (keys_index + key_len))
  data_key = decrypt_data_key(keys: Base64.strict_encode64(key_blob))
  key = Base64.decode64(data_key.key)
  aes_gcm_decrypt(ciphertext, key, iv, tag, associated_data&.b)
end

#decrypt_data_key(keys:, request_options: {}) ⇒ DataKey

Decrypt encrypted data keys previously generated by create_data_key.

Returns:



161
162
163
164
165
# File 'lib/workos/vault.rb', line 161

def decrypt_data_key(keys:, request_options: {})
  body = {"keys" => keys}
  response = @client.request(method: :post, path: "/vault/v1/keys/decrypt", auth: true, body: body, request_options: request_options)
  DataKey.from_response(JSON.parse(response.body))
end

#delete_object(object_id:, request_options: {}) ⇒ Object

Permanently delete a Vault encrypted object.



144
145
146
147
# File 'lib/workos/vault.rb', line 144

def delete_object(object_id:, request_options: {})
  @client.request(method: :delete, path: "/vault/v1/kv/#{WorkOS::Util.encode_path(object_id)}", auth: true, request_options: request_options)
  nil
end

#encrypt(data:, key_context:, associated_data: nil) ⇒ Object

Encrypt data locally using AES-GCM with a data key derived from the context. Returns base64(IV || TAG || LEB128(len(keyBlob)) || keyBlob || ciphertext).



171
172
173
174
175
176
177
178
# File 'lib/workos/vault.rb', line 171

def encrypt(data:, key_context:, associated_data: nil)
  pair = create_data_key(key_context: key_context)
  key = Base64.decode64(pair.data_key.key)
  key_blob = Base64.decode64(pair.encrypted_keys)
  prefix = encode_u32_leb128(key_blob.bytesize)
  iv, ciphertext, tag = aes_gcm_encrypt(data.b, key, associated_data&.b)
  Base64.strict_encode64(iv + tag + prefix + key_blob + ciphertext)
end

#get_object_metadata(object_id:, request_options: {}) ⇒ Object

Get a Vault object’s metadata without decrypting the value.



107
108
109
110
# File 'lib/workos/vault.rb', line 107

def (object_id:, request_options: {})
  response = @client.request(method: :get, path: "/vault/v1/kv/#{WorkOS::Util.encode_path(object_id)}/metadata", auth: true, request_options: request_options)
  VaultObject.from_hash(JSON.parse(response.body))
end

#list_object_versions(object_id:, request_options: {}) ⇒ Array<ObjectVersion>

List versions for a specific Vault object.

Returns:



123
124
125
126
127
# File 'lib/workos/vault.rb', line 123

def list_object_versions(object_id:, request_options: {})
  response = @client.request(method: :get, path: "/vault/v1/kv/#{WorkOS::Util.encode_path(object_id)}/versions", auth: true, request_options: request_options)
  parsed = JSON.parse(response.body)
  (parsed["data"] || []).map { |item| ObjectVersion.from_hash(item) }
end

#list_objects(limit: DEFAULT_RESPONSE_LIMIT, before: nil, after: nil, request_options: {}) ⇒ Array<ObjectDigest>

List encrypted Vault objects.

Returns:



114
115
116
117
118
119
# File 'lib/workos/vault.rb', line 114

def list_objects(limit: DEFAULT_RESPONSE_LIMIT, before: nil, after: nil, request_options: {})
  params = {"limit" => limit, "before" => before, "after" => after}.compact
  response = @client.request(method: :get, path: "/vault/v1/kv", auth: true, params: params, request_options: request_options)
  parsed = JSON.parse(response.body)
  (parsed["data"] || []).map { |item| ObjectDigest.from_hash(item) }
end

#read_object(object_id:, request_options: {}) ⇒ Object

Get a Vault object with the value decrypted.



95
96
97
98
# File 'lib/workos/vault.rb', line 95

def read_object(object_id:, request_options: {})
  response = @client.request(method: :get, path: "/vault/v1/kv/#{WorkOS::Util.encode_path(object_id)}", auth: true, request_options: request_options)
  VaultObject.from_hash(JSON.parse(response.body))
end

#read_object_by_name(name:, request_options: {}) ⇒ Object

Get a Vault object by name with the value decrypted.



101
102
103
104
# File 'lib/workos/vault.rb', line 101

def read_object_by_name(name:, request_options: {})
  response = @client.request(method: :get, path: "/vault/v1/kv/name/#{WorkOS::Util.encode_path(name)}", auth: true, request_options: request_options)
  VaultObject.from_hash(JSON.parse(response.body))
end

#update_object(object_id:, value:, version_check: nil, request_options: {}) ⇒ Object

Update an existing Vault object.



137
138
139
140
141
# File 'lib/workos/vault.rb', line 137

def update_object(object_id:, value:, version_check: nil, request_options: {})
  body = {"value" => value, "version_check" => version_check}.compact
  response = @client.request(method: :put, path: "/vault/v1/kv/#{WorkOS::Util.encode_path(object_id)}", auth: true, body: body, request_options: request_options)
  VaultObject.from_hash(JSON.parse(response.body))
end