secret_service 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # SecretService
2
2
 
3
- SecretService allows you to store random secrets in your app (for example the session secret or other shared secrets) more securely.
3
+ SecretService allows you to store secrets (for example the session secret or other shared secrets) more securely.
4
4
 
5
- It does this by distributing the actual secret between your code and your database. That is, the final secret can only be calculated if you know both the secret given in your code and a secret stored in your database. The database part is generated randomly on first use.
5
+ It does this by distributing the actual secret between your code and your database. That is, the final secret can only be calculated if you know both the secret given in your code and a secret stored in your database.
6
6
 
7
- As a useful sideeffect this means your different environments (staging / production) will automatically use different secrets.
7
+ Secrets can either be generated randomly on first use, or set using the Raketask.
8
+
9
+ The Gem does its job by using the secret given in your code to encrypt/decrypt the secret in the database.
8
10
 
11
+ As a useful sideeffect this means your different environments (staging / production) will automatically use different secrets.
9
12
 
10
- It only works for *random* secrets though, you cannot use it to store access tokens or the like.
11
13
 
12
14
 
13
15
  ## Caveat
@@ -30,8 +32,29 @@ To get a random secret, simply use
30
32
 
31
33
  SecretService.secret("dfa24decafdb058448ac1eadb94e2066381cb92ee301e5a43d556555b61c7ea599e06be870e1d90c655c1b56cea172622d2b04a5e986faed42cbae684c5523c9")
32
34
 
35
+ You will probably want to use this in your `config/initializers/secret_token.rb` initializer.
36
+
33
37
  The database entries (and indeed tables) are created on demand.
34
38
 
39
+
40
+ ## Rake tasks
41
+
42
+ If you use Rails 2.x, you need to put the following line into your `Rakefile`:
43
+
44
+ require 'secret_service/rake_tasks'
45
+
46
+ If you want to use a specific secret, you can put it into the database by calling
47
+
48
+ rake secret_service:store
49
+
50
+ The secret will be read from STDIN.
51
+
52
+ To show a previously stored secret, use
53
+
54
+ rake secret_service:show SOURCE_SECRET=the_source_secret
55
+
56
+ where `the_source_secret` is the secret used in the `SecretService.secret(...)` call
57
+
35
58
  ## Contributing
36
59
 
37
60
  1. Fork it
data/Rakefile CHANGED
@@ -32,7 +32,7 @@ namespace :all do
32
32
  desc "Run specs on all spec apps"
33
33
  task :spec do
34
34
  success = true
35
- for_each_directory_of('spec/**/Rakefile') do |directory|
35
+ for_each_directory_of('spec/*/Rakefile') do |directory|
36
36
  env = "SPEC=../../#{ENV['SPEC']} " if ENV['SPEC']
37
37
  success &= system("cd #{directory} && BUNDLE_GEMFILE=./Gemfile #{env} bundle exec rake spec")
38
38
  end
@@ -9,6 +9,8 @@ module SecretService
9
9
  else
10
10
  set_table_name TABLE_NAME
11
11
  end
12
+
13
+ validates_presence_of :key, :value
12
14
  end
13
15
 
14
16
 
@@ -29,6 +31,12 @@ module SecretService
29
31
  secret_record.value
30
32
  end
31
33
 
34
+ def update(key, value)
35
+ secret_record = find_if_present(key) || Secret.new(:key => key)
36
+ secret_record.value = value
37
+ secret_record.save!
38
+ end
39
+
32
40
  def drop_database
33
41
  # tests need this
34
42
  Secret.connection.drop_table TABLE_NAME
@@ -42,7 +50,7 @@ module SecretService
42
50
  def setup_database
43
51
  Secret.connection.create_table TABLE_NAME do |table|
44
52
  table.string :key
45
- table.string :value
53
+ table.text :value
46
54
  table.timestamps
47
55
  end
48
56
  Secret.connection.add_index TABLE_NAME, :key, :unique => true
@@ -0,0 +1,34 @@
1
+ namespace :secret_service do
2
+ desc 'Store a desired secret in the database'
3
+ task :store => :environment do
4
+ store = SecretService::Store.new
5
+ source_secret = ENV['SOURCE_SECRET'] || store.generate_secret
6
+
7
+ puts "Enter secret:"
8
+ final_secret = STDIN.gets.chomp
9
+
10
+ store.set(source_secret, final_secret)
11
+
12
+ puts "We're done!"
13
+ puts
14
+ puts "Retrieve this secret in your app using"
15
+ puts " SecretService.secret(#{source_secret.inspect})"
16
+ puts
17
+ end
18
+
19
+ desc 'Show a previously stored secret'
20
+ task :show => :environment do
21
+ store = SecretService::Store.new
22
+ puts "Enter source secret (as given in your source code):"
23
+ source_secret = STDIN.gets.chomp
24
+
25
+ secret = store.get(source_secret, :only_existing => true)
26
+ if secret
27
+ puts "Secret: ", secret
28
+ else
29
+ puts "Secret not stored"
30
+ end
31
+ puts
32
+ end
33
+
34
+ end
@@ -6,30 +6,47 @@ require 'gibberish'
6
6
 
7
7
  module SecretService
8
8
  class Store
9
- include Singleton
10
9
 
11
- def get(source_secret)
12
- hash("#{source_secret}--#{database_secret(source_secret)}")
10
+ def get(source_secret, options = {})
11
+ decrypt(database_secret(source_secret, options), source_secret)
13
12
  end
14
13
 
14
+ def set(source_secret, final_secret)
15
+ database_store.update(database_key(source_secret), encrypt(final_secret, source_secret))
16
+ end
17
+
18
+ def generate_secret
19
+ SecureRandom.hex(32)
20
+ end
21
+
22
+
15
23
  private
16
24
 
17
25
  def database_store
18
26
  @database_store ||= DatabaseStore.get
19
27
  end
20
28
 
21
- def database_secret(source_secret)
22
- secret = database_store.find(hash(source_secret)) do
23
- generate_secret
29
+ def database_secret(source_secret, options = {})
30
+ secret = database_store.find(database_key(source_secret)) do
31
+ return nil if options[:only_existing]
32
+ encrypt(generate_secret, source_secret)
24
33
  end
25
34
  end
26
35
 
27
- def hash(value)
28
- Gibberish::SHA256(value)
36
+ def database_key(source_secret)
37
+ Gibberish::SHA256(source_secret)
29
38
  end
30
39
 
31
- def generate_secret
32
- SecureRandom.hex(32)
40
+ def encrypt(plain_text, key)
41
+ aes(key).encrypt(plain_text) if plain_text
42
+ end
43
+
44
+ def decrypt(cipher_text, key)
45
+ aes(key).decrypt(cipher_text) if cipher_text
46
+ end
47
+
48
+ def aes(key)
49
+ Gibberish::AES.new(key)
33
50
  end
34
51
  end
35
52
  end
@@ -1,3 +1,3 @@
1
1
  module SecretService
2
- VERSION = "0.0.1" unless defined?(SecretService::VERSION)
2
+ VERSION = "0.1.0" unless defined?(SecretService::VERSION)
3
3
  end
@@ -2,12 +2,33 @@ require "secret_service/version"
2
2
  require "secret_service/store"
3
3
 
4
4
  module SecretService
5
- def self.secret(source_secret, options = {})
6
- if options[:plain]
7
- source_secret
8
- else
5
+
6
+ class << self
7
+
8
+ def secret(source_secret)
9
9
  @secrets ||= {}
10
- @secrets[source_secret] ||= Store.instance.get(source_secret)
10
+ @secrets[source_secret] ||= Store.new.get(source_secret)
11
+ end
12
+
13
+ private
14
+
15
+ def store
16
+ @store ||= Store.new
17
+ end
18
+
19
+ def reset
20
+ @secrets = nil
21
+ @store = nil
22
+ end
23
+
24
+ end
25
+
26
+ if defined?(Rails::Railtie)
27
+ class RakeTaskLoader < Rails::Railtie
28
+ rake_tasks do
29
+ require 'secret_service/rake_tasks'
30
+ end
11
31
  end
12
32
  end
33
+
13
34
  end
@@ -6,5 +6,6 @@ gem 'rspec-rails', '~>1.3'
6
6
  gem 'mysql2', '<0.3'
7
7
  gem 'ruby-debug', :platforms => :mri_18
8
8
  gem 'test-unit', '~>1.2', :platforms => :ruby_19
9
+ gem 'rdoc', :require => false
9
10
 
10
11
  gem 'secret_service', :path => '../..'
@@ -0,0 +1,10 @@
1
+ require(File.join(File.dirname(__FILE__), 'config', 'boot'))
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ require 'tasks/rails'
7
+
8
+ require 'secret_service/rake_tasks'
9
+
10
+ load File.join(File.dirname(__FILE__), '..', '..', 'shared', 'support', 'reconnect_task.rake')
@@ -15,6 +15,6 @@ Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].e
15
15
 
16
16
 
17
17
  Spec::Runner.configure do |config|
18
- config.use_transactional_fixtures = true
18
+ config.use_transactional_fixtures = false
19
19
  config.use_instantiated_fixtures = false
20
20
  end
@@ -0,0 +1,5 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+
3
+ SpecApp::Application.load_tasks
4
+
5
+ load File.join(File.dirname(__FILE__), '..', '..', 'shared', 'support', 'reconnect_task.rake')
@@ -13,6 +13,6 @@ require 'rspec/rails'
13
13
  Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
14
14
 
15
15
  RSpec.configure do |config|
16
- config.use_transactional_fixtures = true
16
+ config.use_transactional_fixtures = false
17
17
  config.use_instantiated_fixtures = false
18
18
  end
@@ -0,0 +1,5 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+
3
+ SpecApp::Application.load_tasks
4
+
5
+ load File.join(File.dirname(__FILE__), '..', '..', 'shared', 'support', 'reconnect_task.rake')
@@ -7,11 +7,10 @@ ENV['RAILS_ROOT'] = 'app_root'
7
7
  require "#{File.dirname(__FILE__)}/../app_root/config/environment"
8
8
  require 'rspec/rails'
9
9
 
10
-
11
10
  # Requires supporting files with custom matchers and macros, etc in ./support/ and its subdirectories.
12
11
  Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
13
12
 
14
13
  RSpec.configure do |config|
15
- config.use_transactional_fixtures = true
14
+ config.use_transactional_fixtures = false
16
15
  config.use_instantiated_fixtures = false
17
16
  end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'Rake tasks' do
5
+
6
+ def execute_rake(task, options = {})
7
+ env = "BUNDLE_GEMFILE=#{File.expand_path(File.join(Rails.root, '..', 'Gemfile'))}"
8
+ options.fetch(:env, {}).each do |key, value|
9
+ env << " #{key}=#{value}"
10
+ end
11
+ # this is the only way I could make it work in ruby 1.8 and 1.9
12
+ Open3.popen3("bash -c 'cd #{Rails.root}; #{env} bundle exec rake #{task}'") do |stdin, stdout, stderr, wait_thr|
13
+ if options[:puts]
14
+ stdin.puts options[:puts]
15
+ end
16
+ stdin.close
17
+ error = stderr.read
18
+ STDERR.puts error if error.present?
19
+ stdout.read
20
+ end
21
+ ensure
22
+ ActiveRecord::Base.connection.reconnect!
23
+ end
24
+
25
+
26
+ describe 'store' do
27
+
28
+ it 'should store the prompted secret under the given key' do
29
+ output = execute_rake('secret_service:store', :puts => 'the_secret')
30
+ (output =~ /SecretService\.secret\("(.*)"\)/).should be_true
31
+ SecretService.secret($1).should == 'the_secret'
32
+ end
33
+
34
+ it 'should allow to set the source secret' do
35
+ output = execute_rake('secret_service:store', :env => {'SOURCE_SECRET' => 'source_secret'}, :puts => 'the_secret')
36
+ output.should =~ /SecretService\.secret\("source_secret"\)/
37
+ end
38
+
39
+ end
40
+
41
+ describe 'show' do
42
+
43
+ it 'should show a previously stored secret' do
44
+ SecretService::Store.new.set('source_secret', 'secret_secret')
45
+ output = execute_rake('secret_service:show', :puts => 'source_secret')
46
+ output.should =~ /secret_secret/
47
+ end
48
+
49
+ it 'should not store a new secret' do
50
+ output = execute_rake('secret_service:show', :puts => 'source_secret')
51
+ SecretService::Store.new.get('source_secret', :only_existing => true).should be_nil
52
+ end
53
+
54
+ it 'should tell if the secret does not exist' do
55
+ output = execute_rake('secret_service:show', :puts => 'source_secret')
56
+ output.should =~ /Secret not stored/
57
+ end
58
+ end
59
+
60
+
61
+ end
@@ -4,21 +4,23 @@ describe SecretService do
4
4
 
5
5
  describe '.secret' do
6
6
 
7
+ let(:store) { double('store') }
8
+
9
+ before do
10
+ SecretService::Store.stub :new => store
11
+ end
12
+
7
13
  it 'should delegate to the store' do
