Skip to content

Commit

Permalink
Merge pull request #3127 from mercedes-benz/feature-3028-prepare-modu…
Browse files Browse the repository at this point in the history
…le-skopeo

Added prepare module Skopeo for preparing docker images
  • Loading branch information
lorriborri committed May 24, 2024
2 parents 60d149b + 4a7dea5 commit f2ffc93
Show file tree
Hide file tree
Showing 34 changed files with 1,699 additions and 638 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@ public class PrepareWrapperKeyConstants {
*/
public static final String KEY_PDS_PREPARE_MODULE_GIT_ENABLED = "pds.prepare.module.git.enabled";

/**
* Flag to enable the skopeo prepare module
*/
public static final String KEY_PDS_PREPARE_MODULE_SKOPEO_ENABLED = "pds.prepare.module.skopeo.enabled";

/**
* Flag to clean the git folder from git files and clone without history
*/
public static final String KEY_PDS_PREPARE_AUTO_CLEANUP_GIT_FOLDER = "pds.prepare.auto.cleanup.git.folder";

/**
* Filename for skopeo authentication file
*/
public static final String KEY_PDS_PREPARE_AUTHENTICATION_FILE_SKOPEO = "pds.prepare.authentication.file.skopeo";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.mercedesbenz.sechub.wrapper.prepare.modules;

import static com.mercedesbenz.sechub.wrapper.prepare.modules.InputValidatorExitcode.*;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mercedesbenz.sechub.commons.model.*;
import com.mercedesbenz.sechub.wrapper.prepare.prepare.PrepareWrapperContext;

public class AbstractInputValidator implements InputValidator {

private static final Logger LOG = LoggerFactory.getLogger(AbstractInputValidator.class);
private final String TYPE;
private final Pattern LOCATION_PATTERN;
private final Pattern USERNAME_PATTERN;
private final Pattern PASSWORD_PATTERN;
private final List<String> forbiddenCharacters = Collections
.unmodifiableList(Arrays.asList(">", "<", "!", "?", "*", "'", "\"", ";", "&", "|", "`", "$", "{", "}"));

public AbstractInputValidator(String type, Pattern locationPattern, Pattern usernamePattern, Pattern passwordPattern) {
assertPatternNotNull(locationPattern);
assertPatternNotNull(usernamePattern);
assertPatternNotNull(passwordPattern);
if (isTypeNullOrEmpty(type)) {
throw new IllegalArgumentException("Type must not be null or empty.");
}

this.TYPE = type;
this.LOCATION_PATTERN = locationPattern;
this.USERNAME_PATTERN = usernamePattern;
this.PASSWORD_PATTERN = passwordPattern;
}

public void validate(PrepareWrapperContext context) throws PrepareWrapperInputValidatorException {
validateModule(context);
validateCredentials(context);
}

private void validateModule(PrepareWrapperContext context) throws PrepareWrapperInputValidatorException {
SecHubRemoteDataConfiguration secHubRemoteDataConfiguration = context.getRemoteDataConfiguration();
String location = secHubRemoteDataConfiguration.getLocation();
String type = secHubRemoteDataConfiguration.getType();

if (isTypeNullOrEmpty(type)) {
LOG.debug("No type defined. Location is: {}", location);
validateLocation(location);
return;
} else if (isMatchingType(type)) {
LOG.debug("Type is matching type {}. Location is: {}", TYPE, location);
validateLocation(location);
return;
}
throw new PrepareWrapperInputValidatorException("Defined type " + type + " was not modules type " + TYPE + ".", TYPE_NOT_MATCHING_PATTERN);
}

private void validateCredentials(PrepareWrapperContext context) throws PrepareWrapperInputValidatorException {
SecHubRemoteDataConfiguration secHubRemoteDataConfiguration = context.getRemoteDataConfiguration();

if (secHubRemoteDataConfiguration.getCredentials().isPresent()) {
SecHubRemoteCredentialConfiguration remoteCredentialConfiguration = secHubRemoteDataConfiguration.getCredentials().get();
if (remoteCredentialConfiguration.getUser().isPresent()) {
SecHubRemoteCredentialUserData user = remoteCredentialConfiguration.getUser().get();
validateUsername(user.getName());
validatePassword(user.getPassword());
return;
}
// credentials object was empty
throw new IllegalStateException("Defined credentials must contain credential user and can not be empty.");
}
}

public void validateUsername(String username) throws PrepareWrapperInputValidatorException {
if (username == null || username.isBlank()) {
throw new IllegalStateException("Defined username must not be null or empty. Username is required for login.");
}

if (!USERNAME_PATTERN.matcher(username).matches()) {
throw new PrepareWrapperInputValidatorException("Defined username must match the " + TYPE + " pattern.", CREDENTIALS_USERNAME_NOT_MATCHING_PATTERN);
}
}

public void validatePassword(String password) throws PrepareWrapperInputValidatorException {
if (password == null || password.isBlank()) {
throw new IllegalStateException("Defined password must not be null or empty. Password is required for login.");
}

if (!PASSWORD_PATTERN.matcher(password).matches()) {
throw new PrepareWrapperInputValidatorException("Defined password must match the " + TYPE + " Api token pattern.",
CREDENTIALS_PASSWORD_NOT_MATCHING_PATTERN);
}
}

public void validateLocation(String location) throws PrepareWrapperInputValidatorException {
if (location == null || location.isBlank()) {
throw new IllegalStateException("Defined location must not be null or empty. Location is required for download remote data.");
}
validateLocationCharacters(location);
if (!LOCATION_PATTERN.matcher(location).matches()) {
throw new PrepareWrapperInputValidatorException("Defined location must match the " + TYPE + " pattern.", LOCATION_NOT_MATCHING_PATTERN);
}
}

private boolean isTypeNullOrEmpty(String type) {
return type == null || type.isEmpty();
}

private boolean isMatchingType(String type) {
return TYPE.equalsIgnoreCase(type);
}

private void validateLocationCharacters(String url) {
if (url.contains(" ")) {
throw new IllegalArgumentException("Defined location URL must not contain whitespaces.");
}
for (String forbiddenCharacter : forbiddenCharacters) {
if (url.contains(forbiddenCharacter)) {
throw new IllegalArgumentException("Defined location URL must not contain forbidden characters: " + forbiddenCharacter);
}
}
}

private void assertPatternNotNull(Pattern pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern must not be null.");
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package com.mercedesbenz.sechub.wrapper.prepare.modules;

import java.util.List;
import com.mercedesbenz.sechub.wrapper.prepare.prepare.PrepareWrapperContext;

public interface InputValidator {

boolean validateLocation(String location);

void validateUsername(String username);

void validatePassword(String password);

void validateLocationCharacters(String url, List<String> forbiddenCharacters);

void validate(PrepareWrapperContext context) throws PrepareWrapperInputValidatorException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mercedesbenz.sechub.wrapper.prepare.modules;

public enum InputValidatorExitcode {

LOCATION_NOT_MATCHING_PATTERN(1),

CREDENTIALS_USERNAME_NOT_MATCHING_PATTERN(2),

CREDENTIALS_PASSWORD_NOT_MATCHING_PATTERN(3),

TYPE_NOT_MATCHING_PATTERN(4);

private int exitCode;

private InputValidatorExitcode(int exitCode) {
this.exitCode = exitCode;
}

public int getExitCode() {
return exitCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mercedesbenz.sechub.wrapper.prepare.modules;

public class PrepareWrapperInputValidatorException extends Exception {
private static final long serialVersionUID = 1L;

private InputValidatorExitcode exitCode;

public PrepareWrapperInputValidatorException(String message, InputValidatorExitcode exitCode) {
this(message, null, exitCode);
}

public PrepareWrapperInputValidatorException(String message, Exception e, InputValidatorExitcode exitCode) {
super(message, e);
this.exitCode = exitCode;
}

public InputValidatorExitcode getExitCode() {
return exitCode;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
// SPDX-License-Identifier: MIT
package com.mercedesbenz.sechub.wrapper.prepare.modules;

import static com.mercedesbenz.sechub.wrapper.prepare.cli.PrepareWrapperEnvironmentVariables.PDS_PREPARE_CREDENTIAL_PASSWORD;
import static com.mercedesbenz.sechub.wrapper.prepare.cli.PrepareWrapperEnvironmentVariables.PDS_PREPARE_CREDENTIAL_USERNAME;

import java.io.IOException;
import java.util.HashMap;

import javax.crypto.SealedObject;

import org.springframework.stereotype.Service;

import com.mercedesbenz.sechub.commons.core.security.CryptoAccess;
import com.mercedesbenz.sechub.commons.model.SecHubRemoteCredentialUserData;
import com.mercedesbenz.sechub.wrapper.prepare.prepare.PrepareWrapperContext;

@Service
public interface PrepareWrapperModule {

boolean isAbleToPrepare(PrepareWrapperContext context);

void prepare(PrepareWrapperContext context) throws IOException;
boolean prepare(PrepareWrapperContext context) throws IOException;

default void addSealedUserCredentials(SecHubRemoteCredentialUserData user, HashMap<String, SealedObject> credentialMap) {
SealedObject sealedUsername = CryptoAccess.CRYPTO_STRING.seal(user.getName());
SealedObject sealedPassword = CryptoAccess.CRYPTO_STRING.seal(user.getPassword());
credentialMap.put(PDS_PREPARE_CREDENTIAL_USERNAME, sealedUsername);
credentialMap.put(PDS_PREPARE_CREDENTIAL_PASSWORD, sealedPassword);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,18 @@
import static com.mercedesbenz.sechub.wrapper.prepare.cli.PrepareWrapperKeyConstants.KEY_PDS_PREPARE_PROCESS_TIMEOUT_SECONDS;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.crypto.SealedObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.mercedesbenz.sechub.commons.core.security.CryptoAccess;
import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterValueConstants;
import com.mercedesbenz.sechub.commons.pds.PDSProcessAdapterFactory;
import com.mercedesbenz.sechub.commons.pds.ProcessAdapter;

@Component
abstract class WrapperTool {
public abstract class WrapperTool {

private static final Logger LOG = LoggerFactory.getLogger(WrapperTool.class);
private static final int defaultMinutesToWaitForProduct = PDSDefaultParameterValueConstants.DEFAULT_MINUTES_TO_WAIT_FOR_PRODUCT;
Expand All @@ -32,12 +26,9 @@ abstract class WrapperTool {
@Value("${" + KEY_PDS_PREPARE_PROCESS_TIMEOUT_SECONDS + ":-1}")
private int pdsPrepareProcessTimeoutSeconds;

@Autowired
PDSProcessAdapterFactory processAdapterFactory;

abstract void cleanUploadDirectory(String uploadDirectory) throws IOException;
protected abstract void cleanUploadDirectory(String uploadDirectory) throws IOException;

void waitForProcessToFinish(ProcessAdapter process) {
protected void waitForProcessToFinish(ProcessAdapter process) {

LOG.debug("Wait for wrapper to finish process.");
int seconds = calculateTimeoutSeconds();
Expand All @@ -46,28 +37,17 @@ void waitForProcessToFinish(ProcessAdapter process) {
try {
exitDoneInTime = process.waitFor(seconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("GIT wrapper could not finish process.", e);
throw new RuntimeException("Wrapper for executed modul " + this.getClass().getSimpleName() + " could not finish process.", e);
}

if (!exitDoneInTime) {
throw new RuntimeException("GIT wrapper could not finish process. Waited " + pdsPrepareProcessTimeoutSeconds + " seconds.");
throw new RuntimeException("Wrapper for executed modul " + this.getClass().getSimpleName() + " could not finish process. Waited "
+ pdsPrepareProcessTimeoutSeconds + " seconds.");
}

if (process.exitValue() != 0) {
throw new RuntimeException("GIT wrapper process failed with exit code: " + process.exitValue());
}
}

void exportEnvironmentVariables(ProcessBuilder builder, Map<String, SealedObject> credentialMap) throws IOException {
Map<String, String> environment = builder.environment();
if (credentialMap != null && !credentialMap.isEmpty()) {
for (Map.Entry<String, SealedObject> entry : credentialMap.entrySet()) {
try {
environment.put(entry.getKey(), CryptoAccess.CRYPTO_STRING.unseal(entry.getValue()));
} catch (Exception e) {
throw new IOException("Error while unsealing credential: " + entry.getKey(), e);
}
}
throw new RuntimeException(
"Wrapper for executed modul " + this.getClass().getSimpleName() + " process failed with exit code: " + process.exitValue());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package com.mercedesbenz.sechub.wrapper.prepare.modules;
package com.mercedesbenz.sechub.wrapper.prepare.modules.git;

import com.mercedesbenz.sechub.wrapper.prepare.modules.ToolContext;

public class GitContext extends ToolContext {
private boolean cloneWithoutHistory;
Expand Down
Loading

0 comments on commit f2ffc93

Please sign in to comment.