feat: pin mx host key in keygen
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ name=mail-login
|
|||||||
id=mail-login
|
id=mail-login
|
||||||
type=user
|
type=user
|
||||||
author=HITME.PL
|
author=HITME.PL
|
||||||
version=1.0.8
|
version=1.0.9
|
||||||
active=no
|
active=no
|
||||||
installed=no
|
installed=no
|
||||||
user_run_as=root
|
user_run_as=root
|
||||||
|
|||||||
+92
-1
@@ -5,8 +5,24 @@ KEY_DIR="${MAIL_LOGIN_KEY_DIR:-/usr/local/hitme_plugins/mail-login/secrets}"
|
|||||||
KEY_NAME="${MAIL_LOGIN_KEY_NAME:-mx1_ed25519}"
|
KEY_NAME="${MAIL_LOGIN_KEY_NAME:-mx1_ed25519}"
|
||||||
KEY_PATH="$KEY_DIR/$KEY_NAME"
|
KEY_PATH="$KEY_DIR/$KEY_NAME"
|
||||||
MX_MODE="${MX_LOGIN_MODE:-1}"
|
MX_MODE="${MX_LOGIN_MODE:-1}"
|
||||||
|
SETTINGS_FILE="${MAIL_LOGIN_SETTINGS_FILE:-/usr/local/directadmin/plugins/mail-login/plugin-settings.conf}"
|
||||||
HELPER_COMMAND="${MAIL_LOGIN_HELPER_COMMAND:-/usr/local/hitme_plugins/mail-login-helper/bin/create-login-url}"
|
HELPER_COMMAND="${MAIL_LOGIN_HELPER_COMMAND:-/usr/local/hitme_plugins/mail-login-helper/bin/create-login-url}"
|
||||||
|
|
||||||
|
config_value() {
|
||||||
|
key="$1"
|
||||||
|
if [ ! -f "$SETTINGS_FILE" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
awk -F= -v key="$key" '
|
||||||
|
$1 == key {
|
||||||
|
value = substr($0, index($0, "=") + 1)
|
||||||
|
gsub(/^[ \t]+|[ \t]+$/, "", value)
|
||||||
|
print value
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$SETTINGS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
derive_servername() {
|
derive_servername() {
|
||||||
if [ -n "${SERVERNAME:-}" ]; then
|
if [ -n "${SERVERNAME:-}" ]; then
|
||||||
printf '%s' "$SERVERNAME"
|
printf '%s' "$SERVERNAME"
|
||||||
@@ -45,12 +61,64 @@ detect_source_ip() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pin_mx_host_key() {
|
||||||
|
if [ -z "$MX_HOST" ]; then
|
||||||
|
HOST_KEY_STATUS="skipped: MAIL_LOGIN_SSH_HOST is not configured"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! command -v ssh-keyscan >/dev/null 2>&1; then
|
||||||
|
HOST_KEY_STATUS="skipped: ssh-keyscan is not available"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
known_dir="$(dirname "$KNOWN_HOSTS")"
|
||||||
|
mkdir -p "$known_dir"
|
||||||
|
chmod 700 "$known_dir"
|
||||||
|
touch "$KNOWN_HOSTS"
|
||||||
|
chmod 600 "$KNOWN_HOSTS"
|
||||||
|
|
||||||
|
lookup="$MX_HOST"
|
||||||
|
if [ "$MX_PORT" != "22" ]; then
|
||||||
|
lookup="[$MX_HOST]:$MX_PORT"
|
||||||
|
fi
|
||||||
|
if ssh-keygen -F "$lookup" -f "$KNOWN_HOSTS" >/dev/null 2>&1; then
|
||||||
|
HOST_KEY_STATUS="already present for $lookup"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp="$KNOWN_HOSTS.$$"
|
||||||
|
if ssh-keyscan -T "$SCAN_TIMEOUT" -p "$MX_PORT" -t ed25519 "$MX_HOST" > "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
|
||||||
|
cat "$tmp" >> "$KNOWN_HOSTS"
|
||||||
|
chmod 600 "$KNOWN_HOSTS"
|
||||||
|
HOST_KEY_FINGERPRINT="$(ssh-keygen -lf "$tmp" 2>/dev/null | head -n 1 || true)"
|
||||||
|
HOST_KEY_STATUS="pinned $lookup"
|
||||||
|
rm -f "$tmp"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$tmp"
|
||||||
|
HOST_KEY_STATUS="failed: cannot fetch ED25519 host key for $MX_HOST:$MX_PORT"
|
||||||
|
}
|
||||||
|
|
||||||
SERVER_LABEL="$(clean_label "$(derive_servername)")"
|
SERVER_LABEL="$(clean_label "$(derive_servername)")"
|
||||||
if [ -z "$SERVER_LABEL" ]; then
|
if [ -z "$SERVER_LABEL" ]; then
|
||||||
echo "Cannot determine SERVERNAME" >&2
|
echo "Cannot determine SERVERNAME" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
CONFIG_SSH_HOST="$(config_value MAIL_LOGIN_SSH_HOST || true)"
|
||||||
|
CONFIG_SSH_PORT="$(config_value MAIL_LOGIN_SSH_PORT || true)"
|
||||||
|
CONFIG_KNOWN_HOSTS="$(config_value MAIL_LOGIN_SSH_KNOWN_HOSTS || true)"
|
||||||
|
CONFIG_SCAN_TIMEOUT="$(config_value MAIL_LOGIN_CONNECT_TIMEOUT_SECONDS || true)"
|
||||||
|
|
||||||
|
MX_HOST="${MAIL_LOGIN_SSH_HOST:-$CONFIG_SSH_HOST}"
|
||||||
|
MX_PORT="${MAIL_LOGIN_SSH_PORT:-$CONFIG_SSH_PORT}"
|
||||||
|
MX_PORT="${MX_PORT:-22}"
|
||||||
|
KNOWN_HOSTS="${MAIL_LOGIN_SSH_KNOWN_HOSTS:-$CONFIG_KNOWN_HOSTS}"
|
||||||
|
KNOWN_HOSTS="${KNOWN_HOSTS:-$KEY_DIR/known_hosts}"
|
||||||
|
SCAN_TIMEOUT="${MAIL_LOGIN_CONNECT_TIMEOUT_SECONDS:-$CONFIG_SCAN_TIMEOUT}"
|
||||||
|
SCAN_TIMEOUT="${SCAN_TIMEOUT:-5}"
|
||||||
|
|
||||||
case "$MX_MODE" in
|
case "$MX_MODE" in
|
||||||
1|2) ;;
|
1|2) ;;
|
||||||
*)
|
*)
|
||||||
@@ -59,6 +127,20 @@ case "$MX_MODE" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
case "$MX_PORT" in
|
||||||
|
*[!0-9]*|'')
|
||||||
|
echo "MAIL_LOGIN_SSH_PORT must be numeric" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$SCAN_TIMEOUT" in
|
||||||
|
*[!0-9]*|'')
|
||||||
|
echo "MAIL_LOGIN_CONNECT_TIMEOUT_SECONDS must be numeric" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
SOURCE_IP="$(detect_source_ip || true)"
|
SOURCE_IP="$(detect_source_ip || true)"
|
||||||
case "$SOURCE_IP" in
|
case "$SOURCE_IP" in
|
||||||
*[!A-Za-z0-9:./_-]*|'')
|
*[!A-Za-z0-9:./_-]*|'')
|
||||||
@@ -82,6 +164,10 @@ fi
|
|||||||
chmod 600 "$KEY_PATH"
|
chmod 600 "$KEY_PATH"
|
||||||
chmod 644 "$KEY_PATH.pub"
|
chmod 644 "$KEY_PATH.pub"
|
||||||
|
|
||||||
|
HOST_KEY_STATUS=""
|
||||||
|
HOST_KEY_FINGERPRINT=""
|
||||||
|
pin_mx_host_key
|
||||||
|
|
||||||
PUB_KEY="$(cat "$KEY_PATH.pub")"
|
PUB_KEY="$(cat "$KEY_PATH.pub")"
|
||||||
BASE_OPTIONS="from=\"$SOURCE_IP\",restrict,no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty"
|
BASE_OPTIONS="from=\"$SOURCE_IP\",restrict,no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty"
|
||||||
|
|
||||||
@@ -102,7 +188,12 @@ $KEY_PATH.pub
|
|||||||
|
|
||||||
Set this in plugin-settings.conf:
|
Set this in plugin-settings.conf:
|
||||||
MAIL_LOGIN_SSH_IDENTITY=$KEY_PATH
|
MAIL_LOGIN_SSH_IDENTITY=$KEY_PATH
|
||||||
MAIL_LOGIN_SSH_KNOWN_HOSTS=/root/.ssh/known_hosts
|
MAIL_LOGIN_SSH_KNOWN_HOSTS=$KNOWN_HOSTS
|
||||||
|
|
||||||
|
MX known_hosts:
|
||||||
|
$KNOWN_HOSTS
|
||||||
|
Pinned MX host key: $HOST_KEY_STATUS
|
||||||
|
${HOST_KEY_FINGERPRINT}
|
||||||
|
|
||||||
Add this line to /root/.ssh/authorized_keys on mx1:
|
Add this line to /root/.ssh/authorized_keys on mx1:
|
||||||
$AUTH_OPTIONS $PUB_KEY $COMMENT
|
$AUTH_OPTIONS $PUB_KEY $COMMENT
|
||||||
|
|||||||
@@ -47,6 +47,51 @@ test('keygen script detects source ip without parameters', function (): void {
|
|||||||
assert_not_contains('SOURCE_SERVER_PUBLIC_IP', $output);
|
assert_not_contains('SOURCE_SERVER_PUBLIC_IP', $output);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keygen script pins mx host key from plugin settings', function (): void {
|
||||||
|
$keyDir = TEST_TMP . '/keys-known-hosts';
|
||||||
|
$knownHosts = TEST_TMP . '/known-hosts/known_hosts';
|
||||||
|
$settings = TEST_TMP . '/plugin-settings.conf';
|
||||||
|
$binDir = TEST_TMP . '/fake-scan-bin';
|
||||||
|
$hostKey = TEST_TMP . '/mx-host-ed25519';
|
||||||
|
mkdir($binDir, 0700, true);
|
||||||
|
|
||||||
|
exec(sprintf('ssh-keygen -q -t ed25519 -N "" -f %s -C %s', escapeshellarg($hostKey), escapeshellarg('mx1.test')), $keygenOut, $keygenCode);
|
||||||
|
assert_same(0, $keygenCode, implode("\n", $keygenOut));
|
||||||
|
$pub = trim((string)file_get_contents($hostKey . '.pub'));
|
||||||
|
$parts = explode(' ', $pub);
|
||||||
|
$knownHostLine = '[mx1.test]:10022 ' . $parts[0] . ' ' . $parts[1];
|
||||||
|
|
||||||
|
test_write($settings, implode("\n", [
|
||||||
|
'MAIL_LOGIN_SSH_HOST=mx1.test',
|
||||||
|
'MAIL_LOGIN_SSH_PORT=10022',
|
||||||
|
'MAIL_LOGIN_SSH_KNOWN_HOSTS=' . $knownHosts,
|
||||||
|
'MAIL_LOGIN_CONNECT_TIMEOUT_SECONDS=5',
|
||||||
|
'',
|
||||||
|
]));
|
||||||
|
test_write($binDir . '/ssh-keyscan', "#!/bin/sh\nif [ \"$1 $2 $3 $4 $5 $6 $7\" = '-T 5 -p 10022 -t ed25519 mx1.test' ]; then cat <<'EOF'\n$knownHostLine\nEOF\nexit 0\nfi\nexit 1\n");
|
||||||
|
chmod($binDir . '/ssh-keyscan', 0755);
|
||||||
|
|
||||||
|
$script = PLUGIN_ROOT . '/scripts/keygen.sh';
|
||||||
|
$cmd = sprintf(
|
||||||
|
'PATH=%s:$PATH MAIL_LOGIN_SETTINGS_FILE=%s MAIL_LOGIN_KEY_DIR=%s SERVERNAME=%s SOURCE_IP=%s %s',
|
||||||
|
escapeshellarg($binDir),
|
||||||
|
escapeshellarg($settings),
|
||||||
|
escapeshellarg($keyDir),
|
||||||
|
escapeshellarg('h4'),
|
||||||
|
escapeshellarg('203.0.113.10'),
|
||||||
|
escapeshellarg($script)
|
||||||
|
);
|
||||||
|
|
||||||
|
exec($cmd, $out, $code);
|
||||||
|
|
||||||
|
assert_same(0, $code, implode("\n", $out));
|
||||||
|
assert_true(is_file($knownHosts));
|
||||||
|
assert_contains('[mx1.test]:10022', file_get_contents($knownHosts) ?: '');
|
||||||
|
$output = implode("\n", $out);
|
||||||
|
assert_contains('MAIL_LOGIN_SSH_KNOWN_HOSTS=' . $knownHosts, $output);
|
||||||
|
assert_contains('Pinned MX host key:', $output);
|
||||||
|
});
|
||||||
|
|
||||||
test('keygen script prints helper forced command line when mx login mode is helper', function (): void {
|
test('keygen script prints helper forced command line when mx login mode is helper', function (): void {
|
||||||
$keyDir = TEST_TMP . '/keys-helper';
|
$keyDir = TEST_TMP . '/keys-helper';
|
||||||
$script = PLUGIN_ROOT . '/scripts/keygen.sh';
|
$script = PLUGIN_ROOT . '/scripts/keygen.sh';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ test('plugin metadata and packaging docs match project rules', function (): void
|
|||||||
assert_contains('name=mail-login', $conf);
|
assert_contains('name=mail-login', $conf);
|
||||||
assert_contains('id=mail-login', $conf);
|
assert_contains('id=mail-login', $conf);
|
||||||
assert_contains('type=user', $conf);
|
assert_contains('type=user', $conf);
|
||||||
assert_contains('version=1.0.8', $conf);
|
assert_contains('version=1.0.9', $conf);
|
||||||
assert_contains('user_run_as=root', $conf);
|
assert_contains('user_run_as=root', $conf);
|
||||||
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/src', $packing);
|
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/src', $packing);
|
||||||
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/archives/X.Y.Z/mail-login.tar.gz', $packing);
|
assert_contains('/Users/marek/GPT/ChatGPT/da_plugins/mail-login/archives/X.Y.Z/mail-login.tar.gz', $packing);
|
||||||
|
|||||||
Reference in New Issue
Block a user