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.
Files changed (42) hide show
  1. data/CHANGELOG.textile +9 -0
  2. data/README.textile +120 -0
  3. data/bin/arver +10 -0
  4. data/lib/arver.rb +6 -0
  5. data/lib/arver/action.rb +78 -0
  6. data/lib/arver/adduser_action.rb +54 -0
  7. data/lib/arver/bootstrap.rb +35 -0
  8. data/lib/arver/cli.rb +133 -0
  9. data/lib/arver/close_action.rb +61 -0
  10. data/lib/arver/command_wrapper.rb +42 -0
  11. data/lib/arver/config.rb +54 -0
  12. data/lib/arver/create_action.rb +58 -0
  13. data/lib/arver/deluser_action.rb +34 -0
  14. data/lib/arver/gc_action.rb +12 -0
  15. data/lib/arver/gpg_key_manager.rb +80 -0
  16. data/lib/arver/host.rb +74 -0
  17. data/lib/arver/hostgroup.rb +17 -0
  18. data/lib/arver/info_action.rb +22 -0
  19. data/lib/arver/io_logger.rb +34 -0
  20. data/lib/arver/key_generator.rb +29 -0
  21. data/lib/arver/key_info_action.rb +29 -0
  22. data/lib/arver/key_saver.rb +128 -0
  23. data/lib/arver/keystore.rb +70 -0
  24. data/lib/arver/list_action.rb +7 -0
  25. data/lib/arver/local_config.rb +41 -0
  26. data/lib/arver/log.rb +39 -0
  27. data/lib/arver/log_levels.rb +9 -0
  28. data/lib/arver/luks_wrapper.rb +29 -0
  29. data/lib/arver/node_with_script_hooks.rb +25 -0
  30. data/lib/arver/open_action.rb +70 -0
  31. data/lib/arver/partition.rb +53 -0
  32. data/lib/arver/partition_hierarchy_node.rb +112 -0
  33. data/lib/arver/runtime_config.rb +22 -0
  34. data/lib/arver/ssh_command_wrapper.rb +21 -0
  35. data/lib/arver/string.rb +8 -0
  36. data/lib/arver/target_list.rb +33 -0
  37. data/lib/arver/test_config_loader.rb +21 -0
  38. data/lib/arver/test_partition.rb +9 -0
  39. data/lib/arver/tree.rb +32 -0
  40. data/lib/arver/version.rb +3 -0
  41. data/man/arver.5 +429 -0
  42. 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
@@ -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,12 @@
1
+ module Arver
2
+ class GCAction < Action
3
+ def initialize( target_list )
4
+ super( target_list )
5
+ self.open_keystore
6
+ end
7
+
8
+ def post_action
9
+ keystore.save
10
+ end
11
+ end
12
+ 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
+
@@ -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