From 2381f5569670f5b3ea4ce64bd2f97196fadc2fb1 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 21 May 2025 20:20:10 +0200 Subject: [PATCH] fixed #972 - SSH server not offering keyboard-interactive when only OOB or SSO auth is enabled for a user --- tests/test_ssh_user_auth_in_browser.py | 24 +++++++++++++++---- tests/test_ssh_user_auth_otp.py | 16 +++++++------ warpgate-core/src/config_providers/db.rs | 15 +++++++++--- warpgate-protocol-ssh/src/server/session.rs | 4 ++++ .../src/admin/AuthPolicyEditor.svelte | 11 +++++---- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/tests/test_ssh_user_auth_in_browser.py b/tests/test_ssh_user_auth_in_browser.py index 0f147cee..47217b51 100644 --- a/tests/test_ssh_user_auth_in_browser.py +++ b/tests/test_ssh_user_auth_in_browser.py @@ -9,6 +9,11 @@ from .util import wait_port class Test: + # When include_pk is False, we're testing for + # https://github.com/warp-tech/warpgate/issues/972 + # where the SSH server fails to offer keyboard-interactive authentication + # when no OTP credential is present. + @pytest.mark.parametrize("include_pk", [True, False]) @pytest.mark.asyncio async def test( self, @@ -16,6 +21,7 @@ class Test: wg_c_ed25519_pubkey: Path, timeout, shared_wg: WarpgateProcess, + include_pk: bool, ): ssh_port = processes.start_ssh_server( trusted_keys=[wg_c_ed25519_pubkey.read_text()] @@ -32,13 +38,24 @@ class Test: api.create_password_credential( user.id, sdk.NewPasswordCredential(password="123") ) + if include_pk: + api.create_public_key_credential( + user.id, + sdk.NewPublicKeyCredential( + label="Public Key", + openssh_public_key=open("ssh-keys/id_ed25519.pub").read().strip() + ), + ) api.add_user_role(user.id, role.id) api.update_user( user.id, sdk.UserDataRequest( username=user.username, credential_policy=sdk.UserRequireCredentialsPolicy( - ssh=[sdk.CredentialKind.WEBUSERAPPROVAL], + ssh=[sdk.CredentialKind.WEBUSERAPPROVAL] if not include_pk else [ + sdk.CredentialKind.PUBLICKEY, + sdk.CredentialKind.WEBUSERAPPROVAL, + ], ), ), ) @@ -80,11 +97,10 @@ class Test: f"{user.username}:{ssh_target.name}@localhost", "-p", str(shared_wg.ssh_port), - "-i", - "/dev/null", + "-o", + "IdentityFile=ssh-keys/id_ed25519", "ls", "/bin/sh", - password="123", ) msg = await ws.receive(5) diff --git a/tests/test_ssh_user_auth_otp.py b/tests/test_ssh_user_auth_otp.py index 7cb429c5..8a3581d9 100644 --- a/tests/test_ssh_user_auth_otp.py +++ b/tests/test_ssh_user_auth_otp.py @@ -2,6 +2,7 @@ from asyncio import subprocess from base64 import b64decode from uuid import uuid4 import pyotp +import pytest from pathlib import Path from textwrap import dedent @@ -32,13 +33,14 @@ class Test: sdk.RoleDataRequest(name=f"role-{uuid4()}"), ) user = api.create_user(sdk.CreateUserRequest(username=f"user-{uuid4()}")) - api.create_public_key_credential( - user.id, - sdk.NewPublicKeyCredential( - label="Public Key", - openssh_public_key=open("ssh-keys/id_ed25519.pub").read().strip() - ), - ) + if include_public_key: + api.create_public_key_credential( + user.id, + sdk.NewPublicKeyCredential( + label="Public Key", + openssh_public_key=open("ssh-keys/id_ed25519.pub").read().strip(), + ), + ) api.create_otp_credential( user.id, sdk.NewOtpCredential( diff --git a/warpgate-core/src/config_providers/db.rs b/warpgate-core/src/config_providers/db.rs index a14be599..7058b727 100644 --- a/warpgate-core/src/config_providers/db.rs +++ b/warpgate-core/src/config_providers/db.rs @@ -109,12 +109,21 @@ impl ConfigProvider for DatabaseConfigProvider { let user = user_model.load_details(&db).await?; - let supported_credential_types: HashSet = user + let mut available_credential_types = user .credentials .iter() .map(|x| x.kind()) - .filter(|x| supported_credential_types.contains(x)) - .collect(); + .collect::>(); + available_credential_types.insert(CredentialKind::WebUserApproval); + + let supported_credential_types = supported_credential_types + .iter() + .copied() + .collect::>() + .intersection(&available_credential_types) + .copied() + .collect::>(); + let default_policy = Box::new(AnySingleCredentialPolicy { supported_credential_types: supported_credential_types.clone(), }) as Box; diff --git a/warpgate-protocol-ssh/src/server/session.rs b/warpgate-protocol-ssh/src/server/session.rs index 8b6bd16d..75708695 100644 --- a/warpgate-protocol-ssh/src/server/session.rs +++ b/warpgate-protocol-ssh/src/server/session.rs @@ -1497,6 +1497,10 @@ impl ServerSession { CredentialKind::Sso => m.push(MethodKind::KeyboardInteractive), } } + if m.contains(&MethodKind::KeyboardInteractive) { + // Ensure keyboard-interactive is always the last method + m.push(MethodKind::KeyboardInteractive); + } m } diff --git a/warpgate-web/src/admin/AuthPolicyEditor.svelte b/warpgate-web/src/admin/AuthPolicyEditor.svelte index 01a67a38..2b3bd6cc 100644 --- a/warpgate-web/src/admin/AuthPolicyEditor.svelte +++ b/warpgate-web/src/admin/AuthPolicyEditor.svelte @@ -1,9 +1,9 @@