Add SSH auth socket forwarding (#502)

Add `--ssh` flag to forward the host's SSH agent socket into the
container, so we can use SSH authentication for things like cloning
private repos, and also updates the socket path every time the container
starts to handle socket path changes like reboot/re-login.

Closes #498
This commit is contained in:
Renee Chang
2025-08-15 11:54:56 -07:00
committed by GitHub
parent a8dbf294f6
commit 94778981c8
4 changed files with 41 additions and 4 deletions
@@ -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 {
+3
View File
@@ -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 {
+2
View File
@@ -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)
}
@@ -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 {