8
- SecretService::Store.instance.should_receive(:get).with('source secret').and_return('final secret')
14
+ store.should_receive(:get).with('source secret').and_return('final secret')
9
15
  SecretService.secret('source secret').should == 'final secret'
10
16
  end
11
17
 
12
18
  it 'should cache' do
13
- SecretService::Store.instance.should_receive(:get).exactly(:once).and_return('final secret')
19
+ store.should_receive(:get).exactly(:once).and_return('final secret')
14
20
  SecretService.secret('source secret')
15
21
  SecretService.secret('source secret')
16
22
  end
17
23
 
18
- it 'should return the source secret if :plain is true' do
19
- SecretService.secret('source secret', :plain => true).should == 'source secret'
20
- end
21
-
22
24
  end
23
25
 
24
26
  end
@@ -51,6 +51,19 @@ describe SecretService::Store do
51
51
  secret_record.value.size.should > 10
52
52
  end
53
53
 
54
+ context ':only_existing => true' do
55
+
56
+ it 'should not make a new secret' do
57
+ store.get('foobar', :only_existing => true).should be_nil
58
+ end
59
+
60
+ it 'should retrieve an existing secret' do
61
+ secret = store.get('foobar')
62
+ store.get('foobar', :existing => true).should == secret
63
+ end
64
+
65
+ end
66
+
54
67
  context 'concurrency' do
55
68
 
56
69
  def safe_fork
@@ -86,4 +99,26 @@ describe SecretService::Store do
86
99
 
87
100
  end
88
101
 
102
+ describe '#set' do
103
+
104
+ it 'should set the final secret to a given value' do
105
+ source_secret = 'a6df546'
106
+ store.set(source_secret, 'final secret')
107
+ store.get(source_secret).should == 'final secret'
108
+ end
109
+
110
+ end
111
+
112
+ describe '#generate_secret' do
113
+
114
+ it 'should return a random secret' do
115
+ store.generate_secret.should_not == store.generate_secret
116
+ end
117
+
118
+ it 'should return a long secret' do
119
+ store.generate_secret.size.should > 20
120
+ end
121
+
122
+ end
123
+
89
124
  end
@@ -0,0 +1,9 @@
1
+ namespace :secret_service do
2
+ namespace :test do
3
+ desc 'Reconnect AR'
4
+ task :reconnect => :environment do
5
+ # necessary on ruby 1.8, the specs run our rake tasks using fork, which is not safe for AR
6
+ ActiveRecord::Base.reconnect!
7
+ end
8
+ end
9
+ end
@@ -1,6 +1,6 @@
1
1
  module SecretServiceSpec
2
2
  def self.wipe_store
3
- SecretService.instance_variable_set(:@secrets, nil)
3
+ SecretService.send(:reset)
4
4
  SecretService::DatabaseStore.get.drop_database
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,40 +1,47 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: secret_service
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Tobias Kraze
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-03-01 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2013-03-08 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
15
22
  name: gibberish
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
- description: Secret service provides encryption of your application secrets with a
31
- server side master password
32
- email:
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Secret service provides encryption of your application secrets with a server side master password
36
+ email:
33
37
  - tobias@kraze.eu
34
38
  executables: []
39
+
35
40
  extensions: []
41
+
36
42
  extra_rdoc_files: []
37
- files:
43
+
44
+ files:
38
45
  - .gitignore
39
46
  - .travis.yml
40
47
  - Gemfile
@@ -44,11 +51,13 @@ files:
44
51
  - lib/secret_service.rb
45
52
  - lib/secret_service/database_store.rb
46
53
  - lib/secret_service/database_store/active_record_store.rb
54
+ - lib/secret_service/rake_tasks.rb
47
55
  - lib/secret_service/store.rb
48
56
  - lib/secret_service/version.rb
49
57
  - secret_service.gemspec
50
58
  - spec/rails-2.3/Gemfile
51
59
  - spec/rails-2.3/Rakefile
60
+ - spec/rails-2.3/app_root/Rakefile
52
61
  - spec/rails-2.3/app_root/config/boot.rb
53
62
  - spec/rails-2.3/app_root/config/database.yml
54
63
  - spec/rails-2.3/app_root/config/environment.rb
@@ -64,6 +73,7 @@ files:
64
73
  - spec/rails-3.0/Gemfile
65
74
  - spec/rails-3.0/Rakefile
66
75
  - spec/rails-3.0/app_root/.gitignore
76
+ - spec/rails-3.0/app_root/Rakefile
67
77
  - spec/rails-3.0/app_root/config/application.rb
68
78
  - spec/rails-3.0/app_root/config/boot.rb
69
79
  - spec/rails-3.0/app_root/config/database.yml
@@ -84,6 +94,7 @@ files:
84
94
  - spec/rails-3.2/Gemfile
85
95
  - spec/rails-3.2/Rakefile
86
96
  - spec/rails-3.2/app_root/.gitignore
97
+ - spec/rails-3.2/app_root/Rakefile
87
98
  - spec/rails-3.2/app_root/config/application.rb
88
99
  - spec/rails-3.2/app_root/config/boot.rb
89
100
  - spec/rails-3.2/app_root/config/database.yml
@@ -101,37 +112,49 @@ files:
101
112
  - spec/shared/app_root/app/controllers/application_controller.rb
102
113
  - spec/shared/app_root/config/database.yml.sample
103
114
  - spec/shared/app_root/db/consul_test.db
115
+ - spec/shared/secret_service/rake_tasks_spec.rb
104
116
  - spec/shared/secret_service/secret_service_spec.rb
105
117
  - spec/shared/secret_service/store_spec.rb
118
+ - spec/shared/support/reconnect_task.rake
106
119
  - spec/shared/support/wipe_store.rb
107
- homepage: ''
120
+ has_rdoc: true
121
+ homepage: ""
108
122
  licenses: []
123
+
109
124
  post_install_message:
110
125
  rdoc_options: []
111
- require_paths:
126
+
127
+ require_paths:
112
128
  - lib
113
- required_ruby_version: !ruby/object:Gem::Requirement
129
+ required_ruby_version: !ruby/object:Gem::Requirement
114
130
  none: false
115
- requirements:
116
- - - ! '>='
117
- - !ruby/object:Gem::Version
118
- version: '0'
119
- required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
139
  none: false
121
- requirements:
122
- - - ! '>='
123
- - !ruby/object:Gem::Version
124
- version: '0'
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ hash: 3
144
+ segments:
145
+ - 0
146
+ version: "0"
125
147
  requirements: []
148
+
126
149
  rubyforge_project:
127
- rubygems_version: 1.8.24
150
+ rubygems_version: 1.3.9.5
128
151
  signing_key:
129
152
  specification_version: 3
130
- summary: Secret service provides encryption of your application secrets with a server
131
- side master password
132
- test_files:
153
+ summary: Secret service provides encryption of your application secrets with a server side master password
154
+ test_files:
133
155
  - spec/rails-2.3/Gemfile
134
156
  - spec/rails-2.3/Rakefile
157
+ - spec/rails-2.3/app_root/Rakefile
135
158
  - spec/rails-2.3/app_root/config/boot.rb
136
159
  - spec/rails-2.3/app_root/config/database.yml
137
160
  - spec/rails-2.3/app_root/config/environment.rb
@@ -147,6 +170,7 @@ test_files:
147
170
  - spec/rails-3.0/Gemfile
148
171
  - spec/rails-3.0/Rakefile
149
172
  - spec/rails-3.0/app_root/.gitignore
173
+ - spec/rails-3.0/app_root/Rakefile
150
174
  - spec/rails-3.0/app_root/config/application.rb
151
175
  - spec/rails-3.0/app_root/config/boot.rb
152
176
  - spec/rails-3.0/app_root/config/database.yml
@@ -167,6 +191,7 @@ test_files:
167
191
  - spec/rails-3.2/Gemfile
168
192
  - spec/rails-3.2/Rakefile
169
193
  - spec/rails-3.2/app_root/.gitignore
194
+ - spec/rails-3.2/app_root/Rakefile
170
195
  - spec/rails-3.2/app_root/config/application.rb
171
196
  - spec/rails-3.2/app_root/config/boot.rb
172
197
  - spec/rails-3.2/app_root/config/database.yml
@@ -184,6 +209,8 @@ test_files:
184
209
  - spec/shared/app_root/app/controllers/application_controller.rb
185
210
  - spec/shared/app_root/config/database.yml.sample
186
211
  - spec/shared/app_root/db/consul_test.db
212
+ - spec/shared/secret_service/rake_tasks_spec.rb
187
213
  - spec/shared/secret_service/secret_service_spec.rb
188
214
  - spec/shared/secret_service/store_spec.rb
215
+ - spec/shared/support/reconnect_task.rake
189
216
  - spec/shared/support/wipe_store.rb