kstor 0.4.3 → 0.5.0

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/bin/kstor +48 -40
  4. data/bin/kstor-srv +2 -2
  5. data/lib/kstor/config.rb +2 -1
  6. data/lib/kstor/controller/authentication.rb +25 -4
  7. data/lib/kstor/controller/base.rb +61 -0
  8. data/lib/kstor/controller/request_handler.rb +84 -16
  9. data/lib/kstor/controller/secret.rb +63 -64
  10. data/lib/kstor/controller/users.rb +11 -22
  11. data/lib/kstor/controller.rb +3 -3
  12. data/lib/kstor/crypto/armored_value.rb +82 -0
  13. data/lib/kstor/crypto/keys.rb +9 -41
  14. data/lib/kstor/crypto.rb +0 -1
  15. data/lib/kstor/error.rb +36 -3
  16. data/lib/kstor/log/simple_logger.rb +83 -0
  17. data/lib/kstor/log/systemd_logger.rb +22 -0
  18. data/lib/kstor/log.rb +53 -4
  19. data/lib/kstor/message/base.rb +223 -0
  20. data/lib/kstor/message/error.rb +15 -0
  21. data/lib/kstor/message/group_create.rb +14 -0
  22. data/lib/kstor/message/group_created.rb +16 -0
  23. data/lib/kstor/message/ping.rb +14 -0
  24. data/lib/kstor/message/pong.rb +14 -0
  25. data/lib/kstor/message/secret_create.rb +16 -0
  26. data/lib/kstor/message/secret_created.rb +14 -0
  27. data/lib/kstor/message/secret_delete.rb +14 -0
  28. data/lib/kstor/message/secret_deleted.rb +14 -0
  29. data/lib/kstor/message/secret_list.rb +14 -0
  30. data/lib/kstor/message/secret_search.rb +14 -0
  31. data/lib/kstor/message/secret_unlock.rb +14 -0
  32. data/lib/kstor/message/secret_update_meta.rb +15 -0
  33. data/lib/kstor/message/secret_update_value.rb +15 -0
  34. data/lib/kstor/message/secret_updated.rb +14 -0
  35. data/lib/kstor/message/secret_value.rb +35 -0
  36. data/lib/kstor/message.rb +26 -113
  37. data/lib/kstor/model.rb +42 -25
  38. data/lib/kstor/server.rb +15 -3
  39. data/lib/kstor/session.rb +34 -2
  40. data/lib/kstor/socket_server.rb +20 -1
  41. data/lib/kstor/sql_connection.rb +16 -1
  42. data/lib/kstor/store.rb +182 -72
  43. data/lib/kstor/systemd.rb +5 -0
  44. data/lib/kstor/version.rb +2 -1
  45. data/lib/kstor.rb +1 -1
  46. metadata +25 -3
data/lib/kstor/store.rb CHANGED
@@ -1,23 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'mutex_m'
4
+
3
5
  require 'kstor/sql_connection'
4
6
  require 'kstor/model'
5
7
  require 'kstor/log'
6
8
 
7
9
  module KStor
10
+ # Simplistic cache for list of users and groups.
11
+ class StoreCache
12
+ # Create new cache.
13
+ def initialize
14
+ @cache = {}
15
+ @cache.extend(Mutex_m)
16
+ end
17
+
18
+ class << self
19
+ # @!macro [new] dsl_storecache_property
20
+ # @!attribute $1
21
+ # @return returns cached list of $1
22
+
23
+ # Declare a cached list of values.
24
+ #
25
+ # @param name [Symbol] name of list
26
+ def property(name)
27
+ define_method(name) do |&block|
28
+ @cache.synchronize do
29
+ return @cache[name] if @cache.key?(name)
30
+
31
+ @cache[name] = block.call
32
+ end
33
+ end
34
+
35
+ define_method("#{name}=".to_sym) do |list|
36
+ @cache.synchronize { @cache[name] = list }
37
+ end
38
+
39
+ define_method("forget_#{name}") do
40
+ @cache.synchronize { @cache.delete(name) }
41
+ end
42
+ end
43
+ end
44
+
45
+ # @!macro dsl_storecache_property
46
+ property :users
47
+ # @!macro dsl_storecache_property
48
+ property :groups
49
+ end
50
+
8
51
  # Store and fetch objects in an SQLite database.
9
52
  # rubocop:disable Metrics/MethodLength
10
53
  class Store
