diff --git a/docs/configuration.md b/docs/configuration.md index 75eef17b5..bfb21afbc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -37,6 +37,10 @@ Configure general Wave application settings, such as application name, port, ano `wave.denyPaths` *(optional)* : API path patterns to filter out. Requests for matching artifacts, such as non-existent manifests, are rejected. +`wave.inject-credentials` *(optional)* +: When `true`, Wave injects the resolved workspace registry credentials when proxying image pulls (default: `true`). + When `false`, Wave does not broker registry credentials on the proxy pull path, so images are pulled directly from the target registry using the caller's own credentials (for example an EC2 instance profile or IAM role). Build, inspect, and augmentation flows are unaffected. + `wave.server.url` *(required)* : URL of the Wave server. Can be set using the `${WAVE_SERVER_URL}` environment variable. diff --git a/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy b/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy index 25a6a933c..c17fd057e 100644 --- a/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy +++ b/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy @@ -28,6 +28,7 @@ import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import io.micronaut.cache.annotation.Cacheable import io.micronaut.context.annotation.Context +import io.micronaut.context.annotation.Value import io.micronaut.core.io.buffer.ByteBuffer import io.micronaut.http.client.annotation.Client import io.micronaut.reactor.http.client.ReactorStreamingHttpClient @@ -82,6 +83,14 @@ class RegistryProxyService { @Inject private RegistryCredentialsProvider credentialsProvider + /** + * When 'false' Wave does not inject/broker registry credentials on the proxy pull path, + * so images are expected to be pulled directly from the target registry using the + * caller's own credentials. Build, inspect and augmentation flows are unaffected. + */ + @Value('${wave.inject-credentials:true}') + private boolean injectCredentials + /** * Service to query credentials stored into tower */ @@ -130,6 +139,10 @@ class RegistryProxyService { } protected RegistryCredentials getCredentials(RoutePath route) { + if( !injectCredentials ) { + log.debug "Credentials injection disabled - skipping credentials for route path=${route.targetContainer}" + return null + } final result = credentialsProvider.getCredentials(route, route.identity) log.debug "Credentials for route path=${route.targetContainer}; identity=${route.identity} => ${result}" return result diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 93618c0c3..c24458325 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -56,6 +56,9 @@ micronaut: --- wave: allowAnonymous: true + # when 'false' Wave does not inject/broker registry credentials on the proxy pull path; + # images are pulled directly from the target registry with the caller's own credentials (default: true) + inject-credentials: true server: url: "${WAVE_SERVER_URL:`http://localhost:9090`}" security: diff --git a/src/test/groovy/io/seqera/wave/core/RegistryProxyServiceCredentialsTest.groovy b/src/test/groovy/io/seqera/wave/core/RegistryProxyServiceCredentialsTest.groovy new file mode 100644 index 000000000..d572be00a --- /dev/null +++ b/src/test/groovy/io/seqera/wave/core/RegistryProxyServiceCredentialsTest.groovy @@ -0,0 +1,70 @@ +/* + * Wave, containers provisioning service + * Copyright (c) 2023-2026, Seqera Labs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.seqera.wave.core + +import spock.lang.Specification + +import io.seqera.wave.auth.RegistryCredentials +import io.seqera.wave.auth.RegistryCredentialsProvider + +/** + * Verify the 'wave.inject-credentials' flag gates proxy-pull credential injection. + * + * @author Gavin Elder + */ +class RegistryProxyServiceCredentialsTest extends Specification { + + def 'should not inject credentials when disabled'() { + given: + def provider = Mock(RegistryCredentialsProvider) + def service = new RegistryProxyService() + service.@credentialsProvider = provider + service.@injectCredentials = false + and: + def route = Mock(RoutePath) + + when: + def result = service.getCredentials(route) + + then: + 0 * provider.getCredentials(_, _) + and: + result == null + } + + def 'should inject credentials when enabled'() { + given: + def provider = Mock(RegistryCredentialsProvider) + def creds = Mock(RegistryCredentials) + def service = new RegistryProxyService() + service.@credentialsProvider = provider + service.@injectCredentials = true + and: + def route = Mock(RoutePath) + + when: + def result = service.getCredentials(route) + + then: + 1 * provider.getCredentials(route, _) >> creds + and: + result == creds + } + +}