From 4707d391430ebacd08d00bfcf36360fe982b195a Mon Sep 17 00:00:00 2001 From: steering7253 Date: Mon, 18 May 2026 06:12:21 -0600 Subject: add gpg auth --- crond.autopeer | 2 +- gpg-verify | 57 ++++++++++++++++++++++++++++ install.sh | 3 +- new_user_created.sh | 9 ++++- pam_autopeer/Makefile | 11 ++++++ pam_autopeer/pam_autopeer.c | 87 +++++++++++++++++++++++++++++++++++++++++++ pam_autopeer/pam_autopeer.o | Bin 0 -> 5128 bytes pam_autopeer/pam_autopeer.so | Bin 0 -> 16168 bytes pamd.autopeer | 56 ++++++++++++++++++++++++++++ sshd_config | 8 +++- 10 files changed, 227 insertions(+), 6 deletions(-) create mode 100755 gpg-verify create mode 100644 pam_autopeer/Makefile create mode 100644 pam_autopeer/pam_autopeer.c create mode 100644 pam_autopeer/pam_autopeer.o create mode 100755 pam_autopeer/pam_autopeer.so create mode 100644 pamd.autopeer diff --git a/crond.autopeer b/crond.autopeer index e2e8da8..c582187 100644 --- a/crond.autopeer +++ b/crond.autopeer @@ -1,6 +1,6 @@ 47 * * * * root git -c merge.verifysignatures=false -c core.sshcommand="ssh -i /opt/autopeer/id_autopeer" -C /opt/autopeer/dn42-registry pull --quiet */5 * * * * root /opt/autopeer/cronjob.py >/dev/null -0 * * * * root git -C /opt/autopeer pull --quiet +0 * * * * root git -C /opt/autopeer pull --quiet; cd /opt/autopeer/pam_autopeer; make */15 * * * * root curl -sfSLR -o /etc/bird/roa_dn42.conf -z /etc/bird/roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird2_4.conf && /usr/sbin/birdc configure > /dev/null */15 * * * * root curl -sfSLR -o /etc/bird/roa_dn42_v6.conf -z /etc/bird/roa_dn42_v6.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf && /usr/sbin/birdc configure > /dev/null diff --git a/gpg-verify b/gpg-verify new file mode 100755 index 0000000..615a499 --- /dev/null +++ b/gpg-verify @@ -0,0 +1,57 @@ +#!/bin/bash + +set -o pipefail +exec &>>/var/log/gpg-verify.log +perl -MData::Dumper -e 'print Dumper(\@ARGV);' "$@" + +if [ $# -ne 4 ]; then + exit 2 +fi + +username="$1" +nonce="$2" +key="$3" +sig="$4" + +keyring="$(mktemp)" +curl -sL "$key" | gpg -o - --dearmor >"$keyring" || exit 7 +gpgv_out="$(gpgv --keyring "$keyring" <(echo "$sig" | sed 's/-----BEGIN PGP SIGNATURE-----/&\n\n/') <(echo "$nonce") 2>&1)" +verified_key="$(echo "$gpgv_out" |& grep -oP 'gpgv:\s*using \S+ key \K.*')" + +echo "keyring: $keyring" +echo "$gpgv_out" + +if ! echo "$gpgv_out" | grep -qP 'gpgv: Good signature from'; then + exit 3 +fi + +if [ -z "$verified_key" ]; then + exit 4 +fi + +if [ "$username" = "new" ]; then + if new_user="$(grep -l -s -r -P '^\s*auth:\s*pgp-fingerprint\s+\Q'"$verified_key"'\E(\s|$)' /opt/autopeer/dn42-registry/data/mntner/ | perl -ne 's@^.*/@@; s@-MNT$@@; print lc;' | head -1)"; then + echo "making $new_user"; exit 0 + if getent passwd "$new_user" &>/dev/null; then + exit 0 + else + echo "[autopeer $(hostname -f)] New user being created: $new_user from $key $connection" | socat stdio "$NOTIFY_TO" + /usr/sbin/adduser --disabled-password --quiet --comment "created at $(date +%s) by $key ${connection//:/_}" --ingroup autopeer "$new_user" + /usr/sbin/adduser "$new_user" bird + ( umask 0077; touch "/var/log/autopeer/$new_user".{tim,io}; ) + chown "$new_user" "/var/log/autopeer/$new_user".{tim,io} + exit 0 + fi + else + exit 5 + fi +else + if grep -q -s -P '^\s*auth:\s*pgp-fingerprint\s+\Q'"$verified_key"'\E(\s|$)' /opt/autopeer/dn42-registry/data/mntner/"$(echo "$username" | perl -ne 's@$@-MNT@; print uc;')"; then + exit 0 + else + exit 6 + fi +fi + + +exit 1 diff --git a/install.sh b/install.sh index 2119e5b..de114dd 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ read -p "Press enter once you've done that..." apt install -y git # you needed this to clone apt install -y vim curl wget man-db whois bind9 bind9-dnsutils bird2 # suggestions -apt install -y python3 wireguard-tools cron socat # dependencies +apt install -y python3 wireguard-tools cron socat make libpam-dev # dependencies ln -s /opt/autopeer/sshd_config /etc/ssh/sshd_config.d/autopeer.conf systemctl reload ssh addgroup autopeer @@ -22,6 +22,7 @@ git -c core.sshcommand="ssh -i /opt/autopeer/id_autopeer" clone git@git.dn42.dev git -C /opt/autopeer/dn42-registry config core.sshCommand "ssh -i /opt/autopeer/id_autopeer" ln -s /opt/autopeer/crond.autopeer /etc/cron.d/autopeer +ln -s /opt/autopeer/pamd.autopeer /etc/pam.d/autopeer ln -s /opt/autopeer/sysctld.autopeer /etc/sysctl.d/99-dn42.conf systemctl restart systemd-sysctl diff --git a/new_user_created.sh b/new_user_created.sh index e00a3bb..802d424 100755 --- a/new_user_created.sh +++ b/new_user_created.sh @@ -1,7 +1,12 @@ #!/bin/sh -read method type key <$SSH_USER_AUTH -user="$(grep -l -s -r -P '^\s*auth:\s*\Q'"$type"'\E\s+\Q'"$key"'\E(\s|$)' /opt/autopeer/dn42-registry/data/mntner/ | perl -ne 's@^.*/@@; s@-MNT$@@; print lc;' | head -1)" +if [ -z "$NEW_USER" ]; then # ssh auth + read method type key <$SSH_USER_AUTH + user="$(grep -l -s -r -P '^\s*auth:\s*\Q'"$type"'\E\s+\Q'"$key"'\E(\s|$)' /opt/autopeer/dn42-registry/data/mntner/ | perl -ne 's@^.*/@@; s@-MNT$@@; print lc;' | head -1)" +else # pgp auth + fpr="$(curl -s "$NEW_USER" | gpg --show-keys --with-colons | grep -Po 'fpr:*\K[^:]+' | head -1)" + user="$(grep -l -s -r -P '^\s*auth:\s*pgp-fingerprint\s+\Q'"$fpr"'\E(\s|$)' /opt/autopeer/dn42-registry/data/mntner/ | perl -ne 's@^.*/@@; s@-MNT$@@; print lc;' | head -1)" +fi if getent passwd "$user" >/dev/null 2>&1; then echo "Your account has been created, go ahead and log in:" diff --git a/pam_autopeer/Makefile b/pam_autopeer/Makefile new file mode 100644 index 0000000..355ef35 --- /dev/null +++ b/pam_autopeer/Makefile @@ -0,0 +1,11 @@ +CFLAGS = -fPIC -Wall -Wextra -Wno-unused-parameter +LDFLAGS = -shared + +all: pam_autopeer.so + +pam_autopeer.so: pam_autopeer.o + $(CC) $(LDFLAGS) -o pam_autopeer.so pam_autopeer.c + + +clean: + rm -f pam_autopeer.so pam_autopeer.o diff --git a/pam_autopeer/pam_autopeer.c b/pam_autopeer/pam_autopeer.c new file mode 100644 index 0000000..cf3ee11 --- /dev/null +++ b/pam_autopeer/pam_autopeer.c @@ -0,0 +1,87 @@ +/* +gcc -fPIC -c pam_module.c +gcc -shared -o pam_module.so pam_module.o -lpam +*/ + +//#include +#include +#include +#include +#include +#include +#include +#include +#include + +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { + return PAM_SUCCESS; +} +/* +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { + return PAM_IGNORE; +} +*/ + +static const char *errors[] = { + NULL, + "internal error (fell through)", + "internal error (wrong # args)", + "no good signature foud in gpgv output", + "no verified key found in gpgv output", + "new user: key not found in registry", + "existing user: key not found in this mntner", + "downloading public key failed", +}; + + +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { + const char *username; + int retval = pam_get_user(pamh, &username, "Username: "); + if (retval != PAM_SUCCESS) + return PAM_SYSTEM_ERR; + + int err; + unsigned char nonce[10]; + RAND_bytes(nonce, 10); + char s_nonce[21]; + sprintf(s_nonce, "%x%x%x%x%x%x%x%x%x%x", nonce[0], nonce[1], nonce[2], nonce[3], nonce[4], nonce[5], nonce[6], nonce[7], nonce[8], nonce[9]); + char *key, *sig; + pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &key, "Please enter a URL to download your ASCII-armored key:\n"); + pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &sig, "Please enter a ASCII-armored detached signature of the nonce '%s\\n', without newlines, i.e. `gpg -a --detach-sign -o - -u $YOUR_KEY <(echo %s) | tr -d '\\n'; echo`:\n", s_nonce, s_nonce); + + int pid; + switch (pid = fork()) { + case -1: // error + err = errno; + pam_syslog(pamh, LOG_ERR, "Error fork: %s", strerror(err)); + pam_error(pamh, "Error fork: %s", strerror(err)); + return PAM_SYSTEM_ERR; + case 0: // child + execl("/opt/autopeer/gpg-verify", "/opt/autopeer/gpg-verify", username, s_nonce, key, sig, (char *)NULL); + // if (execl == -1): + err = errno; + pam_syslog(pamh, LOG_ERR, "Error execl: %s", strerror(err)); + pam_error(pamh, "Error execl: %s", strerror(err)); + _exit(PAM_SYSTEM_ERR); + default: // parent + int wstatus; + if (waitpid(pid, &wstatus, 0) == -1) { + err = errno; + pam_syslog(pamh, LOG_ERR, "Error waitpid: %s", strerror(err)); + pam_error(pamh, "Error waitpid: %s", strerror(err)); + } + if (WIFEXITED(wstatus)) { + if (!WEXITSTATUS(wstatus)) { + char newu[100]; + snprintf(newu, sizeof(newu), "NEW_USER=%s", key); + pam_putenv(pamh, newu); + return PAM_SUCCESS; + } else { + pam_syslog(pamh, LOG_ERR, "Error auth: %s (%d)", WEXITSTATUS(wstatus) < sizeof(errors)/sizeof(errors[0]) ? errors[WEXITSTATUS(wstatus)] : "", WEXITSTATUS(wstatus)); + pam_error(pamh, "Error auth: %s (%d)", WEXITSTATUS(wstatus) < sizeof(errors)/sizeof(errors[0]) ? errors[WEXITSTATUS(wstatus)] : "", WEXITSTATUS(wstatus)); + } + } + return PAM_SYSTEM_ERR; + } + return PAM_SYSTEM_ERR; +} diff --git a/pam_autopeer/pam_autopeer.o b/pam_autopeer/pam_autopeer.o new file mode 100644 index 0000000..bb65a05 Binary files /dev/null and b/pam_autopeer/pam_autopeer.o differ diff --git a/pam_autopeer/pam_autopeer.so b/pam_autopeer/pam_autopeer.so new file mode 100755 index 0000000..ec5f12a Binary files /dev/null and b/pam_autopeer/pam_autopeer.so differ diff --git a/pamd.autopeer b/pamd.autopeer new file mode 100644 index 0000000..2a0c5eb --- /dev/null +++ b/pamd.autopeer @@ -0,0 +1,56 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +auth required /opt/autopeer/pam_autopeer/pam_autopeer.so +#@include common-auth + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/sshd_config b/sshd_config index 1afda01..0ca90dc 100644 --- a/sshd_config +++ b/sshd_config @@ -1,7 +1,9 @@ Match user new + PAMServiceName autopeer + KbdInteractiveAuthentication yes AuthorizedKeysCommand /opt/autopeer/authorized_keys.sh %u %t %k %C AuthorizedKeysCommandUser root - AuthenticationMethods publickey + AuthenticationMethods publickey keyboard-interactive AllowAgentForwarding no AllowStreamLocalForwarding no AllowTcpForwarding no @@ -12,9 +14,11 @@ Match user new ForceCommand /opt/autopeer/new_user_created.sh Match group autopeer + PAMServiceName autopeer + KbdInteractiveAuthentication yes AuthorizedKeysCommand /opt/autopeer/authorized_keys.sh %u %t %k %C AuthorizedKeysCommandUser root - AuthenticationMethods publickey + AuthenticationMethods publickey keyboard-interactive AllowAgentForwarding no AllowStreamLocalForwarding no AllowTcpForwarding no -- cgit v1.3.1-10-gc9f91