Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare Module Skopeo #3127

Merged
merged 9 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
lorriborri marked this conversation as resolved.
Show resolved Hide resolved
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);
lorriborri marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading