safedb 0.01.0001

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 (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