Skip to content
Permalink
Browse files Browse the repository at this point in the history
Merge pull request #85 from jenkinsci-cert/security-371-staplerproxy
[SECURITY-371] Secure all administrative monitors
  • Loading branch information
jglick committed Dec 20, 2016
2 parents ce8a2d5 + 17b98d6 commit 6efcf6c
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 15 deletions.
Expand Up @@ -24,14 +24,14 @@
package hudson.diagnosis;

import hudson.model.AdministrativeMonitor;
import jenkins.model.Jenkins;
import hudson.model.AbstractModelObject;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.ExtensionList;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;

import java.io.IOException;
import java.util.List;
Expand Down Expand Up @@ -64,6 +64,7 @@ public String getDisplayName() {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
@RequirePOST
public HttpResponse doAct(@QueryParameter String no) throws IOException {
if(no!=null) {
disable(true);
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/hudson/diagnosis/OldDataMonitor.java
Expand Up @@ -52,6 +52,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;

import jenkins.model.Jenkins;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
Expand Down
Expand Up @@ -36,6 +36,7 @@
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.interceptor.RequirePOST;

/**
* Looks out for a broken reverse proxy setup that doesn't rewrite the location header correctly.
Expand Down Expand Up @@ -85,6 +86,7 @@ public void getTestForReverseProxySetup(String rest) {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
@RequirePOST
public HttpResponse doAct(@QueryParameter String no) throws IOException {
if(no!=null) { // dismiss
disable(true);
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java
Expand Up @@ -28,6 +28,7 @@
import hudson.Extension;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

import java.io.IOException;

Expand All @@ -49,6 +50,7 @@ public boolean isActivated() {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
@RequirePOST
public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
if(req.hasParameter("no")) {
disable(true);
Expand Down
17 changes: 15 additions & 2 deletions core/src/main/java/hudson/model/AdministrativeMonitor.java
Expand Up @@ -34,8 +34,12 @@
import java.io.IOException;

import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

/**
* Checks the health of a subsystem of Jenkins and if there's something
Expand Down Expand Up @@ -75,7 +79,7 @@
* @see Jenkins#administrativeMonitors
*/
@LegacyInstancesAreScopedToHudson
public abstract class AdministrativeMonitor extends AbstractModelObject implements ExtensionPoint {
public abstract class AdministrativeMonitor extends AbstractModelObject implements ExtensionPoint, StaplerProxy {
/**
* Human-readable ID of this monitor, which needs to be unique within the system.
*
Expand Down Expand Up @@ -143,12 +147,21 @@ public boolean isEnabled() {
/**
* URL binding to disable this monitor.
*/
@RequirePOST
public void doDisable(StaplerRequest req, StaplerResponse rsp) throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
disable(true);
rsp.sendRedirect2(req.getContextPath()+"/manage");
}

/**
* Requires ADMINISTER permission for any operation in here.
*/
@Restricted(NoExternalUse.class)
public Object getTarget() {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
return this;
}

/**
* All registered {@link AdministrativeMonitor} instances.
*/
Expand Down
Expand Up @@ -5,6 +5,7 @@
import jenkins.model.Jenkins;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

import java.io.IOException;

Expand All @@ -27,6 +28,7 @@ public boolean isActivated() {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
@RequirePOST
public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
if(req.hasParameter("no")) {
disable(true);
Expand Down
11 changes: 1 addition & 10 deletions core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java
Expand Up @@ -11,7 +11,6 @@
import jenkins.model.Jenkins;
import jenkins.util.io.FileBoolean;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.interceptor.RequirePOST;

Expand All @@ -29,7 +28,7 @@
* @author Kohsuke Kawaguchi
*/
@Extension
public class RekeySecretAdminMonitor extends AsynchronousAdministrativeMonitor implements StaplerProxy {
public class RekeySecretAdminMonitor extends AsynchronousAdministrativeMonitor {

/**
* Whether we detected a need to run the rewrite program.
Expand Down Expand Up @@ -62,14 +61,6 @@ && new FileBoolean(new File(j.getRootDir(),"secret.key.not-so-secret")).isOff()
needed.on();
}

/**
* Requires ADMINISTER permission for any operation in here.
*/
public Object getTarget() {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
return this;
}

@Override
public boolean isActivated() {
return needed.isOn();
Expand Down
Expand Up @@ -8,6 +8,7 @@
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.inject.Inject;
import java.io.IOException;
Expand Down Expand Up @@ -49,6 +50,7 @@ public AdminWhitelistRule getRule() {
/**
* Depending on whether the user said "examin" or "dismiss", send him to the right place.
*/
@RequirePOST
public HttpResponse doAct(@QueryParameter String dismiss) throws IOException {
if(dismiss!=null) {
disable(true);
Expand Down
Expand Up @@ -5,6 +5,7 @@
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.inject.Inject;
import java.io.IOException;
Expand All @@ -28,6 +29,7 @@ public boolean isActivated() {
return rule.getMasterKillSwitch() && config.isRelevant();
}

@RequirePOST
public HttpResponse doAct(@QueryParameter String dismiss) throws IOException {
if(dismiss!=null) {
disable(true);
Expand Down
Expand Up @@ -24,7 +24,7 @@ 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" xmlns:i="jelly:fmt">
<l:layout title="${%JENKINS_HOME is almost full}" permission="${app.ADMINISTER}">
<l:layout title="${%JENKINS_HOME is almost full}">
<l:main-panel>
<h1>
<l:icon class="icon-warning icon-xlg"/>
Expand Down
Expand Up @@ -24,7 +24,7 @@ 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">
<l:layout title="${%Manage Old Data}" permission="${app.ADMINISTER}">
<l:layout title="${%Manage Old Data}">
<st:include page="sidepanel.jelly" it="${app}"/>
<l:main-panel>
<h1>${%Manage Old Data}</h1>
Expand Down
@@ -1,18 +1,31 @@
package hudson.diagnosis;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import hudson.model.User;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.security.Permission;
import jenkins.model.Jenkins;
import org.acegisecurity.context.SecurityContextHolder;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.xml.sax.SAXException;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;

import java.io.IOException;
import java.util.Collections;

/**
* @author Kohsuke Kawaguchi
Expand Down Expand Up @@ -45,6 +58,48 @@ public void flow() throws Exception {
}
}

@Issue("SECURITY-371")
@Test
public void noAccessForNonAdmin() throws Exception {
JenkinsRule.WebClient wc = j.createWebClient();

// TODO: Use MockAuthorizationStrategy in later versions
JenkinsRule.DummySecurityRealm realm = j.createDummySecurityRealm();
realm.addGroups("administrator", "admins");
realm.addGroups("bob", "users");
j.jenkins.setSecurityRealm(realm);
GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();
auth.add(Jenkins.ADMINISTER, "admins");
auth.add(Jenkins.READ, "users");
j.jenkins.setAuthorizationStrategy(auth);

WebRequest request = new WebRequest(wc.createCrumbedUrl("administrativeMonitor/hudsonHomeIsFull/act"), HttpMethod.POST);
NameValuePair param = new NameValuePair("no", "true");
request.setRequestParameters(Collections.singletonList(param));

HudsonHomeDiskUsageMonitor mon = HudsonHomeDiskUsageMonitor.get();

try {
wc.login("bob");
wc.getPage(request);
} catch (FailingHttpStatusCodeException e) {
assertEquals(403, e.getStatusCode());
}
assertTrue(mon.isEnabled());

try {
WebRequest getIndex = new WebRequest(wc.createCrumbedUrl("administrativeMonitor/hudsonHomeIsFull"), HttpMethod.GET);
wc.getPage(getIndex);
} catch (FailingHttpStatusCodeException e) {
assertEquals(403, e.getStatusCode());
}

wc.login("administrator");
wc.getPage(request);
assertFalse(mon.isEnabled());

}

/**
* Gets the warning form.
*/
Expand Down

0 comments on commit 6efcf6c

Please sign in to comment.