donjon 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e2e4af5e144eda80bddb067306735fad06605d4a
4
+ data.tar.gz: bba9c3dd427d5046289498073b47ddbe4d80b306
5
+ SHA512:
6
+ metadata.gz: be4415356a0dfacb7b0a8b9389cfb24148688f06fe57d0dea72be24151f49c00d1ae9640df6cc9661d5ae292944b96577a5f1ab41a235598fb682ebc1350ecc3
7
+ data.tar.gz: e18dfe9d8769cb5c8bdc5b6dd20b96379c39b2208b26f3dae13f951567927af6020eae919ae4485879e70acf1d32a9bea633ef460c9ed39a4963bc5c44978337
data/.gitignore ADDED
@@ -0,0 +1,17 @@
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
data/.rspec ADDED
@@ -0,0 +1,6 @@
1
+ -I.
2
+ --color
3
+ --format progress
4
+ --order random
5
+ #--profile 5
6
+
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source ENV.fetch('GEM_SOURCE', 'https://rubygems.org')
2
+
3
+ # Specify your gem's dependencies in donjon.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
10
+ # vim: set filetype=ruby:
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 HouseTrip Ltd
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.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Donjon
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Install it as you would any gem:
8
+
9
+ $ gem install donjon
10
+
11
+ Then run the interactive setup:
12
+
13
+ $ dj vault:init
14
+
15
+
16
+ ## Usage
17
+
18
+ Once you've set up a vault (you can use `vault:init` to connect to an existing
19
+ vault, e.g. on Dropbox).
20
+
21
+ ```
22
+ Commands:
23
+ dj config:get KEY... # Decrypts the value for KEY from the vault
24
+ dj config:mget [REGEXP] # Decrypts multiple keys (all readable by default)
25
+ dj config:set KEY=VALUE ... # Encrypts KEY and VALUE in the vault
26
+ dj help [COMMAND] # Describe available commands or one specific command
27
+ dj init # Creates a new vault, or connects to an existing vault.
28
+ dj user:add NAME [PATH] # Adds user and their public key to the vault. Reads from standard input if no path is given.
29
+ ```
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( http://github.com/<my-github-username>/donjon/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/dj ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'donjon'
3
+ require 'donjon/cli'
4
+
5
+ Donjon::Commands::Base.start(ARGV)
6
+
data/donjon.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'donjon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "donjon"
8
+ spec.version = Donjon::VERSION
9
+ spec.authors = ["Julien Letessier"]
10
+ spec.email = ["julien.letessier@gmail.com"]
11
+ spec.summary = %q{Secure, multi-user data store.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency 'thor'
21
+ spec.add_dependency 'gibberish'
22
+
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'guard-rspec'
28
+ end
@@ -0,0 +1,6 @@
1
+ module Kernel
2
+ def assert(condition, message = nil, error: RuntimeError)
3
+ return if condition
4
+ raise error.new(message || 'assertion failed')
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ class IO
2
+ def get_password(out: $stderr)
3
+ result = ''
4
+ noecho do
5
+ while char = getch
6
+ case char
7
+ when /[\r\n]/
8
+ break
9
+ when /[\e\b\x7f]/
10
+ result.replace result.chop
11
+ out.write "\b \b"
12
+ when /[\x3\x1A]/ # interrupt, background
13
+ out.write "\nOk, aborting\n"
14
+ abort
15
+ else
16
+ result << char
17
+ out.write '*'
18
+ end
19
+ end
20
+ end
21
+ out.write "\n"
22
+ result
23
+ end
24
+ end
data/lib/donjon.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "donjon/version"
2
+
data/lib/donjon/cli.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'donjon/commands/vault'
2
+ require 'donjon/commands/config'
3
+ require 'donjon/commands/user'
4
+
@@ -0,0 +1,66 @@
1
+ require 'donjon/shell'
2
+ require 'donjon/settings'
3
+ require 'pathname'
4
+ require 'pathname'
5
+ require 'openssl'
6
+ require 'donjon/user'
7
+ require 'donjon/database'
8
+ require 'core_ext/io_get_password'
9
+
10
+ module Donjon
11
+ module Commands
12
+ class Base < Thor
13
+ def self.start(args)
14
+ super(args, shell: Shell.instance)
15
+ end
16
+
17
+ def self.decl(method_name)
18
+ define_method(method_name.to_sym) { |*args| send(method_name.gsub(':','_').to_sym, *args) }
19
+ end
20
+
21
+ protected
22
+
23
+ def settings
24
+ @settings ||= Settings.new
25
+ end
26
+
27
+ def check_configured
28
+ return if settings.configured?
29
+ say "Oops, I can't run that until you've configured me.", :red
30
+ say "Run 'vault:init' and I'll help out!"
31
+ exit 1
32
+ end
33
+
34
+ def repo
35
+ @repo ||= begin
36
+ check_configured
37
+ Repository.new(settings.vault_path)
38
+ end
39
+ end
40
+
41
+ def actor
42
+ @actor ||= begin
43
+ check_configured
44
+ pem_data = Pathname.new(settings.private_key).read
45
+ password = _get_password("Please enter the password for your private key (#{settings.private_key})")
46
+ key = OpenSSL::PKey::RSA.new(pem_data, password)
47
+ User.new(repo: repo, name: settings.user_name, key: key)
48
+ end
49
+ end
50
+
51
+ def database
52
+ @database ||= begin
53
+ Database.new(actor: actor)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def _get_password(message)
60
+ say message, :green
61
+ $stdout.write('> ')
62
+ $stdin.get_password
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,41 @@
1
+ require 'donjon/commands/base'
2
+
3
+ module Donjon
4
+ module Commands
5
+ Base.class_eval do
6
+ desc 'config:set KEY=VALUE ...', 'Encrypts KEY and VALUE in the vault'
7
+ decl 'config:set'
8
+
9
+ desc 'config:get KEY...', 'Decrypts the value for KEY from the vault'
10
+ decl 'config:get'
11
+
12
+ desc 'config:mget [REGEXP]', 'Decrypts multiple keys (all readable by default)'
13
+ decl 'config:mget'
14
+
15
+ private
16
+
17
+ def config_set(*keyvals)
18
+ keyvals.each do |keyval|
19
+ m = /([^=]*)=(.*)/.match(keyval)
20
+ key = m[1]
21
+ value = m[2]
22
+ database[key] = value
23
+ end
24
+ end
25
+
26
+ def config_get(*keys)
27
+ keys.each do |key|
28
+ puts "#{key}: #{database[key]}"
29
+ end
30
+ end
31
+
32
+ def config_mget(regexp = nil)
33
+ regexp = Regexp.new(regexp) if regexp
34
+ database.each do |key, value|
35
+ next if regexp && regexp !~ key
36
+ puts "#{key}: #{value}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,36 @@
1
+ require 'donjon/commands/base'
2
+ require 'donjon/user'
3
+
4
+ module Donjon
5
+ module Commands
6
+ Base.class_eval do
7
+ desc 'user:add NAME [PATH]', 'Adds user and their public key to the vault. Reads from standard input if no path is given.'
8
+ decl 'user:add'
9
+
10
+ private
11
+
12
+ def user_add(name, path = nil)
13
+ if path == nil
14
+ say "Please paste #{name}'s public key in PEM format.", :green
15
+ say "They can obtain it by running e.g.:"
16
+ say "$ openssl rsa -in ~/.ssh/id_rsa -pubout -outform pem"
17
+ $stderr.write('> ')
18
+ key_data = ''
19
+ while line = $stdin.gets
20
+ break if line.strip.empty?
21
+ key_data << line
22
+ end
23
+ else
24
+ key_data = Pathname.new(key_path).expand_path.read
25
+ end
26
+
27
+ key = OpenSSL::PKey::RSA.new(key_data, '').public_key
28
+ say "Saving #{name}'s public key..."
29
+ User.new(name: name, key: key, repo: actor.repo).save
30
+ say "Making the database readable by #{name}..."
31
+ database.update
32
+ say "Success! #{name} has been added to the vault.", [:green, :bold]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ require 'thor'
2
+ require 'pathname'
3
+ require 'donjon/commands/base'
4
+ require 'donjon/repository'
5
+ require 'donjon/settings'
6
+ require 'donjon/configurator'
7
+ require 'donjon/shell'
8
+
9
+ module Donjon
10
+ module Commands
11
+ Base.class_eval do
12
+ desc "init", 'Creates a new vault, or connects to an existing vault.'
13
+
14
+ def init
15
+ if settings.configured?
16
+ say 'This vault is already configured :)', :green
17
+ say 'If you want another one, set DONJONRC to a new configuration file'
18
+ say "(if it doesn't exist I will create one for you)"
19
+ return
20
+ end
21
+
22
+ Configurator.new(settings: settings).run
23
+ end
24
+
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,166 @@
1
+ require 'donjon/user'
2
+ require 'core_ext/io_get_password'
3
+ require 'ostruct'
4
+
5
+ module Donjon
6
+ class Configurator
7
+ def initialize(settings:)
8
+ @settings = settings
9
+ end
10
+
11
+ def run
12
+ _get_name
13
+ _get_repo
14
+ _get_private_key_path
15
+ _save_user
16
+ _thank_user if changes?
17
+ nil
18
+ end
19
+
20
+ private
21
+
22
+ def changes?
23
+ !!@changes
24
+ end
25
+
26
+ def changes!
27
+ @changes = true
28
+ end
29
+
30
+ def _thank_user
31
+ _shell.say "Thanks! All your settings are saved.", [:green, :bold]
32
+ end
33
+
34
+ def _get_repo
35
+ path = @settings.vault_path ||= begin
36
+ changes!
37
+ _shell.say "Where do you want your vault? [~/.donjon]", :green
38
+ ans = _shell.ask('>')
39
+ ans = '~/.donjon' if ans.empty?
40
+ Pathname.new(ans).expand_path.to_s
41
+ end
42
+ Donjon::Repository.new(path)
43
+ end
44
+
45
+ def _get_name
46
+ @settings.user_name ||= begin
47
+ changes!
48
+ _shell.say "Hi! How do you want to be called? [#{_default_name}]", :green
49
+ ans = _shell.ask '>'
50
+ ans.empty? ? _default_name : ans
51
+ end
52
+ end
53
+
54
+ def _get_private_key_path
55
+ @settings.private_key ||= begin
56
+ changes!
57
+ _get_key_path.path.to_s
58
+ end
59
+ end
60
+
61
+ def _get_key_path
62
+ @_key_path ||= begin
63
+ _shell.say [
64
+ 'Do you want to use an existing private key or create one?',
65
+ 'If you use an existing key, it needs to be an encrypted, 2048-bit RSA key.',
66
+ 'Use [e]xisting or [c]reate?',
67
+ ].join("\n"), :green
68
+ ans = _shell.ask('>')
69
+
70
+ case ans
71
+ when /[Ee]/
72
+ _get_existing_key
73
+ when /[Cc]/
74
+ _get_new_key
75
+ else
76
+ _shell.say 'No idea what you mean, sorry.', :red
77
+ exit 1
78
+ end
79
+ end
80
+ end
81
+
82
+ def _get_existing_key
83
+ _shell.say('What is the path to your key? [~/.ssh/id_rsa]', :green)
84
+ ans = _shell.ask('>')
85
+ ans = '~/.ssh/id_rsa' if ans.empty?
86
+ path = Pathname.new(ans).expand_path
87
+ if !path.exist?
88
+ _shell.say 'Sorry, cannot find that file.', :red
89
+ exit 1
90
+ end
91
+ _check_key(path)
92
+ end
93
+
94
+
95
+ def _check_key(path, password = nil)
96
+ unless _is_key_encrypted?(path)
97
+ _shell.say 'That key is not password-protected. Sorry, I\' not taking that risk!', :red
98
+ exit 1
99
+ end
100
+
101
+ if password.nil?
102
+ _shell.say "I'll now try to load your private key.", :green
103
+ _shell.say "Please enter your key password (I won't store it anywhere)", :green
104
+ $stdout.write "> "
105
+ $stdout.flush
106
+ password = $stdin.get_password
107
+ end
108
+
109
+ key = OpenSSL::PKey::RSA.new(path.read, password)
110
+ if key.n.num_bits != 2_048
111
+ _shell.say "Sorry, that key isn't 2,048 bits long.", :red
112
+ exit 1
113
+ end
114
+
115
+ _shell.say "Okay, that key seems valid.", :green
116
+ OpenStruct.new key: key.public_key, path: path
117
+ end
118
+
119
+ def _get_new_key
120
+ _shell.say "Where do you want to store your new key? [~/.ssh/donjon_rsa]", :green
121
+ ans = _shell.ask('>')
122
+ ans = '~/.ssh/donjon_rsa' if ans.empty?
123
+ path = Pathname.new(ans).expand_path
124
+
125
+ _shell.say "Please enter a password for your new key (I won't store it anywhere)", :green
126
+ $stdout.write "> "
127
+ $stdout.flush
128
+ password = $stdin.get_password
129
+
130
+ command = "ssh-keygen -q -t rsa -b 2048 -N '#{password}' -f #{path}"
131
+ unless system(command)
132
+ _shell.say "Sorry, key generation failed!", :red
133
+ exit 1
134
+ end
135
+
136
+ _check_key(path, password)
137
+ end
138
+
139
+ def _save_user
140
+ if user = User.find(repo: _get_repo, name: _get_name)
141
+ if user.key.to_pem.strip != _get_key_path.key.to_pem.strip
142
+ _shell.say "There's already a user with your name in the vault, and the public keys don't match. I'm afraid I'm a bit stumped.", :red
143
+ exit 1
144
+ end
145
+ return
146
+ end
147
+ User.new(name: _get_name, repo: _get_repo, key: _get_key_path.key).save
148
+ nil
149
+ end
150
+
151
+ def _is_key_encrypted?(path)
152
+ OpenSSL::PKey::RSA.new(path.read, '')
153
+ return false
154
+ rescue OpenSSL::PKey::RSAError
155
+ return true
156
+ end
157
+
158
+ def _default_name
159
+ ENV['USER'] || ENV['LOGNAME']
160
+ end
161
+
162
+ def _shell
163
+ Shell.instance
164
+ end
165
+ end
166
+ end