#! /usr/bin/env ruby Main { # synopsis <<-__ ~> echo 'plaintext' | sekrets write sekrets.enc --key 42 ~> sekrets read sekrets.enc --key 42 ~> sekrets edit sekrets.enc __ description <<-__ TL;DR # create an encrypted config file ruby -r yaml -e'puts {:api_key => 1234}.to_yaml' | sekrets write config/settings.yml.enc --key 42 # display it sekrets read config/settings.yml.enc --key 42 # edit it sekrets edit config/settings.yml.enc --key 42 # see that it's encrypted cat config/settings.yml.enc # commit it git add config/settings.yml.enc # put the decryption key in a file echo 42 > sekrets.key # ignore this file in git echo sekrets.key >> .gitgnore # make sure this file gets deployed on your server echo " require 'sekrets/capistrano' " >> Capfile # commit and deploy git add config/settings.yml.enc git commit -am'encrypted settings yo' git pull && git push && cap staging deploy # access these settings in your application code settings = Sekrets.settings_for('./config/settings.yml.enc') GENERAL sekrets provides commandline tools and a library to manage and access encrypted files in your code base. it allows one to check encrypted infomation into a repository and to manage it alongside the rest of the code base. it elimnates the need to check in unencrypted information, keys, or other sensitive infomation. sekrets provides both a general mechanism for managing arbitrary encrypted files and a specific mechanism for managing encrypted config files. KEY LOOKUP for *all* operations, from the command line or otherwise, sekrets uses the following algorithm to search for a decryption key: - any key passed directly as a parameter to a library call will be preferred - otherwise the code looks for a companion key file. for example, given the file 'config/sekrets.yml.enc' sekrets will look for a key at config/sekrets.yml.enc.key and config/sekrets.yml.enc.k if either of these is found to be non-empty the contents of the file will be used as the decryption key for that file. you should *never* commit these key files and also add them to your .gitignore - or similar. - next a project key file is looked for. the path of this file is ./sekrets.key normally and, in a rails' application RAILS_ROOT/sekrets.key - if that is not found sekrets looks for the key in the environment under the env var SEKRETS_KEY the env var used is configurable in the library - next the global key file is search for, the path of this file is ~/.sekrets.key - finally, if no key has yet been specified or found, the user is prompted to input the key. prompt only occurs if the user us attached to a tty. so, for example, no prompt will hang and application being started in the background such as a rails' application being managed by passenger. see Sekrets.key_for for more details KEY DISTRIBUTION sekrets does *not* attempt to solve the key distribution problem for you, with one exception: if you are using capistrano to do a 'vanilla' ssh based deploy a simple recipe is provided which will detect a local keyfile and scp it onto the remote server(s) on deploy. sekrets assumes that the local keyfile, if it exists, is correct. in plain english the capistrano recipe does: scp ./sekrets.key deploy@remote.host.com:/rails_root/current/sekrets.key it goes without saying that the local keyfile should *never* be checked in and also should be in .gitignore distribution of this key among developers is outside the scope of the library. likely unencrypted email is the best mechanism for distribution ;-/ __ # def run help! end # mode(:write){ synopsis <<-__ write encrypted data to file __ description <<-__ write mode writes it's input (default STDIN) to output (deafult STDOUT) after encrypting it with key __ examples <<-__ ~> echo 'the pazzw0rd' | sekrets write -k 42 > sekrets.enc ~> sekrets write output.enc input.txt -k 42 __ argument('output', 'o'){ argument :required default STDOUT } argument('input', 'i'){ argument :required default STDIN } option('key', 'k'){ argument :required } def run Sekrets.openw(params[:output].value) do |output| key = key_for(output) Sekrets.openr(params[:input].value) do |input| decrypted = input.read encrypted = Sekrets.encrypt(key, decrypted) output.write(encrypted) end end end } # mode(:read){ synopsis <<-__ reads encrypted data from a file __ description <<-__ read mode reads it's input (default STDIN) and writes it's output (default STDOUT) after decrypting it with key __ examples <<-__ ~> cat output.enc | sekrets read -k 42 ~> sekrets read output.enc output.dec -k 42 __ argument('input', 'i'){ argument :required default STDIN } argument('output', 'o'){ argument :required default STDOUT } option('key', 'k'){ argument :required } def run Sekrets.openr(params[:input].value) do |input| key = key_for(input) Sekrets.openw(params[:output].value) do |output| encrypted = input.read decrypted = Sekrets.decrypt(key, encrypted) output.write(decrypted) end end end } # mode(:recrypt){ synopsis <<-__ re-encrypts a file with a new key __ description <<-__ provide the old and new key by using the --key option twice the first key is the current decryption key the second key is the new one if you do not provide the keys as options you will be prompted for them __ examples <<-__ ~> sekrets recrypt sekrets.enc -k current_key -k new_key __ argument(:path) option('key', 'k'){ argument :required } def run path = params[:path].value cur_key, new_key, *ignored = params[:key].values.first(2) unless cur_key cur_key = Sekrets.ask("#{ path } [cur key]") end unless new_key new_key = Sekrets.ask("#{ path } [new key]") end decrypted = Sekrets.read(path, :key => cur_key) encrypted = Sekrets.write(path, decrypted, :key => new_key) decrypted = Sekrets.read(path, :key => new_key) puts path end } # mode(:edit){ synopsis <<-__ spawn an external editor to edit an encrypted file __ description <<-__ edit mode looks first to the unix environment variable SEKRETS_EDTIOR or, if that is not found, the normal EDITOR variable to determine which editor to launch. it assumes that your editor can be launched from the commandline with a file as single argument. vim is the default editor. after the editor program exits succesfully the temporary file is encrypted and saved. __ examples <<-__ ~> sekrets edit my.passwords.txt -k ~> EDITOR=mate sekrets edit my.passwords.txt -k __ argument(:path) option('key', 'k'){ argument :required } def run path = params[:path].value path = File.expand_path(path) key = key_for(path) decrypted = if test(?s, path) Sekrets.read(path, :key => key) else '' end basename = File.basename(path) encrypted = nil Sekrets.tmpdir do IO.binwrite(basename, decrypted) command = "#{ Sekrets.editor } #{ basename }" system(command) if $?.exitstatus == 0 content = IO.binread(basename) Sekrets.write(path, content, :key => key) end end end } # def key_for(arg) options = {} if params[:key].given? options[:key] = params[:key].value end key = Sekrets.key_for!(arg, options) end } BEGIN { require 'pathname' bindir = Pathname.new(__FILE__).dirname.to_s root = File.dirname(bindir) libdir = File.join(root, 'lib') require "#{ libdir }/sekrets.rb" begin require 'pry' rescue Object nil end trap('SIGINT'){ STDERR.puts; exit!(42) } if((sekrets_argv = ENV['SEKRETS_ARGV'])) ENV.delete('SEKRETS_ARGV') command = [$0, sekrets_argv, *ARGV].flatten.compact.join(' ') exec(command) end }