From 7df587684e90e220d8060dbbe5b723d207ac422b Mon Sep 17 00:00:00 2001 From: haorui Date: Fri, 12 Jun 2026 00:50:53 +0800 Subject: [PATCH] Verify kernel archive SHA-256 digests --- Package.swift | 1 + .../System/Kernel/KernelSet.swift | 41 +++++++-- .../System/SystemStart.swift | 13 ++- .../ContainerSystemConfig.swift | 11 ++- .../Client/ClientKernel.swift | 14 ++- .../ContainerAPIService/Client/XPC+.swift | 1 + .../Server/Kernel/KernelHarness.swift | 18 +++- .../Server/Kernel/KernelService.swift | 52 ++++++++++- .../Subcommands/System/TestKernelSet.swift | 5 ++ .../KernelServiceTests.swift | 90 +++++++++++++++++++ .../ConfigurationLoaderTests.swift | 21 +++++ docs/command-reference.md | 3 +- docs/container-system-config.md | 11 +-- .../container-system-config-tutorial.md | 1 + 14 files changed, 257 insertions(+), 25 deletions(-) create mode 100644 Tests/ContainerAPIServiceTests/KernelServiceTests.swift diff --git a/Package.swift b/Package.swift index e9d8579d0..4d2ecf175 100644 --- a/Package.swift +++ b/Package.swift @@ -207,6 +207,7 @@ let package = Package( name: "ContainerAPIServiceTests", dependencies: [ .product(name: "Containerization", package: "containerization"), + "ContainerAPIService", "ContainerResource", "ContainerRuntimeLinuxClient", "ContainerRuntimeClient", diff --git a/Sources/ContainerCommands/System/Kernel/KernelSet.swift b/Sources/ContainerCommands/System/Kernel/KernelSet.swift index b4a85d66a..04a531ad9 100644 --- a/Sources/ContainerCommands/System/Kernel/KernelSet.swift +++ b/Sources/ContainerCommands/System/Kernel/KernelSet.swift @@ -47,6 +47,9 @@ extension Application { @Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file") var tarPath: String? = nil + @Option(name: .long, help: "Expected SHA-256 digest for the tar archive, as hex or with a sha256: prefix") + var sha256: String? = nil + @OptionGroup public var logOptions: Flags.Logging @@ -58,7 +61,11 @@ extension Application { let url = containerSystemConfig.kernel.url let path: String = containerSystemConfig.kernel.binaryPath log.info("Installing the recommended kernel from \(url)...") - try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force) + try await Self.downloadAndInstallWithProgressBar( + tarRemoteURL: url, + kernelFilePath: path, + expectedSha256: containerSystemConfig.kernel.sha256, + force: force) return } guard tarPath != nil else { @@ -68,6 +75,9 @@ extension Application { } private func setKernelFromBinary() async throws { + guard sha256 == nil else { + throw ArgumentParser.ValidationError("'--sha256' can only be used with '--tar'") + } guard let binaryPath else { throw ArgumentParser.ValidationError("missing argument '--binary'") } @@ -87,13 +97,23 @@ extension Application { let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path let fm = FileManager.default if fm.fileExists(atPath: localTarPath) { - try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force) + try await ClientKernel.installKernelFromTar( + tarFile: localTarPath, + kernelFilePath: binaryPath, + platform: platform, + expectedSha256: sha256, + force: force) return } guard let remoteURL = URL(string: tarPath) else { throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?") } - try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force) + try await Self.downloadAndInstallWithProgressBar( + tarRemoteURL: remoteURL, + kernelFilePath: binaryPath, + platform: platform, + expectedSha256: sha256, + force: force) } private func getSystemPlatform() throws -> SystemPlatform { @@ -107,7 +127,13 @@ extension Application { } } - static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws { + static func downloadAndInstallWithProgressBar( + tarRemoteURL: URL, + kernelFilePath: String, + platform: SystemPlatform = .current, + expectedSha256: String? = nil, + force: Bool + ) async throws { let progressConfig = try ProgressConfig( showTasks: true, totalTasks: 2 @@ -118,7 +144,12 @@ extension Application { } progress.start() try await ClientKernel.installKernelFromTar( - tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force) + tarFile: tarRemoteURL.absoluteString, + kernelFilePath: kernelFilePath, + platform: platform, + progressUpdate: progress.handler, + expectedSha256: expectedSha256, + force: force) progress.finish() } diff --git a/Sources/ContainerCommands/System/SystemStart.swift b/Sources/ContainerCommands/System/SystemStart.swift index 4b3a96243..55939cf28 100644 --- a/Sources/ContainerCommands/System/SystemStart.swift +++ b/Sources/ContainerCommands/System/SystemStart.swift @@ -157,7 +157,10 @@ extension Application { guard await !kernelExists() else { return } - try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath) + try await installDefaultKernel( + kernelURL: containerSystemConfig.kernel.url, + kernelBinaryPath: containerSystemConfig.kernel.binaryPath, + kernelSha256: containerSystemConfig.kernel.sha256) } private func installInitialFilesystem(initImage: String) async throws { @@ -171,7 +174,7 @@ extension Application { } } - private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws { + private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelSha256: String?) async throws { var shouldInstallKernel = false if kernelInstall == nil { print("No default kernel configured.") @@ -191,7 +194,11 @@ extension Application { return } log.info("Installing kernel...") - try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true) + try await KernelSet.downloadAndInstallWithProgressBar( + tarRemoteURL: kernelURL, + kernelFilePath: kernelBinaryPath, + expectedSha256: kernelSha256, + force: true) } private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool { diff --git a/Sources/ContainerPersistence/ContainerSystemConfig.swift b/Sources/ContainerPersistence/ContainerSystemConfig.swift index f0b9f36f2..4a986d858 100644 --- a/Sources/ContainerPersistence/ContainerSystemConfig.swift +++ b/Sources/ContainerPersistence/ContainerSystemConfig.swift @@ -168,21 +168,26 @@ final public class KernelConfig: Codable, Sendable { public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186" public static let defaultURL: URL = URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")! + public static let defaultSha256 = "f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91" private enum CodingKeys: String, CodingKey { case binaryPath case url + case sha256 } public let binaryPath: String public let url: URL + public let sha256: String? public init( binaryPath: String = defaultBinaryPath, - url: URL = defaultURL + url: URL = defaultURL, + sha256: String? = nil ) { self.binaryPath = binaryPath self.url = url + self.sha256 = sha256 ?? (url.absoluteString == Self.defaultURL.absoluteString ? Self.defaultSha256 : nil) } public init(from decoder: any Decoder) throws { @@ -197,6 +202,9 @@ final public class KernelConfig: Codable, Sendable { } else { self.url = Self.defaultURL } + self.sha256 = + try container.decodeIfPresent(String.self, forKey: .sha256) + ?? (self.url.absoluteString == Self.defaultURL.absoluteString ? Self.defaultSha256 : nil) } // JSONEncoder special-cases URL to encode as absoluteString, but third-party @@ -209,6 +217,7 @@ final public class KernelConfig: Codable, Sendable { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(binaryPath, forKey: .binaryPath) try container.encode(url.absoluteString, forKey: .url) + try container.encodeIfPresent(sha256, forKey: .sha256) } } diff --git a/Sources/Services/ContainerAPIService/Client/ClientKernel.swift b/Sources/Services/ContainerAPIService/Client/ClientKernel.swift index 3cac4693c..d5d6a6d4a 100644 --- a/Sources/Services/ContainerAPIService/Client/ClientKernel.swift +++ b/Sources/Services/ContainerAPIService/Client/ClientKernel.swift @@ -42,15 +42,23 @@ extension ClientKernel { try await client.send(message) } - public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool) - async throws - { + public static func installKernelFromTar( + tarFile: String, + kernelFilePath: String, + platform: SystemPlatform, + progressUpdate: ProgressUpdateHandler? = nil, + expectedSha256: String? = nil, + force: Bool + ) async throws { let client = newClient() let message = XPCMessage(route: .installKernel) message.set(key: .kernelTarURL, value: tarFile) message.set(key: .kernelFilePath, value: kernelFilePath) message.set(key: .kernelForce, value: force) + if let expectedSha256 { + message.set(key: .kernelSha256, value: expectedSha256) + } let platformData = try JSONEncoder().encode(platform) message.set(key: .systemPlatform, value: platformData) diff --git a/Sources/Services/ContainerAPIService/Client/XPC+.swift b/Sources/Services/ContainerAPIService/Client/XPC+.swift index 499b82b84..0654a241c 100644 --- a/Sources/Services/ContainerAPIService/Client/XPC+.swift +++ b/Sources/Services/ContainerAPIService/Client/XPC+.swift @@ -112,6 +112,7 @@ public enum XPCKeys: String { case kernelFilePath case systemPlatform case kernelForce + case kernelSha256 /// Init image reference case initImage diff --git a/Sources/Services/ContainerAPIService/Server/Kernel/KernelHarness.swift b/Sources/Services/ContainerAPIService/Server/Kernel/KernelHarness.swift index d12905778..cba0eca45 100644 --- a/Sources/Services/ContainerAPIService/Server/Kernel/KernelHarness.swift +++ b/Sources/Services/ContainerAPIService/Server/Kernel/KernelHarness.swift @@ -35,6 +35,7 @@ public struct KernelHarness: Sendable { let kernelFilePath = try message.kernelFilePath() let platform = try message.platform() let force = try message.kernelForce() + let expectedSha256 = message.kernelSha256() guard let kernelTarUrl = try message.kernelTarURL() else { // We have been given a path to a kernel binary on disk @@ -47,7 +48,12 @@ public struct KernelHarness: Sendable { let progressUpdateService = ProgressUpdateService(message: message) try await self.service.installKernelFrom( - tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force) + tar: kernelTarUrl, + kernelFilePath: kernelFilePath, + platform: platform, + progressUpdate: progressUpdateService?.handler, + expectedSha256: expectedSha256, + force: force) return message.reply() } @@ -85,13 +91,17 @@ extension XPCMessage { guard let kernelTarURLString = self.string(key: .kernelTarURL) else { return nil } - guard let k = URL(string: kernelTarURLString) else { - throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)") + if let k = URL(string: kernelTarURLString), k.scheme != nil { + return k } - return k + return URL(fileURLWithPath: kernelTarURLString) } fileprivate func kernelForce() throws -> Bool { self.bool(key: .kernelForce) } + + fileprivate func kernelSha256() -> String? { + self.string(key: .kernelSha256) + } } diff --git a/Sources/Services/ContainerAPIService/Server/Kernel/KernelService.swift b/Sources/Services/ContainerAPIService/Server/Kernel/KernelService.swift index 84c55a532..320904fea 100644 --- a/Sources/Services/ContainerAPIService/Server/Kernel/KernelService.swift +++ b/Sources/Services/ContainerAPIService/Server/Kernel/KernelService.swift @@ -19,6 +19,7 @@ import Containerization import ContainerizationArchive import ContainerizationError import ContainerizationExtras +import CryptoKit import Foundation import Logging import TerminalProgress @@ -81,7 +82,14 @@ public actor KernelService { /// Copies a kernel binary from inside of tar file into the managed kernels directory /// as the default kernel for the provided platform. /// The parameter `tar` maybe a location to a local file on disk, or a remote URL. - public func installKernelFrom(tar: URL, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler?, force: Bool) async throws { + public func installKernelFrom( + tar: URL, + kernelFilePath: String, + platform: SystemPlatform, + progressUpdate: ProgressUpdateHandler?, + expectedSha256: String? = nil, + force: Bool + ) async throws { log.debug( "KernelService: enter", metadata: [ @@ -114,7 +122,12 @@ public actor KernelService { let taskManager = ProgressTaskCoordinator() let downloadTask = await taskManager.startTask() var tarFile = tar - if !FileManager.default.fileExists(atPath: tar.absoluteString) { + let localTarPath = tar.scheme == nil || tar.isFileURL ? tar.path : nil + let isLocalTar = localTarPath.map { FileManager.default.fileExists(atPath: $0) } ?? false + if isLocalTar, let localTarPath { + tarFile = URL(fileURLWithPath: localTarPath) + } + if !isLocalTar { self.log.debug("KernelService: start download", metadata: ["tar": "\(tar)"]) tarFile = tempDir.appendingPathComponent(tar.lastPathComponent) var downloadProgressUpdate: ProgressUpdateHandler? @@ -125,17 +138,50 @@ public actor KernelService { } await taskManager.finish() + if let expectedSha256 { + await progressUpdate?([ + .setDescription("Verifying kernel download") + ]) + try Self.verifySHA256Digest(of: tarFile, expected: expectedSha256) + } + await progressUpdate?([ .setDescription("Unpacking kernel") ]) let kernelFile = try self.extractFile(tarFile: tarFile, at: kernelFilePath, to: tempDir) try self.installKernel(kernelFile: kernelFile, platform: platform, force: force) - if !FileManager.default.fileExists(atPath: tar.absoluteString) { + if !isLocalTar { try FileManager.default.removeItem(at: tarFile) } } + static func verifySHA256Digest(of file: URL, expected: String) throws { + let lowercased = expected.lowercased() + let expectedDigest = lowercased.hasPrefix("sha256:") ? String(lowercased.dropFirst("sha256:".count)) : lowercased + guard expectedDigest.count == 64, expectedDigest.utf8.allSatisfy({ ($0 >= 48 && $0 <= 57) || ($0 >= 97 && $0 <= 102) }) else { + throw ContainerizationError(.invalidArgument, message: "invalid SHA-256 digest '\(expected)'") + } + + let actualDigest = try sha256Hex(of: file) + guard actualDigest == expectedDigest else { + throw ContainerizationError( + .invalidState, + message: "kernel archive digest mismatch: expected sha256:\(expectedDigest), got sha256:\(actualDigest)" + ) + } + } + + static func sha256Hex(of file: URL) throws -> String { + var hasher = SHA256() + let handle = try FileHandle(forReadingFrom: file) + defer { try? handle.close() } + while let data = try handle.read(upToCount: Int(1.mib())), !data.isEmpty { + hasher.update(data: data) + } + return hasher.finalize().map { String(format: "%02x", $0) }.joined() + } + private func setDefaultKernel(name: String, platform: SystemPlatform) throws { log.debug( "KernelService: enter", diff --git a/Tests/CLITests/Subcommands/System/TestKernelSet.swift b/Tests/CLITests/Subcommands/System/TestKernelSet.swift index f88e19e17..b39dd6f56 100644 --- a/Tests/CLITests/Subcommands/System/TestKernelSet.swift +++ b/Tests/CLITests/Subcommands/System/TestKernelSet.swift @@ -25,6 +25,7 @@ import Testing class TestCLIKernelSet: CLITest { let remoteTar = ContainerSystemConfig().kernel.url let defaultBinaryPath = ContainerSystemConfig().kernel.binaryPath + let defaultSha256 = KernelConfig.defaultSha256 deinit { try? resetDefaultBinary() @@ -85,6 +86,8 @@ class TestCLIKernelSet: CLITest { localTarPath.path, "--binary", symlinkBinaryPath, + "--sha256", + defaultSha256, ] try doKernelSet(extraArgs: extraArgs) @@ -100,6 +103,8 @@ class TestCLIKernelSet: CLITest { remoteTar.absoluteString, "--binary", symlinkBinaryPath, + "--sha256", + defaultSha256, ] try doKernelSet(extraArgs: extraArgs) diff --git a/Tests/ContainerAPIServiceTests/KernelServiceTests.swift b/Tests/ContainerAPIServiceTests/KernelServiceTests.swift new file mode 100644 index 000000000..ae96eb9a4 --- /dev/null +++ b/Tests/ContainerAPIServiceTests/KernelServiceTests.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the container project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Containerization +import ContainerizationArchive +import ContainerizationError +import Foundation +import Logging +import Testing + +@testable import ContainerAPIService + +struct KernelServiceTests { + @Test func verifySHA256Digest() throws { + try withTempFile(contents: "kernel archive") { file in + try KernelService.verifySHA256Digest(of: file, expected: "sha256:\(KernelService.sha256Hex(of: file))") + #expect(throws: ContainerizationError.self) { + try KernelService.verifySHA256Digest(of: file, expected: "sha256:not-a-digest") + } + #expect(throws: ContainerizationError.self) { + try KernelService.verifySHA256Digest(of: file, expected: String(repeating: "0", count: 64)) + } + } + } + + @Test func installKernelFromLocalTarVerifiesDigest() async throws { + try await withTempDir { tempDir in + let kernelPath = "boot/vmlinux" + let kernelData = Data("kernel binary".utf8) + let tarFile = try Self.writeTar( + at: tempDir.appendingPathComponent("kernel.tar"), + path: kernelPath, + data: kernelData) + let service = try KernelService( + log: Logger(label: "com.apple.container.test.kernel-service"), + appRoot: tempDir.appendingPathComponent("app")) + let digest = try KernelService.sha256Hex(of: tarFile) + + try await service.installKernelFrom( + tar: URL(string: tarFile.path)!, + kernelFilePath: kernelPath, + platform: .linuxArm, + progressUpdate: nil, + expectedSha256: "sha256:\(digest)", + force: false) + + let kernel = try await service.getDefaultKernel(platform: .linuxArm) + #expect(try Data(contentsOf: kernel.path) == kernelData) + } + } + + private static func writeTar(at tarFile: URL, path: String, data: Data) throws -> URL { + let archiver = try ArchiveWriter(format: .paxRestricted, filter: .none, file: tarFile) + let entry = WriteEntry() + entry.path = path + entry.fileType = .regular + entry.permissions = 0o644 + entry.size = numericCast(data.count) + try archiver.writeEntry(entry: entry, data: data) + try archiver.finishEncoding() + return tarFile + } + + private func withTempFile(contents: String, body: (URL) throws -> Void) throws { + let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try Data(contents.utf8).write(to: file) + defer { try? FileManager.default.removeItem(at: file) } + try body(file) + } + + private func withTempDir(body: (URL) async throws -> Void) async throws { + let dir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: dir) } + try await body(dir) + } +} diff --git a/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift b/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift index 5f27694dc..44b4ce4fb 100644 --- a/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift +++ b/Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift @@ -95,6 +95,7 @@ struct ConfigurationLoaderTests { #expect(!config.vminit.image.isEmpty) #expect(!config.kernel.binaryPath.isEmpty) #expect(!config.kernel.url.absoluteString.isEmpty) + #expect(config.kernel.sha256 == KernelConfig.defaultSha256) #expect(config.network.subnet == nil) #expect(config.network.subnetv6 == nil) #expect(config.registry.domain == "docker.io") @@ -120,6 +121,7 @@ struct ConfigurationLoaderTests { [kernel] binaryPath = "custom/path" url = "https://example.com/kernel.tar" + sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" [network] subnet = "10.0.0.1/16" @@ -147,6 +149,7 @@ struct ConfigurationLoaderTests { #expect(config.vminit.image == "custom-init:latest") #expect(config.kernel.binaryPath == "custom/path") #expect(config.kernel.url.absoluteString == "https://example.com/kernel.tar") + #expect(config.kernel.sha256 == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") let expectedSubnet = try CIDRv4("10.0.0.1/16") let expectedSubnetV6 = try CIDRv6("fd01::/48") #expect(config.network.subnet == expectedSubnet) @@ -173,6 +176,24 @@ struct ConfigurationLoaderTests { } } + @Test func customKernelURLWithoutSha256LeavesDigestUnset() async throws { + try await TemporaryStorage.withTempDir { tempDir in + let toml = """ + [kernel] + url = "https://example.com/custom-kernel.tar" + """ + let tmpFile = tempDir.appending("test.toml") + try Self.writeToml(toml, to: tmpFile) + + let config: ContainerSystemConfig = try await ConfigurationLoader.load(configurationFiles: [tmpFile]) + #expect(config.kernel.url.absoluteString == "https://example.com/custom-kernel.tar") + #expect(config.kernel.sha256 == nil) + } + + let programmaticConfig = KernelConfig(url: URL(string: "https://example.com/custom-kernel.tar")!) + #expect(programmaticConfig.sha256 == nil) + } + @Test func unknownKeysIgnored() async throws { try await TemporaryStorage.withTempDir { tempDir in let toml = """ diff --git a/docs/command-reference.md b/docs/command-reference.md index 1492c7f28..6a79ef6d7 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -1530,7 +1530,7 @@ Installs or updates the Linux kernel used by the container runtime on macOS host **Usage** ```bash -container system kernel set [--arch ] [--binary ] [--force] [--recommended] [--tar ] [--debug] +container system kernel set [--arch ] [--binary ] [--force] [--recommended] [--tar ] [--sha256 ] [--debug] ``` **Options** @@ -1540,6 +1540,7 @@ container system kernel set [--arch ] [--binary ] [--force] [--rec * `--force`: Overwrites an existing kernel with the same name * `--recommended`: Download and install the recommended kernel as the default (takes precedence over all other flags) * `--tar `: Filesystem path or remote URL to a tar archive containing a kernel file +* `--sha256 `: Expected SHA-256 digest for the tar archive, as hex or with a `sha256:` prefix ### `container system property list (ls)` diff --git a/docs/container-system-config.md b/docs/container-system-config.md index 547ae5ff7..98ef3468d 100644 --- a/docs/container-system-config.md +++ b/docs/container-system-config.md @@ -15,7 +15,7 @@ Source of truth: [`Sources/ContainerPersistence/ContainerSystemConfig.swift`](.. [build] # builder VM resources and image [container] # default per-container resources [dns] # default DNS domain for DNS resolution on host -[kernel] # guest kernel binary path and download URL +[kernel] # guest kernel binary path, download URL, and digest [network] # default subnets for new networks [registry] # default registry domain [vminit] # default vminitd image to use @@ -54,10 +54,11 @@ Defaults applied when `container run` / `container create` is invoked without `- Guest kernel used when launching container VMs. Defaults change per release as kernels are bumped — check the [source](../Sources/ContainerPersistence/ContainerSystemConfig.swift) for current values. -| Key | Type | Default | Description | -|--------------|----------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| `binaryPath` | `String` | `"opt/kata/share/kata-containers/vmlinux-6.18.15-186"` | Path **inside** the downloaded kernel archive that points to the kernel binary. | -| `url` | `URL` | `"https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst"` | Archive to download when no kernel is installed. Encoded and decoded as a plain string in TOML. | +| Key | Type | Default | Description | +|--------------|-----------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| +| `binaryPath` | `String` | `"opt/kata/share/kata-containers/vmlinux-6.18.15-186"` | Path **inside** the downloaded kernel archive that points to the kernel binary. | +| `url` | `URL` | `"https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst"` | Archive to download when no kernel is installed. Encoded and decoded as a plain string in TOML. | +| `sha256` | `String?` | `"f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"` | Expected SHA-256 digest for the archive, as hex or with a `sha256:` prefix. When unset for a custom URL, remote kernel downloads are not verified. | ## `[network]` diff --git a/docs/tutorials/container-system-config-tutorial.md b/docs/tutorials/container-system-config-tutorial.md index df8a4f79e..1b555833c 100644 --- a/docs/tutorials/container-system-config-tutorial.md +++ b/docs/tutorials/container-system-config-tutorial.md @@ -77,6 +77,7 @@ domain = "test" [kernel] binaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186" url = "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst" +sha256 = "f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91" [network]