Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 6 additions & 36 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ properties([
])

def axes = [
platforms: ['linux', 'windows'],
platforms: ['linux'],
jdks: [21, 25],
]

Expand Down Expand Up @@ -128,7 +128,12 @@ axes.values().combinations {
}
withChecks(name: 'Tests', includeStage: true) {
realtimeJUnit(healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml') {
// Only use with replay, not with a commit
// sh 'curl -O https://home.markwaite.net/~mwaite/test-pom.patch && git apply test-pom.patch && git diff'
// mavenOptions.add(0, "-Dignore.dirt")
infra.runMaven(mavenOptions, jdk)
// Only use with replay, not with a commit
// sh 'git checkout -- */pom.xml'
if (isUnix()) {
sh 'git add . && git diff --exit-code HEAD'
}
Expand Down Expand Up @@ -216,41 +221,6 @@ axes.values().combinations {
}
}

def athAxes = [
platforms: ['linux'],
jdks: [21],
browsers: ['firefox'],
]
athAxes.values().combinations {
def (platform, jdk, browser) = it
builds["ath-${platform}-jdk${jdk}-${browser}"] = {
retry(conditions: [agent(), nonresumable()], count: 2) {
node('docker-highmem') {
// Just to be safe
deleteDir()
checkout scm

withChecks(name: 'Tests', includeStage: true) {
infra.withArtifactCachingProxy {
sh "bash ath.sh ${jdk} ${browser}"
}
junit testResults: 'target/ath-reports/TEST-*.xml', testDataPublishers: [[$class: 'AttachmentPublisher']]
}
/*
* Currently disabled, as the fact that this is a manually created subset will confuse Launchable,
* which expects this to be a full build. When we implement subsetting, this can be re-enabled using
* Launchable's subset rather than our own.
*/
/*
withCredentials([string(credentialsId: 'launchable-jenkins-acceptance-test-harness', variable: 'LAUNCHABLE_TOKEN')]) {
sh "launchable verify && launchable record tests --no-build --flavor platform=${platform} --flavor jdk=${jdk} --flavor browser=${browser} maven './target/ath-reports'"
}
*/
}
}
}
}

builds.failFast = failFast
parallel builds
infra.maybePublishIncrementals()
1 change: 1 addition & 0 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<mina-sshd.version>2.16.0</mina-sshd.version>
<!-- Filled in by jacoco-maven-plugin -->
<jacocoSurefireArgs />
<test>QuotedStringTokenizerTest</test>
</properties>

<dependencyManagement>
Expand Down
1 change: 1 addition & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ THE SOFTWARE.
<remoting.minimum.supported.version>3107.v665000b_51092</remoting.minimum.supported.version>
<!-- Filled in by jacoco-maven-plugin -->
<jacocoSurefireArgs />
<test>EnvVarsTest</test>
</properties>

<dependencyManagement>
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/jenkins/security/csp/CspHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package jenkins.security.csp;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;

Expand All @@ -35,14 +36,16 @@
@Restricted(Beta.class)
public enum CspHeader {
ContentSecurityPolicy("Content-Security-Policy"),
ContentSecurityPolicyReportOnly("Content-Security-Policy-Report-Only");
ContentSecurityPolicyReportOnly("Content-Security-Policy-Report-Only"),
None(null);

private final String headerName;

CspHeader(String headerName) {
this.headerName = headerName;
}

@CheckForNull
public String getHeaderName() {
return headerName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,11 @@ public String getReportingEndpointsHeaderValue(HttpServletRequest req) {
}

/**
* Determines the name of the HTTP header to set.
* Determines the name of the HTTP header to set, or {@code null} if none.
*
* @return the name of the HTTP header to set.
*/
@CheckForNull
public String getContentSecurityPolicyHeaderName() {
final Optional<CspHeaderDecider> decider = CspHeaderDecider.getCurrentDecider();
if (decider.isPresent()) {
Expand Down
20 changes: 12 additions & 8 deletions core/src/main/java/jenkins/security/csp/impl/CspFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@

CspDecorator cspDecorator = ExtensionList.lookupSingleton(CspDecorator.class);
final String headerName = cspDecorator.getContentSecurityPolicyHeaderName();
final boolean headerShouldBeSet = headerName != null;

Check warning on line 64 in core/src/main/java/jenkins/security/csp/impl/CspFilter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 64 is only partially covered, one branch is missing

// This is the preliminary value outside Stapler request handling (and providing a context object)
final String headerValue = cspDecorator.getContentSecurityPolicyHeaderValue(req);

final boolean isResourceRequest = ResourceDomainConfiguration.isResourceRequest(req);
if (!isResourceRequest) {

if (headerShouldBeSet && !isResourceRequest) {

Check warning on line 71 in core/src/main/java/jenkins/security/csp/impl/CspFilter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 71 is only partially covered, 2 branches are missing
// The Filter/Decorator approach needs us to "set" headers rather than "add", so no additional endpoints are supported at the moment.
final String reportingEndpoints = cspDecorator.getReportingEndpointsHeaderValue(req);
if (reportingEndpoints != null) {
Expand All @@ -78,14 +80,16 @@
try {
chain.doFilter(req, rsp);
} finally {
try {
final String actualHeader = rsp.getHeader(headerName);
if (!isResourceRequest && hasUnexpectedDifference(headerValue, actualHeader)) {
LOGGER.log(Level.FINE, "CSP header has unexpected differences: Expected '" + headerValue + "' but got '" + actualHeader + "'");
if (headerShouldBeSet) {

Check warning on line 83 in core/src/main/java/jenkins/security/csp/impl/CspFilter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 83 is only partially covered, one branch is missing
try {
final String actualHeader = rsp.getHeader(headerName);
if (!isResourceRequest && hasUnexpectedDifference(headerValue, actualHeader)) {
LOGGER.log(Level.FINE, "CSP header has unexpected differences: Expected '" + headerValue + "' but got '" + actualHeader + "'");
}
} catch (RuntimeException e) {
// Be defensive just in case
LOGGER.log(Level.FINER, "Error checking CSP header after request processing", e);
}
} catch (RuntimeException e) {
// Be defensive just in case
LOGGER.log(Level.FINER, "Error checking CSP header after request processing", e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
package jenkins.security.csp.impl;

import hudson.Extension;
import hudson.Util;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -50,7 +52,8 @@
final String systemProperty = SystemProperties.getString(SYSTEM_PROPERTY_NAME);
if (systemProperty != null) {
LOGGER.log(Level.FINEST, "Using system property: {0}", new Object[]{ systemProperty });
return Arrays.stream(CspHeader.values()).filter(h -> h.getHeaderName().equals(systemProperty)).findFirst();
final String expected = Util.fixEmptyAndTrim(systemProperty);
return Arrays.stream(CspHeader.values()).filter(h -> Objects.equals(expected, h.getHeaderName())).findFirst();

Check warning on line 56 in core/src/main/java/jenkins/security/csp/impl/SystemPropertyHeaderDecider.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 55-56 are not covered by tests
}
return Optional.empty();
}
Expand Down
105 changes: 105 additions & 0 deletions core/src/main/java/jenkins/telemetry/impl/ContentSecurityPolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* The MIT License
*
* Copyright (c) 2025, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.telemetry.impl;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
import java.time.LocalDate;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import jenkins.security.csp.AdvancedConfigurationDescriptor;
import jenkins.security.csp.Contributor;
import jenkins.security.csp.CspBuilder;
import jenkins.security.csp.CspHeader;
import jenkins.security.csp.CspHeaderDecider;
import jenkins.security.csp.impl.CspConfiguration;
import jenkins.telemetry.Telemetry;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* Collect information about Content Security Policy configuration.
*
*/
@Restricted(NoExternalUse.class)
@Extension
public class ContentSecurityPolicy extends Telemetry {

Check warning on line 55 in core/src/main/java/jenkins/telemetry/impl/ContentSecurityPolicy.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 55 is not covered by tests

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

@NonNull
@Override
public String getDisplayName() {
return "Content Security Policy";
}

@NonNull
@Override
public LocalDate getStart() {
return LocalDate.of(2025, 12, 1);
}

@NonNull
@Override
public LocalDate getEnd() {
return LocalDate.of(2026, 6, 1);
}

@Override
public JSONObject createContent() {
final JSONObject data = new JSONObject();
data.put("enforce", ExtensionList.lookupSingleton(CspConfiguration.class).isEnforce());
final Optional<CspHeaderDecider> decider = CspHeaderDecider.getCurrentDecider();
data.put("decider", decider.map(Object::getClass).map(Class::getName).orElse(null));
data.put("header", decider.map(CspHeaderDecider::decide).filter(Optional::isPresent).map(Optional::get).map(CspHeader::getHeaderName).orElse(null));

Set<String> contributors = new TreeSet<>();
ExtensionList.lookup(Contributor.class).stream().map(Contributor::getClass).map(Class::getName).forEach(contributors::add);
data.put("contributors", contributors);

Set<String> configurations = new TreeSet<>();
ExtensionList.lookup(AdvancedConfigurationDescriptor.class).stream().map(AdvancedConfigurationDescriptor::getClass).map(Class::getName).forEach(configurations::add);
data.put("configurations", configurations);

try {
Map<String, Map<String, Integer>> directivesSize = new CspBuilder().withDefaultContributions().getMergedDirectives().stream()
.map(d -> Map.entry(d.name(), Map.of("entries", d.values().size(), "chars", String.join(" ", d.values()).length())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
data.put("directivesSize", directivesSize);
} catch (RuntimeException ex) {
LOGGER.log(Level.FINE, "Error during directive processing", ex);
}

data.put("components", buildComponentInformation());
return data;

Check warning on line 103 in core/src/main/java/jenkins/telemetry/impl/ContentSecurityPolicy.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 62-103 are not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@
@NonNull
@Override
public LocalDate getStart() {
return LocalDate.of(2023, 12, 17);
return LocalDate.of(2026, 1, 4);
}

@NonNull
@Override
public LocalDate getEnd() {
return LocalDate.of(2024, 4, 1);
return LocalDate.of(2026, 4, 1);

Check warning on line 79 in core/src/main/java/jenkins/telemetry/impl/JavaSystemProperties.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 73-79 are not covered by tests
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/resources/hudson/model/Run/console-log.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</j:when>
<!-- output is completed now. -->
<j:otherwise>
<pre class="console-output">
<pre id="out" class="console-output">
<st:getOutput var="output" />
<j:whitespace>${it.writeLogTo(offset,output)}</j:whitespace>
</pre>
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/resources/hudson/slaves/Cloud/sidepanel.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ THE SOFTWARE.
<l:header />
<l:side-panel>
<l:tasks>
<l:task contextMenu="false" href="." icon="symbol-computer" title="${%Status}"/>
<l:task href="configure" icon="symbol-settings"
<j:set var="url" value="${h.getNearestAncestorUrl(request2,it)}"/>
<l:task contextMenu="false" href="${url}/" icon="symbol-computer" title="${%Status}"/>
<l:task href="${url}/configure" icon="symbol-settings"
title="${app.hasPermission(app.ADMINISTER) ? '%Configure' : '%View Configuration'}"/>
<l:delete permission="${app.ADMINISTER}" title="${%Delete Cloud}" message="${%delete.cloud(it.displayName)}"/>
<l:delete permission="${app.ADMINISTER}" title="${%Delete Cloud}" message="${%delete.cloud(it.displayName)}" urlPrefix="${url}"/>
<t:actions />
</l:tasks>
<j:forEach var="action" items="${it.allActions}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<st:setHeader name="${it.getContentSecurityPolicyHeaderName()}" value="${it.getContentSecurityPolicyHeaderValue(request2)}" />
<st:setHeader name="Reporting-Endpoints" value="${it.getReportingEndpointsHeaderValue(request2)}" />
<j:set var="cspHeaderName" value="${it.getContentSecurityPolicyHeaderName()}"/>
<j:if test="${cspHeaderName != null}">
<st:setHeader name="${cspHeaderName}" value="${it.getContentSecurityPolicyHeaderValue(request2)}" />
<st:setHeader name="Reporting-Endpoints" value="${it.getReportingEndpointsHeaderValue(request2)}" />
</j:if>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,14 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:description>${%blurb(it.headerName)}</f:description>
<f:description>
<j:choose>
<j:when test="${it.headerName != null}">
${%blurb(it.headerName)}
</j:when>
<j:otherwise>
${%blurbUnset}
</j:otherwise>
</j:choose>
</f:description>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
blurb = Content Security Policy is configured to use the HTTP header <code>{0}</code> based on the system property <code>jenkins.security.csp.CspHeader.headerName</code>. \
It cannot be configured through the UI while this system property specifies a header name.
It cannot be configured through the UI while this system property is set.
blurbUnset = Content Security Policy is disabled based on the system property <code>jenkins.security.csp.CspHeader.headerName</code>. \
It cannot be configured through the UI while this system property is set.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core">
This trial collects basic information about the current configuration of Content Security Policy (CSP):
<ul>
<li>Whether the user-facing configuration option is set to enforce CSP</li>
<li>The active <code>CspHeaderDecider</code>, which roughly corresponds to how Jenkins is run and whether system properties related to CSP are set</li>
<li>The current Content Security Policy header (<code>Content-Security-Policy</code> or <code>Content-Security-Policy-Report-Only</code>)</li>
<li>Which <code>Contributor</code>s are known, i.e., extensions that contribute to the CSP rules</li>
<li>Which <code>AdvancedConfiguration</code>s are known, i.e., extensions that add CSP configuration options</li>
<li>Which directives (e.g., <code>img-src</code>) are set, and the length in bytes of their values</li>
</ul>

<p>
<strong>This trial does not collect the actual CSP rules.</strong>
</p>
<p>
Additionally this trial collects the list of installed plugins, their version, and the version of Jenkins.
This data will be used to understand how widely CSP protection is used.
</p>
</j:jelly>
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ THE SOFTWARE.
<spotless.check.skip>false</spotless.check.skip>
<ban-junit4-imports.skip>false</ban-junit4-imports.skip>
<!-- Make sure to keep the jetty-ee9-maven-plugin version in war/pom.xml in sync with the Jetty release in Winstone: -->
<winstone.version>8.1023.v8b_42b_1b_79b_f7</winstone.version>
<winstone.version>8.1029.vd3071f6b_5988</winstone.version>
<node.version>24.11.1</node.version>
</properties>

Expand Down
2 changes: 2 additions & 0 deletions test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ THE SOFTWARE.

<!-- Filled in by maven-hpi-plugin with "-javaagent:/path/to/mockito-core-<version>.jar" -->
<jenkins.javaAgent />

<test>hudson.AboutJenkinsTest</test>
</properties>

<dependencyManagement>
Expand Down
Loading
Loading