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
@@ -0,0 +1,34 @@
|
|
1
|
+
module Arver
|
2
|
+
|
3
|
+
class IOLogger
|
4
|
+
|
5
|
+
include LogLevels
|
6
|
+
|
7
|
+
attr_accessor :level, :stream
|
8
|
+
|
9
|
+
def initialize( stream = $stdout, level = Info )
|
10
|
+
@level = level
|
11
|
+
@stream = stream
|
12
|
+
end
|
13
|
+
|
14
|
+
def trace( string )
|
15
|
+
write( string ) if level <= Trace
|
16
|
+
end
|
17
|
+
def debug( string )
|
18
|
+
write( string ) if level <= Debug
|
19
|
+
end
|
20
|
+
def info( string)
|
21
|
+
write( string ) if level <= Info
|
22
|
+
end
|
23
|
+
def warn( string )
|
24
|
+
write( string ) if level <= Warn
|
25
|
+
end
|
26
|
+
def error( string )
|
27
|
+
write( string ) if level <= Error
|
28
|
+
end
|
29
|
+
def write( string )
|
30
|
+
stream.write( string.to_s+"\n" )
|
31
|
+
stream.flush
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Arver
|
2
|
+
class KeyGenerator
|
3
|
+
def initialize
|
4
|
+
@keys = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def generate_key( user, partition )
|
8
|
+
key = ActiveSupport::SecureRandom.base64(192)
|
9
|
+
add( user, partition, key )
|
10
|
+
key
|
11
|
+
end
|
12
|
+
|
13
|
+
def add( user, partition, luks_key )
|
14
|
+
@keys[user] ||= {}
|
15
|
+
@keys[user][partition.path] = { :key => luks_key, :time => Time.now.to_f }
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_key( user, partition )
|
19
|
+
@keys[user].delete( partition.path )
|
20
|
+
end
|
21
|
+
|
22
|
+
def dump
|
23
|
+
@keys.each do | user, user_keys |
|
24
|
+
KeySaver.save( user, user_keys.to_yaml ) unless user_keys.empty?
|
25
|
+
end
|
26
|
+
@keys = {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Arver
|
2
|
+
class KeyInfoAction < Action
|
3
|
+
def initialize(targets)
|
4
|
+
super(targets)
|
5
|
+
self.open_keystore
|
6
|
+
end
|
7
|
+
def pre_action
|
8
|
+
Arver::Log.write( "Listing keys: (+) available (-) not available: " )
|
9
|
+
end
|
10
|
+
def pre_host(h)
|
11
|
+
Arver::Log.write( " #{h.name}" )
|
12
|
+
end
|
13
|
+
def pre_partition(p)
|
14
|
+
if keystore.luks_key?(p)
|
15
|
+
line = " +"
|
16
|
+
else
|
17
|
+
line = " -"
|
18
|
+
end
|
19
|
+
versions = keystore.key_versions(p).collect do |v|
|
20
|
+
if v == 0
|
21
|
+
"0"
|
22
|
+
else
|
23
|
+
Date.strptime(v.to_s,'%s')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
Arver::Log.write( "#{line} #{p.device_path} (#{versions.join(", ")})" )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Arver
|
2
|
+
class KeySaver
|
3
|
+
|
4
|
+
def self.save( user, key )
|
5
|
+
unless GPGKeyManager.check_key_of( user )
|
6
|
+
return
|
7
|
+
end
|
8
|
+
gpg_key = GPGKeyManager.key_of( user )
|
9
|
+
key = add_padding( key )
|
10
|
+
crypto = GPGME::Crypto.new :armor => true
|
11
|
+
begin
|
12
|
+
if( Arver::RuntimeConfig.instance.trust_all )
|
13
|
+
encrypted = crypto.encrypt( key, {:recipients => gpg_key.fingerprint, :always_trust => true})
|
14
|
+
else
|
15
|
+
encrypted = crypto.encrypt( key, {:recipients => gpg_key.fingerprint, :armor => true})
|
16
|
+
end
|
17
|
+
rescue GPGME::Error => gpgerr
|
18
|
+
Arver::Log.error( "GPGME Error #{gpgerr} Message: #{gpgerr.message}" )
|
19
|
+
return
|
20
|
+
end
|
21
|
+
key_encrypted = encrypted.read
|
22
|
+
unless( Arver::RuntimeConfig.instance.dry_run )
|
23
|
+
FileUtils.mkdir_p key_path(user) unless File.exists?( key_path(user) )
|
24
|
+
filename = key_path(user)+"/"+OpenSSL::Digest::SHA1.new(key_encrypted).to_s
|
25
|
+
File.open( filename, 'w' ) do |f|
|
26
|
+
f.write key_encrypted
|
27
|
+
end
|
28
|
+
end
|
29
|
+
filename
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.key_path( user )
|
33
|
+
config_path+"/keys/"+user
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.config_path
|
37
|
+
Arver::LocalConfig.instance.config_dir
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.purge_keys( user )
|
41
|
+
FileUtils.rm_rf( key_path( user ) )
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.num_of_key_files( user )
|
45
|
+
Dir.entries( key_path( user ) ).size - 2
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.read( user )
|
49
|
+
GPGKeyManager.check_key_of( user )
|
50
|
+
return [] unless File.exists?( key_path( user ) )
|
51
|
+
decrypted = []
|
52
|
+
crypto = GPGME::Crypto.new
|
53
|
+
Dir.entries( key_path( user ) ).sort.each do | file |
|
54
|
+
unless( file == "." || file == ".." )
|
55
|
+
Arver::Log.trace( "Loading keyfile "+file )
|
56
|
+
key_encrypted = File.open( key_path( user )+"/"+file )
|
57
|
+
begin
|
58
|
+
decrypted_txt = crypto.decrypt( key_encrypted, { :passphrase_callback => method( :passfunc ) } )
|
59
|
+
rescue GPGME::Error => gpgerr
|
60
|
+
Arver::Log.error( "GPGME Error #{gpgerr} Message: #{gpgerr.message}" )
|
61
|
+
next
|
62
|
+
end
|
63
|
+
decrypted_key = substract_padding( decrypted_txt.read )
|
64
|
+
decrypted += [ decrypted_key ];
|
65
|
+
end
|
66
|
+
end
|
67
|
+
decrypted
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.add_padding( key )
|
71
|
+
marker = "--"+ActiveSupport::SecureRandom.base64( 82 )
|
72
|
+
size = 450000
|
73
|
+
padding_size = size - key.size
|
74
|
+
if padding_size <= 0
|
75
|
+
padding_size = 0
|
76
|
+
Arver::Log.warn( "Warning: Your arver keys exceed the maximal padding size, therefore i can no longer disguise how many keys you possess.")
|
77
|
+
end
|
78
|
+
padding = ActiveSupport::SecureRandom.base64( padding_size )
|
79
|
+
marker +"\n"+ key + "\n" + marker + "\n" + padding
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.substract_padding( key )
|
83
|
+
if( key.starts_with? '--- ' )
|
84
|
+
Arver::Log.warn( "Warning: you are using deprecated unpadded keyfiles. Please run garbage collect!" )
|
85
|
+
return key
|
86
|
+
end
|
87
|
+
marker = ""
|
88
|
+
striped_key = ""
|
89
|
+
key.each_line do |line|
|
90
|
+
if( marker.empty? )
|
91
|
+
marker = line
|
92
|
+
elsif( line == marker )
|
93
|
+
break
|
94
|
+
else
|
95
|
+
striped_key += line
|
96
|
+
end
|
97
|
+
end
|
98
|
+
striped_key.chomp
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def passfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd)
|
105
|
+
Arver::Log.write("Passphrase for #{uid_hint}: ")
|
106
|
+
begin
|
107
|
+
io = IO.for_fd(fd, 'w')
|
108
|
+
io.puts( ask("") { |q| q.echo = false } )
|
109
|
+
io.flush
|
110
|
+
ensure
|
111
|
+
(0 ... $_.length).each do |i| $_[i] = ?0 end if $_
|
112
|
+
end
|
113
|
+
Arver::Log.write("")
|
114
|
+
end
|
115
|
+
|
116
|
+
def testpassfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd)
|
117
|
+
$stderr.write("Passphrase for #{uid_hint}: (test Mode) ")
|
118
|
+
$stderr.flush
|
119
|
+
begin
|
120
|
+
io = IO.for_fd(fd, 'w')
|
121
|
+
io.puts( "test" )
|
122
|
+
io.flush
|
123
|
+
ensure
|
124
|
+
(0 ... $_.length).each do |i| $_[i] = ?0 end if $_
|
125
|
+
end
|
126
|
+
$stderr.puts
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Arver
|
2
|
+
class Keystore
|
3
|
+
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
attr_accessor :username
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@keys = {}
|
10
|
+
@key_versions = {}
|
11
|
+
self.username= Arver::LocalConfig.instance.username
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
flush_keys
|
16
|
+
KeySaver.read( self.username ).each do | loaded |
|
17
|
+
YAML.load( loaded ).each do | target, key |
|
18
|
+
load_luks_key(target,key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
purge_keys
|
25
|
+
KeySaver.save(username, @keys.to_yaml)
|
26
|
+
end
|
27
|
+
|
28
|
+
def purge_keys
|
29
|
+
KeySaver.purge_keys( username )
|
30
|
+
end
|
31
|
+
|
32
|
+
def flush_keys
|
33
|
+
@keys = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def luks_key(partition)
|
37
|
+
@keys[partition.path][:key] unless ! @keys[partition.path]
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_luks_key(partition, new_key)
|
41
|
+
if( new_key.kind_of? Hash )
|
42
|
+
if( ! @keys[partition] || @keys[partition][:time] <= new_key[:time] )
|
43
|
+
@keys[partition] = new_key
|
44
|
+
end
|
45
|
+
else
|
46
|
+
unless( @keys[partition] )
|
47
|
+
@keys[partition] = { :key => new_key, :time => 0.0 }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
mark_key_version(partition,@keys[partition])
|
51
|
+
end
|
52
|
+
|
53
|
+
def mark_key_version(path,key)
|
54
|
+
@key_versions[path] ||= []
|
55
|
+
@key_versions[path] << key[:time]
|
56
|
+
end
|
57
|
+
|
58
|
+
def key_versions(partition)
|
59
|
+
@key_versions[partition.path] || []
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_luks_key(partition, new_key)
|
63
|
+
@keys[partition.path] = { :key => new_key, :time => Time.new.to_f }
|
64
|
+
end
|
65
|
+
|
66
|
+
def luks_key?(partition)
|
67
|
+
! @keys[partition.path].nil?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Arver::LocalConfig
|
2
|
+
#this Config Object holds the local defaults for Arver. All options correspond to the ones set in .arver.local
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
def path
|
6
|
+
File.expand_path("~/.arver")
|
7
|
+
end
|
8
|
+
|
9
|
+
def config
|
10
|
+
@config ||= load_file( path )
|
11
|
+
end
|
12
|
+
|
13
|
+
def default
|
14
|
+
{ 'config_dir' => "~/.arverdata", 'username' => "" }
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_file(filename)
|
18
|
+
content = YAML.load(File.read(filename)) if File.exists?(filename)
|
19
|
+
self.default.merge(content||{})
|
20
|
+
end
|
21
|
+
|
22
|
+
def save
|
23
|
+
File.open( path, 'w' ) { |f| f.write(self.config.to_yaml) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def username
|
27
|
+
self.config['username']
|
28
|
+
end
|
29
|
+
|
30
|
+
def username= username
|
31
|
+
self.config['username'] = username
|
32
|
+
end
|
33
|
+
|
34
|
+
def config_dir
|
35
|
+
File.expand_path(self.config['config_dir'])
|
36
|
+
end
|
37
|
+
|
38
|
+
def config_dir=(directory)
|
39
|
+
self.config['config_dir'] = directory
|
40
|
+
end
|
41
|
+
end
|
data/lib/arver/log.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Arver
|
2
|
+
class Log
|
3
|
+
|
4
|
+
include LogLevels
|
5
|
+
|
6
|
+
def self.logger()
|
7
|
+
@@logger ||= IOLogger.new
|
8
|
+
end
|
9
|
+
def self.logger=( logger )
|
10
|
+
@@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.trace( string )
|
14
|
+
logger.trace( string )
|
15
|
+
end
|
16
|
+
def self.debug( string )
|
17
|
+
logger.debug( string )
|
18
|
+
end
|
19
|
+
def self.info( string )
|
20
|
+
logger.info( string )
|
21
|
+
end
|
22
|
+
def self.warn( string )
|
23
|
+
logger.warn( string )
|
24
|
+
end
|
25
|
+
def self.error( string )
|
26
|
+
logger.error( string )
|
27
|
+
end
|
28
|
+
def self.write( string )
|
29
|
+
logger.write( string )
|
30
|
+
end
|
31
|
+
def self.level( num )
|
32
|
+
logger.level=( num )
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Arver
|
2
|
+
class LuksWrapper
|
3
|
+
def self.addKey( key_slot, partition )
|
4
|
+
Arver::SSHCommandWrapper.create( "cryptsetup", [ "--batch-mode", "--key-slot=#{key_slot}", "luksAddKey", partition.device_path ], partition.parent, true )
|
5
|
+
end
|
6
|
+
def self.close( partition )
|
7
|
+
Arver::SSHCommandWrapper.create( "cryptsetup", [ "luksClose", "#{partition.name}"], partition.parent, true )
|
8
|
+
end
|
9
|
+
def self.dump( partition )
|
10
|
+
Arver::SSHCommandWrapper.create( "cryptsetup", [ "luksDump", partition.device_path ], partition.parent, true )
|
11
|
+
end
|
12
|
+
def self.create( key_slot, partition )
|
13
|
+
Arver::SSHCommandWrapper.create( "cryptsetup", [ "--batch-mode", "--key-slot=#{key_slot}", "--cipher=aes-cbc-essiv:sha256", "--key-size=256", "luksFormat", partition.device_path ], partition.parent, true )
|
14
|
+
end
|
15
|
+
def self.killSlot( key_slot, partition )
|
16
|
+
Arver::SSHCommandWrapper.create( "cryptsetup", [ "--batch-mode", "luksKillSlot", partition.device_path, key_slot ], partition.parent, true )
|
17
|
+
end
|
18
|
+
def self.open( partition )
|
19
|
+
Arver::SSHCommandWrapper.create( "cryptsetup", [ "--batch-mode", "luksOpen", "-T 1", partition.device_path, partition.name ], partition.parent, true )
|
20
|
+
end
|
21
|
+
def self.open?( partition )
|
22
|
+
Arver::SSHCommandWrapper.create( "test", [ "-b", "/dev/mapper/#{partition.name}" ], partition.parent, true )
|
23
|
+
end
|
24
|
+
def self.was_wrong_key?( command_wrapper )
|
25
|
+
# before version 1.2 return value was 234
|
26
|
+
command_wrapper.return_value == 234 || command_wrapper.return_value == 2
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Arver
|
2
|
+
module NodeWithScriptHooks
|
3
|
+
attr_accessor :pre_open, :pre_close, :post_open, :post_close
|
4
|
+
def script_hooks_to_yaml
|
5
|
+
yaml = ""
|
6
|
+
yaml << "'pre_open': '#{pre_open}'\n" unless pre_open.nil?
|
7
|
+
yaml << "'pre_close': '#{pre_close}'\n" unless pre_close.nil?
|
8
|
+
yaml << "'post_open': '#{post_open}'\n" unless post_open.nil?
|
9
|
+
yaml << "'post_close': '#{post_close}'\n" unless post_close.nil?
|
10
|
+
yaml
|
11
|
+
end
|
12
|
+
def script_hooks_from_hash( hash )
|
13
|
+
hash.each do | name, data |
|
14
|
+
self.pre_open= data if name == "pre_open"
|
15
|
+
self.pre_close= data if name == "pre_close"
|
16
|
+
self.post_open= data if name == "post_open"
|
17
|
+
self.post_close= data if name == "post_close"
|
18
|
+
end
|
19
|
+
hash.delete("pre_open")
|
20
|
+
hash.delete("pre_close")
|
21
|
+
hash.delete("post_open")
|
22
|
+
hash.delete("post_close")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|