secret_store 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in secret_store.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ SecretStore
2
+ ===========
3
+
4
+ Store secrets for your app in a encrypted in a yaml file.
5
+
6
+ Often when working on a web application, you have the need for storing a
7
+ variety of API keys or secrets to 3rd party services. However, it's not
8
+ desireable to check those secrets in as plain text to the code respository.
9
+
10
+ One common way around this is to set such keys in the environment. This is,
11
+ for example, what [Heroku recommends](https://devcenter.heroku.com/articles/config-vars)
12
+ for use on their platform. While it works, it can end up cumbersome, is not easily
13
+ replicatable, and can require additional restarts in addition to a code
14
+ deploy depending on the architecture.
15
+
16
+ Instead, SecretStore encrypts all secrets with one master password, so there's only
17
+ one key to inject into the environment.
18
+
19
+ Installation
20
+ ------------
21
+
22
+ install it via rubygems:
23
+
24
+ ```
25
+ gem install secret_store
26
+ ```
27
+
28
+ or put it in your Gemfile:
29
+
30
+ ```ruby
31
+ # Gemfile
32
+ gem 'secret_store'
33
+ ```
34
+
35
+ Usage
36
+ -----
37
+
38
+ ```ruby
39
+ require "secret_store"
40
+
41
+ secret_store = SecretStore.new("master_password", "/path/to/data.yml")
42
+ secret_store.store("some_api_key", "c7dd199")
43
+ secret_store.get("some_api_key") # => "c7dd199"
44
+ secret_store.get("unknown_key") # => raises IndexError
45
+
46
+ secret_store.store("known_key", "b123fa") => stores
47
+ secret_store.store("known_key", "new_val") => raises error
48
+ secret_store.store("known_key", "new_val", :force) => overwrites stored
49
+ ```
50
+
51
+ For a typical application, it could be desirable to define a
52
+ single SecretStore instance; for example, in a rails initializer.
53
+
54
+ How
55
+ ---
56
+
57
+ SecretStore uses [Gibberish](https://github.com/mdp/gibberish/) under the
58
+ hood to AES encrypt secrets in a YAML file. Gibbersh currently has only
59
+ stdlib and core ruby dependencies, so it makes this code easier to read
60
+ without any extra requirements.
61
+
62
+ Caveats
63
+ -------
64
+
65
+ SecretStore code is not recommended for high security (ex: PCI compliance required)
66
+ use. For your Todos webapp, it's fine!
67
+
68
+ Credits
69
+ -------
70
+
71
+ This was inspired by the [passw3rd](https://github.com/oreoshake/passw3rd) gem.
72
+
73
+ #### Copyright
74
+
75
+ Copyright (c) (2012) Brendon Murphy. See license.txt for details.
76
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
@@ -0,0 +1,3 @@
1
+ class SecretStore
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,80 @@
1
+ require "gibberish"
2
+ require "yaml"
3
+ require "secret_store/version"
4
+
5
+ class SecretStore
6
+ attr_reader :file_path
7
+
8
+ def initialize(password, file_path)
9
+ self.password = password
10
+ self.file_path = file_path
11
+ end
12
+
13
+ def store(key, secret, force = false)
14
+ load_data
15
+
16
+ if ! force && @data[key.to_s]
17
+ raise "Key #{key} already stored"
18
+ end
19
+
20
+ @data.merge!(key.to_s => encrypt(secret))
21
+ store_data
22
+ load_data[key]
23
+ end
24
+
25
+ def get(key)
26
+ ciphertext = @data.fetch(key.to_s)
27
+ cipher.decrypt(ciphertext)
28
+ end
29
+
30
+ def encrypt(secret)
31
+ cipher.encrypt(secret).chomp
32
+ end
33
+
34
+ def change_password(new_password)
35
+ new_data = {}
36
+
37
+ @data.each_key do |key|
38
+ new_data[key] = get(key)
39
+ end
40
+
41
+ self.password = new_password
42
+
43
+ new_data.each do |key, plaintext|
44
+ @data[key] = encrypt(plaintext)
45
+ end
46
+
47
+ store_data
48
+ end
49
+
50
+ private
51
+
52
+ def file_path=(file_path)
53
+ @file_path = file_path
54
+ load_data
55
+ @file_path
56
+ end
57
+
58
+ def password=(password)
59
+ @cipher = nil
60
+ @password = password
61
+ end
62
+
63
+ def cipher
64
+ @cipher ||= Gibberish::AES.new(@password)
65
+ end
66
+
67
+ def load_data
68
+ begin
69
+ @data = YAML.load_file(file_path) || {}
70
+ rescue Errno::ENOENT
71
+ @data = {}
72
+ end
73
+ end
74
+
75
+ def store_data
76
+ File.open(@file_path, File::RDWR|File::CREAT|File::LOCK_EX, 0640) do |f|
77
+ f.puts YAML.dump @data
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "secret_store/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "secret_store"
7
+ s.version = SecretStore::VERSION
8
+ s.authors = ["Brendon Murphy"]
9
+ s.email = ["xternal1+github@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Store secrets for your app in a encrypted in a yaml file.}
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = "secret_store"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency "gibberish"
21
+
22
+ s.add_development_dependency "rake"
23
+ s.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe SecretStore, "initializing" do
4
+ let(:tmpfile) { Tempfile.new("secret_store") }
5
+
6
+ it "takes a password and file path" do
7
+ subject = SecretStore.new("pass", tmpfile.path)
8
+ subject.file_path.should == tmpfile.path
9
+ end
10
+ end
11
+
12
+ describe SecretStore, "storing a secret" do
13
+ let(:tmpfile) { Tempfile.new("secret_store") }
14
+ subject { SecretStore.new("the_pass", tmpfile.path) }
15
+
16
+ context "when the key is not already stored" do
17
+ it "stores the value for the key in a yaml data file" do
18
+ subject.store("foobar", "fizzbuzz")
19
+ data = YAML.load_file(tmpfile.path)
20
+ data["foobar"].should_not be_empty
21
+ end
22
+
23
+ it "stores in an encrypted fashion" do
24
+ subject.store("foobar", "fizzbuzz")
25
+ data = YAML.load_file(tmpfile.path)
26
+ data["foobar"].should_not == "fizzbuzz"
27
+ end
28
+
29
+ it "leaves other data in the store file intact" do
30
+ tmpfile.puts YAML.dump("already" => "here")
31
+ tmpfile.flush
32
+ subject.store("foobar", "fizzbuzz")
33
+ data = YAML.load_file(tmpfile.path)
34
+ data["already"].should == "here"
35
+ end
36
+ end
37
+
38
+ context "when the key is already stored" do
39
+ it "raises if not forced" do
40
+ subject.store("foobar", "fizzbuzz")
41
+ lambda {
42
+ subject.store("foobar", "fizzbuzz")
43
+ }.should raise_error
44
+ end
45
+
46
+ it "stores if forced" do
47
+ subject.store("foobar", "fizzbuzz")
48
+ lambda {
49
+ subject.store("foobar", "fizzbuzz", :force)
50
+ }.should_not raise_error
51
+ end
52
+ end
53
+ end
54
+
55
+ describe SecretStore, "getting a secret" do
56
+ let(:tmpfile) { Tempfile.new("secret_store") }
57
+ subject { SecretStore.new("the_pass", tmpfile.path) }
58
+
59
+ it "returns the decrypted secret" do
60
+ subject.store("foobar", "fizzbuzz")
61
+ subject.get("foobar").should == "fizzbuzz"
62
+ end
63
+
64
+ it "raises IndexError if the key is not found" do
65
+ lambda{
66
+ subject.get("not_found")
67
+ }.should raise_error(IndexError)
68
+ end
69
+
70
+ it "raises OpenSSL::Cipher::CipherError if the password for the store is wrong" do
71
+ subject.store("foobar", "fizzbuzz")
72
+ with_wrong_pass = SecretStore.new("wrong_pass", tmpfile.path)
73
+ lambda {
74
+ with_wrong_pass.get("foobar")
75
+ }.should raise_error(OpenSSL::Cipher::CipherError)
76
+ end
77
+ end
78
+
79
+ describe SecretStore, "changing the password" do
80
+ let(:tmpfile) { Tempfile.new("secret_store") }
81
+ subject { SecretStore.new("the_pass", tmpfile.path) }
82
+
83
+ it "resets the value for each secret key" do
84
+ tmpfile.puts YAML.dump("foo" => "bar", "fizz" => "buzz")
85
+ subject.change_password("new_password")
86
+ data = YAML.load_file(tmpfile.path)
87
+ data["foo"].should_not == "bar"
88
+ data["fizz"].should_not == "buzz"
89
+ end
90
+
91
+ it "leaves you with the ability to get secrets using the new password" do
92
+ subject.store("foo", "bar")
93
+ subject.change_password("new_pass")
94
+
95
+ subject.get("foo").should == "bar"
96
+
97
+ with_new_pass = SecretStore.new("new_pass", tmpfile.path)
98
+ subject.get("foo").should == "bar"
99
+ end
100
+ end
@@ -0,0 +1,8 @@
1
+ require 'tempfile'
2
+
3
+ require File.expand_path("../../lib/secret_store", __FILE__)
4
+
5
+ RSpec.configure do |config|
6
+ config.mock_with :rspec
7
+ end
8
+
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secret_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brendon Murphy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: gibberish
16
+ requirement: &2160632160 !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: *2160632160
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &2160631720 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2160631720
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &2160631280 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2160631280
47
+ description: Store secrets for your app in a encrypted in a yaml file.
48
+ email:
49
+ - xternal1+github@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - README.md
57
+ - Rakefile
58
+ - lib/secret_store.rb
59
+ - lib/secret_store/version.rb
60
+ - secret_store.gemspec
61
+ - spec/secret_store_spec.rb
62
+ - spec/spec_helper.rb
63
+ homepage: ''
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ segments:
76
+ - 0
77
+ hash: -1229492496997866402
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ segments:
85
+ - 0
86
+ hash: -1229492496997866402
87
+ requirements: []
88
+ rubyforge_project: secret_store
89
+ rubygems_version: 1.8.15
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Store secrets for your app in a encrypted in a yaml file.
93
+ test_files: []