Skip to content

Commit

Permalink
Merge branch 'master' into bump-aws-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
munishchouhan authored Oct 9, 2024
2 parents 8b0dba5 + 2b20eee commit d676d24
Show file tree
Hide file tree
Showing 24 changed files with 198 additions and 130 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dependencies {
compileOnly("io.micronaut:micronaut-http-validation")
implementation("jakarta.persistence:jakarta.persistence-api:3.0.0")
api 'io.seqera:lib-mail:1.0.0'
api 'io.seqera:wave-api:0.13.1'
api 'io.seqera:wave-api:0.13.2'
api 'io.seqera:wave-utils:0.14.1'
implementation("io.micronaut:micronaut-http-client")
implementation("io.micronaut:micronaut-jackson-databind")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package io.seqera.wave.service.mirror
package io.seqera.wave.configuration

import java.time.Duration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class ScanConfig {
@Value('${wave.scan.retry-attempts:1}')
int retryAttempts

@Value('${wave.scan.status.duration:1h}')
@Value('${wave.scan.status.duration:5d}')
Duration statusDuration

@Value('${wave.scan.id.duration:7d}')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import io.seqera.wave.service.request.ContainerStatusService
import io.seqera.wave.service.request.TokenData
import io.seqera.wave.service.scan.ContainerScanService
import io.seqera.wave.service.validation.ValidationService
import io.seqera.wave.service.validation.ValidationServiceImpl
import io.seqera.wave.tower.PlatformId
import io.seqera.wave.tower.User
import io.seqera.wave.tower.auth.JwtAuth
Expand Down Expand Up @@ -240,14 +241,14 @@ class ContainerController {
throw new BadRequestException("Attribute `nameStrategy` is not allowed by legacy container endpoint")

// prevent the use of container file and freeze without a custom build repository
if( req.containerFile && req.freeze && !isCustomRepo0(req.buildRepository) && (!v2 || (v2 && !req.packages)))
if( req.containerFile && req.freeze && !validationService.isCustomRepo(req.buildRepository) && (!v2 || (v2 && !req.packages)))
throw new BadRequestException("Attribute `buildRepository` must be specified when using freeze mode [1]")

// prevent the use of container image and freeze without a custom build repository
if( req.containerImage && req.freeze && !isCustomRepo0(req.buildRepository) )
if( req.containerImage && req.freeze && !validationService.isCustomRepo(req.buildRepository) )
throw new BadRequestException("Attribute `buildRepository` must be specified when using freeze mode [2]")

if( v2 && req.packages && req.freeze && !isCustomRepo0(req.buildRepository) && !buildConfig.defaultPublicRepository )
if( v2 && req.packages && req.freeze && !validationService.isCustomRepo(req.buildRepository) && !buildConfig.defaultPublicRepository )
throw new BadRequestException("Attribute `buildRepository` must be specified when using freeze mode [3]")

if( v2 && req.packages ) {
Expand Down Expand Up @@ -280,18 +281,6 @@ class ContainerController {
return HttpResponse.ok(resp)
}

protected boolean isCustomRepo0(String repo) {
if( !repo )
return false
if( buildConfig.defaultPublicRepository && repo.startsWith(buildConfig.defaultPublicRepository) )
return false
if( buildConfig.defaultBuildRepository && repo.startsWith(buildConfig.defaultBuildRepository) )
return false
if( buildConfig.defaultCacheRepository && repo.startsWith(buildConfig.defaultCacheRepository) )
return false
return true
}

protected void storeContainerRequest0(SubmitContainerTokenRequest req, ContainerRequest data, TokenData token, String target, String ip) {
try {
final recrd = new WaveContainerRecord(req, data, target, ip, token.expiration)
Expand Down Expand Up @@ -455,7 +444,7 @@ class ContainerController {
scanId = build.scanId
mirrorFlag = false
}
else if( req.mirrorRegistry ) {
else if( req.mirror ) {
final mirror = makeMirrorRequest(req, identity, digest)
final track = checkMirror(mirror, identity, req.dryRun)
targetImage = track.targetImage
Expand Down Expand Up @@ -502,9 +491,16 @@ class ContainerController {

protected MirrorRequest makeMirrorRequest(SubmitContainerTokenRequest request, PlatformId identity, String digest) {
final coords = ContainerCoordinates.parse(request.containerImage)
if( coords.registry == request.mirrorRegistry )
throw new BadRequestException("Source and target mirror registry as the same - offending value '${request.mirrorRegistry}'")
final targetImage = request.mirrorRegistry + '/' + coords.imageAndTag
final target = ContainerCoordinates.parse(request.buildRepository)
if( !coords.imageAndTag )
throw new BadRequestException("Missing mirror source image - offending value '${request.containerImage}'")
if( !target.registry )
throw new BadRequestException("Missing mirror target registry - offending value '${request.buildRepository}'")
if( coords.registry == target.registry )
throw new BadRequestException("Source and target mirror registries are the same - offending value '${request.buildRepository}'")
final targetImage = target.repository
? target.repository + '/' + coords.imageAndTag
: target.registry + '/' + coords.imageAndTag
final configJson = inspectService.credentialsConfigJson(null, request.containerImage, targetImage, identity)
final platform = request.containerPlatform
? ContainerPlatform.of(request.containerPlatform)
Expand Down Expand Up @@ -582,41 +578,45 @@ class ContainerController {
// check valid image name
msg = validationService.checkContainerName(req.containerImage)
if( msg ) throw new BadRequestException(msg)
// stop here for mirror request
if( req.mirror )
return
// check build repo
msg = validationService.checkBuildRepository(req.buildRepository, false)
if( !req.mirror )
msg = validationService.checkBuildRepository(req.buildRepository, ValidationServiceImpl.RepoType.Build)
if( msg ) throw new BadRequestException(msg)
// check cache repository
msg = validationService.checkBuildRepository(req.cacheRepository, true)
msg = validationService.checkBuildRepository(req.cacheRepository, ValidationServiceImpl.RepoType.Cache)
if( msg ) throw new BadRequestException(msg)
}

void validateMirrorRequest(SubmitContainerTokenRequest req, boolean v2) throws BadRequestException {
if( !req.mirrorRegistry )
if( !req.mirror )
return
// container mirror validation
if( !v2 )
throw new BadRequestException("Container mirroring requires the use of v2 API")
if( !req.containerImage )
throw new BadRequestException("Attribute `containerImage` is required when specifying `mirrorRegistry`")
throw new BadRequestException("Attribute `containerImage` is required when specifying `mirror` mode")
if( !req.towerAccessToken )
throw new BadRequestException("Container mirroring requires an authenticated request - specify the tower token attribute")
if( req.freeze )
throw new BadRequestException("Attribute `mirrorRegistry` and `freeze` conflict each other")
throw new BadRequestException("Attribute `mirror` and `freeze` conflict each other")
if( req.containerFile )
throw new BadRequestException("Attribute `mirrorRegistry` and `containerFile` conflict each other")
throw new BadRequestException("Attribute `mirror` and `containerFile` conflict each other")
if( req.containerIncludes )
throw new BadRequestException("Attribute `mirrorRegistry` and `containerIncludes` conflict each other")
throw new BadRequestException("Attribute `mirror` and `containerIncludes` conflict each other")
if( req.containerConfig )
throw new BadRequestException("Attribute `mirrorRegistry` and `containerConfig` conflict each other")
throw new BadRequestException("Attribute `mirror` and `containerConfig` conflict each other")
if( !req.buildRepository )
throw new BadRequestException("Attribute `buildRepository` is required when specifying `mirror` mode")
final coords = ContainerCoordinates.parse(req.containerImage)
if( coords.registry == req.mirrorRegistry )
throw new BadRequestException("Source and target mirror registry as the same - offending value '${req.mirrorRegistry}'")
def msg = validationService.checkMirrorRegistry(req.mirrorRegistry)
final target = ContainerCoordinates.parse(req.buildRepository)
if( coords.registry == target.registry )
throw new BadRequestException("Source and target mirror registry are the same - offending value '${req.buildRepository}'")
def msg = validationService.checkBuildRepository(req.buildRepository, ValidationServiceImpl.RepoType.Mirror)
if( msg )
throw new BadRequestException(msg)
if( !isCustomRepo0(req.mirrorRegistry) ) {
throw new BadRequestException("Not allowed mirror registry - offending value '${req.mirrorRegistry}'")
}
}

@Error(exception = AuthorizationException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import groovy.transform.CompileStatic
import io.seqera.wave.configuration.BlobCacheConfig
import io.seqera.wave.configuration.ScanConfig
import io.seqera.wave.service.builder.BuildRequest
import io.seqera.wave.service.mirror.MirrorConfig
import io.seqera.wave.configuration.MirrorConfig
import io.seqera.wave.service.mirror.MirrorRequest
import io.seqera.wave.service.scan.ScanRequest
import jakarta.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import io.kubernetes.client.openapi.models.V1Pod
import io.kubernetes.client.openapi.models.V1PodList
import io.seqera.wave.configuration.BlobCacheConfig
import io.seqera.wave.configuration.ScanConfig
import io.seqera.wave.service.mirror.MirrorConfig
import io.seqera.wave.configuration.MirrorConfig

/**
* Defines Kubernetes operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import io.seqera.wave.configuration.BlobCacheConfig
import io.seqera.wave.configuration.BuildConfig
import io.seqera.wave.configuration.ScanConfig
import io.seqera.wave.core.ContainerPlatform
import io.seqera.wave.service.mirror.MirrorConfig
import io.seqera.wave.configuration.MirrorConfig
import io.seqera.wave.service.scan.Trivy
import jakarta.inject.Inject
import jakarta.inject.Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

package io.seqera.wave.service.mirror

import io.seqera.wave.configuration.MirrorConfig

import java.time.Duration

import groovy.transform.CompileStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Value
import io.seqera.wave.service.mirror.MirrorConfig
import io.seqera.wave.configuration.MirrorConfig
import io.seqera.wave.service.mirror.MirrorRequest
import jakarta.inject.Inject
import jakarta.inject.Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import io.micronaut.context.annotation.Primary
import io.micronaut.context.annotation.Requires
import io.seqera.wave.exception.BadRequestException
import io.seqera.wave.service.k8s.K8sService
import io.seqera.wave.service.mirror.MirrorConfig
import io.seqera.wave.configuration.MirrorConfig
import io.seqera.wave.service.mirror.MirrorRequest
import jakarta.inject.Inject
import jakarta.inject.Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ interface PersistenceService {
*/
WaveScanRecord loadScanRecord(String scanId)

/**
* Check if a scan record exist
*
* @param scanId The Id of the scan to check
* @return {@code true} if the scan record with the specified id exists or {@code false} otherwise
*/
boolean existsScanRecord(String scanId)

/**
* Load a mirror state record
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class LocalPersistenceService implements PersistenceService {
requestStore.get(token)
}

@Override
boolean existsScanRecord(String scanId) {
scanStore.containsKey(scanId)
}

@Override
void saveScanRecord(WaveScanRecord scanRecord) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ class SurrealPersistenceService implements PersistenceService {
json.replaceFirst(/"vulnerabilities":\s*\[]/, value)
}

@Override
boolean existsScanRecord(String scanId) {
final statement = "SELECT count() FROM wave_scan where id == 'wave_scan:⟨$scanId⟩'"
final json = surrealDb.sqlAsString(getAuthorization(), statement)
final type = new TypeReference<ArrayList<SurrealResult<Map>>>() {}
final data= json ? JacksonHelper.fromJson(json, type) : null
final result = data && data[0].result ? data[0].result[0].count==1 : false
return result
}

@Override
WaveScanRecord loadScanRecord(String scanId) {
if( !scanId )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<ScanE
log.debug "Container scan required by scanOnRequest=$request"
scan(fromContainer(request))
}
else if( request.scanId && request.buildId && request.buildNew==false && !existsScan(request.scanId) ) {
log.debug "Container scan required by cached request=$request"
scan(fromContainer(request))
}
else {
log.trace "Container scan NOT required by scanOnRequest=$request"
}
Expand All @@ -149,6 +153,10 @@ class ContainerScanServiceImpl implements ContainerScanService, JobHandler<ScanE
return scanStore.get(scanId)
}

boolean existsScan(String scanId) {
return scanStore.get(scanId) ?: persistenceService.existsScanRecord(scanId)
}

@Override
WaveScanRecord getScanRecord(String scanId) {
try{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ interface ValidationService {

String checkContainerName(String name)

String checkBuildRepository(String repo, boolean cache)
String checkBuildRepository(String repo, ValidationServiceImpl.RepoType type)

String checkMirrorRegistry(String registry)
boolean isCustomRepo(String repo)

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
package io.seqera.wave.service.validation

import groovy.transform.CompileStatic
import io.seqera.wave.configuration.BuildConfig
import io.seqera.wave.model.ContainerCoordinates
import io.seqera.wave.util.StringUtils
import jakarta.inject.Inject
import jakarta.inject.Singleton
/**
* Validation service
Expand All @@ -31,8 +33,13 @@ import jakarta.inject.Singleton
@CompileStatic
class ValidationServiceImpl implements ValidationService {

enum RepoType { Build, Cache, Mirror }

static private final List<String> VALID_PROTOCOLS = ['http','https']

@Inject
private BuildConfig buildConfig

@Override
String checkEndpoint(String endpoint) {
final scheme = StringUtils.getUrlProtocol(endpoint)
Expand Down Expand Up @@ -74,38 +81,39 @@ class ValidationServiceImpl implements ValidationService {
}

@Override
String checkBuildRepository(String repo, boolean cache) {
if( !repo )
String checkBuildRepository(String repo, RepoType type ) {
if( !repo && type!=RepoType.Mirror )
return null
final type = cache ? "build cache" : "build"
// repo is required when using mirror more
if( !repo && type==RepoType.Mirror )
return "Missing target build repository required by 'mirror' mode"
final typeStr = type==RepoType.Cache ? "build cache" : "build"
// check does not start with a protocol prefix
final prot = StringUtils.getUrlProtocol(repo)
if( prot )
return "Container ${type} repository should not include any protocol prefix - offending value: $repo"
return "Container ${typeStr} repository should not include any protocol prefix - offending value: '$repo'"
// check no tag is included
final coords = ContainerCoordinates.parse(repo)
if( !coords.repository )
return "Container ${type} repository is invalid or incomplete - offending value: $repo"
if( !coords.repository && type!=RepoType.Mirror )
return "Container ${typeStr} repository is invalid or incomplete - offending value: '$repo'"
if( coords.reference && repo.endsWith(":${coords.reference}") )
return "Container ${type} repository should not include any tag suffix - offending value: $repo"
return "Container ${typeStr} repository should not include any tag suffix - offending value: '$repo'"
if( type==RepoType.Mirror && !isCustomRepo(coords.registry) )
return "Mirror registry not allowed - offending value '${repo}'"
else
return null
}

@Override
String checkMirrorRegistry(String registry) {
if( !registry )
return null
final prot = StringUtils.getUrlProtocol(registry)
if( prot )
return "Mirror registry should not include any protocol prefix - offending value: $registry"
// check no tag is included
final coords = ContainerCoordinates.parse(registry)
if( coords.repository )
return "Mirror registry syntax is invalid - offending value: ${registry}"
if( coords.registry == 'wave.seqera.io' || coords.registry?.contains('.wave.seqera.io') )
return "Mirror registry not allowed - offending value: ${registry}"
return null
boolean isCustomRepo(String repo) {
if( !repo )
return false
if( buildConfig.defaultPublicRepository && repo.startsWith(buildConfig.defaultPublicRepository) )
return false
if( buildConfig.defaultBuildRepository && repo.startsWith(buildConfig.defaultBuildRepository) )
return false
if( buildConfig.defaultCacheRepository && repo.startsWith(buildConfig.defaultCacheRepository) )
return false
return true
}

}
1 change: 1 addition & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ wave:
singularity-image-arm64: "quay.io/singularity/singularity:v3.11.4-slim-arm64"
repo: "195996028523.dkr.ecr.eu-west-1.amazonaws.com/wave/build/dev"
cache: "195996028523.dkr.ecr.eu-west-1.amazonaws.com/wave/build/cache"
public-repo: "community.wave.seqera.io"
# note changing this should be matching the micronaut.server.idle-timeout (see above)
timeout: 900s
status:
Expand Down
Loading

0 comments on commit d676d24

Please sign in to comment.