mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 07:35:24 +00:00
release-2.7.1 (#1296)
* Add Helm and GitOps deployment setup * fix: build better-sqlite3 from source in Docker (#1267) * fix: preserve runtime SSL settings (#1268) * fix: support forwarding from the memory SSH agent (#1269) * fix: support forwarding from the memory agent * style: format memory agent test * fix: prompt for encrypted SFTP key passphrases (#1270) * fix: prompt for SFTP key passphrases * style: format SSH key utility test * fix: include host context in automation notifications (#1271) * fix: include host context in automation notifications * style: format automation notification changes * fix: reserve sidebar height for host tags (#1272) * fix: keep host action rows stable at large font sizes (#1273) * fix: honor certificate setting during server probe (#1274) * fix: package standard Linux icon sizes (#1275) * fix: avoid duplicate Docker HTTPS listener (#1276) * Fix host status without metrics collection (#1277) * fix: allow eight-digit secure auth codes (#1263) Allow TOTP prompts to accept secure auth codes longer than six digits without blocking valid authentication attempts. Generated with Codebuff 🤖 Co-authored-by: Chetan <chetan.development@gmail.com> Co-authored-by: Codebuff <noreply@codebuff.com> * Harden Helm deployment defaults * Update Helm workflow action * Exclude Helm templates from Prettier * Fix browser RDP file drops (#1279) * Fix Proxmox guest credential usernames (#1280) * Add WSL local terminal option (#1281) * refactor: split the transfer engine into focused modules (#1282) * refactor: extract SFTP promisify helpers into sftp-promisify module * refactor: extract transfer timing and rate stats into transfer-stats module * refactor: extract transfer error classes and recovery checks into transfer-errors module * refactor: extract host/path utility helpers into transfer-host-utils module * refactor: extract SFTP directory tree helpers into transfer-sftp-dir module * refactor: extract segment copy job builder into transfer-segment-copy module * refactor: extract file scan and sample helpers into transfer-scan module * refactor: move throttled progress helper into transfer-stats module * style: format transfer modules * perf: optimize tmux monitor aggregation (#1283) * fix: reserve credential tag row height (#1284) * feat: edit AI provider model settings (#1285) * fix: clarify click-to-expand host setting (#1286) * fix: allow portable imports on remote databases (#1287) * fix: allow HTTPS to share the configured port (#1288) * fix: resolve synced jump hosts on the server (#1289) * fix: make terminal clipboard shortcuts layout independent (#1290) * fix: use compatible fetch dispatcher for Tailscale (#1291) * fix: add OIDC environment recovery override (#1292) * fix: coalesce rapid mobile terminal input (#1293) * fix: coalesce rapid mobile terminal input * fix: support clean xterm patch installs * fix: resolve synced remote desktop host IDs (#1295) * feat: make the SFTP file manager path bar editable (#1294) Co-authored-by: Maxime Bonillo <257463937+dropafterfree@users.noreply.github.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> * feat: add passkey sign in to the login screen * fix: remove rounded corners from the host list search bar * fix: stop image storage settings text wrapping to one word per line * fix: prevent malformed websocket messages from crashing the server * chore: increment version * fix: remove gaps between host rows in the sidebar list Keep sub-pixel row measurements and stop wiping the size cache on hover. * fix: Failed to connect through jump hosts (#1180) https://github.com/Termix-SSH/Support/issues/1180 * feat: Progress bar for file downloads in the file manager (#1158) https://github.com/Termix-SSH/Support/issues/1158 * feat: Allow setting Silent OIDC Login via ENV var (#1174) https://github.com/Termix-SSH/Support/issues/1174 * feat: `IdentityFile` to limit the number of attempts by agents (#1165) https://github.com/Termix-SSH/Support/issues/1165 * feat: Credentials clone (#1159) https://github.com/Termix-SSH/Support/issues/1159 * chore: update release notes * docs: move helm setup guide to the docs site * fix: type errors in FilteredAgent agent identity handling * fix: remove stale better-sqlite3 prebuilds so the source build is used * fix: actually build better-sqlite3 from source so arm64 docker images work * fix: credential edit pencil in host editor and add clone action to credential list * fix: clear editingHost so the credential pencil actually opens the editor * chore: run format and lint * fix: folder drag and drop upload failing in the file manager * chore: sync Crowdin translations for 2.7.1 --------- Co-authored-by: alex-ctms <alex-ctms@users.noreply.github.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: Chetan Kumar <74929596+ckloop@users.noreply.github.com> Co-authored-by: Chetan <chetan.development@gmail.com> Co-authored-by: Codebuff <noreply@codebuff.com> Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com> Co-authored-by: dropafterfree <maxime.bonillo@gmail.com> Co-authored-by: Maxime Bonillo <257463937+dropafterfree@users.noreply.github.com>
This commit is contained in:
165 files changed
+6778
-2090
No files matched your search
@@ -1,13 +1,18 @@
|
||||
import dgram from "dgram";
|
||||
import net from "net";
|
||||
import ssh2Pkg, {
|
||||
type BaseAgent as BaseAgentType,
|
||||
type GetStreamCallback,
|
||||
type IdentityCallback,
|
||||
type KnownPublicKeys,
|
||||
type ParsedKey,
|
||||
type SignCallback,
|
||||
type SigningRequestOptions,
|
||||
} from "ssh2";
|
||||
|
||||
const { BaseAgent } = ssh2Pkg;
|
||||
type KnownPublicKey = KnownPublicKeys[number];
|
||||
|
||||
const { AgentProtocol, BaseAgent } = ssh2Pkg;
|
||||
const DEFAULT_PORT_KNOCK_TIMEOUT_MS = 1000;
|
||||
|
||||
type Sleep = (ms: number) => Promise<void>;
|
||||
@@ -34,6 +39,23 @@ export class MemoryAgent extends BaseAgent {
|
||||
cb(null, [this.key]);
|
||||
}
|
||||
|
||||
getStream(cb: GetStreamCallback): void {
|
||||
const protocol = new AgentProtocol(false);
|
||||
|
||||
protocol.on("identities", (request) => {
|
||||
protocol.getIdentitiesReply(request, [this.key]);
|
||||
});
|
||||
|
||||
protocol.on("sign", (request, publicKey, data, options) => {
|
||||
this.sign(publicKey, data, options, (error, signature) => {
|
||||
if (error || !signature) return protocol.failureReply(request);
|
||||
protocol.signReply(request, signature);
|
||||
});
|
||||
});
|
||||
|
||||
cb(null, protocol);
|
||||
}
|
||||
|
||||
sign(
|
||||
_pubKey: ParsedKey | Buffer | string,
|
||||
data: Buffer,
|
||||
@@ -85,6 +107,84 @@ export async function resolveAgentSocket(
|
||||
return { socketPath: resolved };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an agent so only identities matching a specific public key are
|
||||
* offered to the server, mirroring ssh_config's IdentityFile + IdentitiesOnly
|
||||
* for agent auth. Prevents exhausting the server's MaxAuthTries when the
|
||||
* agent holds many keys.
|
||||
*/
|
||||
export class FilteredAgent extends BaseAgent {
|
||||
private inner: BaseAgentType;
|
||||
private publicKeyBlob: Buffer;
|
||||
|
||||
constructor(inner: BaseAgentType, publicKeyBlob: Buffer) {
|
||||
super();
|
||||
this.inner = inner;
|
||||
this.publicKeyBlob = publicKeyBlob;
|
||||
}
|
||||
|
||||
private matches(key: KnownPublicKey): boolean {
|
||||
try {
|
||||
const blob =
|
||||
typeof key === "string"
|
||||
? Buffer.from(key)
|
||||
: Buffer.isBuffer(key)
|
||||
? key
|
||||
: "getPublicSSH" in key
|
||||
? key.getPublicSSH()
|
||||
: null;
|
||||
return (
|
||||
Buffer.isBuffer(blob) && Buffer.compare(blob, this.publicKeyBlob) === 0
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getIdentities(cb: IdentityCallback): void {
|
||||
this.inner.getIdentities((err, keys) => {
|
||||
if (err || !keys) return cb(err, keys);
|
||||
cb(
|
||||
null,
|
||||
keys.filter((key) => this.matches(key)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getStream(cb: GetStreamCallback): void {
|
||||
if (typeof this.inner.getStream !== "function") {
|
||||
return cb(new Error("Agent does not support forwarding."));
|
||||
}
|
||||
this.inner.getStream(cb);
|
||||
}
|
||||
|
||||
sign(
|
||||
pubKey: ParsedKey | Buffer | string,
|
||||
data: Buffer,
|
||||
optionsOrCb: SigningRequestOptions | SignCallback,
|
||||
cb?: SignCallback,
|
||||
): void {
|
||||
this.inner.sign(
|
||||
pubKey,
|
||||
data,
|
||||
optionsOrCb as SigningRequestOptions,
|
||||
cb as SignCallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function parseAgentIdentityBlob(agentIdentity: string): Buffer | null {
|
||||
const { utils } = ssh2Pkg;
|
||||
const parsed = utils.parseKey(agentIdentity.trim());
|
||||
if (parsed instanceof Error || !parsed) return null;
|
||||
const key = Array.isArray(parsed) ? parsed[0] : parsed;
|
||||
try {
|
||||
return key.getPublicSSH();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyAgentAuth(
|
||||
connectConfig: Record<string, unknown>,
|
||||
terminalConfig: Record<string, unknown> | undefined,
|
||||
@@ -93,7 +193,21 @@ export async function applyAgentAuth(
|
||||
if ("error" in result) return result;
|
||||
|
||||
const { createAgent } = ssh2Pkg;
|
||||
connectConfig.agent = createAgent(result.socketPath);
|
||||
const agent = createAgent(result.socketPath);
|
||||
|
||||
const agentIdentity = (
|
||||
terminalConfig?.agentIdentity as string | undefined
|
||||
)?.trim();
|
||||
if (agentIdentity) {
|
||||
const publicKeyBlob = parseAgentIdentityBlob(agentIdentity);
|
||||
if (!publicKeyBlob) {
|
||||
return { error: "Invalid public key provided for agent identity." };
|
||||
}
|
||||
connectConfig.agent = new FilteredAgent(agent, publicKeyBlob);
|
||||
} else {
|
||||
connectConfig.agent = agent;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user