arver 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.textile +9 -0
- data/README.textile +120 -0
- data/bin/arver +10 -0
- data/lib/arver.rb +6 -0
- data/lib/arver/action.rb +78 -0
- data/lib/arver/adduser_action.rb +54 -0
- data/lib/arver/bootstrap.rb +35 -0
- data/lib/arver/cli.rb +133 -0
- data/lib/arver/close_action.rb +61 -0
- data/lib/arver/command_wrapper.rb +42 -0
- data/lib/arver/config.rb +54 -0
- data/lib/arver/create_action.rb +58 -0
- data/lib/arver/deluser_action.rb +34 -0
- data/lib/arver/gc_action.rb +12 -0
- data/lib/arver/gpg_key_manager.rb +80 -0
- data/lib/arver/host.rb +74 -0
- data/lib/arver/hostgroup.rb +17 -0
- data/lib/arver/info_action.rb +22 -0
- data/lib/arver/io_logger.rb +34 -0
- data/lib/arver/key_generator.rb +29 -0
- data/lib/arver/key_info_action.rb +29 -0
- data/lib/arver/key_saver.rb +128 -0
- data/lib/arver/keystore.rb +70 -0
- data/lib/arver/list_action.rb +7 -0
- data/lib/arver/local_config.rb +41 -0
- data/lib/arver/log.rb +39 -0
- data/lib/arver/log_levels.rb +9 -0
- data/lib/arver/luks_wrapper.rb +29 -0
- data/lib/arver/node_with_script_hooks.rb +25 -0
- data/lib/arver/open_action.rb +70 -0
- data/lib/arver/partition.rb +53 -0
- data/lib/arver/partition_hierarchy_node.rb +112 -0
- data/lib/arver/runtime_config.rb +22 -0
- data/lib/arver/ssh_command_wrapper.rb +21 -0
- data/lib/arver/string.rb +8 -0
- data/lib/arver/target_list.rb +33 -0
- data/lib/arver/test_config_loader.rb +21 -0
- data/lib/arver/test_partition.rb +9 -0
- data/lib/arver/tree.rb +32 -0
- data/lib/arver/version.rb +3 -0
- data/man/arver.5 +429 -0
- metadata +155 -0
data/CHANGELOG.textile
ADDED
data/README.textile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
h1. arver
|
2
|
+
|
3
|
+
* https://git.codecoop.org/projects/arver
|
4
|
+
|
5
|
+
h2. DESCRIPTION:
|
6
|
+
|
7
|
+
arver is a tool to manage encrypted harddisks.
|
8
|
+
|
9
|
+
Imagine you are a collective with several admin members. Your servers have
|
10
|
+
diffrent LUKS encrypted devices.
|
11
|
+
|
12
|
+
Either you would need 1 password for every device which everyone needs to know
|
13
|
+
or you use arver! Arver has 1 password for each device and for each member. This
|
14
|
+
password is stored encrypted with the personal gpg-key in the data directory.
|
15
|
+
The admins only need to know the password to the their own gpg secret key.
|
16
|
+
|
17
|
+
This has the following advantages:
|
18
|
+
|
19
|
+
* No need to share passwords or password patterns
|
20
|
+
** Often people share passwords amongst each another. This has the drawback that
|
21
|
+
in case of an emergency every password needs to be changed. Which means that
|
22
|
+
everyone else needs to learn a bunch of new passwords and changing these
|
23
|
+
passwords is also quite cumbersome and time consuming.
|
24
|
+
** As the amount of passwords might grow with your disks and hosts you will start
|
25
|
+
using a password pattern to derive passwords for each disk from that pattern.
|
26
|
+
This has the drawback that you can hardly share only partial access to disks
|
27
|
+
with a certain admin, as if she knows the pattern she will also likely have
|
28
|
+
access to every other disk. Furthermore, if once one password is leaked and
|
29
|
+
the patter is easily visible, all the other passwords are also compromised.
|
30
|
+
* Managing your encrypted harddisks is scriptable, which means that you can
|
31
|
+
recover much faster from outages
|
32
|
+
* Revoking access for an admin is scriptable and therefore done in one call and
|
33
|
+
also much safer than revoking manually for each disk.
|
34
|
+
* Finer grained access. As for each user and each disk there will be a seperate
|
35
|
+
password by design. You can also grant access to certain disks also only
|
36
|
+
selectively. So for example new admins in your group can only open the disks
|
37
|
+
for your most important services or for which they are respnsible, while access
|
38
|
+
to the other disks is restriced to other admins.
|
39
|
+
|
40
|
+
h1. Usage
|
41
|
+
|
42
|
+
arver ships with a detailed man page, describing the usage in detail.
|
43
|
+
|
44
|
+
h1. Limitations
|
45
|
+
|
46
|
+
* arver supports only up to 8 users as LUKS has only 8 key slots (LUKS NUMKEYS).
|
47
|
+
|
48
|
+
h1. Known Issues
|
49
|
+
|
50
|
+
h2. GPGME and gpg-agent
|
51
|
+
|
52
|
+
If arver asks you multiple times for the password, you might consider to use
|
53
|
+
gpg-agent, so you can decrypt your keypair once and the use it for all your
|
54
|
+
stored keys.
|
55
|
+
|
56
|
+
You can test gpg-agent by trying to decrypt an encrypted file for your user in
|
57
|
+
data/keys/USERNAME/key_X . It will tell you about possible gpg-erorrs.
|
58
|
+
|
59
|
+
Configuring gpg-agent is quite simple and you find information on the following
|
60
|
+
website: http://dougbarton.us/PGP/gpg-agent.html
|
61
|
+
|
62
|
+
If you install gpg-agent like dougbarton recomends, you need to further verify
|
63
|
+
that the environment variable GPG_AGENT_INFO is accessible within the arver
|
64
|
+
script. An option is to add the following entry to your .bashrc
|
65
|
+
|
66
|
+
if [ -r "${HOME}/.gpg-agent-info" ]; then
|
67
|
+
. ${HOME}/.gpg-agent-info
|
68
|
+
export GPG_AGENT_INFO
|
69
|
+
fi
|
70
|
+
|
71
|
+
h1. Requirements
|
72
|
+
|
73
|
+
arver only works with cryptsetup-luks >= 1.0.5 as previous versions do not
|
74
|
+
support key slots properly for our usage.
|
75
|
+
|
76
|
+
h1. Installation
|
77
|
+
|
78
|
+
The easiest way to install arver is by gem
|
79
|
+
|
80
|
+
sudo gem install arver
|
81
|
+
|
82
|
+
This will install all required dependecies automatically. If your distributions
|
83
|
+
contains an arver package we recommend installation by your package manager.
|
84
|
+
|
85
|
+
The following ruby gems are required for arver:
|
86
|
+
|
87
|
+
* gpgme 2
|
88
|
+
* activesupport 2
|
89
|
+
* escape
|
90
|
+
|
91
|
+
For development you will need the following additional gems:
|
92
|
+
|
93
|
+
* rake
|
94
|
+
* cucumber
|
95
|
+
* rspec
|
96
|
+
|
97
|
+
h1. License
|
98
|
+
|
99
|
+
(The MIT License)
|
100
|
+
|
101
|
+
Copyright (c) 2010 arver
|
102
|
+
|
103
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
104
|
+
a copy of this software and associated documentation files (the
|
105
|
+
'Software'), to deal in the Software without restriction, including
|
106
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
107
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
108
|
+
permit persons to whom the Software is furnished to do so, subject to
|
109
|
+
the following conditions:
|
110
|
+
|
111
|
+
The above copyright notice and this permission notice shall be
|
112
|
+
included in all copies or substantial portions of the Software.
|
113
|
+
|
114
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
115
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
116
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
117
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
118
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
119
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
120
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/arver
ADDED
data/lib/arver.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
%w{ singleton yaml fileutils active_support gpgme escape openssl}.each {|f| require f }
|
2
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
3
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
%w{ gpg_key_manager luks_wrapper action create_action list_action gc_action adduser_action deluser_action info_action close_action open_action target_list command_wrapper ssh_command_wrapper log_levels io_logger log string bootstrap local_config config test_config_loader node_with_script_hooks partition_hierarchy_node host hostgroup tree partition test_partition key_generator key_saver keystore runtime_config key_info_action}.each {|f| require "arver/#{f}" }
|
6
|
+
|
data/lib/arver/action.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Arver
|
2
|
+
class Action
|
3
|
+
|
4
|
+
attr_accessor :keystore, :target_list, :target_user, :slot_of_target_user, :generator, :key
|
5
|
+
|
6
|
+
def initialize( target_list )
|
7
|
+
self.target_list= target_list
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_user( username )
|
11
|
+
return true unless needs_target_user?
|
12
|
+
unless Arver::Config.instance.exists?(username)
|
13
|
+
Arver::Log.error( "No such user" )
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
return false unless verify_key_on_target( username )
|
17
|
+
self.slot_of_target_user= Arver::Config.instance.slot( username )
|
18
|
+
self.target_user= username
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify_key_on_target( username )
|
23
|
+
Arver::GPGKeyManager.check_key_of( username )
|
24
|
+
end
|
25
|
+
|
26
|
+
def needs_target_user?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_key_generator
|
31
|
+
self.generator= Arver::KeyGenerator.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def open_keystore
|
35
|
+
self.keystore= Arver::Keystore.instance
|
36
|
+
keystore.load
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_on( node )
|
40
|
+
node.run_action( self ) if node.target?( self.target_list )
|
41
|
+
end
|
42
|
+
|
43
|
+
def pre_action
|
44
|
+
end
|
45
|
+
|
46
|
+
def post_action
|
47
|
+
end
|
48
|
+
|
49
|
+
def pre_host( host )
|
50
|
+
end
|
51
|
+
|
52
|
+
def verify?( partition )
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_key( partition )
|
57
|
+
self.key= keystore.luks_key( partition )
|
58
|
+
|
59
|
+
if( key.nil? )
|
60
|
+
Arver::Log.error( "No permission on #{partition.path}. Skipping." )
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def pre_partition( partition )
|
67
|
+
end
|
68
|
+
|
69
|
+
def execute_partition( partition )
|
70
|
+
end
|
71
|
+
|
72
|
+
def post_partition( partition )
|
73
|
+
end
|
74
|
+
|
75
|
+
def post_host( host )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Arver
|
2
|
+
class AdduserAction < Action
|
3
|
+
|
4
|
+
def initialize( target_list )
|
5
|
+
super( target_list )
|
6
|
+
self.open_keystore
|
7
|
+
self.new_key_generator
|
8
|
+
end
|
9
|
+
|
10
|
+
def pre_action
|
11
|
+
tl = ""
|
12
|
+
target_list.each { |t| tl += ( tl.empty? ? "": ", " )+t.name }
|
13
|
+
Arver::Log.info( "adduser was called with target(s) #{tl} and user #{target_user} (slot-no #{slot_of_target_user})" )
|
14
|
+
end
|
15
|
+
|
16
|
+
def needs_target_user?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify?( partition )
|
21
|
+
if not Arver::RuntimeConfig.instance.ask_password then
|
22
|
+
return false unless load_key( partition )
|
23
|
+
else
|
24
|
+
self.key= ask("Enter the password for the volume: #{partition.device}") {|q| q.echo = false}
|
25
|
+
end
|
26
|
+
unless( Arver::LuksWrapper.open?(partition).execute )
|
27
|
+
Arver::Log.error( "WARNING: "+partition.name+" is not open. skipping." )
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute_partition( partition )
|
34
|
+
Arver::Log.info( "Generating keys for partition #{partition.device}" )
|
35
|
+
|
36
|
+
# generate a key for the new user
|
37
|
+
Arver::Log.debug( "generate_key (#{target_user},#{partition.path})" )
|
38
|
+
|
39
|
+
newkey = generator.generate_key( target_user, partition )
|
40
|
+
|
41
|
+
caller = Arver::LuksWrapper.addKey( slot_of_target_user.to_s, partition )
|
42
|
+
caller.execute( key + "\n" + newkey )
|
43
|
+
|
44
|
+
unless( caller.success? )
|
45
|
+
Arver::Log.error( "Could not add user to #{partition.path} \n #{caller.output}" )
|
46
|
+
generator.remove_key( target_user, partition )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def post_action
|
51
|
+
self.generator.dump
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Arver::Bootstrap
|
2
|
+
class << self
|
3
|
+
def run(options)
|
4
|
+
local = Arver::LocalConfig.instance
|
5
|
+
local.config_dir = options[:config_dir] unless options[:config_dir].empty?
|
6
|
+
local.username = options[:user] unless options[:user].empty?
|
7
|
+
|
8
|
+
unless local.username.present?
|
9
|
+
Arver::Log.error( "No user defined" )
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
|
13
|
+
config = Arver::Config.instance
|
14
|
+
config.load
|
15
|
+
|
16
|
+
self.load_runtime_config(options)
|
17
|
+
|
18
|
+
unless Arver::Config.instance.exists?(local.username)
|
19
|
+
Arver::Log.error( "No such user #{local.username}" )
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
Arver::GPGKeyManager.check_key_of(local.username)
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_runtime_config(options)
|
26
|
+
rtc = Arver::RuntimeConfig.instance
|
27
|
+
rtc.dry_run = options[:dry_run]
|
28
|
+
rtc.ask_password = options[:ask_password]
|
29
|
+
rtc.force = options[:force]
|
30
|
+
rtc.violence = options[:violence]
|
31
|
+
rtc.test_mode = options[:test_mode]
|
32
|
+
rtc.trust_all = options[:trust_all]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/arver/cli.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Arver
|
4
|
+
class CLI
|
5
|
+
def self.execute(output,arguments=[])
|
6
|
+
|
7
|
+
Arver::Log.logger= IOLogger.new( output )
|
8
|
+
|
9
|
+
options = {
|
10
|
+
:user => '',
|
11
|
+
:config_dir => '',
|
12
|
+
:dry_run => false,
|
13
|
+
:test_mode => false,
|
14
|
+
:ask_password => false,
|
15
|
+
:force => false,
|
16
|
+
:violence => false,
|
17
|
+
:action => nil,
|
18
|
+
:argument => {},
|
19
|
+
}
|
20
|
+
|
21
|
+
parser = OptionParser.new do |opts|
|
22
|
+
opts.banner = <<-BANNER.gsub(/^ /,'')
|
23
|
+
arver.
|
24
|
+
|
25
|
+
Usage: #{File.basename($0)} [options] ACTION
|
26
|
+
|
27
|
+
Options:
|
28
|
+
BANNER
|
29
|
+
opts.on("-c", "--config-dir PATH", String,
|
30
|
+
"Path to config dir.",
|
31
|
+
"Default: .arver") { |arg| options[:config_dir] = arg }
|
32
|
+
opts.on("-u", "--user NAME", String,
|
33
|
+
"Username." ) { |arg| options[:user] = arg }
|
34
|
+
opts.on("-h", "--help",
|
35
|
+
"Show this help message.") { Arver::Log.write opts; return }
|
36
|
+
opts.on("--dry-run",
|
37
|
+
"Test your command.") { options[:dry_run] = true }
|
38
|
+
opts.on("--ask-password",
|
39
|
+
"Ask for Password when --add-user.") { options[:ask_password] = true }
|
40
|
+
opts.on("-t", "--trust-all",
|
41
|
+
"Use untrusted GPG Keys.") { options[:trust_all] = true }
|
42
|
+
opts.on("--force",
|
43
|
+
"Apply force (allow duplicate keys)") { options[:force] = true }
|
44
|
+
opts.on("--violence",
|
45
|
+
"Apply violence (allow destruction of disk)") { options[:violence] = true }
|
46
|
+
opts.on("-v",
|
47
|
+
"Verbose") { Arver::Log.level( Arver::LogLevels::Debug ) }
|
48
|
+
opts.on("--vv",
|
49
|
+
"Max Verbose") { Arver::Log.level( Arver::LogLevels::Trace ) }
|
50
|
+
opts.on( "-l", "--list-targets",
|
51
|
+
"List targets." ) { options[:action] = :list; }
|
52
|
+
opts.on( "-g", "--garbage-collect",
|
53
|
+
"Expunge old keys." ) { options[:action] = :gc; }
|
54
|
+
opts.on( "-k TARGET", "--keys TARGET", String,
|
55
|
+
"List local keys for this target.") { |arg| options[:argument][:target] = arg; options[:action] = :key_info; }
|
56
|
+
opts.on("--test-mode",
|
57
|
+
"Test mode (internal use)") { options[:test_mode] = true }
|
58
|
+
opts.separator "Targets:"
|
59
|
+
opts.on(
|
60
|
+
" Possible Paths are: 'Group', 'Host', 'Device', 'Host/Device',\n"+
|
61
|
+
" 'Group/Host/Device' or 'ALL'.\n"+
|
62
|
+
" Multiple Parameters can be given as comma separated list.\n"+
|
63
|
+
" Ambigues Target parameters will not be executed." )
|
64
|
+
opts.separator "Actions:"
|
65
|
+
opts.on_tail( "--create TARGET", String,
|
66
|
+
"Create new arver partition on Target." ) { |arg| options[:argument][:target] = arg; options[:action] = :create; }
|
67
|
+
opts.on_tail( "-o TARGET", "--open TARGET", String,
|
68
|
+
"Open target." ) { |arg| options[:argument][:target] = arg; options[:action] = :open; }
|
69
|
+
opts.on_tail( "-c TARGET", "--close TARGET", String,
|
70
|
+
"Close target." ) { |arg| options[:argument][:target] = arg; options[:action] = :close; }
|
71
|
+
opts.on_tail( "-a USER TARGET", "--add-user USER TARGET", String,
|
72
|
+
"Add a user to target.") { |user| options[:action] = :adduser; options[:argument][:user] = user; }
|
73
|
+
opts.on_tail( "-d USER TARGET", "--del-user USER TARGET", String,
|
74
|
+
"Remove a user from target.") { |user| options[:action] = :deluser; options[:argument][:user] = user; }
|
75
|
+
opts.on_tail( "-i TARGET", "--info TARGET", String,
|
76
|
+
"LUKS info about a target.") { |arg| options[:argument][:target] = arg; options[:action] = :info; }
|
77
|
+
|
78
|
+
begin
|
79
|
+
opts.parse!(arguments)
|
80
|
+
rescue
|
81
|
+
Arver::Log.write opts; return
|
82
|
+
end
|
83
|
+
|
84
|
+
if options[:action] == :deluser || options[:action] == :adduser
|
85
|
+
options[:argument][:target] = arguments.last
|
86
|
+
end
|
87
|
+
|
88
|
+
if options[:action].nil? ||
|
89
|
+
( options[:action] != :list && options[:action] != :gc && ! options[:argument][:target] ) ||
|
90
|
+
( ( options[:action] == :adduser || options[:action] == :deluser ) && ! options[:argument][:target] )
|
91
|
+
Arver::Log.write opts; return
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
unless( Arver::Bootstrap.run( options ) )
|
96
|
+
return
|
97
|
+
end
|
98
|
+
|
99
|
+
target_list = TargetList.get_list( options[:argument][:target] )
|
100
|
+
if target_list.empty? && ( options[:action] != :list && options[:action] != :gc )
|
101
|
+
Arver::Log.write( "No targets found" )
|
102
|
+
return false
|
103
|
+
end
|
104
|
+
|
105
|
+
run_action( options[:action], target_list, options[:argument][:user] )
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.run_action( action, target_list, target_user )
|
109
|
+
actions = {
|
110
|
+
:list => Arver::ListAction,
|
111
|
+
:gc => Arver::GCAction,
|
112
|
+
:create => Arver::CreateAction,
|
113
|
+
:open => Arver::OpenAction,
|
114
|
+
:close => Arver::CloseAction,
|
115
|
+
:adduser => Arver::AdduserAction,
|
116
|
+
:deluser => Arver::DeluserAction,
|
117
|
+
:info => Arver::InfoAction,
|
118
|
+
:key_info => Arver::KeyInfoAction,
|
119
|
+
}
|
120
|
+
|
121
|
+
action = (actions[ action ]).new( target_list )
|
122
|
+
|
123
|
+
return false unless( action.on_user( target_user ) )
|
124
|
+
|
125
|
+
catch ( :abort_action ) do
|
126
|
+
action.pre_action
|
127
|
+
action.run_on( Arver::Config.instance.tree )
|
128
|
+
action.post_action
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|