secret_store 0.0.1

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 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: []