safedb 0.01.0001
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|