提交 67f33850 编写于 作者: J Jesse Glick

Merge branch 'security' into security-rc

Conflicts:
	core/src/main/java/hudson/model/DownloadService.java
	core/src/main/java/hudson/model/UpdateSite.java
	core/src/main/java/jenkins/model/DownloadSettings.java
	core/src/main/java/jenkins/util/JSONSignatureValidator.java
	core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly
	core/src/main/resources/jenkins/model/DownloadSettings/config.groovy
	core/src/main/resources/jenkins/model/DownloadSettings/help-useBrowser.html
	test/src/test/java/hudson/model/DownloadServiceTest.java
......@@ -66,7 +66,7 @@ public class DownloadService extends PageDecorator {
* Builds up an HTML fragment that starts all the download jobs.
*/
public String generateFragment() {
if (!DownloadSettings.get().isUseBrowser()) {
if (!DownloadSettings.usePostBack()) {
return "";
}
if (neverUpdate) return "";
......@@ -170,6 +170,30 @@ public class DownloadService extends PageDecorator {
}
}
/**
* Loads JSON from a JSON-with-{@code postMessage} URL.
* @param src a URL to a JSON HTML file (typically including {@code id} and {@code version} query parameters)
* @return the embedded JSON text
* @throws IOException if either downloading or processing failed
*/
@Restricted(NoExternalUse.class)
public static String loadJSONHTML(URL src) throws IOException {
InputStream is = ProxyConfiguration.open(src).getInputStream();
try {
String jsonp = IOUtils.toString(is, "UTF-8");
String preamble = "window.parent.postMessage(JSON.stringify(";
int start = jsonp.indexOf(preamble);
int end = jsonp.lastIndexOf("),'*');");
if (start >= 0 && end > start) {
return jsonp.substring(start + preamble.length(), end).trim();
} else {
throw new IOException("Could not find JSON in " + src);
}
} finally {
is.close();
}
}
/**
* Represents a periodically updated JSON data file obtained from a remote URL.
*
......@@ -283,9 +307,7 @@ public class DownloadService extends PageDecorator {
* This is where the browser sends us the data.
*/
public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException {
if (!DownloadSettings.get().isUseBrowser()) {
throw new IOException("not allowed");
}
DownloadSettings.checkPostBackAccess();
long dataTimestamp = System.currentTimeMillis();
due = dataTimestamp+getInterval(); // success or fail, don't try too often
......@@ -317,7 +339,7 @@ public class DownloadService extends PageDecorator {
@Restricted(NoExternalUse.class)
public FormValidation updateNow() throws IOException {
return load(loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
return load(loadJSONHTML(new URL(getUrl() + ".html?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
}
/**
......
......@@ -174,9 +174,7 @@ public class UpdateSite {
* This is the endpoint that receives the update center data file from the browser.
*/
public FormValidation doPostBack(StaplerRequest req) throws IOException, GeneralSecurityException {
if (!DownloadSettings.get().isUseBrowser()) {
throw new IOException("not allowed");
}
DownloadSettings.checkPostBackAccess();
return updateData(IOUtils.toString(req.getInputStream(),"UTF-8"), true);
}
......
......@@ -25,6 +25,8 @@
package jenkins.model;
import hudson.Extension;
import hudson.Main;
import hudson.model.AdministrativeMonitor;
import hudson.model.AsyncPeriodicWork;
import hudson.model.DownloadService;
import hudson.model.TaskListener;
......@@ -32,6 +34,7 @@ import hudson.model.UpdateSite;
import hudson.util.FormValidation;
import java.io.IOException;
import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
......@@ -49,7 +52,7 @@ import org.kohsuke.stapler.StaplerRequest;
return Jenkins.getInstance().getInjector().getInstance(DownloadSettings.class);
}
private boolean useBrowser = true; // historical default, not necessarily recommended
private boolean useBrowser = false;
public DownloadSettings() {
load();
......@@ -69,6 +72,21 @@ import org.kohsuke.stapler.StaplerRequest;
save();
}
@Override public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
}
public static boolean usePostBack() {
return get().isUseBrowser() && Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER);
}
public static void checkPostBackAccess() throws AccessDeniedException {
if (!get().isUseBrowser()) {
throw new AccessDeniedException("browser-based download disabled");
}
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
}
@Extension public static final class DailyCheck extends AsyncPeriodicWork {
public DailyCheck() {
......@@ -79,10 +97,24 @@ import org.kohsuke.stapler.StaplerRequest;
return DAY;
}
@Override public long getInitialDelay() {
return Main.isUnitTest ? DAY : 0;
}
@Override protected void execute(TaskListener listener) throws IOException, InterruptedException {
if (get().isUseBrowser()) {
return;
}
boolean due = false;
for (UpdateSite site : Jenkins.getInstance().getUpdateCenter().getSites()) {
if (site.isDue()) {
due = true;
break;
}
}
if (!due) {
return;
}
HttpResponse rsp = Jenkins.getInstance().getPluginManager().doCheckUpdatesServer();
if (rsp instanceof FormValidation) {
listener.error(((FormValidation) rsp).renderHtml());
......@@ -91,4 +123,12 @@ import org.kohsuke.stapler.StaplerRequest;
}
@Extension public static final class Warning extends AdministrativeMonitor {
@Override public boolean isActivated() {
return DownloadSettings.get().isUseBrowser();
}
}
}
......@@ -135,6 +135,9 @@ public class JSONSignatureValidator {
// which isn't useful at all
Set<TrustAnchor> anchors = new HashSet<TrustAnchor>(); // CertificateUtil.getDefaultRootCAs();
Jenkins j = Jenkins.getInstance();
if (j == null) {
return anchors;
}
for (String cert : (Set<String>) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) {
if (cert.endsWith("/") || cert.endsWith(".txt")) {
continue; // skip directories also any text files that are meant to be documentation
......
......@@ -31,8 +31,8 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:invokeStatic var="ds" className="jenkins.model.DownloadSettings" method="get"/>
<j:if test="${ds.useBrowser}">
<j:invokeStatic var="enabled" className="jenkins.model.DownloadSettings" method="usePostBack"/>
<j:if test="${enabled}">
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.due or forcedUpdateCheck}">
<script>
......
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright 2015 Jesse Glick.
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<div class="warning">
<form method="post" action="${rootURL}/${it.url}/disable">
<j:out value="${%blurb(rootURL)}"/>
<f:submit value="${%Dismiss}"/>
</form>
</div>
</j:jelly>
# The MIT License
#
# Copyright 2015 Jesse Glick.
#
# 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.
blurb=\
You currently are using browser-based download to retrieve metadata for Jenkins plugins and tools. \
This has reliability issues and is not considered fully secure. \
Consider <a href="{0}/configureSecurity">switching to server-based download</a>.
......@@ -2,8 +2,7 @@ package jenkins.security.DownloadSettings
def f = namespace(lib.FormTagLib)
f.section(title: _("Download Preferences")) {
f.entry(title: _("Use Browser"), field: "useBrowser") {
f.checkbox()
}
// TODO avoid indentation somehow
f.entry(field: "useBrowser") {
f.checkbox(title: _("Use browser for metadata download"))
}
<div>
<p>
Check to force the user’s browser to download metadata (lists of available plugins, tools etc.)
rather than Jenkins itself doing it.
Actual file downloads (plugins, tools) will still happen from Jenkins itself,
but this can be used to at least <em>see</em> new metadata when Jenkins cannot access the Internet
(but your own browser can, perhaps using some special proxy that Jenkins is not configured to use).
</div>
</p>
<p>
Use of browser mode is discouraged.
It has robustness problems and has been limited to users with Overall/Administer.
</p>
/*
* The MIT License
*
* Copyright 2015 Jesse Glick.
*
* 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 hudson.model;
import hudson.util.FormValidation;
import java.net.URL;
import java.util.Set;
import java.util.TreeSet;
import net.sf.json.JSONObject;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.WithoutJenkins;
//@Issue("SECURITY-163")
public class DownloadService2Test {
@Rule public JenkinsRule r = new JenkinsRule();
@Test public void updateNow() throws Exception {
for (DownloadService.Downloadable d : DownloadService.Downloadable.all()) {
FormValidation v = d.updateNow();
assertEquals(v.toString(), FormValidation.Kind.OK, v.kind);
}
}
@WithoutJenkins
@Test public void loadJSONHTML() throws Exception {
assertRoots("[list, signature]", "hudson.tasks.Maven.MavenInstaller.json.html"); // format used by most tools
assertRoots("[data, signature, version]", "hudson.tools.JDKInstaller.json.html"); // anomalous format
}
private static void assertRoots(String expected, String file) throws Exception {
URL resource = DownloadService2Test.class.getResource(file);
assertNotNull(file, resource);
JSONObject json = JSONObject.fromObject(DownloadService.loadJSONHTML(resource));
@SuppressWarnings("unchecked") Set<String> keySet = json.keySet();
assertEquals(expected, new TreeSet<String>(keySet).toString());
}
}
......@@ -5,6 +5,7 @@ import java.io.IOException;
import java.net.URL;
import java.util.Set;
import java.util.TreeSet;
import jenkins.model.DownloadSettings;
import net.sf.json.JSONObject;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.HudsonTestCase;
......@@ -30,6 +31,7 @@ public class DownloadServiceTest extends HudsonTestCase {
// to bypass the URL restriction, we'll trigger downloadService.download ourselves
job = new Downloadable("test", "UNUSED");
Downloadable.all().add(job);
DownloadSettings.get().setUseBrowser(true);
}
@Issue("JENKINS-5536")
......
<!DOCTYPE html><html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><script>window.onload = function () { window.parent.postMessage(JSON.stringify(
{
"list": [
{
"id": "3.2.2",
"name": "3.2.2",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.2.2-bin.zip"
},
{
"id": "3.2.1",
"name": "3.2.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.2.1-bin.zip"
},
{
"id": "3.1.1",
"name": "3.1.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.1.1-bin.zip"
},
{
"id": "3.1.0",
"name": "3.1.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.1.0-bin.zip"
},
{
"id": "3.0.5",
"name": "3.0.5",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.5-bin.zip"
},
{
"id": "3.0.4",
"name": "3.0.4",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.4-bin.zip"
},
{
"id": "3.0.3",
"name": "3.0.3",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.3-bin.zip"
},
{
"id": "3.0.2",
"name": "3.0.2",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.2-bin.zip"
},
{
"id": "3.0.1",
"name": "3.0.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.1-bin.zip"
},
{
"id": "3.0",
"name": "3.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0-bin.zip"
},
{
"id": "2.2.1",
"name": "2.2.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.2.1-bin.zip"
},
{
"id": "2.2.0",
"name": "2.2.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.2.0-bin.zip"
},
{
"id": "2.1.0",
"name": "2.1.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.1.0-bin.zip"
},
{
"id": "2.0.11",
"name": "2.0.11",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.11-bin.zip"
},
{
"id": "2.0.10",
"name": "2.0.10",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.10-bin.zip"
},
{
"id": "2.0.9",
"name": "2.0.9",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.9-bin.zip"
},
{
"id": "2.0.8",
"name": "2.0.8",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.8-bin.zip"
},
{
"id": "2.0.7",
"name": "2.0.7",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.7-bin.zip"
},
{
"id": "2.0.6",
"name": "2.0.6",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.6-bin.zip"
},
{
"id": "2.0.5",
"name": "2.0.5",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.5-bin.zip"
},
{
"id": "2.0.4",
"name": "2.0.4",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.4-bin.zip"
},
{
"id": "2.0.3",
"name": "2.0.3",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.3-bin.zip"
},
{
"id": "2.0.2",
"name": "2.0.2",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.2-bin.zip"
},
{
"id": "2.0.1",
"name": "2.0.1",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.1-bin.zip"
},
{
"id": "2.0",
"name": "2.0",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0-bin.zip"
},
{
"id": "1.1",
"name": "1.1",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.1.zip"
},
{
"id": "1.0.2",
"name": "1.0.2",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.0.2.zip"
},
{
"id": "1.0.1",
"name": "1.0.1",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.0.1.zip"
},
{
"id": "1.0",
"name": "1.0",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.0.zip"
}
],
"signature": {
"certificates": ["MIICmTCCAYECBQDerb70MA0GCSqGSIb3DQEBBAUAMIGKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2UxGDAWBgNVBAoTD0plbmtpbnMgUHJvamVjdDEaMBgGA1UEAxMRS29oc3VrZSBLYXdhZ3VjaGkxHTAbBgkqhkiG9w0BCQEWDmtrQGtvaHN1a2Uub3JnMB4XDTE0MDEyNzIxMDQwN1oXDTE1MDEyNzIxMDQwN1owXjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExGDAWBgNVBAoTD0plbmtpbnMgUHJvamVjdDEgMB4GA1UEAxMXQ29tbXVuaXR5IFVwZGF0ZSBDZW50ZXIwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAvAYxdnnMyREVQkfsMmGNXj2mFMgur+jjavJx5Wjc6Mfiq1x33Ps7qprhaklHmCg720Xe30E2+I/5R00XcUA+CwIDAQABMA0GCSqGSIb3DQEBBAUAA4IBAQCv9qYtq4miozdu/uzCvj2Dfj8Ms39ATivtuPiHCWagcOiR1SAjKI4DTdbz9WfNHH67TQrSSOwt7+HBjBVVh8WP1bI+OuJwgqmjcTuy14tByA3I5QZqM05SvZnVYY9ce5uXXNxe+FMwTRvnywbGPOPGG9xnfEsfN7NUjpAYbgM6fIkBMepOeL38IPUJb5pbDvM+F1zPaEJxnhGxnj3CuLu0XsI0h2wKCak1cJaLACJ+YSwPZju5/xNjQSibxAeoWh4u3JDQdgMSIOVGZWndypvr/xhLT2mVdXHSU7kpNB8Ojz9nC62g7qrC22bgUa1cGVIlljIi2Sf/xlXLu9Csksf+"],
"correct_digest": "NCZ9Z7HAwOMg7MnVrEVUsR5b+UA=",
"correct_signature": "ne6nBPs70d9Hh1gOMiKlVhj+zLbNjbMMVpn0Wn7MwF14MpVlCenkJouOw9b1Bq6Ay5L6bJ4iVvoCbvFhJyRrcg==",
"digest": "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
"signature": "PzSbGGnXzyOz2elTuaHwshl0uz/BZ50XcsuD/dyKq8fAyfqT27n2ghZETXAUKia4lFpTlqtuIm9+NySEbxYuTQ=="
}
}
),'*'); };</script></body></html>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册