kytoon 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.document +5 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +29 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +81 -0
  6. data/Rakefile +35 -0
  7. data/VERSION +1 -0
  8. data/config/server_group_vpc.json +14 -0
  9. data/config/server_group_xen.json +24 -0
  10. data/lib/kytoon.rb +8 -0
  11. data/lib/kytoon/providers/cloud_servers_vpc.rb +6 -0
  12. data/lib/kytoon/providers/cloud_servers_vpc/client.rb +197 -0
  13. data/lib/kytoon/providers/cloud_servers_vpc/connection.rb +148 -0
  14. data/lib/kytoon/providers/cloud_servers_vpc/server.rb +121 -0
  15. data/lib/kytoon/providers/cloud_servers_vpc/server_group.rb +401 -0
  16. data/lib/kytoon/providers/cloud_servers_vpc/ssh_public_key.rb +29 -0
  17. data/lib/kytoon/providers/cloud_servers_vpc/vpn_network_interface.rb +33 -0
  18. data/lib/kytoon/providers/xenserver.rb +1 -0
  19. data/lib/kytoon/providers/xenserver/server_group.rb +371 -0
  20. data/lib/kytoon/server_group.rb +46 -0
  21. data/lib/kytoon/ssh_util.rb +23 -0
  22. data/lib/kytoon/util.rb +118 -0
  23. data/lib/kytoon/version.rb +8 -0
  24. data/lib/kytoon/vpn/vpn_connection.rb +46 -0
  25. data/lib/kytoon/vpn/vpn_network_manager.rb +237 -0
  26. data/lib/kytoon/vpn/vpn_openvpn.rb +112 -0
  27. data/lib/kytoon/xml_util.rb +15 -0
  28. data/rake/kytoon.rake +115 -0
  29. data/test/client_test.rb +111 -0
  30. data/test/helper.rb +18 -0
  31. data/test/server_group_test.rb +253 -0
  32. data/test/server_test.rb +69 -0
  33. data/test/ssh_util_test.rb +22 -0
  34. data/test/test_helper.rb +194 -0
  35. data/test/test_kytoon.rb +7 -0
  36. data/test/util_test.rb +23 -0
  37. data/test/vpn_network_manager_test.rb +40 -0
  38. metadata +247 -0
@@ -0,0 +1,23 @@
1
+ module Kytoon
2
+
3
+ module SshUtil
4
+
5
+ def self.remove_known_hosts_ip(ip, known_hosts_file=File.join(ENV['HOME'], ".ssh", "known_hosts"))
6
+
7
+ return if ip.nil? or ip.empty?
8
+ return if not FileTest.exist?(known_hosts_file)
9
+
10
+ existing=IO.read(known_hosts_file)
11
+ File.open(known_hosts_file, 'w') do |file|
12
+ existing.each_line do |line|
13
+ if not line =~ Regexp.new("^#{ip}.*$") then
14
+ file.write(line)
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,118 @@
1
+ require 'yaml'
2
+ require 'socket'
3
+ require 'kytoon/server_group'
4
+
5
+ module Kytoon
6
+
7
+ module Util
8
+
9
+ SSH_OPTS="-o StrictHostKeyChecking=no"
10
+
11
+ @@configs=nil
12
+
13
+ def self.hostname
14
+ Socket.gethostname
15
+ end
16
+
17
+ def self.load_configs
18
+
19
+ return @@configs if not @@configs.nil?
20
+
21
+ config_file=ENV['KYTOON_CONFIG_FILE']
22
+ if config_file.nil? then
23
+
24
+ config_file=ENV['HOME']+File::SEPARATOR+".kytoon.conf"
25
+ if not File.exists?(config_file) then
26
+ config_file="/etc/kytoon.conf"
27
+ end
28
+
29
+ end
30
+
31
+ if File.exists?(config_file) then
32
+ configs=YAML.load_file(config_file)
33
+ raise_if_nil_or_empty(configs, "cloud_servers_vpc_url")
34
+ raise_if_nil_or_empty(configs, "cloud_servers_vpc_username")
35
+ raise_if_nil_or_empty(configs, "cloud_servers_vpc_password")
36
+ @@configs=configs
37
+ else
38
+ raise "Failed to load kytoon config file. Please configure /etc/kytoon.conf or create a .kytoon.conf config file in your HOME directory."
39
+ end
40
+
41
+ @@configs
42
+
43
+ end
44
+
45
+ def self.load_public_key
46
+
47
+ ssh_dir=ENV['HOME']+File::SEPARATOR+".ssh"+File::SEPARATOR
48
+ if File.exists?(ssh_dir+"id_rsa.pub")
49
+ pubkey=IO.read(ssh_dir+"id_rsa.pub")
50
+ elsif File.exists?(ssh_dir+"id_dsa.pub")
51
+ pubkey=IO.read(ssh_dir+"id_dsa.pub")
52
+ else
53
+ raise "Failed to load SSH key. Please create a SSH public key pair in your HOME directory."
54
+ end
55
+
56
+ pubkey.chomp
57
+
58
+ end
59
+
60
+ def self.raise_if_nil_or_empty(options, key)
61
+ if not options or options[key].nil? or options[key].empty? then
62
+ raise "Please specify a valid #{key.to_s} parameter."
63
+ end
64
+ end
65
+
66
+ def self.remote_exec(script_text, gateway_ip)
67
+ if gateway_ip.nil?
68
+ sg=ServerGroup.get
69
+ gateway_ip=sg.gateway_ip
70
+ end
71
+
72
+ out=%x{
73
+ ssh #{SSH_OPTS} root@#{gateway_ip} bash <<-"REMOTE_EXEC_EOF"
74
+ #{script_text}
75
+ REMOTE_EXEC_EOF
76
+ }
77
+ retval=$?
78
+ if block_given? then
79
+ yield retval.success?, out
80
+ else
81
+ return [retval.success?, out]
82
+ end
83
+ end
84
+
85
+ def self.remote_multi_exec(hosts, script_text, gateway_ip)
86
+
87
+ if gateway_ip.nil?
88
+ sg=ServerGroup.get
89
+ gateway_ip=sg.gateway_ip
90
+ end
91
+
92
+ results = {}
93
+ threads = []
94
+
95
+ hosts.each do |host|
96
+ t = Thread.new do
97
+ out=%x{
98
+ ssh #{SSH_OPTS} root@#{gateway_ip} bash <<-"REMOTE_EXEC_EOF"
99
+ ssh #{host} bash <<-"EOF_HOST"
100
+ #{script_text}
101
+ EOF_HOST
102
+ REMOTE_EXEC_EOF
103
+ }
104
+ retval=$?
105
+ results.store host, [retval.success?, out]
106
+ end
107
+ threads << t
108
+ end
109
+
110
+ threads.each {|t| t.join}
111
+
112
+ return results
113
+
114
+ end
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,8 @@
1
+ module Kytoon
2
+
3
+ class Version
4
+ KYTOON_ROOT = File.dirname(File.expand_path("../", File.dirname(__FILE__)))
5
+ VERSION = IO.read(File.join(KYTOON_ROOT, 'VERSION'))
6
+ end
7
+
8
+ end
@@ -0,0 +1,46 @@
1
+
2
+ module Kytoon
3
+ module Vpn
4
+ class VpnConnection
5
+
6
+ CERT_DIR=File.join(ENV['HOME'], '.pki', 'openvpn')
7
+
8
+ def initialize(group, client = nil)
9
+ @group = group
10
+ @client = client
11
+ end
12
+
13
+ def create_certs
14
+ @ca_cert=get_cfile('ca.crt')
15
+ @client_cert=get_cfile('client.crt')
16
+ @client_key=get_cfile('client.key')
17
+
18
+ vpn_interface = @client.vpn_network_interfaces[0]
19
+
20
+ FileUtils.mkdir_p(get_cfile)
21
+ File::chmod(0700, File.join(ENV['HOME'], '.pki'))
22
+ File::chmod(0700, CERT_DIR)
23
+
24
+ File.open(@ca_cert, 'w') { |f| f.write(vpn_interface.ca_cert) }
25
+ File.open(@client_cert, 'w') { |f| f.write(vpn_interface.client_cert) }
26
+ File.open(@client_key, 'w') do |f|
27
+ f.write(vpn_interface.client_key)
28
+ f.chmod(0600)
29
+ end
30
+ end
31
+
32
+ def delete_certs
33
+ FileUtils.rm_rf(get_cfile)
34
+ end
35
+
36
+ def get_cfile(file = nil)
37
+ if file
38
+ File.join(CERT_DIR, @group.id.to_s, file)
39
+ else
40
+ File.join(CERT_DIR, @group.id.to_s)
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,237 @@
1
+ require 'json'
2
+ require 'builder'
3
+ require 'rexml/document'
4
+ require 'rexml/xpath'
5
+ require 'uuidtools'
6
+ require 'ipaddr'
7
+ require 'fileutils'
8
+ require 'tempfile'
9
+
10
+ module Kytoon
11
+ module Vpn
12
+
13
+ class VpnNetworkManager < VpnConnection
14
+
15
+ def initialize(group, client = nil)
16
+ super(group, client)
17
+ end
18
+
19
+ def connect
20
+ create_certs
21
+ configure_gconf
22
+ puts %x{#{sudo_display} nmcli con up id "VPC Group: #{@group.id}"}
23
+ end
24
+
25
+ def disconnect
26
+ puts %x{#{sudo_display} nmcli con down id "VPC Group: #{@group.id}"}
27
+ end
28
+
29
+ def connected?
30
+ return system("#{sudo_display} nmcli con status | grep -c 'VPC Group: #{@group.id}' &> /dev/null")
31
+ end
32
+
33
+ def clean
34
+ unset_gconf_config
35
+ delete_certs
36
+ end
37
+
38
+ def configure_gconf
39
+
40
+ xml = Builder::XmlMarkup.new
41
+ xml.gconfentryfile do |file|
42
+ file.entrylist({ "base" => "/system/networking/connections/vpc_#{@group.id}"}) do |entrylist|
43
+
44
+ entrylist.entry do |entry|
45
+ entry.key("connection/autoconnect")
46
+ entry.value do |value|
47
+ value.bool("false")
48
+ end
49
+ end
50
+ entrylist.entry do |entry|
51
+ entry.key("connection/id")
52
+ entry.value do |value|
53
+ value.string("VPC Group: #{@group.id}")
54
+ end
55
+ end
56
+ entrylist.entry do |entry|
57
+ entry.key("connection/name")
58
+ entry.value do |value|
59
+ value.string("connection")
60
+ end
61
+ end
62
+ entrylist.entry do |entry|
63
+ entry.key("connection/timestamp")
64
+ entry.value do |value|
65
+ value.string(Time.now.to_i.to_s)
66
+ end
67
+ end
68
+ entrylist.entry do |entry|
69
+ entry.key("connection/type")
70
+ entry.value do |value|
71
+ value.string("vpn")
72
+ end
73
+ end
74
+ entrylist.entry do |entry|
75
+ entry.key("connection/uuid")
76
+ entry.value do |value|
77
+ value.string(UUIDTools::UUID.random_create)
78
+ end
79
+ end
80
+ entrylist.entry do |entry|
81
+ entry.key("ipv4/addresses")
82
+ entry.value do |value|
83
+ value.list("type" => "int") do |list|
84
+ end
85
+ end
86
+ end
87
+ entrylist.entry do |entry|
88
+ entry.key("ipv4/dns")
89
+ entry.value do |value|
90
+ value.list("type" => "int") do |list|
91
+ ip=IPAddr.new(@group.vpn_network.chomp("0")+"1")
92
+ list.value do |lv|
93
+ lv.int(ip_to_integer(ip.to_s))
94
+ end
95
+ end
96
+ end
97
+ end
98
+ entrylist.entry do |entry|
99
+ entry.key("ipv4/dns-search")
100
+ entry.value do |value|
101
+ value.list("type" => "string") do |list|
102
+ list.value do |lv|
103
+ lv.string(@group.domain_name)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ entrylist.entry do |entry|
109
+ entry.key("ipv4/ignore-auto-dns")
110
+ entry.value do |value|
111
+ value.bool("true")
112
+ end
113
+ end
114
+ entrylist.entry do |entry|
115
+ entry.key("ipv4/method")
116
+ entry.value do |value|
117
+ value.string("auto")
118
+ end
119
+ end
120
+ entrylist.entry do |entry|
121
+ entry.key("ipv4/name")
122
+ entry.value do |value|
123
+ value.string("ipv4")
124
+ end
125
+ end
126
+ entrylist.entry do |entry|
127
+ entry.key("ipv4/never-default")
128
+ entry.value do |value|
129
+ value.bool("true")
130
+ end
131
+ end
132
+ entrylist.entry do |entry|
133
+ entry.key("ipv4/routes")
134
+ entry.value do |value|
135
+ value.list("type" => "int") do |list|
136
+ end
137
+ end
138
+ end
139
+ entrylist.entry do |entry|
140
+ entry.key("vpn/ca")
141
+ entry.value do |value|
142
+ value.string(@ca_cert)
143
+ end
144
+ end
145
+ entrylist.entry do |entry|
146
+ entry.key("vpn/cert")
147
+ entry.value do |value|
148
+ value.string(@client_cert)
149
+ end
150
+ end
151
+ entrylist.entry do |entry|
152
+ entry.key("vpn/comp-lzo")
153
+ entry.value do |value|
154
+ value.string("yes")
155
+ end
156
+ end
157
+ entrylist.entry do |entry|
158
+ entry.key("vpn/connection-type")
159
+ entry.value do |value|
160
+ value.string("tls")
161
+ end
162
+ end
163
+ entrylist.entry do |entry|
164
+ entry.key("vpn/key")
165
+ entry.value do |value|
166
+ value.string(@client_key)
167
+ end
168
+ end
169
+ if @group.vpn_proto == "tcp"
170
+ entrylist.entry do |entry|
171
+ entry.key("vpn/proto-tcp")
172
+ entry.value do |value|
173
+ value.string("yes")
174
+ end
175
+ end
176
+ else
177
+ entrylist.entry do |entry|
178
+ entry.key("vpn/proto-udp")
179
+ entry.value do |value|
180
+ value.string("yes")
181
+ end
182
+ end
183
+ end
184
+ if @group.vpn_device == "tap"
185
+ entrylist.entry do |entry|
186
+ entry.key("vpn/tap-dev")
187
+ entry.value do |value|
188
+ value.string("yes")
189
+ end
190
+ end
191
+ end
192
+ entrylist.entry do |entry|
193
+ entry.key("vpn/remote")
194
+ entry.value do |value|
195
+ value.string(@group.gateway_ip)
196
+ end
197
+ end
198
+ entrylist.entry do |entry|
199
+ entry.key("vpn/service-type")
200
+ entry.value do |value|
201
+ value.string("org.freedesktop.NetworkManager.openvpn")
202
+ end
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ Tempfile.open('w') do |f|
209
+ f.write(xml.target!)
210
+ f.flush
211
+ puts %x{gconftool-2 --load #{f.path}}
212
+ end
213
+
214
+ return true
215
+
216
+ end
217
+
218
+ def unset_gconf_config
219
+ puts %x{gconftool-2 --recursive-unset /system/networking/connections/vpc_#{@group.id}}
220
+ end
221
+
222
+ def ip_to_integer(ip_string)
223
+ return 0 if ip_string.nil?
224
+ ip_arr=ip_string.split(".").collect{ |s| s.to_i }
225
+ return ip_arr[0] + ip_arr[1]*2**8 + ip_arr[2]*2**16 + ip_arr[3]*2**24
226
+ end
227
+
228
+ def sudo_display
229
+ if ENV['DISPLAY'].nil? or ENV['DISPLAY'] != ":0.0" then
230
+ "sudo"
231
+ else
232
+ ""
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,112 @@
1
+ module Kytoon
2
+ module Vpn
3
+ class VpnOpenVpn < VpnConnection
4
+
5
+ def initialize(group, client = nil)
6
+ super(group, client)
7
+ end
8
+
9
+ def connect
10
+ create_certs
11
+
12
+ @up_script=get_cfile('up.bash')
13
+ File.open(@up_script, 'w') do |f|
14
+ f << <<EOF_UP
15
+ #!/bin/bash
16
+
17
+ # setup routes
18
+ /sbin/route add #{@group.vpn_network.chomp("0")+"1"} dev \$dev
19
+ /sbin/route add -net #{@group.vpn_network} netmask 255.255.128.0 gw #{@group.vpn_network.chomp("0")+"1"}
20
+
21
+ mv /etc/resolv.conf /etc/resolv.conf.bak
22
+ egrep ^search /etc/resolv.conf.bak | sed -e 's/search /search #{@group.domain_name} /' > /etc/resolv.conf
23
+ echo 'nameserver #{@group.vpn_network.chomp("0")+"1"}' >> /etc/resolv.conf
24
+ grep ^nameserver /etc/resolv.conf.bak >> /etc/resolv.conf
25
+ EOF_UP
26
+ f.chmod(0700)
27
+ end
28
+ @down_script=get_cfile('down.bash')
29
+ File.open(@down_script, 'w') do |f|
30
+ f << <<EOF_DOWN
31
+ #!/bin/bash
32
+ mv /etc/resolv.conf.bak /etc/resolv.conf
33
+ EOF_DOWN
34
+ f.chmod(0700)
35
+ end
36
+
37
+ @config_file=get_cfile('config')
38
+ File.open(@config_file, 'w') do |f|
39
+ f << <<EOF_CONFIG
40
+ client
41
+ dev #{@group.vpn_device}
42
+ proto #{@group.vpn_proto}
43
+
44
+ #Change my.publicdomain.com to your public domain or IP address
45
+ remote #{@group.gateway_ip} 1194
46
+
47
+ resolv-retry infinite
48
+ nobind
49
+ persist-key
50
+ persist-tun
51
+
52
+ script-security 2
53
+
54
+ ca #{@ca_cert}
55
+ cert #{@client_cert}
56
+ key #{@client_key}
57
+
58
+ ns-cert-type server
59
+
60
+ route-nopull
61
+
62
+ comp-lzo
63
+
64
+ verb 3
65
+ up #{@up_script}
66
+ down #{@down_script}
67
+ EOF_CONFIG
68
+ f.chmod(0600)
69
+ end
70
+
71
+ disconnect if File.exist?(get_cfile('openvpn.pid'))
72
+ out=%x{sudo openvpn --config #{@config_file} --writepid #{get_cfile('openvpn.pid')} --daemon}
73
+ retval=$?
74
+ if retval.success? then
75
+ poll_vpn_interface
76
+ puts "OK."
77
+ else
78
+ raise "Failed to create VPN connection: #{out}"
79
+ end
80
+ end
81
+
82
+ def disconnect
83
+ raise "Not running? No pid file found!" unless File.exist?(get_cfile('openvpn.pid'))
84
+ pid = File.read(get_cfile('openvpn.pid')).chomp
85
+ system("sudo kill -TERM #{pid}")
86
+ File.delete(get_cfile('openvpn.pid'))
87
+ end
88
+
89
+ def connected?
90
+ system("/sbin/route -n | grep #{@group.vpn_network.chomp("0")+"1"} &> /dev/null")
91
+ end
92
+
93
+ def clean
94
+ delete_certs
95
+ end
96
+
97
+ private
98
+ def poll_vpn_interface
99
+ interface_name=@group.vpn_device+"0"
100
+ 1.upto(30) do |i|
101
+ break if system("/sbin/ifconfig #{interface_name} > /dev/null 2>&1")
102
+ if i == 30 then
103
+ disconnect
104
+ raise "Failed to connect to VPN."
105
+ end
106
+ sleep 0.5
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+ end