nub 0.0.96 → 0.0.103
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.
- 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
|