-
Notifications
You must be signed in to change notification settings - Fork 193
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
Conversation
906fe37
to
5c53b94
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Added a few comments/suggestions.
src/main/java/org/jenkinsci/plugins/workflow/cps/EnvironmentWatcher.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/cps/EnvironmentWatcher.java
Outdated
Show resolved
Hide resolved
From a very brief glance I suspect this is what should also be extended to finally fix JENKINS-47101. |
.../resources/org/jenkinsci/plugins/workflow/cps/view/EnvironmentWatcherRunReport/summary.jelly
Outdated
Show resolved
Hide resolved
Let me know if you want suggestions about that. I do not think it would be out of scope: you look to be defining the same APIs which have long been proposed to clean up the way sensitive environment variables are handled in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really close, but we need to fix the use of the system property and the accidental serialization of Run
in the inner class of InterpolatedSecretsAction
.
/** | ||
* Calls {@link Step}s and other DSL objects. | ||
*/ | ||
@PersistIn(PROGRAM) | ||
public class DSL extends GroovyObjectSupport implements Serializable { | ||
@SuppressFBWarnings("MS_SHOULD_BE_FINAL") // Used to control warning behavior of unsafe Groovy interpolation | ||
@Restricted(NoExternalUse.class) | ||
public static String UNSAFE_GROOVY_INTERPOLATION = DSL.class.getName() + ".UNSAFE_GROOVY_INTERPOLATION"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now this is just a string, you need to look up the property with this name, either here or in the places where you want to check the property's value, and I would set a default value so that there are no NPEs if the property is unset:
public static String UNSAFE_GROOVY_INTERPOLATION = DSL.class.getName() + ".UNSAFE_GROOVY_INTERPOLATION"; | |
public static String UNSAFE_GROOVY_INTERPOLATION = System.getProperty(DSL.class.getName() + ".UNSAFE_GROOVY_INTERPOLATION", "warn"); |
public static class InterpolatedWarnings { | ||
final String stepName; | ||
final List<String> interpolatedVariables; | ||
final Run run; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since InterpolatedWarnings
is stored in a non-transient field in InterpolatedSecretsAction
, the run
field here must also be transient or else there will be issues. It looks like you do use the field in getStepSignature
, so I think you could either delete the field and make that method take the Run
as a parameter, make run
transient here and add methods to restore it when onLoad
and onAttached
are called in the outer class, or maybe you could make InterpolatedWarnings
a non-static inner class and use the transient run
field from the parent directly (but I'm not sure if @ExportedBean
works with non-static inner classes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3e54d36
made Run
transient. Confirmed stepSignature
in api
Map<String, ?> udArgs = ud.getArguments(); | ||
valueString = udArgs.entrySet().stream() | ||
.map(InterpolatedSecretsAction::argumentToString) | ||
.collect(Collectors.joining(", ", udName + "(", ")")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is right when using symbols, but when $class
is used the syntax is a list like [$class: udName, args...]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(A map, not a list.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in b52438d to handle $class
. Not sure what I should be using to test those changes. I just looked up some examples I found online.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically any extension without a @Symbol
.
src/main/java/org/jenkinsci/plugins/workflow/cps/view/InterpolatedSecretsAction.java
Show resolved
Hide resolved
src/test/java/org/jenkinsci/plugins/workflow/cps/view/InterpolatedSecretsActionTest.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/cps/view/InterpolatedSecretsAction.java
Show resolved
Hide resolved
src/main/java/org/jenkinsci/plugins/workflow/cps/view/InterpolatedSecretsAction.java
Outdated
Show resolved
Hide resolved
Remove @ symbol for UninstantiatedDescribable getStepSignature
List<InterpolatedSecretsAction.InterpolatedWarnings> warnings = reportAction.getWarnings(); | ||
MatcherAssert.assertThat(warnings.size(), is(1)); | ||
InterpolatedSecretsAction.InterpolatedWarnings stepWarning = warnings.get(0); | ||
MatcherAssert.assertThat(stepWarning.getStepSignature(), is("archiveArtifacts(delegate: archiveArtifacts(<anonymous>: ${PASSWORD}))")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test and a few others shows there are still a few issues with InterpolatedWarnings.getStepSignature
. It would be good to fix these, but I also think they are relatively unlikely to occur in practice with interpolated passwords, so maybe it would be better to merge this and handle that in a follow-up?
-
When using symbol-based syntax and the
UninstantiatedDescribable
only has one required parameter and there is only one provided argument (i.e.hasSoleRequiredParameter
returns true), the value can be provided directly instead of using thekey: value
syntax, meaning that we should never see things like<anonymous>:
in the output. In this particular case, fixing this issue (but not the other issue) would make the outputarchiveArtifacts(archiveArtifacts(${PASSWORD}))
, which is a bit closer to what was written in the Pipeline. -
Meta steps like
archiveArtifacts
are handled incorrectly. For example, in this case the outerarchiveArtifacts
is actually thestep
step, but generally the outer step is should be omitted so only the inner describable is shown. The direct meta step invocation inDSL.invokeDescribable
specifically exists to handle this case so users don't have to write usestep
explicitly. You can check for this case usingStepDescriptor.isMetaStep
(check that thatFlowNode
is an instance ofStepNode
and then callStepNode.getDescriptor
) along with making sure that the argument is for theDescribable
type corresponding to the required parameter of the meta step, maybe with something like this inInterpolatedWarnings.getStepSignature
:FlowNode node; // Maybe return this when looking up step arguments? if (node instanceof StepNode) { StepDescriptor descriptor = ((StepNode)node).getDescriptor(); if (descriptor.isMetaStep()) { DescribableParameter p = DescribableModel.of(descriptor.clazz).getFirstRequiredParameter(); if (p != null) { Object arg = ArgumentsAction.getResolvedArguments(f).get(p.getName()); return argumentToString(arg); } } } // handle as normal step using existing code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempted to address comments in 7d8672c
@@ -436,6 +437,7 @@ protected Object invokeDescribable(String symbol, Object _args) { | |||
NamedArgsAndClosure args = parseArgs(_args, metaStep!=null && metaStep.takesImplicitBlockArgument(), | |||
UninstantiatedDescribable.ANONYMOUS_KEY, singleArgumentOnly, new HashSet<>()); | |||
UninstantiatedDescribable ud = new UninstantiatedDescribable(symbol, null, args.namedArgs); | |||
ud.setModel(symbolModel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed to set the DescribableModel
in order for hasSoleRequiredArgument
to work.
if (arg instanceof UninstantiatedDescribable) { | ||
return argumentToString(arg); | ||
} else { | ||
return stepName + "(" + argumentToString(arg) + ")"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know in the case of ArchiveArtifacts
the inner argument was an UninstantiatedDescribable
with a symbol
so that it would print out the signature correctly. What I was less sure about, was if this was always going to be the case in these scenarios where there is an outer CoreStep
.
@@ -101,7 +102,6 @@ | |||
<dependency> | |||
<groupId>org.jenkins-ci.plugins</groupId> | |||
<artifactId>script-security</artifactId> | |||
<version>1.75</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current version of the BOM makes this redundant.
pom.xml
Outdated
@@ -93,6 +93,7 @@ | |||
<dependency> | |||
<groupId>org.jenkins-ci.plugins.workflow</groupId> | |||
<artifactId>workflow-support</artifactId> | |||
<version>3.6-rc759.3e4ba3120e7a</version> <!-- TODO: https://github.com/jenkinsci/workflow-support-plugin/pull/110 --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That PR needs to be released before we merge and release this one.
@@ -621,6 +626,22 @@ public void testReallyUnusualStepInstantiations() throws Exception { | |||
equalTo(NotStoredReason.UNSERIALIZABLE)); | |||
} | |||
|
|||
@Issue("JENKINS-47101") | |||
@Test public void passwordParametersSanitized() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@car-roll This is the new test I added for jenkinsci/workflow-support-plugin#110.
JENKINS-63254
PoC version 2 for groovy string interpolation interception.
This version does not require any modifications to core. Sensitive variables are registered in
BindingStep
via API fromEnvironmentExtender
. Scanning occurs inDSL.flattenGString()
.Requires:
jenkinsci/credentials-binding-plugin#105
jenkinsci/workflow-step-api-plugin#57
jenkins-infra/jenkins.io#3814
This PR also subsumes jenkinsci/workflow-durable-task-step-plugin#132
Strings that have been interpolated through
DSL.flattenGstring
are now tracked throughDSL.parseArgs()
and stored inNamedArgsAndClosure
. The interpolated strings are then compared against the list of sensitive variables fromEnvironmentExpander
via the api established in jenkinsci/workflow-step-api-plugin#57. Interpolated string warnings are displayed in the console output as well as the build page.Special note: changes were made to
DSL.invokeDescribable
, specifically when there is no associated meta-step for a symbol (i.e. when building up a nested argument for a step). Previously, the method would return anUninstantiatedDescribable
and it would be instantiated later inDSL.invokeStep
. Now, theUninstantiatedDescribable
is packed intoNamedArgsAndClosure
and later unpacked wheninvokeStep
callsparseArgs
.The changes in
ArgumentsActionImpl
address Jennkins-47101. It uses the newworkflow-step
API to identify sensitive variables. TheSAFE_ENVIRONMENT_VARIABLES
list has been removed and instead of checking for safe variables, we now check for sensitive variables. Sensitive variables are no longer dropped withNotStoredReason.MASKED_VALUE
, but are now replaced with the variables name (e.g. "echo s3cret" becomes "echo ${PASSWORD}"). This allows us to display the signature of the steps that contain interpolation warnings.Example screenshot here: