safedb 0.01.0001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +21 -0
  6. data/README.md +793 -0
  7. data/Rakefile +16 -0
  8. data/bin/safe +5 -0
  9. data/lib/configs/README.md +58 -0
  10. data/lib/extension/array.rb +162 -0
  11. data/lib/extension/dir.rb +35 -0
  12. data/lib/extension/file.rb +123 -0
  13. data/lib/extension/hash.rb +33 -0
  14. data/lib/extension/string.rb +572 -0
  15. data/lib/factbase/facts.safedb.net.ini +38 -0
  16. data/lib/interprete.rb +462 -0
  17. data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
  18. data/lib/keytools/kdf.api.rb +243 -0
  19. data/lib/keytools/kdf.bcrypt.rb +265 -0
  20. data/lib/keytools/kdf.pbkdf2.rb +262 -0
  21. data/lib/keytools/kdf.scrypt.rb +190 -0
  22. data/lib/keytools/key.64.rb +326 -0
  23. data/lib/keytools/key.algo.rb +109 -0
  24. data/lib/keytools/key.api.rb +1391 -0
  25. data/lib/keytools/key.db.rb +330 -0
  26. data/lib/keytools/key.docs.rb +195 -0
  27. data/lib/keytools/key.error.rb +110 -0
  28. data/lib/keytools/key.id.rb +271 -0
  29. data/lib/keytools/key.ident.rb +243 -0
  30. data/lib/keytools/key.iv.rb +107 -0
  31. data/lib/keytools/key.local.rb +259 -0
  32. data/lib/keytools/key.now.rb +402 -0
  33. data/lib/keytools/key.pair.rb +259 -0
  34. data/lib/keytools/key.pass.rb +120 -0
  35. data/lib/keytools/key.rb +585 -0
  36. data/lib/logging/gem.logging.rb +132 -0
  37. data/lib/modules/README.md +43 -0
  38. data/lib/modules/cryptology/aes-256.rb +154 -0
  39. data/lib/modules/cryptology/amalgam.rb +70 -0
  40. data/lib/modules/cryptology/blowfish.rb +130 -0
  41. data/lib/modules/cryptology/cipher.rb +207 -0
  42. data/lib/modules/cryptology/collect.rb +138 -0
  43. data/lib/modules/cryptology/crypt.io.rb +225 -0
  44. data/lib/modules/cryptology/engineer.rb +99 -0
  45. data/lib/modules/mappers/dictionary.rb +288 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/modules/storage/git.store.rb +399 -0
  48. data/lib/session/fact.finder.rb +334 -0
  49. data/lib/session/require.gem.rb +112 -0
  50. data/lib/session/time.stamp.rb +340 -0
  51. data/lib/session/user.home.rb +49 -0
  52. data/lib/usecase/cmd.rb +487 -0
  53. data/lib/usecase/config/README.md +57 -0
  54. data/lib/usecase/docker/README.md +146 -0
  55. data/lib/usecase/docker/docker.rb +49 -0
  56. data/lib/usecase/edit/README.md +43 -0
  57. data/lib/usecase/edit/delete.rb +46 -0
  58. data/lib/usecase/export.rb +40 -0
  59. data/lib/usecase/files/README.md +37 -0
  60. data/lib/usecase/files/eject.rb +56 -0
  61. data/lib/usecase/files/file_me.rb +78 -0
  62. data/lib/usecase/files/read.rb +169 -0
  63. data/lib/usecase/files/write.rb +89 -0
  64. data/lib/usecase/goto.rb +57 -0
  65. data/lib/usecase/id.rb +36 -0
  66. data/lib/usecase/import.rb +157 -0
  67. data/lib/usecase/init.rb +63 -0
  68. data/lib/usecase/jenkins/README.md +146 -0
  69. data/lib/usecase/jenkins/jenkins.rb +208 -0
  70. data/lib/usecase/login.rb +71 -0
  71. data/lib/usecase/logout.rb +28 -0
  72. data/lib/usecase/open.rb +71 -0
  73. data/lib/usecase/print.rb +40 -0
  74. data/lib/usecase/put.rb +81 -0
  75. data/lib/usecase/set.rb +44 -0
  76. data/lib/usecase/show.rb +138 -0
  77. data/lib/usecase/terraform/README.md +91 -0
  78. data/lib/usecase/terraform/terraform.rb +121 -0
  79. data/lib/usecase/token.rb +35 -0
  80. data/lib/usecase/update/README.md +55 -0
  81. data/lib/usecase/update/rename.rb +180 -0
  82. data/lib/usecase/use.rb +41 -0
  83. data/lib/usecase/verse.rb +20 -0
  84. data/lib/usecase/view.rb +71 -0
  85. data/lib/usecase/vpn/README.md +150 -0
  86. data/lib/usecase/vpn/vpn.ini +31 -0
  87. data/lib/usecase/vpn/vpn.rb +54 -0
  88. data/lib/version.rb +3 -0
  89. data/safedb.gemspec +34 -0
  90. metadata +193 -0
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module SafeDb
5
+
6
+ require 'inifile'
7
+
8
+ # An OpenSession dictionary is a <b>2D (two dimensional) hash</b> data
9
+ # structure backed by an encrypted file.
10
+ #
11
+ # It supports operations to <b>read from</b> and <b>write to</b> a known
12
+ # filepath and given the correct symmetric encryption key it will
13
+ #
14
+ # - decrypt <b>after reading from</b> the file and
15
+ # - encrypt <b>before writing to</b> the file
16
+ #
17
+ # This dictionary extends {Hash} in order to deliver on its core key value
18
+ # storage and retrieve use cases. Extend this dictionary and provide
19
+ # context specific methods through constants to read and write context
20
+ # specific data.
21
+ #
22
+ # == The <em>Current</em> Dictionary Section
23
+ #
24
+ # This Dictionary is <b>two-dimensional</b> so all key-value pairs are stored
25
+ # under the auspices of a section.
26
+ #
27
+ # The Dictionary can track the <b>current section</b> for you and all data
28
+ # exchanges can occur in lieu of a single section if you so wish by using
29
+ # the provided {put} and {get} methods.
30
+ #
31
+ # To employ section management functionality you should pass in a current
32
+ # <b>section id</b> when creating the dictionary.
33
+ #
34
+ # @example
35
+ # To use the dictionary in the raw (unextended) format you create
36
+ # write and read it like this.
37
+ #
38
+ # ----------------------------------------------------------------------
39
+ #
40
+ # my_dictionary = Dictionary.create( "/path/to/backing/file" )
41
+ #
42
+ # my_dictionary["user23"] = {}
43
+ # my_dictionary["user23"]["Name"] = "Joe Bloggs"
44
+ # my_dictionary["user23"]["Email"] = "joebloggs@example.com"
45
+ # my_dictionary["user23"]["Phone"] = "+44 07342 800080"
46
+ #
47
+ # my_dictionary.write( "crypt-key-1234-wxyz" )
48
+ #
49
+ # ----------------------------------------------------------------------
50
+ #
51
+ # my_dictionary = Dictionary.create( "/path/to/backing/file", "crypt-key-1234-wxyz" )
52
+ # puts my_dictionary.has_key? "user23" # => true
53
+ # puts my_dictionary["user23"].length # => 3
54
+ # puts my_dictionary["user23"]["Email"] # => "joebloggs@example.com"
55
+ #
56
+ # ----------------------------------------------------------------------
57
+ class Dictionary < Hash
58
+
59
+ attr_accessor :backing_filepath, :section_id
60
+
61
+
62
+ # Create either a new empty dictionary or unmarshal (deserialize) the
63
+ # dictionary from an encrypted file depending on whether a file exists
64
+ # at the backing_file parameter location.
65
+ #
66
+ # If the backing file indeed exists, the crypt key will be employed to
67
+ # decode and then decrypt the contents before the unmarshal operation.
68
+ #
69
+ # The filepath is stored as an instance variable hence the {write}
70
+ # operation does not need to be told <b>where to?</b>
71
+ #
72
+ # @example
73
+ # # Create Dictionary the first time
74
+ # my_dictionary = Dictionary.create( "/path/to/backing/file" )
75
+ #
76
+ # # Create Dictionary from an Encrypted Backing File
77
+ # my_dictionary = Dictionary.create( "/path/to/backing/file", "crypt-key-1234-wxyz" )
78
+ #
79
+ # @param backing_file [String]
80
+ # the backing file is the filepath to this Dictionary's encrypted
81
+ # backing file when serialized. If no file exists at this path the
82
+ # operation will instantiate and return a new empty {Hash} object.
83
+ #
84
+ # @param crypt_key [String]
85
+ # if the backing file exists then this parameter must contain a
86
+ # robust symmetric decryption key. The symmetric key will be used
87
+ # for decryption after the base64 encoded file is read.
88
+ #
89
+ # Note that the decryption key is never part of the dictionary object.
90
+ # This class method knows it but the new Dictionary has no crypt key
91
+ # instance variable. Another crypt key must then be introduced when
92
+ # serializing (writing) the dictionary back into a file.
93
+ #
94
+ # @return [Dictionary]
95
+ # return a new Dictionary that knows where to go if it needs
96
+ # to read (deserialize) or write (serialize) itself.
97
+ #
98
+ # If no file exists at the path a new empty {Hash} object is
99
+ # returned.
100
+ #
101
+ # If a file exists, then the crypt_key parameter is expected
102
+ # to be the decryption and key and the dictionary will be based
103
+ # on the decrypted contents of the file.
104
+ #
105
+ # @raise [ArgumentError]
106
+ # An {ArgumentError} is raised if either no decryption key is provided
107
+ # or one that is unsuitable (ie was not used within the encryption).
108
+ # Errors can also arise if the block coding and decoding has not been
109
+ # done satisfactorily.
110
+ def self.create backing_file, crypt_key = nil
111
+
112
+ key_missing = File.file?( backing_file ) && crypt_key.nil?
113
+ raise ArgumentError, "No crypt key provided for file #{backing_file}" if key_missing
114
+
115
+ dictionary = Dictionary.new
116
+ dictionary.backing_filepath = backing_file
117
+
118
+ return dictionary unless File.file? backing_file
119
+
120
+ file_contents = File.read( backing_file ).strip
121
+ plaintext_str = file_contents.block_decode_decrypt( crypt_key )
122
+ dictionary.ingest_contents( plaintext_str )
123
+
124
+ return dictionary
125
+
126
+ end
127
+
128
+
129
+ # Create either a new dictionary containing the specified section or unmarshal
130
+ # (deserialize) the dictionary from an encrypted file depending on whether a
131
+ # file exists at the backing_file parameter location and then <b>create</b> the
132
+ # section <b>only if it does not exist</b>.
133
+ #
134
+ # If the backing file indeed exists, the crypt key will be employed to
135
+ # decode and then decrypt the contents before the unmarshal operation.
136
+ #
137
+ # The filepath is stored as an instance variable hence the {write}
138
+ # operation does not need to be told <b>where to?</b>
139
+ #
140
+ # This dictionary will also know which <b>"section"</b> should be used to
141
+ # put, add, update and delete key/value data. You can employ this dictionary
142
+ # such that <b>each instance only creates, updates, removes and/or reads</b>
143
+ # from a single section.
144
+ #
145
+ # @example
146
+ # # Create Dictionary the first time with a section.
147
+ # my_dictionary = Dictionary.create( "/path/to/file", "Europe" )
148
+ #
149
+ # # Create Dictionary from an Encrypted Backing File
150
+ # my_dictionary = Dictionary.create( "/path/to/file", "Europe", "1234-wxyz" )
151
+ #
152
+ # @param backing_file [String]
153
+ # the backing file is the filepath to this Dictionary's encrypted
154
+ # backing file when serialized.
155
+ #
156
+ # @param section_id [String]
157
+ # the created dictionary know which <b>section</b> should be used to
158
+ # put, add, update and delete key/value data. If the backing file
159
+ # does not exist a new section is created in the empty dictionary.
160
+ #
161
+ # If the file exists a new section is created only if it is not
162
+ # already present inside the dictionary.
163
+ #
164
+ # @param crypt_key [String]
165
+ # if the backing file exists then this parameter must contain a
166
+ # robust symmetric decryption key. The symmetric key will be used
167
+ # for decryption after the base64 encoded file is read.
168
+ #
169
+ # Note that the decryption key is never part of the dictionary object.
170
+ # This class method knows it but the new Dictionary has no crypt key
171
+ # instance variable. Another crypt key must then be introduced when
172
+ # serializing (writing) the dictionary back into a file.
173
+ #
174
+ # @return [Dictionary]
175
+ # return a new Dictionary that knows where to go if it needs
176
+ # to read (deserialize) or write (serialize) itself.
177
+ #
178
+ # If no file exists at the path a new empty {Hash} object is
179
+ # returned.
180
+ #
181
+ # If a file exists, then the crypt_key parameter is expected
182
+ # to be the decryption and key and the dictionary will be based
183
+ # on the decrypted contents of the file.
184
+ #
185
+ # @raise [ArgumentError]
186
+ # An {ArgumentError} is raised if either no decryption key is provided
187
+ # or one that is unsuitable (ie was not used within the encryption).
188
+ # Errors can also arise if the block coding and decoding has not been
189
+ # done satisfactorily.
190
+ def self.create_with_section backing_file, section_id, crypt_key = nil
191
+
192
+ dictionary = create( backing_file, crypt_key = nil )
193
+ dictionary.section_id = section_id
194
+ dictionary[section_id] = {} unless dictionary.has_key?( section_id )
195
+
196
+ return dictionary
197
+
198
+ end
199
+
200
+
201
+ # Write the data in this dictionary hash map into a file-system
202
+ # backend mirror whose path was specified in the {Dictionary.create}
203
+ # factory method.
204
+ #
205
+ # Technology for encryption at rest is mandatory when using this
206
+ # Dictionary to write and read files from the filesystem.
207
+ #
208
+ # Calling this {self.write} method when the file at the prescribed path
209
+ # does not exist results in the directory structure being created
210
+ # (if necessary) and then the (possibly encrypted) file being written.
211
+ #
212
+ # @param crypt_key [String]
213
+ # this parameter must contain a robust symmetric crypt key to use for
214
+ # the encryption before writing to the filesystem.
215
+ #
216
+ # Note that the decryption key is never part of the dictionary object.
217
+ # For uncrackable security this key must be changed every time the
218
+ # file is written.
219
+ def write crypt_key
220
+
221
+ ini_file = IniFile.new
222
+ self.each_key do |section_name|
223
+ ini_file[section_name] = self[section_name]
224
+ end
225
+
226
+ crypted_text = ini_file.to_s.encrypt_block_encode( crypt_key )
227
+
228
+ FileUtils.mkdir_p File.dirname(@backing_filepath)
229
+ File.write @backing_filepath, crypted_text
230
+
231
+ end
232
+
233
+
234
+
235
+ def get key_name
236
+ return self[@section_id][key_name]
237
+ end
238
+
239
+
240
+
241
+ def put key_name, key_value
242
+ self[@section_id][key_name] = key_value
243
+ end
244
+
245
+
246
+
247
+
248
+ # Ingest the contents of the INI string and merge it into a
249
+ # this object which is a {Hash}.
250
+ #
251
+ # @param the_contents [String]
252
+ # the INI string that will be ingested and morphed into
253
+ # this dictionary.
254
+ #
255
+ # @raise [ArgumentError]
256
+ # if the content contains any nil section name, key name
257
+ # or key value.
258
+ def ingest_contents the_contents
259
+
260
+ ini_file = IniFile.new( :content => the_contents )
261
+ ini_file.each do | data_group, data_key, data_value |
262
+ ingest_entry data_group, data_key, data_value
263
+ end
264
+
265
+ end
266
+
267
+
268
+ private
269
+
270
+
271
+ def ingest_entry section_name, key_name, value
272
+
273
+ msg = "A NIL object detected during ingestion of file [#{@filepath}]."
274
+ raise ArgumentError.new msg if section_name.nil? || key_name.nil? || value.nil?
275
+
276
+ if self.has_key? section_name then
277
+ self[section_name][key_name] = value
278
+ else
279
+ self.store section_name, { key_name => value }
280
+ end
281
+
282
+ end
283
+
284
+
285
+ end
286
+
287
+
288
+ end
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module SafeDb
5
+
6
+ module Store
7
+
8
+ # Cold storage can sync repositories with a <b>bias during conflicts</b>
9
+ # either to the <em>remote repository</em> <b>when pulling</b>, and then
10
+ # conversely to the <em>local reposiory</em> <b>when pushing</b>.
11
+ #
12
+ # In between the sync operations a ColdStore can create, read, update and
13
+ # delete to and from the local mirror.
14
+ #
15
+ # == ColdStore | Use Cases
16
+ #
17
+ # Any <b>self-respecting coldstore</b> must, after initialization, provide
18
+ # some basic (and mandatory) behaviour.
19
+ #
20
+ # These include
21
+ #
22
+ # - <b>read</b> - reading text from a (possibly unavailable) frozen path
23
+ # - <b>write</b> - writing text (effectively freezing it) to a path
24
+ # - <b>pull</b> - sync with a <b>collision bias</b> that favours the remote mirror
25
+ # - <b>push</b> - sync with a <b>collision bias</b> that favours the local mirror
26
+ #
27
+ # <b>Cold Storage</b> is borrowed from BitCoin and represents offline storage
28
+ # for keys and crypts. safe separates keys and crypts so that you can
29
+ # transfer and share secrets by moving keys (not the crypts).
30
+ #
31
+ # == Houses and Gold Bullion
32
+ #
33
+ # You don't carry houses or gold bullion around to rent, share or transfer
34
+ # their ownership.
35
+ #
36
+ # You copy keys to rent secrets and when the tenure is up (or you change your
37
+ # mind) you revoke access with a metaphorical lock change.
38
+ #
39
+ # safe embodies concepts like an owner who rents as opposed to a change
40
+ # in ownership.
41
+ #
42
+ # == trade secrets | commoditizing secrets
43
+ #
44
+ # safe is a conduit through which secrets can be bought and sold.
45
+ #
46
+ # It commoditizes secrets so that they can be owned, traded, leased and
47
+ # auctioned. Options to acquire or relinquish them at set prices can easily
48
+ # be taken out.
49
+ class ColdStore
50
+
51
+ # @param base_path [String]
52
+ # path to the store's (mirror) base directory.
53
+ # If the denoted directory does not exist an attempt will be made to
54
+ # create it. If a file exists at this path an error will be thrown.
55
+ #
56
+ # @param domain [String]
57
+ # the domain is an identifier (and namespace) denoting which safe
58
+ # "account" is being accessed. safe allows the creation and use of
59
+ # multiple domains.
60
+ def initialize local_path
61
+
62
+ @store_path = local_path
63
+ FileUtils.mkdir_p @store_path
64
+
65
+ end
66
+
67
+
68
+ # Read the file frozen (in this store mirror) at this path and
69
+ # return its contents.
70
+ #
71
+ # Coldstores are usually frozen offline (offmachine) so for this
72
+ # to work the {ColdStore.pull} behaviour must have executed to
73
+ # create a local store mirror. This method reads from that mirror.
74
+ #
75
+ # @param from_path [String]
76
+ # read the file frozen at this path and return its contents
77
+ # so that the defreeze process can begin.
78
+ #
79
+ # This path is relative to the base of the store defined in
80
+ # the constructor.
81
+ #
82
+ # @return [String]
83
+ # return the text frozen in a file at the denoted local path
84
+ #
85
+ # nil is reurned if no file can be found in the local mirror
86
+ # at the configured path
87
+ #
88
+ # @raise [RuntimeError]
89
+ # unless the path exists in this coldstore and that path is
90
+ # a directory (as opposed to a file).
91
+ #
92
+ # @raise [ArgumentError]
93
+ # if more than one file match is made at the path specified.
94
+ def read from_path
95
+
96
+ frozen_filepath = File.join @store_path, from_path
97
+ frozen_dir_path = File.dirname(frozen_filepath)
98
+
99
+ log.info(x) { "Coldstore will search in folder [#{frozen_dir_path.hr_path}]" }
100
+
101
+ exists_msg = "Directory #{frozen_dir_path} does not exist in store."
102
+ is_dir_msg = "Path #{frozen_dir_path} should be a directory (not a file)."
103
+ raise RuntimeError, exists_msg unless File.exists? frozen_dir_path
104
+ raise RuntimeError, is_dir_msg unless File.directory? frozen_dir_path
105
+
106
+ full_filepath = ""
107
+ file_matched = false
108
+
109
+ Dir.glob("#{frozen_dir_path}/**/*.os.txt").each do |matched_path|
110
+
111
+ log.info(x) { "Coldstore search with [#{from_path}] has matched [#{matched_path.hr_path}]" }
112
+ log.info(x) { "Ignore directory at [#{matched_path.hr_path}]." } if File.directory? matched_path
113
+ next if File.directory? matched_path
114
+
115
+ two_match_msg = "More than one file matched. The second is #{matched_path}."
116
+ raise ArgumentError, two_match_msg if file_matched
117
+ file_matched = true
118
+
119
+ full_filepath = matched_path
120
+
121
+ end
122
+
123
+ no_file_msg = "Coldstore could not find path [#{from_path}] from [#{@store_path}]."
124
+ raise RuntimeError, no_file_msg unless file_matched
125
+
126
+ log.info(x) { "Coldstore matched exactly one envelope at [#{full_filepath.hr_path}]." }
127
+ return File.read full_filepath
128
+
129
+ end
130
+
131
+
132
+ # Write (freeze) the text into a file at the denoted path. The
133
+ # folder path will be created if need be.
134
+ #
135
+ # Coldstores are usually frozen offline (offmachine) so after
136
+ # this method completes the {ColdStore.push} behaviour must be
137
+ # executed to synchronize the local coldstore freezer with the
138
+ # remote mirror.
139
+ #
140
+ # @param this_text [String]
141
+ # this is the text that needs to be frozen into the local and
142
+ # subsequently the remote coldstore freezer.
143
+ #
144
+ # @param to_path [String]
145
+ # write the text (effectively freezing it) into the file at
146
+ # this path. An attempt will be made to put down the necessary
147
+ # directory structure.
148
+ #
149
+ # This path is relative to the base of the store defined in
150
+ # the constructor.
151
+ def write this_text, to_path
152
+
153
+ freeze_filepath = File.join @store_path, to_path
154
+
155
+ log.info(x) { "ColdStore freezing #{this_text.length} characters of worthless text."}
156
+ log.info(x) { "ColdStore freeze file path => #{freeze_filepath.hr_path}"}
157
+
158
+ FileUtils.mkdir_p(File.dirname(freeze_filepath))
159
+ File.write freeze_filepath, this_text
160
+
161
+ end
162
+
163
+
164
+ private
165
+
166
+ # @todo - write sync (with a local bias during conflicts)
167
+ # The open up to the public (published) api.
168
+ def push
169
+
170
+
171
+ end
172
+
173
+ # @todo - write sync (with a rmote bias during conflicts)
174
+ # The open up to the public (published) api.
175
+ def pull
176
+
177
+ end
178
+
179
+
180
+ end
181
+
182
+
183
+ end
184
+
185
+
186
+ end