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.
This commit is contained in:
Sebastian Moßburger
2025-12-23 07:14:32 -05:00
committed by GitHub
parent dcbc7bf71d
commit 02bd878212
7 changed files with 63 additions and 12 deletions
+3 -3
View File
@@ -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"
}
},
{
+3
View File
@@ -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(
@@ -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)
}
@@ -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
}
}
@@ -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)
+3 -1
View File
@@ -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)
+3 -3
View File
@@ -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"
}
},
{