arver 0.0.5
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.
- 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
|