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