icfs 0.1.0 → 0.1.1
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 +4 -4
- data/bin/icfs_demo_check.rb +29 -0
- data/data/docker/build-web.sh +27 -0
- data/data/docker/compose-demo.yml +41 -0
- data/data/docker/compose-init.yml +32 -0
- data/data/docker/icfs-app.rb +50 -0
- data/data/docker/icfs-cfg.yml +88 -0
- data/data/docker/icfs-init.rb +75 -0
- data/data/docker/icfs-ruby/Dockerfile +22 -0
- data/data/docker/icfs-ruby/build.sh +14 -0
- data/data/docker/nginx.conf +68 -0
- data/lib/icfs.rb +2 -3
- data/lib/icfs/api.rb +41 -41
- data/lib/icfs/cache_elastic.rb +6 -4
- data/lib/icfs/items.rb +62 -1
- data/lib/icfs/store.rb +25 -0
- data/lib/icfs/store_fs.rb +10 -0
- data/lib/icfs/store_s3.rb +11 -0
- data/lib/icfs/users.rb +2 -0
- data/lib/icfs/users_elastic.rb +5 -3
- data/lib/icfs/users_fs.rb +3 -3
- data/lib/icfs/utils/backup.rb +177 -0
- data/lib/icfs/utils/check.rb +290 -0
- data/lib/icfs/validate.rb +0 -63
- data/lib/icfs/web/auth_ssl.rb +10 -2
- data/lib/icfs/web/client.rb +2 -2
- metadata +14 -2
data/lib/icfs/store_fs.rb
CHANGED
@@ -57,6 +57,16 @@ class StoreFs < Store
|
|
57
57
|
end
|
58
58
|
|
59
59
|
|
60
|
+
###############################################
|
61
|
+
# (see Store#file_size)
|
62
|
+
#
|
63
|
+
def file_size(cid, enum, lnum, fnum)
|
64
|
+
File.size(_file(cid, enum, lnum, fnum))
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
|
60
70
|
###############################################
|
61
71
|
# (see Store#tempfile)
|
62
72
|
#
|
data/lib/icfs/store_s3.rb
CHANGED
@@ -64,6 +64,17 @@ class StoreS3 < Store
|
|
64
64
|
end
|
65
65
|
|
66
66
|
|
67
|
+
###############################################
|
68
|
+
# (see Store#file_size)
|
69
|
+
def file_size(cid, enum, lnum, fnum)
|
70
|
+
key = _file(cid, enum, lnum, fnum)
|
71
|
+
resp = @s3.head_object( bucket: @bck, key: key )
|
72
|
+
return resp.content_length
|
73
|
+
rescue Aws::S3::Errors::NotFound
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
|
67
78
|
###############################################
|
68
79
|
# (see Store#tempfile)
|
69
80
|
#
|
data/lib/icfs/users.rb
CHANGED
data/lib/icfs/users_elastic.rb
CHANGED
@@ -17,6 +17,8 @@ module ICFS
|
|
17
17
|
##########################################################################
|
18
18
|
# Implements {ICFS::Users Users} using Elasticsearch to cache
|
19
19
|
# details from another {ICFS::Users Users} instance.
|
20
|
+
#
|
21
|
+
# @todo Add logging
|
20
22
|
#
|
21
23
|
class UsersElastic < Users
|
22
24
|
|
@@ -120,12 +122,12 @@ class UsersElastic < Users
|
|
120
122
|
obj['active'] = true
|
121
123
|
|
122
124
|
# store in cache
|
123
|
-
json =
|
125
|
+
json = Items.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
|
124
126
|
_write(:users, urg, json)
|
125
127
|
|
126
128
|
# use cached version
|
127
129
|
else
|
128
|
-
obj =
|
130
|
+
obj = Items.parse(json, 'User/Role/Group'.freeze, ValUserCache)
|
129
131
|
end
|
130
132
|
|
131
133
|
# expired
|
@@ -146,7 +148,7 @@ class UsersElastic < Users
|
|
146
148
|
obj['last'] = now
|
147
149
|
|
148
150
|
# and store in cache
|
149
|
-
json =
|
151
|
+
json = Items.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
|
150
152
|
_write(:users, urg, json)
|
151
153
|
end
|
152
154
|
|
data/lib/icfs/users_fs.rb
CHANGED
@@ -26,7 +26,7 @@ class UsersFs < Users
|
|
26
26
|
# read a raw file
|
27
27
|
def _read(fn)
|
28
28
|
json = File.read(File.join(@path, fn + '.json'.freeze))
|
29
|
-
obj =
|
29
|
+
obj = Items.parse(json, 'User/Role/Group'.freeze, Users::ValUser)
|
30
30
|
if obj['name'] != fn
|
31
31
|
raise(Error::Values, 'UsersFs user %s name mismatch'.freeze % fn)
|
32
32
|
end
|
@@ -51,7 +51,7 @@ class UsersFs < Users
|
|
51
51
|
# (see Users#read)
|
52
52
|
#
|
53
53
|
def read(urg)
|
54
|
-
|
54
|
+
Items.validate(urg, 'User/Role/Group'.freeze, Items::FieldUsergrp)
|
55
55
|
|
56
56
|
# get the base user
|
57
57
|
usr = _read(urg)
|
@@ -113,7 +113,7 @@ class UsersFs < Users
|
|
113
113
|
# (see Users#write)
|
114
114
|
#
|
115
115
|
def write(obj)
|
116
|
-
|
116
|
+
Items.validate(obj, 'User/Role/Group'.freeze, Users::ValUser)
|
117
117
|
json = JSON.pretty_generate(obj)
|
118
118
|
|
119
119
|
# write to temp file
|
@@ -0,0 +1,177 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License version 3.
|
8
|
+
#
|
9
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
|
12
|
+
require_relative '../store_fs'
|
13
|
+
#
|
14
|
+
module ICFS
|
15
|
+
|
16
|
+
module Utils
|
17
|
+
|
18
|
+
##########################################################################
|
19
|
+
# Backup and restore utilities
|
20
|
+
#
|
21
|
+
class Backup
|
22
|
+
|
23
|
+
###############################################
|
24
|
+
# Instance
|
25
|
+
#
|
26
|
+
# @param cache [Cache] The live cache
|
27
|
+
# @param store [Store] The live store
|
28
|
+
# @param log [Logger] Where to log
|
29
|
+
#
|
30
|
+
def initialize(cache, store, log)
|
31
|
+
@cache = cache
|
32
|
+
@store = store
|
33
|
+
@log = log
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
###############################################
|
38
|
+
# Copy an item
|
39
|
+
#
|
40
|
+
def _item(dest, title, read, write, args, val=nil)
|
41
|
+
@log.debug('ICFS copy: %s'.freeze % title)
|
42
|
+
|
43
|
+
# read the item
|
44
|
+
json = @store.send(read, *args)
|
45
|
+
if !json
|
46
|
+
@log.warn('ICFS copy: %s is missing'.freeze % title)
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# parse the item if requested
|
51
|
+
if val
|
52
|
+
obj = JSON.parse(json)
|
53
|
+
err = Validate.check(obj, val)
|
54
|
+
if err
|
55
|
+
@log.error('ICFS copy: %s bad format'.freeze % title)
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# write the item
|
61
|
+
dest.send(write, *args, json)
|
62
|
+
return obj
|
63
|
+
|
64
|
+
rescue JSON::ParserError
|
65
|
+
@log.error('ICFS copy: %s bad JSON'.freeze % title)
|
66
|
+
return nil
|
67
|
+
end # def _item
|
68
|
+
private :_item
|
69
|
+
|
70
|
+
|
71
|
+
###############################################
|
72
|
+
# Transfer a case to another store
|
73
|
+
#
|
74
|
+
# @param cid [String] The case ID
|
75
|
+
# @param dest [Store] The destination store
|
76
|
+
# @param lnum_max [Integer] The highest log
|
77
|
+
# @param lnum_min [Integer] The lowest log
|
78
|
+
#
|
79
|
+
def copy(cid, dest, lnum_max, lnum_min=1)
|
80
|
+
@log.info('ICFS copy: %s %d-%d'.freeze % [cid, lnum_min, lnum_max])
|
81
|
+
if lnum_min > lnum_max
|
82
|
+
raise ArgumentError, 'ICFS copy, log num min is larger than max'.freeze
|
83
|
+
end
|
84
|
+
|
85
|
+
# each log
|
86
|
+
lnum = lnum_min
|
87
|
+
while lnum <= lnum_max
|
88
|
+
|
89
|
+
# copy the log
|
90
|
+
log = _copy_item(dest,
|
91
|
+
'log %d'.freeze % lnum,
|
92
|
+
:log_read,
|
93
|
+
:log_write,
|
94
|
+
[cid, lnum],
|
95
|
+
Items::ItemLog
|
96
|
+
)
|
97
|
+
if !log
|
98
|
+
lnum += 1
|
99
|
+
next
|
100
|
+
end
|
101
|
+
|
102
|
+
# entry
|
103
|
+
enum = log['entry']['num']
|
104
|
+
_copy_item(dest,
|
105
|
+
'entry %d-%d'.freeze % [enum, lnum],
|
106
|
+
:entry_read,
|
107
|
+
:entry_write,
|
108
|
+
[cid, lnum]
|
109
|
+
)
|
110
|
+
|
111
|
+
# index
|
112
|
+
if log['index']
|
113
|
+
xnum = log['index']['num']
|
114
|
+
_copy_item(dest,
|
115
|
+
'index %d-%d'.freeze % [xnum, lnum],
|
116
|
+
:index_read,
|
117
|
+
:index_write,
|
118
|
+
[cid, xnum, lnum]
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
# action
|
123
|
+
if log['action']
|
124
|
+
anum = log['action']['lnum']
|
125
|
+
_copy_item(dest,
|
126
|
+
'action %d-%d'.freeze % [anum, lnum],
|
127
|
+
:action_read,
|
128
|
+
:action_write,
|
129
|
+
[cid, anum, lnum]
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
# case
|
134
|
+
if log['case_hash']
|
135
|
+
_copy_item(dest,
|
136
|
+
'case %d'.freeze, % lnum,
|
137
|
+
:case_read,
|
138
|
+
:case_write,
|
139
|
+
[cid, lnum]
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
# files
|
144
|
+
if log['files_hash']
|
145
|
+
log['files_hash'].each_index do |fnum|
|
146
|
+
|
147
|
+
@log.debug('ICFS copy: file %d-%d-%d'.freeze % [enum, lnum, fnum])
|
148
|
+
|
149
|
+
# read
|
150
|
+
fi = @store.file_read(cid, enum, lnum, fnum)
|
151
|
+
if !fi
|
152
|
+
@log.warn('ICFS copy: file %d-%d-%d missing'.freeze %
|
153
|
+
[enum, lnum, fnum])
|
154
|
+
next
|
155
|
+
end
|
156
|
+
|
157
|
+
# copy
|
158
|
+
tmp = dest.tempfile
|
159
|
+
IO.copy_stream(fi, tmp)
|
160
|
+
@store.close(fi)
|
161
|
+
|
162
|
+
# write
|
163
|
+
dest.file_write(cid, enum, lnum, fnum, tmp)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
lnum += 1
|
168
|
+
end
|
169
|
+
|
170
|
+
end # def copy()
|
171
|
+
|
172
|
+
|
173
|
+
end # class ICFS::Utils::Backup
|
174
|
+
|
175
|
+
end # module ICFS::Utils
|
176
|
+
|
177
|
+
end # module ICFS
|
@@ -0,0 +1,290 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License version 3.
|
8
|
+
#
|
9
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
|
12
|
+
#
|
13
|
+
module ICFS
|
14
|
+
|
15
|
+
|
16
|
+
##########################################################################
|
17
|
+
# Low level utilities for working directly with ICFS systems.
|
18
|
+
#
|
19
|
+
# @todo Archive/move case tool written
|
20
|
+
#
|
21
|
+
module Utils
|
22
|
+
|
23
|
+
|
24
|
+
##########################################################################
|
25
|
+
# Check a case for errors
|
26
|
+
#
|
27
|
+
# @todo Make a function to auto find the last log
|
28
|
+
#
|
29
|
+
class Check
|
30
|
+
|
31
|
+
|
32
|
+
###############################################
|
33
|
+
# Instance
|
34
|
+
#
|
35
|
+
# @param store [Store] The store to check
|
36
|
+
# @param log [Logger] Where to log
|
37
|
+
#
|
38
|
+
def initialize(store, log)
|
39
|
+
@store = store
|
40
|
+
@log = log
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
###############################################
|
45
|
+
# check an object
|
46
|
+
#
|
47
|
+
def _item(title, read, read_args, hist, hash, val, con)
|
48
|
+
|
49
|
+
@log.debug('ICFS check: %s'.freeze % title)
|
50
|
+
|
51
|
+
# read
|
52
|
+
json = @store.send(read, *read_args)
|
53
|
+
if !json
|
54
|
+
if hist
|
55
|
+
@log.warn('ICFS check: %s is missing and historical'.freeze % title)
|
56
|
+
else
|
57
|
+
@log.error('ICFS check: %s is missing and current'.freeze % title)
|
58
|
+
end
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# hash
|
63
|
+
if hash
|
64
|
+
if hash != ICFS.hash(json)
|
65
|
+
@log.error('ICFS check: %s hash bad'.freeze % title)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
@log.warn('ICFS check: %s hash unverified'.freeze % title)
|
69
|
+
end
|
70
|
+
|
71
|
+
# parse
|
72
|
+
obj = JSON.parse(json)
|
73
|
+
err = Validate.check(obj, val)
|
74
|
+
if err
|
75
|
+
@log.error('ICFS check: %s bad format'.freeze % title)
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# inconsistent
|
80
|
+
con.each do |name, num|
|
81
|
+
if obj[name] != read_args[num]
|
82
|
+
@log.error('ICFS check: %s inconsistent'.freeze % title)
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
return obj
|
88
|
+
|
89
|
+
rescue JSON::ParserError
|
90
|
+
@log.error('ICFS check: %s bad JSON'.freeze % title)
|
91
|
+
return nil
|
92
|
+
end # def _item()
|
93
|
+
private :_item
|
94
|
+
|
95
|
+
|
96
|
+
###############################################
|
97
|
+
# Check a case
|
98
|
+
#
|
99
|
+
# @param cur_log [Integer] The last log
|
100
|
+
# @param cur_hash [String] The hash of the last log
|
101
|
+
#
|
102
|
+
def check(cid, cur_log, cur_hash, opts={})
|
103
|
+
@log.info('ICFS check: case %s'.freeze % cid)
|
104
|
+
|
105
|
+
ent_cur = Set.new
|
106
|
+
cse_cur = false
|
107
|
+
idx_cur = Set.new
|
108
|
+
act_cur = Set.new
|
109
|
+
file_cur = Set.new
|
110
|
+
|
111
|
+
|
112
|
+
# go thru the logs from most current
|
113
|
+
lnum = cur_log
|
114
|
+
hash_log = cur_hash
|
115
|
+
time_log = Time.now.to_i
|
116
|
+
while( lnum > 0 )
|
117
|
+
|
118
|
+
# log
|
119
|
+
log = _item(
|
120
|
+
'log %d'.freeze % lnum,
|
121
|
+
:log_read,
|
122
|
+
[cid, lnum],
|
123
|
+
false,
|
124
|
+
hash_log,
|
125
|
+
Items::ItemLog,
|
126
|
+
[
|
127
|
+
['caseid'.freeze, 0].freeze,
|
128
|
+
['log'.freeze, 1].freeze
|
129
|
+
].freeze,
|
130
|
+
)
|
131
|
+
if !log
|
132
|
+
hash_log = nil
|
133
|
+
lnum = lnum - 1
|
134
|
+
next
|
135
|
+
end
|
136
|
+
|
137
|
+
# check that time decreases
|
138
|
+
if log['time'] > time_log
|
139
|
+
@log.warn('ICFS check: log %d time inconsistent'.freeze % lnum)
|
140
|
+
end
|
141
|
+
|
142
|
+
# entry
|
143
|
+
enum = log['entry']['num']
|
144
|
+
ent = _item(
|
145
|
+
'entry %d-%d' % [enum, lnum],
|
146
|
+
:entry_read,
|
147
|
+
[cid, enum, lnum],
|
148
|
+
ent_cur.include?(enum),
|
149
|
+
log['entry']['hash'],
|
150
|
+
Items::ItemEntry,
|
151
|
+
[
|
152
|
+
['caseid'.freeze, 0].freeze,
|
153
|
+
['entry'.freeze, 1].freeze,
|
154
|
+
['log'.freeze, 2].freeze
|
155
|
+
].freeze,
|
156
|
+
)
|
157
|
+
|
158
|
+
# current entry
|
159
|
+
unless ent_cur.include?(enum)
|
160
|
+
ent_cur.add(enum)
|
161
|
+
if ent['files']
|
162
|
+
ent['files'].each do |fd|
|
163
|
+
file_cur.add( '%d-%d-%d'.freeze % [enum, fd['num'], fd['log']] )
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# index
|
169
|
+
if log['index']
|
170
|
+
xnum = log['index']['num']
|
171
|
+
idx = _item(
|
172
|
+
'index %d-%d'. freeze % [xnum, lnum],
|
173
|
+
:index_read,
|
174
|
+
[cid, xnum, lnum],
|
175
|
+
idx_cur.include?(xnum),
|
176
|
+
log['index']['hash'],
|
177
|
+
Items::ItemIndex,
|
178
|
+
[
|
179
|
+
['caseid'.freeze, 0].freeze,
|
180
|
+
['index'.freeze, 1].freeze,
|
181
|
+
['log'.freeze, 2].freeze
|
182
|
+
]
|
183
|
+
)
|
184
|
+
idx_cur.add(xnum)
|
185
|
+
end
|
186
|
+
|
187
|
+
# action
|
188
|
+
if log['action']
|
189
|
+
anum = log['action']['num']
|
190
|
+
act = _item(
|
191
|
+
'action %d-%d'.freeze % [anum, lnum],
|
192
|
+
:action_read,
|
193
|
+
[cid, anum, lnum],
|
194
|
+
act_cur.include?(anum),
|
195
|
+
log['action']['hash'],
|
196
|
+
Items::ItemAction,
|
197
|
+
[
|
198
|
+
['caseid'.freeze, 0].freeze,
|
199
|
+
['action'.freeze, 1].freeze,
|
200
|
+
['log'.freeze, 2].freeze
|
201
|
+
]
|
202
|
+
)
|
203
|
+
act_cur.add(anum)
|
204
|
+
end
|
205
|
+
|
206
|
+
# case
|
207
|
+
if log['case_hash']
|
208
|
+
cse = _item(
|
209
|
+
'case %d'.freeze % lnum,
|
210
|
+
:case_read,
|
211
|
+
[cid, lnum],
|
212
|
+
cse_cur,
|
213
|
+
log['case_hash'],
|
214
|
+
Items::ItemCase,
|
215
|
+
[
|
216
|
+
['caseid'.freeze, 0].freeze,
|
217
|
+
['log'.freeze, 1].freeze
|
218
|
+
]
|
219
|
+
)
|
220
|
+
cse_cur = true
|
221
|
+
end
|
222
|
+
|
223
|
+
# files
|
224
|
+
if log['files_hash']
|
225
|
+
fnum = 0
|
226
|
+
log['files_hash'].each do |hash|
|
227
|
+
fnum = fnum + 1
|
228
|
+
fn = '%d-%d-%d'.freeze % [enum, fnum, lnum]
|
229
|
+
cur = file_cur.include?(fn)
|
230
|
+
file_cur.delete(fn) if cur
|
231
|
+
|
232
|
+
@log.debug('ICFS check: file %s'.freeze % fn)
|
233
|
+
|
234
|
+
# read/size
|
235
|
+
if opts[:hash_all] || (cur && opts[:hash_current])
|
236
|
+
fi = @store.file_read(cid, enum, lnum, fnum)
|
237
|
+
elsif opts[:stat_all] || (cur && opts[:stat_current])
|
238
|
+
fi = @store.file_size(cur, enum, lnum, fnum)
|
239
|
+
else
|
240
|
+
fi = true
|
241
|
+
end
|
242
|
+
|
243
|
+
# missing
|
244
|
+
if !fi
|
245
|
+
if cur
|
246
|
+
@log.error('ICFS check: file %s missing and current'.freeze %
|
247
|
+
fn)
|
248
|
+
else
|
249
|
+
@log.warn('ICFS check: file %s missing and historical'.freeze %
|
250
|
+
fn)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# hash
|
255
|
+
if fi.is_a?(File)
|
256
|
+
# check
|
257
|
+
if hash != ICFS.hash_temp(fi)
|
258
|
+
@log.error('ICFS check: file %s hash bad'.freeze % fn)
|
259
|
+
end
|
260
|
+
|
261
|
+
# close
|
262
|
+
if fi.respond_to?(:close!)
|
263
|
+
fi.close!
|
264
|
+
else
|
265
|
+
fi.close
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# previous log
|
272
|
+
lnum = lnum - 1
|
273
|
+
hash_log = log['prev']
|
274
|
+
end
|
275
|
+
|
276
|
+
# check for any non-existant current files
|
277
|
+
unless file_cur.empty?
|
278
|
+
file_cur.each do |fn|
|
279
|
+
@log.error('ICFS check: file %s current but not logged'.freeze % fn)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
@log.debug('ICFS check: case %s complete'.freeze % cid)
|
284
|
+
end # def check()
|
285
|
+
|
286
|
+
|
287
|
+
end # class ICFS::Utils::Check
|
288
|
+
|
289
|
+
end # module ICFS::Utils
|
290
|
+
end # module ICFS
|