Skip to content

Commit 979fbd0

Browse files
gavinelderclaudepditommaso
authored
Bugfix: do not serve freeze/mirror images via the Wave proxy token path (#1087)
* fix: do not serve freeze/mirror images via the Wave proxy token path Freeze and mirror requests publish the image to the caller's target registry and return a direct registry reference; they are meant to be pulled directly from that registry. However the ephemeral /v2/wt/<token>/ proxy route still resolved and served them for the token TTL. Reject durable (freeze/mirror) requests on the proxy token path so the image can only be pulled directly from the target registry. Status/detail lookups by request id are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Make freeze/mirror rejection message actionable; tighten test Point the caller to the direct image reference in the NotFoundException message, and assert the message carries that reference in the test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Return direct image for durable requests on the V1 endpoint (#1090) makeResponseV2 already returns the direct target-registry image for freeze/mirror (durable) requests, but makeResponseV1 kept handing back the Wave proxy token url. Since durable images are no longer served via the proxy token path (see RouteHandler), V1 freeze clients pulling via targetImage would 404. Resolve targetImage to the direct image for durable requests on V1 too, matching V2. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
1 parent e8b5281 commit 979fbd0

4 files changed

Lines changed: 54 additions & 2 deletions

File tree

src/main/groovy/io/seqera/wave/core/RouteHandler.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ class RouteHandler {
6262
if( !request ) {
6363
throw new NotFoundException("Unknown Wave container for token '$token'")
6464
}
65+
// freeze and mirror requests publish the image to the target registry and are
66+
// meant to be pulled directly from there - they must not be served via the Wave
67+
// proxy token path (see also ContainerRequest.durable)
68+
if( request.durable() ) {
69+
throw new NotFoundException("Container '$token' is a freeze/mirror image and must be pulled directly from '${request.containerImage}'")
70+
}
6571
// the image name (without tag) must match
6672
final coords = request.coordinates()
6773
if( image != coords.image )

src/main/groovy/io/seqera/wave/util/ContainerHelper.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ class ContainerHelper {
116116
}
117117

118118
static SubmitContainerTokenResponse makeResponseV1(ContainerRequest data, TokenData token, String waveImage) {
119-
final target = waveImage
119+
// freeze/mirror images are published to the target registry and must be pulled
120+
// directly from there - the Wave proxy token path does not serve them (see RouteHandler)
121+
final target = data.durable() ? data.containerImage : waveImage
120122
final build = data.buildNew ? data.buildId : null
121123
return new SubmitContainerTokenResponse(data.requestId, token.value, target, token.expiration, data.containerImage, build, null, null, null, null, null)
122124
}

src/test/groovy/io/seqera/wave/core/RouteHandlerTest.groovy

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,24 @@ class RouteHandlerTest extends Specification {
133133

134134
}
135135

136+
@Unroll
137+
def 'should reject proxy pull for durable freeze/mirror requests' () {
138+
given:
139+
def tokenService = Mock(ContainerRequestService)
140+
141+
when:
142+
new RouteHandler(tokenService).parse(REQ_PATH)
143+
then:
144+
1 * tokenService.getRequest(TOKEN) >> REQUEST
145+
and:
146+
def e = thrown(NotFoundException)
147+
// the error must point the caller to the direct image reference
148+
e.message.contains(IMAGE)
149+
150+
where:
151+
REQ_PATH | TOKEN | IMAGE | REQUEST
152+
'/v2/wt/a1/library/ubuntu/manifests/latest' | 'a1' | 'quay.io/org/ubuntu:1.0' | ContainerRequest.of(containerImage: 'quay.io/org/ubuntu:1.0', freeze: true)
153+
'/v2/wt/b2/library/ubuntu/blobs/latest' | 'b2' | 'quay.io/org/ubuntu:1.0' | ContainerRequest.of(containerImage: 'quay.io/org/ubuntu:1.0', type: ContainerRequest.Type.Mirror)
154+
}
155+
136156
}

src/test/groovy/io/seqera/wave/util/ContainerHelperTest.groovy

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,36 @@ class ContainerHelperTest extends Specification {
253253
result.cached == null
254254
result.freeze == null
255255

256-
where:
256+
where:
257257
NEW_BUILD | EXPECTED_BUILD_ID
258258
false | null
259259
true | '123'
260260
}
261261

262+
@Unroll
263+
def 'should create response v1 for durable request returning the direct image' () {
264+
given:
265+
def data = ContainerRequest.of(
266+
containerImage: 'quay.io/org/container:1.0',
267+
freeze: IS_FREEZE,
268+
type: TYPE )
269+
def token = new TokenData('123abc', Instant.now().plusSeconds(100))
270+
def target = 'wave.com/wt/123abc/org/container:1.0'
271+
272+
when:
273+
def result = ContainerHelper.makeResponseV1(data, token, target)
274+
then:
275+
// freeze/mirror images must resolve to the direct target-registry image,
276+
// not the Wave proxy token url (which no longer serves durable images)
277+
result.targetImage == 'quay.io/org/container:1.0'
278+
result.containerImage == 'quay.io/org/container:1.0'
279+
280+
where:
281+
TYPE | IS_FREEZE
282+
null | true
283+
ContainerRequest.Type.Mirror | false
284+
}
285+
262286
@Unroll
263287
def 'should create response v2' () {
264288
given:

0 commit comments

Comments
 (0)