with_cred 0.0.1 → 0.0.3

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/.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: