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,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.