Skip to content

Commit 49f1a56

Browse files
authored
Fix ssh forwarding to use current SSH_AUTH_SOCK value (apple#1420)
This PR fixes apple#357, passing `SSH_AUTH_SOCK` env variable from current terminal to the `SandboxService` so that the container can mount the correct ssh auth socket. For that, it introduces `env` parameters to `bootstrap` RPC of both `ContainersService` and `SandboxService`. This parameter is used only for passing `SSH_AUTH_SOCK` now, but can be extended to pass more runtime env variables. This PR is a follow up PR of apple#1214. ## Type of Change - [X] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Motivation and Context Container run `--ssh` was inheriting `SSH_AUTH_SOCK` env variable from launchd, not from current terminal. ## Testing - [X] Tested locally - [ ] Added/updated tests - [ ] Added/updated docs
1 parent 9be1020 commit 49f1a56

12 files changed

Lines changed: 152 additions & 22 deletions

File tree

Sources/ContainerCommands/Builder/BuilderStart.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,12 @@ private func startBuildKit(
326326
)
327327
defer { try? io.close() }
328328

329-
let process = try await client.bootstrap(id: id, stdio: io.stdio)
329+
var env: [String: String] = [:]
330+
if let sshAuthSock = ProcessInfo.processInfo.environment["SSH_AUTH_SOCK"] {
331+
env["SSH_AUTH_SOCK"] = sshAuthSock
332+
}
333+
334+
let process = try await client.bootstrap(id: id, stdio: io.stdio, env: env)
330335
try await process.start()
331336
await taskManager?.finish()
332337
try io.closeAfterStart()

Sources/ContainerCommands/Container/ContainerRun.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ extension Application {
123123
try? io.close()
124124
}
125125

126-
let process = try await client.bootstrap(id: id, stdio: io.stdio)
126+
var env: [String: String] = [:]
127+
if let sshAuthSock = ProcessInfo.processInfo.environment["SSH_AUTH_SOCK"] {
128+
env["SSH_AUTH_SOCK"] = sshAuthSock
129+
}
130+
131+
let process = try await client.bootstrap(id: id, stdio: io.stdio, env: env)
127132
progress.finish()
128133

129134
if !self.managementFlags.cidfile.isEmpty {

Sources/ContainerCommands/Container/ContainerStart.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ extension Application {
8787
try? io.close()
8888
}
8989

90-
let process = try await client.bootstrap(id: container.id, stdio: io.stdio)
90+
var env: [String: String] = [:]
91+
if let sshAuthSock = ProcessInfo.processInfo.environment["SSH_AUTH_SOCK"] {
92+
env["SSH_AUTH_SOCK"] = sshAuthSock
93+
}
94+
95+
let process = try await client.bootstrap(id: container.id, stdio: io.stdio, env: env)
9196
progress.finish()
9297

9398
if detach {

Sources/Services/ContainerAPIService/Client/ContainerClient.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public struct ContainerClient: Sendable {
113113
}
114114

115115
/// Bootstrap the container's init process.
116-
public func bootstrap(id: String, stdio: [FileHandle?]) async throws -> ClientProcess {
116+
public func bootstrap(id: String, stdio: [FileHandle?], env: [String: String]? = nil) async throws -> ClientProcess {
117117
let request = XPCMessage(route: .containerBootstrap)
118118

119119
for (i, h) in stdio.enumerated() {
@@ -133,6 +133,9 @@ public struct ContainerClient: Sendable {
133133
}
134134

135135
do {
136+
let env = try JSONEncoder().encode(env)
137+
request.set(key: .env, value: env)
138+
136139
request.set(key: .id, value: id)
137140
try await xpcClient.send(request)
138141
return ClientProcessImpl(containerId: id, xpcClient: xpcClient)

Sources/Services/ContainerAPIService/Client/XPC+.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public enum XPCKeys: String {
5656
case plugin
5757
/// Archive path to export rootfs
5858
case archive
59+
/// Environment variables passed from terminal
60+
case env
5961

6062
/// Health check request.
6163
case ping

Sources/Services/ContainerAPIService/Server/Containers/ContainersHarness.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,16 @@ public struct ContainersHarness: Sendable {
5555
)
5656
}
5757
let stdio = message.stdio()
58-
try await service.bootstrap(id: id, stdio: stdio)
58+
59+
guard let data = message.dataNoCopy(key: .env) else {
60+
throw ContainerizationError(
61+
.invalidArgument,
62+
message: "env cannot be empty"
63+
)
64+
}
65+
let env = try JSONDecoder().decode([String: String]?.self, from: data)
66+
67+
try await service.bootstrap(id: id, stdio: stdio, env: env)
5968
return message.reply()
6069
}
6170

Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,12 +398,13 @@ public actor ContainersService {
398398
}
399399

400400
/// Bootstrap the init process of the container.
401-
public func bootstrap(id: String, stdio: [FileHandle?]) async throws {
401+
public func bootstrap(id: String, stdio: [FileHandle?], env: [String: String]?) async throws {
402402
log.debug(
403403
"ContainersService: enter",
404404
metadata: [
405405
"func": "\(#function)",
406406
"id": "\(id)",
407+
"env": "\(env ?? [:])",
407408
]
408409
)
409410
defer {
@@ -473,7 +474,7 @@ public actor ContainersService {
473474
id: id,
474475
runtime: runtime
475476
)
476-
try await sandboxClient.bootstrap(stdio: stdio, allocatedAttachments: allocatedAttachments)
477+
try await sandboxClient.bootstrap(stdio: stdio, allocatedAttachments: allocatedAttachments, env: env)
477478

478479
try await self.exitMonitor.registerProcess(
479480
id: id,

Sources/Services/ContainerSandboxService/Client/SandboxClient.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public struct SandboxClient: Sendable {
7777

7878
// Runtime Methods
7979
extension SandboxClient {
80-
public func bootstrap(stdio: [FileHandle?], allocatedAttachments: [AllocatedAttachment]) async throws {
80+
public func bootstrap(stdio: [FileHandle?], allocatedAttachments: [AllocatedAttachment], env: [String: String]? = nil) async throws {
8181
let request = XPCMessage(route: SandboxRoutes.bootstrap.rawValue)
8282

8383
for (i, h) in stdio.enumerated() {
@@ -97,6 +97,9 @@ extension SandboxClient {
9797
}
9898

9999
do {
100+
let env = try JSONEncoder().encode(env)
101+
request.set(key: SandboxKeys.env.rawValue, value: env)
102+
100103
try request.setAllocatedAttachments(allocatedAttachments)
101104
try await self.client.send(request)
102105
} catch {

Sources/Services/ContainerSandboxService/Client/SandboxKeys.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public enum SandboxKeys: String {
4343
/// Container statistics
4444
case statistics
4545

46+
/// Environment variables passed from terminal.
47+
case env
48+
4649
/// Network resource keys.
4750
case allocatedAttachments
4851
case networkAdditionalData

Sources/Services/ContainerSandboxService/Server/SandboxService.swift

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,17 @@ public actor SandboxService {
7676
}
7777
}
7878

79-
private static func sshAuthSocketHostUrl(config: ContainerConfiguration) -> URL? {
80-
if config.ssh, let sshSocket = Foundation.ProcessInfo.processInfo.environment[Self.sshAuthSocketEnvVar] {
81-
return URL(fileURLWithPath: sshSocket)
79+
private static func sshAuthSocketHostUrl(config: ContainerConfiguration, hostEnv: [String: String]?, log: Logger? = nil) -> URL? {
80+
guard config.ssh else {
81+
return nil
8282
}
83-
return nil
83+
84+
guard let sshSocket = hostEnv?[Self.sshAuthSocketEnvVar] else {
85+
log?.warning("ssh forwarding requested but no \(Self.sshAuthSocketEnvVar) found")
86+
return nil
87+
}
88+
89+
return URL(fileURLWithPath: sshSocket)
8490
}
8591

8692
public init(
@@ -141,6 +147,8 @@ public actor SandboxService {
141147
)
142148
}
143149

150+
let env = try message.env()
151+
144152
let bundle = ContainerResource.Bundle(path: self.root)
145153
try bundle.createLogFile()
146154

@@ -216,7 +224,7 @@ public actor SandboxService {
216224
let id = config.id
217225
let rootfs = try bundle.containerRootfs.asMount
218226
let container = try LinuxContainer(id, rootfs: rootfs, vmm: vmm, logger: self.log) { czConfig in
219-
try Self.configureContainer(czConfig: &czConfig, config: config)
227+
try Self.configureContainer(czConfig: &czConfig, config: config, hostEnv: env, log: self.log)
220228
czConfig.interfaces = interfaces
221229
czConfig.process.stdout = stdout
222230
czConfig.process.stderr = stderr
@@ -710,7 +718,7 @@ public actor SandboxService {
710718
let czConfig = try self.configureProcessConfig(
711719
config: processInfo.config,
712720
stdio: processInfo.io,
713-
containerConfig: containerInfo.config
721+
containerConfig: containerInfo.config,
714722
)
715723

716724
let process = try await container.exec(id, configuration: czConfig)
@@ -837,7 +845,9 @@ public actor SandboxService {
837845

838846
private static func configureContainer(
839847
czConfig: inout LinuxContainer.Configuration,
840-
config: ContainerConfiguration
848+
config: ContainerConfiguration,
849+
hostEnv: [String: String]?,
850+
log: Logger? = nil,
841851
) throws {
842852
czConfig.cpus = config.resources.cpus
843853
czConfig.memoryInBytes = config.resources.memoryInBytes
@@ -870,7 +880,7 @@ public actor SandboxService {
870880
czConfig.sockets.append(socketConfig)
871881
}
872882

873-
if let socketUrl = Self.sshAuthSocketHostUrl(config: config) {
883+
if let socketUrl = Self.sshAuthSocketHostUrl(config: config, hostEnv: hostEnv, log: log) {
874884
let socketPath = socketUrl.path(percentEncoded: false)
875885
let attrs = try? FileManager.default.attributesOfItem(atPath: socketPath)
876886
let permissions = (attrs?[.posixPermissions] as? NSNumber)
@@ -914,14 +924,14 @@ public actor SandboxService {
914924

915925
private static func configureInitialProcess(
916926
czConfig: inout LinuxContainer.Configuration,
917-
config: ContainerConfiguration
927+
config: ContainerConfiguration,
918928
) throws {
919929
let process = config.initProcess
920930

921931
czConfig.process.arguments = [process.executable] + process.arguments
922932
czConfig.process.environmentVariables = process.environment
923933

924-
if Self.sshAuthSocketHostUrl(config: config) != nil {
934+
if config.ssh {
925935
if !czConfig.process.environmentVariables.contains(where: { $0.starts(with: "\(Self.sshAuthSocketEnvVar)=") }) {
926936
czConfig.process.environmentVariables.append("\(Self.sshAuthSocketEnvVar)=\(Self.sshAuthSocketGuestPath)")
927937
}
@@ -971,7 +981,7 @@ public actor SandboxService {
971981
proc.arguments = [config.executable] + config.arguments
972982
proc.environmentVariables = config.environment
973983

974-
if Self.sshAuthSocketHostUrl(config: containerConfig) != nil {
984+
if containerConfig.ssh {
975985
if !proc.environmentVariables.contains(where: { $0.starts(with: "\(Self.sshAuthSocketEnvVar)=") }) {
976986
proc.environmentVariables.append("\(Self.sshAuthSocketEnvVar)=\(Self.sshAuthSocketGuestPath)")
977987
}
@@ -1155,6 +1165,13 @@ extension XPCMessage {
11551165
return try JSONDecoder().decode(ProcessConfiguration.self, from: data)
11561166
}
11571167

1168+
fileprivate func env() throws -> [String: String]? {
1169+
guard let data = self.dataNoCopy(key: SandboxKeys.env.rawValue) else {
1170+
throw ContainerizationError(.invalidArgument, message: "empty env")
1171+
}
1172+
return try JSONDecoder().decode([String: String]?.self, from: data)
1173+
}
1174+
11581175
fileprivate func getAllocatedAttachments() throws -> [AllocatedAttachment] {
11591176
guard let attachmentArray = xpc_dictionary_get_value(self.underlying, SandboxKeys.allocatedAttachments.rawValue) else {
11601177
throw ContainerizationError(.invalidArgument, message: "missing allocatedAttachments array in message")

0 commit comments

Comments
 (0)