From 02bd8782120777ef0cdc2ad9053ecfe7c47bc370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Mo=C3=9Fburger?= Date: Tue, 23 Dec 2025 13:14:32 +0100 Subject: [PATCH] feat(registry): Add custom ca certificate override (#402) Closely related to https://github.com/apple/container/issues/305 I would like to override the used SSL TrustRoots via standard env variables. This here would add this configuration and would give an entrypoint for an implementation of 305 to provide CLI flags or similar. This has no tests yet, as this would require setting up something like a MITM proxy when testing against a registry. As I am unfamiliar with the codebase, I would be willing to do this, but would require a first nudge on where to best implement this. To actually use this, we would need to add the allowed env variables to the `container system start` command env filter. --- Package.resolved | 6 +-- Package.swift | 3 ++ .../Image/ImageStore/ImageStore.swift | 4 +- Sources/ContainerizationExtras/TLSUtils.swift | 39 +++++++++++++++++++ .../Client/RegistryClient.swift | 13 +++++-- Sources/cctl/LoginCommand.swift | 4 +- vminitd/Package.resolved | 6 +-- 7 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 Sources/ContainerizationExtras/TLSUtils.swift diff --git a/Package.resolved b/Package.resolved index 58c65149..abb229e3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "ac3ef791bf32cf99904bb681e6465fd729983add7f517f1c9342249121799bef", + "originHash" : "c82be4e21117351bb3f942869ce90d35dcd0dd0223dc1c49ce7a56b52709e836", "pins" : [ { "identity" : "async-http-client", @@ -168,8 +168,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "4b38f35946d00d8f6176fe58f96d83aba64b36c7", - "version" : "2.31.0" + "revision" : "173cc69a058623525a58ae6710e2f5727c663793", + "version" : "2.36.0" } }, { diff --git a/Package.swift b/Package.swift index 8cbe16ac..5418600a 100644 --- a/Package.swift +++ b/Package.swift @@ -46,6 +46,7 @@ let package = Package( .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"), .package(url: "https://github.com/apple/swift-system.git", from: "1.4.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.36.0"), ], targets: [ .target( @@ -231,6 +232,8 @@ let package = Package( "ContainerizationError", .product(name: "Collections", package: "swift-collections"), .product(name: "Logging", package: "swift-log"), + .product(name: "NIOSSL", package: "swift-nio-ssl"), + ] ), .testTarget( diff --git a/Sources/Containerization/Image/ImageStore/ImageStore.swift b/Sources/Containerization/Image/ImageStore/ImageStore.swift index 7db197ff..c4566ce4 100644 --- a/Sources/Containerization/Image/ImageStore/ImageStore.swift +++ b/Sources/Containerization/Image/ImageStore/ImageStore.swift @@ -237,7 +237,7 @@ extension ImageStore { ) async throws -> Image { let matcher = createPlatformMatcher(for: platform) - let client = try RegistryClient(reference: reference, insecure: insecure, auth: auth) + let client = try RegistryClient(reference: reference, insecure: insecure, auth: auth, tlsConfiguration: TLSUtils.makeEnvironmentAwareTLSConfiguration()) let ref = try Reference.parse(reference) let name = ref.path @@ -289,7 +289,7 @@ extension ImageStore { guard let tag = ref.tag ?? ref.digest else { throw ContainerizationError(.invalidArgument, message: "invalid tag/digest for image reference \(reference)") } - let client = try RegistryClient(reference: reference, insecure: insecure, auth: auth) + let client = try RegistryClient(reference: reference, insecure: insecure, auth: auth, tlsConfiguration: TLSUtils.makeEnvironmentAwareTLSConfiguration()) let operation = ExportOperation(name: name, tag: tag, contentStore: self.contentStore, client: client, progress: progress) try await operation.export(index: img.descriptor, platforms: matcher) } diff --git a/Sources/ContainerizationExtras/TLSUtils.swift b/Sources/ContainerizationExtras/TLSUtils.swift new file mode 100644 index 00000000..2d3afa07 --- /dev/null +++ b/Sources/ContainerizationExtras/TLSUtils.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization 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 Foundation +import NIO +import NIOSSL + +public enum TLSUtils { + + public static func makeEnvironmentAwareTLSConfiguration() -> TLSConfiguration { + var tlsConfig = TLSConfiguration.makeClientConfiguration() + + // Check standard SSL environment variables in priority order + let customCAPath = + ProcessInfo.processInfo.environment["SSL_CERT_FILE"] + ?? ProcessInfo.processInfo.environment["CURL_CA_BUNDLE"] + ?? ProcessInfo.processInfo.environment["REQUESTS_CA_BUNDLE"] + + if let caPath = customCAPath { + tlsConfig.trustRoots = .file(caPath) + } + // else: use .default + + return tlsConfig + } +} diff --git a/Sources/ContainerizationOCI/Client/RegistryClient.swift b/Sources/ContainerizationOCI/Client/RegistryClient.swift index db4c497f..5930bd4d 100644 --- a/Sources/ContainerizationOCI/Client/RegistryClient.swift +++ b/Sources/ContainerizationOCI/Client/RegistryClient.swift @@ -22,6 +22,7 @@ import Foundation import Logging import NIO import NIOHTTP1 +import NIOSSL #if os(macOS) import Network @@ -66,7 +67,8 @@ public final class RegistryClient: ContentClient { reference: String, insecure: Bool = false, auth: Authentication? = nil, - logger: Logger? = nil + tlsConfiguration: TLSConfiguration? = nil, + logger: Logger? = nil, ) throws { let ref = try Reference.parse(reference) guard let domain = ref.resolvedDomain else { @@ -86,7 +88,8 @@ public final class RegistryClient: ContentClient { scheme: scheme, port: port, authentication: auth, - retryOptions: Self.defaultRetryOptions + retryOptions: Self.defaultRetryOptions, + tlsConfiguration: tlsConfiguration, ) } @@ -98,7 +101,8 @@ public final class RegistryClient: ContentClient { clientID: String? = nil, retryOptions: RetryOptions? = nil, bufferSize: Int = Int(4.mib()), - logger: Logger? = nil + tlsConfiguration: TLSConfiguration? = nil, + logger: Logger? = nil, ) { var components = URLComponents() components.scheme = scheme @@ -118,6 +122,9 @@ public final class RegistryClient: ContentClient { let proxyPort = proxyURL.port ?? (proxyURL.scheme == "https" ? 443 : 80) httpConfiguration.proxy = HTTPClient.Configuration.Proxy.server(host: proxyHost, port: proxyPort) } + if tlsConfiguration != nil { + httpConfiguration.tlsConfiguration = tlsConfiguration + } if let logger { self.client = HTTPClient(eventLoopGroupProvider: .singleton, configuration: httpConfiguration, backgroundActivityLogger: logger) diff --git a/Sources/cctl/LoginCommand.swift b/Sources/cctl/LoginCommand.swift index 2f37631d..9b67fc1c 100644 --- a/Sources/cctl/LoginCommand.swift +++ b/Sources/cctl/LoginCommand.swift @@ -17,6 +17,7 @@ import ArgumentParser import Containerization import ContainerizationError +import ContainerizationExtras import ContainerizationOCI import Foundation @@ -74,7 +75,8 @@ extension Application { shouldRetry: ({ response in response.status.code >= 500 }) - ) + ), + tlsConfiguration: TLSUtils.makeEnvironmentAwareTLSConfiguration(), ) try await client.ping() try keychain.save(domain: server, username: username, password: password) diff --git a/vminitd/Package.resolved b/vminitd/Package.resolved index 527e80af..a5c8dcdd 100644 --- a/vminitd/Package.resolved +++ b/vminitd/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "0855d97d95dd20ece9675e6b2aa0f7560c0843a73eadc8b42b9a9353047cb802", + "originHash" : "317bd17e5e8ea1d37ffc5ec5f87172d8d348613454782e2fe0ac36ae707e87f9", "pins" : [ { "identity" : "async-http-client", @@ -150,8 +150,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "4b38f35946d00d8f6176fe58f96d83aba64b36c7", - "version" : "2.31.0" + "revision" : "173cc69a058623525a58ae6710e2f5727c663793", + "version" : "2.36.0" } }, {