Skip to content
Snippets Groups Projects
Verified Commit ae79a899 authored by Radim Janča's avatar Radim Janča :speech_balloon:
Browse files

Init commit

parent 315e03bd
Branches
No related tags found
No related merge requests found
Pipeline #9939 passed
stages:
- build
build-segment-viewer:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker build --pull -t "$CI_REGISTRY_IMAGE/segment_viewer" .
- docker push "$CI_REGISTRY_IMAGE/segment_viewer"
only:
- master
FROM ruby:2.5.3-slim-stretch
LABEL Description="segment_viewer"
RUN apt-get update \
&& apt-get install -y build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -ms /bin/bash segment_viewer
USER segment_viewer
WORKDIR /home/segment_viewer
RUN chmod 755 /home/segment_viewer
COPY Gemfile .
COPY Gemfile.lock .
RUN bundle install
RUN mkdir /home/segment_viewer/.segment_viewer
COPY segment_viewer.rb .
ENTRYPOINT /home/segment_viewer/segment_viewer.rb
source ENV['GEM_SOURCE'] || 'https://rubygems.org'
gem 'pmap', '~> 1.1', '>= 1.1.1'
gem 'curses', '~> 1.2'
GEM
remote: https://rubygems.org/
specs:
curses (1.2.5)
pmap (1.1.1)
PLATFORMS
ruby
DEPENDENCIES
curses (~> 1.2)
pmap (~> 1.1, >= 1.1.1)
BUNDLED WITH
1.16.2
# segment_viewer # segment_viewer
DNS PTR resolver for segments defined in `networks.yml`.
## Usage
```
docker run -it --rm \
-v <ABSOLUTE_PATH>/networks.yml:/home/segment_viewer/home/segment_viewer/.segment_viewer/.networks.yml \
-v segment_viewer:/home/segment_viewer/.segment_viewer:Z \
test
```
---
##############################################################################
## Expected format:
##
## firewall_groups:
## - name: String
## in:
## - network: CIDR
## policy: allow | deny
## protocols: [String, String, ...] # or `~` for any
## ports: [Integer, Integer, ...] # or `~` for any
## out:
## - network: CIDR
## policy: allow | deny
## protocols: [String, String, ...] # or `~` for any
## ports: [Integer, Integer, ...] # or `~` for any
##
## trunks:
## - name: String
## vlans: [Integer, Integer, ...]
## env: [String, String, ...]
##
## networks:
## - name: String
## network: CIDR
## label: String
## vlan: Integer
## gw: IPv4
## nat: IPv4
## dhcp: IPv4
## trunk: String
## firewall_group: String
## env: [String, String, ...]
##
##############################################################################
networks:
- name: Example
network: 10.0.1.0/28
label: Example-label
vlan: ~
gw: 10.0.1.1
nat: ~
dhcp: ~
trunk: ~
firewall_group: ~
env: Example-environment
#!/usr/bin/env ruby
require 'curses'
require 'resolv'
require 'ipaddr'
require 'yaml'
require 'pmap'
require 'fileutils'
NS='ns.muni.cz'.freeze
CONF_DIR="#{ENV['HOME']}/.segment_viewer".freeze
DNS_CACHE="#{CONF_DIR}/.cache.yml".freeze
NETWORKS_FILE="#{CONF_DIR}/.networks.yml".freeze
RESOLV_TIMEOUT=5.freeze
THREAD_COUNT=128.freeze
class ItemStore
def initialize(items, window_size ,first = 0)
@items = items
@item_selected_index = 0
@first_visible = first
@window_size = window_size > 0 ? window_size : 0
end
def move_up
@item_selected_index -= 1 unless @item_selected_index.zero?
scroll_up if @item_selected_index < @first_visible
end
def move_down
@item_selected_index += 1 unless @item_selected_index >= last_item_index
scroll_down if @item_selected_index > last_visible
end
def visible_items
@items[@first_visible, @window_size]
end
def selected_visible_item_index
raise "Selected item index (#{@item_selected_index}) under visible windows (first: #{@first_visible})" if @item_selected_index < @first_visible
raise "Selected item index (#{@item_selected_index}) above visible windows (last: #{last_visible})" if @item_selected_index > last_item_index
(@item_selected_index - @first_visible)
end
def selected_item
@items[@item_selected_index]
end
def resize(window_size)
raise "Window size is 0 :-(" if window_size < 0
@window_size = window_size
if @item_selected_index < @first_visible
@first_visible = @item_selected_index
elsif @item_selected_index > last_visible
@first_visible += 1 while @item_selected_index != last_visible
elsif last_visible > last_item_index
scroll_up until @first_visible.zero? or last_item_index == last_visible
end
end
private
def last_visible
@first_visible + @window_size - 1
end
def last_item_index
@items.size - 1
end
def scroll_up
@first_visible -= 1 unless @first_visible.zero?
end
def scroll_down
@first_visible += 1 unless @item_selected_index > last_item_index
end
end
class Panel
def initialize(side)
@height = Curses.lines
@width = Curses.cols / 2
@top = 0
@left = side == 'left' ? 0 : Curses.cols / 2
@win = Curses::Window.new(@height, @width, @top ,@left)
@win.box('|', '-')
@win.refresh
@active = false
end
def activate
@active = true
end
def deactivate
@active = false
end
def clear
@win.box('|', '-')
(@height - 2).times do |index|
@win.setpos(index + 1, 1)
@win.addstr ' ' * (@width - 2)
end
end
def draw_menu
clear
@item_store.visible_items.each_with_index do |item, index|
@win.setpos(index + 1, 2)
@win.attrset((@active and @item_store.selected_visible_item_index == index) ? Curses::A_STANDOUT : Curses::A_NORMAL)
@win.addstr item.ljust(@width - 4, ' ')
end
@win.attrset(Curses::A_NORMAL)
@win.refresh
end
def draw_message(msg)
@item_store = ItemStore.new(msg, @height - 2)
deactivate
draw_menu
end
def load_items(data)
@item_store = ItemStore.new(data, @height - 2)
draw_menu
end
def resize
@height = Curses.lines
@width = Curses.cols / 2
@item_store.resize (@height - 2)
draw_menu
end
def selected_item
@item_store.selected_item
end
def select_item
@win.keypad = true
ch = @win.getch
case ch
when Curses::KEY_UP then @item_store.move_up
when Curses::KEY_DOWN then @item_store.move_down
end
draw_menu
@win.keypad = false
return ch
end
end
class Resolver
def initialize(cache = DNS_CACHE)
@cache = cache
@resolver = Resolv::DNS.new(:nameserver => NS)
@resolver.timeouts = RESOLV_TIMEOUT
if File.file?(@cache)
@dns = YAML.load_file(@cache)
else
FileUtils.mkdir_p(CONF_DIR) unless File.directory?(CONF_DIR)
@dns = {}
end
end
def refresh_range(cidr)
resolve_range(cidr, true)
end
def resolve_range_l(cidr, refresh = false)
resolve_range(cidr, refresh)
@dns[cidr].map { |record| "#{record['ip'].to_s.ljust(16, ' ')} #{record['names'].join(' ')}" }
end
def resolve_range_s(cidr, refresh = false)
resolve_range(cidr, refresh)
dns_short = @dns[cidr].chunk { |record| record['names'] }.map do |el|
if el.first.none? && el.last.size > 2
[false, [el.last.first, { 'ip' => ' ...', 'names' => [] }, el.last.last]]
else
el
end
end.flat_map { |el| el.last }
dns_short.map { |record| "#{record['ip'].to_s.ljust(16, ' ')} #{record['names'].join(' ')}" }
end
private
def resolve_range(cidr, refresh = false)
if refresh or not @dns.key? cidr
@dns[cidr] = IPAddr.new(cidr).to_range.pmap(THREAD_COUNT) do |ip|
{
'ip' => ip.to_s,
'names' => @resolver.getnames(ip.to_s).map{ |dns| dns.to_s }
}
end
File.write(@cache, @dns.to_yaml)
end
end
end
def network_details (networks, name)
networks.select{|net| net['name'] == name }[0].map{|k,v| "#{k}: #{v}"}
end
def resolve_dns (panel, resolver, networks, name, force = false, long = false)
cidr = networks.select{|net| net['name'] == name }[0]['network']
panel.draw_message ["Resolving dns for #{cidr}"]
panel.activate
if long
panel.load_items resolver.resolve_range_l(cidr, force)
else
panel.load_items resolver.resolve_range_s(cidr, force)
end
end
def resolve_dns_all (panel, resolver, networks)
networks.each do |net|
cidr = net['network']
panel.draw_message ["Resolving dns for all networks. Current: #{cidr}"]
resolver.refresh_range cidr
end
panel.draw_message ["Resolving dns for all networks finished."]
end
def show_help
if not defined? @helppane
@helppane = Panel.new('right')
help = ["Segment_viewer help, YAY!"]
help << ""
help << "* use arrows to navigate/select segments"
help << "* use 'l' to show all IPs"
help << "* use 'r' to reload dns data for current or all networks"
help << " app uses cache, so refreshing is encouraged"
help << "* use 'h' to show this help"
help << "* use 'q' to exit"
help << ""
help << "WARNING: private segments are resolved"
help << " only inside MU network!"
@helppane.draw_message help
else
@helppane.resize
end
end
Curses.init_screen
Curses.start_color
Curses.noecho
Curses.curs_set 0
begin
lpane = Panel.new('left')
networks = YAML::load_file(File.join(__dir__, NETWORKS_FILE))['networks'].select { |net| !net['network'].nil? }
network_names = networks.map{ |net| net['name'] }.sort
menu = networks.map { |net| net['env'] }.flatten.uniq
resolver = Resolver.new()
lpane.activate
lpane.load_items menu
rpane = Panel.new('right')
show_help
active_panel = lpane
mode = 'main_menu'
while true
key_pressed = active_panel.select_item
case key_pressed
when 'q' then exit
when 'h' then show_help
when Curses::KEY_RESIZE
lpane.resize
rpane.resize
end
if mode == 'main_menu'
case key_pressed
when Curses::KEY_RIGHT
network_subset = networks.select{ |net| net['env'].include? lpane.selected_item }
lpane.load_items network_subset.map{ |net| net['name'] }.sort
rpane.load_items network_details(networks, lpane.selected_item)
mode = 'segment'
when 'r' then resolve_dns_all(rpane, resolver, networks)
end
elsif mode == 'segment'
if active_panel == lpane
case key_pressed
when Curses::KEY_UP then rpane.load_items network_details(networks, lpane.selected_item)
when Curses::KEY_DOWN then rpane.load_items network_details(networks, lpane.selected_item)
when Curses::KEY_LEFT
lpane.load_items menu
mode = 'main_menu'
when Curses::KEY_RIGHT
active_panel = rpane
resolve_dns(rpane, resolver, networks, lpane.selected_item)
when 'r' then resolve_dns_all(rpane, resolver, networks)
end
elsif active_panel == rpane
case key_pressed
when 'r' then resolve_dns(rpane, resolver, networks, lpane.selected_item, true)
when 'l' then resolve_dns(rpane, resolver, networks, lpane.selected_item, false, true)
when Curses::KEY_LEFT
active_panel = lpane
rpane.deactivate
rpane.load_items network_details(networks, lpane.selected_item)
end
end
end
end
rescue => ex
Curses.close_screen
puts ex
puts ex.backtrace
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment