encipher 0.0.1a

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2960852e4181fe772df6e890cb788745f1b024b7
4
+ data.tar.gz: 616cfc3a33a901aea4a816ab5f45c3705c69e8e4
5
+ SHA512:
6
+ metadata.gz: 82ab87765e53556bfd026d5a9ee40dfa3b86a391f5773c5462104b6052527d9c92972f3301fceee6d0d6a2deec4eaa2d619cf176df8f6d694ab728e6276e5d52
7
+ data.tar.gz: 1c10702862f9fc8eb685b37d0c8e34048446231df372cd21df1f756258817586f29a5779f86f3f880f1176940a637d204b5061e3f7e91aca501e812775816ea1
data/.env ADDED
@@ -0,0 +1,5 @@
1
+ TEST_KEY: asdfkasdf;lkajsdf;lkajsdf;lkasjdf;laksdjf
2
+ TEST_SECRET: 98hw7fh87yf9ba8sd7f
3
+
4
+ TEST_KEY2: asdfkasdf;lkajsdf;lkajsdf;lkasjdf;laksdjf
5
+ TEST_SECRET2: 98hw7fh87yf9ba8sd7f
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .DS_Store*
24
+ .encipher
25
+ developers.yml
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm: 2.1.2
3
+ cache: bundler
4
+ before_script:
5
+ - bundle exec clint --lint lib --warn
6
+ - bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in encipher.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Joey Lorich
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,97 @@
1
+ # Encipher
2
+
3
+ An RSA ssh-key encryption based configuration storage solution.
4
+
5
+ Keeping configuration information in the environment is one of the tenants of a [twelve-factor app](http://12factor.net/config), however configuring each server individually can be time consuming and when configuration information needs to change often lots of work must be done.
6
+
7
+ Encipher provides a method to simplify this situation by keeping enviroment and user restricted, public-key encrypted, secrets right in the repository. On application load Encipher decrepts the secrets for the appropriate environment using a private key held on the server and stores them in memory.
8
+
9
+ ####Really? Secrets in the repository?
10
+
11
+ Yes. In general, secret information should not be kept in a source repository. Each developer with permission to clone a repository should not necessarily have permission to deploy or edit configuration information. However, all Encipher-kept secrets are only decryptable by a user with a valid, enrolled, private key that has had a certain environments secrets enabled by an authorized user.
12
+
13
+ ####How it works
14
+ Each encipher user and server has a public ssh key enrolled by another authroized user for a given environment (the initial secrets database creation enrolls the first user). The enrolled user's public key is then stored for future use. When a secret is added or modified, an entry is created and encrypted with each enrolled key. Each user can *only* decrypt secrets encrypted with their public key, so limiting a key's access to a certain set of secrets is easy.
15
+
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'encipher', git: git://github.com/jlorich/encipher.git
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+
28
+ ## Environment-based restrictions
29
+ All actions are by default segregated by encipher-environment. Each key must be enrolled in each environment to be able to read or set secrets for it. The enviorment is by default `:development`, but may be set by specifiying the `ENCIPHER_ENV` enviornment variable. The enviornment may also be set in code by calling `Encipher.env = :my_env`.
30
+
31
+ All secrets set will only be encrypted for keys enrolled in the given environment.
32
+
33
+
34
+ ## Command line usage
35
+
36
+ Initialize encipher with your desired private key (this location will be cached in the .encipher file)
37
+
38
+ encipher init
39
+
40
+ You can also specify the key path with
41
+
42
+ encipher init /path/to/your/private_key
43
+
44
+ #####Enroll yourself. Your public key will be auto-generated from the private key used with encipher init.
45
+
46
+ encipher enroll
47
+
48
+ #####Enroll a new user or servers key
49
+
50
+ encipher enroll ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCvRm/KvZy8iDyDgjokMMMMUz8UK84OlBGbeeo6VT8UZc6e8E1xUNfFCNp6xUQMO8N+vpqxlOr3haAXn6sdHCnMb8BpWYwq0Un19WaToTiv/15tRvZzkQw9KPu/TjBy8OaSjNAAxo5rWkJbDc0K4WdBtDJ4uk3i+UmxpYXhOtC9eCLgMdxZ6xIWuP0ymyYe/SZSdupeKblaehYyFEb2NKTVbvnbef79ZogJG7IlWFYS+qaqk+xFZIRYEX3yLIijF/WvLmZw2NVurb8rhnNLNDI3v+Gy+bw0sitZKvX5MWunpmykFY8/5YnWuEA1AJGaexC/1EWXUzVN6o2my4Aqlwz
51
+
52
+ #####To store a secret:
53
+
54
+ encipher set "secret name" "this is really cool"
55
+
56
+ #####To retreive a secret:
57
+
58
+ encipher get "secret name"
59
+
60
+ #####To list all enrolled secrets:
61
+
62
+ encipher list
63
+
64
+ ## Easy Editing
65
+ For ease of editing, Encipher also provides the `edit` command line option.
66
+
67
+ encipher edit
68
+
69
+ Calling `edit` will launch an external editor (specified by the `EDITOR` environment variable, and by default `vi` on most systems) populated with the entire secrets hash in [YAML](http://www.yaml.org/) for the current environment. Once saved, all specified secrets will be re-encrypted for all appropriately enrolled keys and the tempfile used for editing will be destroyed.
70
+
71
+ ## Programmatic usage
72
+
73
+ ##### Configuration
74
+ Each application can be configured as follows:
75
+
76
+ Encipher.configure do |config|
77
+ config.keypath = '~/.ssh/my_server_key'
78
+ config.env = :staging
79
+ end
80
+
81
+ ##### Secrets
82
+
83
+ All secrets are accessabile by calling
84
+
85
+ Encipher.secrets
86
+
87
+ This returns a [Recursive OpenStruct](https://github.com/aetherknight/recursive-open-struct) (a hash deeply accessible by string, symbol, or dot notation) containing all secrets information for the specified environment. Once loaded the information is cached in-memory so all subsequent calls to `secrets` won't need to re-decrypt each secret.
88
+
89
+ If secrets reloading is desired `Encipher.reload` can be called, or `Encipher.secrets(reload: true)` can be referenced to force reloading.
90
+
91
+ ## Contributing
92
+
93
+ 1. Fork it ( https://github.com/jlorich/encipher/fork )
94
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
95
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
96
+ 4. Push to the branch (`git push origin my-new-feature`)
97
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'encipher/cli/cli'
4
+
5
+ Encipher::Cli::Cli.start(ARGV)
@@ -0,0 +1,47 @@
1
+ Documentation:
2
+ Enabled: false
3
+
4
+ EmptyLinesAroundBody:
5
+ Enabled: false
6
+
7
+ LineLength:
8
+ Max: 120
9
+ Exclude:
10
+
11
+ ClassLength:
12
+ Enabled: true
13
+ CountComments: false
14
+ Max: 150
15
+
16
+ MethodLength:
17
+ Enabled: false
18
+
19
+ WhileUntilModifier:
20
+ Enabled: false
21
+
22
+ ClassAndModuleChildren:
23
+ Enabled: false
24
+
25
+ Rails/ActionFilter:
26
+ Enabled: false
27
+
28
+ Rails/DefaultScope:
29
+ Enabled: false
30
+
31
+ Rails/HasAndBelongsToMany:
32
+ Enabled: false
33
+
34
+ Rails/Output:
35
+ Enabled: false
36
+
37
+ Rails/ReadWriteAttribute:
38
+ Enabled: false
39
+
40
+ Rails/ScopeArgs:
41
+ Enabled: false
42
+
43
+ Rails/Validation:
44
+ Enabled: false
45
+
46
+ Rails/Delegate:
47
+ Enabled: false
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'encipher/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "encipher"
8
+ spec.version = Encipher::VERSION
9
+ spec.authors = ["Joey Lorich"]
10
+ spec.email = ["joseph@lorich.me"]
11
+ spec.summary = %q{Secure secrets management}
12
+ spec.description = %q{Secure secrets management description!}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency 'rake', '~> 10.3.2'
23
+ spec.add_development_dependency 'pry-byebug', '~> 1.3.3'
24
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
25
+ spec.add_development_dependency 'clint_eastwood', '~> 0.0.1'
26
+
27
+ spec.add_dependency 'thor', '~> 0.19.1'
28
+ spec.add_dependency 'highline', '~> 1.6.21'
29
+ spec.add_dependency 'deep_merge', '~> 1.0.1'
30
+ spec.add_dependency 'net-ssh', '~> 2.9.1'
31
+ spec.add_dependency 'sqlite3', '~> 1.3.9'
32
+ spec.add_dependency 'data_mapper', '~> 1.2.0'
33
+ spec.add_dependency 'dm-sqlite-adapter', '~> 1.2.0'
34
+ spec.add_dependency 'dot_configure', '~> 0.0.1'
35
+ spec.add_dependency 'exedit', '~> 0.0.2'
36
+ spec.add_dependency 'awesome_print', '~> 1.2.0'
37
+ spec.add_dependency 'recursive-open-struct', '~> 0.5.0'
38
+
39
+ spec.executables = ['encipher']
40
+ end
@@ -0,0 +1,14 @@
1
+ require 'encipher/version'
2
+ require 'encipher/security'
3
+ require 'encipher/vault'
4
+ require 'encipher/environment'
5
+ require 'encipher/models/secret'
6
+ require 'encipher/models/user'
7
+ require 'encipher/encipher'
8
+
9
+ require 'sqlite3'
10
+ require 'rubygems'
11
+ require 'base64'
12
+ require 'data_mapper'
13
+ require 'net/ssh'
14
+ require 'pry'
@@ -0,0 +1,41 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+ require 'ostruct'
4
+ require 'encipher/vault'
5
+ require 'encipher/environment'
6
+
7
+ module Encipher
8
+ module Cli
9
+
10
+ # Base CLI commands
11
+ class Base < Thor
12
+ protected
13
+
14
+ # Loads and configures encipher
15
+ def environment
16
+ return @environment if defined? @environment
17
+
18
+ @environment = Encipher::Environment.new(config.key_path)
19
+ end
20
+
21
+ def vault
22
+ return @vault if defined? @vault
23
+
24
+ @vault = Encipher::Vault.new(config.key_path)
25
+ end
26
+
27
+ # Loads and returns the config file (memoized)
28
+ def config
29
+ return @config if defined? @config
30
+
31
+ config_path = File.expand_path './.encipher'
32
+
33
+ unless File.exist? config_path
34
+ fail 'You must run encipher init before running'
35
+ end
36
+
37
+ @config = OpenStruct.new YAML.load(File.read(config_path))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,86 @@
1
+ require 'encipher/cli/base'
2
+ require 'encipher/cli/convert'
3
+ require 'exedit'
4
+ require 'json'
5
+ require 'awesome_print'
6
+ require 'pry'
7
+
8
+ module Encipher
9
+ module Cli
10
+ # Encipher command line interface
11
+ class Cli < Base
12
+ desc 'get [key]', 'Returns an unencrypted value'
13
+ def get(key)
14
+ puts environment.get key
15
+ end
16
+
17
+ desc 'set [key] [value]', 'Set an encrypted value'
18
+ def set(key, value)
19
+ environment.set key, value
20
+ end
21
+
22
+ desc 'list', 'Lists all secrets for the current environment'
23
+ def list
24
+ puts environment.list
25
+ end
26
+
27
+ desc 'edit', 'Edit all secrets in an external editor'
28
+ def edit
29
+ result = Exedit.edit environment.hash.to_yaml
30
+
31
+ environment.hash = YAML.load(result)
32
+ end
33
+
34
+ desc 'keys', 'Lists all enrolled keys'
35
+ def keys
36
+ environment.user_hash.each do |id, ssh_key|
37
+ wrapped = wrap(ssh_key, 70)
38
+
39
+ if wrapped.count > 1
40
+ puts "#{id.to_s.rjust(10)} - #{wrapped.shift}"
41
+
42
+ wrapped.each do |string|
43
+ puts " #{string}"
44
+ end
45
+ else
46
+ puts puts "#{id} - #{ssh_key}"
47
+ end
48
+ end
49
+ end
50
+
51
+ desc 'convert [type]', 'Converts various password stores to encipher'
52
+ subcommand 'convert', Convert
53
+
54
+ desc 'init', 'loads they key at the given keypath'
55
+ def init(key_path = '~/.ssh/id_rsa')
56
+ key_path = File.expand_path key_path
57
+ config_path = File.expand_path './.encipher'
58
+
59
+ fail '.encipher file already exists' if File.exist? config_path
60
+ fail 'Specified key path does not exist' unless File.exist? key_path
61
+
62
+ File.open(config_path, 'w') do |file|
63
+ file.write({ key_path: key_path }.to_yaml)
64
+ end
65
+
66
+ puts 'Encipher is ready for use'
67
+ end
68
+
69
+ desc 'enroll', 'Enrolls a new public key'
70
+ def enroll(key = nil)
71
+ vault.enroll(key)
72
+ end
73
+
74
+ desc 'revoke', 'Destroys a user and all their secrets'
75
+ def revoke(user_id)
76
+ vault.revoke(user_id)
77
+ end
78
+
79
+ protected
80
+
81
+ def wrap(string, width)
82
+ string.chars.each_slice(width).map(&:join)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,26 @@
1
+ require 'encipher/cli/base'
2
+
3
+ module Encipher
4
+ module Cli
5
+ # Encipher conversions
6
+ class Convert < Base
7
+ # desc 'dotenv', 'Loads a .env file to secrets.db'
8
+ # def dotenv(path='./.env')
9
+ # path = File.expand_path path
10
+
11
+ # if !File.exist? path
12
+ # fail Exception.new 'A valid .env file must exist'
13
+ # end
14
+
15
+ # loader = Dotenv.new(path, encipher)
16
+ # loader.store
17
+
18
+ # if loader.secrets.count == 0
19
+ # puts 'No secrets stored'
20
+ # else
21
+ # puts "#{loader.secrets.count} secrets loaded"
22
+ # end
23
+ # end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # Encipher
2
+ module Encipher
3
+ # Dotenv parser
4
+ class Dotenv
5
+ def initialize(path, encipher)
6
+ @dotenv = YAML.load(File.read(path))
7
+ @encipher = encipher
8
+ end
9
+
10
+ def secrets
11
+ @dotenv
12
+ end
13
+
14
+ def store
15
+ @dotenv.each do |name, secret|
16
+ @encipher.store name, secret
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ require 'dot_configure'
2
+ require 'recursive-open-struct'
3
+
4
+ # Encipher secrets storage
5
+ module Encipher
6
+ extend DotConfigure
7
+
8
+ # Requires a key or array of keys to be present, otherwise throw an exception
9
+ def self.require(keys)
10
+ [*keys].each do |key|
11
+ fail "Encipher key (#{key}) not found" unless secrets.keys.include? key.to_s
12
+ end
13
+ end
14
+
15
+ # Gets the current encipher environment
16
+ # Precedence: Manually set env, ENCIPHER_ENV environment variable, options env, default
17
+ def self.env
18
+ @env ||= if ENV['ENCIPHER_ENV']
19
+ ENV['ENCIPHER_ENV'].to_sym
20
+ else
21
+ (options.env ? options.env.to_sym : nil) || :development
22
+ end
23
+ end
24
+
25
+ # Manually set the encipher env
26
+ def self.env=(env)
27
+ @env = env.to_sym
28
+ end
29
+
30
+ # Loads, caches, and returns all available secrets
31
+ # @param reload [Boolean] Forces the secrets to reload
32
+ def self.secrets(reload: false)
33
+ return @secrets if @secrets && !reload
34
+ environment = Encipher::Environment.new(options.keypath)
35
+ @secrets = RecursiveOpenStruct.new(environment.hash)
36
+ end
37
+
38
+ # Forces a secrets reload
39
+ def self.reload
40
+ secrets(reload: true)
41
+ end
42
+ end
@@ -0,0 +1,73 @@
1
+ require 'yaml'
2
+ require 'encipher'
3
+ require 'encipher/vault'
4
+ require 'recursive-open-struct'
5
+
6
+ module Encipher
7
+ # Enviornment variable storage
8
+ class Environment < Vault
9
+
10
+ # Store a new environment variable
11
+ def set(name, value)
12
+ require_user
13
+
14
+ # Delete all existing variables of that name
15
+ where(type: :env, name: name).map(&:destroy)
16
+
17
+ User.each do |user|
18
+ lock(Secret.new(
19
+ user: user,
20
+ unlocked_value: {
21
+ env: Encipher.env,
22
+ type: :env,
23
+ name: name,
24
+ value: value
25
+ }
26
+ )).save
27
+ end
28
+ end
29
+
30
+ def get(name)
31
+ require_user
32
+
33
+ env = unlock(where(type: :env, name: name).first).unlocked_value
34
+ env[:value]
35
+ end
36
+
37
+ def list
38
+ require_user
39
+ where(type: :env).map { |s| unlock(s).unlocked_value[:name] }
40
+ end
41
+
42
+ def hash
43
+ all.each_with_object({}) do |secret, result|
44
+ result[secret[:name]] = secret[:value]
45
+ end
46
+ end
47
+
48
+ def hash=(hash)
49
+ hash.each do |key, value|
50
+ set(key, value)
51
+ end
52
+ end
53
+
54
+ def user_hash
55
+ users.each_with_object({}) do |user, result|
56
+ key = OpenSSL::PKey::RSA.new(user.public_key)
57
+ type = key.ssh_type
58
+ data = [key.to_blob].pack('m0')
59
+ result[user.id] = "#{type} #{data}"
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def users
66
+ User.all
67
+ end
68
+
69
+ def all
70
+ where(type: :env).map { |s| unlock(s).unlocked_value }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ require 'data_mapper'
2
+
3
+ module Encipher
4
+ # An encipher secret
5
+ class Secret
6
+ include DataMapper::Resource
7
+
8
+ attr_accessor :unlocked_value
9
+ attr_accessor :locked
10
+
11
+ belongs_to :user
12
+
13
+ property :id, Serial
14
+ property :value, Text
15
+
16
+ def value=(v)
17
+ fail 'Secret must be unlocked to set the value' if locked
18
+
19
+ super v
20
+ end
21
+
22
+ def unlocked_value=(value)
23
+ @unlocked_value = value
24
+ @unlocked = true
25
+ end
26
+
27
+ def save
28
+ fail 'Cannot save an unlocked secret' unless locked
29
+ super
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require 'data_mapper'
2
+
3
+ module Encipher
4
+ # An encipher user
5
+ class User
6
+ include DataMapper::Resource
7
+
8
+ has n, :secrets
9
+
10
+ property :id, Serial
11
+ property :public_key, Text
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ # Encipher
2
+ module Encipher
3
+ # Handles key loading, encryption, and decryption
4
+ class Security
5
+
6
+ # Loads the private key
7
+ def initialize(private_key)
8
+ @private_key = Net::SSH::KeyFactory.load_private_key(private_key)
9
+ end
10
+
11
+ # Returns the pem public key associated with this security object
12
+ def public_key
13
+ require_key
14
+
15
+ @private_key.public_key.to_pem
16
+ end
17
+
18
+ # Encrypts a string of text with the given public key
19
+ def encrypt(string, public_key = nil)
20
+ require_key unless public_key
21
+
22
+ key = OpenSSL::PKey::RSA.new(public_key || @private_key.public_key.to_pem)
23
+
24
+ Base64.encode64(key.public_encrypt(string))
25
+ end
26
+
27
+ # Decrypts a string of text with the current private key
28
+ def decrypt(string)
29
+ require_key
30
+
31
+ @private_key.private_decrypt(Base64.decode64(string))
32
+ end
33
+
34
+ private
35
+
36
+ # Raises an error if no private key has been loaded
37
+ def require_key
38
+ fail 'No private key loaded' unless @private_key
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,123 @@
1
+ require 'pry'
2
+ require 'data_mapper'
3
+ require 'encipher/security'
4
+ require 'encipher/models/user'
5
+ require 'encipher/models/secret'
6
+ require 'net/ssh'
7
+ require 'base64'
8
+ require 'deep_merge'
9
+
10
+ module Encipher
11
+ # The main encipher secrets storage entrypoint
12
+ class Vault
13
+ # Initalizes the database and loads the keys
14
+ def initialize(secret_key)
15
+ @secret_key = secret_key
16
+ initialize_database
17
+ end
18
+
19
+ # locks a secrets contents
20
+ def lock(secret)
21
+ secret.value = security.encrypt(secret.unlocked_value.to_yaml, secret.user.public_key)
22
+ secret.locked = true
23
+ secret
24
+ end
25
+
26
+ # unlocks a secrets contents
27
+ def unlock(secret)
28
+ secret.locked = false
29
+ secret.unlocked_value = YAML.load(security.decrypt(secret.value))
30
+ secret
31
+ end
32
+
33
+ def where(options)
34
+ require_user
35
+
36
+ [].tap do |r|
37
+ current_user.secrets.each do |secret|
38
+ contents = unlock(secret).unlocked_value
39
+ r << secret if compare(contents, { env: Encipher.env }.deep_merge(options))
40
+ end
41
+ end
42
+ end
43
+
44
+ # checks if the keys/values in options are included in ojbect
45
+ def compare(object, options)
46
+ options.keys.each do |key|
47
+ return false unless object[key] == options[key]
48
+ end
49
+
50
+ true
51
+ end
52
+
53
+ # Enroll a new user's key or the current users
54
+ def enroll(key = nil, path: nil)
55
+ fail 'Must specify key OR path, not both' unless (!!key ^ !!path) || !(key || path)
56
+
57
+ if path
58
+ fail 'Bad key path' unless File.exist? File.expand_path(path)
59
+ data = File.read(File.expand_path path)
60
+ key = Net::SSH::KeyFactory.load_data_public_key(data).to_pem
61
+ elsif key
62
+ key = Net::SSH::KeyFactory.load_data_public_key(key).to_pem
63
+ else
64
+ key = security.public_key
65
+ end
66
+
67
+ if user_exists? key
68
+ puts 'Key already enrolled'
69
+ else
70
+ User.create(public_key: key)
71
+ puts 'Key was successfully enrolled'
72
+ end
73
+ end
74
+
75
+ # Checks if a user exists by public key
76
+ def user_exists?(public_key)
77
+ User.all(public_key: public_key).count > 0
78
+ end
79
+
80
+ # Revoke a user by id
81
+ def revoke(user_id)
82
+ require_user
83
+
84
+ user = User.find(user_id)
85
+ user.secrets.each do |secret|
86
+ secret.destroy
87
+ end
88
+
89
+ user.destroy
90
+ end
91
+
92
+ protected
93
+
94
+ # returns the memoized security object
95
+ def security
96
+ return @security if defined? @security
97
+ @security = Security.new(@secret_key)
98
+ end
99
+
100
+ # Finalizes datamapper and initializes the sqlite database
101
+ def initialize_database(database_path = './secrets.db')
102
+ DataMapper.finalize
103
+
104
+ database_path = File.expand_path database_path
105
+
106
+ @dm = DataMapper.setup(:default, "sqlite:///#{database_path}")
107
+
108
+ return if File.exist? database_path
109
+
110
+ SQLite3::Database.new(database_path)
111
+ DataMapper.auto_migrate!
112
+ end
113
+
114
+ # Gets the current user object
115
+ def current_user
116
+ User.all(public_key: security.public_key).first
117
+ end
118
+
119
+ def require_user
120
+ fail 'Current user must be enrolled' unless current_user
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ module Encipher
2
+ VERSION = '0.0.1a'
3
+ end
Binary file
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encipher::Cli::Base do
4
+ let(:key_path) { 'keypath' }
5
+ let(:config) { double(key_path: 'keypath') }
6
+
7
+ context do
8
+ before :each do
9
+ allow(subject).to receive(:config) { config }
10
+ end
11
+
12
+ describe '.environment' do
13
+ let(:environment) { double }
14
+
15
+ it 'memoizes the environment object' do
16
+ subject.instance_variable_set(:@environment, environment)
17
+ expect(Encipher::Environment).to_not receive(:new)
18
+ expect(subject.send(:environment)).to be environment
19
+ end
20
+
21
+ it 'creates a new environment object if not set' do
22
+ expect(Encipher::Environment).to receive(:new).with(key_path) { environment }
23
+ expect(subject.send(:environment)).to be(environment)
24
+ end
25
+ end
26
+
27
+ describe '.vault' do
28
+ let(:vault) { double }
29
+
30
+ it 'memoizes the vault object' do
31
+ subject.instance_variable_set(:@vault, vault)
32
+ expect(Encipher::Vault).to_not receive(:new)
33
+ expect(subject.send(:vault)).to be vault
34
+ end
35
+
36
+ it 'creates a new vault object if not set' do
37
+ expect(Encipher::Vault).to receive(:new).with(key_path) { vault }
38
+ expect(subject.send(:vault)).to be(vault)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '.config' do
44
+ let (:yaml_hash) { double }
45
+ let (:config) { double }
46
+
47
+ before :each do
48
+ allow(OpenStruct).to receive(:new) { config }
49
+ allow(YAML).to receive(:load) { yaml_hash }
50
+ allow(File).to receive(:read)
51
+ allow(File).to receive(:exist?) { true }
52
+ end
53
+
54
+ it 'memoizes the config object' do
55
+ subject.instance_variable_set(:@config, config)
56
+ expect(OpenStruct).to_not receive(:new)
57
+ expect(subject.send(:config)).to be config
58
+ end
59
+
60
+ it 'loads the config from yaml' do
61
+ expect(YAML).to receive(:load)
62
+ subject.send(:config)
63
+ end
64
+
65
+ it 'creates a new config object if not set' do
66
+ expect(OpenStruct).to receive(:new).with(yaml_hash) { config }
67
+ expect(subject.send(:config)).to be(config)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,82 @@
1
+ require 'pry-byebug'
2
+ require 'encipher'
3
+ require 'encipher/cli/cli'
4
+
5
+ # This file was generated by the `rails generate rspec:install` command. Conventionally, all
6
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
7
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
8
+ # file to always be loaded, without a need to explicitly require it in any files.
9
+ #
10
+ # Given that it is always loaded, you are encouraged to keep this file as
11
+ # light-weight as possible. Requiring heavyweight dependencies from this file
12
+ # will add to the boot time of your test suite on EVERY test run, even for an
13
+ # individual file that may not need all of that loaded. Instead, make a
14
+ # separate helper file that requires this one and then use it only in the specs
15
+ # that actually need it.
16
+ #
17
+ # The `.rspec` file also contains a few flags that are not defaults but that
18
+ # users commonly want.
19
+ #
20
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
+ RSpec.configure do |config|
22
+ # The settings below are suggested to provide a good initial experience
23
+ # with RSpec, but feel free to customize to your heart's content.
24
+ # These two settings work together to allow you to limit a spec run
25
+ # to individual examples or groups you care about by tagging them with
26
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
27
+ # get run.
28
+ config.filter_run :focus
29
+ config.run_all_when_everything_filtered = true
30
+
31
+ # Many RSpec users commonly either run the entire suite or an individual
32
+ # file, and it's useful to allow more verbose output when running an
33
+ # individual spec file.
34
+ if config.files_to_run.one?
35
+ # Use the documentation formatter for detailed output,
36
+ # unless a formatter has already been configured
37
+ # (e.g. via a command-line flag).
38
+ config.default_formatter = 'doc'
39
+ end
40
+
41
+ # Print the 10 slowest examples and example groups at the
42
+ # end of the spec run, to help surface which specs are running
43
+ # particularly slow.
44
+ config.profile_examples = 10
45
+
46
+ # Run specs in random order to surface order dependencies. If you find an
47
+ # order dependency and want to debug it, you can fix the order by providing
48
+ # the seed, which is printed after each run.
49
+ # --seed 1234
50
+ config.order = :random
51
+
52
+ # Seed global randomization in this process using the `--seed` CLI option.
53
+ # Setting this allows you to use `--seed` to deterministically reproduce
54
+ # test failures related to randomization by passing the same `--seed` value
55
+ # as the one that triggered the failure.
56
+ Kernel.srand config.seed
57
+
58
+ # rspec-expectations config goes here. You can use an alternate
59
+ # assertion/expectation library such as wrong or the stdlib/minitest
60
+ # assertions if you prefer.
61
+ config.expect_with :rspec do |expectations|
62
+ # Enable only the newer, non-monkey-patching expect syntax.
63
+ # For more details, see:
64
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
65
+ expectations.syntax = :expect
66
+ end
67
+
68
+ config.color = true
69
+
70
+ # rspec-mocks config goes here. You can use an alternate test double
71
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
72
+ config.mock_with :rspec do |mocks|
73
+ # Enable only the newer, non-monkey-patching expect syntax.
74
+ # For more details, see:
75
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
76
+ mocks.syntax = :expect
77
+
78
+ # Prevents you from mocking or stubbing a method that does not exist on
79
+ # a real object. This is generally recommended.
80
+ mocks.verify_partial_doubles = true
81
+ end
82
+ end
metadata ADDED
@@ -0,0 +1,296 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: encipher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1a
5
+ platform: ruby
6
+ authors:
7
+ - Joey Lorich
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 10.3.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 10.3.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: clint_eastwood
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: thor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 0.19.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 0.19.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: highline
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 1.6.21
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 1.6.21
111
+ - !ruby/object:Gem::Dependency
112
+ name: deep_merge
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 1.0.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: 1.0.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: net-ssh
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 2.9.1
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: 2.9.1
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: 1.3.9
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: 1.3.9
153
+ - !ruby/object:Gem::Dependency
154
+ name: data_mapper
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ~>
158
+ - !ruby/object:Gem::Version
159
+ version: 1.2.0
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ~>
165
+ - !ruby/object:Gem::Version
166
+ version: 1.2.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: dm-sqlite-adapter
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: 1.2.0
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ~>
179
+ - !ruby/object:Gem::Version
180
+ version: 1.2.0
181
+ - !ruby/object:Gem::Dependency
182
+ name: dot_configure
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ~>
186
+ - !ruby/object:Gem::Version
187
+ version: 0.0.1
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ~>
193
+ - !ruby/object:Gem::Version
194
+ version: 0.0.1
195
+ - !ruby/object:Gem::Dependency
196
+ name: exedit
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ~>
200
+ - !ruby/object:Gem::Version
201
+ version: 0.0.2
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ~>
207
+ - !ruby/object:Gem::Version
208
+ version: 0.0.2
209
+ - !ruby/object:Gem::Dependency
210
+ name: awesome_print
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ~>
214
+ - !ruby/object:Gem::Version
215
+ version: 1.2.0
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ~>
221
+ - !ruby/object:Gem::Version
222
+ version: 1.2.0
223
+ - !ruby/object:Gem::Dependency
224
+ name: recursive-open-struct
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ~>
228
+ - !ruby/object:Gem::Version
229
+ version: 0.5.0
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ~>
235
+ - !ruby/object:Gem::Version
236
+ version: 0.5.0
237
+ description: Secure secrets management description!
238
+ email:
239
+ - joseph@lorich.me
240
+ executables:
241
+ - encipher
242
+ extensions: []
243
+ extra_rdoc_files: []
244
+ files:
245
+ - .env
246
+ - .gitignore
247
+ - .travis.yml
248
+ - Gemfile
249
+ - LICENSE.txt
250
+ - README.md
251
+ - Rakefile
252
+ - bin/encipher
253
+ - config/rubocop.yml
254
+ - encipher.gemspec
255
+ - lib/encipher.rb
256
+ - lib/encipher/cli/base.rb
257
+ - lib/encipher/cli/cli.rb
258
+ - lib/encipher/cli/convert.rb
259
+ - lib/encipher/dotenv.rb
260
+ - lib/encipher/encipher.rb
261
+ - lib/encipher/environment.rb
262
+ - lib/encipher/models/secret.rb
263
+ - lib/encipher/models/user.rb
264
+ - lib/encipher/security.rb
265
+ - lib/encipher/vault.rb
266
+ - lib/encipher/version.rb
267
+ - secrets.db
268
+ - spec/enchiper/cli/base_spec.rb
269
+ - spec/spec_helper.rb
270
+ homepage: ''
271
+ licenses:
272
+ - MIT
273
+ metadata: {}
274
+ post_install_message:
275
+ rdoc_options: []
276
+ require_paths:
277
+ - lib
278
+ required_ruby_version: !ruby/object:Gem::Requirement
279
+ requirements:
280
+ - - '>='
281
+ - !ruby/object:Gem::Version
282
+ version: '0'
283
+ required_rubygems_version: !ruby/object:Gem::Requirement
284
+ requirements:
285
+ - - '>'
286
+ - !ruby/object:Gem::Version
287
+ version: 1.3.1
288
+ requirements: []
289
+ rubyforge_project:
290
+ rubygems_version: 2.0.14
291
+ signing_key:
292
+ specification_version: 4
293
+ summary: Secure secrets management
294
+ test_files:
295
+ - spec/enchiper/cli/base_spec.rb
296
+ - spec/spec_helper.rb