Added the fwknop lsof launcher under the extras/ directory
The fwknop lsof launcher (extras/fwknop-launcher/fwknop-launcher-lsof.pl) is a lightweight daemon that allows the user to not have to manually run the fwknop client when attempting to gain access to a service that is protected by Single Packet Authorization via fwknopd. This is accomplished by checking the output of lsof to look for pending connections in the SYN_SENT state, which (usually) indicate that a remote firewall is blocking the attempted connection. At this point, the launcher executes the fwknop client with the --get-key arg (so the user must place the key in the local filesystem) to generate an SPA packet for the attempted connection. The remote fwknopd daemon will reconfigure the firewall to allow temporary access, and this usually happens fast enough that the original connection attempt will then succeed. The idea for this was originally for a pcap-based connection watcher by Sebastien Jeanquier.
This commit is contained in:
+329
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
#############################################################################
|
||||
#
|
||||
# File: fwknop-launcher-lsof.pl
|
||||
#
|
||||
# URL: http://www.cipherdyne.org/fwknop/
|
||||
#
|
||||
# Purpose: This script provides a lightweight mechanism to launch the fwknop
|
||||
# client shortly after a local user tries to initiate a connection
|
||||
# to a remote service that is protected by fwknopd. The advantage
|
||||
# of using this script is that local users don't have to run the
|
||||
# fwknop client themselves before initiating an outbound
|
||||
# connection. The idea for this script came from Sebastien
|
||||
# Jeanquier. It is not required to have the fwknopd daemon
|
||||
# installed in order for this script to be effective - only the
|
||||
# fwknop client needs to be available.
|
||||
#
|
||||
# Author: Michael Rash (mbr@cipherdyne.org)
|
||||
#
|
||||
# Version: 2.0.0rc5
|
||||
#
|
||||
# Copyright (C) 2011 Michael Rash (mbr@cipherdyne.org)
|
||||
#
|
||||
# License - GNU Public License version 2 (GPLv2):
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
#
|
||||
#############################################################################
|
||||
#
|
||||
|
||||
use POSIX;
|
||||
use Data::Dumper;
|
||||
use Getopt::Long;
|
||||
use strict;
|
||||
|
||||
#================== config ==================
|
||||
my $launcher_config = '~/.fwknop-launcher.conf';
|
||||
|
||||
my $sleep_interval = 1;
|
||||
|
||||
my $lsof_cmd = '/usr/bin/lsof';
|
||||
my $fwknop_cmd = '/usr/bin/fwknop';
|
||||
#================ end config ================
|
||||
|
||||
my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|; ### IPv4
|
||||
|
||||
my %spa_dispatch_cache = ();
|
||||
my %spa_config_dsts = ();
|
||||
my $fwknop_args_append = '';
|
||||
my $fwknop_args_override = '';
|
||||
my $key_file = '';
|
||||
my $home_dir = '';
|
||||
my $user = '';
|
||||
my $no_daemon = 0;
|
||||
my $verbose = 0;
|
||||
my $help = 0;
|
||||
|
||||
die "[*] Use --help for usage information.\n" unless(GetOptions (
|
||||
'config=s' => \$launcher_config, # launcher config
|
||||
'lsof-cmd=s' => \$lsof_cmd, # lsof path
|
||||
'fwknop-cmd=s' => \$fwknop_cmd, # fwknop path
|
||||
'sleep-interval=i' => \$sleep_interval, # seconds
|
||||
'user=s' => \$user, # set the user
|
||||
'home-dir=s' => \$home_dir, # set the home dir
|
||||
'no-daemon' => \$no_daemon, # run in the foreground
|
||||
'verbose' => \$verbose, # verbose mode
|
||||
'help' => \$help # Print help
|
||||
));
|
||||
|
||||
&usage() if $help;
|
||||
|
||||
&init();
|
||||
|
||||
&daemonize() unless $no_daemon;
|
||||
|
||||
&watch_lsof();
|
||||
|
||||
exit 0;
|
||||
|
||||
#============== end main =============
|
||||
|
||||
sub watch_lsof() {
|
||||
|
||||
my $lsof_exec_ctr = 0;
|
||||
|
||||
for (;;) {
|
||||
|
||||
my $cmd = "$lsof_cmd -u $user -a -n -P -i 4"; ### IPv4
|
||||
|
||||
print "[+] Executing: $cmd\n" if $no_daemon and $verbose;
|
||||
|
||||
open LSOF, "$cmd |" or die "[*] Could not execute $cmd: $!";
|
||||
|
||||
### COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
||||
### telnet 31483 mbr 3u IPv4 54543 0t0 TCP 127.0.0.1:41619->127.0.0.1:1234 (SYN_SENT)
|
||||
|
||||
while (<LSOF>) {
|
||||
if (/^(\S+)\s+(\d+)\s+\S+\s+.*\s(\S+)\s+($ip_re):
|
||||
(\d+)\-\>($ip_re):(\d+)\s+\(SYN_SENT\)/x) {
|
||||
my $command = $1;
|
||||
my $pid = $2;
|
||||
my $proto = lc($3);
|
||||
my $src_ip = $4;
|
||||
my $s_port = $5;
|
||||
my $dst_ip = $6;
|
||||
my $d_port = $7;
|
||||
|
||||
print "[+] Matched line: $_" if $no_daemon and $verbose;
|
||||
|
||||
my $connection_string = "$command:$proto:$src_ip:$s_port:$dst_ip:$d_port";
|
||||
|
||||
next if defined $spa_dispatch_cache{$connection_string};
|
||||
|
||||
if (&is_valid_fwknop_dst($command, $proto, $dst_ip, $d_port)) {
|
||||
|
||||
if ($no_daemon and $verbose) {
|
||||
print "[+] Matched connection, launching fwknop.\n";
|
||||
}
|
||||
|
||||
&dispatch_fwknop($proto, $dst_ip, $d_port);
|
||||
|
||||
### add this line to the dispatch cache so that we don't
|
||||
### send multiple SPA packets for the same connection
|
||||
$spa_dispatch_cache{$connection_string} = '';
|
||||
|
||||
} else {
|
||||
if ($no_daemon and $verbose) {
|
||||
print "[-] Attempted connection not matched by ",
|
||||
"any SPA_ACCESS variable in $launcher_config\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close LSOF;
|
||||
|
||||
$lsof_exec_ctr++;
|
||||
|
||||
if ($lsof_exec_ctr == 3600) {
|
||||
%spa_dispatch_cache = ();
|
||||
$lsof_exec_ctr = 0;
|
||||
}
|
||||
|
||||
sleep $sleep_interval;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub is_valid_fwknop_dst() {
|
||||
my ($cmd, $proto, $dst_ip, $d_port) = @_;
|
||||
|
||||
CMD: for my $access_cmd (keys %spa_config_dsts) {
|
||||
next CMD unless $access_cmd eq 'any' or $cmd eq $access_cmd;
|
||||
PROTO: for my $access_proto (keys %{$spa_config_dsts{$access_cmd}}) {
|
||||
next PROTO unless $access_proto eq 'any' or $proto eq $access_proto;
|
||||
IP: for my $access_ip (keys %{$spa_config_dsts{$access_cmd}{$access_proto}}) {
|
||||
next IP unless $access_ip eq 'any' or $access_ip eq $dst_ip;
|
||||
PORT: for my $access_port
|
||||
(keys %{$spa_config_dsts{$access_cmd}{$access_proto}{$access_ip}}) {
|
||||
return 1 if $access_port eq 'any' or $d_port == $access_port;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub dispatch_fwknop() {
|
||||
my ($proto, $dst_ip, $d_port) = @_;
|
||||
|
||||
my $fwknop_cmd_str = "$fwknop_cmd -A $proto/$d_port -s -D " .
|
||||
"$dst_ip --get-key $key_file";
|
||||
|
||||
if ($fwknop_args_append) {
|
||||
$fwknop_cmd_str .= " $fwknop_args_append";
|
||||
} elsif ($fwknop_args_override) {
|
||||
$fwknop_cmd_str = "$fwknop_cmd $fwknop_args_override";
|
||||
}
|
||||
|
||||
print "[+] Executing: $fwknop_cmd_str\n" if $no_daemon;
|
||||
|
||||
open FWKNOP, "$fwknop_cmd_str |"
|
||||
or die "[*] Could not execute: $fwknop_cmd_str: $!";
|
||||
close FWKNOP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub daemonize() {
|
||||
|
||||
my $pid = fork();
|
||||
exit 0 if $pid;
|
||||
die "[*] $0: Couldn't fork: $!" unless defined $pid;
|
||||
POSIX::setsid() or die "[*] $0: Can't start a new session: $!";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub init() {
|
||||
|
||||
if ($launcher_config =~ m|^\~|) {
|
||||
|
||||
### get the path to the user's home directory
|
||||
&get_homedir() unless $home_dir;
|
||||
|
||||
$launcher_config =~ s|^\~|$home_dir|;
|
||||
}
|
||||
|
||||
my $found_key_file = 0;
|
||||
my $found_access_var = 0;
|
||||
|
||||
my $line_ctr = 1;
|
||||
open F, "< $launcher_config"
|
||||
or die "[*] Could not open $launcher_config: $!";
|
||||
while (<F>) {
|
||||
if (/^SPA_ACCESS\s+(\S+):(\S+):(\S+):(\S+);/) {
|
||||
my $cmd = $1;
|
||||
my $proto = lc($2);
|
||||
my $dst_ip = $3;
|
||||
my $d_port = $4;
|
||||
|
||||
### validate the protocol
|
||||
unless ($proto eq 'any'
|
||||
or $proto eq 'tcp'
|
||||
or $proto eq 'udp'
|
||||
) {
|
||||
die "[*] Invalid proto '$proto' ",
|
||||
"in $launcher_config at line: $line_ctr";
|
||||
}
|
||||
|
||||
### validate connection destination IP
|
||||
unless ($dst_ip eq 'any'
|
||||
or $dst_ip =~ /$ip_re/) {
|
||||
die "[*] Invalid IP '$dst_ip' ",
|
||||
"in $launcher_config at line: $line_ctr";
|
||||
}
|
||||
|
||||
### validate connection destination port
|
||||
unless ($d_port eq 'any'
|
||||
or $d_port =~ /^\d+$/) {
|
||||
die "[*] Invalid port '$d_port' ",
|
||||
"in $launcher_config at line: $line_ctr";
|
||||
}
|
||||
|
||||
### add this dst into the valid destinations cache
|
||||
$spa_config_dsts{$cmd}{$proto}{$dst_ip}{$d_port} = '';
|
||||
|
||||
$found_access_var = 1;
|
||||
|
||||
} elsif (/^KEY_FILE\s+(\S+);/) {
|
||||
$key_file = $1;
|
||||
|
||||
if ($key_file =~ m|^\~|) {
|
||||
&get_homedir() unless $home_dir;
|
||||
$key_file =~ s|^\~|$home_dir|;
|
||||
}
|
||||
|
||||
$found_key_file = 1;
|
||||
|
||||
} elsif (/^USER\s+(\S+);/) {
|
||||
### might have been set via the command line
|
||||
$user = $1 unless $user;
|
||||
|
||||
} elsif (/^FWKNOP_ARGS_APPEND\s+\"(.*)\";/) {
|
||||
$fwknop_args_append = $1;
|
||||
} elsif (/^FWKNOP_ARGS_OVERRIDE\s+\"(.*)\";/) {
|
||||
$fwknop_args_override = $1;
|
||||
}
|
||||
|
||||
$line_ctr++;
|
||||
}
|
||||
close F;
|
||||
|
||||
unless ($found_key_file) {
|
||||
die "[*] Must have a key file defined via KEY_FILE";
|
||||
}
|
||||
|
||||
unless ($found_access_var) {
|
||||
die "[*] Must have at least one SPA_ACCESS var defined";
|
||||
}
|
||||
|
||||
&get_username() unless $user;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_homedir() {
|
||||
|
||||
$home_dir = (getpwuid($<))[7];
|
||||
|
||||
unless ($home_dir) {
|
||||
$home_dir = $ENV{'HOME'} if defined $ENV{'HOME'};
|
||||
}
|
||||
|
||||
die "[*] Could not determine home directory. Use the -d <homedir> option."
|
||||
unless $home_dir;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_username() {
|
||||
|
||||
$user = (getpwuid($<))[0];
|
||||
|
||||
unless ($user) {
|
||||
if (defined $ENV{'USER'}) {
|
||||
$user = $ENV{'USER'};
|
||||
} elsif (defined $ENV{'USERNAME'}) {
|
||||
$user = $ENV{'USERNAME'};
|
||||
}
|
||||
}
|
||||
|
||||
die "[*] Could not determine username. Use the -u <user> option."
|
||||
unless $user;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub usage() {
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# This file defines various settings for the fwknop-launcher.pl script. By
|
||||
# using the fwknop-launcher, the user does not have to manually run the fwknop
|
||||
# client before trying to access a remote service - the launcher will execute
|
||||
# the client on behalf of the user when a connection is attempted (as seen
|
||||
# currently through lsof).
|
||||
#
|
||||
|
||||
### The main variable in this file is the SPA_ACCESS variable below (at least
|
||||
### one definition of SPA_ACCESS is required). It can be
|
||||
### specified multiple times - one on each line. The format for this variable
|
||||
### is as follows:
|
||||
#
|
||||
# SPA_ACCESS <command>:<proto>:<dst_ip>:<dst_port>;
|
||||
#
|
||||
# Example: SPA_ACCESS ssh:tcp:1.2.3.4:22;
|
||||
#
|
||||
# The string "any" can be used as a wildcard (see below). Note that it is
|
||||
# usually a good idea to restrict the launcher to sending SPA access requests
|
||||
# that the remote fwknopd daemon is allowing - such as port 22 for SSH access.
|
||||
#
|
||||
# SPA_ACCESS any:any:any:any;
|
||||
|
||||
SPA_ACCESS ssh:tcp:any:22;
|
||||
|
||||
KEY_FILE ~/.fwknop.key;
|
||||
|
||||
#FWKNOP_ARGS_APPEND "--server-port 50003";
|
||||
|
||||
#FWKNOP_ARGS_OVERRIDE "-A tcp/22 -s -D 127.0.0.1 --get-key /some/file";
|
||||
Reference in New Issue
Block a user