mirror of
https://github.com/apple/container.git
synced 2026-07-13 21:17:05 +00:00
57b07fa2ac
Closes https://github.com/apple/container/issues/1687 The default kernel archive is downloaded from a remote release URL during first-run setup and via `container system kernel set --recommended`. Previously, the archive contents were not verified after download, so integrity depended on HTTPS and the release artifact remaining unchanged. This change adds digest verification for kernel archives. The recommended/default kernel now has pinned digest metadata using an algorithm-prefixed value such as `sha256:<hex>`. `container system kernel set --tar` accepts `--digest`; remote tar URLs require it, and local tar archives can also be verified before unpacking and installation. The system config also supports `kernel.digest`, and a custom `kernel.url` must provide a digest for that archive.
255 lines
9.7 KiB
Swift
255 lines
9.7 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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 CVersion
|
|
import ContainerVersion
|
|
import ContainerizationExtras
|
|
import Foundation
|
|
|
|
/// Top-level configuration decoded from config.toml.
|
|
///
|
|
/// Each section maps to a nested struct. Missing keys fall back to
|
|
/// hardcoded defaults via custom `init(from:)` implementations.
|
|
public final class ContainerSystemConfig: Codable, Sendable, Initable {
|
|
public let build: BuildConfig
|
|
public let container: ContainerConfig
|
|
public let dns: DNSConfig
|
|
public let kernel: KernelConfig
|
|
public let machine: MachineConfig
|
|
public let network: NetworkConfig
|
|
public let registry: RegistryConfig
|
|
public let vminit: VminitConfig
|
|
|
|
public init(
|
|
build: BuildConfig = .init(),
|
|
container: ContainerConfig = .init(),
|
|
dns: DNSConfig = .init(),
|
|
kernel: KernelConfig = .init(),
|
|
machine: MachineConfig = MachineConfig.default,
|
|
network: NetworkConfig = .init(),
|
|
registry: RegistryConfig = .init(),
|
|
vminit: VminitConfig = .init()
|
|
) {
|
|
self.build = build
|
|
self.container = container
|
|
self.dns = dns
|
|
self.kernel = kernel
|
|
self.machine = machine
|
|
self.network = network
|
|
self.registry = registry
|
|
self.vminit = vminit
|
|
}
|
|
|
|
public init() {
|
|
self.build = .init()
|
|
self.container = .init()
|
|
self.dns = .init()
|
|
self.kernel = .init()
|
|
self.machine = MachineConfig.default
|
|
self.network = .init()
|
|
self.registry = .init()
|
|
self.vminit = .init()
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.build = try container.decodeIfPresent(BuildConfig.self, forKey: .build) ?? .init()
|
|
self.container = try container.decodeIfPresent(ContainerConfig.self, forKey: .container) ?? .init()
|
|
self.dns = try container.decodeIfPresent(DNSConfig.self, forKey: .dns) ?? .init()
|
|
self.kernel = try container.decodeIfPresent(KernelConfig.self, forKey: .kernel) ?? .init()
|
|
self.machine = try container.decodeIfPresent(MachineConfig.self, forKey: .machine) ?? MachineConfig.default
|
|
self.network = try container.decodeIfPresent(NetworkConfig.self, forKey: .network) ?? .init()
|
|
self.registry = try container.decodeIfPresent(RegistryConfig.self, forKey: .registry) ?? .init()
|
|
self.vminit = try container.decodeIfPresent(VminitConfig.self, forKey: .vminit) ?? .init()
|
|
}
|
|
}
|
|
|
|
final public class BuildConfig: Codable, Sendable {
|
|
public static let defaultRosetta = true
|
|
public static let defaultCPUs = 2
|
|
public static let defaultMemory = try! MemorySize("2048MB")
|
|
public static var defaultImage: String {
|
|
let tag = String(cString: get_container_builder_shim_version())
|
|
return "ghcr.io/apple/container-builder-shim/builder:\(tag)"
|
|
}
|
|
|
|
public let rosetta: Bool
|
|
public let cpus: Int
|
|
public let memory: MemorySize
|
|
public let image: String
|
|
|
|
public init(
|
|
rosetta: Bool = defaultRosetta,
|
|
cpus: Int = defaultCPUs,
|
|
memory: MemorySize = defaultMemory,
|
|
image: String = defaultImage
|
|
) {
|
|
self.rosetta = rosetta
|
|
self.cpus = cpus
|
|
self.memory = memory
|
|
self.image = image
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.rosetta = try container.decodeIfPresent(Bool.self, forKey: .rosetta) ?? Self.defaultRosetta
|
|
self.cpus = try container.decodeIfPresent(Int.self, forKey: .cpus) ?? Self.defaultCPUs
|
|
self.memory = try container.decodeIfPresent(MemorySize.self, forKey: .memory) ?? Self.defaultMemory
|
|
self.image = try container.decodeIfPresent(String.self, forKey: .image) ?? Self.defaultImage
|
|
}
|
|
}
|
|
|
|
final public class ContainerConfig: Codable, Sendable {
|
|
public static let defaultCPUs = 4
|
|
public static let defaultMemory = try! MemorySize("1g")
|
|
|
|
public let cpus: Int
|
|
public let memory: MemorySize
|
|
|
|
public init(cpus: Int = defaultCPUs, memory: MemorySize = defaultMemory) {
|
|
self.cpus = cpus
|
|
self.memory = memory
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.cpus = try container.decodeIfPresent(Int.self, forKey: .cpus) ?? Self.defaultCPUs
|
|
self.memory = try container.decodeIfPresent(MemorySize.self, forKey: .memory) ?? Self.defaultMemory
|
|
}
|
|
}
|
|
|
|
final public class DNSConfig: Codable, Sendable {
|
|
public let domain: String?
|
|
|
|
public init(domain: String? = nil) {
|
|
self.domain = domain
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.domain = try container.decodeIfPresent(String.self, forKey: .domain)
|
|
}
|
|
}
|
|
|
|
final public class VminitConfig: Codable, Sendable {
|
|
public static var defaultImage: String {
|
|
let tag = String(cString: get_swift_containerization_version())
|
|
return tag == "latest"
|
|
? "vminit:latest"
|
|
: "ghcr.io/apple/containerization/vminit:\(tag)"
|
|
}
|
|
|
|
public let image: String
|
|
|
|
public init(image: String = defaultImage) {
|
|
self.image = image
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.image = try container.decodeIfPresent(String.self, forKey: .image) ?? Self.defaultImage
|
|
}
|
|
}
|
|
|
|
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 defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case binaryPath
|
|
case url
|
|
case digest
|
|
}
|
|
|
|
public let binaryPath: String
|
|
public let url: URL
|
|
public let digest: String
|
|
|
|
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
|
|
self.binaryPath = binaryPath
|
|
self.url = url
|
|
self.digest = digest
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.binaryPath =
|
|
try container.decodeIfPresent(String.self, forKey: .binaryPath)
|
|
?? Self.defaultBinaryPath
|
|
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
|
|
guard let parsed = URL(string: urlString) else {
|
|
throw DecodingError.dataCorruptedError(
|
|
forKey: .url,
|
|
in: container,
|
|
debugDescription: "invalid kernel URL '\(urlString)'")
|
|
}
|
|
self.url = parsed
|
|
} else {
|
|
self.url = Self.defaultURL
|
|
}
|
|
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
|
|
self.digest = digest
|
|
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
|
|
self.digest = Self.defaultDigest
|
|
} else {
|
|
throw DecodingError.dataCorruptedError(
|
|
forKey: .digest,
|
|
in: container,
|
|
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
|
|
}
|
|
}
|
|
|
|
// JSONEncoder special-cases URL to encode as absoluteString, but third-party
|
|
// encoders (e.g. TOMLEncoder) hit Foundation's default Codable conformance which
|
|
// encodes into a keyed container with a "relative" key. Encode as a plain string
|
|
// so all formats produce a consistent URL representation.
|
|
// If more config types start using URL, consider a property wrapper or a wrapper
|
|
// type (like MemorySize) that encodes/decodes URL as a string uniformly.
|
|
public func encode(to encoder: any Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(binaryPath, forKey: .binaryPath)
|
|
try container.encode(url.absoluteString, forKey: .url)
|
|
try container.encode(digest, forKey: .digest)
|
|
}
|
|
}
|
|
|
|
final public class NetworkConfig: Codable, Sendable {
|
|
public let subnet: CIDRv4?
|
|
public let subnetv6: CIDRv6?
|
|
|
|
public init(subnet: CIDRv4? = nil, subnetv6: CIDRv6? = nil) {
|
|
self.subnet = subnet
|
|
self.subnetv6 = subnetv6
|
|
}
|
|
}
|
|
|
|
final public class RegistryConfig: Codable, Sendable {
|
|
public static let defaultDomain = "docker.io"
|
|
|
|
public let domain: String
|
|
|
|
public init(domain: String = defaultDomain) {
|
|
self.domain = domain
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.domain = try container.decodeIfPresent(String.self, forKey: .domain) ?? Self.defaultDomain
|
|
}
|
|
}
|