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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.yardopts +3 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +793 -0
- data/Rakefile +16 -0
- data/bin/safe +5 -0
- data/lib/configs/README.md +58 -0
- data/lib/extension/array.rb +162 -0
- data/lib/extension/dir.rb +35 -0
- data/lib/extension/file.rb +123 -0
- data/lib/extension/hash.rb +33 -0
- data/lib/extension/string.rb +572 -0
- data/lib/factbase/facts.safedb.net.ini +38 -0
- data/lib/interprete.rb +462 -0
- data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
- data/lib/keytools/kdf.api.rb +243 -0
- data/lib/keytools/kdf.bcrypt.rb +265 -0
- data/lib/keytools/kdf.pbkdf2.rb +262 -0
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1391 -0
- data/lib/keytools/key.db.rb +330 -0
- data/lib/keytools/key.docs.rb +195 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.ident.rb +243 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +259 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +585 -0
- data/lib/logging/gem.logging.rb +132 -0
- data/lib/modules/README.md +43 -0
- data/lib/modules/cryptology/aes-256.rb +154 -0
- data/lib/modules/cryptology/amalgam.rb +70 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +138 -0
- data/lib/modules/cryptology/crypt.io.rb +225 -0
- data/lib/modules/cryptology/engineer.rb +99 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/modules/storage/git.store.rb +399 -0
- data/lib/session/fact.finder.rb +334 -0
- data/lib/session/require.gem.rb +112 -0
- data/lib/session/time.stamp.rb +340 -0
- data/lib/session/user.home.rb +49 -0
- data/lib/usecase/cmd.rb +487 -0
- data/lib/usecase/config/README.md +57 -0
- data/lib/usecase/docker/README.md +146 -0
- data/lib/usecase/docker/docker.rb +49 -0
- data/lib/usecase/edit/README.md +43 -0
- data/lib/usecase/edit/delete.rb +46 -0
- data/lib/usecase/export.rb +40 -0
- data/lib/usecase/files/README.md +37 -0
- data/lib/usecase/files/eject.rb +56 -0
- data/lib/usecase/files/file_me.rb +78 -0
- data/lib/usecase/files/read.rb +169 -0
- data/lib/usecase/files/write.rb +89 -0
- data/lib/usecase/goto.rb +57 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +157 -0
- data/lib/usecase/init.rb +63 -0
- data/lib/usecase/jenkins/README.md +146 -0
- data/lib/usecase/jenkins/jenkins.rb +208 -0
- data/lib/usecase/login.rb +71 -0
- data/lib/usecase/logout.rb +28 -0
- data/lib/usecase/open.rb +71 -0
- data/lib/usecase/print.rb +40 -0
- data/lib/usecase/put.rb +81 -0
- data/lib/usecase/set.rb +44 -0
- data/lib/usecase/show.rb +138 -0
- data/lib/usecase/terraform/README.md +91 -0
- data/lib/usecase/terraform/terraform.rb +121 -0
- data/lib/usecase/token.rb +35 -0
- data/lib/usecase/update/README.md +55 -0
- data/lib/usecase/update/rename.rb +180 -0
- data/lib/usecase/use.rb +41 -0
- data/lib/usecase/verse.rb +20 -0
- data/lib/usecase/view.rb +71 -0
- data/lib/usecase/vpn/README.md +150 -0
- data/lib/usecase/vpn/vpn.ini +31 -0
- data/lib/usecase/vpn/vpn.rb +54 -0
- data/lib/version.rb +3 -0
- data/safedb.gemspec +34 -0
- metadata +193 -0
data/lib/usecase/cmd.rb
ADDED
@@ -0,0 +1,487 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module SafeDb
|
5
|
+
|
6
|
+
# The parent SafeDb use case is designed to be extended by the cli
|
7
|
+
# (command line) use cases like {SafeDb::Open}, {SafeDb::Put} and
|
8
|
+
# {SafeDb::Lock} because it describes behaviour common to at least two
|
9
|
+
# (but usually more) of the use cases.
|
10
|
+
#
|
11
|
+
# == Common Use Case Behaviour
|
12
|
+
#
|
13
|
+
# This {SafeDb::UseCase} use case is designed to be extended and does preparatory
|
14
|
+
# work to create favourable and useful conditions to make use cases readable,
|
15
|
+
# less repetitive, simpler and concise.
|
16
|
+
#
|
17
|
+
# == Machine (Workstation) Configuration File
|
18
|
+
#
|
19
|
+
# The global configuration filepath is found off the home directory using {Dir.home}.
|
20
|
+
#
|
21
|
+
# ~/.safedb.net/safedb.net.configuration.ini
|
22
|
+
#
|
23
|
+
# The global configuration file in INI format is managed through the methods
|
24
|
+
#
|
25
|
+
# - {grab} read the value at key_name from the default section
|
26
|
+
# - {stash} put directive key/value pair in default section
|
27
|
+
# - {read} read the value at key_name from the parameter section
|
28
|
+
# - {write} put directive key/value pair in parameter section
|
29
|
+
class UseCase
|
30
|
+
|
31
|
+
# This variable should be set to true if the use case call
|
32
|
+
# originates from a shell different from the one through which
|
33
|
+
# the login ocurred.
|
34
|
+
#
|
35
|
+
# To proceed, the shell that hosted the safe login must be a
|
36
|
+
# parent or at least an ancestor of this shell.
|
37
|
+
#
|
38
|
+
# This variable need not be set if the login shell is the direct
|
39
|
+
# parent of this one (the every day manual usage scenario).
|
40
|
+
#
|
41
|
+
# If however the login occurred from a grand or great grandparent
|
42
|
+
# shell (as is the case when nested scripts make an agent-like call),
|
43
|
+
# this variable must be set to true.
|
44
|
+
attr_writer :from_script
|
45
|
+
|
46
|
+
# This prefix denotes keys and their values should be posted as environment
|
47
|
+
# variables within the context (for example terraform) before instigating the
|
48
|
+
# main action like terraform apply.
|
49
|
+
ENV_VAR_PREFIX_A = "evar."
|
50
|
+
ENV_VAR_PREFIX_B = "@evar."
|
51
|
+
|
52
|
+
# This prefix precedes keynames whose map value represents a file object.
|
53
|
+
FILE_KEY_PREFIX = "file::"
|
54
|
+
|
55
|
+
# The base64 encoded representation of the file content is placed into
|
56
|
+
# a map with this keyname.
|
57
|
+
FILE_CONTENT_KEY = "content64"
|
58
|
+
|
59
|
+
# The file base name is placed into a map with this keyname.
|
60
|
+
FILE_NAME_KEY = "filename"
|
61
|
+
|
62
|
+
|
63
|
+
# This is the root command typed into the shell to invoke one of the
|
64
|
+
# safe use cases.
|
65
|
+
COMMANDMENT = "safe"
|
66
|
+
|
67
|
+
|
68
|
+
# The name of the environment variable that will hold the session token
|
69
|
+
# generated by {self.generate_session_token}. This environment variable
|
70
|
+
# is typically instantiated either manually (for ad-hoc use) or through
|
71
|
+
# media such as dot profile.
|
72
|
+
ENV_VAR_KEY_NAME = "SAFE_TTY_TOKEN"
|
73
|
+
|
74
|
+
|
75
|
+
# If and when this command line credentials management app needs to write
|
76
|
+
# any configuration directives to the machine's userspace it will use this
|
77
|
+
# constant to denote the (off-home) directory name.
|
78
|
+
APP_DIR_NAME = "safedb.net"
|
79
|
+
|
80
|
+
|
81
|
+
# Get the master database. This behaviour can only complete
|
82
|
+
# correctly if a successful login precedes this call either
|
83
|
+
# in this or an ancestral shell environment.
|
84
|
+
#
|
85
|
+
# @return [Hash]
|
86
|
+
# the hash data structure returned represents the master
|
87
|
+
# database.
|
88
|
+
def get_master_database
|
89
|
+
|
90
|
+
begin
|
91
|
+
|
92
|
+
log.info(x) { "Request for master db with from_script set to #{@from_script}" }
|
93
|
+
return KeyApi.read_master_db( @from_script )
|
94
|
+
|
95
|
+
rescue OpenSSL::Cipher::CipherError => e
|
96
|
+
|
97
|
+
log.fatal(x) { "Exception getting master db for the safe book." }
|
98
|
+
log.fatal(x) { "The from_script parameter came set as [ #{@from_script} ]" }
|
99
|
+
log.fatal(x) { "The exception message is ~> [[ #{e.message} ]]" }
|
100
|
+
e.backtrace.log_lines
|
101
|
+
abort e.message
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
# The path to the initial configuration file below the user's home
|
108
|
+
# directory. The directory name the configuration file sits in is
|
109
|
+
# a dot prefixed context name derived from the value inside the
|
110
|
+
# {APP_DIR_NAME} constant.
|
111
|
+
#
|
112
|
+
# ~/.<<context-name>>/<<context-name>>-configuration.ini
|
113
|
+
#
|
114
|
+
# You can see the filename too is derived from the context with a
|
115
|
+
# trailing string ending in <b>.ini</b>.
|
116
|
+
#
|
117
|
+
# @return [String] full path to the context configuration file
|
118
|
+
def config_file
|
119
|
+
return File.join config_directory(), "#{APP_DIR_NAME}.configuration.ini"
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# This method returns the absolute path to the directory that the
|
124
|
+
# configuration file sits in. It is basically just the dot prefixed
|
125
|
+
# context name (the {APP_DIR_NAME} constant).
|
126
|
+
#
|
127
|
+
# ~/.<<context-name>>
|
128
|
+
#
|
129
|
+
# @return [String] path to directory holding context configuration file
|
130
|
+
def config_directory
|
131
|
+
return File.join( Dir.home, ".#{APP_DIR_NAME}" )
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# Execute the use cases's flow from beginning when
|
136
|
+
# you validate the input and parameters through the
|
137
|
+
# memorize, execute and the final cleanup.
|
138
|
+
def flow_of_events
|
139
|
+
|
140
|
+
check_pre_conditions
|
141
|
+
execute
|
142
|
+
cleanup
|
143
|
+
check_post_conditions
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# Validate the input parameters and check that the current
|
149
|
+
# state is perfect for executing the use case.
|
150
|
+
#
|
151
|
+
# If either of the above fail - the validation function should
|
152
|
+
# set a human readable string and then throw an exception.
|
153
|
+
def check_pre_conditions
|
154
|
+
|
155
|
+
begin
|
156
|
+
|
157
|
+
pre_validation
|
158
|
+
|
159
|
+
rescue OpenError::CliError => e
|
160
|
+
|
161
|
+
puts ""
|
162
|
+
puts "Your command did not complete successfully."
|
163
|
+
puts "Pre validation checks failed."
|
164
|
+
puts ""
|
165
|
+
puts " => #{e.message}"
|
166
|
+
puts ""
|
167
|
+
abort e.message
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# Override me if you need to
|
174
|
+
def pre_validation
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# After the main flow of events certain state conditions
|
180
|
+
# must hold true thus demonstrating that the observable
|
181
|
+
# value has indeed ben delivered.
|
182
|
+
#
|
183
|
+
# Child classes should subclass this method and place any
|
184
|
+
# post execution (post condition) checks in it and then
|
185
|
+
# make a call to this method through the "super" keyword.
|
186
|
+
def check_post_conditions
|
187
|
+
|
188
|
+
begin
|
189
|
+
|
190
|
+
post_validation
|
191
|
+
|
192
|
+
rescue OpenError::CliError => e
|
193
|
+
|
194
|
+
puts ""
|
195
|
+
puts "Your command did not complete successfully."
|
196
|
+
puts "Post validation checks failed."
|
197
|
+
puts ""
|
198
|
+
puts " => #{e.message}"
|
199
|
+
#### puts " => #{e.culprit}"
|
200
|
+
puts ""
|
201
|
+
abort e.message
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
# Child classes should subclass this method and place any
|
208
|
+
# post execution (post condition) checks in it and then
|
209
|
+
# make a call to this method through the "super" keyword if
|
210
|
+
# this method gets any global behaviour in it worth calling.
|
211
|
+
def post_validation
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
# Execute the main flow of events of the use case. Any
|
217
|
+
# exceptions thrown are captured and if the instance
|
218
|
+
# variale [@human_readable_message] is set - tell the
|
219
|
+
# user about it. Without any message - just tell the
|
220
|
+
# user something went wrong and tell them where the logs
|
221
|
+
# are that might carry more information.
|
222
|
+
def execute
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
# If the use case validation went well, the memorization
|
228
|
+
# went well the
|
229
|
+
def cleanup
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
# This use case is initialized primary by resolving the configured
|
235
|
+
# +general and use case specific facts+. To access the general facts,
|
236
|
+
# a domain name is expected in the parameter delegated by the extension
|
237
|
+
# use case classes.
|
238
|
+
def initialize
|
239
|
+
|
240
|
+
class_name = self.class.name.split(":").last.downcase
|
241
|
+
is_no_token_usecase = [ "token", "init", "id" ].include? class_name
|
242
|
+
return if is_no_token_usecase
|
243
|
+
|
244
|
+
exit(100) unless ops_key_exists?
|
245
|
+
|
246
|
+
fact_filepath = File.sister_filepath( self, "ini", :execute )
|
247
|
+
log.info(x) { "Search location for INI factfile is [#{fact_filepath}]" }
|
248
|
+
return unless File.exists?( fact_filepath )
|
249
|
+
|
250
|
+
@facts = FactFind.new()
|
251
|
+
add_secret_facts @facts
|
252
|
+
@facts.assimilate_ini_file( fact_filepath )
|
253
|
+
@dictionary = @facts.f[ @facts.to_symbol( class_name ) ]
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
|
261
|
+
ENV_PATH = "env.path"
|
262
|
+
KEY_PATH = "key.path"
|
263
|
+
ENVELOPE_KEY_PREFIX = "envelope@"
|
264
|
+
|
265
|
+
LAST_ACCESSED = "last.accessed.time"
|
266
|
+
|
267
|
+
SESSION_DICT_LOCK_SIZE = 32
|
268
|
+
|
269
|
+
SESSION_DICT_LOCK_NAME = "crypted.session.dict.lock"
|
270
|
+
|
271
|
+
ENVELOPE_KEY_SIZE = 32
|
272
|
+
|
273
|
+
ENVELOPE_KEY_NAME = "crypted.envelope.key"
|
274
|
+
|
275
|
+
ENVELOPE_ID_SIZE = 16
|
276
|
+
|
277
|
+
ENVELOPE_ID_NAME = "crypted.envelope.id"
|
278
|
+
|
279
|
+
SESSION_ID_SIZE = 64
|
280
|
+
|
281
|
+
SESSION_FILENAME_ID_SIZE = 24
|
282
|
+
|
283
|
+
SESSION_START_TIMESTAMP_NAME = "session.creation.time"
|
284
|
+
|
285
|
+
MASTER_LOCK_KEY_NAME = "master.session.lock.key"
|
286
|
+
|
287
|
+
APPLICATION_GEM_NAME = "safedb.net"
|
288
|
+
APPLICATION_GEM_WEBSITE = "https://www.safedb.net"
|
289
|
+
APPLICATION_GITHUB_URL = "https://github.com/devops4me/safedb.net"
|
290
|
+
|
291
|
+
|
292
|
+
def add_secret_facts fact_db
|
293
|
+
|
294
|
+
master_db = get_master_database()
|
295
|
+
raise ArgumentError.new "There is no open chapter here." if unopened_envelope?( master_db )
|
296
|
+
chapter_id = ENVELOPE_KEY_PREFIX + master_db[ ENV_PATH ]
|
297
|
+
verse_id = master_db[ KEY_PATH ]
|
298
|
+
chapter_data = KeyDb.from_json( KeyApi.content_unlock( master_db[ chapter_id ] ) )
|
299
|
+
mini_dictionary = chapter_data[ master_db[ KEY_PATH ] ]
|
300
|
+
|
301
|
+
mini_dictionary.each do | key_str, value_str|
|
302
|
+
fact_db.assimilate_fact( "secrets", key_str, value_str )
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
def create_header()
|
309
|
+
|
310
|
+
return KeyApi.format_header(
|
311
|
+
SafeDb::VERSION,
|
312
|
+
APPLICATION_GEM_NAME,
|
313
|
+
APPLICATION_GITHUB_URL,
|
314
|
+
@domain_name
|
315
|
+
)
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
|
321
|
+
def ops_key_exists?
|
322
|
+
|
323
|
+
log_env()
|
324
|
+
|
325
|
+
if ( ENV.has_key? ENV_VAR_KEY_NAME )
|
326
|
+
return true
|
327
|
+
end
|
328
|
+
|
329
|
+
puts ""
|
330
|
+
puts "safe needs you to create a session key."
|
331
|
+
puts "To automate this step see the documentation."
|
332
|
+
puts "To create the key run the below command."
|
333
|
+
puts ""
|
334
|
+
puts " export #{ENV_VAR_KEY_NAME}=`#{COMMANDMENT} token`"
|
335
|
+
puts ""
|
336
|
+
puts "Those are backticks surrounding `#{COMMANDMENT} token`"
|
337
|
+
puts "Not apostrophes."
|
338
|
+
puts ""
|
339
|
+
|
340
|
+
return false
|
341
|
+
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
def log_env()
|
346
|
+
|
347
|
+
log.info(x) { "Gem Root Folder => #{Gem.dir()}" }
|
348
|
+
log.info(x) { "Gem Config File => #{Gem.config_file()}" }
|
349
|
+
log.info(x) { "Gem Binary Path => #{Gem.default_bindir()}" }
|
350
|
+
log.info(x) { "Gem Host Path => #{Gem.host()}" }
|
351
|
+
log.info(x) { "Gem Caller Folder => #{Gem.location_of_caller()}" }
|
352
|
+
log.info(x) { "Gem Paths List => #{Gem.path()}" }
|
353
|
+
log.info(x) { "Gem Platforms => #{Gem.platforms()}" }
|
354
|
+
log.info(x) { "Gem Ruby Version X => #{Gem.ruby()}" }
|
355
|
+
log.info(x) { "Gem Ruby Version Y => #{Gem::VERSION}" }
|
356
|
+
log.info(x) { "Gem Ruby Version Z => #{Gem.latest_rubygems_version()}" }
|
357
|
+
log.info(x) { "Gem User Folder => #{Gem.user_dir()}" }
|
358
|
+
log.info(x) { "Gem User Home => #{Gem.user_home()}" }
|
359
|
+
|
360
|
+
return
|
361
|
+
|
362
|
+
end
|
363
|
+
|
364
|
+
|
365
|
+
def unopened_envelope?( key_database )
|
366
|
+
|
367
|
+
return false if key_database.has_key?( ENV_PATH )
|
368
|
+
print_unopened_envelope()
|
369
|
+
return true
|
370
|
+
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
def print_unopened_envelope()
|
375
|
+
|
376
|
+
puts ""
|
377
|
+
puts "Problem - before creating, reading or changing data you"
|
378
|
+
puts "must first open a path to it like this."
|
379
|
+
puts ""
|
380
|
+
puts " #{COMMANDMENT} open email.accounts joe@gmail.com"
|
381
|
+
puts ""
|
382
|
+
puts " then you put data at that path"
|
383
|
+
puts ""
|
384
|
+
puts " #{COMMANDMENT} put username joebloggs"
|
385
|
+
puts " #{COMMANDMENT} put password jo3s-s3cr3t"
|
386
|
+
puts " #{COMMANDMENT} put phone-no 07123456789"
|
387
|
+
puts " #{COMMANDMENT} put question \"Mums maiden name\""
|
388
|
+
puts ""
|
389
|
+
puts " and why not read it back"
|
390
|
+
puts ""
|
391
|
+
puts " #{COMMANDMENT} get password"
|
392
|
+
puts ""
|
393
|
+
puts " then close the path."
|
394
|
+
puts ""
|
395
|
+
puts " #{COMMANDMENT} close"
|
396
|
+
puts ""
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
|
401
|
+
def print_already_logged_in
|
402
|
+
|
403
|
+
puts ""
|
404
|
+
puts "We are already logged in. Open a secret envelope, put, then seal."
|
405
|
+
puts ""
|
406
|
+
puts " #{COMMANDMENT} open aws.credentials:s3reader"
|
407
|
+
puts " #{COMMANDMENT} put access_key ABCD1234"
|
408
|
+
puts " #{COMMANDMENT} put secret_key FGHIJ56789"
|
409
|
+
puts " #{COMMANDMENT} put region_key eu-central-1"
|
410
|
+
puts " #{COMMANDMENT} seal"
|
411
|
+
puts ""
|
412
|
+
|
413
|
+
end
|
414
|
+
|
415
|
+
|
416
|
+
def print_already_initialized
|
417
|
+
|
418
|
+
puts ""
|
419
|
+
puts "You can go ahead and login."
|
420
|
+
puts "Your domain [#{@domain_name}] is already setup."
|
421
|
+
puts "You should already know the password."
|
422
|
+
puts ""
|
423
|
+
puts " #{COMMANDMENT} login #{@domain_name}"
|
424
|
+
puts ""
|
425
|
+
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
def print_domain_initialized
|
430
|
+
|
431
|
+
puts ""
|
432
|
+
puts "It is time to login."
|
433
|
+
puts "The protector keys for [#{@domain_name}] have been setup."
|
434
|
+
puts "From now on you simply login to use this domain."
|
435
|
+
puts ""
|
436
|
+
puts " #{COMMANDMENT} login #{@domain_name}"
|
437
|
+
puts ""
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
def print_not_initialized
|
443
|
+
|
444
|
+
puts ""
|
445
|
+
puts "Please initialize the app domain on this machine."
|
446
|
+
puts "Give a domain name and a folder for key storage."
|
447
|
+
puts ""
|
448
|
+
puts " #{COMMANDMENT} init <domain_name> \"$HOME/open.world\""
|
449
|
+
puts ""
|
450
|
+
|
451
|
+
end
|
452
|
+
|
453
|
+
|
454
|
+
def print_success_initializing
|
455
|
+
|
456
|
+
puts ""
|
457
|
+
puts "Success - now open a secret envelope, put, then seal."
|
458
|
+
puts ""
|
459
|
+
puts " #{COMMANDMENT} open aws.credentials:s3reader"
|
460
|
+
puts " #{COMMANDMENT} put access_key ABCD1234"
|
461
|
+
puts " #{COMMANDMENT} put secret_key FGHIJ56789"
|
462
|
+
puts " #{COMMANDMENT} put region_key eu-central-1"
|
463
|
+
puts " #{COMMANDMENT} seal"
|
464
|
+
puts ""
|
465
|
+
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
def print_login_success
|
470
|
+
|
471
|
+
puts ""
|
472
|
+
puts "Success - you are logged in."
|
473
|
+
puts ""
|
474
|
+
puts " #{COMMANDMENT} open aws.credentials:s3reader"
|
475
|
+
puts " #{COMMANDMENT} put access_key ABCD1234"
|
476
|
+
puts " #{COMMANDMENT} put secret_key FGHIJ56789"
|
477
|
+
puts " #{COMMANDMENT} put region_key eu-central-1"
|
478
|
+
puts " #{COMMANDMENT} seal"
|
479
|
+
puts ""
|
480
|
+
|
481
|
+
end
|
482
|
+
|
483
|
+
|
484
|
+
end
|
485
|
+
|
486
|
+
|
487
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
# Safe | Increasing Key Derivation Function Cost
|
3
|
+
|
4
|
+
Safe uses two **key derivation functions** (**BCrypt** and **PBKDF2**) to transform the human sourced password into a key. The only role the resulting key plays is the encryption and decryption of a large **highly random computer generated key** which in turn protects the **master database**.
|
5
|
+
|
6
|
+
### Why are two (2) key derivation algorithms used?
|
7
|
+
|
8
|
+
Your credentials are still safe even in the rare case of a successful analytical attack being discovered on one of the algorithms.
|
9
|
+
|
10
|
+
## Why High Computational Costs are Desirable?
|
11
|
+
|
12
|
+
Unlike most algorithms, key derivation functions **work best when they run slowly!** This protects against brute force attacks where attackers use "rainbow tables" or try to iterate over common passwords in an attempt to rederive the key.
|
13
|
+
|
14
|
+
Your responsibility is to make **safe as slow as is tolerable** by increasing the number of iterations required to derive each key.
|
15
|
+
|
16
|
+
## Safe | Increasing the Cost of Both Key Derivation Functions
|
17
|
+
|
18
|
+
You should increase the cost of **safe's** key derivation functions until safe commands run as slow as is tolerably and no less!
|
19
|
+
|
20
|
+
```bash
|
21
|
+
safe cost bcrypt 3
|
22
|
+
safe cost pbkdf2 4
|
23
|
+
```
|
24
|
+
|
25
|
+
Both algorithms can be configured with a cost parameter from 1 to 7 inclusive. The default cost is 1 for both and is moderately secure and runs as slowly as is tolerable on an IBM ThinkPad laptop with an Intel Pentium i5 processor with 16G of RAM.
|
26
|
+
|
27
|
+
Note that PBKDF2 has no maximum. BCrypt limits the cost to 2^16.
|
28
|
+
|
29
|
+
<pre>
|
30
|
+
-------- - ------------ - --------------- - --------------------- - ---------------- -
|
31
|
+
| Cost | BCrypt | BCrypt | PBKDF2 | PBKDF2 |
|
32
|
+
| | Cost | Iterations | Cost | Iterations |
|
33
|
+
| ------ - ------------ - --------------- - --------------------- - ---------------- |
|
34
|
+
| 1 | 2^10 | 1,024 | 3^0 x 100,000 | 100,000 |
|
35
|
+
| 2 | 2^11 | 2,048 | 3^1 x 100,000 | 300,000 |
|
36
|
+
| 3 | 2^12 | 4,096 | 3^2 x 100,000 | 900,000 |
|
37
|
+
| 4 | 2^13 | 8,192 | 3^3 x 100,000 | 2,700,000 |
|
38
|
+
| 5 | 2^14 | 16,384 | 3^4 x 100,000 | 8,100,000 |
|
39
|
+
| 6 | 2^15 | 32,768 | 3^5 x 100,000 | 24,300,000 |
|
40
|
+
| 7 | 2^16 | 65,536 | 3^6 x 100,000 | 72,900,000 |
|
41
|
+
-------- - ------------ - --------------- - --------------------- - ---------------- -
|
42
|
+
</pre>
|
43
|
+
|
44
|
+
When you increase the cost **safe will become perceivably slower**. With a cost of 7, a laptop takes many minutes but an AWS cloud compute optimized M5 server crunches through in mere seconds.
|
45
|
+
|
46
|
+
## What is Your Data Worth?
|
47
|
+
|
48
|
+
Attackers can bring a significant amount of modern data centre hardware to the table in order to access your credentials.
|
49
|
+
|
50
|
+
However, these computing resources cost money and the amount of money an attacker spends will be proportional to the perceived gains from a successfully attack. The bigger the dollar signs in their eyes, the more they will spend.
|
51
|
+
|
52
|
+
The default settings coupled with a **12 character password** takes (on average) 50 years to crack with computing resources that will cost $1,000 every single day.
|
53
|
+
|
54
|
+
### Twenty Million Dollars
|
55
|
+
|
56
|
+
If what you are protecting is worth more than **$(50 x 366 x 1,000)**, you should use an at least 16 character password and increase the computational cost parameters for both key derivation functions.
|
57
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
|
2
|
+
# safe jenkins <command>
|
3
|
+
|
4
|
+
|
5
|
+
### safe jenkins post [aws|docker|git] <<jenkins-host-url>> | introduction
|
6
|
+
|
7
|
+
Use **`safe jenkins post`** to inject both your **AWS IAM User** and **docker login/password** credentials into your Jenkins 2.0 continuous integration portal reachable by the **jenkins host url** given in the 4th parameter of the safe command.
|
8
|
+
|
9
|
+
---
|
10
|
+
|
11
|
+
## safe jenkins post | prerequisite
|
12
|
+
|
13
|
+
Before you can inject credentials into jenkins using **`safe jenkins post`** you must
|
14
|
+
|
15
|
+
- be logged into your safe
|
16
|
+
- have opened the appropriate chapter/verse
|
17
|
+
- have put the required credential key/value pairs into the safe
|
18
|
+
- have the jenkins service up and running
|
19
|
+
|
20
|
+
After the post (to jenkins), your continuous integration jobs will be able to access the credential values via their IDs as stated in the below table.
|
21
|
+
|
22
|
+
---
|
23
|
+
|
24
|
+
## safe jenkins post aws | key names table
|
25
|
+
|
26
|
+
As credentials are WORO (write once, read often), safe makes the reading part very very easy (and secure) so your effort is frontloaded.
|
27
|
+
|
28
|
+
| Safe Key | Jenkins Credential IDs | Environment Variable | Description |
|
29
|
+
|:-----------:|:----------------------:|:--------------------- |:-------------------------------------------------------- |
|
30
|
+
| @access.key | safe.aws.access.key | AWS_ACCESS_KEY_ID | The AWS IAM user's access key credential. |
|
31
|
+
| @secret.key | safe.aws.secret.key | AWS_SECRET_ACCESS_KEY | The AWS IAM user's secret key credential. |
|
32
|
+
| region.key | safe.aws.region.key | AWS_REGION | The AWS region key that your Jenkins service points to. |
|
33
|
+
|
34
|
+
So you can see that by convention, safe expects the credential keys in the safe to be named a particular way, and likewise, you can be assured of the IDs it gives those credentials when posted to Jenkins.
|
35
|
+
|
36
|
+
|
37
|
+
## safe jenkins post | credentials lifecycle
|
38
|
+
|
39
|
+
The life of the credentials begins when you create an IAM user and record its access and secret keys. Then
|
40
|
+
|
41
|
+
- you login to safe and store the 3 keys and their values
|
42
|
+
- safe jenkins post will read the values and post them to Jenkins
|
43
|
+
- Jenkins stores the values in conjunction with the Jenkins Credential IDs
|
44
|
+
- pipeline jobs ask Jenkins to put the Credential ID values against environment variables
|
45
|
+
- tools like Terraform and AwsCli use the environment variables to work in the cloud
|
46
|
+
|
47
|
+
|
48
|
+
## Jenkinsfile | Usage in Pipeline Jobs
|
49
|
+
|
50
|
+
Here is a pipeline declaration within a Jenkinsfile that asks Jenkins to put the credential values in its secrets store into the stated environment variables.
|
51
|
+
|
52
|
+
environment
|
53
|
+
{
|
54
|
+
AWS_ACCESS_KEY_ID = credentials( 'safe.aws.access.key' )
|
55
|
+
AWS_SECRET_ACCESS_KEY = credentials( 'safe.aws.secret.key' )
|
56
|
+
AWS_REGION = credentials( 'safe.aws.region.key' )
|
57
|
+
}
|
58
|
+
|
59
|
+
After **`safe jenkins post aws`** you can **click into the Credentials item in the Jenkins main menu** to assure yourself that the credentials have indeed been properly injected.
|
60
|
+
|
61
|
+
---
|
62
|
+
|
63
|
+
## How to Write AWS Credentials into your Safe
|
64
|
+
|
65
|
+
In order to **`safe terraform apply`** or **`safe jenkins post aws <<jenkins-host-url>>`** or `safe visit` you must first put those ubiquitous IAM programmatic user credentials into your safe.
|
66
|
+
|
67
|
+
$ safe login joebloggs.com # open the book
|
68
|
+
|
69
|
+
$ safe open iam dev.s3.reader # open chapter and verse
|
70
|
+
$ safe put @access.key ABCD1234EFGH5678 # Put IAM access key in safe
|
71
|
+
$ safe put @secret.key xyzabcd1234efgh5678 # Put IAM secret key in safe
|
72
|
+
$ safe put region.key eu-west-3 # infrastructure in Paris
|
73
|
+
|
74
|
+
$ safe open iam canary.admin # open chapter and verse
|
75
|
+
$ safe put @access.key 4321DCBA8765WXYZ # Put IAM access key in safe
|
76
|
+
$ safe put @secret.key 5678uvwx4321abcd9876 # Put IAM secret key in safe
|
77
|
+
$ safe put region.key eu-west-1 # infrastructure in Dublin
|
78
|
+
|
79
|
+
$ safe logout
|
80
|
+
|
81
|
+
|
82
|
+
---
|
83
|
+
|
84
|
+
|
85
|
+
## How to write DockerHub Credentials into your Safe
|
86
|
+
|
87
|
+
#### safe jenkins post docker https://jenkins.example.com
|
88
|
+
|
89
|
+
Before you can issue a **`safe jenkins post docker http://localhost:8080`** you must insert your docker login credentials in the form of a docker.username and @docker.password into your safe. Remember that any key starting with the `@ sign` tells the safe to keep it a secret like when you issue a **`safe show`** command.
|
90
|
+
|
91
|
+
$ safe login joebloggs.com # open the book
|
92
|
+
$ safe open docker production # at the docker (for production) chapter and verse
|
93
|
+
$ safe put docker.username admin # Put the Docker repository login docker.username into the safe
|
94
|
+
$ safe put @docker.password s3cr3t # Put the Docker repository login @docker.password into the safe
|
95
|
+
$ safe logout
|
96
|
+
|
97
|
+
When docker credentials are injected into a Jenkins service the safe will expect to find **lines** at the open chapter and verse location with key names **`docker.username`** and **`@docker.password`**.
|
98
|
+
|
99
|
+
The safe promises to inject credentials with an ID of **safe.docker.login.id** so any jenkins jobs that need to use the docker login docker.username and password must specify this ID when talking to the Jenkins credentials service.
|
100
|
+
|
101
|
+
|
102
|
+
### DockerHub Credentials Inject Response
|
103
|
+
|
104
|
+
Here is an example of posting dockerhub credentials into a Jenkins service running on the local machine.
|
105
|
+
|
106
|
+
``` bash
|
107
|
+
safe jenkins post docker http://localhost:8080
|
108
|
+
```
|
109
|
+
|
110
|
+
If successful safe provides a polite response detailing what just happened.
|
111
|
+
|
112
|
+
```
|
113
|
+
- Jenkins Host Url : http://localhost:8080/credentials/store/system/domain/_/createCredentials
|
114
|
+
- Credentials ID : safe.docker.login.id
|
115
|
+
- Inject Username : devops4me
|
116
|
+
- So what is this? : The docker repository login credentials in the shape of a username and password.
|
117
|
+
|
118
|
+
% Total % Received % Xferd Average Speed Time Time Time Current
|
119
|
+
Dload Upload Total Spent Left Speed
|
120
|
+
100 428 0 0 100 428 0 47555 --:--:-- --:--:-- --:--:-- 47555
|
121
|
+
```
|
122
|
+
|
123
|
+
---
|
124
|
+
|
125
|
+
|
126
|
+
## safe integrations | we need your help
|
127
|
+
|
128
|
+
**You can help to extend safe's integrations.**
|
129
|
+
|
130
|
+
By design - safe integrations are simple to write. They primarily integrate with producers and consumers. To deliver efficacy to devops engineers safe will endeavour to
|
131
|
+
|
132
|
+
- **send** credentials to **downstream consumers** and
|
133
|
+
- **receive** credentials from **upstream producers**
|
134
|
+
|
135
|
+
safe needs pull requests from the devops community and it promises to always strive to keep the task of writing an integration extremely simple.
|
136
|
+
|
137
|
+
### integrations | what giving takes?
|
138
|
+
|
139
|
+
Currently, writing an integration entails delivering 3 or 4 artifacts which are
|
140
|
+
|
141
|
+
- 1 simple Ruby class
|
142
|
+
- 1 README.md documenting the command structure, the prerequisites and the expected outcome
|
143
|
+
- 1 class containing unit tests
|
144
|
+
- (optionaly) an INI file if many configuration and facts are involved
|
145
|
+
|
146
|
+
Giving doesn't take much so roll up your sleeves (or frocks) and get writing.
|