Re: Request for a Lockdown option

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

 



Simon Josefsson wrote in
 <87jzi1fg24.fsf@xxxxxxxxxxx>:
 |Jochen Bern <Jochen.Bern@xxxxxxxxx> writes:
 |> (And since you mention "port knocking", I'd like to repeat how fond I
 |> am of upgrading that original concept to a single-packet
 |> crypto-armored implementation like fwknop.)
 |
 |I am reluctantly considering to use some kind of port knocking mechanism
 |on some machines, however I really don't want to carry around shared
 |symmetric keys or setup yet another public/private key infrastructure
 |for that purpose.  I already have a working infrastructure for SSH
 |authentication.
 |
 |Does anyone know of any implementation that allows me to configure a
 |PGP/SSH/FIDO/TPM/whatever public key on the server side, and it then
 |only listens to signed port knocks from the corresponding private keys?

No, but for many years i do have a super simple port-knock server
to do the I/O plus sh(1)ell based client which can do .. whatever.
The whitelist default whitelists the source IP for 30 seconds.

   836 0% 1 0% /root/port-knock-server PORT-NUMBER /root/bin/port-knock-client.sh

But it "integrates" into and relies upon the firewall via

    # port_knock: input only server
    if [ -n "${SERVER}" ] && fwcore_has_i port_knock; then
            : ${FWCORE_PORT_KNOCK:?port_knock in FWCORE_IPROTOS needs FWCORE_PORT_KNOCK}
            if ipaddr_split ap "${FWCORE_PORT_KNOCK}"; then
                    add_rule -p udp --dport ${port} \
                            -m recent --name port_knock --set \
                            -m recent --name port_knock --rcheck --seconds 60 --reap --hitcount 2 \
                            -m recent --name alien_super --set -j DROP
                    add_rule -p udp --dport ${port} -j f_m1
            fi
    fi

Which allows only one packet per minute, otherwise the
alien_super rule will block you for 23+ hours.  I would not do it
without that, as it would then really be a door to attacks.

One could very well change the script to allow more keys, to
delete a key once used (commented out now), require to create
a new one, etc.  Of course, as it is a shell script, no setuid or
setgid works, people need to be trusted.

At earlier times it also started the public ssh instance as such,
that was only running on request.  Now that only within VPN, but
the port-knock is still needed due to super-strict firewall rules
and TCP etc which send packets and after a reboot of the server
these will be alien and thus cause blocks to apply...

 |I notice fwknop has PGP support, but it requires a private key on the
 |server side, and that's really annoying.  Instead of using public-key
 |encryption, shouldn't be possible to rely only on public-key signing
 |instead?  I already carry around a physical device with a public/private
 |keypair in it, and I need that for SSH public-key authentication anyway.
 |To avoid replay attacks, the signed data needs to be an ever increasing
 |counter or timestamp a'la HOTP/TOTP.
 |
 |I think this could be a good builtin functionality of OpenSSH, it
 |already has all of the public/private key trust infrastructure
 |available, what is missing is just the plumbing to connect it the
 |firewall.  Maybe it could go into a separate binary and not in the
 |default sshd though.  How about a sshfwkd?

With the possibilities that ssh-keygen -Y sign|verify have added,
one could easily adapt the server and client to send "user-name
MSG", so that the server could look into authorized_keys of
user-name and verify MSG, whatever that is.
(Or only use the current encryption thing for user-name, or
completely different and without user-name, but then a possible
large bunch of directories would need to be searched i guess.  To
be continue..)

Ie, for me personally sshfwkd would only make sense if it could be
made to listen on a different interface than the real SSH server.
I'll attach my very, very simple things, but which work for me
without any problems for years.  (They require IPv4.)

 |/Simon

--steffen
|
|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)
#!/bin/sh -
#@ 

: ${PORT:?"Need PORT"}
: ${HOST:=$(hostname)}
OUTFILE=.port-knock
: ${USER:?"Need USER"}
: ${GROUP:=$USER}
#PATH=$PATH:/usr/local/bin

blacklist() {
	logger -t /root/bin/port-knock-client.sh "black listing $1"
	/root/bin/net-qos.sh add alien_super "$1"
	/root/bin/net-qos.sh del port_knock "$1"
}

whitelist() {
	logger -t /root/bin/port-knock-client.sh "white listing $1 for 30 seconds"
	/root/bin/net-qos.sh del alien_super "$1"
	/root/bin/net-qos.sh del sshorvpn "$1"
	/root/bin/net-qos.sh del port_knock "$1"
	/root/bin/net-qos.sh eval fw_rules_ins 1 i^a^-src "$1"
#	 /root/bin/net-qos.sh start-daemon sshd
	(
		sleep 30
		/root/bin/net-qos.sh eval eval fw_rules_del i^a^-src "$1"
	) </dev/null >/dev/null 2>&1 &
}

case "$1" in
create)
	ttye= ttyr=
#	 if command -v stty >/dev/null 2>&1; then
#		 ttye="stty -echo"
#		 ttyr="stty $(stty -g)"
#		 trap "$ttyr" EXIT
#		 trap exit INT QUIT TERM
#	 fi
	while [ -z "$data" ]; do
		printf 'Enter data: '
		$ttye
		read data
		$ttyr
		printf '\n'
	done
	[ -n "$ttye" ] && trap '' EXIT

	{
		k=$(dd if=/dev/urandom bs=1 count=512 | LC_ALL=C tr -cd 'a-zA-Z0-9_.,=@%^+-')
	} 2>/dev/null

	umask 0022
	printf 'data='"'"'%s'"'"'\nk='"'"'%s'"'"'\ne='"'"'%s'"'"'\n' \
		"$data" "$k" "$(echo $data | openssl enc -aes256 -k ${k} -a -A -pbkdf2 -e)" \
		> "$OUTFILE"
	chown $USER:$GROUP "$OUTFILE"
	chmod 0660 "$OUTFILE"
	;;
verify)
	mydat=$2
	ip=$3
	if [ -s "$OUTFILE" ]; then
		. "$OUTFILE"
		if echo "${mydat}" | openssl enc -aes256 -k "${k}" -a -A -pbkdf2 -d >/dev/null 2>&1; then
#			 : > "$OUTFILE"
			whitelist "$ip"
			exit 0
		fi
	fi
	blacklist "$ip"
	;;
blacklist)
	ip=$2
	blacklist "$ip"
	;;
knock)
	if command -v bash >/dev/null 2>&1; then
		if [ -s "$OUTFILE" ]; then :; else
			OUTFILE=$(basename "$OUTFILE")
			if [ -s "$OUTFILE" ]; then :; else
				echo >&2 'Knocking requires '$OUTFILE
				exit 1
			fi
		fi
		</dev/null HOST="$HOST" PORT="$PORT" OUTFILE="$OUTFILE" bash -c '
			. "$OUTFILE"
			while :; do
				printf 1..
				printf "%s" "$e" > /dev/udp/"$HOST"/"$PORT"
				printf " done\n"
				sleep 1
				ping -4 -c 1 -w1 "$HOST" && break
				echo "Did not work out, sleeping 60 seconds and retrying"
				sleep 60
			done
		'
	else
		echo >&2 'Knocking requires bash'
		exit 1
	fi
	;;
*)
	echo >&2 'Synopsis: '$0' create|verify DATA IP|blacklist IP|knock'
	exit 1
	;;
esac

# s-sht-mode
/*@ port-knock-server.c: invoke a script upon connection
 *@ Synopsis: port-knock-server [-v] port command
 *@ "command" arguments will be "verify|blacklist" "IPv4 address"
 *@ TODO IPv6
 *
 * Public Domain.
 */
#define _GNU_SOURCE

#include <sys/socket.h>
#include <sys/types.h>

#include <arpa/inet.h>
#include <netinet/in.h>

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int a_verbose;
static int a_sigterm_seen;

static void a_sigterm(int sig);

int
main(int argc, char **argv){
	char dbuf[128], nbuf[INET_ADDRSTRLEN +1], cbuf[sizeof(dbuf) + sizeof(nbuf) + 128];
	struct sigaction siac;
	struct sockaddr_in soa4;
	socklen_t soal;
	size_t hooklen;
	char const *hook;
	int es, port, sofd;

	es = 1;

	if(argc > 3 && !strcmp(argv[1], "-v")){
		a_verbose = 1;
		--argc, ++argv;
	}
	if(argc != 3){
		fprintf(stderr, "Synopsis: %s [-v] port command\n", argv[0]);
		goto jleave;
	}
	++es;

	port = atoi(argv[1]);
	if(port <= 0 || port > 65535){
		fprintf(stderr, "Bad port: %s -> %d\n", argv[1], port);
		goto jleave;
	}
	++es;

	if((hooklen = strlen(hook = argv[2])) +1 >= sizeof(dbuf)){
		fprintf(stderr, "Command too long: %s\n", hook);
		goto jleave;
	}
	++es;

	if(chdir("/") == -1){
		fprintf(stderr, "Cannot chdir(2) to /\n");
		goto jleave;
	}
	++es;

	while((sofd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){
		if(errno == EINTR)
			continue;
		goto jleave;
	}
	++es;

	memset(&soa4, 0, soal = sizeof(soa4));
	soa4.sin_family = AF_INET;
	soa4.sin_port = htons(port);
	soa4.sin_addr.s_addr = INADDR_ANY;

	while(bind(sofd, (struct sockaddr const*)&soa4, soal) == -1){
		if(errno != EINTR)
			goto jclose;
	}
	es = 0;

	memset(&siac, 0, sizeof siac);
	siac.sa_handler = &a_sigterm;
	sigemptyset(&siac.sa_mask);
	sigaction(SIGTERM, &siac, NULL);

	while(!a_sigterm_seen){
		ssize_t ss;

		ss = recvfrom(sofd, dbuf, sizeof(dbuf) -1, 0, (struct sockaddr*)&soa4, &soal);

		if(ss > 0){
			char const *ian;

			dbuf[(size_t)ss] = '\0';

			if((ian = inet_ntop(AF_INET, &soa4.sin_addr, nbuf, sizeof(nbuf))) != NULL){
				char *cmd, *cp;

				cmd = cbuf;

				memcpy(cmd, hook, hooklen);
				cmd += hooklen;
				*cmd++ = ' ';

				for(cp = dbuf;; ++cp){
					if(*cp == '\0'){
						if((ssize_t)(cp - dbuf) != ss)
							goto jblacklist;
						else{
							memcpy(cmd, "verify ", sizeof("verify ") -1);
							cmd += sizeof("verify ") -1;
							memcpy(cmd, dbuf, (size_t)ss);
							cmd += ss;
						}
						break;
					}else if((*cp & 0x80) || iscntrl(*cp)){
jblacklist:
						memcpy(cmd, "blacklist", sizeof("blacklist") -1);
						cmd += sizeof("blacklist") -1;
						break;
					}
				}

				*cmd++ = ' ';
				memcpy(cmd, ian, strlen(ian) +1);

				if(a_verbose)
					fprintf(stderr, "COMMAND: %s\n", cbuf);
				system(cbuf);
			}
		}
	}

jclose:
	close(sofd);
jleave:
	return es;
}

static void
a_sigterm(int sig){
	(void)sig;
	a_sigterm_seen = 1;
}

/* s-itt-mode */
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@xxxxxxxxxxx
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux