diff --git a/Sources/ContainerClient/Core/ContainerConfiguration.swift b/Sources/ContainerClient/Core/ContainerConfiguration.swift index b65c358d..29769841 100644 --- a/Sources/ContainerClient/Core/ContainerConfiguration.swift +++ b/Sources/ContainerClient/Core/ContainerConfiguration.swift @@ -40,14 +40,16 @@ public struct ContainerConfiguration: Sendable, Codable { public var rosetta: Bool = false /// Initial or main process of the container. public var initProcess: ProcessConfiguration - /// Platform for the container + /// Platform for the container. public var platform: ContainerizationOCI.Platform = .current /// Resource values for the container. public var resources: Resources = .init() - /// Name of the runtime that supports the container + /// Name of the runtime that supports the container. public var runtimeHandler: String = "container-runtime-linux" /// Configure exposing virtualization support in the container. public var virtualization: Bool = false + /// Enable SSH agent socket forwarding from host to container. + public var ssh: Bool = false enum CodingKeys: String, CodingKey { case id @@ -65,6 +67,7 @@ public struct ContainerConfiguration: Sendable, Codable { case resources case runtimeHandler case virtualization + case ssh } /// Create a configuration from the supplied Decoder, initializing missing @@ -99,6 +102,7 @@ public struct ContainerConfiguration: Sendable, Codable { resources = try container.decodeIfPresent(Resources.self, forKey: .resources) ?? .init() runtimeHandler = try container.decodeIfPresent(String.self, forKey: .runtimeHandler) ?? "container-runtime-linux" virtualization = try container.decodeIfPresent(Bool.self, forKey: .virtualization) ?? false + ssh = try container.decodeIfPresent(Bool.self, forKey: .ssh) ?? false } public struct DNSConfiguration: Sendable, Codable { diff --git a/Sources/ContainerClient/Flags.swift b/Sources/ContainerClient/Flags.swift index 98854954..b3b0e09b 100644 --- a/Sources/ContainerClient/Flags.swift +++ b/Sources/ContainerClient/Flags.swift @@ -155,6 +155,9 @@ public struct Flags { "Expose virtualization capabilities to the container. (Host must have nested virtualization support, and guest kernel must have virtualization capabilities enabled)" ) public var virtualization: Bool = false + + @Flag(name: .customLong("ssh"), help: "Forward SSH agent socket to container") + public var ssh = false } public struct Progress: ParsableArguments { diff --git a/Sources/ContainerClient/Utility.swift b/Sources/ContainerClient/Utility.swift index 2366466e..504c2195 100644 --- a/Sources/ContainerClient/Utility.swift +++ b/Sources/ContainerClient/Utility.swift @@ -208,6 +208,8 @@ public struct Utility { // to enable socket forwarding from container to host. config.publishedSockets = try Parser.publishSockets(management.publishSockets) + config.ssh = management.ssh + return (config, kernel) } diff --git a/Sources/Services/ContainerSandboxService/SandboxService.swift b/Sources/Services/ContainerSandboxService/SandboxService.swift index bfa2602a..25e1b2dc 100644 --- a/Sources/Services/ContainerSandboxService/SandboxService.swift +++ b/Sources/Services/ContainerSandboxService/SandboxService.swift @@ -49,6 +49,9 @@ public actor SandboxService { private var processes: [String: ProcessInfo] = [:] private var socketForwarders: [SocketForwarderResult] = [] + private static let sshAuthSocketGuestPath = "/run/host-services/ssh-auth.sock" + private static let sshAuthSocketEnvVar = "SSH_AUTH_SOCK" + /// Create an instance with a bundle that describes the container. /// /// - Parameters: @@ -718,6 +721,17 @@ public actor SandboxService { czConfig.sockets.append(socketConfig) } + if config.ssh { + if let sshSocket = Foundation.ProcessInfo.processInfo.environment[Self.sshAuthSocketEnvVar] { + let socketConfig = UnixSocketConfiguration( + source: URL(fileURLWithPath: sshSocket), + destination: URL(fileURLWithPath: Self.sshAuthSocketGuestPath), + direction: .into + ) + czConfig.sockets.append(socketConfig) + } + } + czConfig.hostname = config.id if let dns = config.dns { @@ -726,7 +740,7 @@ public actor SandboxService { searchDomains: dns.searchDomains, options: dns.options) } - Self.configureInitialProcess(czConfig: &czConfig, process: config.initProcess) + Self.configureInitialProcess(czConfig: &czConfig, config: config) } private func getDefaultNameserver(attachmentConfigurations: [AttachmentConfiguration]) async throws -> String? { @@ -744,10 +758,24 @@ public actor SandboxService { private static func configureInitialProcess( czConfig: inout LinuxContainer.Configuration, - process: ProcessConfiguration + config: ContainerConfiguration ) { + let process = config.initProcess + czConfig.process.arguments = [process.executable] + process.arguments czConfig.process.environmentVariables = process.environment + + // Add SSH_AUTH_SOCK if ssh forwarding is enabled + if config.ssh { + if czConfig.sockets.contains(where: { + $0.destination == URL(fileURLWithPath: Self.sshAuthSocketGuestPath) + }) { + if !czConfig.process.environmentVariables.contains(where: { $0.starts(with: "\(Self.sshAuthSocketEnvVar)=") }) { + czConfig.process.environmentVariables.append("\(Self.sshAuthSocketEnvVar)=\(Self.sshAuthSocketGuestPath)") + } + } + } + czConfig.process.terminal = process.terminal czConfig.process.workingDirectory = process.workingDirectory czConfig.process.rlimits = process.rlimits.map {