mkit 0.4.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb997b1bc61151dde6209eeaefdf4f669df9255b7e69735eb0c5c56e346222f1
4
- data.tar.gz: 3819a29d9335a2d4831051d133d9e0056b93d54803701a27ed596a750f907d7b
3
+ metadata.gz: ca554f25f295a7403d5f0d654663d466430a361c8aa2629961f1710579e62506
4
+ data.tar.gz: d670fcc4523810439d73244276766a8e5ff70f2b8a052b292941014a11e7ca3b
5
5
  SHA512:
6
- metadata.gz: c95a967e4a016554700b9e844de87481fd8f4b1f71e2cb4cdae16509104b177485f6cdcc3aa114f3d4ab8680e8d39fb05c125fca4dd81edbb089800d08ec275e
7
- data.tar.gz: f62ac16133370c7bc41f901fe148b708c046f4598331848daefd4bb108419f7b2fc510a9176bff771fbff3afa4e38d5ce000d70d8cad1feeb41b8f339956d802
6
+ metadata.gz: 825f5736fe72ce3bec3aab11f5edfa75b286a6e08ff4e6dc44be37c6d11a034e84a0910fca6ce78cb05ead4483ec228c368a749a301793396ddfc55043fc5bf9
7
+ data.tar.gz: 5b0c25f145e1c5ec19d8242d8e613c6746fc3ea25f4f1df0f39dc434ee43e56d480b399075230154d547abad466555f884077f1a1fff08590005645a762b3279
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  This is micro kubernetes(tm) on Ruby(tm), a simple tool to deploy containers to mimic a (very) minimalistic k8 cluster with a nice REST API.
4
4
 
5
+ It's also a frontend for `docker`, providing an easier way for your services to be locally available, without the need to care about local `ports` availability.
6
+
5
7
  It contains an internal DNS and uses HAProxy for routing/balancing/fail-over for Pods access.
6
8
  The database is a simple sqlite3 db and the server is a Sinatra based application.
7
9
 
@@ -16,86 +18,57 @@ The daemon is responsible for HAProxy pods routing configuration. It also provid
16
18
  * Docker
17
19
  * Linux (iproute2 package)
18
20
 
21
+ **Note:** in order to have **ssl support**, you must install `openssl-dev` package (e.g. `libssl-dev` on Ubuntu) prior to install MKIt gem.
22
+
19
23
  ## Install
20
24
 
21
- This is a simple ruby gem, so to install run
25
+ This is a simple ruby gem, so to install execute:
22
26
  ```
23
27
  # gem install mkit
24
28
  ```
25
29
 
26
- ## Running
27
-
28
- The `daemon` requires `root` user (due to `ip` and `haproxy`), you can run it directly on the repository root...
29
-
30
- ```
31
- # ./mkitd --help
32
- Usage: mkitd [options]
33
- -c config-dir set the config dir (default is /etc/mkit)
34
- -p port set the port (default is 4567)
35
- -b bind specify bind address (e.g. /tmp/app.sock)
36
- -s server specify rack server/handler
37
- -q turn on quiet mode (default is off)
38
- -x turn on the mutex lock (default is off)
39
- -e env set the environment (default is development)
40
- -o addr set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))
41
-
42
- ```
43
-
44
- or after the `gem install mkit-<version>.gem`. The server and client will be installed on host.
45
-
46
- ```
47
- # mkitd
48
- ...
49
- 0.65s info: MKIt is up and running! [ec=0xbe0] [pid=45804] [2023-12-29 15:46:04 +0000]
50
- ```
30
+ ## Configuration
51
31
 
52
- There's also samples on the samples dir, for daemontools and systemd.
32
+ ### Server configuration
53
33
 
54
- ### Accessing the API
34
+ On startup, [the server configuration](config) will be created on `/etc/mkit`.
55
35
 
56
- A client is provided to interact with mkit server.
36
+ The server will available by default on `https://localhost:4567` but you can configure server startup parameters on `/etc/mkit/mkitd_config.sh`
57
37
 
58
- Run `mkitc help` to list current supported commands.
38
+ Please check [systemd](samples/systemd) or [daemontools](samples/daemontools) directories for more details.
59
39
 
60
40
  ```
61
- Usage: mkitc <command> [options]
62
-
63
- Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster
64
-
65
- Commands:
66
-
67
- ps show services status (alias for status)
68
- status show services status
69
- logs prints service logs
70
- start start service
71
- stop stop service
72
- restart restart service
73
- create create new service
74
- update update service
75
- rm remove service
76
- version prints mkit server version
77
- proxy haproxy status and control
78
-
79
- Run 'mkitc help <command>' for specific command information.
41
+ # /etc/mkit/mkitd_config.sh
42
+ #
43
+ # mkitd server options (for systemd unit | daemontools)
44
+ #
45
+ OPTIONS=""
46
+ # e.g. OPTIONS="-b 0.0.0.0"
80
47
  ```
81
-
82
- Example:
48
+ HAProxy config directory and control commands are defined on [mkit_config.yml](config/mkit_config.yml)
83
49
 
84
50
  ```
85
- $ mkitc ps postgres
86
- +----+----------+---------------+----------+--------------+---------+
87
- | id | name | addr | ports | pods | status |
88
- +----+----------+---------------+----------+--------------+---------+
89
- | 2 | postgres | 10.210.198.10 | tcp/4532 | 49b5e4c8f247 | RUNNING |
90
- +----+----------+---------------+----------+--------------+---------+
51
+ # /etc/mkit/mkit_config.yml - mkit server configuration file.
52
+ mkit:
53
+ my_network:
54
+ ip: 10.210.198.1
55
+ haproxy:
56
+ config_dir: /etc/haproxy/haproxy.d
57
+ ctrl:
58
+ start: systemctl start haproxy
59
+ stop: systemctl stop haproxy
60
+ reload: systemctl reload haproxy
61
+ restart: systemctl restart haproxy
62
+ status: systemctl status haproxy
63
+ database:
64
+ env: development
65
+ clients:
66
+ - id: client_1_id
67
+ - id: client_2_id
68
+ - ...
91
69
  ```
92
- The service `postgres` is available on IP `10.210.198.10:5432`
93
-
94
- ## Configuration
95
-
96
- On startup, configuration files on `config` directory will be copied to `/etc/mkit`. HAProxy config directory and control commands are defined on `mkit_config.yml`
97
70
 
98
- You must configure `haproxy` to use config directory. e.g. on Ubuntu
71
+ You must configure `haproxy` to use config directory. for example on Ubuntu:
99
72
 
100
73
  ```
101
74
  # /etc/default/haproxy
@@ -112,6 +85,41 @@ CONFIG="/etc/haproxy/haproxy.d"
112
85
  #EXTRAOPTS="-de -m 16"
113
86
  ```
114
87
 
88
+ #### Authorization
89
+
90
+ To access MKIt server API, you must add each client `id` to server configuration:
91
+
92
+ ```
93
+ # /etc/mkit/mkit_config.yml - mkit server configuration file.
94
+ mkit:
95
+ my_network:
96
+ ...
97
+ clients:
98
+ - id: client_1_id
99
+ - id: client_2_id
100
+ - ...
101
+ ```
102
+
103
+ ### Client configuration
104
+
105
+ On `mkitc` first call, default configuration will be copied to `$HOME/.mkit` with `local`default profile set.
106
+
107
+ You must call `mkitc init` to initialize client configuration.
108
+
109
+ Client identification key (`my_id`) will be generated, printed out to console and saved to the client's configuration file.
110
+
111
+ You may edit the local configuration file to add more servers and change active profile with `$mkitc profile set <profile_name>`, e.g. `$mkitc profile set server_2`
112
+
113
+ ```
114
+ # ~/.mkit/mkitc_config.yml
115
+ mkit:
116
+ local:
117
+ server.uri: https://localhost:4567
118
+ server_2: # you can add more servers. change the client active profile with mkitc profile command
119
+ server.uri: https://192.168.29.232:4567
120
+ my_id: unique_id # this id is generated running mkitc init
121
+ ```
122
+
115
123
  ### Service
116
124
 
117
125
  ```
@@ -123,7 +131,12 @@ service:
123
131
  # <external_port>:[internal_port]:<tcp|http>:[round_robin (default)|leastconn]
124
132
  # to define a range on `external_port`, leave `internal_port` blank
125
133
  # - 5000-5100::tcp:round_robin
126
- # range on `internal_port` is not supported
134
+ # range on `internal_port` is not supported
135
+ # ssl suport
136
+ # <external_port>:[internal_port]:<tcp|http>:round_robin|leastconn[:ssl[,<cert.pem>(mkit.pem default)>]]
137
+ # e.g.
138
+ # - 443:80:http:round_robin:ssl # uses mkitd default crt file (mkit.pem)
139
+ # - 443:80:http:round_robin:ssl,/etc/pki/foo.pem # custom crt file full path
127
140
  - 5672:5672:tcp:round_robin
128
141
  - 80:15672:http:round_robin
129
142
  resources:
@@ -138,6 +151,72 @@ service:
138
151
  RABBITMQ_DEFAULT_VHOST: mkit
139
152
  ```
140
153
 
154
+ ## Running
155
+
156
+ The `mkitd server daemon` requires `root` user (due to `ip` and `haproxy`).
157
+ After installing the gem, server and client will be available on host.
158
+ ```
159
+ # mkitd --help
160
+ Usage: mkitd [options]
161
+ -c config-dir set the config dir (default is /etc/mkit)
162
+ -p port set the port (default is 4567)
163
+ -b bind specify bind address (e.g. 0.0.0.0)
164
+ -e env set the environment (default is development)
165
+ -o addr alias for '-b' option
166
+ --no-ssl disable ssl - use http for local server. (default is https)
167
+ --ssl-key-file PATH Path to private key (default mkit internal)
168
+ --ssl-cert-file PATH Path to certificate (default mkit internal)
169
+ ```
170
+
171
+ There's also samples for [systemd](samples/systemd) and [daemontools](samples/daemontools) as well for some miscellaneous [spps](samples/apps).
172
+
173
+ ### Accessing the API
174
+
175
+ A client is provided to interact with MKIt server.
176
+
177
+ Run `mkitc help` for a list of current supported commands.
178
+
179
+ ```
180
+ Usage: mkitc <command> [options]
181
+
182
+ Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster
183
+
184
+ Commands:
185
+
186
+ init init mkit client
187
+ ps show services status (alias for status)
188
+ status show services status
189
+ logs prints service logs
190
+ start start service
191
+ stop stop service
192
+ restart restart service
193
+ create create new service
194
+ update update service
195
+ rm remove service
196
+ version prints mkit server version
197
+ proxy haproxy status and control
198
+ profile mkit client configuration profile
199
+
200
+ Run 'mkitc help <command>' for specific command information.
201
+ ```
202
+
203
+ Example:
204
+
205
+ ```
206
+ $ mkitc ps
207
+ +----+-------+---------------+-------------------+--------------+---------+
208
+ | id | name | addr | ports | pods | status |
209
+ +----+-------+---------------+-------------------+--------------+---------+
210
+ | 1 | mongo | 10.210.198.10 | tcp/27017 | 106e2b59cb11 | RUNNING |
211
+ | 2 | nexus | 10.210.198.11 | http/80,https/443 | 68e239e5102a | RUNNING |
212
+ +----+-------+---------------+-------------------+--------------+---------+
213
+ ```
214
+ The service `mongo` is available on IP `10.210.198.10:27017`
215
+ The service `nexus` is available on IP `10.210.198.11:80` and on port `443` with ssl.
216
+
217
+ **Note:** Don't forget to call `mkitc init` to initialize client configuration and to add the `client-id`
218
+ to the server authorized clients list.
219
+
141
220
  ## Development
142
221
 
143
222
  * build the gem
data/bin/mkitc CHANGED
@@ -9,25 +9,29 @@ require 'json'
9
9
  require 'net_http_unix'
10
10
  require 'securerandom'
11
11
  require 'erb'
12
+ require 'uri'
13
+ require 'fileutils'
12
14
 
13
- class InvalidParametersException < RuntimeError
15
+ class InvalidParametersException < Exception
14
16
  attr_reader :command
17
+
15
18
  def initialize(cause, command = nil)
16
19
  super(cause)
17
20
  @command = command
18
21
  end
19
22
  end
20
23
 
21
- class MKItClient
22
- def initialize
23
- @client = NetX::HTTPUnix.new('localhost', 4567)
24
- end
25
-
26
- def dict
24
+ class CommandPalette
25
+ def schema
27
26
  global_args = [
28
27
  { short: '-v', long: '--verbose', help: 'verbose', mandatory: false, value: nil }
29
28
  ]
30
- command_dict = [
29
+ [
30
+ {
31
+ cmd: 'init',
32
+ help: 'init mkit client',
33
+ request: { }
34
+ },
31
35
  {
32
36
  cmd: 'ps',
33
37
  args: [
@@ -140,69 +144,96 @@ class MKItClient
140
144
  ],
141
145
  help: 'haproxy status and control',
142
146
  usage: ['<start|stop|restart|status>']
147
+ },
148
+ {
149
+ cmd: 'profile',
150
+ options: [
151
+ {
152
+ cmd: 'set',
153
+ request: { verb: 'set' },
154
+ args: [
155
+ { name: 'profile_name', mandatory: true }
156
+ ],
157
+ help: 'set mkit client configuration profile'
158
+ },
159
+ {
160
+ cmd: 'show',
161
+ request: { verb: 'show' },
162
+ help: 'show mkit client current profile'
163
+ }
164
+ ],
165
+ help: 'mkit client configuration profile',
166
+ usage: ['<[set <profile_name>]|[show]>']
143
167
  }
144
168
  ]
145
- command_dict
146
169
  end
170
+ end
147
171
 
148
- def help(cause: nil, cmd: nil)
149
- msg = ''
150
- if cause.nil?
151
- my_cmd = cmd
152
- else
153
- msg += "MKItc: #{cause.message}\n"
154
- my_cmd = cause.command
155
- end
156
- if my_cmd.nil?
157
- msg += "\nUsage: mkitc <command> [options]\n\n"
158
- msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n"
159
- msg += "Commands:\n\n"
160
- dict.each do |c|
161
- msg += format("%-10s %s\n", c[:cmd], c[:help])
162
- end
163
- msg += "\n"
164
- msg += "Run 'mkitc help <command>' for specific command information.\n\n"
165
- else
166
- msg += format("\nUsage: mkitc %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' '))
167
- msg += format("%s\n", my_cmd[:help])
168
- unless my_cmd[:options].nil?
169
- msg += "\nOptions:\n"
170
- my_cmd[:options].each do |c|
171
- msg += format("%-10s %s\n", c[:cmd], c[:help])
172
- end
173
- end
174
- msg += "\n"
172
+ class MKItClient
173
+ def initialize
174
+ @root = File.expand_path('..', __dir__)
175
+ @config_dir = "#{ENV['HOME']}/.mkit"
176
+ @profile_file = "#{@config_dir}/current"
177
+ @commands = CommandPalette.new
178
+ @config_file = "#{@config_dir}/mkitc_config.yml"
179
+ create_default_config
180
+ end
181
+
182
+ def create_default_config
183
+ unless File.exist?(@config_dir)
184
+ puts "Creating config directory on '#{@config_dir}'..."
185
+ FileUtils.mkdir_p(@config_dir)
175
186
  end
176
- puts msg
177
- exit 1
187
+ FileUtils.cp("#{@root}/config/mkitc_config.yml", @config_dir) unless File.exist?(@config_file)
188
+ profile({ verb: 'set' }, { profile_name: 'local' }) unless File.exist?(@profile_file)
178
189
  end
179
190
 
180
- def create(request, request_hash = nil)
181
- unless File.file?(request_hash[:file])
182
- raise InvalidParametersException.new('File not found.', c = dict.select { |k| k[:cmd] == 'create' }.first)
191
+ def read_configuration(init_call = false)
192
+ current_profile = File.read(@profile_file)
193
+ if current_profile.nil? || current_profile.empty?
194
+ # force set default
195
+ profile({ verb: 'set' }, { profile_name: 'local' })
196
+ current_profile = 'local'
183
197
  end
198
+ cfg = YAML.load_file(@config_file)
184
199
 
185
- yaml = YAML.load_file(request_hash[:file])
186
- if yaml['service'].nil?
187
- raise InvalidParametersException.new('Invalid configuration file', c = dict.select { |k| k[:cmd] == 'create' }.first)
188
- else
189
- request(request, request_hash)
200
+ if cfg['mkit'].nil? || cfg['mkit'][current_profile.lstrip].nil?
201
+ raise InvalidParametersException, "invalid configuration found on '~/.mkit' or profile not found"
190
202
  end
191
- end
192
203
 
193
- def update(request, request_hash = nil)
194
- unless File.file?(request_hash[:file])
195
- raise InvalidParametersException.new('File not found.', c = dict.select { |k| k[:cmd] == 'update' }.first)
204
+ @configuration = cfg['mkit'][current_profile.lstrip]
205
+ if !init_call && cfg['my_id'].nil?
206
+ raise InvalidParametersException.new("Please run 'mkitc init' to initialize mkit client.", find_command('init'))
196
207
  end
208
+ @my_id = cfg['my_id']
209
+ cfg
210
+ end
197
211
 
198
- yaml = YAML.load_file(request_hash[:file])
199
- if yaml['service'].nil?
200
- raise InvalidParametersException.new('Invalid configuration file', c = dict.select { |k| k[:cmd] == 'update' }.first)
212
+ def client(req)
213
+ read_configuration
214
+ req['X-API-KEY'] = @my_id
215
+ uri = URI(@configuration['server.uri'])
216
+ case uri.scheme
217
+ when 'https'
218
+ @client = NetX::HTTPUnix.new(uri.host, uri.port)
219
+ @client.use_ssl = true
220
+ @client.verify_mode = OpenSSL::SSL::VERIFY_NONE
221
+ when 'http'
222
+ @client = NetX::HTTPUnix.new(uri.host, uri.port)
223
+ when 'sock'
224
+ @client = NetX::HTTPUnix.new("unix://#{uri.path}")
201
225
  else
202
- id = yaml['service']['name']
203
- request_hash[:id] = id
204
- request(request, request_hash)
226
+ raise InvalidParametersException, 'Invalid mkit server uri. Please check configuration'
205
227
  end
228
+ @client.request(req)
229
+ end
230
+
231
+ def dict
232
+ @commands.schema
233
+ end
234
+
235
+ def find_command(cmd)
236
+ dict.select { |k| k[:cmd] == cmd }.first
206
237
  end
207
238
 
208
239
  def parse_args(args)
@@ -211,55 +242,35 @@ class MKItClient
211
242
  # short circuit for help
212
243
  if cmd == 'help' || args.empty?
213
244
  if args.size > 1
214
- c = dict.select { |k| k[:cmd] == args[1] }.first
245
+ c = find_command(args[1])
215
246
  raise InvalidParametersException, "'#{args[1]}' is not a valid help topic." if c.nil?
216
247
  end
217
248
  return help(cmd: c)
218
249
  else
219
- c = dict.select { |k| k[:cmd] == cmd }.first
250
+ c = find_command(cmd)
220
251
  end
221
252
  raise InvalidParametersException, 'Command not found' if c.nil?
222
253
 
254
+ command = c
223
255
  myargs = args.dup
224
256
  myargs.delete(cmd)
225
257
 
226
- max_args_size = c[:args].nil? ? 0 : c[:args].size
227
- max_options_size = c[:options].nil? ? 0 : 1
228
- max_args_size += max_options_size
229
-
230
- min_args_size = c[:args].nil? ? 0 : c[:args].select { |a| a[:mandatory] == true }.size
231
- min_options_size = c[:options].nil? ? 0 : 1
232
- min_args_size += min_options_size
233
-
234
- if myargs.size > max_args_size || myargs.size < min_args_size
235
- raise InvalidParametersException.new('Invalid parameters found.', c)
236
- end
237
-
238
258
  request_hash = {}
239
- request = c[:request]
259
+ request = command[:request]
240
260
  unless myargs.empty?
241
- unless c[:args].nil?
242
- idx = 0
243
- c[:args].each do |a|
244
- request_hash[a[:name].to_sym] = myargs[idx]
245
- request[:uri] = request[:uri] + a[:uri] unless a[:uri].nil?
246
- idx += 1
247
- end
248
- end
249
261
  # options
250
262
  unless c[:options].nil?
251
- option = nil
252
- myargs.each do |s|
253
- option = c[:options].select { |o| o[:cmd] == s }.first
254
- raise InvalidParametersException.new('Invalid parameters found.', c) if option.nil? || option.empty?
255
- end
256
- raise InvalidParametersException.new('Invalid parameters found.', c) if option.nil? || option.empty?
263
+ command = c[:options].select { |o| o[:cmd] == myargs[0] }.first
264
+ raise InvalidParametersException.new('Invalid parameters found.', c) if command.nil? || command.empty?
257
265
 
258
- request = option[:request]
266
+ myargs.delete_at(0)
267
+ request = command[:request]
259
268
  end
269
+ fill_cmd_args(command[:args], myargs, request, request_hash)
260
270
  end
261
- raise InvalidParametersException, "Can't find request." if request.nil?
271
+ raise InvalidParametersException.new('Invalid command or parameters.', c) if request.nil?
262
272
 
273
+ validate_command(command, request_hash)
263
274
  if respond_to? c[:cmd]
264
275
  send(c[:cmd], request, request_hash)
265
276
  else
@@ -267,11 +278,25 @@ class MKItClient
267
278
  end
268
279
  end
269
280
 
270
- def doIt(args)
271
- result = parse_args(args)
272
- puts result
273
- rescue InvalidParametersException => e
274
- help(cause: e)
281
+ def fill_cmd_args(args, myargs, request, request_hash)
282
+ return if args.nil?
283
+
284
+ idx = 0
285
+ args.each do |a|
286
+ request_hash[a[:name].to_sym] = myargs[idx]
287
+ request[:uri] = request[:uri] + a[:uri] unless a[:uri].nil?
288
+ idx += 1
289
+ end
290
+ end
291
+
292
+ def validate_command(command, request_hash)
293
+ return if command[:args].nil?
294
+
295
+ command[:args].select { |a| a[:mandatory] == true }.each do |a|
296
+ if request_hash[a[:name].to_sym].nil?
297
+ raise InvalidParametersException.new("Missing mandatory parameter: #{a[:name]}", command)
298
+ end
299
+ end
275
300
  end
276
301
 
277
302
  def request(request, request_args = nil)
@@ -304,7 +329,7 @@ class MKItClient
304
329
  when :delete
305
330
  req = Net::HTTP::Delete.new(uri)
306
331
  end
307
- @client.request(req).body
332
+ client(req).body
308
333
  end
309
334
 
310
335
  def attach(file)
@@ -318,6 +343,118 @@ class MKItClient
318
343
  body << "\r\n--#{boundary}--\r\n"
319
344
  [body.join, boundary]
320
345
  end
346
+
347
+ def doIt(args)
348
+ result = parse_args(args)
349
+ puts result
350
+ rescue InvalidParametersException => e
351
+ help(cause: e)
352
+ end
353
+
354
+ def help(cause: nil, cmd: nil)
355
+ msg = ''
356
+ if cause.nil?
357
+ my_cmd = cmd
358
+ else
359
+ msg += "MKItc: #{cause.message}\n"
360
+ my_cmd = cause.command
361
+ end
362
+ if my_cmd.nil?
363
+ msg += "\nUsage: mkitc <command> [options]\n\n"
364
+ msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n"
365
+ msg += "Commands:\n\n"
366
+ dict.each do |c|
367
+ msg += format("%-10s %s\n", c[:cmd], c[:help])
368
+ end
369
+ msg += "\n"
370
+ msg += "Run 'mkitc help <command>' for specific command information.\n\n"
371
+ else
372
+ msg += format("\nUsage: mkitc %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' '))
373
+ msg += format("%s\n", my_cmd[:help])
374
+ unless my_cmd[:options].nil?
375
+ msg += "\nOptions:\n"
376
+ my_cmd[:options].each do |c|
377
+ msg += format("%-10s %s\n", c[:cmd], c[:help])
378
+ end
379
+ end
380
+ msg += "\n"
381
+ end
382
+ puts msg
383
+ exit 1
384
+ end
385
+
386
+ def init(request, request_hash = nil)
387
+ cfg = read_configuration(true)
388
+ if cfg['my_id'].nil?
389
+ my_id = SecureRandom.uuid.gsub('-','')
390
+ cfg['my_id'] = my_id
391
+ File.write(@config_file, cfg.to_yaml)
392
+ puts "Please check if your api-key is on mkitd server allowed keys"
393
+ else
394
+ my_id = cfg['my_id']
395
+ end
396
+ puts "Your api-key is #{my_id}"
397
+ end
398
+ def create(request, request_hash = nil)
399
+ unless File.file?(request_hash[:file])
400
+ raise InvalidParametersException.new('File not found.', find_command('create'))
401
+ end
402
+
403
+ yaml = YAML.load_file(request_hash[:file])
404
+ if yaml['service'].nil?
405
+ raise InvalidParametersException.new('Invalid configuration file', find_command('create'))
406
+ else
407
+ request(request, request_hash)
408
+ end
409
+ end
410
+
411
+ def update(request, request_hash = nil)
412
+ unless File.file?(request_hash[:file])
413
+ raise InvalidParametersException.new('File not found.', find_command('update'))
414
+ end
415
+
416
+ yaml = YAML.load_file(request_hash[:file])
417
+ if yaml['service'].nil?
418
+ raise InvalidParametersException.new('Invalid configuration file', find_command('update'))
419
+ else
420
+ id = yaml['service']['name']
421
+ request_hash[:id] = id
422
+ request(request, request_hash)
423
+ end
424
+ end
425
+
426
+ def profile(request, request_hash = {})
427
+ cfg = YAML.load_file("#{@config_dir}/mkitc_config.yml")
428
+ cmd = find_command('profile')
429
+ if cfg['mkit'].nil?
430
+ raise InvalidParametersException.new(
431
+ "Invalid configuration on '~/.mkit'\nPlease fix or clean up for defaults apply", cmd
432
+ )
433
+ end
434
+
435
+ case request[:verb]
436
+ when 'set'
437
+ profile = request_hash[:profile_name]
438
+ if cfg['mkit'][profile.lstrip].nil?
439
+ raise InvalidParametersException.new("Profile not found on '~/.mkit' configuration", cmd)
440
+ end
441
+
442
+ puts "Setting current profile to #{profile}."
443
+ File.write(@profile_file, request_hash[:profile_name])
444
+ ''
445
+ when 'show'
446
+ active = File.read("#{@config_dir}/current")
447
+ cfg['mkit'].map do |k, _v|
448
+ if k == active
449
+ "*#{k}"
450
+ else
451
+ k
452
+ end
453
+ end.join(' ')
454
+ else
455
+ raise InvalidParametersException.new("Invalid 'profile' operation", cmd)
456
+ end
457
+ end
321
458
  end
322
459
 
323
460
  #
data/bin/mkitd CHANGED
@@ -9,9 +9,7 @@ def up
9
9
  require 'sinatra'
10
10
  require 'sinatra/base'
11
11
  require 'mkit'
12
- # defaults
13
- PARAMS_CONFIG[:bind] ||= 'localhost'
14
- PARAMS_CONFIG[:port] ||= 4567
12
+
15
13
  MKIt.startup(options: PARAMS_CONFIG)
16
14
 
17
15
  use Rack::MethodOverride
@@ -21,26 +19,27 @@ def up
21
19
 
22
20
  # sinatra::base ignores in parameters
23
21
  # set it here or via configure...
24
- Sinatra::Application.run!({ port: PARAMS_CONFIG[:port], bind: PARAMS_CONFIG[:bind] })
25
- # MKIt::Server.run
22
+ # Sinatra::Application.run!({ port: PARAMS_CONFIG[:port], bind: PARAMS_CONFIG[:bind] })
23
+ Sinatra::Application.run! do |server|
24
+ MKIt.options(server)
25
+ end
26
+
26
27
  end
27
28
 
28
29
  if ARGV.any?
29
30
  require 'optparse'
30
31
  parser = OptionParser.new do |op|
31
32
  op.on('-c config-dir', 'set the config dir (default is /etc/mkit)') { |val| PARAMS_CONFIG[:config_dir] = val }
32
- op.on('-p port', 'set the port (default is 4567)') { |val| PARAMS_CONFIG[:port] = Integer(val) }
33
- op.on('-b bind ', 'specify bind address (e.g. /tmp/app.sock)') { |val| PARAMS_CONFIG[:bind] = val }
34
- op.on('-s server', 'specify rack server/handler') { |val| PARAMS_CONFIG[:server] = val }
35
- op.on('-q', 'turn on quiet mode (default is off)') { PARAMS_CONFIG[:quiet] = true }
36
- op.on('-x', 'turn on the mutex lock (default is off)') { PARAMS_CONFIG[:lock] = true }
37
- op.on('-e env', 'set the environment (default is development)') do |val|
33
+ op.on('-p port', 'set the port (default is 4567)') { |val| PARAMS_CONFIG[:port] = Integer(val) }
34
+ op.on('-b bind', 'specify bind address (e.g. 0.0.0.0)') { |val| PARAMS_CONFIG[:bind] = val }
35
+ op.on('-o addr', 'alias for bind option') { |val| PARAMS_CONFIG[:bind] = val }
36
+ op.on('-e env', 'set the environment (default is development)') do |val|
38
37
  ENV['RACK_ENV'] = val
39
38
  PARAMS_CONFIG[:environment] = val.to_sym
40
39
  end
41
- op.on('-o addr', "set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))") do |val|
42
- PARAMS_CONFIG[:bind] = val
43
- end
40
+ op.on('--no-ssl', 'disable ssl - use http for local server. (default is https)') { PARAMS_CONFIG[:ssl] = false }
41
+ op.on('--ssl-key-file PATH', 'Path to private key (default mkit internal)') { |val| PARAMS_CONFIG[:private_key_file] = val }
42
+ op.on('--ssl-cert-file PATH', 'Path to certificate (default mkit internal)') { |val| PARAMS_CONFIG[:cert_chain_file] = val }
44
43
  end
45
44
  begin
46
45
  parser.parse!(ARGV.dup)
@@ -1,3 +1,4 @@
1
+ ---
1
2
  mkit:
2
3
  my_network:
3
4
  ip: 10.210.198.1
@@ -12,4 +13,5 @@ mkit:
12
13
  bin: /usr/sbin/haproxy
13
14
  database:
14
15
  env: development
15
-
16
+ clients:
17
+ - add_client_id: add client's id to allow access to this server
@@ -0,0 +1,3 @@
1
+ mkit:
2
+ local:
3
+ server.uri: https://localhost:4567
@@ -1,5 +1,5 @@
1
1
  #
2
- # kidsd server options (for systemd unit | daemontools)
2
+ # mkitd server options (for systemd unit | daemontools)
3
3
  #
4
4
  OPTIONS=""
5
5
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ServicePortsSsl< ActiveRecord::Migration[5.1]
4
+ def up
5
+ add_column :service_ports, :ssl, :string
6
+ add_column :service_ports, :crt, :string
7
+ end
8
+ end
@@ -24,9 +24,28 @@ module MKIt
24
24
  end
25
25
 
26
26
  def build_table_row(data)
27
- ports = data.service_port&.each.map { |p| "#{p.mode}/#{p.external_port}" }.join(',')
27
+ ports = data.service_port&.each.map { |p| build_port(p) }.join(',')
28
28
  pods = data.pod.each.map { |p| p.name.to_s }.join(' ')
29
29
  [data.id, data.name, data.lease&.ip, ports, pods, data.status]
30
30
  end
31
+
32
+ def build_port(p)
33
+ case p.mode
34
+ when 'http'
35
+ if p.ssl?
36
+ "#{p.mode}s/#{p.external_port}"
37
+ else
38
+ "#{p.mode}/#{p.external_port}"
39
+ end
40
+ when 'tcp'
41
+ if p.ssl?
42
+ "s#{p.mode}/#{p.external_port}"
43
+ else
44
+ "#{p.mode}/#{p.external_port}"
45
+ end
46
+ else
47
+ "#{p.mode}/#{p.external_port}"
48
+ end
49
+ end
31
50
  end
32
51
  end
@@ -9,6 +9,14 @@ module MKIt
9
9
  set :show_exceptions, false
10
10
  set :raise_errors, false
11
11
 
12
+ before do
13
+ api_key = request.env['HTTP_X_API_KEY']
14
+ cfg = YAML.load_file(MKIt::Config.config_file)
15
+ if cfg.nil? || cfg['mkit'].nil? || cfg['mkit']['clients'].nil? || !cfg['mkit']['clients'].map{|h| h['id']}.include?(api_key)
16
+ error 401, 'Unauthorized - please add your client-id to authorized clients list'
17
+ end
18
+ end
19
+
12
20
  error MKIt::BaseException do |e|
13
21
  MKItLogger.debug e
14
22
  error e.error_code, e.message
@@ -4,7 +4,7 @@ require 'mkit/exceptions'
4
4
  class ServicePort < ActiveRecord::Base
5
5
  belongs_to :service
6
6
 
7
- CONFIG_EXPRESSION=/^(.*?):(.*?):(tcp|http):(.*?)$/
7
+ CONFIG_EXPRESSION=/^(.*?):(.*?):(tcp|http):(.*?)($|:ssl$|:ssl,(.+))$/
8
8
 
9
9
  def self.create(service:, config:)
10
10
  sp = ServicePort.new(service: service, version: service.version)
@@ -18,22 +18,34 @@ class ServicePort < ActiveRecord::Base
18
18
  # # src:dest:tcp|http:round_robin|leastconn
19
19
  # - 5532:5432:tcp:round_robin
20
20
  # - 5532-6000::tcp:round_robin
21
+ # # ssl support:
22
+ # # src:dest:tcp|http:round_robin|leastconn[:ssl[,<cert.pem>(mkit.pem default)>]]
23
+ # - 443:80:tcp:round_robin:ssl # crt file is mkit.pem
24
+ # - 443:80:tcp:round_robin:ssl,/etc/pki/foo.pem # crt file full path
21
25
  # model:
22
26
  # service_ports:
23
27
  # - external: 5432
24
28
  # internal: 5432
25
29
  # mode: tcp|http
26
30
  # load_bal: round_robin
31
+ # ssl: true|false
32
+ # crt: full path
27
33
  def parse_config(config)
28
34
  ports = config.match(CONFIG_EXPRESSION)
29
35
  raise MKIt::InvalidPortsConfiguration.new("no match with config expression $#{CONFIG_EXPRESSION}") if ports.nil?
30
36
 
37
+ puts ports
31
38
  self.external_port = ports[1]
32
39
  self.internal_port = ports[2]
33
40
  self.mode = ports[3]
34
41
  self.load_bal = ports[4]
42
+ self.ssl = !ports[5].nil? && ports[5].start_with?(':ssl') ? 'true':'false'
43
+ self.crt = ports[7].nil? ? MKIt::Utils.proxy_cert : ports[7]
35
44
  end
36
45
 
46
+ def ssl?
47
+ self.ssl == 'true'
48
+ end
37
49
  def load_balance
38
50
  case self.load_bal
39
51
  when /^round_robin$/
@@ -6,7 +6,7 @@
6
6
  # start <%=name%>-<%=port.external_port%>
7
7
  #
8
8
  frontend <%=name%>-<%=port.external_port%>-front
9
- bind <%=lease.ip%>:<%=port.external_port%>
9
+ bind <%=lease.ip%>:<%=port.external_port%> <%=if port.ssl? then "ssl crt #{port.crt}" end%>
10
10
  mode <%=port.mode%>
11
11
  #
12
12
  use_backend <%=name%>-<%=port.external_port%>-back
@@ -6,13 +6,18 @@ require 'yaml'
6
6
  module MKIt
7
7
  module Config
8
8
  extend self
9
+
9
10
  def load_yml!(path)
11
+ @config_file = path
10
12
  @config = YAML.load(File.new(path).read).to_o
11
13
  end
14
+
15
+ def config_file
16
+ @config_file
17
+ end
12
18
  #
13
19
  def method_missing(name,*args)
14
20
  return @config.send(name,*args)
15
- super.method_missing name
16
21
  end
17
22
  end
18
23
  end
@@ -0,0 +1,28 @@
1
+ require 'openssl'
2
+
3
+ module MKIt
4
+ class EasySSL
5
+ def self.create_self_certificate(cert_dir)
6
+ unless File.exist?("#{cert_dir}/#{MKIt::Utils::MKIT_CRT}")
7
+ key = OpenSSL::PKey::RSA.new 4096
8
+ name = OpenSSL::X509::Name.parse '/CN=MKIt/DC=server'
9
+ cert = OpenSSL::X509::Certificate.new
10
+ cert.version = 2
11
+ cert.serial = 0
12
+ cert.not_before = Time.now
13
+ cert.not_after = Time.now + 20 * 365 * 24 * 60 * 60
14
+ cert.public_key = key.public_key
15
+ cert.subject = name
16
+ cert.issuer = name
17
+ cert.sign key, OpenSSL::Digest.new('SHA256')
18
+ # my cert and key files
19
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_CRT}", 'w' do |io| io.write cert.to_pem end
20
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_KEY}", 'w' do |io| io.write key.to_pem end
21
+ # haproxy default ssl cert
22
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_PEM}", 'w' do |io| io.write cert.to_pem end
23
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_PEM}", 'a' do |io| io.write key.to_pem end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
data/lib/mkit/utils.rb CHANGED
@@ -4,6 +4,10 @@ module MKIt
4
4
  module Utils
