sekrets 0.4.2

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