Skip to content

Commit

Permalink
Use tempalte data for script authentication #3710
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
winzj committed Dec 9, 2024
1 parent e1510aa commit 409aa0d
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 152 deletions.
21 changes: 21 additions & 0 deletions sechub-pds-solutions/owaspzap/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ 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 login using templates and assets

To start a scan this pds-solution executes the script link:docker/scripts/owasp-zap.sh[owasp-zap.sh].
The script login mechanism using the SecHub templates and assets feature requires the following file to be present at execution time of the script to work:
----
$PDS_JOB_EXTRACTED_ASSETS_FOLDER/script.groovy
----
SecHub will automatically create the directory `$PDS_JOB_EXTRACTED_ASSETS_FOLDER`. The shell script requires a file called `script.groovy` inside the directory to work, which does the authentication steps necessary for the targeted application.
When setting a template with assets for webscan authentication, ensure that the authentication script file at the proposed location exists. For webscan authentication, the templates and assets are setup correctly,
if the PDS extracts the assets that `$PDS_JOB_EXTRACTED_ASSETS_FOLDER/script.groovy` exists by the time the script `owasp-zap.sh` is executed.
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 login 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 login section of the sechub configuration JSON.

== Run Locally

This is the easiest way to get started.
Expand Down
7 changes: 7 additions & 0 deletions sechub-pds-solutions/owaspzap/docker/scripts/owasp-zap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ else
echo "Use default value of wrapper for WRAPPER_RETRY_WAITTIME_MILLISECONDS"
fi

if [ -f "$PDS_JOB_EXTRACTED_ASSETS_FOLDER/script.groovy" ] ; then
export ZAP_GROOVY_LOGIN_SCRIPT_FILE="$PDS_JOB_EXTRACTED_ASSETS_FOLDER/script.groovy"
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 @@ -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,21 +250,25 @@ public ZapScanContextBuilder setProxyInformation(ProxyInformation proxyInformati
}

public ZapScanContextBuilder setZapRuleIDsToDeactivate(List<String> zapRuleIDsToDeactivate) {
this.zapRuleIDsToDeactivate.clear();
this.zapRuleIDsToDeactivate = zapRuleIDsToDeactivate;
return this;
}

public ZapScanContextBuilder addApiDefinitionFiles(List<File> apiDefinitionFiles) {
this.apiDefinitionFiles.clear();
this.apiDefinitionFiles.addAll(apiDefinitionFiles);
return this;
}

public ZapScanContextBuilder addZapURLsIncludeSet(Set<String> zapURLsIncludeList) {
this.zapURLsExcludeSet.clear();
this.zapURLsIncludeSet.addAll(zapURLsIncludeList);
return this;
}

public ZapScanContextBuilder addZapURLsExcludeSet(Set<String> zapURLsExcludeList) {
this.zapURLsExcludeSet.clear();
this.zapURLsExcludeSet.addAll(zapURLsExcludeList);
return this;
}
Expand Down Expand Up @@ -308,6 +304,7 @@ public ZapScanContextBuilder setClientCertificateFile(File clientCertificateFile
}

public ZapScanContextBuilder addHeaderValueFiles(Map<String, File> headerValueFiles) {
this.headerValueFiles.clear();
this.headerValueFiles.putAll(headerValueFiles);
return this;
}
Expand All @@ -322,6 +319,12 @@ public ZapScanContextBuilder setGroovyScriptLoginFile(File groovyScriptLoginFile
return this;
}

public ZapScanContextBuilder setTemplateVariables(Map<String, String> templateVariables) {
this.templateVariables.clear();
this.templateVariables.putAll(templateVariables);
return this;
}

public ZapScanContext build() {
ZapScanContext zapScanContext = new ZapScanContext();
zapScanContext.serverConfig = this.serverConfig;
Expand Down Expand Up @@ -359,8 +362,10 @@ public ZapScanContext build() {
zapScanContext.ajaxSpiderBrowserId = this.ajaxSpiderBrowserId;

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

return zapScanContext;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

import com.mercedesbenz.sechub.commons.model.SecHubScanConfiguration;
import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration;
import com.mercedesbenz.sechub.commons.model.template.TemplateData;
import com.mercedesbenz.sechub.commons.model.template.TemplateDataResolver;
import com.mercedesbenz.sechub.commons.model.template.TemplateType;
import com.mercedesbenz.sechub.zapwrapper.cli.CommandLineSettings;
import com.mercedesbenz.sechub.zapwrapper.cli.ZapWrapperExitCode;
import com.mercedesbenz.sechub.zapwrapper.cli.ZapWrapperRuntimeException;
Expand Down Expand Up @@ -66,6 +69,10 @@ public ZapScanContext create(CommandLineSettings settings) {

Map<String, File> headerValueFiles = fetchHeaderValueFiles(sechubScanConfig);

File groovyScriptFile = fetchGroovyScriptFile(settings);
Map<String, String> templateVariables = fetchTemplateVariables(sechubScanConfig);
assertValidScriptLoginConfiguration(groovyScriptFile, templateVariables);

/* we always use the SecHub job UUID as Zap context name */
String contextName = settings.getJobUUID();
if (contextName == null) {
Expand All @@ -80,7 +87,7 @@ public ZapScanContext create(CommandLineSettings settings) {
ZapPDSEventHandler zapEventHandler = createZapEventhandler(settings);

/* @formatter:off */
ZapScanContext scanContext = ZapScanContext.builder()
ZapScanContext scanContext = ZapScanContext.builder()
.setTargetUrl(targetUrl)
.setVerboseOutput(settings.isVerboseEnabled())
.setReportFile(settings.getReportFile())
Expand All @@ -102,7 +109,8 @@ public ZapScanContext create(CommandLineSettings settings) {
.setRetryWaittimeInMilliseconds(settings.getRetryWaittimeInMilliseconds())
.setZapProductMessageHelper(productMessagehelper)
.setZapPDSEventHandler(zapEventHandler)
.setGroovyScriptLoginFile(fetchGroovyScriptFile(settings))
.setGroovyScriptLoginFile(groovyScriptFile)
.setTemplateVariables(templateVariables)
.build();
/* @formatter:on */
return scanContext;
Expand Down Expand Up @@ -276,4 +284,52 @@ private File fetchGroovyScriptFile(CommandLineSettings settings) {
}
return new File(groovyScriptFile);
}

private Map<String, String> fetchTemplateVariables(SecHubScanConfiguration sechubScanConfig) {
TemplateDataResolver templateDataResolver = new TemplateDataResolver();
TemplateData templateData = templateDataResolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, sechubScanConfig);
if (templateData == null) {
return new LinkedHashMap<>();
}
return templateData.getVariables();
}

/**
* This method verifies that the script login configuration is valid. No script
* login configured is a valid configuration as well.
*
* @param groovyScriptFile
* @param templateVariables
*
* @throws ZapWrapperRuntimeException
*/
private void assertValidScriptLoginConfiguration(File groovyScriptFile, Map<String, String> templateVariables) {
// no script login was defined
if (groovyScriptFile == null && templateVariables.isEmpty()) {
return;
}
// A script was defined, but no template data where defined
if (groovyScriptFile != null && templateVariables.isEmpty()) {
throw new ZapWrapperRuntimeException(
"When a groovy login script is defined, the variables: '" + ZapTemplateDataVariableKeys.USERNAME_KEY + "' and '"
+ ZapTemplateDataVariableKeys.PASSWORD_KEY + "' must be set inside webscan template data!",
ZapWrapperExitCode.UNSUPPORTED_CONFIGURATION);
}
// No script was defined, but template data where defined
if (groovyScriptFile == null && !templateVariables.isEmpty()) {
throw new ZapWrapperRuntimeException("When no groovy login script is defined, no template data variables must be defined!",
ZapWrapperExitCode.UNSUPPORTED_CONFIGURATION);
}
// if a script and the template data are defined, the mandatory variables must
// be present
if (groovyScriptFile != null && !templateVariables.isEmpty()) {
if (templateVariables.get(ZapTemplateDataVariableKeys.USERNAME_KEY) == null
|| templateVariables.get(ZapTemplateDataVariableKeys.PASSWORD_KEY) == null) {
throw new ZapWrapperRuntimeException(
"For script authentication webscans using templates, the variables: '" + ZapTemplateDataVariableKeys.USERNAME_KEY + "' and '"
+ ZapTemplateDataVariableKeys.PASSWORD_KEY + "' must be set inside webscan template data!",
ZapWrapperExitCode.UNSUPPORTED_CONFIGURATION);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
package com.mercedesbenz.sechub.zapwrapper.config;

public class ZapTemplateDataVariableKeys {

public static final String USERNAME_KEY = "username";
public static final String PASSWORD_KEY = "password";
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.mercedesbenz.sechub.zapwrapper.cli.ZapWrapperRuntimeException;
import com.mercedesbenz.sechub.zapwrapper.config.ProxyInformation;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScanContext;
import com.mercedesbenz.sechub.zapwrapper.config.ZapTemplateDataVariableKeys;
import com.mercedesbenz.sechub.zapwrapper.config.auth.ZapAuthenticationType;
import com.mercedesbenz.sechub.zapwrapper.config.auth.ZapSessionManagementType;
import com.mercedesbenz.sechub.zapwrapper.helper.ZapPDSEventHandler;
Expand Down Expand Up @@ -95,7 +96,7 @@ public void scan() throws ZapWrapperRuntimeException {
/* After scan */
generateZapReport();
cleanUp();
} catch (ClientApiException e) {
} catch (ClientApiException | ZapWrapperRuntimeException e) {
cleanUp();
throw new ZapWrapperRuntimeException("For scan: " + scanContext.getContextName() + ". An error occured while scanning!", e,
ZapWrapperExitCode.PRODUCT_EXECUTION_ERROR);
Expand Down Expand Up @@ -322,16 +323,14 @@ UserInformation setupLoginInsideZapContext(int zapContextId) throws ClientApiExc
return initBasicAuthentication(zapContextId, webLoginConfiguration.getBasic().get());
}

if (scriptLoginWanted()) {
if (scriptLoginConfigured()) {
LOG.info("For scan {}: Setting up authentcation and session management method for script authentication.", scanContext.getContextName());
setupAuthenticationAndSessionManagementMethodForScriptLogin(zapContextId);

LOG.info("For scan {}: Performing script authentication.", scanContext.getContextName());
String zapAuthSessionName = scriptLogin.login(scanContext, clientApiWrapper);

// TODO 2024-11-21 jan: read the username from templateData as soon as it is
// implemented
String username = "DUMMY";
String username = scanContext.getTemplateVariables().get(ZapTemplateDataVariableKeys.USERNAME_KEY);
/* @formatter:off */
LOG.info("For scan {}: Setup scan user in ZAP to use authenticated session.", scanContext.getContextName());
StringBuilder authCredentialsConfigParams = new StringBuilder();
Expand Down Expand Up @@ -839,8 +838,8 @@ private void addXSecHubDASTHeader() throws ClientApiException {
clientApiWrapper.addReplacerRule(description, enabled, matchtype, matchregex, matchstring, replacement, initiators, url);
}

private boolean scriptLoginWanted() {
return scanContext.getGroovyScriptLoginFile() != null;
private boolean scriptLoginConfigured() {
return scanContext.getGroovyScriptLoginFile() != null && !scanContext.getTemplateVariables().isEmpty();
}

record UserInformation(String userName, int zapuserId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.mercedesbenz.sechub.commons.model.login.WebLoginConfiguration;
import com.mercedesbenz.sechub.commons.model.login.WebLoginTOTPConfiguration;
import com.mercedesbenz.sechub.zapwrapper.config.ZapScanContext;
import com.mercedesbenz.sechub.zapwrapper.config.ZapTemplateDataVariableKeys;
import com.mercedesbenz.sechub.zapwrapper.util.TOTPGenerator;
import com.mercedesbenz.sechub.zapwrapper.util.ZapWrapperStringDecoder;

Expand Down Expand Up @@ -79,7 +80,6 @@ public ScriptLoginResult executeScript(File scriptFile, ZapScanContext scanConte
}

private Bindings createBindings(ZapScanContext scanContext, ScriptEngine scriptEngine, FirefoxDriver firefox, WebDriverWait wait) {
// TODO 2024-11-21 jan: use templates structure from sechub webscan config
SecHubWebScanConfiguration secHubWebScanConfiguration = scanContext.getSecHubWebScanConfiguration();
WebLoginConfiguration webLoginConfiguration = secHubWebScanConfiguration.getLogin().get();

Expand All @@ -95,11 +95,7 @@ private Bindings createBindings(ZapScanContext scanContext, ScriptEngine scriptE
totpGenerator = new TOTPGenerator(decodedSeed, totp.getTokenLength(), totp.getHashAlgorithm(), totp.getValidityInSeconds());
}

// TODO 2024-11-21 jan: read the username and password from templateData as soon
// as it is
// implemented
String user = "DUMMY";
String password = "DUMMY";
Map<String, String> templateVariables = scanContext.getTemplateVariables();

Bindings bindings = scriptEngine.createBindings();
bindings.put(FIREFOX_WEBDRIVER_KEY, firefox);
Expand All @@ -108,9 +104,14 @@ private Bindings createBindings(ZapScanContext scanContext, ScriptEngine scriptE
bindings.put(SECHUB_WEBSCAN_CONFIG_KEY, secHubWebScanConfiguration);
bindings.put(TOTP_GENERATOR_KEY, totpGenerator);

bindings.put(USER_KEY, user);
bindings.put(PASSWORD_KEY, password);
bindings.put(LOGIN_URL_KEY, webLoginConfiguration.getUrl().toString());
bindings.put(USER_KEY, templateVariables.get(ZapTemplateDataVariableKeys.USERNAME_KEY));
bindings.put(PASSWORD_KEY, templateVariables.get(ZapTemplateDataVariableKeys.PASSWORD_KEY));
if (webLoginConfiguration.getUrl() != null) {
bindings.put(LOGIN_URL_KEY, webLoginConfiguration.getUrl().toString());
} else {
// if no dedicated login URL is set we assume an automated redirect
bindings.put(LOGIN_URL_KEY, scanContext.getTargetUrlAsString());
}
bindings.put(TARGET_URL_KEY, scanContext.getTargetUrlAsString());

return bindings;
Expand Down
Loading

0 comments on commit 409aa0d

Please sign in to comment.