bifrossht 0.0.1
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 +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: []
|