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,61 @@
|
|
1
|
+
module Arver
|
2
|
+
class CloseAction < Action
|
3
|
+
|
4
|
+
def verify?( partition )
|
5
|
+
unless( Arver::LuksWrapper.open?(partition).execute )
|
6
|
+
Arver::Log.error( partition.name+" not open. skipping." )
|
7
|
+
return false
|
8
|
+
end
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_partition( partition )
|
13
|
+
Arver::Log.info( "closing: "+partition.path )
|
14
|
+
caller = Arver::LuksWrapper.close( partition )
|
15
|
+
unless( caller.execute )
|
16
|
+
Arver::Log.error( "Aborting: Something went wrong when closing "+partition.name+":\n"+caller.output )
|
17
|
+
throw( :abort_action )
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def pre_host( host )
|
22
|
+
return if host.pre_close.nil?
|
23
|
+
Arver::Log.info( "Running script: " + host.pre_close + " on " + host.name )
|
24
|
+
c = Arver::SSHCommandWrapper.create( host.pre_close, [] , host, true )
|
25
|
+
unless c.execute
|
26
|
+
Arver::Log.error( "Aborting: pre_close on #{host.name} failed:\n"+c.output )
|
27
|
+
throw( :abort_action )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def pre_partition( partition )
|
32
|
+
return if partition.pre_close.nil?
|
33
|
+
Arver::Log.info( "Running script: " + partition.pre_close + " on " + partition.parent.name )
|
34
|
+
c = Arver::SSHCommandWrapper.create( partition.pre_close, [] , partition.parent, true )
|
35
|
+
unless c.execute
|
36
|
+
Arver::Log.error( "Aborting: pre_close on #{partition.name} failed:\n"+c.output )
|
37
|
+
throw( :abort_action )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post_partition( partition )
|
42
|
+
return if partition.post_close.nil?
|
43
|
+
Arver::Log.info( "Running script: " + partition.post_close + " on " + partition.parent.name )
|
44
|
+
c = Arver::SSHCommandWrapper.create( partition.post_close, [] , partition.parent, true )
|
45
|
+
unless c.execute
|
46
|
+
Arver::Log.error( "Aborting: post_close on #{partition.name} failed:\n"+c.output )
|
47
|
+
throw( :abort_action )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def post_host( host )
|
52
|
+
return if host.post_close.nil?
|
53
|
+
Arver::Log.info( "Running script: " + host.post_close + " on " + host.name )
|
54
|
+
c = Arver::SSHCommandWrapper.create( host.post_close, [] , host, true )
|
55
|
+
unless c.execute
|
56
|
+
Arver::Log.error( "Aborting: post_close on #{host.name} failed:\n"+c.output )
|
57
|
+
throw( :abort_action )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Arver
|
2
|
+
class CommandWrapper
|
3
|
+
|
4
|
+
attr_accessor :command, :arguments_array, :return_value, :output
|
5
|
+
|
6
|
+
def self.create( cmd, args = [] )
|
7
|
+
c = CommandWrapper.new
|
8
|
+
c.command= cmd
|
9
|
+
c.arguments_array= args
|
10
|
+
c
|
11
|
+
end
|
12
|
+
|
13
|
+
def escaped_command
|
14
|
+
Escape.shell_command([ command ] + arguments_array )
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute( input = "" )
|
18
|
+
Arver::Log.trace( "** Execute: "+self.escaped_command )
|
19
|
+
self.run( escaped_command, input )
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def success?
|
24
|
+
return_value == 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def run( command, input )
|
28
|
+
if( Arver::RuntimeConfig.instance.test_mode || Arver::RuntimeConfig.instance.dry_run )
|
29
|
+
self.output= ""
|
30
|
+
self.return_value= 0
|
31
|
+
else
|
32
|
+
IO.popen( command, "w+") do |pipe|
|
33
|
+
pipe.puts( input ) unless input.empty?
|
34
|
+
pipe.close_write
|
35
|
+
self.output= pipe.read
|
36
|
+
end
|
37
|
+
self.return_value= $?.exitstatus
|
38
|
+
end
|
39
|
+
self.success?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/arver/config.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Arver
|
2
|
+
class Config
|
3
|
+
|
4
|
+
attr_accessor :tree, :users
|
5
|
+
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@tree = Arver::Tree.new
|
10
|
+
@users = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def path
|
14
|
+
File.expand_path( Arver::LocalConfig.instance.config_dir )
|
15
|
+
end
|
16
|
+
|
17
|
+
def load
|
18
|
+
if( ! File.exists?( path ) )
|
19
|
+
Arver::Log.error( "config-dir "+path+" does not exist" )
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
@users= ( load_file( path+"/users" ) )
|
23
|
+
tree.clear
|
24
|
+
tree.from_hash( load_file( path+"/disks" ) )
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_file( filename )
|
28
|
+
YAML.load( File.read(filename) ) if File.exists?( filename )
|
29
|
+
end
|
30
|
+
|
31
|
+
def save
|
32
|
+
FileUtils.mkdir_p( path ) unless File.exists?( path )
|
33
|
+
File.open( path+"/users", 'w' ) { |f| f.write( users.to_yaml ) }
|
34
|
+
File.open( path+"/disks", 'w' ) { |f| f.write( tree.to_yaml ) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def exists?( user )
|
38
|
+
! users[user].nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def gpg_key user
|
42
|
+
users[user]['gpg'] if exists?(user)
|
43
|
+
end
|
44
|
+
|
45
|
+
def slot user
|
46
|
+
users[user]['slot'] if exists?(user)
|
47
|
+
end
|
48
|
+
|
49
|
+
def == other
|
50
|
+
return tree == other.tree && users == other.users if other.is_a?(Arver::Config)
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Arver
|
2
|
+
class CreateAction < 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 verify?( partition )
|
11
|
+
key = self.keystore.luks_key( partition )
|
12
|
+
if( ! key.nil? and ! Arver::RuntimeConfig.instance.force )
|
13
|
+
Arver::Log.warn( "DANGEROUS: you do have already a key for partition #{partition.path} (apply --force to continue)" )
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute_partition( partition )
|
20
|
+
Arver::Log.info( "creating: "+partition.path )
|
21
|
+
|
22
|
+
slot_of_user = Arver::Config.instance.slot( Arver::LocalConfig.instance.username )
|
23
|
+
Arver::Log.debug( "generating a new key for partition #{partition.device} on #{partition.path}" )
|
24
|
+
|
25
|
+
# checking if disk is not already LUKS formatted
|
26
|
+
Arver::Log.debug( "checking if disk is already LUKS formatted." )
|
27
|
+
Arver::Log.info( "!! if the next line reads 'Command failed' please ignore it! (sorry this will become more sane soon) !!" )
|
28
|
+
|
29
|
+
caller = Arver::LuksWrapper.dump( partition )
|
30
|
+
caller.execute
|
31
|
+
|
32
|
+
if caller.output.include?('LUKS header information') then
|
33
|
+
Arver::Log.warn( "VERY DANGEROUS: the partition #{partition.device} is already formatted with LUKS - returning (continue with --violence)" )
|
34
|
+
Arver::Log.warn( "If you wish to integrate an existing disk into arver use --add-user #{Arver::LocalConfig.instance.username} instead." )
|
35
|
+
if Arver::RuntimeConfig.instance.violence then
|
36
|
+
Arver::Log.info( "you applied --violence, so we will continue ..." )
|
37
|
+
else
|
38
|
+
Arver::Log.info( "for more information see /tmp/luks_create_error.txt" )
|
39
|
+
system("echo \"#{caller.output}\" > /tmp/luks_create_error.txt")
|
40
|
+
throw( :abort_action ) if not Arver::RuntimeConfig.instance.violence
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Arver::Log.trace( "starting key generation..." )
|
45
|
+
key = self.generator.generate_key( Arver::LocalConfig.instance.username, partition )
|
46
|
+
caller = Arver::LuksWrapper.create( slot_of_user.to_s, partition )
|
47
|
+
caller.execute( key )
|
48
|
+
unless( caller.success? )
|
49
|
+
Arver::Log.error( "Could not create Partition!" )
|
50
|
+
self.generator.remove_key( Arver::LocalConfig.instance.username, partition )
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def post_action
|
55
|
+
self.generator.dump
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Arver
|
2
|
+
class DeluserAction < Action
|
3
|
+
def initialize( target_list )
|
4
|
+
super( target_list )
|
5
|
+
self.open_keystore
|
6
|
+
end
|
7
|
+
|
8
|
+
def needs_target_user?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
def verify_key_on_target( username )
|
12
|
+
#del user needs no key, slot number is enough
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def verify?( partition )
|
17
|
+
unless( load_key( partition ) )
|
18
|
+
Arver::Log.error( "No permission on " + partition.path )
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute_partition( partition )
|
25
|
+
Arver::Log.info( "remove user user #{target_user} (slot-no #{slot_of_target_user.to_s}) from #{partition.path}" )
|
26
|
+
|
27
|
+
caller = Arver::LuksWrapper.killSlot( slot_of_target_user.to_s, partition )
|
28
|
+
caller.execute( key )
|
29
|
+
unless( caller.success? )
|
30
|
+
Arver::Log.error( "Could not remove user:\n" + caller.output )
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Arver
|
2
|
+
class GPGKeyManager
|
3
|
+
class << self
|
4
|
+
def _key_of( user )
|
5
|
+
conf = Arver::Config.instance
|
6
|
+
fp = conf.gpg_key( user )
|
7
|
+
return false if fp.nil?
|
8
|
+
fp = fp.gsub(" ","")
|
9
|
+
key = GPGME::Key.find(:public, fp)
|
10
|
+
if key.size != 1
|
11
|
+
return false
|
12
|
+
end
|
13
|
+
key = key.first
|
14
|
+
if fp.size == 40 && fp != key.fingerprint
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
key
|
18
|
+
end
|
19
|
+
|
20
|
+
def key_of( user )
|
21
|
+
key = _key_of( user )
|
22
|
+
unless key
|
23
|
+
Arver::Log.error( "There is no unique gpg key for #{user} with the fiven fingerprint available." )
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
key
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_key_of( user )
|
30
|
+
conf = Arver::Config.instance
|
31
|
+
fp = conf.gpg_key( user )
|
32
|
+
if fp.nil?
|
33
|
+
Arver::Log.error( "#{user} has no gpg fingerprint defined." )
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
fp = fp.gsub(" ","")
|
37
|
+
if fp.size != 40
|
38
|
+
Arver::Log.error( "Please use the full fingerprint to define the gpg key for #{user}. The current config might be ambiguous." )
|
39
|
+
end
|
40
|
+
|
41
|
+
if( Arver::RuntimeConfig.instance.test_mode )
|
42
|
+
`gpg --import ../spec/data/fixtures/test_key 2> /dev/null`
|
43
|
+
end
|
44
|
+
|
45
|
+
config_path = Arver::LocalConfig.instance.config_dir
|
46
|
+
FileUtils.mkdir_p "#{config_path}/keys/public" unless File.exists?( "#{config_path}/keys/public" )
|
47
|
+
key = _key_of( user )
|
48
|
+
user_pubkey_file = config_path+"/keys/public/"+user
|
49
|
+
on_disk = File.exists?( user_pubkey_file )
|
50
|
+
|
51
|
+
if ! key && ! on_disk
|
52
|
+
Arver::Log.error( "No publickey for #{user} found. Aborting all actions." )
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
if ! key && on_disk
|
56
|
+
Arver::Log.warn( "Importing Publickey for #{user} from #{user_pubkey_file} into gpg keyring." )
|
57
|
+
key_import = File.read( user_pubkey_file )
|
58
|
+
GPGME::Key.import(key_import)
|
59
|
+
key = _key_of( user )
|
60
|
+
end
|
61
|
+
if key
|
62
|
+
if( ! Arver::RuntimeConfig.instance.trust_all && key.owner_trust != 5 )
|
63
|
+
Arver::Log.error( "You do not trust the key of #{user}!\nYou have to set the trust-level using 'gpg --edit-key #{key.primary_subkey.keyid}'.\nYou should verify the fingerprint over a secure channel." );
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
key_export = key.export( :armor => true ).read
|
67
|
+
if on_disk
|
68
|
+
key_on_disk = File.read( user_pubkey_file )
|
69
|
+
return true if key_on_disk == key_export
|
70
|
+
end
|
71
|
+
File.open( user_pubkey_file, 'w' ) do |f|
|
72
|
+
f.write( key_export )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
data/lib/arver/host.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Arver
|
2
|
+
class Host
|
3
|
+
|
4
|
+
attr_accessor :port, :username
|
5
|
+
attr_writer :address
|
6
|
+
|
7
|
+
include Arver::PartitionHierarchyNode
|
8
|
+
include Arver::NodeWithScriptHooks
|
9
|
+
|
10
|
+
def initialize( name, hostgroup )
|
11
|
+
self.name = name
|
12
|
+
self.parent = hostgroup
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_partition(partition)
|
16
|
+
add_child(partition)
|
17
|
+
end
|
18
|
+
|
19
|
+
def partition(name)
|
20
|
+
child(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def address
|
24
|
+
return @address unless @address.nil?
|
25
|
+
self.name
|
26
|
+
end
|
27
|
+
|
28
|
+
def port
|
29
|
+
return @port unless @port.nil?
|
30
|
+
'22'
|
31
|
+
end
|
32
|
+
|
33
|
+
def username
|
34
|
+
return @username unless @username.nil?
|
35
|
+
'root'
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_yaml
|
39
|
+
yaml = ""
|
40
|
+
yaml += "'address': '"+address+"'\n" unless @address.nil?
|
41
|
+
yaml += "'port': '"+port+"'\n" unless @port.nil?
|
42
|
+
yaml += "'username': '"+username+"'\n" unless @username.nil?
|
43
|
+
yaml += script_hooks_to_yaml
|
44
|
+
yaml += super
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_hash( hash )
|
48
|
+
script_hooks_from_hash( hash )
|
49
|
+
hash.each do | name, data |
|
50
|
+
if( name == "port" )
|
51
|
+
self.port = data
|
52
|
+
next
|
53
|
+
end
|
54
|
+
if( name == "address" )
|
55
|
+
self.address = data
|
56
|
+
next
|
57
|
+
end
|
58
|
+
if( name == "username" )
|
59
|
+
self.username= data
|
60
|
+
next
|
61
|
+
end
|
62
|
+
#no matching keyword -> its a partition:
|
63
|
+
p = Arver::Partition.new( name, self )
|
64
|
+
p.from_hash( data )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_action( action )
|
69
|
+
action.pre_host( self )
|
70
|
+
super
|
71
|
+
action.post_host( self )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Arver
|
2
|
+
class Hostgroup
|
3
|
+
|
4
|
+
include PartitionHierarchyNode
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
super(name, Arver::Config.instance.tree)
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_hash hash
|
11
|
+
hash.each do | name, data |
|
12
|
+
h = Arver::Host.new( name, self )
|
13
|
+
h.from_hash( data )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Arver
|
2
|
+
class InfoAction < Action
|
3
|
+
def initialize( target_list )
|
4
|
+
super( target_list )
|
5
|
+
self.open_keystore
|
6
|
+
end
|
7
|
+
|
8
|
+
def pre_host( host )
|
9
|
+
Arver::Log.info( "\n-- "+host.path+":" )
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_partition(partition)
|
13
|
+
info = {}
|
14
|
+
(caller = Arver::LuksWrapper.dump(partition)).execute
|
15
|
+
caller.output.each_line do |line|
|
16
|
+
next unless line =~ /^[A-Z].*: .*$/
|
17
|
+
info.store(*line.split(':',2).collect{|f| f.strip })
|
18
|
+
end
|
19
|
+
Arver::Log.info(" #{sprintf("%0-20s",partition.name.first(20))}: #{sprintf("%0-40s",partition.device_path.first(40))}: Slots: #{(0..7).map{|i| info["Key Slot #{i}"] == 'ENABLED' ? 'X' : '_'}.join}; LUKSv#{info['Version']}; Cypher: #{info['Cipher name']}:#{info['Cipher mode']}:#{info['Hash spec']}; UUID=#{info['UUID']}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|