From 79027c587e533d8b7046152b1bee486abf6b510c Mon Sep 17 00:00:00 2001 From: Sven Herzberg Date: Wed, 29 Oct 2025 19:57:51 +0100 Subject: [PATCH 1/2] ci: Run End-to-End Tests for Debug and Release Configurations Motivation ---------- It looks like building images with containertool on Linux fails downloading the base image `swift:slim` from dockerhub. Modifications ------------- Update end to end and integration workflows to test both `scratch` and `swift:slim` base images. Result ------ End-to-End Tests will now fail in release configuration. Test Plan --------- Other tests are not affected by this. --- .github/workflows/endtoend_tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/endtoend_tests.yml b/.github/workflows/endtoend_tests.yml index 90a50d3..07a0195 100644 --- a/.github/workflows/endtoend_tests.yml +++ b/.github/workflows/endtoend_tests.yml @@ -21,6 +21,9 @@ jobs: - 5000:5000 strategy: matrix: + from: + - scratch + - swift:slim example: - Examples/HelloWorldVapor - Examples/HelloWorldHummingbird @@ -52,7 +55,7 @@ jobs: --allow-network-connections all \ build-container-image \ --repository localhost:5000/example \ - --from scratch + --from ${{ matrix.from }} - name: Run the example run: | From c06f14b12895c4bd93bd69c017bece11c6792ef2 Mon Sep 17 00:00:00 2001 From: Euan Harris Date: Tue, 4 Nov 2025 15:42:29 +0000 Subject: [PATCH 2/2] ContainerRegistry: Remove Authorization header when redirecting on Linux Docker Hub now redirects pulls for swift:slim to an S3 bucked behind CloudFlare. URLSession on macOS follows the redirect correctly, but URLSession on Linux incorrectly includes an Authorization header which S3 rejects. This commit adds a URLSession delegate which removes the Authorization header when following a redirect. --- .../ContainerRegistry/RegistryClient.swift | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/Sources/ContainerRegistry/RegistryClient.swift b/Sources/ContainerRegistry/RegistryClient.swift index 74d56c9..41fcfda 100644 --- a/Sources/ContainerRegistry/RegistryClient.swift +++ b/Sources/ContainerRegistry/RegistryClient.swift @@ -104,11 +104,92 @@ public struct RegistryClient { // URLSessionConfiguration.default allows request and credential caching, making testing confusing. // The SwiftPM sandbox also prevents URLSession from writing to the cache, which causes warnings. // .ephemeral has no caches. - let urlsession = URLSession(configuration: .ephemeral) + // A delegate is needed to remove the Authorization header when following HTTP redirects on Linux. + let urlsession = URLSession( + configuration: .ephemeral, + delegate: RegistryURLSessionDelegate(), + delegateQueue: nil + ) try await self.init(registry: registryURL, client: urlsession, auth: auth) } } +final class RegistryURLSessionDelegate: NSObject {} + +extension RegistryURLSessionDelegate: URLSessionDelegate, URLSessionTaskDelegate { + /// Called if the RegistryClient receives an HTTP redirect from the registry. + /// - Parameters: + /// - session: The session containing the task whose request resulted in a redirect. + /// - task: The task whose request resulted in a redirect. + /// - response: An object containing the server’s response to the original request. + /// - request: A URL request object filled out with the new location. + /// - completionHandler: A block that your handler should call with either the value + /// of the request parameter, a modified URL request object, or NULL to refuse the + /// redirect and return the body of the redirect response. + func urlSession( + _ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Swift.Void + ) { + // The Authorization header should be removed when following a redirect: + // + // https://fetch.spec.whatwg.org/#http-redirect-fetch + // + // URLSession on macOS does this, but on Linux the header is left in place. + // This causes problems when pulling images from Docker Hub on Linux. + // + // Docker Hub redirects to AWS S3 via CloudFlare. Including the Authorization header + // in the redirected request causes a 400 error to be returned with the XML message: + // + // InvalidRequest: Missing x-amz-content-sha256 + // + // Removing the Authorization header makes the redirected request work. + // + // The spec also requires that if the redirected request is a POST, the method + // should be changed to GET and the body should be deleted: + // + // https://datatracker.ietf.org/doc/html/rfc7231#section-6.4 + // + // URLSession makes these changes before calling this delegate method: + // + // https://github.com/swiftlang/swift-corelibs-foundation/blob/265274a4be41b3d4d74fe4626d970898e4df330f/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift#L567C1-L572C1 + // + // In the delegate: + // - response.url is origin of the redirect response + // - request.url is value of the redirect response's Location header + // + // URLSession also limits redirect loops: + // + // https://github.com/swiftlang/swift-corelibs-foundation/blob/265274a4be41b3d4d74fe4626d970898e4df330f/Sources/FoundationNetworking/URLSession/HTTP/HTTPURLProtocol.swift#L459C1-L460C38 + + var request = request + + guard let origin = response.url, let redirect = request.url else { + // Reject the redirect if either URL is missing + completionHandler(nil) + return + } + + // https://fetch.spec.whatwg.org/#http-redirect-fetch + if !origin.hasSameOrigin(as: redirect) { + // Header names are case-insensitive + request.allHTTPHeaderFields = request.allHTTPHeaderFields? + .filter({ $0.key.lowercased() != "authorization" }) + } + + completionHandler(request) + } +} + +extension URL { + // https://html.spec.whatwg.org/multipage/browsers.html#same-origin + func hasSameOrigin(as other: URL) -> Bool { + self.scheme == other.scheme && self.host == other.host && self.port == other.port + } +} + extension URL { /// The base distribution endpoint URL var distributionEndpoint: URL { self.appendingPathComponent("/v2/") }