54
+ # Create a new store backed by the given SQLite database file.
55
+ #
56
+ # @param file_path [String] path to SQLite database file
57
+ # @return [KStor::Store] a data store
11
58
  def initialize(file_path)
12
59
  @file_path = file_path
13
60
  @db = SQLConnection.new(file_path)
14
- @cache = {}
61
+ @cache = StoreCache.new
15
62
  end
16
63
 
64
+ # Execute the given block in a database transaction.
17
65
  def transaction(&)
18
66
  @db.transaction(&)
19
67
  end
20
68
 
69
+ # True if database contains any users.
70
+ #
71
+ # @return [Boolean] false if user table is empty
21
72
  def users?
22
73
  rows = @db.execute('SELECT count(*) AS n FROM users')
23
74
  count = Integer(rows.first['n'])
@@ -26,25 +77,33 @@ module KStor
26
77
  count.positive?
27
78
  end
28
79
 
80
+ # Create a new user in database.
81
+ #
82
+ # @param user [KStor::Model::User] the user to create
83
+ # @return [KStor::Model::User] the same user with a brand-new ID
29
84
  def user_create(user)
30
85
  @db.execute(<<-EOSQL, user.login, user.name, 'new')
31
86
  INSERT INTO users (login, name, status)
32
87
  VALUES (?, ?, ?)
33
88
  EOSQL
34
- user.id = @db.last_insert_row_id
89
+ user_id = @db.last_insert_row_id
35
90
  Log.debug("store: stored new user #{user.login}")
36
91
  params = [user.kdf_params, user.pubk, user.encrypted_privk].map(&:to_s)
37
- return user if params.any?(&:nil?)
92
+ return user_id if params.any?(&:nil?)
38
93
 
39
94
  @db.execute(<<-EOSQL, user.id, *params)
40
95
  INSERT INTO users_crypto_data (user_id, kdf_params, pubk, encrypted_privk)
41
96
  VALUES (?, ?, ?, ?)
42
97
  EOSQL
43
98
  Log.debug("store: stored user crypto data for #{user.login}")
99
+ @cache.forget_users
44
100
 
45
- user
101
+ user_id
46
102
  end
47
103
 
104
+ # Update user name, status and keychain.
105
+ #
106
+ # @param user [KStor::Model::User] user to modify.
48
107
  def user_update(user)
49
108
  @db.execute(<<-EOSQL, user.name, user.status, user.id)
50
109
  UPDATE users SET name = ?, status = ?
@@ -60,6 +119,12 @@ module KStor
60
119
  EOSQL
61
120
  end
62
121
 
122
+ # Add a group private key to a user keychain.
123
+ #
124
+ # @param user_id [Integer] ID of an existing user
125
+ # @param group_id [Integer] ID of an existing group
126
+ # @param encrypted_privk [KStor::Crypto::ArmoredValue] group private key
127
+ # encrypted with user public key
63
128
  def keychain_item_create(user_id, group_id, encrypted_privk)
64
129
  @db.execute(<<-EOSQL, user_id, group_id, encrypted_privk.to_s)
65
130
  INSERT INTO group_members (user_id, group_id, encrypted_privk)
@@ -67,64 +132,79 @@ module KStor
67
132
  EOSQL
68
133
  end
69
134
 
135
+ # Create a new group.
136
+ #
137
+ # Note that it doesn't store the group private key, as it must only exist
138
+ # in users keychains.
139
+ #
140
+ # @param name [String] Name of the new group (must be unique in database)
141
+ # @param pubk [KStor::Crypto::PublicKey] group public key
142
+ # @return [Integer] ID of the new group
70
143
  def group_create(name, pubk)
71
144
  @db.execute(<<-EOSQL, name, pubk.to_s)
72
145
  INSERT INTO groups (name, pubk)
73
146
  VALUES (?, ?)
74
147
  EOSQL
148
+ @cache.forget_groups
75
149
  @db.last_insert_row_id
76
150
  end
77
151
 
152
+ # List all groups.
153
+ #
154
+ # Note that this list is cached in memory, so calling this method multiple
155
+ # times should be cheap.
156
+ #
157
+ # @return [Array[KStor::Model::Group]] a list of all groups in database
78
158
  def groups
79
- return @cache[:groups] if @cache.key?(:groups)
80
-
81
- Log.debug('store: loading groups')
82
- rows = @db.execute(<<-EOSQL)
83
- SELECT id,
84
- name,
85
- pubk
86
- FROM groups
87
- ORDER BY name
88
- EOSQL
89
- @cache[:groups] = rows.to_h do |r|
90
- a = []
91
- a << r['id']
92
- a << Model::Group.new(
93
- id: r['id'], name: r['name'], pubk: Crypto::PublicKey.new(r['pubk'])
94
- )
95
- a
159
+ @cache.groups do
160
+ Log.debug('store: loading groups')
161
+ rows = @db.execute(<<-EOSQL)
162
+ SELECT id,
163
+ name,
164
+ pubk
165
+ FROM groups
166
+ ORDER BY name
167
+ EOSQL
168
+ rows.to_h do |r|
169
+ a = []
170
+ a << r['id']
171
+ a << Model::Group.new(
172
+ id: r['id'], name: r['name'], pubk: Crypto::PublicKey.new(r['pubk'])
173
+ )
174
+ a
175
+ end
96
176
  end
97
177
  end
98
178
 
179
+ # List all users.
180
+ #
181
+ # Note that this list is cached in memory, so calling this method multiple
182
+ # times should be cheap.
183
+ #
184
+ # @return [Array[KStor::Model::User]] a list of all users in database
99
185
  def users
100
- return @cache[:users] if @cache.key?(:users)
101
-
102
- Log.debug('store: loading users')
103
- rows = @db.execute(<<-EOSQL)
104
- SELECT u.id,
105
- u.login,
106
- u.name,
107
- u.status,
108
- c.pubk
109
- FROM users u
110
- LEFT JOIN users_crypto_data c ON (c.user_id = u.id)
111
- ORDER BY u.login
112
- EOSQL
186
+ @cache.users do
187
+ Log.debug('store: loading users')
188
+ rows = @db.execute(<<-EOSQL)
189
+ SELECT u.id,
190
+ u.login,
191
+ u.name,
192
+ u.status,
193
+ c.pubk
194
+ FROM users u
195
+ LEFT JOIN users_crypto_data c ON (c.user_id = u.id)
196
+ ORDER BY u.login
197
+ EOSQL
113
198
 
114
- @cache[:users] = users_from_resultset(rows)
199
+ users_from_resultset(rows)
200
+ end
115
201
  end
116
202
 
117
- # in: login
118
- # out:
119
- # - ID
120
- # - name
121
- # - status
122
- # - public key
123
- # - key derivation function parameters
124
- # - encrypted private key
125
- # - keychain: hash of:
126
- # - group ID
127
- # - encrypted group private key
203
+ # Lookup user by login.
204
+ #
205
+ # @param login [String] User login
206
+ # @return [KStor::Model::User, nil] a user object instance with encrypted
207
+ # private data, or nil if login was not found in database.
128
208
  def user_by_login(login)
129
209
  Log.debug("store: loading user by login #{login.inspect}")
130
210
  rows = @db.execute(<<-EOSQL, login)
@@ -142,14 +222,11 @@ module KStor
142
222
  user_from_resultset(rows, include_crypto_data: true)
143
223
  end
144
224
 
145
- # in: user ID
146
- # out:
147
- # - ID
148
- # - name
149
- # - status
150
- # - public key
151
- # - key derivation function parameters
152
- # - encrypted private key
225
+ # Lookup user by ID.
226
+ #
227
+ # @param user_id [Integer] User ID
228
+ # @return [KStor::Model::User, nil] a user object instance with encrypted
229
+ # private data, or nil if login was not found in database.
153
230
  def user_by_id(user_id)
154
231
  Log.debug("store: loading user by ID ##{user_id}")
155
232
  rows = @db.execute(<<-EOSQL, user_id)
@@ -167,12 +244,11 @@ module KStor
167
244
  user_from_resultset(rows, include_crypto_data: true)
168
245
  end
169
246
 
170
- # in: user ID
171
- # out: array of:
172
- # - secret ID
173
- # - group ID common between user and secret
174
- # - secret encrypted metadata
175
- # - secret value and metadata author IDs
247
+ # List of all secrets that should be readable by a user.
248
+ #
249
+ # @param user_id [Integer] ID of user that will read the secrets
250
+ # @return [Array[KStor::Model::Secret]] A list of secrets, that may be
251
+ # empty.
176
252
  def secrets_for_user(user_id)
177
253
  Log.debug("store: loading secrets for user ##{user_id}")
178
254
  rows = @db.execute(<<-EOSQL, user_id)
@@ -195,8 +271,12 @@ module KStor
195
271
  rows.map { |r| secret_from_row(r) }
