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

[JENKINS-63254][JENKINS-47101] Insecure Groovy String Interpolation Warnings #370

Merged
merged 81 commits into from
Nov 6, 2020
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
009bad3
added unit test showing leaked password
car-roll Jul 14, 2020
52f6f4b
working PoC
car-roll Jul 24, 2020
75c7406
add listener to report errors to pipeline output
car-roll Jul 24, 2020
191f08e
PoC 2 for groovy interpolation interception. Does not require core mods
car-roll Jul 28, 2020
ccf9040
Wrap EnvironmentExpander and EnvVars together for parseArgs
car-roll Jul 31, 2020
5c53b94
use incrementals, revert jenkins version
car-roll Jul 31, 2020
c5ef8ad
code cleanup
car-roll Jul 31, 2020
1a7793d
catch null arguments
car-roll Jul 31, 2020
0829025
Make unit test windows friendly. Remove dollar sign from password
car-roll Jul 31, 2020
18653c0
update to use newer implementation of EnvironmentExpander
car-roll Aug 4, 2020
5ba9b2a
Use updated api in EnvironmentExpander
car-roll Aug 5, 2020
aeebf17
address review comments
car-roll Aug 27, 2020
02804b3
add factory method, add report action, and summary page
car-roll Sep 1, 2020
20d781d
change from table to list, change icon
car-roll Sep 2, 2020
b404458
update jelly formatting, update unit test
car-roll Sep 5, 2020
22c4ae3
Check for empty body
car-roll Sep 8, 2020
41984a6
Update body check, support legacy stage behavior
car-roll Sep 10, 2020
d4759b1
add check for empty args
car-roll Sep 10, 2020
4a98072
fix variable clashing
car-roll Sep 10, 2020
8bdc019
address review comments
car-roll Sep 10, 2020
e975453
Refactor Action name, generate action only when there are secrets exp…
car-roll Sep 10, 2020
d4765a4
update null environment variable test
car-roll Sep 10, 2020
341972a
check for "bat" args on windows
car-roll Sep 11, 2020
ade619f
avoid reflective API
car-roll Sep 11, 2020
79d255a
address review comments
car-roll Sep 11, 2020
0abb665
address review comments
car-roll Sep 15, 2020
7a5d0c7
update jelly file path, fix localization error with parenthesis
car-roll Sep 15, 2020
506d5b3
add placeholder explanation page for jelly
car-roll Sep 15, 2020
93b018b
more refactoring of envwatcher
car-roll Sep 15, 2020
065b129
update step-api dependency
car-roll Sep 15, 2020
4903d4e
support detecting interpolation in describables
car-roll Sep 16, 2020
cc5b3b0
Track groovy strings instead of using InterpolatedSecretsDetector.
car-roll Sep 18, 2020
8517f12
added InterpolatedUninstantiatedDescribable
car-roll Sep 21, 2020
f12f226
add null checks for environmentexpander and envars
car-roll Sep 21, 2020
9a31bce
Merge remote-tracking branch 'upstream/master' into interpolation-v2
car-roll Sep 22, 2020
6e8b5eb
Refactor ArgumentsActionImpl using EnvironmentExpander and removing s…
car-roll Sep 23, 2020
eaa4c90
set sensitiveVariables as field instead of recursively passing through
car-roll Sep 23, 2020
70c4c22
Move interpolatedStrings into parseArgs
car-roll Sep 23, 2020
8a799dc
Remove duplicate code
car-roll Sep 23, 2020
1afab59
no metaStep returns NamedArgsAndClosure
car-roll Sep 25, 2020
dbe7d8f
Update error logging
car-roll Sep 25, 2020
902ab29
add windows support for unit test
car-roll Sep 25, 2020
6a39c5c
report step name and arguments that log a warning
car-roll Sep 28, 2020
ec29d4b
Handle multiple sensitive variables in one argument
car-roll Sep 28, 2020
97b1535
fix windows tests
car-roll Sep 28, 2020
b34e5fd
Merge remote-tracking branch 'upstream/master' into interpolation-v2
car-roll Sep 29, 2020
b0d183b
update documentation
car-roll Sep 29, 2020
b846c44
refactoring
car-roll Oct 1, 2020
4f9997d
fix jelly output
car-roll Oct 1, 2020
8506214
fix unit tests, some clean up
car-roll Oct 1, 2020
e6284e9
add redirect to console warning
car-roll Oct 1, 2020
ef157e9
address review comments
car-roll Oct 8, 2020
00d220b
simplify parseArgs
car-roll Oct 8, 2020
a8df396
centralize parsing of NamedArgsAndClosure
car-roll Oct 12, 2020
7c8022b
Sort arguments in step signature
car-roll Oct 12, 2020
a890c43
Merge remote-tracking branch 'upstream/master' into interpolation-v2
car-roll Oct 12, 2020
36050b4
fix comments
car-roll Oct 13, 2020
163e7cb
make step arguments print out in order they were added
car-roll Oct 14, 2020
3740ed1
update workflow-step-api and credentials-binding to release versions
car-roll Oct 15, 2020
a20db3d
update bom
car-roll Oct 19, 2020
b14f8cf
bump bom to v15
car-roll Oct 19, 2020
d489f73
address review comments
car-roll Oct 19, 2020
11b12ba
make getStepSignature recursive
car-roll Oct 20, 2020
bd0dfd2
make recursion more generic, add unit test for getStepSignature()
car-roll Oct 20, 2020
86cf9f3
parse UninstantiatedDescribable in getStepSignature
car-roll Oct 20, 2020
a24ffe0
control warning behavior with system property
car-roll Oct 20, 2020
cd43425
update unit tests with new UninstantiatedDescribable output
car-roll Oct 20, 2020
ac2ec02
Merge remote-tracking branch 'upstream/master' into interpolation-v2
car-roll Oct 27, 2020
41e50e6
address review comments
car-roll Nov 2, 2020
3e54d36
make InterpolatedWarnings.run transient field
car-roll Nov 2, 2020
b52438d
update UninstantiatedDescribable $class toString
car-roll Nov 3, 2020
49695e0
update InterpolatedSecretesAction onLoad and onAttached
car-roll Nov 3, 2020
7d8672c
Update getStepSignature to better reflect pipeline input
car-roll Nov 5, 2020
611326c
Remove printing of step signature
car-roll Nov 5, 2020
96c2f30
remove setting the model for the Uninstantiated Describable
car-roll Nov 5, 2020
7811148
Make sure password parameters are masked in step arguments
dwnusbaum Nov 5, 2020
f7798af
Remove InterpolatedSecretsActionTest.java
dwnusbaum Nov 5, 2020
40eb64a
Align workflow-support tests jar with incremental version
dwnusbaum Nov 5, 2020
bc8d268
Update to latest workflow-support incremental
dwnusbaum Nov 5, 2020
e173261
Merge remote-tracking branch 'upstream/master' into interpolation-v2
car-roll Nov 5, 2020
4524d26
update pom, update changelog to prepare for release
car-roll Nov 6, 2020
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 @@ -81,6 +81,7 @@ private static final class DynamicContextQuery {
}
}

@FunctionalInterface
interface ThrowingSupplier<T> {
T get() throws IOException;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public class CpsStepContext extends DefaultStepContext { // TODO add XStream cla
private transient List<CpsBodyInvoker> bodyInvokers = new ArrayList<>();

/**
* While {@link CpsStepContext} has not received teh response, maintains the body closure.
* While {@link CpsStepContext} has not received the response, maintains the body closure.
*
* This is the implicit closure block passed to the step invocation.
*/
Expand Down
238 changes: 182 additions & 56 deletions src/main/java/org/jenkinsci/plugins/workflow/cps/DSL.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,13 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;


/**
* Implements {@link ArgumentsAction} by storing step arguments, with sanitization.
Expand All @@ -66,47 +65,39 @@ public class ArgumentsActionImpl extends ArgumentsAction {
@CheckForNull
private Map<String,Object> arguments;

private final Set<String> sensitiveVariables;

boolean isUnmodifiedBySanitization = true;

private static final Logger LOGGER = Logger.getLogger(ArgumentsActionImpl.class.getName());

public ArgumentsActionImpl(@Nonnull Map<String, Object> stepArguments, @CheckForNull EnvVars env) {
public ArgumentsActionImpl(@Nonnull Map<String, Object> stepArguments, @CheckForNull EnvVars env, @Nonnull Set<String> sensitiveVariables) {
this.sensitiveVariables = new HashSet<>(sensitiveVariables);
arguments = serializationCheck(sanitizeMapAndRecordMutation(stepArguments, env));
}

/** Create a step, sanitizing strings for secured content */
public ArgumentsActionImpl(@Nonnull Map<String, Object> stepArguments) {
this(stepArguments, new EnvVars());
this(stepArguments, new EnvVars(), Collections.emptySet());
}

/** For testing use only */
ArgumentsActionImpl(){
ArgumentsActionImpl(@Nonnull Set<String> sensitiveVariables){
this.isUnmodifiedBySanitization = false;
this.arguments = Collections.emptyMap();
this.sensitiveVariables = sensitiveVariables;
}

/** See if sensitive environment variable content is in a string */
public static boolean isStringSafe(@CheckForNull String input, @CheckForNull EnvVars variables, @Nonnull Set<String> safeEnvVariables) {
if (input == null || variables == null || variables.size() == 0) {
return true;
/** See if sensitive environment variable content is in a string and replace the content with its associated variable name, otherwise return string unmodified*/
public static String replaceSensitiveVariables(@Nonnull String input, @CheckForNull EnvVars variables, @Nonnull Set<String> sensitiveVariables) {
if (variables == null || variables.size() == 0 || sensitiveVariables.size() ==0) {
return input;
}
StringBuilder pattern = new StringBuilder();
int count = 0;
for (Map.Entry<String,String> ent : variables.entrySet()) {
String val = ent.getValue();
if (val == null || val.isEmpty() || safeEnvVariables.contains(ent.getKey())) { // Skip values that are safe
continue;
}
if (count > 0) {
pattern.append('|');
}
pattern.append(Pattern.quote(val));
count++;
String modded = input;
for (String sensitive : sensitiveVariables) {
modded = modded.replace(variables.get(sensitive), "${" + sensitive + "}");
}
return (count > 0)
? !Pattern.compile(pattern.toString()).matcher(input).find()
: true;
return modded;
}

/** Restrict stored arguments to a reasonable subset of types so we don't retain totally arbitrary objects
Expand Down Expand Up @@ -138,98 +129,6 @@ boolean isStorableType(Object ob) {
return c.isPrimitive() || (c.isArray() && !(c.getComponentType().isPrimitive())); // Primitive arrays are not legal here
}

/** Normal environment variables, as opposed to ones that might come from credentials bindings */
private static final HashSet<String> SAFE_ENVIRONMENT_VARIABLES = new HashSet<>(Arrays.asList(
// Pipeline/Jenkins variables in normal builds
"BRANCH_NAME",
"BUILD_DISPLAY_NAME",
"BUILD_ID",
"BUILD_NUMBER",
"BUILD_TAG",
"BUILD_URL",
"CHANGE_AUTHOR",
"CHANGE_AUTHOR_DISPLAY_NAME",
"CHANGE_AUTHOR_EMAIL",
"CHANGE_ID",
"CHANGE_TARGET",
"CHANGE_TITLE",
"CHANGE_URL",
"EXECUTOR_NUMBER",
"HUDSON_COOKIE",
"HUDSON_HOME",
"HUDSON_SERVER_COOKIE",
"HUDSON_URL",
"JENKINS_HOME",
"JENKINS_SERVER_COOKIE",
"JENKINS_URL",
"JOB_BASE_NAME",
"JOB_NAME",
"JOB_URL",
"NODE_LABELS",
"NODE_NAME",
"STAGE_NAME",
"WORKSPACE",

// Normal system variables for posix environments
"HOME",
"LANG",
"LOGNAME",
"MAIL",
"NLSPATH",
"PATH",
"PWD",
"SHELL",
"SHLVL",
"TERM",
"USER",
"XFILESEARCHPATH",

// Windows system variables
"ALLUSERSPROFILE",
"APPDATA",
"CD",
"ClientName",
"CMDEXTVERSION",
"CMDCMDLINE",
"CommonProgramFiles",
"COMPUTERNAME",
"COMSPEC",
"DATE",
"ERRORLEVEL",
"HighestNumaNodeNumber",
"HOMEDRIVE",
"HOMEPATH",
"LOCALAPPDATA",
"LOGONSERVER",
"NUMBER_OF_PROCESSORS",
"OS",
"PATHEXT",
"PROCESSOR_ARCHITECTURE",
"PROCESSOR_ARCHITEW6432",
"PROCESSOR_IDENTIFIER",
"PROCESSOR_LEVEL",
"PROCESSOR_REVISION",
"ProgramW6432",
"ProgramData",
"ProgramFiles",
"ProgramFiles (x86)",
"PROMPT",
"PSModulePath",
"Public",
"RANDOM",
"%SessionName%",
"SYSTEMDRIVE",
"SYSTEMROOT",
"TEMP", "TMP",
"TIME",
"UserDnsDomain",
"USERDOMAIN",
"USERDOMAIN_roamingprofile",
// "USERNAME", // Not whitelisted because this is a likely variable name for credentials binding
"USERPROFILE",
"WINDIR"
));

/**
* Sanitize a list recursively
*/
Expand Down Expand Up @@ -279,7 +178,7 @@ Object sanitizeArrayAndRecordMutation(@Nonnull Object[] objects, @CheckForNull E

/** Recursively sanitize a single object by:
* - Exploding {@link Step}s and {@link UninstantiatedDescribable}s into their Maps to sanitize
* - Removing unsafe strings using {@link #isStringSafe(String, EnvVars, Set)} and replace with {@link NotStoredReason#MASKED_VALUE}
* - Removing unsafe strings using {@link #replaceSensitiveVariables(String, EnvVars, Set)} and replace with the variable name
* - Removing oversized objects using {@link #isOversized(Object)} and replacing with {@link NotStoredReason#OVERSIZE_VALUE}
* While making an effort not to retain needless copies of objects and to re-use originals where possible
* (including the Step or UninstantiatedDescribable)
Expand Down Expand Up @@ -331,9 +230,12 @@ Object sanitizeObjectAndRecordMutation(@CheckForNull Object o, @CheckForNull Env
this.isUnmodifiedBySanitization = true;
return NotStoredReason.UNSERIALIZABLE;
}
} else if (modded instanceof String && vars != null && !vars.isEmpty() && !isStringSafe((String)modded, vars, SAFE_ENVIRONMENT_VARIABLES)) {
this.isUnmodifiedBySanitization = false;
return NotStoredReason.MASKED_VALUE;
} else if (modded instanceof String && vars != null && !vars.isEmpty()) {
String replaced = replaceSensitiveVariables((String)modded, vars, sensitiveVariables);
if (!replaced.equals(modded)) {
this.isUnmodifiedBySanitization = false;
}
return replaced;
}

if (modded != tempVal) {
Expand Down Expand Up @@ -391,12 +293,11 @@ Map<String, Object> serializationCheck(@Nonnull Map<String, Object> arguments) {
@Nonnull
Map<String,Object> sanitizeMapAndRecordMutation(@Nonnull Map<String, Object> mapContents, @CheckForNull EnvVars variables) {
// Package scoped so we can test it directly
HashMap<String, Object> output = Maps.newHashMapWithExpectedSize(mapContents.size());
LinkedHashMap<String, Object> output = new LinkedHashMap<>(mapContents.size());

boolean isMutated = false;
for (Map.Entry<String,?> param : mapContents.entrySet()) {
Object modded = sanitizeObjectAndRecordMutation(param.getValue(), variables);

if (modded != param.getValue()) {
// Sanitization stripped out some values, so we need to store the mutated object
output.put(param.getKey(), modded);
Expand Down
Loading