Skip to content

Commit f55fe3d

Browse files
committed
Add an otp-setup helper and an enrollment banner
Enrolling meant typing a google-authenticator command line with the right secret path and answering six prompts. otp-setup wraps that: it writes to the same file the PAM module reads, backs up an existing secret before replacing it, and --show prints the secret and scratch codes again. It also drops the confirmation step when there is no terminal, so it works under docker exec instead of failing on a prompt it cannot read. Because the module lets an unenrolled user straight in, nothing told them the secret was still missing. A profile.d snippet now says so on login, and stops once the secret exists.
1 parent ee1db2d commit f55fe3d

5 files changed

Lines changed: 103 additions & 7 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ You can optionally set the docker argument `hostname`
8484

8585
Setting `OTP_ACCESS` to `true` requires a time based one time password, on top of the ssh key, or on top of the password when `PASSWORD_ACCESS` is enabled as well. The codes are generated by any TOTP application, and the secret is kept in `/config/.google_authenticator` so it survives a container recreation.
8686

87-
As long as that secret does not exist, the user is let in without being asked for a code, which is what makes the enrollment possible. Connect as usual and run:
87+
As long as that secret does not exist, the user is let in without being asked for a code, which is what makes the enrollment possible. A banner on login says so. Connect as usual and run:
8888
```
89-
google-authenticator -t -d -f -r 3 -R 30 -W -s /config/.google_authenticator
89+
otp-setup
9090
```
9191

92-
Scan the QR code with your authenticator application and write down the scratch codes, they are the only way back in if you lose it. Every login from that point on asks for a verification code.
92+
Scan the QR code with your authenticator application and write down the scratch codes, they are the only way back in if you lose it. Every login from that point on asks for a verification code. `otp-setup --show` prints the secret again, `otp-setup` run a second time replaces it after asking.
9393

9494
## Key Generation
9595

readme-vars.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ app_setup_block: |
6868
6969
Setting `OTP_ACCESS` to `true` requires a time based one time password, on top of the ssh key, or on top of the password when `PASSWORD_ACCESS` is enabled as well. The codes are generated by any TOTP application, and the secret is kept in `/config/.google_authenticator` so it survives a container recreation.
7070
71-
As long as that secret does not exist, the user is let in without being asked for a code, which is what makes the enrollment possible. Connect as usual and run:
71+
As long as that secret does not exist, the user is let in without being asked for a code, which is what makes the enrollment possible. A banner on login says so. Connect as usual and run:
7272
```
73-
google-authenticator -t -d -f -r 3 -R 30 -W -s /config/.google_authenticator
73+
otp-setup
7474
```
7575
76-
Scan the QR code with your authenticator application and write down the scratch codes, they are the only way back in if you lose it. Every login from that point on asks for a verification code.
76+
Scan the QR code with your authenticator application and write down the scratch codes, they are the only way back in if you lose it. Every login from that point on asks for a verification code. `otp-setup --show` prints the secret again, `otp-setup` run a second time replaces it after asking.
7777
7878
## Key Generation
7979

root/etc/profile.d/otp-setup.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Point a user that OTP applies to, but who has not enrolled yet, at otp-setup.
2+
# Until they do, pam_google_authenticator lets them in without asking for a
3+
# code, so nothing else would tell them the secret is still missing.
4+
5+
[ -z "$SSH_CONNECTION" ] && return 0
6+
grep -q "pam_google_authenticator.so" /etc/pam.d/sshd 2>/dev/null || return 0
7+
[ -f /config/.google_authenticator ] && return 0
8+
9+
cat << 'BANNER'
10+
11+
────────────────────────────────────────────────────────────
12+
Two factor authentication is enabled on this server, but
13+
you have not set up your authenticator application yet.
14+
15+
Run:
16+
otp-setup
17+
18+
to get your QR code and your scratch codes. Your next
19+
login then asks for a 6 digit code on top of your key.
20+
────────────────────────────────────────────────────────────
21+
22+
BANNER

root/etc/s6-overlay/s6-rc.d/init-openssh-server-config/run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ EOF
149149
echo "OTP (2FA) ssh access is enabled."
150150
else
151151
echo "OTP (2FA) ssh access is enabled, but ${USER_NAME} has no secret yet and is not prompted for a code."
152-
echo "Run 'google-authenticator -t -d -f -r 3 -R 30 -W -s ${OTP_SECRET}' as ${USER_NAME} to enroll."
152+
echo "Log in and run 'otp-setup' to enroll."
153153
fi
154154
else
155155
echo "OTP (2FA) ssh access is disabled."

root/usr/local/bin/otp-setup

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#! /bin/bash
2+
# Generate, regenerate or display the TOTP secret used by OTP_ACCESS.
3+
4+
set -e
5+
6+
# absolute, and matching the secret= passed to pam_google_authenticator, so the
7+
# helper and the PAM module always agree on the file regardless of $HOME
8+
SECRET_FILE=/config/.google_authenticator
9+
10+
usage() {
11+
cat << EOF
12+
Usage:
13+
otp-setup Generate a TOTP secret, or regenerate it after asking.
14+
Prints the QR code, the secret and the scratch codes.
15+
otp-setup --show Print the current secret file again.
16+
otp-setup --help Show this help.
17+
EOF
18+
}
19+
20+
case "${1:-}" in
21+
--show|-s)
22+
if [[ ! -f "$SECRET_FILE" ]]; then
23+
echo "No OTP secret yet. Run 'otp-setup' to create one."
24+
exit 1
25+
fi
26+
echo "This prints your secret and scratch codes in clear text. Make sure"
27+
echo "nobody is watching and that this session is not being recorded."
28+
echo
29+
echo "=== ${SECRET_FILE} ==="
30+
echo "First line is the base32 secret, the trailing digit lines are the"
31+
echo "emergency scratch codes."
32+
echo
33+
cat "$SECRET_FILE"
34+
;;
35+
--help|-h)
36+
usage
37+
;;
38+
"")
39+
if [[ -f "$SECRET_FILE" ]]; then
40+
read -r -p "A secret already exists in ${SECRET_FILE}. Regenerating invalidates the old one. Continue? [y/N] " answer || answer=""
41+
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
42+
echo "Aborted."
43+
exit 0
44+
fi
45+
backup="${SECRET_FILE}.bak.$(date +%s)"
46+
cp "$SECRET_FILE" "$backup"
47+
echo "Old secret kept in ${backup}"
48+
fi
49+
50+
# typing the first code back catches a QR that was scanned wrong, but
51+
# there is nothing to type it with when this runs without a terminal,
52+
# as it does under docker exec
53+
confirm=()
54+
if [[ ! -t 0 ]]; then
55+
confirm=(--no-confirm)
56+
fi
57+
58+
# -t time based, -d one use per code, -f write without asking,
59+
# -r 3 -R 30 three attempts per 30s, -W narrow the accepted time window
60+
google-authenticator -t -d -f -r 3 -R 30 -W "${confirm[@]}" -s "$SECRET_FILE"
61+
chmod 600 "$SECRET_FILE"
62+
63+
echo
64+
echo "=== setup complete ==="
65+
echo "Scan the QR code above with your authenticator application."
66+
echo "Write down the scratch codes, they are the only way back in if you"
67+
echo "lose the application."
68+
echo "Your next login asks for a 6 digit code."
69+
;;
70+
*)
71+
usage
72+
exit 2
73+
;;
74+
esac

0 commit comments

Comments
 (0)