with_cred 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ log/
1
2
  *.gem
2
3
  *.rbc
3
4
  .bundle
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in with_cred.gemspec
4
4
  gemspec
5
+ gem 'rspec'
6
+ eval File.read(File.expand_path('../spec/dummy/Gemfile', __FILE__))
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
1
  # WithCred
2
2
 
3
- Put your credentials in a standard convenient place. This place is an
4
- encrypted tarball, which should be decrypted on deploy. If you like, you
5
- can commit them into your CVS, but this is not recommended
3
+ Put your credentials in a standard convenient place. This place is
4
+ 1) An encrypted tarball, which could decrypt on deploy. If you like, you
5
+ can commit them into your SCM/VCS, but this is not recommended.
6
+ 2) A series of unencrypted YAML files. Like above, but you can't commit
7
+ them into your SCM/VCS
8
+ 3) An encrypted environment variable and a password. This gem helps you
9
+ encrypt your credentials on deployment with capistrano and run commands
10
+ with the credentials as environment variables. The credentials may never
11
+ be persistently stored.
6
12
 
7
13
  ## Installation
8
14
 
@@ -22,7 +28,7 @@ Or install it yourself as:
22
28
 
23
29
  (1) Add the directory credentials/ and credentials.* to your .gitignore
24
30
  (2) Put your facebook credentials as follows in
25
- credentials/facebook.yaml
31
+ credentials/production/facebook.yaml
26
32
  api_token: "DEADBEEF543254738o25y437"
27
33
  api_secret: "FEEBDAED3215432543523452"
28
34
  (3) Add `config.credentials_mode = "production"` to your config/environments/production.rb
@@ -31,21 +37,39 @@ api_secret: "FEEBDAED3215432543523452"
31
37
  WithCred.entials(:facebook) do |credentials|
32
38
  #Set up the facebook API stuff
33
39
  end
40
+
41
+ The credentials_mode is independent from Rails.env. If you want your
42
+ credentials to be available to all credentials_mode s, then you should
43
+ put your yaml in credentials/
34
44
  ```
35
45
 
36
46
  Tasks
37
47
  ```
38
48
  # Decrypt, asking for password
49
+ ```
39
50
  rake credentials:decrypt
40
51
 
41
52
  # Decrypt, Looking in the file /etc/yourapp/.secret for the password
53
+ ```
42
54
  rake credentials:decrypt[/etc/yourapp/.secret]
43
55
 
44
56
  # Encrypt, asking for the password
57
+ ```
45
58
  rake credentials:encrypt
46
59
 
47
60
  # Encrypt, Looking in the file /etc/yourapp/.secret for the password
48
- rake credentials:encrypt[/etc/yourapp/.secret]
61
+ ```
62
+ rake [[credentials:encrypt[/etc/yourapp/.secret]]]
63
+
64
+ # Output a lockfile, for credentials compatability checking. Use the
65
+ lockfile to check compatability of credentials with the app. Check
66
+ this file into your VCS/SCM.
67
+ ```
68
+ rake credentials:lock
69
+
70
+ # Check that the credentials match what we expect from the lockfile.
71
+ ```
72
+ rake credentials:check
49
73
 
50
74
  ## Contributing
51
75
 
@@ -0,0 +1,43 @@
1
+ # Can't figure out how to require this file only for capistrano tasks.
2
+ # It's not required by default, so feel free to copypasta
3
+
4
+ Capistrano::Configuration.instance(:must_exist).load do
5
+
6
+ def with_credentials_run(*args)
7
+ pwd = ENV['PASSWORD']
8
+ cred = WithCred.encrypted
9
+
10
+ if pwd
11
+ args[0] = "ENCRYPTED_CREDENTIALS='#{cred}' ; #{args[0]}"
12
+ end
13
+
14
+ if cred
15
+ args[0] = "PASSWORD='#{pwd}' ; #{args[0]}"
16
+ end
17
+
18
+ run(*args)
19
+ end
20
+
21
+
22
+ namespace :credentials do
23
+ desc "ask for credentials password"
24
+ task :password_prompt do
25
+ ENV['PASSWORD'] = Capistrano::CLI.password_prompt("Please enter password to decrypt credentials.")
26
+ end
27
+
28
+ desc "check"
29
+ task :check do
30
+ credentials.password_prompt
31
+ with_credentials_run "bundle exec rake credentials:check"
32
+ end
33
+
34
+ desc "lock"
35
+ task :lock do
36
+ credentials.password_prompt
37
+ WithCred.lock
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
@@ -6,6 +6,10 @@ module WithCred
6
6
  rake_tasks do
7
7
  Dir[File.expand_path('../tasks/*.rake', __FILE__)].each { |f| load f }
8
8
  end
9
+
10
+ config.before_configuration do
11
+ WithCred.configure
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -0,0 +1,10 @@
1
+ Capistrano::Configuration.instance.load do
2
+
3
+ namespace :password do
4
+ desc "ask for credentials password"
5
+ task :prompt do
6
+ WithCred::Deployment.password = Capistrano::CLI.password_prompt("Please enter password to decrypt credentials.")
7
+ end
8
+ end
9
+
10
+ end
@@ -20,4 +20,22 @@ namespace :credentials do
20
20
 
21
21
  end
22
22
 
23
+ desc "Encrypt for use as an environment variable"
24
+ task :env_encrypt do |t, args|
25
+ password = ENV['PASSWORD']
26
+ raise "Please supply a password as an environment variable" unless password
27
+
28
+ puts WithCred.encrypted
29
+ end
30
+
31
+ desc "Check that the environment variable credentials are correct"
32
+ task :check => :environment do
33
+ WithCred.check!
34
+ end
35
+
36
+ desc "Output a lockfile unique to the credentials set"
37
+ task :lock => :environment do
38
+ WithCred.lock
39
+ end
40
+
23
41
  end
@@ -1,3 +1,3 @@
1
1
  module WithCred
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/with_cred.rb CHANGED
@@ -1,28 +1,189 @@
1
1
  require "with_cred/version"
2
+ require "with_cred/railtie" if defined?(::Rails)
3
+ #require "with_cred/capistrano" if defined?(::Capistrano)
4
+ require "base64"
5
+ require "encryptor"
2
6
 
3
7
  class CredentialsNotFoundError < StandardError ; end
4
8
 
5
9
  module WithCred
6
- def self.entials_for(file)
7
10
 
8
- # This method ignores the block if the credentials are required, and we are not supposed to need them.
9
- # Passes the credentials into the block as read from the credentials YAML file at "#{Rails.root}/credentials/#{file}.yaml"
11
+ class InvalidCredentialsError < StandardError ; end
10
12
 
11
- if (allowed = (Rails.application.config.credentials_mode == "production"))
13
+ class Configuration
14
+ attr_accessor :credentials_dir, :credententials_mode, :credentials_hash, :password
12
15
 
