mirror of
https://github.com/utmapp/UTM.git
synced 2026-10-12 02:24:01 +00:00
remote: add SPICE ticket password auth
This commit is contained in:
@@ -146,7 +146,11 @@ import Virtualization // for getting network interfaces
|
||||
"unix=on"
|
||||
"addr=\(spiceSocketURL.lastPathComponent)"
|
||||
}
|
||||
"disable-ticketing=on"
|
||||
if let _ = qemu.spiceServerPassword {
|
||||
"password-secret=secspice0"
|
||||
} else {
|
||||
"disable-ticketing=on"
|
||||
}
|
||||
if !isRemoteSpice {
|
||||
"image-compression=off"
|
||||
"playback-compression=off"
|
||||
@@ -176,6 +180,11 @@ import Virtualization // for getting network interfaces
|
||||
f("-vga")
|
||||
f("none")
|
||||
}
|
||||
if let password = qemu.spiceServerPassword {
|
||||
// assume anyone who can read this is in our trust domain
|
||||
f("-object")
|
||||
f("secret,id=secspice0,data=\(password)")
|
||||
}
|
||||
}
|
||||
|
||||
private func filterDisplayIfRemote(_ display: any QEMUDisplayDevice) -> any QEMUDisplayDevice {
|
||||
|
||||
@@ -77,6 +77,9 @@ struct UTMQemuConfigurationQEMU: Codable {
|
||||
|
||||
/// Set to TLS public key for SPICE server in SubjectPublicKey. Not saved.
|
||||
var spiceServerPublicKey: Data?
|
||||
|
||||
/// Set to a password shared with the client. Not saved.
|
||||
var spiceServerPassword: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case hasDebugLog = "DebugLog"
|
||||
|
||||
@@ -82,7 +82,7 @@ extension UTMData {
|
||||
/// - options: Start options
|
||||
/// - server: Remote server
|
||||
/// - Returns: Port number to SPICE server
|
||||
func startRemote(vm: VMData, options: UTMVirtualMachineStartOptions, forClient client: UTMRemoteServer.Remote) async throws -> (port: UInt16, publicKey: Data) {
|
||||
func startRemote(vm: VMData, options: UTMVirtualMachineStartOptions, forClient client: UTMRemoteServer.Remote) async throws -> (port: UInt16, publicKey: Data, password: String) {
|
||||
guard let wrapped = vm.wrapped as? UTMQemuVirtualMachine, type(of: wrapped).capabilities.supportsRemoteSession else {
|
||||
throw UTMDataError.unsupportedBackend
|
||||
}
|
||||
@@ -94,7 +94,7 @@ extension UTMData {
|
||||
}
|
||||
try await wrapped.start(options: options.union(.remoteSession))
|
||||
vmWindows[vm] = session
|
||||
return (wrapped.config.qemu.spiceServerPort!, wrapped.config.qemu.spiceServerPublicKey!)
|
||||
return (wrapped.config.qemu.spiceServerPort!, wrapped.config.qemu.spiceServerPublicKey!, wrapped.config.qemu.spiceServerPassword!)
|
||||
}
|
||||
|
||||
func stop(vm: VMData) {
|
||||
|
||||
@@ -268,9 +268,9 @@ extension UTMRemoteClient {
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
func startVirtualMachine(id: UUID, options: UTMVirtualMachineStartOptions) async throws -> (port: UInt16, publicKey: Data) {
|
||||
func startVirtualMachine(id: UUID, options: UTMVirtualMachineStartOptions) async throws -> (port: UInt16, publicKey: Data, password: String) {
|
||||
let reply = try await _startVirtualMachine(parameters: .init(id: id, options: options))
|
||||
return (reply.spiceServerPort, reply.spiceServerPublicKey)
|
||||
return (reply.spiceServerPort, reply.spiceServerPublicKey, reply.spiceServerPassword)
|
||||
}
|
||||
|
||||
func stopVirtualMachine(id: UUID, method: UTMVirtualMachineStopMethod) async throws {
|
||||
|
||||
@@ -130,6 +130,7 @@ extension UTMRemoteMessageServer {
|
||||
struct Reply: Serializable, Codable {
|
||||
let spiceServerPort: UInt16
|
||||
let spiceServerPublicKey: Data
|
||||
let spiceServerPassword: String
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -631,8 +631,8 @@ extension UTMRemoteServer {
|
||||
|
||||
private func _startVirtualMachine(parameters: M.StartVirtualMachine.Request) async throws -> M.StartVirtualMachine.Reply {
|
||||
let vm = try await findVM(withId: parameters.id)
|
||||
let (port, publicKey) = try await data.startRemote(vm: vm, options: parameters.options, forClient: client)
|
||||
return .init(spiceServerPort: port, spiceServerPublicKey: publicKey)
|
||||
let (port, publicKey, password) = try await data.startRemote(vm: vm, options: parameters.options, forClient: client)
|
||||
return .init(spiceServerPort: port, spiceServerPublicKey: publicKey, spiceServerPassword: password)
|
||||
}
|
||||
|
||||
private func _stopVirtualMachine(parameters: M.StopVirtualMachine.Request) async throws -> M.StopVirtualMachine.Reply {
|
||||
|
||||
@@ -170,7 +170,11 @@ extension UTMRemoteSpiceVirtualMachine {
|
||||
options.insert(.hasDebugLog)
|
||||
}
|
||||
#endif
|
||||
let ioService = UTMSpiceIO(host: server.host, tlsPort: Int(spiceServer.port), serverPublicKey: spiceServer.publicKey, options: options)
|
||||
let ioService = UTMSpiceIO(host: server.host,
|
||||
tlsPort: Int(spiceServer.port),
|
||||
serverPublicKey: spiceServer.publicKey,
|
||||
password: spiceServer.password,
|
||||
options: options)
|
||||
ioService.logHandler = { (line: String) -> Void in
|
||||
guard !line.contains("spice_make_scancode") else {
|
||||
return // do not log key presses for privacy reasons
|
||||
|
||||
@@ -384,6 +384,11 @@ extension String {
|
||||
}
|
||||
return Int(numeric)
|
||||
}
|
||||
|
||||
static func random(length: Int) -> String {
|
||||
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
return String((0..<length).map{ _ in letters.randomElement()! })
|
||||
}
|
||||
}
|
||||
|
||||
extension Encodable {
|
||||
|
||||
@@ -276,10 +276,12 @@ extension UTMQemuVirtualMachine {
|
||||
let isRunningAsDisposible = options.contains(.bootDisposibleMode)
|
||||
let isRemoteSession = options.contains(.remoteSession)
|
||||
let spicePort = isRemoteSession ? try UTMSocketUtils.reservePort() : nil
|
||||
let spicePassword = isRemoteSession ? String.random(length: 32) : nil
|
||||
await MainActor.run {
|
||||
config.qemu.isDisposable = isRunningAsDisposible
|
||||
config.qemu.spiceServerPort = spicePort
|
||||
config.qemu.isSpiceServerTlsEnabled = true
|
||||
config.qemu.spiceServerPassword = spicePassword
|
||||
}
|
||||
|
||||
// start TPM
|
||||
|
||||
@@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithSocketUrl:(NSURL *)socketUrl options:(UTMSpiceIOOptions)options NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithHost:(NSString *)host tlsPort:(NSInteger)tlsPort serverPublicKey:(NSData *)serverPublicKey options:(UTMSpiceIOOptions)options NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithHost:(NSString *)host tlsPort:(NSInteger)tlsPort serverPublicKey:(NSData *)serverPublicKey password:(NSString *)password options:(UTMSpiceIOOptions)options NS_DESIGNATED_INITIALIZER;
|
||||
- (void)changeSharedDirectory:(NSURL *)url;
|
||||
|
||||
- (BOOL)startWithError:(NSError * _Nullable *)error;
|
||||
|
||||
@@ -26,6 +26,7 @@ NSString *const kUTMErrorDomain = @"com.utmapp.utm";
|
||||
@property (nonatomic, nullable) NSString *host;
|
||||
@property (nonatomic) NSInteger tlsPort;
|
||||
@property (nonatomic, nullable) NSData *serverPublicKey;
|
||||
@property (nonatomic, nullable) NSString *password;
|
||||
@property (nonatomic) UTMSpiceIOOptions options;
|
||||
@property (nonatomic, readwrite, nullable) CSDisplay *primaryDisplay;
|
||||
@property (nonatomic) NSMutableArray<CSDisplay *> *mutableDisplays;
|
||||
@@ -74,11 +75,12 @@ NSString *const kUTMErrorDomain = @"com.utmapp.utm";
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithHost:(NSString *)host tlsPort:(NSInteger)tlsPort serverPublicKey:(NSData *)serverPublicKey options:(UTMSpiceIOOptions)options {
|
||||
- (instancetype)initWithHost:(NSString *)host tlsPort:(NSInteger)tlsPort serverPublicKey:(NSData *)serverPublicKey password:(NSString *)password options:(UTMSpiceIOOptions)options {
|
||||
if (self = [super init]) {
|
||||
self.host = host;
|
||||
self.tlsPort = tlsPort;
|
||||
self.serverPublicKey = serverPublicKey;
|
||||
self.password = password;
|
||||
self.options = options;
|
||||
self.mutableDisplays = [NSMutableArray array];
|
||||
self.mutableSerials = [NSMutableArray array];
|
||||
@@ -94,6 +96,7 @@ NSString *const kUTMErrorDomain = @"com.utmapp.utm";
|
||||
self.spiceConnection = [[CSConnection alloc] initWithUnixSocketFile:relativeSocketFile];
|
||||
} else {
|
||||
self.spiceConnection = [[CSConnection alloc] initWithHost:self.host tlsPort:[@(self.tlsPort) stringValue] serverPublicKey:self.serverPublicKey];
|
||||
self.spiceConnection.password = self.password;
|
||||
}
|
||||
self.spiceConnection.delegate = self;
|
||||
self.spiceConnection.audioEnabled = (self.options & UTMSpiceIOOptionsHasAudio) == UTMSpiceIOOptionsHasAudio;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"location" : "https://github.com/utmapp/CocoaSpice.git",
|
||||
"state" : {
|
||||
"branch" : "visionos",
|
||||
"revision" : "9591cdf41282a7e6edbe7b705adbb957592ba347"
|
||||
"revision" : "9d286ba10b8ed953bf21c04ddd64237372163132"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user