提交 d4c9e40b 编写于 作者: M mindless

[SECURITY-5] Add several missing permission checks in CLI commands.

Current CLI is always available and most commands can be run by anonymous users.
Now only a short help message and the login command are available for anonymous
in a Hudson without anonymous read permission, and each individual command also
checks the appropriate permission for that command.  Details:
* Vulnerable CLI commands now fixed:
  build, clear-queue, copy-job, create-job, delete-builds, delete-job,
  disable-job, enable-job, install-plugin, install-tool, keep-build,
  mail, restart, safe-restart, set-build-result
* Broken CLI commands now fixed:
  connect-node, delete-node, disconnect-node, offline-node, online-node
* Other vulnerable command (not CLI) now fixed: /gc (JVM garbage collect)


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@33626 71c3de6d-444a-0410-be80-ed276b4c234a
上级 75cc09e9
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -32,6 +32,7 @@ import hudson.model.ParametersDefinitionProperty;
import hudson.model.ParameterDefinition;
import hudson.Extension;
import hudson.AbortException;
import hudson.model.Item;
import hudson.util.EditDistance;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
......@@ -66,8 +67,9 @@ public class BuildCommand extends CLICommand {
public Map<String,String> parameters = new HashMap<String, String>();
protected int run() throws Exception {
ParametersAction a = null;
job.checkPermission(Item.BUILD);
ParametersAction a = null;
if (!parameters.isEmpty()) {
ParametersDefinitionProperty pdp = job.getProperty(ParametersDefinitionProperty.class);
if (pdp==null)
......@@ -106,6 +108,7 @@ public class BuildCommand extends CLICommand {
);
}
// TODO: CLI can authenticate as different users, so should record which user here..
public static class CLICause extends Cause {
public String getShortDescription() {
return "Started by command line";
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -40,7 +40,6 @@ import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.hudson.annotation_indexer.Indexed;
import org.jvnet.tiger_types.Types;
import org.kohsuke.args4j.ClassParser;
import org.kohsuke.args4j.CmdLineException;
......@@ -48,7 +47,6 @@ import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.spi.OptionHandler;
import java.io.BufferedInputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
......@@ -181,6 +179,8 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
if (auth==Hudson.ANONYMOUS)
auth = loadStoredAuthentication();
sc.setAuthentication(auth); // run the CLI with the right credential
if (!(this instanceof LoginCommand || this instanceof HelpCommand))
Hudson.getInstance().checkPermission(Hudson.READ);
return run();
} catch (CmdLineException e) {
stderr.println(e.getMessage());
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -23,13 +23,12 @@
*/
package hudson.cli;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.TopLevelItem;
import hudson.Extension;
import hudson.model.Item;
import org.kohsuke.args4j.Argument;
import java.io.Serializable;
/**
* Copies a job from CLI.
......@@ -51,6 +50,8 @@ public class CopyJobCommand extends CLICommand {
protected int run() throws Exception {
Hudson h = Hudson.getInstance();
h.checkPermission(Item.CREATE);
if (h.getItem(dst)!=null) {
stderr.println("Job '"+dst+"' already exists");
return -1;
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -25,6 +25,7 @@ package hudson.cli;
import hudson.model.Hudson;
import hudson.Extension;
import hudson.model.Item;
import org.kohsuke.args4j.Argument;
/**
......@@ -44,6 +45,8 @@ public class CreateJobCommand extends CLICommand {
protected int run() throws Exception {
Hudson h = Hudson.getInstance();
h.checkPermission(Item.CREATE);
if (h.getItem(name)!=null) {
stderr.println("Job '"+name+"' already exists");
return -1;
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -25,6 +25,7 @@ package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import java.io.IOException;
import java.io.PrintStream;
......@@ -51,6 +52,8 @@ public class DeleteBuildsCommand extends AbstractBuildRangeCommand {
@Override
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
job.checkPermission(Run.DELETE);
for (AbstractBuild build : builds)
build.delete();
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -28,7 +28,6 @@ import hudson.model.Hudson;
import hudson.remoting.ChannelClosedException;
import groovy.lang.Binding;
import groovy.lang.Closure;
import org.acegisecurity.Authentication;
import org.codehaus.groovy.tools.shell.Groovysh;
import org.codehaus.groovy.tools.shell.IO;
import org.codehaus.groovy.tools.shell.Shell;
......@@ -60,6 +59,8 @@ public class GroovyshCommand extends CLICommand {
public int main(List<String> args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) {
// this allows the caller to manipulate the JVM state, so require the admin privilege.
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
// TODO: ^as this class overrides main() (which has authentication stuff),
// how to get ADMIN permission for this command?
// this being remote means no jline capability is available
System.setProperty("jline.terminal", UnsupportedTerminal.class.getName());
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -24,6 +24,7 @@
package hudson.cli;
import hudson.Extension;
import hudson.model.Hudson;
import java.util.Map;
import java.util.TreeMap;
......@@ -41,6 +42,12 @@ public class HelpCommand extends CLICommand {
}
protected int run() {
if (!Hudson.getInstance().hasPermission(Hudson.READ)) {
stderr.println("You must authenticate to access this Hudson.\n"
+ "Use --username/--password/--password-file parameters or login command.");
return 0;
}
Map<String,CLICommand> commands = new TreeMap<String,CLICommand>();
for (CLICommand c : CLICommand.all())
commands.put(c.getName(),c);
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -61,6 +61,9 @@ public class InstallPluginCommand extends CLICommand {
public boolean restart;
protected int run() throws Exception {
Hudson h = Hudson.getInstance();
h.checkPermission(Hudson.ADMINISTER);
for (String source : sources) {
// is this a file?
FilePath f = new FilePath(channel, source);
......@@ -90,7 +93,7 @@ public class InstallPluginCommand extends CLICommand {
}
// is this a plugin the update center?
UpdateSite.Plugin p = Hudson.getInstance().getUpdateCenter().getPlugin(source);
UpdateSite.Plugin p = h.getUpdateCenter().getPlugin(source);
if (p!=null) {
stdout.println("Installing "+source+" from update center");
p.deploy().get();
......@@ -102,7 +105,7 @@ public class InstallPluginCommand extends CLICommand {
}
if (restart)
Hudson.getInstance().restart();
h.restart();
return 0; // all success
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -32,6 +32,7 @@ import hudson.model.Run;
import hudson.model.Executor;
import hudson.model.Node;
import hudson.model.EnvironmentSpecific;
import hudson.model.Item;
import hudson.remoting.Callable;
import hudson.slaves.NodeSpecific;
import hudson.util.EditDistance;
......@@ -63,6 +64,20 @@ public class InstallToolCommand extends CLICommand {
}
protected int run() throws Exception {
Hudson h = Hudson.getInstance();
h.checkPermission(Hudson.READ);
// where is this build running?
BuildIDs id = channel.call(new BuildIDs());
if (!id.isComplete())
throw new AbortException("This command can be only invoked from a build executing inside Hudson");
AbstractProject p = Hudson.getInstance().getItemByFullName(id.job, AbstractProject.class);
if (p==null)
throw new AbortException("No such job found: "+id.job);
p.checkPermission(Item.CONFIGURE);
List<String> toolTypes = new ArrayList<String>();
for (ToolDescriptor<?> d : ToolInstallation.all()) {
toolTypes.add(d.getDisplayName());
......@@ -71,7 +86,7 @@ public class InstallToolCommand extends CLICommand {
for (ToolInstallation t : d.getInstallations()) {
toolNames.add(t.getName());
if (t.getName().equals(toolName))
return install(t);
return install(t, id, p);
}
// didn't find the right tool name
......@@ -96,16 +111,7 @@ public class InstallToolCommand extends CLICommand {
/**
* Performs an installation.
*/
private int install(ToolInstallation t) throws IOException, InterruptedException {
// where is this build running?
BuildIDs id = channel.call(new BuildIDs());
if (!id.isComplete())
throw new AbortException("This command can be only invoked from a build executing inside Hudson");
AbstractProject p = Hudson.getInstance().getItemByFullName(id.job, AbstractProject.class);
if (p==null)
throw new AbortException("No such job found: "+id.job);
private int install(ToolInstallation t, BuildIDs id, AbstractProject p) throws IOException, InterruptedException {
Run b = p.getBuildByNumber(Integer.parseInt(id.number));
if (b==null)
......
......@@ -40,6 +40,8 @@ public class ListChangesCommand extends AbstractBuildRangeCommand {
@Override
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
// Loading job for this CLI command requires Item.READ permission.
// No other permission check needed.
switch (format) {
case XML:
PrintWriter w = new PrintWriter(stdout);
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -25,6 +25,8 @@ package hudson.cli;
import hudson.tasks.Mailer;
import hudson.Extension;
import hudson.model.Hudson;
import hudson.model.Item;
import javax.mail.internet.MimeMessage;
import javax.mail.Transport;
......@@ -44,6 +46,7 @@ public class MailCommand extends CLICommand {
}
protected int run() throws Exception {
Hudson.getInstance().checkPermission(Item.CONFIGURE);
Transport.send(new MimeMessage(Mailer.descriptor().createSession(),stdin));
return 0;
}
......
......@@ -25,7 +25,9 @@
package hudson.cli;
import hudson.Extension;
import hudson.model.Item;
import hudson.model.Result;
import hudson.model.Run;
import org.kohsuke.args4j.Argument;
/**
......@@ -45,7 +47,9 @@ public class SetBuildResultCommand extends CommandDuringBuild {
@Override
protected int run() throws Exception {
getCurrentlyBuilding().setResult(result);
Run r = getCurrentlyBuilding();
r.getParent().checkPermission(Item.BUILD);
r.setResult(result);
return 0;
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -39,6 +39,7 @@ public class VersionCommand extends CLICommand {
}
protected int run() {
// CLICommand.main checks Hudson.READ permission.. no other check needed.
stdout.println(Hudson.VERSION);
return 0;
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
* Copyright (c) 2004-2010, Sun Microsystems, 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
......@@ -149,6 +149,7 @@ public class CLIRegisterer extends ExtensionFinder {
parser.parseArgument(args);
sc.setAuthentication(authenticator.authenticate()); // run the CLI with the right credential
hudson.checkPermission(Hudson.READ);
// resolve them
Object instance = null;
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Tom Huybrechts
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Daniel Dyer, Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -243,11 +244,13 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
/**
* Deletes this item.
*/
@CLIMethod(name="delete-job")
public void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
checkPermission(DELETE);
requirePOST();
delete();
rsp.sendRedirect2(req.getContextPath()+"/"+getParent().getUrl());
if (rsp != null) // null for CLI
rsp.sendRedirect2(req.getContextPath()+"/"+getParent().getUrl());
}
public void delete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
......@@ -262,7 +265,6 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
/**
* Deletes this item.
*/
@CLIMethod(name="delete-job")
public synchronized void delete() throws IOException, InterruptedException {
performDelete();
......
/*
* The MIT License
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot, Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot,
* Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -503,12 +505,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
save();
}
@CLIMethod(name="disable-job")
public void disable() throws IOException {
makeDisabled(true);
}
@CLIMethod(name="enable-job")
public void enable() throws IOException {
makeDisabled(false);
}
......@@ -1572,6 +1572,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
}
@CLIMethod(name="disable-job")
public HttpResponse doDisable() throws IOException, ServletException {
requirePOST();
checkPermission(CONFIGURE);
......@@ -1579,6 +1580,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return new HttpRedirect(".");
}
@CLIMethod(name="enable-job")
public HttpResponse doEnable() throws IOException, ServletException {
requirePOST();
checkPermission(CONFIGURE);
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -281,6 +282,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*/
@CLIMethod(name="connect-node")
public void cliConnect(@Option(name="-f",usage="Cancel any currently pending connect operation and retry from scratch") boolean force) throws ExecutionException, InterruptedException {
checkPermission(Hudson.ADMINISTER);
connect(force).get();
}
......@@ -336,6 +338,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*/
@CLIMethod(name="disconnect-node")
public void cliDisconnect(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
checkPermission(Hudson.ADMINISTER);
disconnect(new ByCLI(cause)).get();
}
......@@ -344,11 +347,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*/
@CLIMethod(name="offline-node")
public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
checkPermission(Hudson.ADMINISTER);
setTemporarilyOffline(true,new ByCLI(cause));
}
@CLIMethod(name="online-node")
public void cliOnline() throws ExecutionException, InterruptedException {
checkPermission(Hudson.ADMINISTER);
setTemporarilyOffline(false,null);
}
......
......@@ -152,6 +152,7 @@ import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorListener;
import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.HttpRedirect;
......@@ -1340,7 +1341,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
}
@CLIResolver
public Computer getComputer(String name) {
public Computer getComputer(@Argument(required=true,metaVar="NAME",usage="Node name") String name) {
if(name.equals("(master)"))
name = "";
......@@ -2862,6 +2863,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* For debugging. Expose URL to perform GC.
*/
public void doGc(StaplerResponse rsp) throws IOException {
checkPermission(Hudson.ADMINISTER);
System.gc();
rsp.setStatus(HttpServletResponse.SC_OK);
rsp.setContentType("text/plain");
......@@ -2874,7 +2876,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* Handles HTTP requests for duplex channels for CLI.
*/
public void doCli(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
if(!"POST".equals(Stapler.getCurrentRequest().getMethod())) {
if (!"POST".equals(req.getMethod())) {
// for GET request, serve _cli.jelly, assuming this is a browser
checkPermission(READ);
req.getView(this,"_cli.jelly").forward(req,rsp);
......@@ -2918,16 +2920,18 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*
* This first replaces "app" to {@link HudsonIsRestarting}
*/
@CLIMethod(name="restart")
public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException {
checkPermission(ADMINISTER);
if(Stapler.getCurrentRequest().getMethod().equals("GET")) {
if (req != null && req.getMethod().equals("GET")) {
req.getView(this,"_restart.jelly").forward(req,rsp);
return;
}
restart();
rsp.sendRedirect2(".");
if (rsp != null) // null for CLI
rsp.sendRedirect2(".");
}
/**
......@@ -2937,22 +2941,23 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*
* @since 1.332
*/
@CLIMethod(name="safe-restart")
public void doSafeRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException {
checkPermission(ADMINISTER);
if(Stapler.getCurrentRequest().getMethod().equals("GET")) {
if (req != null && req.getMethod().equals("GET")) {
req.getView(this,"_safeRestart.jelly").forward(req,rsp);
return;
}
safeRestart();
rsp.sendRedirect2(".");
if (rsp != null) // null for CLI
rsp.sendRedirect2(".");
}
/**
* Performs a restart.
*/
@CLIMethod(name="restart")
public void restart() throws RestartNotSupportedException {
final Lifecycle lifecycle = Lifecycle.get();
lifecycle.verifyRestartable(); // verify that Hudson is restartable
......@@ -2982,7 +2987,6 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* Queues up a restart to be performed once there are no builds currently running.
* @since 1.332
*/
@CLIMethod(name="safe-restart")
public void safeRestart() throws RestartNotSupportedException {
final Lifecycle lifecycle = Lifecycle.get();
lifecycle.verifyRestartable(); // verify that Hudson is restartable
......
......@@ -335,6 +335,7 @@ public class Queue extends ResourceController implements Saveable {
*/
@CLIMethod(name="clear-queue")
public synchronized void clear() {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
for (WaitingItem i : waitingList)
i.onCancelled();
waitingList.clear();
......
......@@ -1553,8 +1553,6 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
}
public void doToggleLogKeep( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
checkPermission(UPDATE);
keepLog(!keepLog);
rsp.forwardToPreviousPage(req);
}
......@@ -1568,6 +1566,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
}
public void keepLog(boolean newValue) throws IOException {
checkPermission(UPDATE);
keepLog = newValue;
save();
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册