kstor 0.4.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.
- checksums.yaml +7 -0
- data/README.md +7 -0
- data/bin/kstor +286 -0
- data/bin/kstor-srv +26 -0
- data/lib/kstor/config.rb +66 -0
- data/lib/kstor/controller/authentication.rb +79 -0
- data/lib/kstor/controller/secret.rb +201 -0
- data/lib/kstor/controller/users.rb +62 -0
- data/lib/kstor/controller.rb +80 -0
- data/lib/kstor/crypto/ascii_armor.rb +27 -0
- data/lib/kstor/crypto/keys.rb +116 -0
- data/lib/kstor/crypto.rb +240 -0
- data/lib/kstor/error.rb +85 -0
- data/lib/kstor/log.rb +56 -0
- data/lib/kstor/message.rb +132 -0
- data/lib/kstor/model.rb +437 -0
- data/lib/kstor/server.rb +51 -0
- data/lib/kstor/session.rb +80 -0
- data/lib/kstor/socket_server.rb +113 -0
- data/lib/kstor/sql_connection.rb +74 -0
- data/lib/kstor/store.rb +383 -0
- data/lib/kstor/systemd.rb +25 -0
- data/lib/kstor/version.rb +5 -0
- data/lib/kstor.rb +10 -0
- metadata +141 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kstor/log'
|
4
|
+
require 'kstor/systemd'
|
5
|
+
|
6
|
+
require 'socket'
|
7
|
+
require 'timeout'
|
8
|
+
|
9
|
+
module KStor
|
10
|
+
# Serve clients on UNIX sockets.
|
11
|
+
class SocketServer
|
12
|
+
GRACEFUL_TIMEOUT = 10
|
13
|
+
|
14
|
+
def initialize(socket_path:, nworkers:)
|
15
|
+
@path = socket_path
|
16
|
+
@nworkers = nworkers
|
17
|
+
@client_queue = Queue.new
|
18
|
+
@workers = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
start_workers
|
23
|
+
server = Systemd.socket
|
24
|
+
Systemd.service_ready
|
25
|
+
loop do
|
26
|
+
maintain_workers
|
27
|
+
@client_queue.enq(server.accept.first)
|
28
|
+
end
|
29
|
+
rescue Interrupt
|
30
|
+
stop(server)
|
31
|
+
Log.info('socket_server: stopped.')
|
32
|
+
end
|
33
|
+
|
34
|
+
def work(client)
|
35
|
+
# Abstract method.
|
36
|
+
client.close
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def worker_run
|
42
|
+
while (client = @client_queue.deq)
|
43
|
+
Log.debug("socket_server: #{Thread.current.name} serving one client")
|
44
|
+
work(client)
|
45
|
+
Log.debug("socket_server: #{Thread.current.name} done serving client")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop(server)
|
50
|
+
Systemd.service_stopping
|
51
|
+
Log.debug('socket_server: closing UNIXServer')
|
52
|
+
server.close
|
53
|
+
Log.debug('socket_server: closing client queue')
|
54
|
+
@client_queue.close
|
55
|
+
Log.debug("socket_server: waiting #{GRACEFUL_TIMEOUT} seconds " \
|
56
|
+
'for workers to finish')
|
57
|
+
Timeout.timeout(GRACEFUL_TIMEOUT) { @workers.each(&:join) }
|
58
|
+
rescue Timeout::Error
|
59
|
+
Log.warn('socket_server: graceful timeout reached, killing workers')
|
60
|
+
immediate_stop(server)
|
61
|
+
end
|
62
|
+
|
63
|
+
def immediate_stop
|
64
|
+
@workers.each { |w| w.raise(Interrupt.new('abort')) }
|
65
|
+
@workers.each(&:join)
|
66
|
+
end
|
67
|
+
|
68
|
+
def start_workers
|
69
|
+
@nworkers.times do |i|
|
70
|
+
@workers << start_worker("worker-#{i}")
|
71
|
+
Log.debug("socket_server: started #{@workers.last.name}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def start_worker(name)
|
76
|
+
thr = Thread.new { worker_run }
|
77
|
+
thr.name = name
|
78
|
+
|
79
|
+
thr
|
80
|
+
end
|
81
|
+
|
82
|
+
def maintain_workers
|
83
|
+
collect_dead_workers.each do |i, w|
|
84
|
+
name = w.name
|
85
|
+
Log.error("socket_server: #{name} died!")
|
86
|
+
rescue_worker_exception(w)
|
87
|
+
Log.info("socket_server: performing resurrection on #{name}")
|
88
|
+
@workers[i] = start_worker(name)
|
89
|
+
Log.debug("socket_server: welcome back, comrade #{name}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def collect_dead_workers
|
94
|
+
deads = {}
|
95
|
+
@workers.each_with_index do |w, i|
|
96
|
+
next if %w[sleep run].include?(w.status)
|
97
|
+
|
98
|
+
Log.debug("socket_server: #{w.name} status is #{w.status.inspect}")
|
99
|
+
deads[i] = w
|
100
|
+
end
|
101
|
+
|
102
|
+
deads
|
103
|
+
end
|
104
|
+
|
105
|
+
# rubocop:disable Lint/RescueException
|
106
|
+
def rescue_worker_exception(worker)
|
107
|
+
worker.join
|
108
|
+
rescue Exception => e
|
109
|
+
Log.exception(e)
|
110
|
+
end
|
111
|
+
# rubocop:enable Lint/RescueException
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kstor/log'
|
4
|
+
require 'kstor/error'
|
5
|
+
|
6
|
+
require 'sqlite3'
|
7
|
+
|
8
|
+
module KStor
|
9
|
+
# Error: can't open database file.
|
10
|
+
class CantOpenDatabase < Error
|
11
|
+
error_code 'SQL/CANTOPEN'
|
12
|
+
error_message "Can't open database file at %s"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Execute SQL commands in a per-thread SQLite connection.
|
16
|
+
class SQLConnection
|
17
|
+
def initialize(file_path)
|
18
|
+
@file_path = file_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(sql, *params, &)
|
22
|
+
database.execute(sql, *params, &)
|
23
|
+
end
|
24
|
+
|
25
|
+
def last_insert_row_id
|
26
|
+
database.last_insert_row_id
|
27
|
+
end
|
28
|
+
|
29
|
+
def transaction(&block)
|
30
|
+
result = nil
|
31
|
+
database.transaction do |db|
|
32
|
+
result = block.call(db)
|
33
|
+
end
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def database
|
41
|
+
key = :kstor_db_connection
|
42
|
+
setup_thread_connection(key)
|
43
|
+
db = Thread.current[key]
|
44
|
+
return db unless db.closed?
|
45
|
+
|
46
|
+
Log.warn('sqlite: bad connection, will re-connect')
|
47
|
+
db.close
|
48
|
+
Thread.current[k] = nil
|
49
|
+
setup_thread_connection(key)
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_thread_connection(key)
|
53
|
+
return if Thread.current[key]
|
54
|
+
|
55
|
+
Log.info(
|
56
|
+
"sqlite: initializing connection in thread #{Thread.current.name}"
|
57
|
+
)
|
58
|
+
Thread.current[key] = connect(@file_path)
|
59
|
+
Log.debug("sqlite: opened #{@file_path}")
|
60
|
+
|
61
|
+
Thread.current[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def connect(file_path)
|
65
|
+
db = SQLite3::Database.new(file_path)
|
66
|
+
db.results_as_hash = true
|
67
|
+
db.type_translation = SQLite3::Translator.new
|
68
|
+
|
69
|
+
db
|
70
|
+
rescue SQLite3::CantOpenException
|
71
|
+
raise Error.for_code('SQL/CANTOPEN', file_path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/kstor/store.rb
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kstor/sql_connection'
|
4
|
+
require 'kstor/model'
|
5
|
+
require 'kstor/log'
|
6
|
+
|
7
|
+
module KStor
|
8
|
+
# Store and fetch objects in an SQLite database.
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
10
|
+
class Store
|
11
|
+
def initialize(file_path)
|
12
|
+
@file_path = file_path
|
13
|
+
@db = SQLConnection.new(file_path)
|
14
|
+
@cache = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def transaction(&)
|
18
|
+
@db.transaction(&)
|
19
|
+
end
|
20
|
+
|
21
|
+
def users?
|
22
|
+
rows = @db.execute('SELECT count(*) AS n FROM users')
|
23
|
+
count = Integer(rows.first['n'])
|
24
|
+
Log.debug("store: count of users is #{count}")
|
25
|
+
|
26
|
+
count.positive?
|
27
|
+
end
|
28
|
+
|
29
|
+
def user_create(user)
|
30
|
+
@db.execute(<<-EOSQL, user.login, user.name, 'new')
|
31
|
+
INSERT INTO users (login, name, status)
|
32
|
+
VALUES (?, ?, ?)
|
33
|
+
EOSQL
|
34
|
+
user.id = @db.last_insert_row_id
|
35
|
+
Log.debug("store: stored new user #{user.login}")
|
36
|
+
params = [user.kdf_params, user.pubk, user.encrypted_privk].map(&:to_s)
|
37
|
+
return user if params.any?(&:nil?)
|
38
|
+
|
39
|
+
@db.execute(<<-EOSQL, user.id, *params)
|
40
|
+
INSERT INTO users_crypto_data (user_id, kdf_params, pubk, encrypted_privk)
|
41
|
+
VALUES (?, ?, ?, ?)
|
42
|
+
EOSQL
|
43
|
+
Log.debug("store: stored user crypto data for #{user.login}")
|
44
|
+
|
45
|
+
user
|
46
|
+
end
|
47
|
+
|
48
|
+
def user_update(user)
|
49
|
+
@db.execute(<<-EOSQL, user.name, user.status, user.id)
|
50
|
+
UPDATE users SET name = ?, status = ?
|
51
|
+
WHERE id = ?
|
52
|
+
EOSQL
|
53
|
+
params = [user.kdf_params, user.pubk, user.encrypted_privk, user.id]
|
54
|
+
@db.execute(<<-EOSQL, *params)
|
55
|
+
UPDATE users_crypto_data SET
|
56
|
+
kdf_params = ?,
|
57
|
+
pubk = ?
|
58
|
+
encrypted_params = ?
|
59
|
+
WHERE user_id = ?
|
60
|
+
EOSQL
|
61
|
+
end
|
62
|
+
|
63
|
+
def keychain_item_create(user_id, group_id, encrypted_privk)
|
64
|
+
@db.execute(<<-EOSQL, user_id, group_id, encrypted_privk.to_s)
|
65
|
+
INSERT INTO group_members (user_id, group_id, encrypted_privk)
|
66
|
+
VALUES (?, ?, ?)
|
67
|
+
EOSQL
|
68
|
+
end
|
69
|
+
|
70
|
+
def group_create(name, pubk)
|
71
|
+
@db.execute(<<-EOSQL, name, pubk.to_s)
|
72
|
+
INSERT INTO groups (name, pubk)
|
73
|
+
VALUES (?, ?)
|
74
|
+
EOSQL
|
75
|
+
@db.last_insert_row_id
|
76
|
+
end
|
77
|
+
|
78
|
+
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
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
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
|
113
|
+
|
114
|
+
@cache[:users] = users_from_resultset(rows)
|
115
|
+
end
|
116
|
+
|
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
|
128
|
+
def user_by_login(login)
|
129
|
+
Log.debug("store: loading user by login #{login.inspect}")
|
130
|
+
rows = @db.execute(<<-EOSQL, login)
|
131
|
+
SELECT u.id,
|
132
|
+
u.login,
|
133
|
+
u.name,
|
134
|
+
u.status,
|
135
|
+
c.kdf_params,
|
136
|
+
c.pubk,
|
137
|
+
c.encrypted_privk
|
138
|
+
FROM users u
|
139
|
+
LEFT JOIN users_crypto_data c ON (c.user_id = u.id)
|
140
|
+
WHERE u.login = ?
|
141
|
+
EOSQL
|
142
|
+
user_from_resultset(rows, include_crypto_data: true)
|
143
|
+
end
|
144
|
+
|
145
|
+
# in: user ID
|
146
|
+
# out:
|
147
|
+
# - ID
|
148
|
+
# - name
|
149
|
+
# - status
|
150
|
+
# - public key
|
151
|
+
# - key derivation function parameters
|
152
|
+
# - encrypted private key
|
153
|
+
def user_by_id(user_id)
|
154
|
+
Log.debug("store: loading user by ID ##{user_id}")
|
155
|
+
rows = @db.execute(<<-EOSQL, user_id)
|
156
|
+
SELECT u.id,
|
157
|
+
u.login,
|
158
|
+
u.name,
|
159
|
+
u.status,
|
160
|
+
c.kdf_params,
|
161
|
+
c.pubk,
|
162
|
+
c.encrypted_privk,
|
163
|
+
FROM users u
|
164
|
+
LEFT JOIN users_crypto_data c ON (c.user_id = u.id)
|
165
|
+
WHERE u.id = ?
|
166
|
+
EOSQL
|
167
|
+
user_from_resultset(rows, include_crypto_data: true)
|
168
|
+
end
|
169
|
+
|
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
|
176
|
+
def secrets_for_user(user_id)
|
177
|
+
Log.debug("store: loading secrets for user ##{user_id}")
|
178
|
+
rows = @db.execute(<<-EOSQL, user_id)
|
179
|
+
SELECT s.id,
|
180
|
+
s.value_author_id,
|
181
|
+
s.meta_author_id,
|
182
|
+
sv.group_id,
|
183
|
+
sv.ciphertext,
|
184
|
+
sv.encrypted_metadata
|
185
|
+
FROM secrets s,
|
186
|
+
secret_values sv,
|
187
|
+
group_members gm
|
188
|
+
WHERE gm.user_id = ?
|
189
|
+
AND gm.group_id = sv.group_id
|
190
|
+
AND sv.secret_id = s.id
|
191
|
+
GROUP BY s.id
|
192
|
+
ORDER BY s.id, sv.group_id
|
193
|
+
EOSQL
|
194
|
+
|
195
|
+
rows.map { |r| secret_from_row(r) }
|
196
|
+
end
|
197
|
+
|
198
|
+
# in: secret ID, user ID
|
199
|
+
# out: encrypted value
|
200
|
+
def secret_fetch(secret_id, user_id)
|
201
|
+
Log.debug(
|
202
|
+
"store: loading secret value ##{secret_id} for user ##{user_id}"
|
203
|
+
)
|
204
|
+
rows = @db.execute(<<-EOSQL, user_id, secret_id)
|
205
|
+
SELECT s.id,
|
206
|
+
s.value_author_id,
|
207
|
+
s.meta_author_id,
|
208
|
+
sv.group_id,
|
209
|
+
sv.ciphertext,
|
210
|
+
sv.encrypted_metadata
|
211
|
+
FROM secrets s,
|
212
|
+
secret_values sv,
|
213
|
+
group_members gm
|
214
|
+
WHERE gm.user_id = ?
|
215
|
+
AND gm.group_id = sv.group_id
|
216
|
+
AND sv.secret_id = ?
|
217
|
+
AND s.id = sv.secret_id
|
218
|
+
EOSQL
|
219
|
+
return nil if rows.empty?
|
220
|
+
|
221
|
+
secret_from_row(rows.first)
|
222
|
+
end
|
223
|
+
|
224
|
+
# in:
|
225
|
+
# - user ID
|
226
|
+
# - hash of:
|
227
|
+
# - group ID
|
228
|
+
# - array of:
|
229
|
+
# - ciphertext
|
230
|
+
# - encrypted metadata
|
231
|
+
# out: secret ID
|
232
|
+
def secret_create(author_id, encrypted_data)
|
233
|
+
Log.debug("store: creating secret for user #{author_id}")
|
234
|
+
@db.execute(<<-EOSQL, author_id, author_id)
|
235
|
+
INSERT INTO secrets (value_author_id, meta_author_id) VALUES (?, ?)
|
236
|
+
EOSQL
|
237
|
+
secret_id = @db.last_insert_row_id
|
238
|
+
encrypted_data.each do |group_id, (ciphertext, encrypted_metadata)|
|
239
|
+
secret_value_create(secret_id, group_id, ciphertext, encrypted_metadata)
|
240
|
+
end
|
241
|
+
|
242
|
+
secret_id
|
243
|
+
end
|
244
|
+
|
245
|
+
def groups_for_secret(secret_id)
|
246
|
+
Log.debug("store: loading group IDs for secret #{secret_id}")
|
247
|
+
rows = @db.execute(<<-EOSQL, secret_id)
|
248
|
+
SELECT group_id
|
249
|
+
FROM secret_values
|
250
|
+
WHERE secret_id = ?
|
251
|
+
EOSQL
|
252
|
+
rows.map { |r| r['group_id'] }
|
253
|
+
end
|
254
|
+
|
255
|
+
# in: secret ID, author ID, array of [group ID, encrypted_metadata]
|
256
|
+
# out: nil
|
257
|
+
def secret_setmeta(secret_id, user_id, group_encrypted_metadata)
|
258
|
+
Log.debug("store: set metadata for secret ##{secret_id}")
|
259
|
+
@db.execute(<<-EOSQL, user_id, secret_id)
|
260
|
+
UPDATE secrets SET meta_author_id = ? WHERE id = ?
|
261
|
+
EOSQL
|
262
|
+
group_encrypted_metadata.each do |group_id, encrypted_metadata|
|
263
|
+
@db.execute(<<-EOSQL, encrypted_metadata.to_s, secret_id, group_id)
|
264
|
+
UPDATE secret_values
|
265
|
+
SET encrypted_metadata = ?
|
266
|
+
WHERE secret_id = ?
|
267
|
+
AND group_id = ?
|
268
|
+
EOSQL
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def secret_setvalue(secret_id, user_id, group_ciphertexts)
|
273
|
+
Log.debug("store: set value for secret ##{secret_id}")
|
274
|
+
@db.execute(<<-EOSQL, user_id, secret_id)
|
275
|
+
UPDATE secrets SET value_author_id = ? WHERE id = ?
|
276
|
+
EOSQL
|
277
|
+
group_ciphertexts.each do |group_id, ciphertext|
|
278
|
+
@db.execute(<<-EOSQL, ciphertext.to_s, secret_id, group_id)
|
279
|
+
UPDATE secret_values
|
280
|
+
SET ciphertext = ?
|
281
|
+
WHERE secret_id = ?
|
282
|
+
AND group_id = ?
|
283
|
+
EOSQL
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def secret_delete(secret_id)
|
288
|
+
Log.debug("store: delete secret ##{secret_id}")
|
289
|
+
# Will cascade to secret_values:
|
290
|
+
@db.execute(<<-EOSQL, secret_id)
|
291
|
+
DELETE FROM secrets WHERE id = ?
|
292
|
+
EOSQL
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
# in: secret ID, group ID, encrypted metadata and value
|
298
|
+
# out: nil
|
299
|
+
def secret_value_create(secret_id, group_id, ciphertext, encrypted_metadata)
|
300
|
+
params = [ciphertext.to_s, encrypted_metadata.to_s]
|
301
|
+
@db.execute(<<-EOSQL, secret_id, group_id, *params)
|
302
|
+
INSERT INTO secret_values (
|
303
|
+
secret_id, group_id,
|
304
|
+
ciphertext, encrypted_metadata
|
305
|
+
) VALUES (
|
306
|
+
?, ?,
|
307
|
+
?, ?
|
308
|
+
)
|
309
|
+
EOSQL
|
310
|
+
end
|
311
|
+
|
312
|
+
def users_from_resultset(rows)
|
313
|
+
users = []
|
314
|
+
while (u = user_from_resultset(rows, include_crypto_data: false))
|
315
|
+
users << u
|
316
|
+
end
|
317
|
+
|
318
|
+
users.to_h { |uu| [uu.id, uu] }
|
319
|
+
end
|
320
|
+
|
321
|
+
def user_from_resultset(rows, include_crypto_data: true)
|
322
|
+
return nil if rows.empty?
|
323
|
+
|
324
|
+
row = rows.shift
|
325
|
+
user_data = {
|
326
|
+
id: row['id'],
|
327
|
+
login: row['login'],
|
328
|
+
name: row['name'],
|
329
|
+
status: row['status'],
|
330
|
+
pubk: Crypto::PublicKey.new(row['pubk'])
|
331
|
+
}
|
332
|
+
include_crypto_data && user_crypto_data_from_resultset(user_data, row)
|
333
|
+
Model::User.new(**user_data)
|
334
|
+
end
|
335
|
+
|
336
|
+
def user_crypto_data_from_resultset(user_data, row)
|
337
|
+
user_data.merge!(
|
338
|
+
kdf_params: Crypto::KDFParams.new(row['kdf_params']),
|
339
|
+
encrypted_privk: Crypto::ArmoredValue.new(row['encrypted_privk']),
|
340
|
+
keychain: keychain_fetch(row['id'])
|
341
|
+
)
|
342
|
+
end
|
343
|
+
|
344
|
+
# in: user ID
|
345
|
+
# out: hash of:
|
346
|
+
# - group ID
|
347
|
+
# - encrypted_private key
|
348
|
+
def keychain_fetch(user_id)
|
349
|
+
rows = @db.execute(<<-EOSQL, user_id)
|
350
|
+
SELECT g.id,
|
351
|
+
g.pubk,
|
352
|
+
gm.encrypted_privk
|
353
|
+
FROM groups g,
|
354
|
+
group_members gm
|
355
|
+
WHERE gm.user_id = ?
|
356
|
+
AND gm.group_id = g.id
|
357
|
+
ORDER BY g.name
|
358
|
+
EOSQL
|
359
|
+
rows.to_h do |r|
|
360
|
+
[
|
361
|
+
r['id'],
|
362
|
+
Model::KeychainItem.new(
|
363
|
+
group_id: r['id'],
|
364
|
+
group_pubk: Crypto::PublicKey.new(r['pubk']),
|
365
|
+
encrypted_privk: Crypto::ArmoredValue.new(r['encrypted_privk'])
|
366
|
+
)
|
367
|
+
]
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def secret_from_row(row)
|
372
|
+
Model::Secret.new(
|
373
|
+
id: row['id'],
|
374
|
+
value_author_id: row['value_author_id'],
|
375
|
+
meta_author_id: row['meta_author_id'],
|
376
|
+
group_id: row['group_id'],
|
377
|
+
ciphertext: Crypto::ArmoredValue.new(row['ciphertext']),
|
378
|
+
encrypted_metadata: Crypto::ArmoredValue.new(row['encrypted_metadata'])
|
379
|
+
)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
# rubocop:enable Metrics/MethodLength
|
383
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sd_notify'
|
4
|
+
|
5
|
+
module KStor
|
6
|
+
# Collection of methods for systemd integration.
|
7
|
+
module Systemd
|
8
|
+
class << self
|
9
|
+
def socket
|
10
|
+
listen_pid = ENV['LISTEN_PID'].to_i
|
11
|
+
return nil unless Process.pid == listen_pid
|
12
|
+
|
13
|
+
Socket.for_fd(3)
|
14
|
+
end
|
15
|
+
|
16
|
+
def service_ready
|
17
|
+
SdNotify.ready
|
18
|
+
end
|
19
|
+
|
20
|
+
def service_stopping
|
21
|
+
SdNotify.stopping
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|