kstor 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: