bifrossht 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +23 -0
- data/README.md +112 -0
- data/Rakefile +44 -0
- data/bin/bifrossht +5 -0
- data/lib/bifrossht/app.rb +67 -0
- data/lib/bifrossht/config.rb +41 -0
- data/lib/bifrossht/config/connection.rb +45 -0
- data/lib/bifrossht/config/element.rb +34 -0
- data/lib/bifrossht/config/host_filter.rb +26 -0
- data/lib/bifrossht/connection.rb +57 -0
- data/lib/bifrossht/connection/base.rb +43 -0
- data/lib/bifrossht/connection/exec.rb +48 -0
- data/lib/bifrossht/errors.rb +6 -0
- data/lib/bifrossht/host_filter.rb +38 -0
- data/lib/bifrossht/host_filter/base.rb +19 -0
- data/lib/bifrossht/host_filter/search_domain.rb +34 -0
- data/lib/bifrossht/logger.rb +42 -0
- data/lib/bifrossht/target.rb +65 -0
- data/lib/bifrossht/version.rb +3 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7b03fe4cd0d9018b1ff90518b74d3019217aa67fc5c89225aaa34abb944b83f0
|
4
|
+
data.tar.gz: ff8d8cc131c39e524f1fb93638482f79e2a7f3b0db08ce73c754a3b1ab16a336
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7445e28653f35372d83dcc0d25a039bd5d50a1471495eefc7e8b2d0187ff58f003c621528faaba087f80b2638f0231b87c2d362da17b84a40ecadb2589bcaa44
|
7
|
+
data.tar.gz: 30fc48521df93f361c5334d943d8f65c3437366d67d184428acec1bcc3c61d2f2926443ae09b31c02e2462205ce45e9ff4cc885532fd4e84f4078fd9308c7ba7
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
bifrossht (0.0.1)
|
5
|
+
gli (~> 2)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
gli (2.18.2)
|
11
|
+
rake (12.3.3)
|
12
|
+
rdoc (6.1.1)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
bifrossht!
|
19
|
+
rake (~> 12)
|
20
|
+
rdoc (~> 6)
|
21
|
+
|
22
|
+
BUNDLED WITH
|
23
|
+
1.16.1
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# bifrossht
|
2
|
+
|
3
|
+
An auto-routing ssh proxy command.
|
4
|
+
|
5
|
+
## What is bifrossht?
|
6
|
+
|
7
|
+
`bifrossht` is a ssh `ProxyCommand` tool.
|
8
|
+
|
9
|
+
It could be used to automate the configuration of ssh hopping
|
10
|
+
in complex environments.
|
11
|
+
|
12
|
+
## How does it work?
|
13
|
+
|
14
|
+
With the use of `HostFilter` additional lookups and rules can be
|
15
|
+
applied to the hostname before connecting:
|
16
|
+
|
17
|
+
* Additional domain lookups
|
18
|
+
* Lookup with hostname prefixes
|
19
|
+
|
20
|
+
After the hostname lookup `bifrossht` will try to detect the hop
|
21
|
+
for connecting to the target server.
|
22
|
+
|
23
|
+
First it tries to match the hop based on configured filters:
|
24
|
+
|
25
|
+
* Regex on the Hostname
|
26
|
+
* Subnet matching on the ip-address
|
27
|
+
|
28
|
+
When no explicit configuration has matched it falls back to:
|
29
|
+
|
30
|
+
* Auto-probing of hops
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
`bifrossht` is written in ruby(>= 2.0) and available from rubygems.org:
|
35
|
+
|
36
|
+
```
|
37
|
+
gem install bifrossht
|
38
|
+
```
|
39
|
+
|
40
|
+
## Configuration
|
41
|
+
|
42
|
+
`bifrossht` must be configured in `~/.bifrossht.yml`:
|
43
|
+
|
44
|
+
```yaml
|
45
|
+
---
|
46
|
+
host_filters:
|
47
|
+
- type: SearchDomain
|
48
|
+
domains:
|
49
|
+
- cluster-xy.provider.tld
|
50
|
+
- internal.provider.tld
|
51
|
+
prefixes:
|
52
|
+
- vm00
|
53
|
+
|
54
|
+
connections:
|
55
|
+
- name: direct
|
56
|
+
type: Exec
|
57
|
+
match_addr:
|
58
|
+
- "192.168.0.0/24"
|
59
|
+
- "192.168.1.0/24"
|
60
|
+
parameters:
|
61
|
+
timeout: 1
|
62
|
+
command: nc %h %p
|
63
|
+
|
64
|
+
- name: dmz
|
65
|
+
type: Exec
|
66
|
+
match:
|
67
|
+
- "dmz.provider.tld$"
|
68
|
+
match_addr:
|
69
|
+
- "80.241.212.0/24"
|
70
|
+
parameters:
|
71
|
+
timeout: 3
|
72
|
+
command: ssh -W hop-dmz.internal.provider.tld
|
73
|
+
|
74
|
+
- name: internet
|
75
|
+
type: Exec
|
76
|
+
skip_probe: true
|
77
|
+
match:
|
78
|
+
- "your-server.de$"
|
79
|
+
- "contabo.net$"
|
80
|
+
- "compute.amazonaws.com$"
|
81
|
+
- "google.internal$"
|
82
|
+
parameters:
|
83
|
+
timeout: 5
|
84
|
+
command: proxytunnel -p gateway.internal.provider.tld:3128 -d %h:%p
|
85
|
+
```
|
86
|
+
|
87
|
+
Then configure the `ProxyCommand` in `~/.ssh/config`:
|
88
|
+
|
89
|
+
```
|
90
|
+
Host *
|
91
|
+
ProxyCommand bifrossht connect -p %p %h
|
92
|
+
```
|
93
|
+
|
94
|
+
## Troubleshooting
|
95
|
+
|
96
|
+
Run the `bifrossht` command standalone and increase log level:
|
97
|
+
|
98
|
+
```
|
99
|
+
bifrossht -l debug connect host0815.internal.provider.tld
|
100
|
+
```
|
101
|
+
|
102
|
+
## Copyright
|
103
|
+
|
104
|
+
2019 Markus Benning
|
105
|
+
|
106
|
+
## License
|
107
|
+
|
108
|
+
This program is free software: you can redistribute it and/or modify
|
109
|
+
it under the terms of the GNU General Public License as published by
|
110
|
+
the Free Software Foundation, either version 3 of the License, or
|
111
|
+
(at your option) any later version.
|
112
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
require 'rdoc/task'
|
5
|
+
require 'cucumber'
|
6
|
+
require 'cucumber/rake/task'
|
7
|
+
Rake::RDocTask.new do |rd|
|
8
|
+
rd.main = "README.rdoc"
|
9
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
10
|
+
rd.title = 'Your application title'
|
11
|
+
end
|
12
|
+
|
13
|
+
spec = eval(File.read('bifrossht.gemspec'))
|
14
|
+
|
15
|
+
Gem::PackageTask.new(spec) do |pkg|
|
16
|
+
end
|
17
|
+
CUKE_RESULTS = 'results.html'
|
18
|
+
CLEAN << CUKE_RESULTS
|
19
|
+
desc 'Run features'
|
20
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
21
|
+
opts = "features --format html -o #{CUKE_RESULTS} --format progress -x"
|
22
|
+
opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
|
23
|
+
t.cucumber_opts = opts
|
24
|
+
t.fork = false
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Run features tagged as work-in-progress (@wip)'
|
28
|
+
Cucumber::Rake::Task.new('features:wip') do |t|
|
29
|
+
tag_opts = ' --tags ~@pending'
|
30
|
+
tag_opts = ' --tags @wip'
|
31
|
+
t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}"
|
32
|
+
t.fork = false
|
33
|
+
end
|
34
|
+
|
35
|
+
task :cucumber => :features
|
36
|
+
task 'cucumber:wip' => 'features:wip'
|
37
|
+
task :wip => 'features:wip'
|
38
|
+
require 'rake/testtask'
|
39
|
+
Rake::TestTask.new do |t|
|
40
|
+
t.libs << "test"
|
41
|
+
t.test_files = FileList['test/*_test.rb']
|
42
|
+
end
|
43
|
+
|
44
|
+
task :default => [:test,:features]
|
data/bin/bifrossht
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'gli'
|
2
|
+
|
3
|
+
require 'bifrossht/version.rb'
|
4
|
+
require 'bifrossht/errors.rb'
|
5
|
+
require 'bifrossht/logger.rb'
|
6
|
+
require 'bifrossht/config.rb'
|
7
|
+
require 'bifrossht/host_filter.rb'
|
8
|
+
require 'bifrossht/connection.rb'
|
9
|
+
require 'bifrossht/target.rb'
|
10
|
+
|
11
|
+
module Bifrossht
|
12
|
+
class App
|
13
|
+
extend GLI::App
|
14
|
+
|
15
|
+
program_desc 'SSH auto-routing proxy command'
|
16
|
+
|
17
|
+
version Bifrossht::VERSION
|
18
|
+
|
19
|
+
subcommand_option_handling :normal
|
20
|
+
arguments :strict
|
21
|
+
|
22
|
+
desc 'configuration file'
|
23
|
+
default_value '~/.bifrossht.yml'
|
24
|
+
arg_name 'path'
|
25
|
+
flag %i[c config]
|
26
|
+
|
27
|
+
desc 'log level'
|
28
|
+
default_value 'info'
|
29
|
+
arg_name 'log_level'
|
30
|
+
flag %i[l log_level]
|
31
|
+
|
32
|
+
desc 'connect to an target'
|
33
|
+
arg 'host'
|
34
|
+
command :connect do |c|
|
35
|
+
c.flag %i[p port], default_value: '22'
|
36
|
+
c.action do |_global_options, options, args|
|
37
|
+
target = Target.new(args[0], options[:port])
|
38
|
+
|
39
|
+
HostFilter.apply(target)
|
40
|
+
hop = Connection.find(target)
|
41
|
+
raise ApplicationError, 'no suitable hop found' unless hop
|
42
|
+
|
43
|
+
Logger.info("Connecting #{target.host} using #{hop.name}...")
|
44
|
+
hop.connect(target)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
pre do |global, _command, _options, _args|
|
49
|
+
Logger.log_level(global[:l])
|
50
|
+
Config.load_config(global[:c])
|
51
|
+
HostFilter.register_filters(Config.host_filters)
|
52
|
+
Connection.register_connections(Config.connections)
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
post do |global, command, options, args|
|
57
|
+
# empty
|
58
|
+
end
|
59
|
+
|
60
|
+
on_error do |e|
|
61
|
+
raise e unless e.is_a? Bifrossht::Error
|
62
|
+
|
63
|
+
Logger.error(e.message)
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'bifrossht/config/element'
|
2
|
+
require 'bifrossht/config/host_filter'
|
3
|
+
require 'bifrossht/config/connection'
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module Bifrossht
|
8
|
+
class Config
|
9
|
+
class << self
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def load_config(path)
|
13
|
+
@config = YAML.load_file(File.expand_path(path)) || {}
|
14
|
+
rescue Errno::ENOENT => e
|
15
|
+
raise ParameterError, "Configuration file: #{e.message}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def host_filters
|
19
|
+
@host_filters ||= build_subconfig('host_filters', Config::HostFilter)
|
20
|
+
end
|
21
|
+
|
22
|
+
def connections
|
23
|
+
@connections ||= build_subconfig('connections', Config::Connection)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_subconfig(key, klass)
|
29
|
+
return [] unless @config.key?(key)
|
30
|
+
|
31
|
+
params = @config[key]
|
32
|
+
|
33
|
+
unless params.is_a? Array
|
34
|
+
raise ParameterError, "#{key} must be an array"
|
35
|
+
end
|
36
|
+
|
37
|
+
params.map { |c| klass.new(c) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Bifrossht
|
2
|
+
class Config
|
3
|
+
class Connection < Config::Element
|
4
|
+
def initialize(options = {})
|
5
|
+
super
|
6
|
+
|
7
|
+
validate_presence 'type', 'name'
|
8
|
+
validate_type 'type', String
|
9
|
+
validate_type 'name', String
|
10
|
+
validate_boolean 'skip_probe'
|
11
|
+
validate_type 'parameters', Hash
|
12
|
+
validate_type 'match', Array
|
13
|
+
validate_type 'match_addr', Array
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
@options['type']
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
@options['name']
|
22
|
+
end
|
23
|
+
|
24
|
+
def skip_probe
|
25
|
+
@options['skip_probe'] || false
|
26
|
+
end
|
27
|
+
|
28
|
+
def parameters
|
29
|
+
@options['parameters'] || {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def match
|
33
|
+
return [] if @options['match'].nil?
|
34
|
+
|
35
|
+
@options['match'].map { |re| Regexp.new re }
|
36
|
+
end
|
37
|
+
|
38
|
+
def match_addr
|
39
|
+
return [] if @options['match_addr'].nil?
|
40
|
+
|
41
|
+
@options['match_addr'].map { |ip| IPAddr.new ip }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bifrossht
|
2
|
+
class Config
|
3
|
+
class Element
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def validate_presence(*options)
|
11
|
+
options.each do |name|
|
12
|
+
raise ParameterError, "#{self.class.name} is missing parameter #{name}" unless @options.key?(name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_type(option, type)
|
17
|
+
return unless @options.key?(option)
|
18
|
+
|
19
|
+
raise ParameterError, "option #{option} for #{self.class} is not of type #{type}" unless @options[option].is_a? type
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_enum(option, values)
|
23
|
+
return unless @options.key?(option)
|
24
|
+
|
25
|
+
value = @options[option]
|
26
|
+
raise ParameterError, "option #{option} for #{self.class} does not allow value #{value}" unless values.include?(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_boolean(option)
|
30
|
+
validate_enum(option, [true, false])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bifrossht
|
2
|
+
class Config
|
3
|
+
class HostFilter < Config::Element
|
4
|
+
def initialize(options = {})
|
5
|
+
super
|
6
|
+
|
7
|
+
validate_presence 'type', 'domains'
|
8
|
+
validate_type 'type', String
|
9
|
+
validate_type 'domains', Array
|
10
|
+
validate_type 'prefixes', Array
|
11
|
+
end
|
12
|
+
|
13
|
+
def type
|
14
|
+
@options['type']
|
15
|
+
end
|
16
|
+
|
17
|
+
def domains
|
18
|
+
@options['domains'] || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def prefixes
|
22
|
+
@options['prefixes'] || []
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'bifrossht/connection/base'
|
2
|
+
#require 'bifrossht/connection/direct'
|
3
|
+
require 'bifrossht/connection/exec'
|
4
|
+
#require 'bifrossht/connection/http_proxy'
|
5
|
+
|
6
|
+
module Bifrossht
|
7
|
+
class Connection
|
8
|
+
class << self
|
9
|
+
attr_reader :connections
|
10
|
+
|
11
|
+
def register_connections(connections = [])
|
12
|
+
connections.each { |c| register_connection(c) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_connection(config)
|
16
|
+
@connections ||= {}
|
17
|
+
|
18
|
+
klass = build_class_name(config.type)
|
19
|
+
@connections[config.name] = klass.new(config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(target)
|
23
|
+
c = match(target)
|
24
|
+
return c unless c.nil?
|
25
|
+
|
26
|
+
probe(target)
|
27
|
+
end
|
28
|
+
|
29
|
+
def match(target)
|
30
|
+
connections.values.select do |c|
|
31
|
+
c.match(target)
|
32
|
+
end.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def probe(target)
|
36
|
+
connections.values.reject(&:skip_probe).each do |c|
|
37
|
+
Logger.debug("probing #{c.name}...")
|
38
|
+
return c if c.probe(target)
|
39
|
+
end
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def connection(hop)
|
45
|
+
@connections[hop]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def build_class_name(type)
|
51
|
+
Object.const_get("Bifrossht::Connection::#{type}")
|
52
|
+
rescue NameError => e
|
53
|
+
raise ParameterError, "Cant load connection: #{e.message}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bifrossht
|
2
|
+
class Connection
|
3
|
+
class Base
|
4
|
+
attr_accessor :config
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
config.name
|
12
|
+
end
|
13
|
+
|
14
|
+
def probe(_target)
|
15
|
+
raise 'not implemented'
|
16
|
+
end
|
17
|
+
|
18
|
+
def connect(_target)
|
19
|
+
raise 'not implemented'
|
20
|
+
end
|
21
|
+
|
22
|
+
def skip_probe
|
23
|
+
config.skip_probe
|
24
|
+
end
|
25
|
+
|
26
|
+
def match(target)
|
27
|
+
host_matches = config.match.select do |re|
|
28
|
+
re.match(target.host)
|
29
|
+
end
|
30
|
+
return true if host_matches.any?
|
31
|
+
|
32
|
+
if target.resolvable?
|
33
|
+
addr_matches = config.match_addr.select do |net|
|
34
|
+
net.include?(target.resolved_ip)
|
35
|
+
end
|
36
|
+
return true if addr_matches.any?
|
37
|
+
end
|
38
|
+
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Bifrossht
|
2
|
+
class Connection
|
3
|
+
class Exec < Base
|
4
|
+
def probe(target)
|
5
|
+
cmd = command(target)
|
6
|
+
Logger.debug("probing command: #{cmd}")
|
7
|
+
in_r, in_w = IO.pipe
|
8
|
+
io = IO.popen(['sh', '-c', cmd, in: in_w, err: '/dev/null'])
|
9
|
+
in_w.close
|
10
|
+
ready = IO.select([io], nil, nil, timeout)
|
11
|
+
if ready
|
12
|
+
banner = io.readline
|
13
|
+
else
|
14
|
+
Logger.debug('probe timed out!')
|
15
|
+
end
|
16
|
+
Process.kill('TERM', io.pid)
|
17
|
+
in_r.close
|
18
|
+
io.close
|
19
|
+
|
20
|
+
return true if banner =~ /^SSH-/
|
21
|
+
|
22
|
+
false
|
23
|
+
rescue EOFError
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect(target)
|
28
|
+
cmd = command(target)
|
29
|
+
Logger.debug("executing: #{cmd}")
|
30
|
+
exec cmd
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def command(target)
|
36
|
+
command_pattern.gsub('%h', target.host).gsub('%p', target.port.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def command_pattern
|
40
|
+
config.parameters['command'] || 'ssh -W %h:%p'
|
41
|
+
end
|
42
|
+
|
43
|
+
def timeout
|
44
|
+
(config.parameters['timeout'] || 3).to_i
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'bifrossht/host_filter/base'
|
2
|
+
require 'bifrossht/host_filter/search_domain'
|
3
|
+
|
4
|
+
module Bifrossht
|
5
|
+
class HostFilter
|
6
|
+
class << self
|
7
|
+
attr_reader :filters
|
8
|
+
|
9
|
+
def register_filters(filters = [])
|
10
|
+
filters.each { |f| register_filter(f) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def register_filter(config)
|
14
|
+
@filters ||= []
|
15
|
+
|
16
|
+
klass = build_class_name(config.type)
|
17
|
+
@filters << klass.new(config)
|
18
|
+
end
|
19
|
+
|
20
|
+
def apply(target)
|
21
|
+
filters.each do |filter|
|
22
|
+
next unless filter.match(target.host)
|
23
|
+
|
24
|
+
new_host = filter.apply(target.host)
|
25
|
+
target.rewrite(new_host)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_class_name(type)
|
32
|
+
Object.const_get("Bifrossht::HostFilter::#{type}")
|
33
|
+
rescue NameError => e
|
34
|
+
raise ParameterError, "Cant load host_filter: #{e.message}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
module Bifrossht
|
4
|
+
class HostFilter
|
5
|
+
class SearchDomain < Base
|
6
|
+
def match(host)
|
7
|
+
host !~ /\./
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply(host)
|
11
|
+
prefixes = [''] + config.prefixes
|
12
|
+
|
13
|
+
config.domains.each do |domain|
|
14
|
+
prefixes.each do |prefix|
|
15
|
+
record = "#{prefix}#{host}.#{domain}"
|
16
|
+
|
17
|
+
begin
|
18
|
+
address = Resolv.getaddress record
|
19
|
+
rescue Resolv::ResolvError => e
|
20
|
+
Logger.debug "SearchDomain: #{e.message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
unless address.nil?
|
24
|
+
Logger.debug "SearchDomain: using #{record}"
|
25
|
+
return record
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
host
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Bifrossht
|
5
|
+
class Logger
|
6
|
+
class << self
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
|
10
|
+
|
11
|
+
def log_level(level = 'warn')
|
12
|
+
mylevel = case level
|
13
|
+
when 'debug' then ::Logger::DEBUG
|
14
|
+
when 'info' then ::Logger::INFO
|
15
|
+
when 'warn' then ::Logger::WARN
|
16
|
+
when 'error' then ::Logger::ERROR
|
17
|
+
when 'fatal' then ::Logger::FATAL
|
18
|
+
else
|
19
|
+
raise "Unknown log-level #{level}"
|
20
|
+
end
|
21
|
+
|
22
|
+
logger.level = mylevel
|
23
|
+
end
|
24
|
+
|
25
|
+
def logger
|
26
|
+
return @logger unless @logger.nil?
|
27
|
+
|
28
|
+
@logger = ::Logger.new(STDERR)
|
29
|
+
@logger.level = ::Logger::INFO
|
30
|
+
@logger.formatter = proc do |severity, _datetime, _progname, msg|
|
31
|
+
if severity == 'INFO'
|
32
|
+
"#{msg}\n"
|
33
|
+
else
|
34
|
+
"#{severity}: #{msg}\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
@logger
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
module Bifrossht
|
5
|
+
class Target
|
6
|
+
attr_reader :entries
|
7
|
+
attr_reader :port
|
8
|
+
|
9
|
+
def initialize(host, port)
|
10
|
+
@entries = [host]
|
11
|
+
@port = port.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def rewrite(new)
|
15
|
+
return if new == host
|
16
|
+
|
17
|
+
@entries.push(new)
|
18
|
+
end
|
19
|
+
|
20
|
+
def orig_host
|
21
|
+
@entries.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def host
|
25
|
+
@entries.last
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
host
|
30
|
+
end
|
31
|
+
|
32
|
+
def ip
|
33
|
+
@ip ||= IPAddr.new host
|
34
|
+
rescue IPAddr::InvalidAddressError
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def ip?
|
39
|
+
!ip.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolved_ip
|
43
|
+
@resolved_ip ||= resolve_address
|
44
|
+
end
|
45
|
+
|
46
|
+
def resolvable?
|
47
|
+
!resolved_ip.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def resolve_address
|
53
|
+
return ip if ip?
|
54
|
+
|
55
|
+
record = Resolv.getaddress(host)
|
56
|
+
return nil unless record
|
57
|
+
|
58
|
+
IPAddr.new(record)
|
59
|
+
rescue Resolv::ResolvError
|
60
|
+
nil
|
61
|
+
rescue IPAddr::InvalidAddressError
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bifrossht
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Markus Benning
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: gli
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
55
|
+
description:
|
56
|
+
email: ich@markusbenning.de
|
57
|
+
executables:
|
58
|
+
- bifrossht
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- Gemfile
|
63
|
+
- Gemfile.lock
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- bin/bifrossht
|
67
|
+
- lib/bifrossht/app.rb
|
68
|
+
- lib/bifrossht/config.rb
|
69
|
+
- lib/bifrossht/config/connection.rb
|
70
|
+
- lib/bifrossht/config/element.rb
|
71
|
+
- lib/bifrossht/config/host_filter.rb
|
72
|
+
- lib/bifrossht/connection.rb
|
73
|
+
- lib/bifrossht/connection/base.rb
|
74
|
+
- lib/bifrossht/connection/exec.rb
|
75
|
+
- lib/bifrossht/errors.rb
|
76
|
+
- lib/bifrossht/host_filter.rb
|
77
|
+
- lib/bifrossht/host_filter/base.rb
|
78
|
+
- lib/bifrossht/host_filter/search_domain.rb
|
79
|
+
- lib/bifrossht/logger.rb
|
80
|
+
- lib/bifrossht/target.rb
|
81
|
+
- lib/bifrossht/version.rb
|
82
|
+
homepage: https://markusbenning.de
|
83
|
+
licenses:
|
84
|
+
- GPL-3.0+
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options:
|
88
|
+
- "--title"
|
89
|
+
- bifrossht
|
90
|
+
- "--main"
|
91
|
+
- README.md
|
92
|
+
- "-ri"
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.7.6
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: SSH auto-routing proxy command
|
112
|
+
test_files: []
|