5
5
  module_function
6
6
 
7
+ MKIT_CRT = 'mkit.crt'
8
+ MKIT_KEY = 'mkit.key'
9
+ MKIT_PEM = 'mkit.pem'
10
+
7
11
  def me
8
12
  'mkit'
9
13
  end
@@ -24,6 +28,10 @@ module MKIt
24
28
  @config_dir.nil? ? "#{self.root}/config" : @config_dir
25
29
  end
26
30
 
31
+ def proxy_cert
32
+ "#{config_dir}/#{MKIT_PEM}"
33
+ end
34
+
27
35
  def load_db_config(db_config_dir = self.config_dir)
28
36
  self.log.info "loading database configurations from '#{config_dir}'..."
29
37
  YAML::load(ERB.new(IO.read("#{db_config_dir}/database.yml")).result)
data/lib/mkit/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module MKIt
2
- VERSION = "0.4.3"
2
+ VERSION = "0.6.0"
3
3
  end
4
4
 
data/lib/mkit.rb CHANGED
@@ -29,6 +29,7 @@ require 'mkit/docker_listener'
29
29
  require 'mkit/app/helpers/haproxy'
30
30
  require 'active_record/tasks/database_tasks'
31
31
  require 'mkit/utils'
32
+ require 'mkit/ssl/easy_ssl'
32
33
 
33
34
  MKItLogger = Console.logger
34
35
 
@@ -40,15 +41,24 @@ module MKIt
40
41
 
41
42
  def self.configure(options:)
42
43
  @root = MKIt::Utils.root
44
+ @options = options
43
45
  MKItLogger.debug!
44
46
  #
45
47
  # config dir
46
48
  @config_dir = if ENV['RACK_ENV'] != 'development'
47
- options[:config_dir].nil? ? '/etc/mkit' : options[:config_dir]
49
+ @options[:config_dir].nil? ? '/etc/mkit' : @options[:config_dir]
48
50
  else
49
- options[:config_dir].nil? ? "#{@root}/config" : options[:config_dir]
51
+ @options[:config_dir].nil? ? "#{@root}/config" : @options[:config_dir]
50
52
  end
51
53
  MKIt::Utils.set_config_dir(@config_dir)
54
+ # defaults
55
+ @bind = options[:bind] ||= 'localhost'
56
+ @port = options[:port] ||= 4567
57
+ @ssl = options[:ssl].nil? ? true : options[:ssl] && true
58
+ @verify_peer = options[:verify_peer].nil? ? false : options[:verify_peer] && true
59
+ @cert_chain_file = options[:cert_chain_file] ||= "#{@config_dir}/#{MKIt::Utils::MKIT_CRT}"
60
+ @private_key_file = options[:private_key_file] ||= "#{@config_dir}/#{MKIt::Utils::MKIT_KEY}"
61
+
52
62
  # create dirs
