donjon 0.0.1

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