196
272
  end
197
273
 
198
- # in: secret ID, user ID
199
- # out: encrypted value
274
+ # Fetch one secret by it's ID.
275
+ #
276
+ # @param secret_id [Integer] ID of secret
277
+ # @param user_id [Integer] ID of secret reader
278
+ # @return [KStor::Model::Secret, nil] A secret, or nil if secret_id was not
279
+ # found or user_id can't read it.
200
280
  def secret_fetch(secret_id, user_id)
201
281
  Log.debug(
202
282
  "store: loading secret value ##{secret_id} for user ##{user_id}"
@@ -221,14 +301,16 @@ module KStor
221
301
  secret_from_row(rows.first)
222
302
  end
223
303
 
224
- # in:
225
- # - user ID
226
- # - hash of:
227
- # - group ID
228
- # - array of:
229
- # - ciphertext
230
- # - encrypted metadata
231
- # out: secret ID
304
+ # Create a new secret.
305
+ #
306
+ # Encrypted data should be a map of group_id to encrypted data for this
307
+ # group's key pair as a two-value array, first metadata and then value.
308
+ #
309
+ # @param author_id [Integer] ID of user that creates the new secret
310
+ # @param encrypted_data
311
+ # [Array[Hash[Integer, Array[KStor::Crypto::ArmoredValue]]]] see above
312
+ # description for shape of value.
313
+ # @return [Integer] ID of new secret
232
314
  def secret_create(author_id, encrypted_data)
233
315
  Log.debug("store: creating secret for user #{author_id}")
234
316
  @db.execute(<<-EOSQL, author_id, author_id)
@@ -242,6 +324,10 @@ module KStor
242
324
  secret_id
243
325
  end
244
326
 
327
+ # List of group IDs that can read this secret.
328
+ #
329
+ # @param secret_id [Integer] ID of secret
330
+ # @return [Array[KStor::Model::Group]] list of group ids
245
331
  def groups_for_secret(secret_id)
246
332
  Log.debug("store: loading group IDs for secret #{secret_id}")
247
333
  rows = @db.execute(<<-EOSQL, secret_id)
@@ -252,8 +338,13 @@ module KStor
252
338
  rows.map { |r| r['group_id'] }
253
339
  end
254
340
 
255
- # in: secret ID, author ID, array of [group ID, encrypted_metadata]
256
- # out: nil
341
+ # Overwrite secret metadata.
342
+ #
343
+ # @param secret_id [Integer] ID of secret to update
344
+ # @param user_id [Integer] ID of user that changes metadata
345
+ # @param group_encrypted_metadata
346
+ # [Array[Hash[Integer, KStor::Crypt::ArmoredValue]]] map of group IDs to
347
+ # encrypted metadata.
257
348
  def secret_setmeta(secret_id, user_id, group_encrypted_metadata)
258
349
  Log.debug("store: set metadata for secret ##{secret_id}")
259
350
  @db.execute(<<-EOSQL, user_id, secret_id)
@@ -269,6 +360,13 @@ module KStor
269
360
  end
270
361
  end
271
362
 
363
+ # Overwrite secret value.
364
+ #
365
+ # @param secret_id [Integer] ID of secret to update
366
+ # @param user_id [Integer] ID of user that changes the value
367
+ # @param group_ciphertexts
368
+ # [Array[Hash[Integer, KStor::Crypt::ArmoredValue]]] map of group IDs to
369
+ # encrypted values.
272
370
  def secret_setvalue(secret_id, user_id, group_ciphertexts)
273
371
  Log.debug("store: set value for secret ##{secret_id}")
274
372
  @db.execute(<<-EOSQL, user_id, secret_id)
@@ -284,6 +382,9 @@ module KStor
284
382
  end
285
383
  end
286
384
 
385
+ # Delete a secrete from database.
386
+ #
387
+ # @param secret_id [Integer] ID of secret
287
388
  def secret_delete(secret_id)
288
389
  Log.debug("store: delete secret ##{secret_id}")
289
390
  # Will cascade to secret_values:
@@ -318,6 +419,15 @@ module KStor
318
419
  users.to_h { |uu| [uu.id, uu] }
319
420
  end
320
421
 
422
+ # Create a new instance of {KStor::Model::User} from a query result rows.
423
+ #
424
+ # This will shift (consume) one row from provided array.
425
+ #
426
+ # @param rows [Array[Hash[String, Any]]] Array of query result rows
427
+ # @param include_crypto_data [Boolean] true if new user object should have
428
+ # encrypted private key data and keychain
429
+ # @return [Array[KStor::Model], nil] A new user object, or nil if there
430
+ # were no more rows
321
431
  def user_from_resultset(rows, include_crypto_data: true)
322
432
  return nil if rows.empty?
323
433
 
data/lib/kstor/systemd.rb CHANGED
@@ -6,6 +6,9 @@ module KStor
6
6
  # Collection of methods for systemd integration.
7
7
  module Systemd
8
8
  class << self
9
+ # Get main socket from systemd
10
+ #
11
+ # @return [nil,Socket] The socket or nil if systemd didn't provide
9
12
  def socket
10
13
  listen_pid = ENV['LISTEN_PID'].to_i
11
14
  return nil unless Process.pid == listen_pid
@@ -13,10 +16,12 @@ module KStor
13
16
  Socket.for_fd(3)
14
17
  end
15
18
 
19
+ # Notify systemd that we're ready to serve clients.
16
20
  def service_ready
17
21
  SdNotify.ready
18
22
  end
19
23
 
24
+ # Notify systemd that we're stopping.
20
25
  def service_stopping
21
26
  SdNotify.stopping
22
27
  end
data/lib/kstor/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KStor
4
- VERSION = '0.4.3'
4
+ # Our version number.
5
+ VERSION = '0.5.0'
5
6
  end
data/lib/kstor.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kstor/log'
3
4
  require 'kstor/config'
4
5
  require 'kstor/store'
5
6
  require 'kstor/controller'
6
7
  require 'kstor/server'
7
- require 'kstor/log'
8
8
  require 'kstor/session'
9
9
  require 'kstor/message'
10
10
  require 'kstor/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kstor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jérémie Pierson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-05 00:00:00.000000000 Z
11
+ date: 2022-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: journald-logger
@@ -100,15 +100,36 @@ files:
100
100
  - lib/kstor/config.rb
101
101
  - lib/kstor/controller.rb
102
102
  - lib/kstor/controller/authentication.rb
103
+ - lib/kstor/controller/base.rb
103
104
  - lib/kstor/controller/request_handler.rb
104
105
  - lib/kstor/controller/secret.rb
105
106
  - lib/kstor/controller/users.rb
106
107
  - lib/kstor/crypto.rb
108
+ - lib/kstor/crypto/armored_value.rb
107
109
  - lib/kstor/crypto/ascii_armor.rb
108
110
  - lib/kstor/crypto/keys.rb
109
111
  - lib/kstor/error.rb
110
112
  - lib/kstor/log.rb
113
+ - lib/kstor/log/simple_logger.rb
114
+ - lib/kstor/log/systemd_logger.rb
111
115
  - lib/kstor/message.rb
116
+ - lib/kstor/message/base.rb
117
+ - lib/kstor/message/error.rb
118
+ - lib/kstor/message/group_create.rb
119
+ - lib/kstor/message/group_created.rb
120
+ - lib/kstor/message/ping.rb
121
+ - lib/kstor/message/pong.rb
122
+ - lib/kstor/message/secret_create.rb
123
+ - lib/kstor/message/secret_created.rb
124
+ - lib/kstor/message/secret_delete.rb
125
+ - lib/kstor/message/secret_deleted.rb
126
+ - lib/kstor/message/secret_list.rb
127
+ - lib/kstor/message/secret_search.rb
128
+ - lib/kstor/message/secret_unlock.rb
129
+ - lib/kstor/message/secret_update_meta.rb
130
+ - lib/kstor/message/secret_update_value.rb
131
+ - lib/kstor/message/secret_updated.rb
132
+ - lib/kstor/message/secret_value.rb
112
133
  - lib/kstor/model.rb
113
134
  - lib/kstor/server.rb
114
135
  - lib/kstor/session.rb
@@ -117,11 +138,12 @@ files:
117
138
  - lib/kstor/store.rb
118
139
  - lib/kstor/systemd.rb
119
140
  - lib/kstor/version.rb
120
- homepage: https://github.com/jeremiepierson/kstor
141
+ homepage: https://git.arlol.net/jpi/kstor
121
142
  licenses:
122
143
  - GPL-3.0-or-later
123
144
  metadata:
124
145
  rubygems_mfa_required: 'true'
146
+ allowed_push_host: https://rubygems.org/
125
147
  post_install_message:
126
148
  rdoc_options: []
127
149
  require_paths: