fix: jump server (#190)

This commit is contained in:
lollipopkit
2023-10-30 17:10:29 +08:00
parent 4971239bfc
commit bff799afd9
9 changed files with 103 additions and 73 deletions

View File

@@ -44,63 +44,78 @@ String getPrivateKey(String id) {
Future<SSHClient> genClient( Future<SSHClient> genClient(
ServerPrivateInfo spi, { ServerPrivateInfo spi, {
void Function(GenSSHClientStatus)? onStatus, void Function(GenSSHClientStatus)? onStatus,
/// Must pass this param when use multi-thread and key login
String? privateKey, String? privateKey,
/// Must pass this param when use multi-thread and key login
String? jumpPrivateKey,
Duration timeout = const Duration(seconds: 5), Duration timeout = const Duration(seconds: 5),
/// [ServerPrivateInfo] of the jump server /// [ServerPrivateInfo] of the jump server
///
/// Must pass this param when use multi-thread and key login
ServerPrivateInfo? jumpSpi, ServerPrivateInfo? jumpSpi,
String? jumpPrivateKey,
}) async { }) async {
onStatus?.call(GenSSHClientStatus.socket); onStatus?.call(GenSSHClientStatus.socket);
SSHSocket? socket;
try {
socket = await SSHSocket.connect(
spi.ip,
spi.port,
timeout: timeout,
);
} catch (e) {
if (spi.alterUrl == null) rethrow;
try {
final ipPort = spi.fromStringUrl();
socket = await SSHSocket.connect(
ipPort.ip,
ipPort.port,
timeout: timeout,
);
} catch (e) {
rethrow;
}
}
final forward = await () async { final socket = await () async {
if (jumpSpi != null) { // Proxy
final jumpSpi_ = () {
// Multi-thread or key login
if (jumpSpi != null) return jumpSpi;
// Main thread
if (spi.jumpId != null) return Stores.server.box.get(spi.jumpId);
}();
if (jumpSpi_ != null) {
final jumpClient = await genClient( final jumpClient = await genClient(
jumpSpi, jumpSpi_,
privateKey: jumpPrivateKey, privateKey: jumpPrivateKey,
timeout: timeout, timeout: timeout,
); );
// Use `0.0.0.0` as localhost to use all interfaces.
return await jumpClient.forwardLocal( return await jumpClient.forwardLocal(
spi.ip, spi.ip,
spi.port, spi.port,
); );
} }
// Direct
try {
return await SSHSocket.connect(
spi.ip,
spi.port,
timeout: timeout,
);
} catch (e) {
if (spi.alterUrl == null) rethrow;
try {
final ipPort = spi.fromStringUrl();
return await SSHSocket.connect(
ipPort.ip,
ipPort.port,
timeout: timeout,
);
} catch (e) {
rethrow;
}
}
}(); }();
if (spi.pubKeyId == null) { final keyId = spi.keyId;
if (keyId == null) {
onStatus?.call(GenSSHClientStatus.pwd); onStatus?.call(GenSSHClientStatus.pwd);
return SSHClient( return SSHClient(
forward ?? socket, socket,
username: spi.user, username: spi.user,
onPasswordRequest: () => spi.pwd, onPasswordRequest: () => spi.pwd,
); );
} }
privateKey ??= getPrivateKey(spi.pubKeyId!); privateKey ??= getPrivateKey(keyId);
onStatus?.call(GenSSHClientStatus.key); onStatus?.call(GenSSHClientStatus.key);
return SSHClient( return SSHClient(
forward ?? socket, socket,
username: spi.user, username: spi.user,
identities: await compute(loadIndentity, privateKey), identities: await compute(loadIndentity, privateKey),
); );

View File

@@ -18,8 +18,9 @@ class ServerPrivateInfo {
final String user; final String user;
@HiveField(4) @HiveField(4)
final String? pwd; final String? pwd;
/// [id] of private key
@HiveField(5) @HiveField(5)
final String? pubKeyId; final String? keyId;
@HiveField(6) @HiveField(6)
final List<String>? tags; final List<String>? tags;
@HiveField(7) @HiveField(7)
@@ -39,7 +40,7 @@ class ServerPrivateInfo {
required this.port, required this.port,
required this.user, required this.user,
required this.pwd, required this.pwd,
this.pubKeyId, this.keyId,
this.tags, this.tags,
this.alterUrl, this.alterUrl,
this.autoConnect, this.autoConnect,
@@ -55,7 +56,7 @@ class ServerPrivateInfo {
name = json["name"]?.toString() ?? name = json["name"]?.toString() ??
'${json["user"]?.toString() ?? 'root'}@${json["ip"].toString()}:${json["port"] ?? 22}', '${json["user"]?.toString() ?? 'root'}@${json["ip"].toString()}:${json["port"] ?? 22}',
pwd = json["authorization"]?.toString(), pwd = json["authorization"]?.toString(),
pubKeyId = json["pubKeyId"]?.toString(), keyId = json["pubKeyId"]?.toString(),
tags = json["tags"]?.cast<String>(), tags = json["tags"]?.cast<String>(),
alterUrl = json["alterUrl"]?.toString(), alterUrl = json["alterUrl"]?.toString(),
autoConnect = json["autoConnect"], autoConnect = json["autoConnect"],
@@ -68,7 +69,7 @@ class ServerPrivateInfo {
data["port"] = port; data["port"] = port;
data["user"] = user; data["user"] = user;
data["authorization"] = pwd; data["authorization"] = pwd;
data["pubKeyId"] = pubKeyId; data["pubKeyId"] = keyId;
data["tags"] = tags; data["tags"] = tags;
data["alterUrl"] = alterUrl; data["alterUrl"] = alterUrl;
data["autoConnect"] = autoConnect; data["autoConnect"] = autoConnect;
@@ -82,7 +83,7 @@ class ServerPrivateInfo {
bool shouldReconnect(ServerPrivateInfo old) { bool shouldReconnect(ServerPrivateInfo old) {
return id != old.id || return id != old.id ||
pwd != old.pwd || pwd != old.pwd ||
pubKeyId != old.pubKeyId || keyId != old.keyId ||
alterUrl != old.alterUrl || alterUrl != old.alterUrl ||
jumpId != old.jumpId; jumpId != old.jumpId;
} }

View File

@@ -22,7 +22,7 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
port: fields[2] as int, port: fields[2] as int,
user: fields[3] as String, user: fields[3] as String,
pwd: fields[4] as String?, pwd: fields[4] as String?,
pubKeyId: fields[5] as String?, keyId: fields[5] as String?,
tags: (fields[6] as List?)?.cast<String>(), tags: (fields[6] as List?)?.cast<String>(),
alterUrl: fields[7] as String?, alterUrl: fields[7] as String?,
autoConnect: fields[8] as bool?, autoConnect: fields[8] as bool?,
@@ -45,7 +45,7 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
..writeByte(4) ..writeByte(4)
..write(obj.pwd) ..write(obj.pwd)
..writeByte(5) ..writeByte(5)
..write(obj.pubKeyId) ..write(obj.keyId)
..writeByte(6) ..writeByte(6)
..write(obj.tags) ..write(obj.tags)
..writeByte(7) ..writeByte(7)

View File

@@ -1,5 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:toolbox/data/res/logger.dart';
import 'package:toolbox/data/res/store.dart';
import '../../../core/utils/server.dart'; import '../../../core/utils/server.dart';
import '../server/server_private_info.dart'; import '../server/server_private_info.dart';
import 'worker.dart'; import 'worker.dart';
@@ -10,6 +13,8 @@ class SftpReq {
final String localPath; final String localPath;
final SftpReqType type; final SftpReqType type;
String? privateKey; String? privateKey;
ServerPrivateInfo? jumpSpi;
String? jumpPrivateKey;
SftpReq( SftpReq(
this.spi, this.spi,
@@ -17,8 +22,13 @@ class SftpReq {
this.localPath, this.localPath,
this.type, this.type,
) { ) {
if (spi.pubKeyId != null) { final keyId = spi.keyId;
privateKey = getPrivateKey(spi.pubKeyId!); if (keyId != null) {
privateKey = getPrivateKey(keyId);
}
if (spi.jumpId != null) {
jumpSpi = Stores.server.box.get(spi.jumpId);
jumpPrivateKey = Stores.key.get(jumpSpi?.keyId)?.key;
} }
} }
} }
@@ -83,7 +93,9 @@ class SftpReqStatus {
break; break;
default: default:
error = Exception('sftp worker event: $event'); error = Exception('sftp worker event: $event');
Loggers.app.warning(error);
dispose(); dispose();
break;
} }
notifyListeners(); notifyListeners();
} }

View File

@@ -81,7 +81,12 @@ Future<void> _download(
try { try {
mainSendPort.send(SftpWorkerStatus.preparing); mainSendPort.send(SftpWorkerStatus.preparing);
final watch = Stopwatch()..start(); final watch = Stopwatch()..start();
final client = await genClient(req.spi, privateKey: req.privateKey); final client = await genClient(
req.spi,
privateKey: req.privateKey,
jumpSpi: req.jumpSpi,
jumpPrivateKey: req.jumpPrivateKey,
);
mainSendPort.send(SftpWorkerStatus.sshConnectted); mainSendPort.send(SftpWorkerStatus.sshConnectted);
/// Create the directory if not exists /// Create the directory if not exists
@@ -131,7 +136,12 @@ Future<void> _upload(
try { try {
mainSendPort.send(SftpWorkerStatus.preparing); mainSendPort.send(SftpWorkerStatus.preparing);
final watch = Stopwatch()..start(); final watch = Stopwatch()..start();
final client = await genClient(req.spi, privateKey: req.privateKey); final client = await genClient(
req.spi,
privateKey: req.privateKey,
jumpSpi: req.jumpSpi,
jumpPrivateKey: req.jumpPrivateKey,
);
mainSendPort.send(SftpWorkerStatus.sshConnectted); mainSendPort.send(SftpWorkerStatus.sshConnectted);
final local = File(req.localPath); final local = File(req.localPath);

View File

@@ -280,14 +280,11 @@ class ServerProvider extends ChangeNotifier {
_setServerState(s, ServerState.connecting); _setServerState(s, ServerState.connecting);
final time1 = DateTime.now(); final time1 = DateTime.now();
final jumpSpi =
spi.jumpId == null ? null : Stores.server.box.get(spi.jumpId);
try { try {
s.client = await genClient( s.client = await genClient(
spi, spi,
timeout: Stores.setting.timeoutD, timeout: Stores.setting.timeoutD,
jumpSpi: jumpSpi,
); );
} catch (e) { } catch (e) {
_limiter.inc(sid); _limiter.inc(sid);
@@ -304,8 +301,8 @@ class ServerProvider extends ChangeNotifier {
if (spi.jumpId == null) { if (spi.jumpId == null) {
Loggers.app.info('Connected to ${spi.name} in $spentTime ms.'); Loggers.app.info('Connected to ${spi.name} in $spentTime ms.');
} else { } else {
Loggers.app.info( Loggers.app
'Connected to ${spi.name} via ${jumpSpi?.name} in $spentTime ms.'); .info('Connected to ${spi.name} via jump server in $spentTime ms.');
} }
_setServerState(s, ServerState.connected); _setServerState(s, ServerState.connected);

View File

@@ -2,9 +2,9 @@
class BuildData { class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 612; static const int build = 616;
static const String engine = "3.13.8"; static const String engine = "3.13.8";
static const String buildAt = "2023-10-28 17:13:40"; static const String buildAt = "2023-10-30 14:48:00";
static const int modifications = 5; static const int modifications = 5;
static const int script = 22; static const int script = 23;
} }

View File

@@ -52,31 +52,26 @@ class _ServerEditPageState extends State<ServerEditPage> {
void initState() { void initState() {
super.initState(); super.initState();
if (widget.spi != null) { final spi = widget.spi;
_nameController.text = widget.spi?.name ?? ''; if (spi != null) {
_ipController.text = widget.spi?.ip ?? ''; _nameController.text = spi.name;
_portController.text = (widget.spi?.port ?? 22).toString(); _ipController.text = spi.ip;
_usernameController.text = widget.spi?.user ?? ''; _portController.text = spi.port.toString();
if (widget.spi?.pubKeyId == null) { _usernameController.text = spi.user;
_passwordController.text = widget.spi?.pwd ?? ''; if (spi.keyId == null) {
_passwordController.text = spi.pwd ?? '';
} else { } else {
_keyIdx.value = Pros.key.pkis.indexWhere( _keyIdx.value = Pros.key.pkis.indexWhere(
(e) => e.id == widget.spi!.pubKeyId, (e) => e.id == widget.spi!.keyId,
); );
} }
if (widget.spi?.tags != null) {
/// List in dart is passed by pointer, so you need to copy it here /// List in dart is passed by pointer, so you need to copy it here
_tags.addAll(widget.spi!.tags!); _tags.addAll(spi.tags ?? []);
}
if (widget.spi?.alterUrl != null) { _altUrlController.text = spi.alterUrl ?? '';
_altUrlController.text = widget.spi!.alterUrl!; _autoConnect.value = spi.autoConnect ?? true;
} _jumpServer.value = spi.jumpId;
if (widget.spi?.autoConnect != null) {
_autoConnect.value = widget.spi!.autoConnect!;
}
if (widget.spi?.jumpId != null) {
_jumpServer.value = widget.spi!.jumpId;
}
} }
} }
@@ -403,7 +398,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
port: int.parse(_portController.text), port: int.parse(_portController.text),
user: _usernameController.text, user: _usernameController.text,
pwd: _passwordController.text.isEmpty ? null : _passwordController.text, pwd: _passwordController.text.isEmpty ? null : _passwordController.text,
pubKeyId: _keyIdx.value != null keyId: _keyIdx.value != null
? Pros.key.pkis.elementAt(_keyIdx.value!).id ? Pros.key.pkis.elementAt(_keyIdx.value!).id
: null, : null,
tags: _tags, tags: _tags,

View File

@@ -151,18 +151,18 @@ Future<void> _gotoSSH(
} }
final path = await () async { final path = await () async {
final tempKeyFileName = 'srvbox_pk_${spi.pubKeyId}'; final tempKeyFileName = 'srvbox_pk_${spi.keyId}';
/// For security reason, save the private key file to app doc path /// For security reason, save the private key file to app doc path
return joinPath(await Paths.doc, tempKeyFileName); return joinPath(await Paths.doc, tempKeyFileName);
}(); }();
final file = File(path); final file = File(path);
final shouldGenKey = spi.pubKeyId != null; final shouldGenKey = spi.keyId != null;
if (shouldGenKey) { if (shouldGenKey) {
if (await file.exists()) { if (await file.exists()) {
await file.delete(); await file.delete();
} }
await file.writeAsString(getPrivateKey(spi.pubKeyId!)); await file.writeAsString(getPrivateKey(spi.keyId!));
extraArgs.addAll(["-i", path]); extraArgs.addAll(["-i", path]);
} }