kytoon 1.0.0

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 (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