mirror of
https://github.com/warp-tech/warpgate.git
synced 2025-09-04 21:55:22 +08:00
fixed #972 - SSH server not offering keyboard-interactive when only OOB or SSO auth is enabled for a user
This commit is contained in:
parent
5ee29b9ad0
commit
2381f55696
5 changed files with 52 additions and 18 deletions
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -109,12 +109,21 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
|
||||
let user = user_model.load_details(&db).await?;
|
||||
|
||||
let supported_credential_types: HashSet<CredentialKind> = user
|
||||
let mut available_credential_types = user
|
||||
.credentials
|
||||
.iter()
|
||||
.map(|x| x.kind())
|
||||
.filter(|x| supported_credential_types.contains(x))
|
||||
.collect();
|
||||
.collect::<HashSet<_>>();
|
||||
available_credential_types.insert(CredentialKind::WebUserApproval);
|
||||
|
||||
let supported_credential_types = supported_credential_types
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<HashSet<_>>()
|
||||
.intersection(&available_credential_types)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let default_policy = Box::new(AnySingleCredentialPolicy {
|
||||
supported_credential_types: supported_credential_types.clone(),
|
||||
}) as Box<dyn CredentialPolicy + Sync + Send>;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { Input } from '@sveltestrap/sveltestrap'
|
||||
import { CredentialKind, type UserRequireCredentialsPolicy } from './lib/api'
|
||||
import type { ExistingCredential } from './CredentialEditor.svelte'
|
||||
import Fa from 'svelte-fa';
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import type { ExistingCredential } from './CredentialEditor.svelte'
|
||||
import Fa from 'svelte-fa'
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
type ProtocolID = 'http' | 'ssh' | 'mysql' | 'postgres'
|
||||
|
||||
|
@ -29,7 +29,7 @@ const labels = {
|
|||
WebUserApproval: 'In-browser auth',
|
||||
}
|
||||
|
||||
const tips: Record<ProtocolID, Map<[CredentialKind, boolean], string> | undefined> = {
|
||||
const tips: Record<ProtocolID, Map<[CredentialKind, boolean], string>> = {
|
||||
postgres: new Map([
|
||||
[
|
||||
[CredentialKind.Password, false],
|
||||
|
@ -40,6 +40,9 @@ const tips: Record<ProtocolID, Map<[CredentialKind, boolean], string> | undefine
|
|||
'Not all clients will show the 2FA auth prompt. The user might need to log in to the Warpgate UI to see the prompt.',
|
||||
],
|
||||
]),
|
||||
http: new Map(),
|
||||
mysql: new Map(),
|
||||
ssh: new Map(),
|
||||
}
|
||||
|
||||
let activeTips: string[] = $derived.by(() => {
|
||||
|
|
Loading…
Add table
Reference in a new issue