nub 0.0.96 → 0.0.103
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +168 -4
- data/lib/nub/log.rb +25 -23
- data/lib/nub/net.rb +192 -31
- data/lib/nub/sys.rb +21 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e03ab8b435d9eccad62fe0d1a515377dddf39cdd752e6ae643dd4337bb3a606e
|
4
|
+
data.tar.gz: 4795f2c20f7eeaf1da565f41c783d15b20007307f3cd926bbe28860419511cdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69825e1f9cb11e189c77ab8ca30f9668300759f30f7fa1805153994c5ffb24eb017b7a3f12abd2067cdf1b04110a64b3558c7ff220d1211172060985ea52f7fc
|
7
|
+
data.tar.gz: cbdb16ed3fb4eb6b9f219492d60279d6035c233d1c21802b745079ea3e1d78fe12e0610bcfc0cc9fcfcaf2c18925c2ceff61434df84727f562287e246566da6b
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Collection of ruby utils I've used in several of my projects and wanted re-usabl
|
|
8
8
|
|
9
9
|
### Table of Contents
|
10
10
|
* [Deploy](#deploy)
|
11
|
-
* [Commander](#commander)
|
11
|
+
* [Commander Module](#commander-module)
|
12
12
|
* [Commands](#commands)
|
13
13
|
* [Command Parameters ](#command-paramaters)
|
14
14
|
* [Chained Commands](#chained-commands)
|
@@ -24,7 +24,22 @@ Collection of ruby utils I've used in several of my projects and wanted re-usabl
|
|
24
24
|
* [Help](#help)
|
25
25
|
* [Examples](#examples)
|
26
26
|
* [Indicators](#indicators)
|
27
|
-
* [Config](#config)
|
27
|
+
* [Config Module](#config-module)
|
28
|
+
* [Core Module](#core-module)
|
29
|
+
* [FileUtils Extensions](#fileutils-extensions)
|
30
|
+
* [Hash Module](#hash-module)
|
31
|
+
* [Log Module](#Log-module)
|
32
|
+
* [Module Extensions](#module-extensions)
|
33
|
+
* [Net Module](#net-module)
|
34
|
+
* [Network Namespaces](#network-namespaces)
|
35
|
+
* [Teamviewer Example](#teamviewer-example)
|
36
|
+
* [PIA VPN Example](#pia-vpn-example)
|
37
|
+
* [Network Proxy](#network-proxy)
|
38
|
+
* [Pacman Module](#pacman-module)
|
39
|
+
* [Process Module](#process-module)
|
40
|
+
* [Sys Module](#sys-module)
|
41
|
+
* [ThreadComm Module](#thread-comm-module)
|
42
|
+
* [User Module](#user-module)
|
28
43
|
* [Ruby Gem Creation](#ruby-gem-creation)
|
29
44
|
* [Package Layout](#package-layout)
|
30
45
|
* [Build Gem](#build-gem)
|
@@ -37,7 +52,7 @@ Collection of ruby utils I've used in several of my projects and wanted re-usabl
|
|
37
52
|
## Deploy <a name="deploy"></a>
|
38
53
|
Run: `bundle install --system`
|
39
54
|
|
40
|
-
## Commander <a name="commander"></a>
|
55
|
+
## Commander Module <a name="commander-module"></a>
|
41
56
|
Commander was created mainly because all available options parsers seemed overly complicated and
|
42
57
|
overweight and partly because I enjoyed understanding every bit going into it. Commander offers
|
43
58
|
***git*** or ***kubectl*** like command syntax.
|
@@ -323,7 +338,7 @@ Usage: ./builder build [options]
|
|
323
338
|
-h|--help Print command/options help
|
324
339
|
```
|
325
340
|
|
326
|
-
## Config <a name="config"></a>
|
341
|
+
## Config Module <a name="config-module"></a>
|
327
342
|
Config is a simple YAML wrapper with some extra features. Since it implements the ***Singleton***
|
328
343
|
pattern you can easily use it through out your app without carrying around instances everywhere.
|
329
344
|
It creates config files in memory and saves them to the config ***~/.config*** directory when saved
|
@@ -334,6 +349,155 @@ Initialize once on entry of your app and leverage throughout:
|
|
334
349
|
Config.init("openvpn.yml")
|
335
350
|
```
|
336
351
|
|
352
|
+
## Core Module <a name="core-module"></a>
|
353
|
+
|
354
|
+
## FileUtils Extensions <a name="fileutils-module"></a>
|
355
|
+
|
356
|
+
## Hash Module <a name="hash-module"></a>
|
357
|
+
|
358
|
+
## Log Module <a name="log-module"></a>
|
359
|
+
|
360
|
+
## Module Extensions <a name="module-extensions"></a>
|
361
|
+
|
362
|
+
## Net Module <a name="net-module"></a>
|
363
|
+
The network module is a collection of network related helpers and automation to simplify tasks and
|
364
|
+
encapsulate functionality into reusable components.
|
365
|
+
|
366
|
+
### Network Namespaces <a name="network-namespaces"></a>
|
367
|
+
Linux by default shares a single set of network interfaces and routing table entries, such that an
|
368
|
+
installed application can bind to all interfaces and has access to all other services currently
|
369
|
+
running on the system. Network namespaces implemented in the kernel provide a way to isolate
|
370
|
+
networks and services from each other. This is the technology that docker uses to isolate docker
|
371
|
+
apps from the host and other docker apps.
|
372
|
+
|
373
|
+
Network namespaces provide a way to have different separate virtual interfaces and routing tables
|
374
|
+
that operate independent of each other. They can be easily manipulated via the ***ip netns*** command.
|
375
|
+
|
376
|
+
```bash
|
377
|
+
# Create a new network namespace
|
378
|
+
# ip netns add <namespace>
|
379
|
+
sudo ip netns add foo
|
380
|
+
|
381
|
+
# List network namespaces
|
382
|
+
ip netns list
|
383
|
+
```
|
384
|
+
|
385
|
+
Once a network namespace has been created you need to configure how it connected to the host. This
|
386
|
+
can be done by creating a pair of virtual Ethernet ***veth*** interfaces and assigning them to the
|
387
|
+
new network namespace as by default they will be assigned to the root namespace. The root namespace
|
388
|
+
or global namespace is the default namespace used by the host for regular networking.
|
389
|
+
|
390
|
+
```bash
|
391
|
+
# Create veth pair veth1 and veth2
|
392
|
+
sudo ip link add veth1 type veth peer name veth2
|
393
|
+
|
394
|
+
# Verify veth pair creation and view their relationship
|
395
|
+
# both are part of the root namespace by default
|
396
|
+
ip a
|
397
|
+
# veth1@veth2: <BROADCAST,MULTICAST,M-DOWN>
|
398
|
+
# veth2@veth1: <BROADCAST,MULTICAST,M-DOWN>
|
399
|
+
|
400
|
+
# Connect foo namespace to root namespace by assigning half the veth pair to the foo namespace
|
401
|
+
sudo ip link set veth2 netns foo
|
402
|
+
|
403
|
+
# Verify that the root namespace listing no longer shows veth2
|
404
|
+
# notice also that the relationship of veth1 has changed
|
405
|
+
ip a
|
406
|
+
# veth1@if4: <BROADCAST,MULTICAST>
|
407
|
+
|
408
|
+
# Verify that the foo namespace now owns veth2
|
409
|
+
sudo ip netns exec foo ip a
|
410
|
+
# lo: <LOOPBACK>
|
411
|
+
# veth2@if5: <BROADCAST,MULTICAST>
|
412
|
+
|
413
|
+
# Assign IPv4 address to the new veth pair
|
414
|
+
sudo ifconfig veth1 192.168.100.1/24 up
|
415
|
+
sudo ip netns exec foo ifconfig veth2 192.168.100.2/24 up
|
416
|
+
|
417
|
+
# Verify that the assigned ips took
|
418
|
+
ip a
|
419
|
+
# inet 192.168.100.1/24 brd 192.168.100.255 scope global veth1
|
420
|
+
|
421
|
+
sudo ip netns exec foo ip a
|
422
|
+
# inet 192.168.100.2/24 brd 192.168.100.255 scope global veth2
|
423
|
+
|
424
|
+
# Verify connectivity between veth pair
|
425
|
+
sudo ping 192.168.100.2
|
426
|
+
# 64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=0.070 ms
|
427
|
+
|
428
|
+
sudo ip netns exec foo ping 192.168.100.1
|
429
|
+
# 64 bytes from 192.168.100.1: icmp_seq=1 ttl=64 time=0.080 ms
|
430
|
+
```
|
431
|
+
|
432
|
+
Now we have connectivity between our veth pair across network namespaces. Because
|
433
|
+
***192.168.100.0/24*** network, that we configured the veth pair on, is a separate network from the
|
434
|
+
host any applications running within the new network namespace will not have connectivity to
|
435
|
+
anything else on your host or networks currently including external access to the internet.
|
436
|
+
|
437
|
+
```bash
|
438
|
+
# Prove out isolation
|
439
|
+
sudo ping www.google.com
|
440
|
+
# 64 bytes from -----.1e100.net (172.217.1.196): icmp_seq=1 ttl=56 time=13.3 ms
|
441
|
+
|
442
|
+
sudo ip netns exec foo ping www.google.com
|
443
|
+
# connect: Network is unreachable
|
444
|
+
```
|
445
|
+
|
446
|
+
In the following sub sections I'll show you how to automated this compliated setup using the
|
447
|
+
***Net*** ruby module.
|
448
|
+
|
449
|
+
#### TeamViewer Example <a name="teamviewer-example"></a>
|
450
|
+
In this example I'll be showing you how to isolate Teamviewer such that Teamviewer is only able to
|
451
|
+
bind to the veth2 IPv4 address that we create for it rather than all network interfaces on the host.
|
452
|
+
This will allow you to have Teamviewer running and accessible from your network facing IP but also
|
453
|
+
to be able to SSH port forward other Teamviewer instances to your loopback interface or other veth
|
454
|
+
addresses.
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
WIP
|
458
|
+
```
|
459
|
+
|
460
|
+
#### PIA VPN Example <a name="pia-vpn-example"></a>
|
461
|
+
```ruby
|
462
|
+
WIP
|
463
|
+
|
464
|
+
Example1: Network namespace with access only to the 192.168.100.0 network which only has the
|
465
|
+
host address 192.168.100.1 available which only has access to the internet via the PIA VPN.
|
466
|
+
|
467
|
+
namespace = 'pia'
|
468
|
+
host_veth = Veth.new('veth1', '192.168.100.1')
|
469
|
+
guest_veth = Veth.new('veth2', '192.168.100.2')
|
470
|
+
network = Network.new('192.168.100.0', '24', ['209.222.18.222', '209.222.18.218'])
|
471
|
+
```
|
472
|
+
|
473
|
+
### Network Proxy <a name="network-proxy"></a>
|
474
|
+
The Net module provides simple access to the system proxy environment variables.
|
475
|
+
|
476
|
+
```ruby
|
477
|
+
Net.proxy.ftp
|
478
|
+
Net.proxy.http
|
479
|
+
Net.proxy.https
|
480
|
+
Net.proxy.no
|
481
|
+
Net.proxy.uri
|
482
|
+
Net.proxy.port
|
483
|
+
|
484
|
+
# Simple way to check if a proxy is set
|
485
|
+
Net.proxy?
|
486
|
+
|
487
|
+
# Bash compatible string to use to insert proxy into commands
|
488
|
+
Net.proxy_export
|
489
|
+
```
|
490
|
+
|
491
|
+
## Pacman Module <a name="pacman-module"></a>
|
492
|
+
|
493
|
+
## Process Module <a name="process-module"></a>
|
494
|
+
|
495
|
+
## Sys Module <a name="sys-module"></a>
|
496
|
+
|
497
|
+
## ThreadComm Module <a name="threadcomm-module"></a>
|
498
|
+
|
499
|
+
## User Module <a name="user-module"></a>
|
500
|
+
|
337
501
|
## Ruby Gem Creation <a name="ruby-gem-creation"></a>
|
338
502
|
http://guides.rubygems.org/make-your-own-gem/
|
339
503
|
|
data/lib/nub/log.rb
CHANGED
@@ -25,6 +25,7 @@ require 'ostruct'
|
|
25
25
|
require 'colorize'
|
26
26
|
require_relative 'sys'
|
27
27
|
require_relative 'core'
|
28
|
+
require_relative 'module'
|
28
29
|
|
29
30
|
LogLevel = OpenStruct.new({
|
30
31
|
error: 0,
|
@@ -38,34 +39,32 @@ LogLevel = OpenStruct.new({
|
|
38
39
|
# Uses Mutex.synchronize where required to provide thread safety.
|
39
40
|
module Log
|
40
41
|
extend self
|
41
|
-
|
42
|
+
mattr_accessor(:id, :path, :level)
|
43
|
+
|
44
|
+
@@level = 3
|
45
|
+
@@path = nil
|
42
46
|
@@_queue = nil
|
43
47
|
@@_stdout = true
|
44
48
|
@@_monitor = Monitor.new
|
45
49
|
|
46
|
-
# Public properties
|
47
|
-
class << self
|
48
|
-
attr_reader(:id, :path)
|
49
|
-
end
|
50
|
-
|
51
50
|
# Singleton's init method can be called multiple times to reset.
|
52
51
|
# @param path [String] path to log file
|
53
52
|
# @param queue [Bool] use a queue as well
|
54
53
|
# @param stdout [Bool] turn on or off stdout
|
55
54
|
# @param level [LogLevel] level at which to log
|
56
55
|
def init(path:nil, level:LogLevel.debug, queue:false, stdout:true)
|
57
|
-
|
56
|
+
self.id ||= 'singleton'.object_id
|
57
|
+
self.path = path ? File.expand_path(path) : nil
|
58
|
+
self.level = level
|
58
59
|
|
59
|
-
@path = path ? File.expand_path(path) : nil
|
60
|
-
@@_level = level
|
61
60
|
@@_queue = queue ? Queue.new : nil
|
62
61
|
@@_stdout = stdout
|
63
62
|
$stdout.sync = true
|
64
63
|
|
65
64
|
# Open log file creating as needed
|
66
|
-
if
|
67
|
-
FileUtils.mkdir_p(File.dirname(
|
68
|
-
@file = File.open(
|
65
|
+
if self.path
|
66
|
+
FileUtils.mkdir_p(File.dirname(self.path)) if !File.exist?(File.dirname(self.path))
|
67
|
+
@file = File.open(self.path, 'a')
|
69
68
|
@file.sync = true
|
70
69
|
end
|
71
70
|
end
|
@@ -74,7 +73,7 @@ module Log
|
|
74
73
|
def call_details
|
75
74
|
@@_monitor.synchronize{
|
76
75
|
|
77
|
-
# Skip first 3 on stack (i.e. 0 = block in call_details, 1 = synchronize, 2 = call_detail)
|
76
|
+
# Skip first 3 on stack (i.e. 0 = block in call_details, 1 = synchronize, 2 = call_detail)
|
78
77
|
stack = caller_locations(3, 20)
|
79
78
|
|
80
79
|
# Skip past any calls in 'log.rb' or 'monitor.rb'
|
@@ -124,7 +123,7 @@ module Log
|
|
124
123
|
|
125
124
|
# Handle output
|
126
125
|
if !str.empty?
|
127
|
-
@file << str.strip_color if
|
126
|
+
@file << str.strip_color if self.path
|
128
127
|
@@_queue << str if @@_queue
|
129
128
|
$stdout.print(str) if @@_stdout
|
130
129
|
end
|
@@ -154,7 +153,7 @@ module Log
|
|
154
153
|
end
|
155
154
|
|
156
155
|
# Handle output
|
157
|
-
@file.puts(str.strip_color) if
|
156
|
+
@file.puts(str.strip_color) if self.path
|
158
157
|
@@_queue << "#{str}\n" if @@_queue
|
159
158
|
$stdout.puts(str) if @@_stdout
|
160
159
|
|
@@ -167,18 +166,19 @@ module Log
|
|
167
166
|
opts = args.find{|x| x.is_a?(Hash)}
|
168
167
|
opts[:loc] = true and opts[:type] = 'E' if opts
|
169
168
|
args << {:loc => true, :type => 'E'} if !opts
|
170
|
-
|
171
|
-
return self.puts(*args)
|
169
|
+
newline = (opts && opts.key?(:newline)) ? opts[:newline] : true
|
170
|
+
return newline ? self.puts(*args) : self.print(*args)
|
172
171
|
}
|
173
172
|
end
|
174
173
|
|
175
174
|
def warn(*args)
|
176
175
|
@@_monitor.synchronize{
|
177
|
-
if LogLevel.warn <=
|
176
|
+
if LogLevel.warn <= self.level
|
178
177
|
opts = args.find{|x| x.is_a?(Hash)}
|
179
178
|
opts[:type] = 'W' if opts
|
180
179
|
args << {:type => 'W'} if !opts
|
181
|
-
|
180
|
+
newline = (opts && opts.key?(:newline)) ? opts[:newline] : true
|
181
|
+
return newline ? self.puts(*args) : self.print(*args)
|
182
182
|
end
|
183
183
|
return true
|
184
184
|
}
|
@@ -186,11 +186,12 @@ module Log
|
|
186
186
|
|
187
187
|
def info(*args)
|
188
188
|
@@_monitor.synchronize{
|
189
|
-
if LogLevel.info <=
|
189
|
+
if LogLevel.info <= self.level
|
190
190
|
opts = args.find{|x| x.is_a?(Hash)}
|
191
191
|
opts[:type] = 'I' if opts
|
192
192
|
args << {:type => 'I'} if !opts
|
193
|
-
|
193
|
+
newline = (opts && opts.key?(:newline)) ? opts[:newline] : true
|
194
|
+
return newline ? self.puts(*args) : self.print(*args)
|
194
195
|
end
|
195
196
|
return true
|
196
197
|
}
|
@@ -198,11 +199,12 @@ module Log
|
|
198
199
|
|
199
200
|
def debug(*args)
|
200
201
|
@@_monitor.synchronize{
|
201
|
-
if LogLevel.debug <=
|
202
|
+
if LogLevel.debug <= self.level
|
202
203
|
opts = args.find{|x| x.is_a?(Hash)}
|
203
204
|
opts[:type] = 'D' if opts
|
204
205
|
args << {:type => 'D'} if !opts
|
205
|
-
|
206
|
+
newline = (opts && opts.key?(:newline)) ? opts[:newline] : true
|
207
|
+
return newline ? self.puts(*args) : self.print(*args)
|
206
208
|
end
|
207
209
|
return true
|
208
210
|
}
|
data/lib/nub/net.rb
CHANGED
@@ -19,12 +19,18 @@
|
|
19
19
|
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
20
|
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
#SOFTWARE.
|
22
|
+
|
22
23
|
require 'ostruct'
|
24
|
+
require_relative 'log'
|
25
|
+
require_relative 'sys'
|
26
|
+
require_relative 'module'
|
27
|
+
|
28
|
+
# Collection of network related helpers
|
29
|
+
module Net
|
30
|
+
extend self
|
31
|
+
mattr_accessor(:agents)
|
23
32
|
|
24
|
-
|
25
|
-
module Net
|
26
|
-
@@_proxy = nil
|
27
|
-
@@_agents = OpenStruct.new({
|
33
|
+
@@agents = OpenStruct.new({
|
28
34
|
windows_ie_6: 'Windows IE 6',
|
29
35
|
windows_ie_7: 'Windows IE 7',
|
30
36
|
windows_mozilla: 'Windows Mozilla',
|
@@ -37,40 +43,195 @@ module Net
|
|
37
43
|
iphone: 'iPhone'
|
38
44
|
})
|
39
45
|
|
40
|
-
#
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def self.get_proxy
|
51
|
-
@@_proxy = {
|
52
|
-
'ftp_proxy' => ENV['ftp_proxy'],
|
53
|
-
'http_proxy' => ENV['http_proxy'],
|
54
|
-
'https_proxy' => ENV['https_proxy'],
|
55
|
-
'no_proxy' => ENV['no_proxy']
|
56
|
-
}
|
46
|
+
# Get fresh proxy from environment
|
47
|
+
def proxy
|
48
|
+
return OpenStruct.new({
|
49
|
+
ftp: ENV['ftp_proxy'],
|
50
|
+
http: ENV['http_proxy'],
|
51
|
+
https: ENV['https_proxy'],
|
52
|
+
no: ENV['no_proxy'],
|
53
|
+
uri: ENV['http_proxy'] ? ENV['http_proxy'].split(':')[0..-2] * ":" : nil,
|
54
|
+
port: ENV['http_proxy'] ? ENV['http_proxy'].split(':').last : nil
|
55
|
+
})
|
57
56
|
end
|
58
57
|
|
59
|
-
#
|
60
|
-
def
|
61
|
-
|
62
|
-
return proxy_exist? ? (@@_proxy.map{|k,v| "export #{k}=#{v}"} * ';') + ";" : nil
|
58
|
+
# Check if a proxy is set
|
59
|
+
def proxy?
|
60
|
+
return !self.proxy.http.nil?
|
63
61
|
end
|
64
62
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
# Get a shell export string for proxies
|
64
|
+
# @param proxy [String] to use rather than default
|
65
|
+
def proxy_export(proxy:nil)
|
66
|
+
if proxy
|
67
|
+
({'ftp_proxy' => proxy,
|
68
|
+
'http_proxy' => proxy,
|
69
|
+
'https_proxy' => proxy
|
70
|
+
}.map{|k,v| "export #{k}=#{v}"} * ';') + ";"
|
71
|
+
elsif self.proxy?
|
72
|
+
(self.proxy.to_h.map{|k,v| (![:uri, :port].include?(k) && v) ? "export #{k}_proxy=#{v}" : nil}.compact * ';') + ";"
|
73
|
+
else
|
74
|
+
return nil
|
75
|
+
end
|
69
76
|
end
|
70
77
|
|
71
78
|
# Check if the system is configured for the kernel to forward ip traffic
|
72
|
-
def
|
73
|
-
return
|
79
|
+
def ip_forward?
|
80
|
+
return File.read('/proc/sys/net/ipv4/ip_forward').include?('1')
|
81
|
+
end
|
82
|
+
|
83
|
+
# ----------------------------------------------------------------------------
|
84
|
+
# Namespace related helpers
|
85
|
+
# ----------------------------------------------------------------------------
|
86
|
+
|
87
|
+
# Virtual Ethernet NIC object
|
88
|
+
# @param name [String] of the veth
|
89
|
+
# @param ip [String] of the veth
|
90
|
+
# @param nic [String] pattern matching nic e.g. en+
|
91
|
+
Veth = Struct.new(:name, :ip, :nic)
|
92
|
+
|
93
|
+
# Network object
|
94
|
+
# @param ip [String] of the network
|
95
|
+
# @param cidr [String] of the network
|
96
|
+
# @param nameservers [Array[String]] to use for new network
|
97
|
+
Network = Struct.new(:ip, :cidr, :nameservers)
|
98
|
+
|
99
|
+
# Check that the namespace has connectivity to the outside world
|
100
|
+
# using a simple curl on google
|
101
|
+
# @param namespace [String] name to use when creating it
|
102
|
+
# @param proxy [String] to use rather than default
|
103
|
+
def namespace_connectivity?(namespace, proxy:nil)
|
104
|
+
success = false
|
105
|
+
Log.info("Checking namespace #{namespace.colorize(:cyan)} for connectivity to google.com", newline:false)
|
106
|
+
|
107
|
+
if File.exists?(File.join("/var/run/netns", namespace))
|
108
|
+
ping = 'curl -sL -w "%{http_code}" http://www.google.com -o /dev/null'
|
109
|
+
return Sys.exec_status("ip netns exec #{namespace} bash -c '#{self.proxy_export(proxy)}#{ping}'", die:false, check:"200")
|
110
|
+
else
|
111
|
+
Sys.exec_status(":", die:false, check:"200")
|
112
|
+
Log.warn("Namespace #{namespace} doesn't exist!")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Create a network namespace with the given name
|
117
|
+
# @param namespace [String] name to use when creating it
|
118
|
+
# @param host_veth [Veth] describes the veth to create for the host side
|
119
|
+
# @param guest_veth [Veth] describes the veth to create for the guest side
|
120
|
+
# @param network [Network] describes the network to share
|
121
|
+
def create_namespace(namespace, host_veth, guest_veth, network)
|
122
|
+
namespace_conf = File.join("/etc/netns", namespace)
|
123
|
+
|
124
|
+
# Ensure namespace i.e. /var/run/netns/<namespace> exists
|
125
|
+
if !File.exists?(File.join("/var/run/netns", namespace))
|
126
|
+
Log.info("Creating VPN Namespace #{namespace.colorize(:cyan)}", newline:false)
|
127
|
+
Sys.exec_status("ip netns add #{namespace}")
|
128
|
+
end
|
129
|
+
|
130
|
+
# Ensure loopback device is running inside the pnamespace
|
131
|
+
if `ip netns exec #{namespace} ip a`.include?("state DOWN")
|
132
|
+
Log.info("Start loopback interface in namespace", newline:false)
|
133
|
+
Sys.exec_status("ip netns exec #{namespace} ip link set lo up")
|
134
|
+
end
|
135
|
+
|
136
|
+
# Create a virtual ethernet pair to communicate across namespaces
|
137
|
+
# by default they will both be in the root namespace until one is assigned to another
|
138
|
+
# e.g. host:192.168.100.1 and guest:192.168.100.2 communicating in network:192.168.100.0
|
139
|
+
if !`ip a`.include?(host_veth.name)
|
140
|
+
Log.info("Create vpn veths #{host_veth.name.colorize(:cyan)} for #{'root'.colorize(:cyan)}
|
141
|
+
and #{guest_veth.name.colorize(:cyan)} for #{namespace.colorize(:cyan)}", newline:false)
|
142
|
+
Sys.exec_status("ip link add #{host_veth.name} type veth peer name #{guest_veth.name}")
|
143
|
+
Log.info("Assign veth #{guest_veth.name.colorize(:cyan)} to namespace #{namespace.colorize(:cyan)}", newline:false)
|
144
|
+
Sys.exec_status("ip link set #{guest_veth.name} netns #{namespace}")
|
145
|
+
end
|
146
|
+
|
147
|
+
# Assign IPv4 addresses and start up the new veth interfaces
|
148
|
+
# sudo ping #{host_veth.ip} and sudo netns exec #{namespace} ping #{guest_veth.ip} should work now
|
149
|
+
if !`ip a`.include?(host_veth.ip)
|
150
|
+
Log.info("Assign ip #{host_veth.ip.colorize(:cyan)} and start #{host_veth.ip.colorize(:cyan)}", newline:false)
|
151
|
+
Sys.exec_status("ifconfig #{host_veth.name} #{File.join(host_veth.ip, nework.cidr)} up")
|
152
|
+
end
|
153
|
+
if !`ip netns exec #{namespace} ip a`.include?(guest_veth.ip)
|
154
|
+
Log.info("Assign ip #{guest_veth.ip.colorize(:cyan)} and start #{guest_veth.ip.colorize(:cyan)}", newline:false)
|
155
|
+
Sys.exec_status("ip netns exec #{namespace} ifconfig #{guest_veth.name} #{File.join(guest_veth.ip, nework.cidr)} up")
|
156
|
+
end
|
157
|
+
|
158
|
+
# Share internet access on host with namespace
|
159
|
+
# Note: to see current forward rules use: iptables -S
|
160
|
+
if !`ip netns exec #{namespace} ip route`.include?('default')
|
161
|
+
Log.info("Set default route for traffic leaving namespace to #{host_veth.ip.colorize(:cyan)}", newline:false)
|
162
|
+
Sys.exec_status("ip netns exec #{namespace} ip route add default via #{host_veth.ip} dev #{guest_veth.name}")
|
163
|
+
end
|
164
|
+
if !`iptables -t nat -S`.include?(File.join(nework.ip, nework.cidr))
|
165
|
+
Log.info("Enable NAT on host for vpn net #{File.join(nework.ip, nework.cidr).colorize(:cyan)}", newline:false)
|
166
|
+
Sys.exec_status("iptables -t nat -A POSTROUTING -s #{File.join(nework.ip, nework.cidr)} -o #{host_veth.nic} -j MASQUERADE")
|
167
|
+
end
|
168
|
+
if !`iptables -S`.include?("-A FORWARD -i #{host_veth.nic}")
|
169
|
+
Log.info("Allow forwarding to #{namespace.colorize(:cyan)}", newline:false)
|
170
|
+
Sys.exec_status("iptables -A FORWARD -i #{host_veth.nic} -o #{host_veth.name} -j ACCEPT")
|
171
|
+
end
|
172
|
+
if !`iptables -S`.include?("-A FORWARD -i #{host_veth.name}")
|
173
|
+
Log.info("Allow forwarding from #{namespace.colorize(:cyan)}", newline:false)
|
174
|
+
Sys.exec_status("iptables -A FORWARD -i #{host_veth.name} -o #{host_veth.nic} -j ACCEPT")
|
175
|
+
end
|
176
|
+
|
177
|
+
# Configure nameserver to use in VPN namespace
|
178
|
+
namespace_conf = File.join("/etc/netns", namespace)
|
179
|
+
if !File.exists?(namespace_conf) && network.nameservers
|
180
|
+
Log.info("Creating nameserver config #{namespace_conf}", newline:false)
|
181
|
+
Sys.exec_status("mkdir -p #{namespace_conf}")
|
182
|
+
network.nameservers.each{|x|
|
183
|
+
Log.info("Adding nameserver #{x.colorize(:cyan)} to config", newline:false)
|
184
|
+
Sys.exec_status("echo 'nameserver #{x}' >> /etc/netns/#{namespace}/resolv.conf")
|
185
|
+
}
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Delete the given network namespace
|
190
|
+
# @param namespace [String] name to use when creating it
|
191
|
+
# @param host_veth [Veth] describes the veth to create for the host side
|
192
|
+
# @param network [Network] describes the network to share
|
193
|
+
def delete_namespace(namespace, host_veth, network)
|
194
|
+
|
195
|
+
# Remove nameserver config for vpn
|
196
|
+
namespace_conf = File.join("/etc/netns", namespace)
|
197
|
+
if File.exists?(namespace_conf)
|
198
|
+
Log.info("Removing nameserver config #{namespace_conf}", newline:false)
|
199
|
+
Sys.exec_status("rm -rf #{namespace_conf}")
|
200
|
+
end
|
201
|
+
|
202
|
+
# Remove NAT and iptables forwarding allowances
|
203
|
+
if `iptables -t nat -S`.include?(File.join(network.ip, network.cidr))
|
204
|
+
Log.info("Removing NAT on host for vpn net #{File.join(network.ip, network.cidr).colorize(:cyan)}", newline:false)
|
205
|
+
Sys.exec_status("iptables -t nat -D POSTROUTING -s #{File.join(network.ip, network.cidr)} -o #{host_veth.nic} -j MASQUERADE")
|
206
|
+
end
|
207
|
+
if `iptables -S`.include?("-A FORWARD -i #{host_veth.nic}")
|
208
|
+
Log.info("Allow forwarding to #{namespace.colorize(:cyan)}", newline:false)
|
209
|
+
Sys.exec_status("iptables -D FORWARD -i #{host_veth.nic} -o #{host_veth.name} -j ACCEPT")
|
210
|
+
end
|
211
|
+
if `iptables -S`.include?("-A FORWARD -i #{host_veth.name}")
|
212
|
+
Log.info("Allow forwarding from #{namespace.colorize(:cyan)}", newline:false)
|
213
|
+
Sys.exec_status("iptables -D FORWARD -i #{host_veth.name} -o #{host_veth.nic} -j ACCEPT")
|
214
|
+
end
|
215
|
+
|
216
|
+
# Remove virtual ethernet interfaces
|
217
|
+
if `ip a`.include?(host_veth.name)
|
218
|
+
Log.info("Removing veth interface #{host_veth.name.colorize(:cyan)} for vpn", newline:false)
|
219
|
+
Sys.exec_status("ip link delete #{host_veth.name}")
|
220
|
+
end
|
221
|
+
|
222
|
+
# Remove namespace for vpn
|
223
|
+
if File.exists?(File.join("/var/run/netns", namespace))
|
224
|
+
Log.info("Removing namespace #{namespace.colorize(:cyan)}", newline:false)
|
225
|
+
Sys.exec_status("ip netns delete #{namespace}")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Execute in the namespace
|
230
|
+
# @param namespace [String] to execut within
|
231
|
+
# @param cmd [String] command to execute
|
232
|
+
# @param proxy [String] to use rather than default
|
233
|
+
def namespace_exec(namespace, cmd, proxy:nil)
|
234
|
+
return `ip netns exec #{namespace} bash -c '#{self.proxy_export(proxy)}#{cmd}'`
|
74
235
|
end
|
75
236
|
end
|
76
237
|
|
data/lib/nub/sys.rb
CHANGED
@@ -98,6 +98,27 @@ module Sys
|
|
98
98
|
return result
|
99
99
|
end
|
100
100
|
|
101
|
+
# Execute the shell command and print status
|
102
|
+
# @param cmd [String] command to execute
|
103
|
+
# @param die [bool] exit on true
|
104
|
+
# @result status [bool] true on success else false
|
105
|
+
def exec_status(cmd, die:true, check:nil)
|
106
|
+
out = `#{cmd}`
|
107
|
+
status = true
|
108
|
+
status = check == out if !check.nil?
|
109
|
+
status = $?.exitstatus == 0 if check.nil?
|
110
|
+
|
111
|
+
#if status
|
112
|
+
if status
|
113
|
+
Log.puts("...success!".colorize(:green), stamp:false)
|
114
|
+
else
|
115
|
+
Log.puts("...failed!".colorize(:red), stamp:false)
|
116
|
+
Log.puts(out.colorize(:red)) and exit if die
|
117
|
+
end
|
118
|
+
|
119
|
+
return status
|
120
|
+
end
|
121
|
+
|
101
122
|
# Read a password from stdin without echoing
|
102
123
|
# @returns pass [String] the password read in
|
103
124
|
def getpass
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.103
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Crummett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|