sekrets 0.4.2

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.
data/bin/sekrets ADDED
@@ -0,0 +1,264 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ Main {
4
+ #
5
+ synopsis <<-__
6
+ ~> echo 'plaintext' | sekrets write sekrets.txt --key 42
7
+
8
+ ~> sekrets read sekrets.txt --key 42
9
+
10
+ ~> sekrets edit sekrets.txt
11
+ __
12
+
13
+ description <<-__
14
+ TL;DR
15
+ # create an encrypted config file
16
+
17
+ ruby -r yaml -e'puts {:api_key => 1234}.to_yaml' | sekrets write config/settings.yml.enc --key 42
18
+
19
+ # display it
20
+
21
+ sekrets read config/settings.yml.enc --key 42
22
+
23
+ # edit it
24
+
25
+ sekrets edit config/settings.yml.enc --key 42
26
+
27
+ # see that it's encrypted
28
+
29
+ cat config/settings.yml.enc
30
+
31
+ # commit it
32
+
33
+ git add config/settings.yml.enc
34
+
35
+ # put the decryption key in a file
36
+
37
+ echo 42 > sekrets.key
38
+
39
+ # ignore this file in git
40
+
41
+ echo sekrets.key >> .gitgnore
42
+
43
+ # make sure this file gets deployed on your server
44
+
45
+ echo " require 'sekrets/capistrano' " >> Capfile
46
+
47
+ # commit and deploy
48
+
49
+ git add config/settings.yml.enc
50
+ git commit -am'encrypted settings yo'
51
+ git pull && git push && cap staging deploy
52
+
53
+ # access these settings in your application code
54
+
55
+ settings = Sekrets.settings_for('./config/settings.yml.enc')
56
+
57
+
58
+ GENERAL
59
+ sekrets provides commandline tools and a library to manage and access
60
+ encrypted files in your code base.
61
+
62
+ it allows one to check encrypted infomation into a repository and to manage
63
+ it alongside the rest of the code base. it elimnates the need to check in
64
+ unencrypted information, keys, or other sensitive infomation.
65
+
66
+ sekrets provides both a general mechanism for managing arbitrary encrypted
67
+ files and a specific mechanism for managing encrypted config files.
68
+
69
+
70
+ KEY LOOKUP
71
+ for *all* operations, from the command line or otherwise, sekrets uses the
72
+ following algorithm to search for a decryption key:
73
+
74
+ - any key passed directly as a parameter to a library call will be preferred
75
+
76
+ - otherwise the code looks for a companion key file. for example, given the
77
+ file 'config/sekrets.yml.enc' sekrets will look for a key at
78
+
79
+ config/sekrets.yml.enc.key
80
+
81
+ and
82
+
83
+ config/sekrets.yml.enc.k
84
+
85
+ if either of these is found to be non-empty the contents of the file will
86
+ be used as the decryption key for that file. you should *never* commit
87
+ these key files and also add them to your .gitignore - or similar.
88
+
89
+ - next a project key file is looked for. the path of this file is
90
+
91
+ ./sekrets.key
92
+
93
+ normally and, in a rails' application
94
+
95
+ RAILS_ROOT/sekrets.key
96
+
97
+ - if that is not found sekrets looks for the key in the environment under
98
+ the env var
99
+
100
+ SEKRETS_KEY
101
+
102
+ the env var used is configurable in the library
103
+
104
+ - next the global key file is search for, the path of this file is
105
+
106
+ ~/.sekrets.key
107
+
108
+ - finally, if no key has yet been specified or found, the user is prompted
109
+ to input the key. prompt only occurs if the user us attached to a tty.
110
+ so, for example, no prompt will hang and application being started in the
111
+ background such as a rails' application being managed by passenger.
112
+
113
+
114
+ see Sekrets.key_for for more details
115
+
116
+ KEY DISTRIBUTION
117
+ sekrets does *not* attempt to solve the key distribution problem for you,
118
+ with one exception:
119
+
120
+ if you are using capistrano to do a 'vanilla' ssh based deploy a simple
121
+ recipe is provided which will detect a local keyfile and scp it onto the
122
+ remote server(s) on deploy.
123
+
124
+ sekrets assumes that the local keyfile, if it exists, is correct.
125
+
126
+ in plain english the capistrano recipe does:
127
+
128
+ scp ./sekrets.key deploy@remote.host.com:/rails_root/current/sekrets.key
129
+
130
+ it goes without saying that the local keyfile should *never* be checked in
131
+ and also should be in .gitignore
132
+
133
+ distribution of this key among developers is outside the scope of the
134
+ library. likely unencrypted email is the best mechanism for distribution
135
+ ;-/
136
+ __
137
+
138
+ #
139
+ def run
140
+ help!
141
+ end
142
+
143
+ #
144
+ mode(:write){
145
+ argument('output', 'o'){
146
+ argument :required
147
+ default STDOUT
148
+ }
149
+
150
+ argument('input', 'i'){
151
+ argument :required
152
+ default STDIN
153
+ }
154
+
155
+ option('key', 'k'){
156
+ argument :required
157
+ }
158
+
159
+ def run
160
+ Sekrets.openw(params[:output].value) do |output|
161
+ key = key_for(output)
162
+
163
+ Sekrets.openr(params[:input].value) do |input|
164
+ decrypted = input.read
165
+ encrypted = Sekrets.encrypt(key, decrypted)
166
+ output.write(encrypted)
167
+ end
168
+ end
169
+ end
170
+ }
171
+
172
+ #
173
+ mode(:read){
174
+ argument('input', 'i'){
175
+ argument :required
176
+ default STDIN
177
+ }
178
+
179
+ argument('output', 'o'){
180
+ argument :required
181
+ default STDOUT
182
+ }
183
+
184
+ option('key', 'k'){
185
+ argument :required
186
+ }
187
+
188
+ def run
189
+ Sekrets.openr(params[:input].value) do |input|
190
+ key = key_for(input)
191
+
192
+ Sekrets.openw(params[:output].value) do |output|
193
+ encrypted = input.read
194
+ decrypted = Sekrets.decrypt(key, encrypted)
195
+ output.write(decrypted)
196
+ end
197
+ end
198
+ end
199
+ }
200
+
201
+ #
202
+ mode(:edit){
203
+ argument(:path)
204
+
205
+ option('key', 'k'){
206
+ argument :required
207
+ }
208
+
209
+ def run
210
+ path = params[:path].value
211
+ path = File.expand_path(path)
212
+
213
+ key = key_for(path)
214
+
215
+ decrypted =
216
+ if test(?s, path)
217
+ Sekrets.read(path, :key => key)
218
+ else
219
+ ''
220
+ end
221
+
222
+ basename = File.basename(path)
223
+ encrypted = nil
224
+
225
+ Sekrets.tmpdir do
226
+ IO.binwrite(basename, decrypted)
227
+
228
+ command = "#{ Sekrets.editor } #{ basename }"
229
+
230
+ system(command)
231
+
232
+ if $?.exitstatus == 0
233
+ content = IO.binread(basename)
234
+ Sekrets.write(path, content, :key => key)
235
+ end
236
+ end
237
+ end
238
+ }
239
+
240
+ #
241
+ def key_for(arg)
242
+ options = {}
243
+
244
+ if params[:key].given?
245
+ options[:key] = params[:key].value
246
+ end
247
+
248
+ key = Sekrets.key_for!(arg, options)
249
+ end
250
+ }
251
+
252
+
253
+ BEGIN {
254
+ require 'pathname'
255
+ bindir = Pathname.new(__FILE__).dirname.to_s
256
+ root = File.dirname(bindir)
257
+ libdir = File.join(root, 'lib')
258
+ require "#{ libdir }/sekrets.rb"
259
+ begin
260
+ require 'pry'
261
+ rescue Object
262
+ nil
263
+ end
264
+ }
@@ -0,0 +1,19 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :sekrets do
3
+ task :upload_key do
4
+ require 'fileutils'
5
+
6
+ rails_root = File.expand_path(File.dirname(__FILE__))
7
+
8
+ src = File.join(rails_root, 'sekrets.key')
9
+ dst = File.join(deploy_to, 'sekrets.key')
10
+
11
+ if test(?s, src)
12
+ upload(src, dst, :recursive => true)
13
+ end
14
+ end
15
+ end
16
+
17
+
18
+ before "deploy:finalize_update", "sekrets:upload_key"
19
+ end
data/lib/sekrets.rb ADDED
@@ -0,0 +1,343 @@
1
+ class Sekrets
2
+ #
3
+ Fattr(:env){ 'SEKRETS_KEY' }
4
+ Fattr(:editor){ ENV['SEKRETS_EDITOR'] || ENV['EDITOR'] || 'vim' }
5
+ Fattr(:root){ defined?(Rails.root) ? Rails.root : '.' }
6
+ Fattr(:project_key){ File.join(root, 'sekrets.key') }
7
+ Fattr(:global_key){ File.join(File.expand_path('~'), '.sekrets.key') }
8
+
9
+ #
10
+ def Sekrets.key_for(*args)
11
+ options = Map.options_for!(args)
12
+ path = args.shift || options[:path]
13
+
14
+ if options.has_key?(:key)
15
+ key = options[:key]
16
+ return(key)
17
+ end
18
+
19
+ path = path_for(path)
20
+
21
+ if path
22
+ keyfiles =
23
+ Coerce.list_of_strings(
24
+ [:keyfile, :keyfiles].map{|k| options[k]},
25
+ %W[ #{ path }.key #{ path }.k ]
26
+ )
27
+
28
+ keyfiles.each do |file|
29
+ if test(?s, file)
30
+ key = IO.binread(file).strip
31
+ return(key)
32
+ end
33
+ end
34
+ end
35
+
36
+ if Sekrets.project_key and test(?s, Sekrets.project_key)
37
+ return IO.binread(Sekrets.project_key).strip
38
+ end
39
+
40
+ env_key = (options[:env] || Sekrets.env).to_s
41
+ if ENV.has_key?(env_key)
42
+ key = ENV[env_key]
43
+ return(key)
44
+ end
45
+
46
+ if Sekrets.global_key and test(?s, Sekrets.global_key)
47
+ return IO.binread(Sekrets.global_key).strip
48
+ end
49
+
50
+ unless options[:prompt] == false
51
+ if console?
52
+ key = Sekrets.ask(path)
53
+ return(key)
54
+ end
55
+ end
56
+
57
+ return nil
58
+ end
59
+
60
+ #
61
+ def Sekrets.key_for!(*args, &block)
62
+ key = Sekrets.key_for(*args, &block)
63
+ raise(ArgumentError, 'no key!') unless key
64
+ key
65
+ end
66
+
67
+ #
68
+ def Sekrets.read(*args, &block)
69
+ options = Map.options_for!(args)
70
+ path = args.shift || options[:path]
71
+ key = args.shift || Sekrets.key_for!(path, options)
72
+
73
+ return nil unless test(?s, path)
74
+
75
+ encrypted = IO.binread(path)
76
+ decrypted = Sekrets.decrypt(key, encrypted)
77
+ new(decrypted)
78
+ end
79
+
80
+ #
81
+ def Sekrets.write(*args, &block)
82
+ options = Map.options_for!(args)
83
+ path = args.shift || options[:path]
84
+ content = args.shift || options[:content]
85
+ key = args.shift || Sekrets.key_for!(path, options)
86
+
87
+ dirname, basename = File.split(File.expand_path(path))
88
+ FileUtils.mkdir_p(dirname)
89
+
90
+ encrypted = Sekrets.encrypt(key, content)
91
+
92
+ tmp = path + '.tmp'
93
+ IO.binwrite(tmp, encrypted)
94
+ FileUtils.mv(tmp, path)
95
+
96
+ encrypted
97
+ new(encrypted)
98
+ end
99
+
100
+ #
101
+ def Sekrets.settings_for(*args, &block)
102
+ decrypted = read(*args, &block)
103
+
104
+ if decrypted
105
+ expanded = ERB.new(decrypted).result(TOPLEVEL_BINDING)
106
+ object = YAML.load(expanded)
107
+ object.is_a?(Hash) ? Map.for(object) : object
108
+ end
109
+ end
110
+
111
+ #
112
+ def Sekrets.prompt_for(*words)
113
+ ["sekrets:", words, "> "].flatten.compact.join(' ')
114
+ end
115
+
116
+ #
117
+ def Sekrets.ask(question)
118
+ @highline ||= HighLine.new
119
+ @highline.ask(prompt_for(question))
120
+ end
121
+
122
+ #
123
+ def Sekrets.console?
124
+ STDIN.tty?
125
+ end
126
+
127
+ #
128
+ def Sekrets.tmpdir(&block)
129
+ dirname = File.join(Dir.tmpdir, 'sekrets', Process.ppid.to_s, Process.pid.to_s, rand.to_s)
130
+
131
+ FileUtils.mkdir_p(dirname)
132
+
133
+ cleanup = proc do
134
+ if dirname and test(?d, dirname)
135
+ FileUtils.rm_rf(dirname)
136
+ end
137
+ end
138
+
139
+ if block
140
+ begin
141
+ Dir.chdir(dirname) do
142
+ block.call(dirname)
143
+ end
144
+ ensure
145
+ cleanup.call
146
+ end
147
+ else
148
+ at_exit{ cleanup.call }
149
+ dirname
150
+ end
151
+ end
152
+
153
+ #
154
+ def Sekrets.openw(arg, &block)
155
+ opened = false
156
+ atomic_move = proc{}
157
+
158
+ io =
159
+ case
160
+ when arg.respond_to?(:read)
161
+ arg
162
+ when arg.to_s.strip == '-'
163
+ STDOUT
164
+ else
165
+ opened = true
166
+ path = File.expand_path(arg.to_s)
167
+ dirname, basename = File.split(path)
168
+ FileUtils.mkdir_p(dirname)
169
+ tmp = path + ".sekrets.tmp.#{ Process.ppid }.#{ Process.pid }"
170
+ at_exit{ FileUtils.rm_f(tmp) }
171
+ atomic_move = proc{ FileUtils.mv(tmp, path) }
172
+ open(tmp, 'wb+')
173
+ end
174
+
175
+ close =
176
+ proc do
177
+ io.close if opened
178
+ atomic_move.call
179
+ end
180
+
181
+ if block
182
+ begin
183
+ block.call(io)
184
+ ensure
185
+ close.call
186
+ end
187
+ else
188
+ at_exit{ close.call }
189
+ io
190
+ end
191
+ end
192
+
193
+ #
194
+ def Sekrets.openr(arg, &block)
195
+ opened = false
196
+
197
+ io =
198
+ case
199
+ when arg.respond_to?(:read)
200
+ arg
201
+ when arg.to_s.strip == '-'
202
+ STDIN
203
+ else
204
+ opened = true
205
+ open(arg, 'rb+')
206
+ end
207
+
208
+ close =
209
+ proc do
210
+ io.close if opened
211
+ end
212
+
213
+ if block
214
+ begin
215
+ block.call(io)
216
+ ensure
217
+ close.call
218
+ end
219
+ else
220
+ at_exit{ close.call }
221
+ io
222
+ end
223
+ end
224
+
225
+ #
226
+ def Sekrets.path_for(object)
227
+ path = nil
228
+
229
+ if object.is_a?(String) or object.is_a?(Pathname)
230
+ return(path = object.to_s)
231
+ end
232
+
233
+ [:original_path, :original_filename, :path, :filename, :pathname].each do |msg|
234
+ if object.respond_to?(msg)
235
+ path = object.send(msg)
236
+ break
237
+ end
238
+ end
239
+
240
+ path
241
+ end
242
+
243
+ #
244
+ module Blowfish
245
+ def cipher(mode, key, data)
246
+ cipher = OpenSSL::Cipher::Cipher.new('bf-cbc').send(mode)
247
+ cipher.key = Digest::SHA256.digest(key.to_s)
248
+ cipher.update(data) << cipher.final
249
+ end
250
+
251
+ def encrypt(key, data)
252
+ cipher(:encrypt, key, data)
253
+ end
254
+
255
+ def decrypt(key, text)
256
+ cipher(:decrypt, key, text)
257
+ end
258
+
259
+ def cycle(key, data)
260
+ decrypt(key, encrypt(key, data))
261
+ end
262
+
263
+ def recrypt(old_key, new_key, data)
264
+ encrypt(new_key, decrypt(old_key, data))
265
+ end
266
+
267
+ extend(self)
268
+ end
269
+
270
+ extend(Blowfish)
271
+ end
272
+
273
+
274
+ Sekret = Sekrets
275
+
276
+
277
+
278
+ BEGIN {
279
+
280
+ require 'openssl'
281
+ require 'fileutils'
282
+ require 'erb'
283
+ require 'yaml'
284
+ require 'tmpdir'
285
+
286
+ class Sekrets < ::String
287
+ Version = '0.4.2' unless defined?(Version)
288
+
289
+ class << Sekrets
290
+ def version
291
+ Sekrets::Version
292
+ end
293
+
294
+ def dependencies
295
+ {
296
+ 'highline' => [ 'highline' , ' >= 1.6.15' ] ,
297
+ 'map' => [ 'map' , ' >= 6.3.0' ] ,
298
+ 'fattr' => [ 'fattr' , ' >= 2.2.1' ] ,
299
+ 'coerce' => [ 'coerce' , ' >= 0.0.3' ] ,
300
+ 'main' => [ 'main' , ' >= 5.1.1' ] ,
301
+ }
302
+ end
303
+
304
+ def libdir(*args, &block)
305
+ @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
306
+ args.empty? ? @libdir : File.join(@libdir, *args)
307
+ ensure
308
+ if block
309
+ begin
310
+ $LOAD_PATH.unshift(@libdir)
311
+ block.call()
312
+ ensure
313
+ $LOAD_PATH.shift()
314
+ end
315
+ end
316
+ end
317
+
318
+ def load(*libs)
319
+ libs = libs.join(' ').scan(/[^\s+]+/)
320
+ Sekrets.libdir{ libs.each{|lib| Kernel.load(lib) } }
321
+ end
322
+ end
323
+ end
324
+
325
+ begin
326
+ require 'rubygems'
327
+ rescue LoadError
328
+ nil
329
+ end
330
+
331
+ Sekrets.dependencies.each do |lib, dependency|
332
+ gem(*dependency) if defined?(gem)
333
+ require(lib)
334
+ end
335
+
336
+ Sekrets.fattr(:description){
337
+ <<-__
338
+
339
+ foobar
340
+
341
+ __
342
+ }
343
+ }
data/sekrets.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ ## sekrets.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "sekrets"
6
+ spec.version = "0.4.2"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "sekrets"
9
+ spec.description = "description: sekrets kicks the ass"
10
+
11
+ spec.files =
12
+ ["README",
13
+ "Rakefile",
14
+ "bin",
15
+ "bin/sekrets",
16
+ "lib",
17
+ "lib/sekrets",
18
+ "lib/sekrets.rb",
19
+ "lib/sekrets/capistrano.rb",
20
+ "sekrets.gemspec",
21
+ "test",
22
+ "test/lib",
23
+ "test/lib/testing.rb",
24
+ "test/sekrets_test.rb"]
25
+
26
+ spec.executables = ["sekrets"]
27
+
28
+ spec.require_path = "lib"
29
+
30
+ spec.test_files = nil
31
+
32
+
33
+ spec.add_dependency(*["highline", " >= 1.6.15"])
34
+
35
+ spec.add_dependency(*["map", " >= 6.3.0"])
36
+
37
+ spec.add_dependency(*["fattr", " >= 2.2.1"])
38
+
39
+ spec.add_dependency(*["coerce", " >= 0.0.3"])
40
+
41
+ spec.add_dependency(*["main", " >= 5.1.1"])
42
+
43
+
44
+ spec.extensions.push(*[])
45
+
46
+ spec.rubyforge_project = "codeforpeople"
47
+ spec.author = "Ara T. Howard"
48
+ spec.email = "ara.t.howard@gmail.com"
49
+ spec.homepage = "https://github.com/ahoward/sekrets"
50
+ end