Skip to content

Commit

Permalink
Use template data for script authentication #3710 (#3715)
Browse files Browse the repository at this point in the history
* Use tempalte data for script authentication #3710

- resolve template data variables
- validate configuration
- add test cases
- update pds solutions to make use of script authentication file if it exists
- add documentation for pds solution
- small improvements to error handling
- add new exit code for command line parsing errors
- add dedicated exception for wrong ZAP wrapper configuration
- updated command line parser to return settings and create scan context
  in an additional step
- improve exception handling of cli
- update test cases
  • Loading branch information
winzj authored Dec 12, 2024
1 parent bb477e2 commit 20749ba
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 225 deletions.
27 changes: 27 additions & 0 deletions sechub-pds-solutions/owaspzap/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@ Furthermore, the combination of OWASP ZAP and PDS make it possible to run both i

This folder contains the necessary scripts to run OWASP ZAP+PDS inside a container locally. Additionally, it contains scripts to build and push the OWASP ZAP+PDS container to your container registry and a Helm chart to install and run OWASP ZAP+PDS in a Kubernetes cluster.

== Script authentication using templates and assets
This PDS solution supports templates for web scan login.

With templates it is very easy to handle complex authentication scenarios (e.g. a corporate MFA login). Please read the [technical documentation](https://mercedes-benz.github.io/sechub/latest/sechub-techdoc.html) for details about templates and assets.

TIP: Templates are not mandatory. If you only need Basic Auth or custom headers template definitions and usage are not necessary.

To start a scan this pds-solution executes the script link:docker/scripts/owasp-zap.sh[owasp-zap.sh].
SecHub will automatically create the directory `$PDS_JOB_EXTRACTED_ASSETS_FOLDER`. The shell script requires a file called `script.groovy` inside the directory for script authentication with templates and assets to work. This script performs the authentication steps necessary for the targeted application.
When creating a template with an asset for webscan authentication and assigning it to a SecHub project, ensure that

- `PDS_OWASP_ZAP.zip` is available as asset file inside the used asset
- `PDS_OWASP_ZAP.zip` contains `script.groovy` at root level

The PDS will extract the file at runtime to `$PDS_JOB_EXTRACTED_ASSETS_FOLDER/webscan-login/script.groovy`.
If the extracted file exists by the time the script `owasp-zap.sh` is executed, the groovy script will be used for automated login.
Details about the configuration of the wrapper application that is used to configure the ZAP, can be found at link:https://github.com/mercedes-benz/sechub/blob/develop/sechub-wrapper-owasp-zap/README.adoc[https://github.com/mercedes-benz/sechub/blob/develop/sechub-wrapper-owasp-zap/README.adoc].

=== Script authentication template data variables
For a webscan script authentication with this pds-solution, template data with the following variables are required:

- `username`
- `password`

These variables are used for the credential data of the scan user. The `username` can be used for any type of user identification like email address, user ID, user name etc.
If TOTP is required make sure to use the dedicated entry inside the webscan authentication section of the sechub configuration JSON.

== Run Locally

This is the easiest way to get started.
Expand Down
9 changes: 9 additions & 0 deletions sechub-pds-solutions/owaspzap/docker/scripts/owasp-zap.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
# SPDX-License-Identifier: MIT

target_script_file="$PDS_JOB_EXTRACTED_ASSETS_FOLDER/webscan-login/script.groovy"

shutdownZAP() {
# --full: to specify the process by looking at full command line including the parameters
pkill -9 --full "/pds/tools/ZAP_"
Expand Down Expand Up @@ -121,6 +123,13 @@ else
echo "Use default value of wrapper for WRAPPER_RETRY_WAITTIME_MILLISECONDS"
fi

if [ -f "$target_script_file" ] ; then
export ZAP_GROOVY_LOGIN_SCRIPT_FILE="$target_script_file"
echo "Use script login file: $ZAP_GROOVY_LOGIN_SCRIPT_FILE"
else
echo "No script login file was found"
fi

echo ""
echo "Start scanning"
echo ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import com.mercedesbenz.sechub.zapwrapper.cli.ZapWrapperCommandLineParser.ZapWrapperCommandLineParserException;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScanContext;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScanContextFactory;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScannerFactory;
import com.mercedesbenz.sechub.zapwrapper.config.ZapWrapperContextCreationException;
import com.mercedesbenz.sechub.zapwrapper.util.TargetConnectionChecker;

public class ZapWrapperCLI {
Expand All @@ -21,31 +23,44 @@ public static void main(String[] args) throws IOException {
private void start(String[] args) throws IOException {
ZapScanContext scanContext = null;
try {
LOG.info("Building the scan configuration.");
scanContext = resolveScanContext(args);
if (scanContext == null) {
/* only happens when help command was executed - here we just exit with 0 */
LOG.info("Parsing command line parameters.");
CommandLineSettings cmdSettings = parseCommandLineArguments(args);
if (cmdSettings.isHelpRequired()) {
System.exit(0);
}
LOG.info("Starting the scan.");
LOG.info("Building the ZAP scan context.");
scanContext = createZapScanContext(cmdSettings);
LOG.info("Starting the ZAP scan.");
startExecution(scanContext);

} catch (ZapWrapperCommandLineParserException e) {
LOG.error("An error occurred while parsing the command line arguments: {}", e.getMessage(), e);
System.exit(ZapWrapperExitCode.UNSUPPORTED_COMMANDLINE_CONFIGURATION.getExitCode());
} catch (ZapWrapperContextCreationException e) {
LOG.error("An error occurred while creating ZAP scan context: {}", e.getMessage(), e);
System.exit(e.getZapWrapperExitCode().getExitCode());
} catch (ZapWrapperRuntimeException e) {
LOG.error("An error occurred during the scan: {}.", e.getMessage(), e);
scanContext.getZapProductMessageHelper().writeProductError(e);
if (scanContext == null) {
LOG.warn("Scan context is null, cannot write product error as message!");
} else {
scanContext.getZapProductMessageHelper().writeProductError(e);
}
System.exit(e.getExitCode().getExitCode());

} catch (ZapWrapperCommandLineParserException e) {
LOG.error("An error occurred while parsing the command line arguments: {}", e.getMessage(), e);
System.exit(ZapWrapperExitCode.UNSUPPORTED_CONFIGURATION.getExitCode());
}
}

private ZapScanContext resolveScanContext(String[] args) throws ZapWrapperCommandLineParserException {
private CommandLineSettings parseCommandLineArguments(String[] args) throws ZapWrapperCommandLineParserException {
ZapWrapperCommandLineParser parser = new ZapWrapperCommandLineParser();
return parser.parse(args);
}

private ZapScanContext createZapScanContext(CommandLineSettings cmdSettings) throws ZapWrapperContextCreationException {
ZapScanContext scanContext;
ZapScanContextFactory contextFactory = new ZapScanContextFactory();
scanContext = contextFactory.create(cmdSettings);
return scanContext;
}

private void startExecution(ZapScanContext scanContext) {
ZapScanExecutor scanExecutor = new ZapScanExecutor(new ZapScannerFactory(), new TargetConnectionChecker());
scanExecutor.execute(scanContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterException;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScanContext;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScanContextFactory;

public class ZapWrapperCommandLineParser {

Expand All @@ -19,23 +17,19 @@ public ZapWrapperCommandLineParserException(String message, Exception e) {
}

/**
* Parses given arguments
* Parses given command line arguments
*
* @param args
* @return configuration or <code>null</code> when only help wanted
* @return command line settings, never <code>null</code>
* @throws ZapWrapperCommandLineParserException
*/
public ZapScanContext parse(String... args) throws ZapWrapperCommandLineParserException {
public CommandLineSettings parse(String... args) throws ZapWrapperCommandLineParserException {
CommandLineSettings settings = parseCommandLineParameters(args);

if (settings.isHelpRequired()) {
showHelp();
return null;
}

ZapScanContextFactory configFactory = new ZapScanContextFactory();
return configFactory.create(settings);

return settings;
}

private CommandLineSettings parseCommandLineParameters(String... args) throws ZapWrapperCommandLineParserException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public enum ZapWrapperExitCode {

CLIENT_CERTIFICATE_CONFIG_INVALID(10),

UNSUPPORTED_COMMANDLINE_CONFIGURATION(11),

;

private int exitCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;

import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration;
import com.mercedesbenz.sechub.zapwrapper.helper.ZapPDSEventHandler;
Expand All @@ -34,9 +27,9 @@ public class ZapScanContext {

private ProxyInformation proxyInformation;

private List<String> zapRuleIDsToDeactivate = new ArrayList<>();
private List<String> zapRuleIDsToDeactivate = new LinkedList<>();

private List<File> apiDefinitionFiles = new ArrayList<>();
private List<File> apiDefinitionFiles = new LinkedList<>();

// Using Set here to avoid duplicates
private Set<String> zapURLsIncludeSet = new HashSet<>();
Expand All @@ -51,9 +44,11 @@ public class ZapScanContext {
private ZapPDSEventHandler zapPDSEventHandler;

private File clientCertificateFile;
private Map<String, File> headerValueFiles;
private Map<String, File> headerValueFiles = new HashMap<>();
private String ajaxSpiderBrowserId;

private File groovyScriptLoginFile;
private Map<String, String> templateVariables = new LinkedHashMap<>();

private ZapScanContext() {
}
Expand Down Expand Up @@ -109,24 +104,15 @@ public List<String> getZapRuleIDsToDeactivate() {
}

public List<File> getApiDefinitionFiles() {
if (apiDefinitionFiles == null) {
return Collections.emptyList();
}
return apiDefinitionFiles;
return Collections.unmodifiableList(apiDefinitionFiles);
}

public Set<String> getZapURLsIncludeSet() {
if (zapURLsIncludeSet == null) {
return Collections.emptySet();
}
return zapURLsIncludeSet;
return Collections.unmodifiableSet(zapURLsIncludeSet);
}

public Set<String> getZapURLsExcludeSet() {
if (zapURLsExcludeSet == null) {
return Collections.emptySet();
}
return zapURLsExcludeSet;
return Collections.unmodifiableSet(zapURLsExcludeSet);
}

public boolean connectionCheckEnabled() {
Expand Down Expand Up @@ -165,6 +151,10 @@ public File getGroovyScriptLoginFile() {
return groovyScriptLoginFile;
}

public Map<String, String> getTemplateVariables() {
return Collections.unmodifiableMap(templateVariables);
}

public static ZapScanContextBuilder builder() {
return new ZapScanContextBuilder();
}
Expand Down Expand Up @@ -210,7 +200,9 @@ public static class ZapScanContextBuilder {

private File groovyScriptLoginFile;

private List<String> zapRuleIDsToDeactivate;
private List<String> zapRuleIDsToDeactivate = new LinkedList<>();

private Map<String, String> templateVariables = new LinkedHashMap<>();

public ZapScanContextBuilder setServerConfig(ZapServerConfiguration serverConfig) {
this.serverConfig = serverConfig;
Expand Down Expand Up @@ -258,22 +250,30 @@ public ZapScanContextBuilder setProxyInformation(ProxyInformation proxyInformati
}

public ZapScanContextBuilder setZapRuleIDsToDeactivate(List<String> zapRuleIDsToDeactivate) {
this.zapRuleIDsToDeactivate = zapRuleIDsToDeactivate;
if (zapRuleIDsToDeactivate != null) {
this.zapRuleIDsToDeactivate = new LinkedList<>(zapRuleIDsToDeactivate);
}
return this;
}

public ZapScanContextBuilder addApiDefinitionFiles(List<File> apiDefinitionFiles) {
this.apiDefinitionFiles.addAll(apiDefinitionFiles);
public ZapScanContextBuilder setApiDefinitionFiles(List<File> apiDefinitionFiles) {
if (apiDefinitionFiles != null) {
this.apiDefinitionFiles = new LinkedList<>(apiDefinitionFiles);
}
return this;
}

public ZapScanContextBuilder addZapURLsIncludeSet(Set<String> zapURLsIncludeList) {
this.zapURLsIncludeSet.addAll(zapURLsIncludeList);
public ZapScanContextBuilder setZapURLsIncludeSet(Set<String> zapURLsIncludeSet) {
if (zapURLsIncludeSet != null) {
this.zapURLsIncludeSet = new HashSet<>(zapURLsIncludeSet);
}
return this;
}

public ZapScanContextBuilder addZapURLsExcludeSet(Set<String> zapURLsExcludeList) {
this.zapURLsExcludeSet.addAll(zapURLsExcludeList);
public ZapScanContextBuilder setZapURLsExcludeSet(Set<String> zapURLsExcludeSet) {
if (zapURLsExcludeSet != null) {
this.zapURLsExcludeSet = new HashSet<>(zapURLsExcludeSet);
}
return this;
}

Expand Down Expand Up @@ -307,8 +307,10 @@ public ZapScanContextBuilder setClientCertificateFile(File clientCertificateFile
return this;
}

public ZapScanContextBuilder addHeaderValueFiles(Map<String, File> headerValueFiles) {
this.headerValueFiles.putAll(headerValueFiles);
public ZapScanContextBuilder setHeaderValueFiles(Map<String, File> headerValueFiles) {
if (headerValueFiles != null) {
this.headerValueFiles = new HashMap<>(headerValueFiles);
}
return this;
}

Expand All @@ -322,6 +324,13 @@ public ZapScanContextBuilder setGroovyScriptLoginFile(File groovyScriptLoginFile
return this;
}

public ZapScanContextBuilder setTemplateVariables(Map<String, String> templateVariables) {
if (templateVariables != null) {
this.templateVariables = new LinkedHashMap<>(templateVariables);
}
return this;
}

public ZapScanContext build() {
ZapScanContext zapScanContext = new ZapScanContext();
zapScanContext.serverConfig = this.serverConfig;
Expand All @@ -340,8 +349,8 @@ public ZapScanContext build() {

zapScanContext.apiDefinitionFiles = this.apiDefinitionFiles;

zapScanContext.zapURLsIncludeSet.addAll(this.zapURLsIncludeSet);
zapScanContext.zapURLsExcludeSet.addAll(this.zapURLsExcludeSet);
zapScanContext.zapURLsIncludeSet = this.zapURLsIncludeSet;
zapScanContext.zapURLsExcludeSet = this.zapURLsExcludeSet;

zapScanContext.connectionCheckEnabled = this.connectionCheckEnabled;

Expand All @@ -359,8 +368,10 @@ public ZapScanContext build() {
zapScanContext.ajaxSpiderBrowserId = this.ajaxSpiderBrowserId;

zapScanContext.groovyScriptLoginFile = this.groovyScriptLoginFile;
zapScanContext.templateVariables = this.templateVariables;

return zapScanContext;
}

}
}
Loading

0 comments on commit 20749ba

Please sign in to comment.