Skip to content

Commit

Permalink
Merge pull request #312 from jenkinsci/atual/fix-creds
Browse files Browse the repository at this point in the history
Fix credentials not masked when `DurableTaskStep.USE_WATCHING` is true
  • Loading branch information
alextu authored Jun 26, 2023
2 parents 639ed0d + 8d4fd5b commit 28e4842
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
package hudson.plugins.gradle.injection;

import hudson.Extension;
import hudson.model.Queue;
import hudson.model.Run;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator;
import org.jenkinsci.plugins.workflow.steps.DynamicContext;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;

@SuppressWarnings("unused")
@Extension
public class GradleEnterpriseExceptionTaskListenerDecoratorFactory extends DynamicContext.Typed<TaskListenerDecorator> {
public class GradleEnterpriseExceptionTaskListenerDecoratorFactory implements TaskListenerDecorator.Factory {

@Nonnull
@Override
protected Class<TaskListenerDecorator> type() {
return TaskListenerDecorator.class;
}
private static final Logger LOGGER = Logger.getLogger(GradleEnterpriseExceptionTaskListenerDecoratorFactory.class.getName());

@CheckForNull
@Override
protected TaskListenerDecorator get(DelegatedContext context) throws IOException, InterruptedException {
Run run = context.get(Run.class);
return new GradleEnterpriseExceptionTaskListenerDecorator(run);
@CheckForNull
public TaskListenerDecorator of(@Nonnull FlowExecutionOwner owner) {
if (!isBuildAgentErrorsEnabled()) {
return null;
}
try {
Queue.Executable executable = owner.getExecutable();
if (executable instanceof WorkflowRun) {
return new GradleEnterpriseExceptionTaskListenerDecorator((WorkflowRun) executable);
}
} catch (IOException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
return null;
}

@SuppressWarnings("rawtypes")
public static class GradleEnterpriseExceptionTaskListenerDecorator extends TaskListenerDecorator {
public static class GradleEnterpriseExceptionTaskListenerDecorator extends TaskListenerDecorator implements Serializable {
private static final long serialVersionUID = 1L;

private final transient Run run;
Expand All @@ -40,12 +51,16 @@ public GradleEnterpriseExceptionTaskListenerDecorator(Run run) {
@Nonnull
@Override
public OutputStream decorate(@Nonnull OutputStream logger) {
InjectionConfig injectionConfig = InjectionConfig.get();
if (injectionConfig.isEnabled() && injectionConfig.isCheckForBuildAgentErrors()) {
if (isBuildAgentErrorsEnabled()) {
return new GradleEnterpriseExceptionLogProcessor(logger, run);
}
return logger;
}
}

static boolean isBuildAgentErrorsEnabled() {
InjectionConfig injectionConfig = InjectionConfig.get();
return injectionConfig.isEnabled() && injectionConfig.isCheckForBuildAgentErrors();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package hudson.plugins.gradle

import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.domains.Domain
import hudson.slaves.DumbSlave
import hudson.util.Secret
import org.jenkinsci.plugins.plaincredentials.StringCredentials
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl
import org.junit.Rule
import org.junit.rules.RuleChain

Expand All @@ -9,6 +16,7 @@ import org.junit.rules.RuleChain
abstract class BaseGradleIntegrationTest extends AbstractIntegrationTest {

public final GradleInstallationRule gradleInstallationRule = new GradleInstallationRule(j)
static final String GRADLE_ENTERPRISE_PLUGIN_VERSION = '3.13.4'

@Rule
public final RuleChain rules = RuleChain.outerRule(noSpaceInTmpDirs).around(j).around(gradleInstallationRule)
Expand All @@ -20,4 +28,35 @@ abstract class BaseGradleIntegrationTest extends AbstractIntegrationTest {
switches : '--no-daemon'
]
}

def enableBuildInjection(DumbSlave slave,
String gradleVersion,
URI repositoryAddress = null,
Boolean globalAutoInjectionCheckEnabled = false) {
withGlobalEnvVars {
put("JENKINSGRADLEPLUGIN_BUILD_SCAN_OVERRIDE_GRADLE_HOME", getGradleHome(slave, gradleVersion))
put('GRADLE_OPTS', '-Dscan.uploadInBackground=false')
if (globalAutoInjectionCheckEnabled) {
put("JENKINSGRADLEPLUGIN_GLOBAL_AUTO_INJECTION_CHECK", "true")
}
}

withInjectionConfig {
enabled = true
gradlePluginVersion = GRADLE_ENTERPRISE_PLUGIN_VERSION
gradlePluginRepositoryUrl = repositoryAddress?.toString()
}

restartSlave(slave)
}

static String getGradleHome(DumbSlave slave, String gradleVersion) {
return "${slave.getRemoteFS()}/tools/hudson.plugins.gradle.GradleInstallation/${gradleVersion}"
}

def registerCredentials(String id, String secret) {
StringCredentials creds = new StringCredentialsImpl(CredentialsScope.GLOBAL, id, null, Secret.fromString(secret))
CredentialsProvider.lookupStores(j.jenkins).iterator().next().addCredentials(Domain.global(), creds)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import spock.lang.Unroll
@Unroll
class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest {

private static final String GRADLE_ENTERPRISE_PLUGIN_VERSION = '3.13.2'
private static final String CCUD_PLUGIN_VERSION = '1.8.1'

private static final String MSG_INIT_SCRIPT_APPLIED = "Connection to Gradle Enterprise: http://foo.com"
Expand Down Expand Up @@ -119,24 +118,7 @@ class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest

DumbSlave agent = createSlave()

def pipelineJob = j.createProject(WorkflowJob)

pipelineJob.setDefinition(new CpsFlowDefinition("""
stage('Build') {
node('foo') {
withGradle {
def gradleHome = tool name: '${gradleInstallationRule.gradleVersion}', type: 'gradle'
writeFile file: 'settings.gradle', text: ''
writeFile file: 'build.gradle', text: ""
if (isUnix()) {
sh "'\${gradleHome}/bin/gradle' help --no-daemon --console=plain -Dcom.gradle.scan.trigger-synthetic-error=true"
} else {
bat(/"\${gradleHome}\\bin\\gradle.bat" help --no-daemon --console=plain -Dcom.gradle.scan.trigger-synthetic-error=true/)
}
}
}
}
""", false))
def pipelineJob = GradleSnippets.pipelineJobWithError(j, gradleInstallationRule)

when:
// first build to download Gradle
Expand All @@ -160,6 +142,38 @@ class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest
}
}

def "credentials are always masked in logs"() {
given:
def secret = 'confidential'
registerCredentials('my-creds', secret)

def gradleVersion = '8.1.1'
gradleInstallationRule.gradleVersion = gradleVersion
gradleInstallationRule.addInstallation()

DumbSlave agent = createSlave()
def pipelineJob = GradleSnippets.pipelineJobWithCredentials(j)

when:
// first build to download Gradle
def firstRun = j.buildAndAssertSuccess(pipelineJob)

then:
j.assertLogContains('password=****', firstRun)
j.assertLogNotContains(secret, firstRun)

when:
enableBuildInjection(agent, gradleVersion)
withInjectionConfig {
checkForBuildAgentErrors = true
}
def secondRun = j.buildAndAssertSuccess(pipelineJob)

then:
j.assertLogContains('password=****', secondRun)
j.assertLogNotContains(secret, secondRun)
}

def 'skips injection if the agent is offline'() {
given:
def gradleVersion = '8.0.2'
Expand Down Expand Up @@ -596,7 +610,7 @@ class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest
then:
j.assertLogContains(MSG_INIT_SCRIPT_APPLIED, secondRun)
j.assertLogContains("accessKey=foo.com=secret", secondRun)
j.assertLogContains("The response from http://foo.com/scans/publish/gradle/3.13.2/token was not from Gradle Enterprise.", secondRun)
j.assertLogContains("The response from http://foo.com/scans/publish/gradle/3.13.4/token was not from Gradle Enterprise.", secondRun)
j.assertLogNotContains(INVALID_ACCESS_KEY_FORMAT_ERROR, secondRun)

}
Expand Down Expand Up @@ -632,7 +646,7 @@ class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest

then:
j.assertLogContains(MSG_INIT_SCRIPT_APPLIED, secondRun)
j.assertLogContains("The response from http://foo.com/scans/publish/gradle/3.13.2/token was not from Gradle Enterprise.", secondRun)
j.assertLogContains("The response from http://foo.com/scans/publish/gradle/3.13.4/token was not from Gradle Enterprise.", secondRun)

and:
StringUtils.countMatches(JenkinsRule.getLog(secondRun), INVALID_ACCESS_KEY_FORMAT_ERROR) == 1
Expand Down Expand Up @@ -666,7 +680,7 @@ class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest
with(agent.getNodeProperty(EnvironmentVariablesNodeProperty.class)) {
with(getEnvVars()) {
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_URL") == "http://localhost"
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_PLUGIN_VERSION") == '3.13.2'
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_PLUGIN_VERSION") == '3.13.4'
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER") == null
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ENFORCE_URL") == null
get("JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_URL") == null
Expand Down Expand Up @@ -731,7 +745,7 @@ class BuildScanInjectionGradleIntegrationTest extends BaseGradleIntegrationTest
with(getEnvVars()) {
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_URL") == "http://localhost"
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ENFORCE_URL") == "true"
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_PLUGIN_VERSION") == '3.13.2'
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_PLUGIN_VERSION") == '3.13.4'
get("JENKINSGRADLEPLUGIN_GRADLE_ENTERPRISE_ALLOW_UNTRUSTED_SERVER") == "true"
get("JENKINSGRADLEPLUGIN_GRADLE_PLUGIN_REPOSITORY_URL") == "http://localhost/repository"
get("JENKINSGRADLEPLUGIN_CCUD_PLUGIN_VERSION") == "1.8.1"
Expand Down Expand Up @@ -881,31 +895,6 @@ task hello {
return new File("${getGradleHome(agent, gradleVersion)}/init.d/init-build-scan.gradle")
}

private static String getGradleHome(DumbSlave slave, String gradleVersion) {
return "${slave.getRemoteFS()}/tools/hudson.plugins.gradle.GradleInstallation/${gradleVersion}"
}

private void enableBuildInjection(DumbSlave slave,
String gradleVersion,
URI repositoryAddress = null,
Boolean globalAutoInjectionCheckEnabled = false) {
withGlobalEnvVars {
put("JENKINSGRADLEPLUGIN_BUILD_SCAN_OVERRIDE_GRADLE_HOME", getGradleHome(slave, gradleVersion))
put('GRADLE_OPTS', '-Dscan.uploadInBackground=false')
if (globalAutoInjectionCheckEnabled) {
put("JENKINSGRADLEPLUGIN_GLOBAL_AUTO_INJECTION_CHECK", "true")
}
}

withInjectionConfig {
enabled = true
gradlePluginVersion = GRADLE_ENTERPRISE_PLUGIN_VERSION
gradlePluginRepositoryUrl = repositoryAddress?.toString()
}

restartSlave(slave)
}

private void disableBuildInjection(DumbSlave slave) {
withInjectionConfig {
enabled = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package hudson.plugins.gradle.injection

import hudson.plugins.gradle.BaseGradleIntegrationTest
import hudson.plugins.gradle.BuildScanAction
import hudson.slaves.DumbSlave
import org.junit.Rule
import org.junit.rules.TestRule
import org.jvnet.hudson.test.FlagRule
import spock.lang.PendingFeature
import spock.lang.Unroll

@Unroll
class BuildScanInjectionGradleWithDurableTaskStepUseWatchingIntegrationTest extends BaseGradleIntegrationTest {

private static final String MSG_INIT_SCRIPT_APPLIED = "Connection to Gradle Enterprise: http://foo.com"

@Rule
public final TestRule durableTaskStepRule = FlagRule.systemProperty("org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep.USE_WATCHING", "true")

@PendingFeature
def "cannot capture build agent errors in pipeline build if DurableTaskStep.USE_WATCHING=true"() {
given:
def gradleVersion = '8.1.1'
gradleInstallationRule.gradleVersion = gradleVersion
gradleInstallationRule.addInstallation()

DumbSlave agent = createSlave('foo')
def pipelineJob = GradleSnippets.pipelineJobWithError(j, gradleInstallationRule)

when:
// first build to download Gradle
def firstRun = j.buildAndAssertSuccess(pipelineJob)

then:
j.assertLogNotContains(MSG_INIT_SCRIPT_APPLIED, firstRun)

when:
enableBuildInjection(agent, gradleVersion)
withInjectionConfig {
checkForBuildAgentErrors = true
}
def secondRun = buildAndAssertFailure(pipelineJob)

then:
secondRun.getAction(BuildScanAction) != null
}

def "credentials are always masked in logs"() {
given:
def secret = 'confidential'
registerCredentials('my-creds', secret)

def gradleVersion = '8.1.1'
gradleInstallationRule.gradleVersion = gradleVersion
gradleInstallationRule.addInstallation()

DumbSlave agent = createSlave('foo')
def pipelineJob = GradleSnippets.pipelineJobWithCredentials(j)

when:
// first build to download Gradle
def firstRun = j.buildAndAssertSuccess(pipelineJob)

then:
j.assertLogContains('password=****', firstRun)
j.assertLogNotContains(secret, firstRun)

when:
enableBuildInjection(agent, gradleVersion)
withInjectionConfig {
checkForBuildAgentErrors = true
}
def secondRun = j.buildAndAssertSuccess(pipelineJob)

then:
j.assertLogContains('password=****', secondRun)
j.assertLogNotContains(secret, secondRun)
}

}
Loading

0 comments on commit 28e4842

Please sign in to comment.