encipher 0.0.1a

Sign up to get free protection for your applications and to get access to all the features.
@@ -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