53
63
  if ENV['RACK_ENV'] != 'development' || !options[:config_dir].nil?
54
64
  check_config_files = false
@@ -65,9 +75,11 @@ module MKIt
65
75
  exit
66
76
  end
67
77
  end
68
- #
78
+
69
79
  # load configuration
70
80
  MKIt::Initializers.load_my_configuration
81
+ # cert
82
+ MKIt::EasySSL.create_self_certificate(@config_dir)
71
83
  #
72
84
  # run config based tasks
73
85
  FileUtils.mkdir_p(MKIt::Config.mkit.haproxy.config_dir)
@@ -92,6 +104,20 @@ module MKIt
92
104
  DatabaseTasks.root = @root
93
105
  end
94
106
 
107
+ def self.options(server)
108
+ if @ssl
109
+ ssl_options = {
110
+ private_key_file: @private_key_file,
111
+ cert_chain_file: @cert_chain_file,
112
+ verify_peer: @verify_peer
113
+ }
114
+ server.ssl = true
115
+ server.ssl_options = ssl_options
116
+ end
117
+ server.backend.port = @port
118
+ server.backend.host = @bind
119
+ end
120
+
95
121
  def self.establish_db_connection
96
122
  ActiveRecord::Base.establish_connection(DatabaseTasks.database_configuration[DatabaseTasks.env])