13
- credentials = nil
16
+ def initialize
17
+ @algorithm = 'aes-256-cbc'
18
+ @credentials_hash = {}
19
+ end
20
+
21
+ def add_from_encrypted(ciphertext)
22
+ if ciphertext && ciphertext.length > 0
23
+ encrypted_binary = Base64.urlsafe_decode64(ciphertext)
24
+ decrypted_yaml = Encryptor.decrypt(encrypted_binary, :key => @password, :algorithm => @algorithm)
25
+ decrypted_hash = YAML::load(decrypted_yaml)
26
+ @credentials_hash.merge!(decrypted_hash)
27
+ end
28
+ end
29
+
30
+ def encrypted
31
+ Base64.urlsafe_encode64(encrypted_binary)
32
+ end
33
+
34
+ def encrypted_binary
35
+ Encryptor.encrypt(sorted_credentials_hash.to_yaml, :key => @password, :algorithm => @algorithm)
36
+ end
37
+
38
+ def check!(fp)
39
+ check(fp) || raise(InvalidCredentialsError.new("The fingerprints do not match"))
40
+ end
41
+
42
+ def check(fp)
43
+ fingerprint == fp
44
+ end
45
+
46
+ def fingerprint
47
+ Digest::SHA256.hexdigest(Base64.urlsafe_decode64(encrypted))
48
+ end
49
+
50
+ def add_credentials(path = [], leaf = {})
51
+
52
+ key = path.pop
53
+
54
+ twig = path.inject(@credentials_hash) do |hash, key|
55
+ hash[key.to_sym] ||= {}
56
+ end
57
+
58
+ twig[key.to_sym] = leaf
59
+
60
+ end
61
+
62
+ def credentials_for_mode(mode)
63
+ our_mode_credentials = @credentials_hash[mode.to_sym] || {}
64
+ all_mode_credentials = @credentials_hash[:_all] || {}
65
+ all_mode_credentials.merge(our_mode_credentials)
66
+ end
67
+
68
+ def sorted_credentials_hash
69
+ def sorter(h)
70
+ result = {}
71
+ h.keys.sort.each do |k|
72
+ if h[k].is_a?(Hash)
73
+ result[k] = sorter(h[k])
74
+ else
75
+ result[k] = h[k]
76
+ end
77
+ end
78
+ result
79
+ end
80
+
81
+ sorter(@credentials_hash)
82
+ end
83
+ end
84
+
85
+ class ApplicationConfiguration < Configuration
86
+
87
+ attr_accessor :credentials_dir, :credentials_mode
88
+
89
+ def initialize
90
+
91
+ super
92
+ if defined?(::Rails)
93
+ @src_root = ::Rails.root
94
+ else
95
+ @src_root = ENV['PWD']
96
+ end
97
+
98
+ if defined?(::Rails) && ::Rails.application.config.respond_to?(:credentials_mode)
99
+ @credentials_mode = ::Rails.application.config.credentials_mode
100
+ else
101
+ @credentials_mode = "production"
102
+ end
103
+
104
+ @credentials_dir = File.join(@src_root, 'credentials')
105
+
106
+ self.password = ENV['PASSWORD']
107
+ self.add_from_encrypted(ENV['ENCRYPTED_CREDENTIALS'])
108
+ add_from_files
109
+
110
+ end
111
+
112
+ def add_from_files
113
+ all_credentials_files = Dir[File.join(@credentials_dir, "**", "*.yaml")]
114
+
115
+ all_credentials_files.each do |path|
116
+
117
+ hash_path =
118
+ path.gsub(/\.[^\.]*$/, '').
119
+ gsub(/^#{self.credentials_dir}/, '').
120
+ split('/').
121
+ reject{|tok| tok.length == 0}
122
+
123
+ if File.join(File.dirname(path), "") == File.join(self.credentials_dir, "")
124
+ hash_path.unshift(:_all)
125
+ end
126
+
127
+ credentials = YAML::load_file(path)
128
+
129
+ add_credentials(hash_path, credentials)
130
+ end
131
+ end
132
+
133
+ def check!(fp = nil)
134
+ super(fp || File.read(lock_file_name))
135
+ end
136
+
137
+ def lock
138
+ File.open(lock_file_name, 'w') do |f|
139
+ f.write fingerprint
140
+ end
141
+ end
142
+
143
+ def credentials_for_current_mode
144
+ credentials_for_mode(@credentials_mode)
145
+ end
146
+
147
+ private
148
+ def lock_file_name
149
+ File.join(@src_root, 'credentials.lock')
150
+ end
14
151
 
15
- begin
16
- credentials = YAML::load_file("#{Rails.root}/credentials/#{file.to_s}.yaml")
17
- rescue
18
- raise CredentialsNotFoundError
152
+ end
153
+
154
+ class << self
155
+ attr_accessor :application_config
156
+
157
+ [:encrypted, :check!, :lock].each do |m|
158
+ define_method m do |*args, &block|
159
+ self.application_config.public_send(m, *args, &block)
19
160
  end
161
+ end
162
+ end
163
+
164
+ def self.configure
165
+ self.application_config ||= ApplicationConfiguration.new
166
+ yield application_config if block_given?
167
+ end
168
+
169
+ def self.deconfigure
170
+ self.application_config = nil
171
+ end
172
+
173
+ def self.entials_for(*hash_path)
20
174
 
21
- yield credentials
175
+ hash_place = application_config.credentials_for_current_mode
176
+
177
+ # Delve
178
+ hash_path.each do |key|
179
+ key = key.to_sym
180
+ hash_place[key] ||= {}
181
+ hash_place = hash_place[key]
22
182
  end
23
183
 
24
- return allowed
184
+ yield hash_place
25
185
 
26
186
  end
187
+
27
188
  end
28
189
 
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', '3.2.13'
4
+
5
+ # Bundle edge Rails instead:
6
+ # gem 'rails', :git => 'git://github.com/rails/rails.git'
7
+
8
+ gem 'sqlite3'
9
+ gem 'capistrano'
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
3
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4
+
5
+ require File.expand_path('../config/application', __FILE__)
6
+
7
+ Dummy::Application.load_tasks
@@ -0,0 +1,16 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'rails'
4
+
5
+ if defined?(Bundler)
6
+ # If you precompile assets before deploying to production, use this line
7
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
8
+ # If you want your assets lazily compiled in production, use this line
9
+ # Bundler.require(:default, :assets, Rails.env)
10
+ end
11
+
12
+ module Dummy
13
+ class Application < Rails::Application
14
+ config.credentials_mode = "development"
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+
3
+ # Set up gems listed in the Gemfile.
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5
+
6
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
@@ -0,0 +1,5 @@
1
+ # Load the rails application
2
+ require File.expand_path('../application', __FILE__)
3
+
4
+ # Initialize the rails application
5
+ Dummy::Application.initialize!
@@ -0,0 +1,3 @@
1
+ Dummy::Application.configure do
2
+ config.active_support.deprecation = :log
3
+ end
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Dummy::Application
@@ -0,0 +1 @@
1
+ :secret: some_token
@@ -0,0 +1 @@
1
+ :password: developer
@@ -0,0 +1 @@
1
+ Connecting to database specified by database.yml
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe WithCred do
4
+ context 'from environment variables' do
5
+ let(:password) { 'secret' }
6
+ let(:credentials) {
7
+ {
8
+ :development => {
9
+ :merchant_account => {
10
+ :api_key => "secret"
11
+ }
12
+ },
13
+ :_all => {
14
+ :gateway => {
15
+ :name => 'swipe'
16
+ }
17
+ }
18
+ }.to_yaml
19
+ }
20
+ let(:encrypted_credentials) {
21
+ Base64.urlsafe_encode64(Encryptor.encrypt(credentials, :key => password, :algorithm => 'aes-256-cbc'))
22
+ }
23
+
24
+ before do
25
+ ENV['ENCRYPTED_CREDENTIALS'] = encrypted_credentials
26
+ ENV['PASSWORD'] = password
27
+
28
+ # Simulate process reload
29
+ WithCred.configure
30
+ end
31
+
32
+ after do
33
+ WithCred.deconfigure
34
+ end
35
+
36
+ it "decrypts the credentials" do
37
+ WithCred.entials_for(:merchant_account) do |c|
38
+ c[:api_key].should == "secret"
39
+ end
40
+ end
41
+
42
+ it "allows credentials for all environments" do
43
+ WithCred.entials_for(:gateway) do |c|
44
+ c[:name].should == "swipe"
45
+ end
46
+ end
47
+
48
+ after do
49
+ ENV['ENCRYPTED_CREDENTIALS'] = nil
50
+ ENV['PASSWORD'] = nil
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require File.expand_path('../dummy/config/environment.rb', __FILE__)
5
+
6
+ RSpec.configure do |config|
7
+ WithCred.deconfigure
8
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe WithCred do
5
+
6
+ context "in a rails app" do
7
+ context "config" do
8
+ let(:config) {
9
+ config = WithCred::ApplicationConfiguration.new
10
+ }
11
+ it "configures based on credentials_mode" do
12
+ config.credentials_mode.should == "development"
13
+ config.credentials_dir.should == File.join(Rails.root, "credentials")
14
+ end
15
+ end
16
+ end
17
+
18
+
19
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe WithCred do
5
+
6
+ context "in a rails app" do
7
+ before do
8
+ WithCred.configure
9
+ end
10
+
11
+ after do
12
+ WithCred.deconfigure
13
+ end
14
+
15
+ context "credentials from yaml files" do
16
+ it "loads credentials from a tree of nested files" do
17
+ WithCred.entials_for(:twitter) do |c|
18
+ c[:api][:secret].should == "some_token"
19
+ end
20
+ end
21
+ end
22
+
23
+ context "retrieving credentials" do
24
+ it "loads credentials for the correct environment" do
25
+ WithCred.entials_for(:twitter) do |c|
26
+ c.should_not be_empty
27
+ end
28
+ end
29
+
30
+ it "ignores credentials for other environments" do
31
+ Rails.application.config.stub(:credentials_mode).and_return("production")
32
+
33
+ WithCred.deconfigure
34
+ WithCred.configure
35
+
36
+ WithCred.entials_for(:twitter) do |c|
37
+ c.should be_empty
38
+ end
39
+ end
40
+
41
+ it "loads root credentials for all environments" do
42
+ WithCred.entials_for(:github) do |c|
43
+ c[:password].should == "developer"
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
data/with_cred.gemspec CHANGED
@@ -9,11 +9,13 @@ Gem::Specification.new do |gem|
9
9
  gem.authors = ["John Cant"]
10
10
  gem.email = ["a.johncant@gmail.com"]
11
11
  gem.description = %q{Simple credentials storage}
12
- gem.summary = %q{bla}
12
+ gem.summary = %q{Credentials as environment variables or as files}
13
13
  gem.homepage = "https://github.com/johncant/with_cred"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'encryptor'
19
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: with_cred
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-03 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2013-05-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: encryptor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
14
30
  description: Simple credentials storage
15
31
  email:
16
32
  - a.johncant@gmail.com
@@ -24,9 +40,25 @@ files:
24
40
  - README.md
25
41
  - Rakefile
26
42
  - lib/with_cred.rb
43
+ - lib/with_cred/capistrano.rb
27
44
  - lib/with_cred/railtie.rb
45
+ - lib/with_cred/recipes.rb
28
46
  - lib/with_cred/tasks/credentials.rake
29
47
  - lib/with_cred/version.rb
48
+ - spec/dummy/Gemfile
49
+ - spec/dummy/Rakefile
50
+ - spec/dummy/config.ru
51
+ - spec/dummy/config/application.rb
52
+ - spec/dummy/config/boot.rb
53
+ - spec/dummy/config/environment.rb
54
+ - spec/dummy/config/environments/development.rb
55
+ - spec/dummy/credentials/development/twitter/api.yaml
56
+ - spec/dummy/credentials/github.yaml
57
+ - spec/dummy/log/development.log
58
+ - spec/env_credentials_spec.rb
59
+ - spec/spec_helper.rb
60
+ - spec/with_cred_spec.rb
61
+ - spec/yaml_credentials_spec.rb
30
62
  - with_cred.gemspec
31
63
  homepage: https://github.com/johncant/with_cred
32
64
  licenses: []
@@ -51,6 +83,20 @@ rubyforge_project:
51
83
  rubygems_version: 1.8.24
52
84
  signing_key:
53
85
  specification_version: 3
54
- summary: bla
55
- test_files: []
86
+ summary: Credentials as environment variables or as files
87
+ test_files:
88
+ - spec/dummy/Gemfile
89
+ - spec/dummy/Rakefile
90
+ - spec/dummy/config.ru
91
+ - spec/dummy/config/application.rb
92
+ - spec/dummy/config/boot.rb
93
+ - spec/dummy/config/environment.rb
94
+ - spec/dummy/config/environments/development.rb
95
+ - spec/dummy/credentials/development/twitter/api.yaml
96
+ - spec/dummy/credentials/github.yaml
97
+ - spec/dummy/log/development.log
98
+ - spec/env_credentials_spec.rb
99
+ - spec/spec_helper.rb
100
+ - spec/with_cred_spec.rb
101
+ - spec/yaml_credentials_spec.rb
56
102
  has_rdoc: