kstor 0.4.2 → 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/log.rb CHANGED
@@ -1,56 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'journald/logger'
4
-
5
3
  module KStor
6
4
  # Central logging to systemd-journald.
7
5
  module Log
8
6
  class << self
7
+ # Log an exception.
8
+ #
9
+ # @param exc [Exception] exception to log.
9
10
  def exception(exc)
10
11
  logger.exception(exc)
11
12
  end
12
13
 
14
+ # Log a debug message.
15
+ #
16
+ # @param msg [String] message
13
17
  def debug(msg)
14
18
  logger.debug(msg)
15
19
  end
16
20
 
21
+ # Log an informative message.
22
+ #
23
+ # @param msg [String] message
17
24
  def info(msg)
18
25
  logger.info(msg)
19
26
  end
20
27
 
28
+ # Log a notice message.
29
+ #
30
+ # @param msg [String] message
21
31
  def notice(msg)
22
32
  logger.notice(msg)
23
33
  end
24
34
 
35
+ # Log a warning.
36
+ #
37
+ # @param msg [String] message
25
38
  def warn(msg)
26
39
  logger.warn(msg)
27
40
  end
28
41
 
42
+ # Log an error message.
43
+ #
44
+ # @param msg [String] message
29
45
  def error(msg)
30
46
  logger.error(msg)
31
47
  end
32
48
 
49
+ # Log a critical error message.
50
+ #
51
+ # @param msg [String] message
33
52
  def critical(msg)
34
53
  logger.critical(msg)
35
54
  end
36
55
 
56
+ # Log an alert message.
57
+ #
58
+ # @param msg [String] message
37
59
  def alert(msg)
38
60
  logger.alert(msg)
39
61
  end
40
62
 
63
+ # Log an emergency message.
64
+ #
65
+ # @param msg [String] message
41
66
  def emergency(msg)
42
67
  logger.emergency(msg)
43
68
  end
44
69
 
70
+ # Set reporting level.
45
71
  def reporting_level=(lvl)
46
- logger.min_priority = lvl
72
+ lvl = level_str_to_int(lvl) if lvl.respond_to?(:to_str)
73
+
74
+ if logger.respond_to?(:min_priority)
75
+ logger.min_priority = lvl
76
+ else
77
+ logger.level = lvl
78
+ end
47
79
  end
48
80
 
49
81
  private
50
82
 
83
+ def level_str_to_int(value)
84
+ case value
85
+ when /^debug$/i then const_get(:DEBUG)
86
+ when /^info$/i then const_get(:INFO)
87
+ when /^warn$/i then const_get(:WARN)
88
+ when /^error$/i then const_get(:ERROR)
89
+ else
90
+ raise "Unknown log level #{value.inspect}"
91
+ end
92
+ end
93
+
51
94
  def logger
52
- @logger ||= Journald::Logger.new('kstor')
95
+ @logger ||= create_logger
53
96
  end
54
97
  end
55
98
  end
56
99
  end
100
+
101
+ if ENV['INIT_IS_SYSTEMD']
102
+ require 'kstor/log/systemd_logger'
103
+ else
104
+ require 'kstor/log/simple_logger'
105
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KStor
4
+ # Units of communication between server and clients.
5
+ module Message
6
+ # A user request or response.
7
+ class Base
8
+ attr_reader :args
9
+
10
+ # Create new message.
11
+ #
12
+ # @param args [Hash] message arguments
13
+ # @param auth [Hash] authentication data
14
+ # @return [KStor::Message::Base] new message
15
+ def initialize(args, auth = {})
16
+ @args = check_args!(args)
17
+ @auth = check_auth!(auth.compact)
18
+ end
19
+
20
+ # Message type.
21
+ def type
22
+ self.class.type
23
+ end
24
+
25
+ # User login
26
+ def login
27
+ @auth[:login]
28
+ end
29
+
30
+ # User password
31
+ def password
32
+ @auth[:password]
33
+ end
34
+
35
+ # User session ID
36
+ def session_id
37
+ @auth[:session_id]
38
+ end
39
+
40
+ # True if this message is a request.
41
+ def request?
42
+ self.class.request
43
+ end
44
+
45
+ # True if this message is a response.
46
+ def response?
47
+ !request?
48
+ end
49
+
50
+ # True if this message is an error response.
51
+ def error?
52
+ response? && type == 'error'
53
+ end
54
+
55
+ # True if this message is a request and has login and password arguments.
56
+ def login_request?
57
+ request? && @auth.key?(:login) && @auth.key?(:password)
58
+ end
59
+
60
+ # True if this message is a request and has a session ID.
61
+ def session_request?
62
+ request? && @auth.key?(:session_id)
63
+ end
64
+
65
+ # Convert this message to a Hash
66
+ #
67
+ # @return [Hash] this message as a Hash.
68
+ def to_h
69
+ h = { 'type' => type, 'args' => @args }
70
+ if login_request?
71
+ h['login'] = @auth[:login]
72
+ h['password'] = @auth[:password]
73
+ elsif session_request? || response?
74
+ h['session_id'] = @auth[:session_id]
75
+ end
76
+
77
+ h
78
+ end
79
+
80
+ # Hide sensitive information when debugging
81
+ def inspect
82
+ if login_request?
83
+ inspect_login_request
84
+ elsif session_request? || response?
85
+ inspect_session_request_or_response
86
+ else
87
+ raise 'WTFBBQ?!???1!11!'
88
+ end
89
+ end
90
+
91
+ # Serialize this message to JSON.
92
+ #
93
+ # @return [String] JSON data.
94
+ def serialize
95
+ to_h.to_json
96
+ end
97
+
98
+ class << self
99
+ attr_reader :type
100
+ attr_reader :request
101
+ attr_reader :registry
102
+ attr_reader :arg_names
103
+
104
+ # Declare message type and direction (request or response).
105
+ def message_type(name, request: nil, response: nil)
106
+ @type = name
107
+ if (request && response) || (!request && !response)
108
+ raise 'Is it a request or a response type?!?'
109
+ end
110
+
111
+ @request = !!request
112
+ end
113
+
114
+ # Declare an argument to this message type.
115
+ def arg(name)
116
+ @arg_names ||= []
117
+ @arg_names << name.to_s
118
+ define_method(name) do
119
+ @args[name.to_s]
120
+ end
121
+ end
122
+
123
+ # Create a new message of the given type
124
+ def for_type(name, args, auth)
125
+ klass = @registry[name.to_sym]
126
+ raise "unknown message type #{name.inspect}" unless klass
127
+
128
+ klass.new(args, auth)
129
+ end
130
+
131
+ # True if message type "name" is known.
132
+ def type?(name)
133
+ @registry.key?(name.to_sym)
134
+ end
135
+
136
+ # List of known types.
137
+ def types
138
+ @registry.types
139
+ end
140
+
141
+ # Parse message.
142
+ def parse(str)
143
+ data = JSON.parse(str)
144
+ type = data.delete('type').to_sym
145
+ args = data.delete('args').transform_keys(&:to_s)
146
+ auth = data.transform_keys(&:to_sym)
147
+ for_type(type, args, auth)
148
+ rescue JSON::ParserError
149
+ raise UnparsableResponse
150
+ end
151
+
152
+ # Register new message type.
153
+ def register(klass)
154
+ @registry ||= {}
155
+ unless klass.respond_to?(:type) && klass.respond_to?(:request)
156
+ raise "#{klass} is not a subclass of #{self}"
157
+ end
158
+
159
+ if @registry.key?(klass.type)
160
+ message_type = subclass.type
161
+ old_klass = @registry[message_type]
162
+ raise "duplicate message type #{message_type} in #{klass}, " \
163
+ "already defined in #{old_klass}"
164
+ end
165
+ @registry[klass.type] = klass
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def check_auth!(opts)
172
+ return opts if response?
173
+ return opts if opts.key?(:login) && opts.key?(:password)
174
+ return opts if opts.key?(:session_id)
175
+
176
+ raise RequestMissesAuthData
177
+ end
178
+
179
+ def check_args!(raw_args)
180
+ args = self.class.arg_names.to_h { |name| [name, raw_args[name]] }
181
+ missing = args.select { |_, v| v.nil? }.keys
182
+ unless missing.empty?
183
+ raise MissingMessageArgument.new(missing.inspect, type)
184
+ end
185
+
186
+ args
187
+ end
188
+
189
+ def inspect_login_request
190
+ base_inspect(
191
+ ['@login=%<login>s', '@password="******"'],
192
+ login: @auth[:login].inspect
193
+ )
194
+ end
195
+
196
+ def inspect_session_request_or_response
197
+ base_inspect(['@session_id=******'])
198
+ end
199
+
200
+ def base_inspect(fmt_parts, **fmt_args)
201
+ begin_fmt = ["#<#{self.class}:%<id>x", '@type=%<type>s']
202
+ end_fmt = ['@args=%<args>s>']
203
+ fmt = (begin_fmt + fmt_parts + end_fmt).join(' ')
204
+ args = fmt_args.merge(
205
+ id: object_id, type: type.inspect, args: @args.inspect
206
+ )
207
+ format(fmt, **args)
208
+ end
209
+ end
210
+
211
+ class << self
212
+ # Register new message type.
213
+ def register_all_message_types
214
+ constants(false).each do |const|
215
+ klass = const_get(const)
216
+ next unless klass.respond_to?(:superclass) && klass.superclass == Base
217
+
218
+ Base.register(klass)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Error response.
8
+ class Error < Base
9
+ message_type :error, response: true
10
+
11
+ arg :code
12
+ arg :message
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to create a group of users.
8
+ class GroupCreate < Base
9
+ message_type :group_create, request: true
10
+
11
+ arg :name
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Response for group created.
8
+ class GroupCreated < Base
9
+ message_type :group_created, response: true
10
+
11
+ arg :group_id
12
+ arg :group_name
13
+ arg :group_pubk
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Ping server.
8
+ class Ping < Base
9
+ message_type :ping, request: true
10
+
11
+ arg :payload
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Ping response.
8
+ class Pong < Base
9
+ message_type :pong, response: true
10
+
11
+ arg :payload
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to create a secret.
8
+ class SecretCreate < Base
9
+ message_type :secret_create, request: true
10
+
11
+ arg :meta
12
+ arg :group_ids
13
+ arg :plaintext
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Response for secret created.
8
+ class SecretCreated < Base
9
+ message_type :secret_created, response: true
10
+
11
+ arg :secret_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to delete a secret.
8
+ class SecretDelete < Base
9
+ message_type :secret_delete, request: true
10
+
11
+ arg :secret_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Response for secret deleted.
8
+ class SecretDeleted < Base
9
+ message_type :secret_deleted, response: true
10
+
11
+ arg :secret_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Response for secret search.
8
+ class SecretList < Base
9
+ message_type :secret_list, response: true
10
+
11
+ arg :secrets
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to search secrets.
8
+ class SecretSearch < Base
9
+ message_type :secret_search, request: true
10
+
11
+ arg :meta
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to decrypt a secret.
8
+ class SecretUnlock < Base
9
+ message_type :secret_unlock, request: true
10
+
11
+ arg :secret_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to update secret metadata.
8
+ class SecretUpdateMeta < Base
9
+ message_type :secret_update_meta, request: true
10
+
11
+ arg :meta
12
+ arg :secret_id
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Request to update secret value.
8
+ class SecretUpdateValue < Base
9
+ message_type :secret_update_value, request: true
10
+
11
+ arg :plaintext
12
+ arg :secret_id
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Response for secret updated.
8
+ class SecretUpdated < Base
9
+ message_type :secret_updated, response: true
10
+
11
+ arg :secret_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kstor/message/base'
4
+
5
+ module KStor
6
+ module Message
7
+ # Response for secret unlock.
8
+ class SecretValue < Base
9
+ message_type :secret_value, response: true
10
+
11
+ arg :id
12
+ arg :value_author
13
+ arg :metadata_author
14
+ arg :metadata
15
+ arg :plaintext
16
+ arg :groups
17
+
18
+ # Create a new secret-value response.
19
+ #
20
+ # @option opts [Integer] :id ID of secret
21
+ # @option opts [Hash] :value_author info on user that created secret value
22
+ # @option opts [Hash] :metadata_author info on user that created secret
23
+ # metadata
24
+ # @option opts [Hash] :metadata metadata of this secret
25
+ # @option opts [String] :plaintext decrypted value of secret
26
+ # @option opts [Array[Hash]] :groups list of groups that can decrypt this
27
+ # secret
28
+ # @param opts [Hash[Symbol, String]] common request options
29
+ def initialize(**opts)
30
+ super(**opts)
31
+ opts.delete(:group_id)
32
+ end
33
+ end
34
+ end
35
+ end