97
123
  ActiveRecord::Base.connection.migration_context.migrations_paths.clear
@@ -5,6 +5,7 @@ service:
5
5
  network: bridge
6
6
  ports:
7
7
  - 80:8081:http:round_robin
8
+ - 443:8081:http:round_robin:ssl
8
9
  resources:
9
10
  max_replicas: 1
10
11
  min_replicas: 1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasco Santos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-12 00:00:00.000000000 Z
11
+ date: 2024-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-dns
@@ -311,9 +311,11 @@ files:
311
311
  - bin/mkitd
312
312
  - config/database.yml
313
313
  - config/mkit_config.yml
314
+ - config/mkitc_config.yml
314
315
  - config/mkitd_config.sh
315
316
  - db/migrate/001_setup.rb
316
317
  - db/migrate/002_mkit_jobs.rb
318
+ - db/migrate/003_service_ports_ssl.rb
317
319
  - db/schema.rb
318
320
  - lib/mkit.rb
319
321
  - lib/mkit/app/controllers/mkit_controller.rb
@@ -349,13 +351,13 @@ files:
349
351
  - lib/mkit/ctypes.rb
350
352
  - lib/mkit/docker_listener.rb
351
353
  - lib/mkit/exceptions.rb
352
- - lib/mkit/haproxy.rb
353
354
  - lib/mkit/job_manager.rb
354
355
  - lib/mkit/mkit_dns.rb
355
356
  - lib/mkit/mkit_interface.rb
356
357
  - lib/mkit/sagas/asaga.rb
357
358
  - lib/mkit/sagas/create_pod_saga.rb
358
359
  - lib/mkit/sagas/saga_manager.rb
360
+ - lib/mkit/ssl/easy_ssl.rb
359
361
  - lib/mkit/status.rb
360
362
  - lib/mkit/utils.rb
361
363
  - lib/mkit/version.rb
data/lib/mkit/haproxy.rb DELETED
@@ -1,48 +0,0 @@
1
- require 'pty'
2
- #
3
- #
4
- #
5
- module MKIt
6
- class HAProxy
7
-
8
- def initialize
9
- # configs
10
- # run standalone | daemon
11
- @running = false
12
- end
13
-
14
- def start
15
- @thread ||= Thread.new {
16
- while (@running) do
17
- cmd = "/usr/sbin/haproxy -f /etc/haproxy/haproxy.d"
18
- %x{#{cmd}}
19
- sleep 1
20
- end
21
- }
22
- @thread.run
23
- puts "haproxy started"
24
- end
25
-
26
- def start
27
- @running = true
28
- @thread ||= Thread.new {
29
- while (@running) do
30
- %{/usr/sbin/haproxy -f /etc/haproxy/haproxy.d/}
31
- sleep(1)
32
- end
33
- }
34
- puts "proxy started"
35
- end
36
-
37
- def stop
38
- puts "proxy stopped"
39
- end
40
-
41
- def status
42
- end
43
-
44
- def reload
45
- end
46
- end
47
- end
48
-