Merge branch 'SECURITY-144' into SECURITY-144-rc

Conflicts:
	core/src/main/java/hudson/EnvVars.java
	core/src/main/java/hudson/FilePath.java
	core/src/main/java/hudson/cli/ClientAuthenticationCache.java
	core/src/main/java/hudson/markup/MyspacePolicy.java
	core/src/main/java/hudson/model/Computer.java
	core/src/main/java/hudson/node_monitors/DiskSpaceMonitorDescriptor.java
	core/src/main/java/hudson/node_monitors/ResponseTimeMonitor.java
	core/src/main/java/hudson/node_monitors/TemporarySpaceMonitor.java
	core/src/main/java/hudson/security/AbstractPasswordBasedSecurityRealm.java
	core/src/main/java/hudson/slaves/Channels.java
	core/src/main/java/hudson/slaves/SlaveComputer.java
	core/src/main/java/hudson/tasks/Fingerprinter.java
	core/src/main/java/hudson/tasks/Shell.java
	core/src/main/java/hudson/tasks/junit/JUnitParser.java
	core/src/main/java/hudson/tasks/test/DefaultTestResultParserImpl.java
	core/src/main/java/hudson/util/ProcessTree.java
	core/src/main/java/hudson/util/RemotingDiagnostics.java
	core/src/main/java/jenkins/model/Jenkins.java
	core/src/test/java/hudson/LauncherTest.java
	pom.xml
	test/src/test/java/hudson/tasks/junit/CaseResultTest.java
This commit is contained in:
Jesse Glick 2014-09-26 15:56:48 -04:00
commit 89cbfc4639
60 changed files with 1200 additions and 301 deletions

View File

@ -23,12 +23,12 @@
*/
package hudson;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.util.CaseInsensitiveComparator;
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
import hudson.util.VariableResolver;
import jenkins.security.MasterToSlaveCallable;
import java.io.File;
import java.io.IOException;
@ -404,7 +404,7 @@ public class EnvVars extends TreeMap<String,String> {
return channel.call(new GetEnvVars());
}
private static final class GetEnvVars implements Callable<EnvVars,RuntimeException> {
private static final class GetEnvVars extends MasterToSlaveCallable<EnvVars,RuntimeException> {
public EnvVars call() {
return new EnvVars(EnvVars.masterEnvVars);
}

View File

@ -112,6 +112,13 @@ import java.util.regex.Pattern;
import static hudson.FilePath.TarCompression.*;
import static hudson.Util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jenkins.FilePathFilter;
import jenkins.MasterToSlaveFileCallable;
import jenkins.SlaveToMasterFileCallable;
import jenkins.security.MasterToSlaveCallable;
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
/**
* {@link File} like object with remoting support.
@ -197,6 +204,17 @@ public final class FilePath implements Serializable {
// since the platform of the slave might be different, can't use java.io.File
private final String remote;
/**
* If this {@link FilePath} is deserialized to handle file access request from a remote computer,
* this field is set to the filter that performs access control.
*
* <p>
* If null, no access control is needed.
*
* @see #filterNonNull()
*/
private transient @Nullable FilePathFilter filter;
/**
* Creates a {@link FilePath} that represents a path on the given node.
*
@ -423,11 +441,11 @@ public final class FilePath implements Serializable {
*/
public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner) throws IOException, InterruptedException {
final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
return act(new FileCallable<Integer>() {
return act(new SecureFileCallable<Integer>() {
public Integer invoke(File f, VirtualChannel channel) throws IOException {
Archiver a = factory.create(out);
try {
scanner.scan(f,a);
scanner.scan(f,reading(a));
} finally {
a.close();
}
@ -455,13 +473,13 @@ public final class FilePath implements Serializable {
* @see #unzipFrom(InputStream)
*/
public void unzip(final FilePath target) throws IOException, InterruptedException {
target.act(new FileCallable<Void>() {
target.act(new SecureFileCallable<Void>() {
public Void invoke(File dir, VirtualChannel channel) throws IOException {
public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
if (FilePath.this.isRemote())
unzip(dir, FilePath.this.read()); // use streams
else
unzip(dir, new File(FilePath.this.getRemote())); // shortcut to local file
unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
return null;
}
private static final long serialVersionUID = 1L;
@ -479,8 +497,8 @@ public final class FilePath implements Serializable {
* @see #untarFrom(InputStream, TarCompression)
*/
public void untar(final FilePath target, final TarCompression compression) throws IOException, InterruptedException {
target.act(new FileCallable<Void>() {
public Void invoke(File dir, VirtualChannel channel) throws IOException {
target.act(new SecureFileCallable<Void>() {
public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
readFromTar(FilePath.this.getName(),dir,compression.extract(FilePath.this.read()));
return null;
}
@ -498,7 +516,7 @@ public final class FilePath implements Serializable {
*/
public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
final InputStream in = new RemoteInputStream(_in);
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
public Void invoke(File dir, VirtualChannel channel) throws IOException {
unzip(dir, in);
return null;
@ -507,7 +525,7 @@ public final class FilePath implements Serializable {
});
}
private static void unzip(File dir, InputStream in) throws IOException {
private void unzip(File dir, InputStream in) throws IOException {
File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
try {
// TODO why does this not simply use ZipInputStream?
@ -519,7 +537,7 @@ public final class FilePath implements Serializable {
}
}
static private void unzip(File dir, File zipFile) throws IOException {
private void unzip(File dir, File zipFile) throws IOException {
dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
ZipFile zip = new ZipFile(zipFile);
@SuppressWarnings("unchecked")
@ -530,15 +548,15 @@ public final class FilePath implements Serializable {
ZipEntry e = entries.nextElement();
File f = new File(dir, e.getName());
if (e.isDirectory()) {
f.mkdirs();
mkdirs(f);
} else {
File p = f.getParentFile();
if (p != null) {
p.mkdirs();
mkdirs(p);
}
InputStream input = zip.getInputStream(e);
try {
IOUtils.copy(input, f);
IOUtils.copy(input, writing(f));
} finally {
input.close();
}
@ -562,7 +580,7 @@ public final class FilePath implements Serializable {
* Absolutizes this {@link FilePath} and returns the new one.
*/
public FilePath absolutize() throws IOException, InterruptedException {
return new FilePath(channel,act(new FileCallable<String>() {
return new FilePath(channel,act(new SecureFileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File f, VirtualChannel channel) throws IOException {
return f.getAbsolutePath();
@ -580,9 +598,10 @@ public final class FilePath implements Serializable {
* @since 1.456
*/
public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
writing(f);
Util.createSymlink(f.getParentFile(),target,f.getName(),listener);
return null;
}
@ -597,10 +616,10 @@ public final class FilePath implements Serializable {
* @since 1.456
*/
public String readLink() throws IOException, InterruptedException {
return act(new FileCallable<String>() {
return act(new SecureFileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
return Util.resolveSymlink(f);
return Util.resolveSymlink(reading(f));
}
});
}
@ -666,7 +685,7 @@ public final class FilePath implements Serializable {
public void untarFrom(InputStream _in, final TarCompression compression) throws IOException, InterruptedException {
try {
final InputStream in = new RemoteInputStream(_in);
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
public Void invoke(File dir, VirtualChannel channel) throws IOException {
readFromTar("input stream",dir, compression.extract(in));
return null;
@ -776,7 +795,8 @@ public final class FilePath implements Serializable {
}
}
private static final class Unpack implements FileCallable<Void> {
// this reads from arbitrary URL
private final class Unpack extends MasterToSlaveFileCallable<Void> {
private final URL archive;
Unpack(URL archive) {
this.archive = archive;
@ -845,7 +865,7 @@ public final class FilePath implements Serializable {
public void copyFrom(FileItem file) throws IOException, InterruptedException {
if(channel==null) {
try {
file.write(new File(remote));
file.write(writing(new File(remote)));
} catch (IOException e) {
throw e;
} catch (Exception e) {
@ -870,9 +890,14 @@ public final class FilePath implements Serializable {
* Code that gets executed on the machine where the {@link FilePath} is local.
* Used to act on {@link FilePath}.
* <strong>Warning:</code> implementations must be serializable, so prefer a static nested class to an inner class.
*
* <p>
* Subtypes would likely want to extend from either {@link MasterToSlaveCallable}
* or {@link SlaveToMasterFileCallable}.
*
* @see FilePath#act(FileCallable)
*/
public interface FileCallable<T> extends Serializable {
public interface FileCallable<T> extends Serializable, RoleSensitive {
/**
* Performs the computational task on the node where the data is located.
*
@ -888,6 +913,15 @@ public final class FilePath implements Serializable {
T invoke(File f, VirtualChannel channel) throws IOException, InterruptedException;
}
/**
* {@link FileCallable}s that can be executed anywhere, including the master.
*
* The code is the same as {@link SlaveToMasterFileCallable}, but used as a marker to
* designate those impls that use {@link FilePathFilter}.
*/
/*package*/ static abstract class SecureFileCallable<T> extends SlaveToMasterFileCallable<T> {
}
/**
* Executes some program on the machine that this {@link FilePath} exists,
* so that one can perform local file operations.
@ -1023,6 +1057,12 @@ public final class FilePath implements Serializable {
throw (IOException)new InterruptedIOException().initCause(e);
}
}
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
task.checkRoles(checker);
}
private static final long serialVersionUID = 1L;
};
}
@ -1032,7 +1072,7 @@ public final class FilePath implements Serializable {
* on which this file is available.
*/
public URI toURI() throws IOException, InterruptedException {
return act(new FileCallable<URI>() {
return act(new SecureFileCallable<URI>() {
private static final long serialVersionUID = 1L;
public URI invoke(File f, VirtualChannel channel) {
return f.toURI();
@ -1070,10 +1110,10 @@ public final class FilePath implements Serializable {
* Creates this directory.
*/
public void mkdirs() throws IOException, InterruptedException {
if(!act(new FileCallable<Boolean>() {
if(!act(new SecureFileCallable<Boolean>() {
private static final long serialVersionUID = 1L;
public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
if(f.mkdirs() || f.exists())
if(mkdirs(f) || f.exists())
return true; // OK
// following Ant <mkdir> task to avoid possible race condition.
@ -1089,10 +1129,10 @@ public final class FilePath implements Serializable {
* Deletes this directory, including all its contents recursively.
*/
public void deleteRecursive() throws IOException, InterruptedException {
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
Util.deleteRecursive(f);
deleteRecursive(deleting(f));
return null;
}
});
@ -1102,15 +1142,38 @@ public final class FilePath implements Serializable {
* Deletes all the contents of this directory, but not the directory itself
*/
public void deleteContents() throws IOException, InterruptedException {
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
Util.deleteContentsRecursive(f);
deleteContentsRecursive(f);
return null;
}
});
}
private void deleteRecursive(File dir) throws IOException {
if(!isSymlink(dir))
deleteContentsRecursive(dir);
try {
deleteFile(deleting(dir));
} catch (IOException e) {
// if some of the child directories are big, it might take long enough to delete that
// it allows others to create new files, causing problemsl ike JENKINS-10113
// so give it one more attempt before we give up.
if(!isSymlink(dir))
deleteContentsRecursive(dir);
deleteFile(deleting(dir));
}
}
private void deleteContentsRecursive(File file) throws IOException {
File[] files = file.listFiles();
if(files==null)
return; // the directory didn't exist in the first place
for (File child : files)
deleteRecursive(child);
}
/**
* Gets the file name portion except the extension.
*
@ -1196,10 +1259,10 @@ public final class FilePath implements Serializable {
*/
public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
try {
return new FilePath(this,act(new FileCallable<String>() {
return new FilePath(this,act(new SecureFileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File dir, VirtualChannel channel) throws IOException {
File f = File.createTempFile(prefix, suffix, dir);
File f = writing(File.createTempFile(prefix, suffix, dir));
return f.getName();
}
}));
@ -1252,22 +1315,22 @@ public final class FilePath implements Serializable {
*/
public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
try {
return new FilePath(channel,act(new FileCallable<String>() {
return new FilePath(channel,act(new SecureFileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File dir, VirtualChannel channel) throws IOException {
if(!inThisDirectory)
dir = new File(System.getProperty("java.io.tmpdir"));
else
dir.mkdirs();
mkdirs(dir);
File f;
try {
f = File.createTempFile(prefix, suffix, dir);
f = creating(File.createTempFile(prefix, suffix, dir));
} catch (IOException e) {
throw new IOException("Failed to create a temporary directory in "+dir,e);
}
Writer w = new FileWriter(f);
Writer w = new FileWriter(writing(f));
try {
w.write(contents);
} finally {
@ -1298,7 +1361,7 @@ public final class FilePath implements Serializable {
*/
public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException {
try {
return new FilePath(this,act(new FileCallable<String>() {
return new FilePath(this,act(new SecureFileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File dir, VirtualChannel channel) throws IOException {
File f = File.createTempFile(prefix, suffix, dir);
@ -1318,10 +1381,10 @@ public final class FilePath implements Serializable {
* @return true, for a modicum of compatibility
*/
public boolean delete() throws IOException, InterruptedException {
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
Util.deleteFile(f);
Util.deleteFile(deleting(f));
return null;
}
});
@ -1332,10 +1395,10 @@ public final class FilePath implements Serializable {
* Checks if the file exists.
*/
public boolean exists() throws IOException, InterruptedException {
return act(new FileCallable<Boolean>() {
return act(new SecureFileCallable<Boolean>() {
private static final long serialVersionUID = 1L;
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
return f.exists();
return stating(f).exists();
}
});
}
@ -1348,10 +1411,10 @@ public final class FilePath implements Serializable {
* @see #touch(long)
*/
public long lastModified() throws IOException, InterruptedException {
return act(new FileCallable<Long>() {
return act(new SecureFileCallable<Long>() {
private static final long serialVersionUID = 1L;
public Long invoke(File f, VirtualChannel channel) throws IOException {
return f.lastModified();
return stating(f).lastModified();
}
});
}
@ -1362,12 +1425,12 @@ public final class FilePath implements Serializable {
* @since 1.299
*/
public void touch(final long timestamp) throws IOException, InterruptedException {
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = -5094638816500738429L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
if(!f.exists())
new FileOutputStream(f).close();
if(!f.setLastModified(timestamp))
new FileOutputStream(creating(f)).close();
if(!stating(f).setLastModified(timestamp))
throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
return null;
}
@ -1375,10 +1438,10 @@ public final class FilePath implements Serializable {
}
private void setLastModifiedIfPossible(final long timestamp) throws IOException, InterruptedException {
String message = act(new FileCallable<String>() {
String message = act(new SecureFileCallable<String>() {
private static final long serialVersionUID = -828220335793641630L;
public String invoke(File f, VirtualChannel channel) throws IOException {
if(!f.setLastModified(timestamp)) {
if(!writing(f).setLastModified(timestamp)) {
if (Functions.isWindows()) {
// On Windows this seems to fail often. See JENKINS-11073
// Therefore don't fail, but just log a warning
@ -1400,10 +1463,10 @@ public final class FilePath implements Serializable {
* Checks if the file is a directory.
*/
public boolean isDirectory() throws IOException, InterruptedException {
return act(new FileCallable<Boolean>() {
return act(new SecureFileCallable<Boolean>() {
private static final long serialVersionUID = 1L;
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
return f.isDirectory();
return stating(f).isDirectory();
}
});
}
@ -1414,10 +1477,10 @@ public final class FilePath implements Serializable {
* @since 1.129
*/
public long length() throws IOException, InterruptedException {
return act(new FileCallable<Long>() {
return act(new SecureFileCallable<Long>() {
private static final long serialVersionUID = 1L;
public Long invoke(File f, VirtualChannel channel) throws IOException {
return f.length();
return stating(f).length();
}
});
}
@ -1427,7 +1490,7 @@ public final class FilePath implements Serializable {
* @since 1.542
*/
public long getFreeDiskSpace() throws IOException, InterruptedException {
return act(new FileCallable<Long>() {
return act(new SecureFileCallable<Long>() {
private static final long serialVersionUID = 1L;
@Override public Long invoke(File f, VirtualChannel channel) throws IOException {
return f.getFreeSpace();
@ -1440,7 +1503,7 @@ public final class FilePath implements Serializable {
* @since 1.542
*/
public long getTotalDiskSpace() throws IOException, InterruptedException {
return act(new FileCallable<Long>() {
return act(new SecureFileCallable<Long>() {
private static final long serialVersionUID = 1L;
@Override public Long invoke(File f, VirtualChannel channel) throws IOException {
return f.getTotalSpace();
@ -1453,7 +1516,7 @@ public final class FilePath implements Serializable {
* @since 1.542
*/
public long getUsableDiskSpace() throws IOException, InterruptedException {
return act(new FileCallable<Long>() {
return act(new SecureFileCallable<Long>() {
private static final long serialVersionUID = 1L;
@Override public Long invoke(File f, VirtualChannel channel) throws IOException {
return f.getUsableSpace();
@ -1478,10 +1541,10 @@ public final class FilePath implements Serializable {
*/
public void chmod(final int mask) throws IOException, InterruptedException {
if(!isUnix() || mask==-1) return;
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
_chmod(f, mask);
_chmod(writing(f), mask);
return null;
}
@ -1509,10 +1572,10 @@ public final class FilePath implements Serializable {
*/
public int mode() throws IOException, InterruptedException, PosixException {
if(!isUnix()) return -1;
return act(new FileCallable<Integer>() {
return act(new SecureFileCallable<Integer>() {
private static final long serialVersionUID = 1L;
public Integer invoke(File f, VirtualChannel channel) throws IOException {
return IOUtils.mode(f);
return IOUtils.mode(stating(f));
}
});
}
@ -1556,10 +1619,10 @@ public final class FilePath implements Serializable {
if (filter != null && !(filter instanceof Serializable)) {
throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
}
return act(new FileCallable<List<FilePath>>() {
return act(new SecureFileCallable<List<FilePath>>() {
private static final long serialVersionUID = 1L;
public List<FilePath> invoke(File f, VirtualChannel channel) throws IOException {
File[] children = f.listFiles(filter);
File[] children = reading(f).listFiles(filter);
if(children ==null) return null;
ArrayList<FilePath> r = new ArrayList<FilePath>(children.length);
@ -1609,10 +1672,10 @@ public final class FilePath implements Serializable {
* @since 1.465
*/
public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
return act(new FileCallable<FilePath[]>() {
return act(new SecureFileCallable<FilePath[]>() {
private static final long serialVersionUID = 1L;
public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
String[] files = glob(f, includes, excludes, defaultExcludes);
String[] files = glob(reading(f), includes, excludes, defaultExcludes);
FilePath[] r = new FilePath[files.length];
for( int i=0; i<r.length; i++ )
@ -1642,18 +1705,20 @@ public final class FilePath implements Serializable {
/**
* Reads this file.
*/
public InputStream read() throws IOException {
public InputStream read() throws IOException, InterruptedException {
if(channel==null)
return new FileInputStream(new File(remote));
return new FileInputStream(reading(new File(remote)));
final Pipe p = Pipe.createRemoteToLocal();
channel.callAsync(new Callable<Void,IOException>() {
actAsync(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void call() throws IOException {
FileInputStream fis=null;
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(remote));
Util.copyStream(fis,p.getOut());
fis = new FileInputStream(reading(f));
Util.copyStream(fis, p.getOut());
return null;
} finally {
org.apache.commons.io.IOUtils.closeQuietly(fis);
@ -1668,7 +1733,7 @@ public final class FilePath implements Serializable {
/**
* Reads this file into a string, by using the current system encoding.
*/
public String readToString() throws IOException {
public String readToString() throws IOException, InterruptedException {
InputStream in = read();
try {
return org.apache.commons.io.IOUtils.toString(in);
@ -1695,16 +1760,16 @@ public final class FilePath implements Serializable {
public OutputStream write() throws IOException, InterruptedException {
if(channel==null) {
File f = new File(remote).getAbsoluteFile();
f.getParentFile().mkdirs();
return new FileOutputStream(f);
mkdirs(f.getParentFile());
return new FileOutputStream(writing(f));
}
return channel.call(new Callable<OutputStream,IOException>() {
return act(new SecureFileCallable<OutputStream>() {
private static final long serialVersionUID = 1L;
public OutputStream call() throws IOException {
File f = new File(remote).getAbsoluteFile();
f.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(f);
public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
f = f.getAbsoluteFile();
mkdirs(f.getParentFile());
FileOutputStream fos = new FileOutputStream(writing(f));
return new RemoteOutputStream(fos);
}
});
@ -1718,11 +1783,11 @@ public final class FilePath implements Serializable {
* @since 1.105
*/
public void write(final String content, final String encoding) throws IOException, InterruptedException {
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
f.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(f);
mkdirs(f.getParentFile());
FileOutputStream fos = new FileOutputStream(writing(f));
Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos);
try {
w.write(content);
@ -1739,10 +1804,10 @@ public final class FilePath implements Serializable {
* @see Util#getDigestOf(File)
*/
public String digest() throws IOException, InterruptedException {
return act(new FileCallable<String>() {
return act(new SecureFileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File f, VirtualChannel channel) throws IOException {
return Util.getDigestOf(f);
return Util.getDigestOf(reading(f));
}
});
}
@ -1755,10 +1820,10 @@ public final class FilePath implements Serializable {
if(this.channel != target.channel) {
throw new IOException("renameTo target must be on the same host");
}
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
f.renameTo(new File(target.remote));
reading(f).renameTo(creating(new File(target.remote)));
return null;
}
});
@ -1773,7 +1838,7 @@ public final class FilePath implements Serializable {
if(this.channel != target.channel) {
throw new IOException("pullUpTo target must be on the same host");
}
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
// JENKINS-16846: if f.getName() is the same as one of the files/directories in f,
@ -1784,12 +1849,12 @@ public final class FilePath implements Serializable {
File t = new File(target.getRemote());
for(File child : tmp.listFiles()) {
for(File child : reading(tmp).listFiles()) {
File target = new File(t, child.getName());
if(!child.renameTo(target))
if(!stating(child).renameTo(creating(target)))
throw new IOException("Failed to rename "+child+" to "+target);
}
tmp.delete();
deleting(tmp).delete();
return null;
}
});
@ -1828,12 +1893,12 @@ public final class FilePath implements Serializable {
public void copyTo(OutputStream os) throws IOException, InterruptedException {
final OutputStream out = new RemoteOutputStream(os);
act(new FileCallable<Void>() {
act(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 4088559042349254141L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
fis = new FileInputStream(reading(f));
Util.copyStream(fis,out);
return null;
} finally {
@ -1875,21 +1940,6 @@ public final class FilePath implements Serializable {
channel.syncLocalIO();
}
/**
* Remoting interface used for {@link FilePath#copyRecursiveTo(String, FilePath)}.
*
* TODO: this might not be the most efficient way to do the copy.
*/
interface RemoteCopier {
/**
* @param fileName
* relative path name to the output file. Path separator must be '/'.
*/
void open(String fileName) throws IOException;
void write(byte[] buf, int len) throws IOException;
void close() throws IOException;
}
/**
* Copies the contents of this directory recursively into the specified target directory.
*
@ -1944,35 +1994,41 @@ public final class FilePath implements Serializable {
public int copyRecursiveTo(final DirScanner scanner, final FilePath target, final String description) throws IOException, InterruptedException {
if(this.channel==target.channel) {
// local to local copy.
return act(new FileCallable<Integer>() {
return act(new SecureFileCallable<Integer>() {
private static final long serialVersionUID = 1L;
public Integer invoke(File base, VirtualChannel channel) throws IOException {
if(!base.exists()) return 0;
assert target.channel==null;
final File dest = new File(target.remote);
final AtomicInteger count = new AtomicInteger();
scanner.scan(base, new FileVisitor() {
@Override public void visit(File f, String relativePath) throws IOException {
scanner.scan(base, reading(new FileVisitor() {
@Override
public void visit(File f, String relativePath) throws IOException {
if (f.isFile()) {
File target = new File(dest, relativePath);
IOUtils.mkdirs(target.getParentFile());
Util.copyFile(f, target);
mkdirsE(target.getParentFile());
Util.copyFile(f, writing(target));
count.incrementAndGet();
}
}
@Override public boolean understandsSymlink() {
@Override
public boolean understandsSymlink() {
return true;
}
@Override public void visitSymlink(File link, String target, String relativePath) throws IOException {
@Override
public void visitSymlink(File link, String target, String relativePath) throws IOException {
try {
IOUtils.mkdirs(new File(dest, relativePath).getParentFile());
mkdirsE(new File(dest, relativePath).getParentFile());
writing(new File(dest, target));
Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
} catch (InterruptedException x) {
throw (IOException) new IOException(x.toString()).initCause(x);
}
count.incrementAndGet();
}
});
}));
return count.get();
}
});
@ -1981,7 +2037,7 @@ public final class FilePath implements Serializable {
// local -> remote copy
final Pipe pipe = Pipe.createLocalToRemote();
Future<Void> future = target.actAsync(new FileCallable<Void>() {
Future<Void> future = target.actAsync(new SecureFileCallable<Void>() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
try {
@ -1992,7 +2048,7 @@ public final class FilePath implements Serializable {
}
}
});
Future<Integer> future2 = actAsync(new FileCallable<Integer>() {
Future<Integer> future2 = actAsync(new SecureFileCallable<Integer>() {
private static final long serialVersionUID = 1L;
@Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
@ -2009,7 +2065,7 @@ public final class FilePath implements Serializable {
// remote -> local copy
final Pipe pipe = Pipe.createRemoteToLocal();
Future<Integer> future = actAsync(new FileCallable<Integer>() {
Future<Integer> future = actAsync(new SecureFileCallable<Integer>() {
private static final long serialVersionUID = 1L;
public Integer invoke(File f, VirtualChannel channel) throws IOException {
try {
@ -2069,10 +2125,10 @@ public final class FilePath implements Serializable {
* @return
* number of files/directories that are written.
*/
private static Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
Archiver tw = ArchiverFactory.TAR.create(out);
try {
scanner.scan(baseDir,tw);
scanner.scan(baseDir,reading(tw));
} finally {
tw.close();
}
@ -2082,17 +2138,18 @@ public final class FilePath implements Serializable {
/**
* Reads from a tar stream and stores obtained files to the base dir.
*/
private static void readFromTar(String name, File baseDir, InputStream in) throws IOException {
private void readFromTar(String name, File baseDir, InputStream in) throws IOException {
TarInputStream t = new TarInputStream(in);
try {
TarEntry te;
while ((te = t.getNextEntry()) != null) {
File f = new File(baseDir,te.getName());
if(te.isDirectory()) {
f.mkdirs();
mkdirs(f);
} else {
File parent = f.getParentFile();
if (parent != null) parent.mkdirs();
if (parent != null) mkdirs(parent);
writing(f);
byte linkFlag = (Byte) LINKFLAG_FIELD.get(te);
if (linkFlag==TarEntry.LF_SYMLINK) {
@ -2131,7 +2188,7 @@ public final class FilePath implements Serializable {
return new RemoteLauncher(listener,channel,channel.call(new IsUnix()));
}
private static final class IsUnix implements Callable<Boolean,IOException> {
private static final class IsUnix extends MasterToSlaveCallable<Boolean,IOException> {
public Boolean call() throws IOException {
return File.pathSeparatorChar==':';
}
@ -2168,7 +2225,7 @@ public final class FilePath implements Serializable {
* @since 1.484
*/
public String validateAntFileMask(final String fileMasks, final int bound) throws IOException, InterruptedException {
return act(new FileCallable<String>() {
return act(new MasterToSlaveFileCallable<String>() {
private static final long serialVersionUID = 1;
public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
if(fileMasks.startsWith("~"))
@ -2209,7 +2266,7 @@ public final class FilePath implements Serializable {
{// check the (2) above next as this is more expensive.
// Try prepending "**/" to see if that results in a match
FileSet fs = Util.createFileSet(dir,"**/"+fileMask);
FileSet fs = Util.createFileSet(reading(dir),"**/"+fileMask);
DirectoryScanner ds = fs.getDirectoryScanner(new Project());
if(ds.getIncludedFilesCount()!=0) {
// try shorter name first so that the suggestion results in least amount of changes
@ -2278,7 +2335,7 @@ public final class FilePath implements Serializable {
return super.isCaseSensitive();
}
};
ds.setBasedir(dir);
ds.setBasedir(reading(dir));
ds.setIncludes(new String[] {pattern});
try {
ds.scan();
@ -2451,8 +2508,14 @@ public final class FilePath implements Serializable {
ois.defaultReadObject();
if(ois.readBoolean()) {
this.channel = channel;
this.filter = null;
} else {
this.channel = null;
// If the remote channel wants us to create a FilePath that points to a local file,
// we need to make sure the access control takes place.
// This covers the immediate case of FileCallables taking FilePath into reference closure implicitly,
// but it also covers more general case of FilePath sent as a return value or argument.
this.filter = FilePathFilter.current();
}
}
@ -2487,6 +2550,14 @@ public final class FilePath implements Serializable {
}
}
/**
* Role check comes from {@link FileCallable}s.
*/
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
callable.checkRoles(checker);
}
public ClassLoader getClassLoader() {
return classLoader;
}
@ -2529,7 +2600,7 @@ public final class FilePath implements Serializable {
* (User's home directory in the Unix sense) of the given channel.
*/
public static FilePath getHomeDirectory(VirtualChannel ch) throws InterruptedException, IOException {
return ch.call(new Callable<FilePath,IOException>() {
return ch.call(new MasterToSlaveCallable<FilePath,IOException>() {
public FilePath call() throws IOException {
return new FilePath(new File(System.getProperty("user.home")));
}
@ -2563,7 +2634,6 @@ public final class FilePath implements Serializable {
scanSingle(new File(dir, workspacePath), archivedPath, visitor);
}
}
}
private static final ExecutorService threadPoolForRemoting = new ContextResettingExecutorService(
@ -2573,4 +2643,93 @@ public final class FilePath implements Serializable {
));
public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
private @Nonnull FilePathFilter filterNonNull() {
return filter!=null ? filter : FilePathFilter.EMPTY;
}
/**
* Wraps {@link FileVisitor} to notify read access to {@link FilePathFilter}.
*/
private FileVisitor reading(final FileVisitor v) {
final FilePathFilter filter = FilePathFilter.current();
if (filter==null) return v;
return new FileVisitor() {
@Override
public void visit(File f, String relativePath) throws IOException {
filter.read(f);
v.visit(f,relativePath);
}
@Override
public void visitSymlink(File link, String target, String relativePath) throws IOException {
filter.read(link);
v.visitSymlink(link, target, relativePath);
}
@Override
public boolean understandsSymlink() {
return v.understandsSymlink();
}
};
}
/**
* Pass through 'f' after ensuring that we can read that file.
*/
private File reading(File f) {
filterNonNull().read(f);
return f;
}
/**
* Pass through 'f' after ensuring that we can access the file attributes.
*/
private File stating(File f) {
filterNonNull().stat(f);
return f;
}
/**
* Pass through 'f' after ensuring that we can create that file/dir.
*/
private File creating(File f) {
filterNonNull().create(f);
return f;
}
/**
* Pass through 'f' after ensuring that we can write to that file.
*/
private File writing(File f) {
FilePathFilter filter = filterNonNull();
if (!f.exists())
filter.create(f);
filter.write(f);
return f;
}
/**
* Pass through 'f' after ensuring that we can delete that file.
*/
private File deleting(File f) {
filterNonNull().delete(f);
return f;
}
private boolean mkdirs(File dir) {
if (dir.exists()) return false;
filterNonNull().mkdirs(dir);
return dir.mkdirs();
}
private File mkdirsE(File dir) throws IOException {
if (dir.exists()) {
return dir;
}
filterNonNull().mkdirs(dir);
return IOUtils.mkdirs(dir);
}
}

View File

@ -38,6 +38,7 @@ import hudson.remoting.VirtualChannel;
import hudson.util.StreamCopyThread;
import hudson.util.ArgumentListBuilder;
import hudson.util.ProcessTree;
import jenkins.security.MasterToSlaveCallable;
import org.apache.commons.io.input.NullInputStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@ -940,7 +941,7 @@ public abstract class Launcher {
getChannel().call(new KillTask(modelEnvVars));
}
private static final class KillTask implements Callable<Void,RuntimeException> {
private static final class KillTask extends MasterToSlaveCallable<Void,RuntimeException> {
private final Map<String, String> modelEnvVars;
public KillTask(Map<String, String> modelEnvVars) {
@ -1097,7 +1098,7 @@ public abstract class Launcher {
IOTriplet getIOtriplet();
}
private static class RemoteLaunchCallable implements Callable<RemoteProcess,IOException> {
private static class RemoteLaunchCallable extends MasterToSlaveCallable<RemoteProcess,IOException> {
private final List<String> cmd;
private final boolean[] masks;
private final String[] env;
@ -1169,7 +1170,7 @@ public abstract class Launcher {
private static final long serialVersionUID = 1L;
}
private static class RemoteChannelLaunchCallable implements Callable<OutputStream,IOException> {
private static class RemoteChannelLaunchCallable extends MasterToSlaveCallable<OutputStream,IOException> {
private final String[] cmd;
private final Pipe out;
private final String workDir;

View File

@ -36,6 +36,7 @@ import hudson.remoting.Channel;
import hudson.remoting.ChannelProperty;
import hudson.security.CliAuthenticator;
import hudson.security.SecurityRealm;
import jenkins.security.MasterToSlaveCallable;
import org.acegisecurity.Authentication;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.context.SecurityContext;
@ -409,7 +410,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
return checkChannel().call(new GetSystemProperty(name));
}
private static final class GetSystemProperty implements Callable<String, IOException> {
private static final class GetSystemProperty extends MasterToSlaveCallable<String, IOException> {
private final String name;
private GetSystemProperty(String name) {
@ -438,7 +439,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
}
}
private static final class GetCharset implements Callable<String, IOException> {
private static final class GetCharset extends MasterToSlaveCallable<String, IOException> {
public String call() throws IOException {
return Charset.defaultCharset().name();
}
@ -453,7 +454,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
return checkChannel().call(new GetEnvironmentVariable(name));
}
private static final class GetEnvironmentVariable implements Callable<String, IOException> {
private static final class GetEnvironmentVariable extends MasterToSlaveCallable<String, IOException> {
private final String name;
private GetEnvironmentVariable(String name) {

View File

@ -1,10 +1,10 @@
package hudson.cli;
import hudson.FilePath;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
@ -39,7 +39,7 @@ public class ClientAuthenticationCache implements Serializable {
private final Properties props = new Properties();
public ClientAuthenticationCache(Channel channel) throws IOException, InterruptedException {
store = (channel==null ? FilePath.localChannel : channel).call(new Callable<FilePath, IOException>() {
store = (channel==null ? FilePath.localChannel : channel).call(new MasterToSlaveCallable<FilePath, IOException>() {
public FilePath call() throws IOException {
File home = new File(System.getProperty("user.home"));
File hudsonHome = new File(home, ".hudson");

View File

@ -28,6 +28,7 @@ import jenkins.model.Jenkins;
import hudson.model.Job;
import hudson.model.Run;
import hudson.remoting.Callable;
import jenkins.security.MasterToSlaveCallable;
import org.kohsuke.args4j.CmdLineException;
import java.io.IOException;
@ -84,7 +85,7 @@ public abstract class CommandDuringBuild extends CLICommand {
/**
* Gets the environment variables that points to the build being executed.
*/
private static final class GetCharacteristicEnvironmentVariables implements Callable<String[],IOException> {
private static final class GetCharacteristicEnvironmentVariables extends MasterToSlaveCallable<String[],IOException> {
public String[] call() throws IOException {
return new String[] {
System.getenv("JOB_NAME"),

View File

@ -31,10 +31,7 @@ import hudson.model.AbstractProject;
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;
import hudson.util.StreamTaskListener;
import hudson.tools.ToolDescriptor;
@ -44,6 +41,7 @@ import java.util.List;
import java.util.ArrayList;
import java.io.IOException;
import jenkins.security.MasterToSlaveCallable;
import org.kohsuke.args4j.Argument;
/**
@ -131,7 +129,7 @@ public class InstallToolCommand extends CLICommand {
return 0;
}
private static final class BuildIDs implements Callable<BuildIDs, IOException> {
private static final class BuildIDs extends MasterToSlaveCallable<BuildIDs, IOException> {
String job,number,id;
public BuildIDs call() throws IOException {

View File

@ -1,7 +1,9 @@
package hudson.cli.util;
import hudson.AbortException;
import hudson.remoting.Callable;
import jenkins.security.MasterToSlaveCallable;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.IOException;
@ -9,15 +11,12 @@ import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
/**
*
* @author vjuranek
* Reads a file (either a path or URL) over a channel.
*
* @author vjuranek
*/
public class ScriptLoader implements Callable<String,IOException> {
public class ScriptLoader extends MasterToSlaveCallable<String,IOException> {
private final String script;
@ -43,5 +42,4 @@ public class ScriptLoader implements Callable<String,IOException> {
s.close();
}
}
}

View File

@ -40,6 +40,7 @@ import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
import hudson.util.RingBufferLogHandler;
import hudson.util.XStream2;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.*;
import org.kohsuke.stapler.interceptor.RequirePOST;
@ -207,7 +208,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
}
private static final class SetLevel implements Callable<Void,Error> {
private static final class SetLevel extends MasterToSlaveCallable<Void,Error> {
/** known loggers (kept per slave), to avoid GC */
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private static final Set<Logger> loggers = new HashSet<Logger>();
private final String name;

View File

@ -34,7 +34,6 @@ import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
import hudson.remoting.Callable;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.security.ACL;
@ -43,6 +42,7 @@ import hudson.util.AlternativeUiTextProvider.Message;
import hudson.util.AtomicFileWriter;
import hudson.util.IOUtils;
import jenkins.model.Jenkins;
import jenkins.security.NotReallyRoleSensitiveCallable;
import org.acegisecurity.Authentication;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
@ -226,7 +226,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
// the test to see if the project already exists or not needs to be done in escalated privilege
// to avoid overwriting
ACL.impersonate(ACL.SYSTEM,new Callable<Void,IOException>() {
ACL.impersonate(ACL.SYSTEM,new NotReallyRoleSensitiveCallable<Void,IOException>() {
final Authentication user = Jenkins.getAuthentication();
@Override
public Void call() throws IOException {
@ -657,7 +657,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
throw new IOException("Expecting "+this.getClass()+" but got "+o.getClass()+" instead");
}
Items.whileUpdatingByXml(new Callable<Void,IOException>() {
Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<Void,IOException>() {
@Override public Void call() throws IOException {
onLoad(getParent(), getRootDir().getName());
return null;
@ -689,7 +689,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
// try to reflect the changes by reloading
getConfigFile().unmarshal(this);
Items.whileUpdatingByXml(new Callable<Void, IOException>() {
Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<Void, IOException>() {
@Override
public Void call() throws IOException {
onLoad(getParent(), getRootDir().getName());

View File

@ -38,7 +38,6 @@ import hudson.model.queue.WorkUnit;
import hudson.node_monitors.NodeMonitor;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.remoting.Callable;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
@ -61,6 +60,7 @@ import hudson.util.Futures;
import hudson.util.NamingThreadFactory;
import jenkins.model.Jenkins;
import jenkins.util.ContextResettingExecutorService;
import jenkins.security.MasterToSlaveCallable;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.StaplerRequest;
@ -1042,7 +1042,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
oneOffExecutors.remove(e);
}
private static class ListPossibleNames implements Callable<List<String>,IOException> {
private static class ListPossibleNames extends MasterToSlaveCallable<List<String>,IOException> {
public List<String> call() throws IOException {
List<String> names = new ArrayList<String>();
@ -1072,7 +1072,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
private static final long serialVersionUID = 1L;
}
private static class GetFallbackName implements Callable<String,IOException> {
private static class GetFallbackName extends MasterToSlaveCallable<String,IOException> {
public String call() throws IOException {
return System.getProperty("host.name");
}
@ -1150,7 +1150,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
w.close();
}
private static final class DumpExportTableTask implements Callable<String,IOException> {
private static final class DumpExportTableTask extends MasterToSlaveCallable<String,IOException> {
public String call() throws IOException {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);

View File

@ -25,7 +25,6 @@ package hudson.model;
import hudson.FilePath;
import hudson.Util;
import hudson.remoting.Callable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -43,6 +42,7 @@ import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.util.VirtualFile;
import org.apache.commons.io.IOUtils;
import org.apache.tools.zip.ZipEntry;
@ -461,7 +461,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
}
}
private static final class BuildChildPaths implements Callable<List<List<Path>>,IOException> {
private static final class BuildChildPaths extends MasterToSlaveCallable<List<List<Path>>,IOException> {
private final VirtualFile cur;
private final Locale locale;
BuildChildPaths(VirtualFile cur, Locale locale) {

View File

@ -26,7 +26,6 @@ package hudson.model;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.listeners.ItemListener;
import hudson.remoting.Callable;
import hudson.security.AccessControlled;
import hudson.util.CopyOnWriteMap;
import hudson.util.Function1;
@ -44,6 +43,7 @@ import java.io.InputStream;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.security.NotReallyRoleSensitiveCallable;
/**
* Defines a bunch of static methods to be used as a "mix-in" for {@link ItemGroup}
@ -222,7 +222,7 @@ public abstract class ItemGroupMixIn {
// reload from the new config
final File rootDir = result.getRootDir();
result = Items.whileUpdatingByXml(new Callable<T,IOException>() {
result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<T,IOException>() {
@Override public T call() throws IOException {
return (T) Items.load(parent, rootDir);
}
@ -253,7 +253,7 @@ public abstract class ItemGroupMixIn {
IOUtils.copy(xml,configXml);
// load it
TopLevelItem result = Items.whileUpdatingByXml(new Callable<TopLevelItem,IOException>() {
TopLevelItem result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<TopLevelItem,IOException>() {
@Override public TopLevelItem call() throws IOException {
return (TopLevelItem) Items.load(parent, dir);
}

View File

@ -59,6 +59,7 @@ import javax.servlet.ServletException;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.WorkspaceLocator;
import org.apache.commons.io.IOUtils;
@ -455,7 +456,7 @@ public abstract class Slave extends Node implements Serializable {
* <li>When it's read on this side as a return value, it morphs itself into {@link ClockDifference}.
* </ol>
*/
private static final class GetClockDifference1 implements Callable<ClockDifference,IOException> {
private static final class GetClockDifference1 extends MasterToSlaveCallable<ClockDifference,IOException> {
public ClockDifference call() {
// this method must be being invoked locally, which means the clock is in sync
return new ClockDifference(0);
@ -468,7 +469,7 @@ public abstract class Slave extends Node implements Serializable {
private static final long serialVersionUID = 1L;
}
private static final class GetClockDifference2 implements Callable<GetClockDifference3,IOException> {
private static final class GetClockDifference2 extends MasterToSlaveCallable<GetClockDifference3,IOException> {
/**
* Capture the time on the master when this object is sent to remote, which is when
* {@link GetClockDifference1#writeReplace()} is run.

View File

@ -26,6 +26,7 @@ package hudson.node_monitors;
import hudson.model.Computer;
import hudson.remoting.Callable;
import hudson.Extension;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
@ -56,7 +57,7 @@ public class ArchitectureMonitor extends NodeMonitor {
/**
* Obtains the string that represents the architecture.
*/
private static class GetArchTask implements Callable<String,IOException> {
private static class GetArchTask extends MasterToSlaveCallable<String,IOException> {
public String call() {
String os = System.getProperty("os.name");
String arch = System.getProperty("os.arch");

View File

@ -23,8 +23,8 @@
*/
package hudson.node_monitors;
import hudson.FilePath.FileCallable;
import hudson.Functions;
import jenkins.MasterToSlaveFileCallable;
import hudson.remoting.VirtualChannel;
import hudson.Util;
import hudson.slaves.OfflineCause;
@ -154,7 +154,7 @@ public abstract class DiskSpaceMonitorDescriptor extends AbstractAsyncNodeMonito
private static final long serialVersionUID = 2L;
}
protected static final class GetUsableSpace implements FileCallable<DiskSpace> {
protected static final class GetUsableSpace extends MasterToSlaveFileCallable<DiskSpace> {
public GetUsableSpace() {}
public DiskSpace invoke(File f, VirtualChannel channel) throws IOException {
long s = f.getUsableSpace();

View File

@ -28,6 +28,7 @@ import hudson.Extension;
import hudson.slaves.OfflineCause;
import hudson.model.Computer;
import hudson.remoting.Callable;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
@ -85,7 +86,7 @@ public class ResponseTimeMonitor extends NodeMonitor {
}
};
private static final class Step1 implements Callable<Data,IOException> {
private static final class Step1 extends MasterToSlaveCallable<Data,IOException> {
private Data cur;
private Step1(Data cur) {
@ -104,7 +105,7 @@ public class ResponseTimeMonitor extends NodeMonitor {
private static final long serialVersionUID = 1L;
}
private static final class Step2 implements Callable<Step3,IOException> {
private static final class Step2 extends MasterToSlaveCallable<Step3,IOException> {
private final Data cur;
private final long start = System.currentTimeMillis();

View File

@ -28,7 +28,7 @@ import hudson.Extension;
import hudson.Functions;
import hudson.model.Computer;
import jenkins.model.Jenkins;
import hudson.remoting.Callable;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.jvnet.hudson.MemoryMonitor;
import org.jvnet.hudson.MemoryUsage;
@ -100,7 +100,7 @@ public class SwapSpaceMonitor extends NodeMonitor {
/**
* Obtains the string that represents the architecture.
*/
private static class MonitorTask implements Callable<MemoryUsage,IOException> {
private static class MonitorTask extends MasterToSlaveCallable<MemoryUsage,IOException> {
public MemoryUsage call() throws IOException {
MemoryMonitor mm;
try {

View File

@ -25,7 +25,7 @@ package hudson.node_monitors;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import jenkins.MasterToSlaveFileCallable;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.remoting.Callable;
@ -83,7 +83,7 @@ public class TemporarySpaceMonitor extends AbstractDiskSpaceMonitor {
return DESCRIPTOR;
}
protected static final class GetTempSpace implements FileCallable<DiskSpace> {
protected static final class GetTempSpace extends MasterToSlaveFileCallable<DiskSpace> {
public DiskSpace invoke(File f, VirtualChannel channel) throws IOException {
// if the disk is really filled up we can't even create a single file,
// so calling File.createTempFile and figuring out the directory won't reliably work.

View File

@ -38,6 +38,7 @@ import hudson.util.HudsonIsRestarting;
import hudson.util.StreamTaskListener;
import static hudson.util.jna.GNUCLibrary.*;
import jenkins.security.MasterToSlaveCallable;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jvnet.libpam.impl.CLibrary.passwd;
import org.jvnet.solaris.libzfs.ACLBuilder;
@ -167,7 +168,7 @@ public class ZFSInstaller extends AdministrativeMonitor implements Serializable
// this is the actual creation of the file system.
// return true indicating a success
return SU.execute(listener, rootUsername, rootPassword, new Callable<String,IOException>() {
return SU.execute(listener, rootUsername, rootPassword, new MasterToSlaveCallable<String,IOException>() {
private static final long serialVersionUID = 7731167233498214301L;
public String call() throws IOException {

View File

@ -23,13 +23,13 @@
*/
package hudson.os.solaris;
import jenkins.MasterToSlaveFileCallable;
import hudson.FileSystemProvisioner;
import hudson.FilePath;
import hudson.WorkspaceSnapshot;
import hudson.FileSystemProvisionerDescriptor;
import hudson.Extension;
import hudson.remoting.VirtualChannel;
import hudson.FilePath.FileCallable;
import hudson.model.AbstractBuild;
import hudson.model.TaskListener;
import hudson.model.AbstractProject;
@ -52,7 +52,7 @@ public class ZFSProvisioner extends FileSystemProvisioner implements Serializabl
private final String rootDataset;
public ZFSProvisioner(Node node) throws IOException, InterruptedException {
rootDataset = node.getRootPath().act(new FileCallable<String>() {
rootDataset = node.getRootPath().act(new MasterToSlaveFileCallable<String>() {
private static final long serialVersionUID = -2142349338699797436L;
public String invoke(File f, VirtualChannel channel) throws IOException {
@ -68,7 +68,7 @@ public class ZFSProvisioner extends FileSystemProvisioner implements Serializabl
public void prepareWorkspace(AbstractBuild<?,?> build, FilePath ws, final TaskListener listener) throws IOException, InterruptedException {
final String name = build.getProject().getFullName();
ws.act(new FileCallable<Void>() {
ws.act(new MasterToSlaveFileCallable<Void>() {
private static final long serialVersionUID = 2129531727963121198L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
@ -87,7 +87,7 @@ public class ZFSProvisioner extends FileSystemProvisioner implements Serializabl
}
public void discardWorkspace(AbstractProject<?, ?> project, FilePath ws) throws IOException, InterruptedException {
ws.act(new FileCallable<Void>() {
ws.act(new MasterToSlaveFileCallable<Void>() {
private static final long serialVersionUID = 1916618107019257530L;
public Void invoke(File f, VirtualChannel channel) throws IOException {

View File

@ -3,13 +3,13 @@ package hudson.security;
import groovy.lang.Binding;
import hudson.FilePath;
import hudson.cli.CLICommand;
import hudson.remoting.Callable;
import hudson.util.spring.BeanBuilder;
import java.io.Console;
import java.io.IOException;
import jenkins.model.Jenkins;
import jenkins.security.ImpersonatingUserDetailsService;
import jenkins.security.SecurityListener;
import jenkins.security.MasterToSlaveCallable;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
@ -152,7 +152,7 @@ public abstract class AbstractPasswordBasedSecurityRealm extends SecurityRealm i
/**
* Asks for the password.
*/
private static class InteractivelyAskForPassword implements Callable<String,IOException> {
private static class InteractivelyAskForPassword extends MasterToSlaveCallable<String,IOException> {
public String call() throws IOException {
Console console = System.console();
if (console == null) return null; // no terminal

View File

@ -28,9 +28,9 @@ import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.PingThread;
import jenkins.security.MasterToSlaveCallable;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
@ -89,7 +89,7 @@ public class ChannelPinger extends ComputerListener {
setUpPingForChannel(channel, pingInterval);
}
private static class SetUpRemotePing implements Callable<Void, IOException> {
private static class SetUpRemotePing extends MasterToSlaveCallable<Void, IOException> {
private static final long serialVersionUID = -2702219700841759872L;
private int pingInterval;
public SetUpRemotePing(int pingInterval) {

View File

@ -29,14 +29,18 @@ import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
import hudson.remoting.CommandTransport;
import hudson.remoting.Launcher;
import hudson.remoting.SocketChannelStream;
import hudson.util.ClasspathBuilder;
import hudson.util.JVMBuilder;
import hudson.util.StreamCopyThread;
import jenkins.security.ChannelConfigurator;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -67,66 +71,84 @@ public class Channels {
* we kill the process.
*/
public static Channel forProcess(String name, ExecutorService execService, InputStream in, OutputStream out, OutputStream header, final Proc proc) throws IOException {
return new Channel(name, execService, in, out, header) {
/**
* Kill the process when the channel is severed.
*/
ChannelBuilder cb = new ChannelBuilder(name,execService) {
@Override
public synchronized void terminate(IOException e) {
super.terminate(e);
try {
proc.kill();
} catch (IOException x) {
// we are already in the error recovery mode, so just record it and move on
LOGGER.log(Level.INFO, "Failed to terminate the severed connection",x);
} catch (InterruptedException x) {
// process the interrupt later
Thread.currentThread().interrupt();
}
}
public Channel build(CommandTransport transport) throws IOException {
return new Channel(this,transport) {
/**
* Kill the process when the channel is severed.
*/
@Override
public synchronized void terminate(IOException e) {
super.terminate(e);
try {
proc.kill();
} catch (IOException x) {
// we are already in the error recovery mode, so just record it and move on
LOGGER.log(Level.INFO, "Failed to terminate the severed connection",x);
} catch (InterruptedException x) {
// process the interrupt later
Thread.currentThread().interrupt();
}
}
@Override
public synchronized void close() throws IOException {
super.close();
// wait for the child process to complete
try {
proc.join();
} catch (InterruptedException e) {
// process the interrupt later
Thread.currentThread().interrupt();
}
@Override
public synchronized void join() throws InterruptedException {
super.join();
// wait for the child process to complete, too
try {
proc.join();
} catch (IOException e) {
throw new IOError(e);
}
}
};
}
};
cb.withHeaderStream(header);
for (ChannelConfigurator cc : ChannelConfigurator.all()) {
cc.onChannelBuilding(cb,null); // TODO: what to pass as a context?
}
return cb.build(in,out);
}
public static Channel forProcess(String name, ExecutorService execService, final Process proc, OutputStream header) throws IOException {
final Thread thread = new StreamCopyThread(name + " stderr", proc.getErrorStream(), header);
thread.start();
return new Channel(name, execService, proc.getInputStream(), proc.getOutputStream(), header) {
/**
* Kill the process when the channel is severed.
*/
ChannelBuilder cb = new ChannelBuilder(name,execService) {
@Override
public synchronized void terminate(IOException e) {
super.terminate(e);
proc.destroy();
// the stderr copier should exit by itself
}
public Channel build(CommandTransport transport) throws IOException {
return new Channel(this,transport) {
/**
* Kill the process when the channel is severed.
*/
@Override
public synchronized void terminate(IOException e) {
super.terminate(e);
proc.destroy();
// the stderr copier should exit by itself
}
@Override
public synchronized void close() throws IOException {
super.close();
// wait for Maven to complete
try {
proc.waitFor();
thread.join();
} catch (InterruptedException e) {
// process the interrupt later
Thread.currentThread().interrupt();
}
@Override
public synchronized void join() throws InterruptedException {
super.join();
// wait for the child process to complete, too
proc.waitFor();
thread.join();
}
};
}
};
cb.withHeaderStream(header);
for (ChannelConfigurator cc : ChannelConfigurator.all()) {
cc.onChannelBuilding(cb,null); // TODO: what to pass as a context?
}
return cb.build(proc.getInputStream(),proc.getOutputStream());
}
/**

View File

@ -23,16 +23,18 @@
*/
package hudson.slaves;
import hudson.model.Computer;
import jenkins.model.Jenkins;
import hudson.model.TaskListener;
import hudson.model.Node;
import hudson.ExtensionPoint;
import hudson.AbortException;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.TaskListener;
import org.jenkinsci.remoting.CallableDecorator;
import hudson.remoting.Channel;
import hudson.AbortException;
import hudson.remoting.ChannelBuilder;
import jenkins.model.Jenkins;
import java.io.IOException;
@ -46,6 +48,7 @@ import javax.annotation.Nonnull;
* @since 1.246
*/
public abstract class ComputerListener implements ExtensionPoint {
/**
* Called before a {@link ComputerLauncher} is asked to launch a connection with {@link Computer}.
*

View File

@ -30,8 +30,8 @@ import hudson.model.Computer;
import hudson.util.TimeUnit2;
import hudson.remoting.VirtualChannel;
import hudson.remoting.Channel;
import hudson.remoting.Callable;
import hudson.Extension;
import jenkins.security.SlaveToMasterCallable;
import java.io.IOException;
import java.util.logging.Logger;
@ -102,7 +102,7 @@ public class ConnectionActivityMonitor extends AsyncPeriodicWork {
public boolean enabled = Boolean.getBoolean(ConnectionActivityMonitor.class.getName()+".enabled");
private static final PingCommand PING_COMMAND = new PingCommand();
private static final class PingCommand implements Callable<Void,RuntimeException> {
private static final class PingCommand extends SlaveToMasterCallable<Void,RuntimeException> {
public Void call() throws RuntimeException {
return null;
}

View File

@ -24,6 +24,8 @@
package hudson.slaves;
import hudson.AbortException;
import hudson.remoting.ChannelBuilder;
import hudson.util.IOUtils;
import hudson.FilePath;
import hudson.Util;
import hudson.model.Computer;
@ -34,7 +36,6 @@ import hudson.model.Queue;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.model.User;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.Launcher;
import hudson.remoting.VirtualChannel;
@ -46,18 +47,10 @@ import hudson.util.RingBufferLogHandler;
import hudson.util.StreamTaskListener;
import hudson.util.io.ReopenableFileOutputStream;
import hudson.util.io.ReopenableRotatingFileOutputStream;
import jenkins.model.Jenkins;
import jenkins.slaves.EncryptedSlaveAgentJnlpFile;
import jenkins.slaves.JnlpSlaveAgentProtocol;
import jenkins.slaves.systemInfo.SlaveSystemInfo;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.io.IOUtils;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;
import org.kohsuke.stapler.interceptor.RequirePOST;
@ -81,6 +74,14 @@ import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
import static hudson.slaves.SlaveComputer.LogHolder.*;
import jenkins.security.ChannelConfigurator;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.JnlpSlaveAgentProtocol;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
/**
@ -345,7 +346,15 @@ public class SlaveComputer extends Computer {
* so the implementation of the listener doesn't need to do that again.
*/
public void setChannel(InputStream in, OutputStream out, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException {
Channel channel = new Channel(nodeName,threadPoolForRemoting, Channel.Mode.NEGOTIATE, in,out, launchLog);
ChannelBuilder cb = new ChannelBuilder(nodeName,threadPoolForRemoting)
.withMode(Channel.Mode.NEGOTIATE)
.withHeaderStream(launchLog);
for (ChannelConfigurator cc : ChannelConfigurator.all()) {
cc.onChannelBuilding(cb,this);
}
Channel channel = cb.build(in,out);
setChannel(channel,launchLog,listener);
}
@ -393,7 +402,7 @@ public class SlaveComputer extends Computer {
return channel.call(new LoadingTime(true));
}
static class LoadingCount implements Callable<Integer,RuntimeException> {
static class LoadingCount extends MasterToSlaveCallable<Integer,RuntimeException> {
private final boolean resource;
LoadingCount(boolean resource) {
this.resource = resource;
@ -404,13 +413,13 @@ public class SlaveComputer extends Computer {
}
}
static class LoadingPrefetchCacheCount implements Callable<Integer,RuntimeException> {
static class LoadingPrefetchCacheCount extends MasterToSlaveCallable<Integer,RuntimeException> {
@Override public Integer call() {
return Channel.current().classLoadingPrefetchCacheCount.get();
}
}
static class LoadingTime implements Callable<Long,RuntimeException> {
static class LoadingTime extends MasterToSlaveCallable<Long,RuntimeException> {
private final boolean resource;
LoadingTime(boolean resource) {
this.resource = resource;
@ -677,19 +686,19 @@ public class SlaveComputer extends Computer {
private static final Logger logger = Logger.getLogger(SlaveComputer.class.getName());
private static final class SlaveVersion implements Callable<String,IOException> {
private static final class SlaveVersion extends MasterToSlaveCallable<String,IOException> {
public String call() throws IOException {
try { return Launcher.VERSION; }
catch (Throwable ex) { return "< 1.335"; } // Older slave.jar won't have VERSION
}
}
private static final class DetectOS implements Callable<Boolean,IOException> {
private static final class DetectOS extends MasterToSlaveCallable<Boolean,IOException> {
public Boolean call() throws IOException {
return File.pathSeparatorChar==':';
}
}
private static final class DetectDefaultCharset implements Callable<String,IOException> {
private static final class DetectDefaultCharset extends MasterToSlaveCallable<String,IOException> {
public String call() throws IOException {
return Charset.defaultCharset().name();
}
@ -706,7 +715,7 @@ public class SlaveComputer extends Computer {
static final RingBufferLogHandler SLAVE_LOG_HANDLER = new RingBufferLogHandler();
}
private static class SlaveInitializer implements Callable<Void,RuntimeException> {
private static class SlaveInitializer extends MasterToSlaveCallable<Void,RuntimeException> {
public Void call() {
// avoid double installation of the handler. JNLP slaves can reconnect to the master multiple times
// and each connection gets a different RemoteClassLoader, so we need to evict them by class name,
@ -760,7 +769,7 @@ public class SlaveComputer extends Computer {
return SlaveSystemInfo.all();
}
private static class SlaveLogFetcher implements Callable<List<LogRecord>,RuntimeException> {
private static class SlaveLogFetcher extends MasterToSlaveCallable<List<LogRecord>,RuntimeException> {
public List<LogRecord> call() {
return new ArrayList<LogRecord>(SLAVE_LOG_HANDLER.getView());
}

View File

@ -24,6 +24,7 @@
package hudson.tasks;
import hudson.FilePath;
import jenkins.MasterToSlaveFileCallable;
import hudson.Launcher;
import hudson.Util;
import hudson.Extension;
@ -247,7 +248,7 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep {
}
}
private static final class ListFiles implements FilePath.FileCallable<Map<String,String>> {
private static final class ListFiles extends MasterToSlaveFileCallable<Map<String,String>> {
private static final long serialVersionUID = 1;
private final String includes, excludes;
private final boolean defaultExcludes;

View File

@ -27,7 +27,7 @@ import com.google.common.collect.ImmutableMap;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import jenkins.MasterToSlaveFileCallable;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
@ -209,7 +209,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
final long buildTimestamp = build.getTimeInMillis();
List<Record> records = ws.act(new FileCallable<List<Record>>() {
List<Record> records = ws.act(new MasterToSlaveFileCallable<List<Record>>() {
public List<Record> invoke(File baseDir, VirtualChannel channel) throws IOException {
List<Record> results = new ArrayList<Record>();

View File

@ -24,13 +24,13 @@
package hudson.tasks;
import hudson.Extension;
import jenkins.MasterToSlaveFileCallable;
import hudson.Launcher;
import hudson.Functions;
import hudson.EnvVars;
import hudson.Util;
import hudson.CopyOnWrite;
import hudson.Launcher.LocalLauncher;
import hudson.FilePath.FileCallable;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
@ -42,7 +42,6 @@ import jenkins.mvn.GlobalMavenConfig;
import jenkins.mvn.GlobalSettingsProvider;
import jenkins.mvn.SettingsProvider;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.slaves.NodeSpecific;
import hudson.tasks._maven.MavenConsoleAnnotator;
@ -59,6 +58,7 @@ import hudson.util.VariableResolver.ByMap;
import hudson.util.VariableResolver.Union;
import hudson.util.FormValidation;
import hudson.util.XStream2;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
@ -217,7 +217,7 @@ public class Maven extends Builder {
* Looks for <tt>pom.xlm</tt> or <tt>project.xml</tt> to determine the maven executable
* name.
*/
private static final class DecideDefaultMavenCommand implements FileCallable<String> {
private static final class DecideDefaultMavenCommand extends MasterToSlaveFileCallable<String> {
private static final long serialVersionUID = -2327576423452215146L;
// command line arguments.
private final String arguments;
@ -506,7 +506,7 @@ public class Maven extends Builder {
public boolean meetsMavenReqVersion(Launcher launcher, int mavenReqVersion) throws IOException, InterruptedException {
// FIXME using similar stuff as in the maven plugin could be better
// olamy : but will add a dependency on maven in core -> so not so good
String mavenVersion = launcher.getChannel().call(new Callable<String,IOException>() {
String mavenVersion = launcher.getChannel().call(new MasterToSlaveCallable<String,IOException>() {
private static final long serialVersionUID = -4143159957567745621L;
public String call() throws IOException {
@ -562,7 +562,7 @@ public class Maven extends Builder {
* Gets the executable path of this maven on the given target system.
*/
public String getExecutable(Launcher launcher) throws IOException, InterruptedException {
return launcher.getChannel().call(new Callable<String,IOException>() {
return launcher.getChannel().call(new MasterToSlaveCallable<String,IOException>() {
private static final long serialVersionUID = 2373163112639943768L;
public String call() throws IOException {

View File

@ -28,12 +28,12 @@ import hudson.Functions;
import hudson.Util;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.util.FormValidation;
import java.io.IOException;
import java.io.ObjectStreamException;
import hudson.util.LineEndingConversion;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
@ -172,7 +172,7 @@ public class Shell extends CommandInterpreter {
return FormValidation.validateExecutable(value);
}
private static final class Shellinterpreter implements Callable<String, IOException> {
private static final class Shellinterpreter extends MasterToSlaveCallable<String, IOException> {
private static final long serialVersionUID = 1L;

View File

@ -40,6 +40,7 @@ import hudson.util.FormValidation;
import hudson.util.HttpResponses;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import net.sf.json.JSONObject;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpClient;
@ -297,7 +298,7 @@ public class JDKInstaller extends ToolInstaller {
public interface FileSystem {
void delete(String file) throws IOException, InterruptedException;
void chmod(String file,int mode) throws IOException, InterruptedException;
InputStream read(String file) throws IOException;
InputStream read(String file) throws IOException, InterruptedException;
/**
* List sub-directories of the given directory and just return the file name portion.
*/
@ -320,7 +321,7 @@ public class JDKInstaller extends ToolInstaller {
$(file).chmod(mode);
}
public InputStream read(String file) throws IOException {
public InputStream read(String file) throws IOException, InterruptedException {
return $(file).read();
}
@ -530,7 +531,7 @@ public class JDKInstaller extends ToolInstaller {
throw new DetectionFailedException("Unknown CPU name: "+arch);
}
static class GetCurrentPlatform implements Callable<Platform,DetectionFailedException> {
static class GetCurrentPlatform extends MasterToSlaveCallable<Platform,DetectionFailedException> {
private static final long serialVersionUID = 1L;
public Platform call() throws DetectionFailedException {
return current();
@ -592,7 +593,7 @@ public class JDKInstaller extends ToolInstaller {
throw new DetectionFailedException("Unknown CPU architecture: "+arch);
}
static class GetCurrentCPU implements Callable<CPU,DetectionFailedException> {
static class GetCurrentCPU extends MasterToSlaveCallable<CPU,DetectionFailedException> {
private static final long serialVersionUID = 1L;
public CPU call() throws DetectionFailedException {
return current();

View File

@ -26,7 +26,7 @@ package hudson.tools;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import jenkins.MasterToSlaveFileCallable;
import hudson.ProxyConfiguration;
import hudson.Util;
import hudson.Functions;
@ -116,7 +116,7 @@ public class ZipExtractionInstaller extends ToolInstaller {
* Sets execute permission on all files, since unzip etc. might not do this.
* Hackish, is there a better way?
*/
static class ChmodRecAPlusX implements FileCallable<Void> {
static class ChmodRecAPlusX extends MasterToSlaveFileCallable<Void> {
private static final long serialVersionUID = 1L;
public Void invoke(File d, VirtualChannel channel) throws IOException {
if(!Functions.isWindows())

View File

@ -29,14 +29,13 @@ import com.sun.jna.ptr.IntByReference;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Util;
import jenkins.model.Jenkins;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.slaves.SlaveComputer;
import hudson.util.ProcessTree.OSProcess;
import hudson.util.ProcessTreeRemoting.IOSProcess;
import hudson.util.ProcessTreeRemoting.IProcessTree;
import jenkins.security.SlaveToMasterCallable;
import org.jvnet.winp.WinProcess;
import org.jvnet.winp.WinpException;
@ -150,7 +149,7 @@ public abstract class ProcessTree implements Iterable<OSProcess>, IProcessTree,
try {
VirtualChannel channelToMaster = SlaveComputer.getChannelToMaster();
if (channelToMaster!=null) {
killers = channelToMaster.call(new Callable<List<ProcessKiller>, IOException>() {
killers = channelToMaster.call(new SlaveToMasterCallable<List<ProcessKiller>, IOException>() {
public List<ProcessKiller> call() throws IOException {
return new ArrayList<ProcessKiller>(ProcessKiller.all());
}

View File

@ -29,11 +29,11 @@ import hudson.FilePath;
import hudson.Functions;
import jenkins.model.Jenkins;
import hudson.remoting.AsyncFutureImpl;
import hudson.remoting.Callable;
import hudson.remoting.DelegatingCallable;
import hudson.remoting.Future;
import hudson.remoting.VirtualChannel;
import hudson.security.AccessControlled;
import jenkins.security.MasterToSlaveCallable;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.kohsuke.stapler.StaplerRequest;
@ -70,7 +70,7 @@ public final class RemotingDiagnostics {
return channel.call(new GetSystemProperties());
}
private static final class GetSystemProperties implements Callable<Map<Object,Object>,RuntimeException> {
private static final class GetSystemProperties extends MasterToSlaveCallable<Map<Object,Object>,RuntimeException> {
public Map<Object,Object> call() {
return new TreeMap<Object,Object>(System.getProperties());
}
@ -89,7 +89,7 @@ public final class RemotingDiagnostics {
return channel.callAsync(new GetThreadDump());
}
private static final class GetThreadDump implements Callable<Map<String,String>,RuntimeException> {
private static final class GetThreadDump extends MasterToSlaveCallable<Map<String,String>,RuntimeException> {
public Map<String,String> call() {
Map<String,String> r = new LinkedHashMap<String,String>();
ThreadInfo[] data = Functions.getThreadInfos();
@ -108,7 +108,7 @@ public final class RemotingDiagnostics {
return channel.call(new Script(script));
}
private static final class Script implements DelegatingCallable<String,RuntimeException> {
private static final class Script extends MasterToSlaveCallable<String,RuntimeException> implements DelegatingCallable<String,RuntimeException> {
private final String script;
private transient ClassLoader cl;
@ -150,7 +150,7 @@ public final class RemotingDiagnostics {
* Obtains the heap dump in an HPROF file.
*/
public static FilePath getHeapDump(VirtualChannel channel) throws IOException, InterruptedException {
return channel.call(new Callable<FilePath, IOException>() {
return channel.call(new MasterToSlaveCallable<FilePath, IOException>() {
public FilePath call() throws IOException {
final File hprof = File.createTempFile("hudson-heapdump", "hprof");
hprof.delete();

View File

@ -25,9 +25,9 @@ package hudson.util.io;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import jenkins.model.Jenkins;
import jenkins.security.SlaveToMasterCallable;
import org.dom4j.io.SAXReader;
import java.io.IOException;
@ -78,7 +78,7 @@ public abstract class ParserConfigurator implements ExtensionPoint, Serializable
if (Jenkins.getInstance()==null) {
Channel ch = Channel.current();
if (ch!=null)
all = ch.call(new Callable<Collection<ParserConfigurator>, IOException>() {
all = ch.call(new SlaveToMasterCallable<Collection<ParserConfigurator>, IOException>() {
private static final long serialVersionUID = -2178106894481500733L;

View File

@ -0,0 +1,106 @@
package jenkins;
import hudson.FilePath;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
import javax.annotation.CheckForNull;
import java.io.File;
/**
* Inspects {@link FilePath} access from remote channels.
*
* @author Kohsuke Kawaguchi
* @see FilePath
* @since 1.THU
*/
public abstract class FilePathFilter {
/**
* Checks if the given file/directory can be read.
*
* On POSIX, this corresponds to the 'r' permission of the file/directory itself.
*/
public void read(File f) throws SecurityException {}
/**
* Checks if the given file can be written.
*
* On POSIX, this corresponds to the 'w' permission of the file itself.
*/
public void write(File f) throws SecurityException {}
/**
* Checks if the given directory can be created.
*
* On POSIX, this corresponds to the 'w' permission of the parent directory.
*/
public void mkdirs(File f) throws SecurityException {}
/**
* Checks if the given file can be created.
*
* On POSIX, this corresponds to the 'w' permission of the parent directory.
*/
public void create(File f) throws SecurityException {}
/**
* Checks if the given file/directory can be deleted.
*
* On POSIX, this corresponds to the 'w' permission of the parent directory.
*/
public void delete(File f) throws SecurityException {}
/**
* Checks if the metadata of the given file/directory (as opposed to the content) can be accessed.
*
* On POSIX, this corresponds to the 'r' permission of the parent directory.
*/
public void stat(File f) throws SecurityException {}
public final void installTo(ChannelBuilder cb) {
synchronized (cb) {
FilePathFilterAggregator filters = (FilePathFilterAggregator) cb.getProperties().get(FilePathFilterAggregator.KEY);
if (filters==null) {
filters = new FilePathFilterAggregator();
cb.withProperty(FilePathFilterAggregator.KEY,filters);
}
filters.add(this);
}
}
public final void uninstallFrom(Channel ch) {
synchronized (ch) {
FilePathFilterAggregator filters = ch.getProperty(FilePathFilterAggregator.KEY);
if (filters!=null) {
filters.remove(this);
}
}
}
/**
* Returns an {@link FilePathFilter} object that represents all the in-scope filters,
* or null if none is needed.
*/
public static @CheckForNull FilePathFilter current() {
Channel ch = Channel.current();
if (ch==null) return null;
return ch.getProperty(FilePathFilterAggregator.KEY);
}
/**
* Immutable instance that represents the empty filter.
*/
public static final FilePathFilter EMPTY = new FilePathFilterAggregator() {
@Override
public void add(FilePathFilter f) {
// noop
}
@Override
public String toString() {
return "None";
}
};
}

View File

@ -0,0 +1,68 @@
package jenkins;
import hudson.FilePath;
import hudson.remoting.ChannelProperty;
import java.io.File;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Maintains a bundle of {@link FilePathFilter} and implement a hook that broadcasts to all the filters.
*
* Accessible as channel property.
*
* @author Kohsuke Kawaguchi
* @see FilePath
* @since 1.THU
*/
class FilePathFilterAggregator extends FilePathFilter {
private final CopyOnWriteArrayList<FilePathFilter> all = new CopyOnWriteArrayList<FilePathFilter>();
public void add(FilePathFilter f) {
all.add(f);
}
public void remove(FilePathFilter f) {
all.remove(f);
}
public void read(File f) throws SecurityException {
for (FilePathFilter filter : all) {
filter.read(f);
}
}
@Override
public void mkdirs(File f) throws SecurityException {
for (FilePathFilter filter : all) {
filter.mkdirs(f);
}
}
public void write(File f) throws SecurityException {
for (FilePathFilter filter : all) {
filter.write(f);
}
}
public void create(File f) throws SecurityException {
for (FilePathFilter filter : all) {
filter.create(f);
}
}
public void delete(File f) throws SecurityException {
for (FilePathFilter filter : all) {
filter.delete(f);
}
}
public void stat(File f) throws SecurityException {
for (FilePathFilter filter : all) {
filter.stat(f);
}
}
@Override public String toString() {
return "FilePathFilterAggregator" + all;
}
static final ChannelProperty<FilePathFilterAggregator> KEY = new ChannelProperty<FilePathFilterAggregator>(FilePathFilterAggregator.class, "FilePathFilters");
}

View File

@ -0,0 +1,18 @@
package jenkins;
import hudson.FilePath.FileCallable;
import jenkins.security.Roles;
import org.jenkinsci.remoting.RoleChecker;
/**
* {@link FileCallable}s that are meant to be only used on the master.
*
* @since 1.THU
*/
public abstract class MasterToSlaveFileCallable<T> implements FileCallable<T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.SLAVE);
}
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,18 @@
package jenkins;
import hudson.FilePath.FileCallable;
import jenkins.security.Roles;
import org.jenkinsci.remoting.RoleChecker;
/**
* {@link FileCallable}s that can be executed on the master, sent by the slave.
*
* @since 1.THU
*/
public abstract class SlaveToMasterFileCallable<T> implements FileCallable<T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.MASTER);
}
private static final long serialVersionUID = 1L;
}

View File

@ -196,6 +196,7 @@ import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import jenkins.security.ConfidentialKey;
import jenkins.security.ConfidentialStore;
import jenkins.security.SecurityListener;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.WorkspaceLocator;
import jenkins.util.Timer;
import jenkins.util.io.FileBoolean;
@ -2034,7 +2035,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
@Override
public Callable<ClockDifference, IOException> getClockDifferenceCallable() {
return new Callable<ClockDifference, IOException>() {
return new MasterToSlaveCallable<ClockDifference, IOException>() {
public ClockDifference call() throws IOException {
return new ClockDifference(0);
}

View File

@ -0,0 +1,132 @@
package jenkins.security;
import hudson.Extension;
import hudson.remoting.Callable;
import hudson.remoting.ChannelBuilder;
import org.jenkinsci.remoting.Role;
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Inspects {@link Callable}s that run on the master.
*
* @author Kohsuke Kawaguchi
* @since 1.THU
*/
@Restricted(NoExternalUse.class) // used implicitly via listener
public class CallableDirectionChecker extends RoleChecker {
private static final String BYPASS_PROP = CallableDirectionChecker.class.getName()+".allow";
/**
* Switch to disable all the defense mechanism completely.
*
* This is an escape hatch in case the fix breaks something critical, to allow the user
* to keep operation.
*/
public static boolean BYPASS = Boolean.getBoolean(BYPASS_PROP);
/**
* Whitelist of {@link RoleSensitive} class names (mostly {@link Callable} classes) that are allowed
* to pass through.
*/
public static ConcurrentMap<String,Void> BYPASS_CALLABLES = new ConcurrentHashMap<String,Void>();
/**
* Test feature that is meant to replace all logging, and log what would have been violations.
* TODO delete before release
*/
private static final PrintWriter BYPASS_LOG;
static {
String log = System.getProperty(CallableDirectionChecker.class.getName()+".log");
if (log == null) {
BYPASS_LOG = null;
} else {
try {
BYPASS_LOG = new PrintWriter(new OutputStreamWriter(new FileOutputStream(log, true)), true);
} catch (FileNotFoundException x) {
throw new ExceptionInInitializerError(x);
}
}
}
private CallableDirectionChecker() {}
@Override
public void check(RoleSensitive subject, @Nonnull Collection<Role> expected) throws SecurityException {
final String name = subject.getClass().getName();
if (expected.contains(Roles.MASTER)) {
if (BYPASS_LOG == null) {
LOGGER.log(Level.FINE, "Executing {0} is allowed since it is targeted for the master role", name);
}
return; // known to be safe
}
if (BYPASS_LOG != null) {
BYPASS_LOG.println(name);
return;
}
if (isWhitelisted(name)) {
// this subject is dubious, but we are letting it through as per whitelisting
LOGGER.log(Level.FINE, "Explicitly allowing {0} to be sent from slave to master", name);
return;
}
throw new SecurityException("Sending " + name + " from slave to master is prohibited");
}
/**
* Is this subject class name whitelisted?
*/
private boolean isWhitelisted(String name) {
if (BYPASS) // everything is whitelisted
return true;
if (BYPASS_CALLABLES.containsKey(name))
return true;
if (Boolean.getBoolean(BYPASS_PROP+"."+name)) {
BYPASS_CALLABLES.put(name,null);
return true;
}
return false;
}
/**
* Installs {@link CallableDirectionChecker} to every channel.
*/
@Restricted(DoNotUse.class) // impl
@Extension
public static class ChannelConfiguratorImpl extends ChannelConfigurator {
@Override
public void onChannelBuilding(ChannelBuilder builder, Object context) {
// if the big red emergency button is pressed, then we need to disable the defense mechanism,
// including enabling classloading.
if (!BYPASS && BYPASS_LOG == null) {
builder.withRemoteClassLoadingAllowed(false);
}
// In either of the above cases, the check method will return normally, but may log things.
builder.withRoleChecker(new CallableDirectionChecker());
}
}
private static final Logger LOGGER = Logger.getLogger(CallableDirectionChecker.class.getName());
}

View File

@ -0,0 +1,44 @@
package jenkins.security;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
import hudson.slaves.SlaveComputer;
import jenkins.model.Jenkins;
import javax.annotation.Nullable;
/**
* Intercepts the new creation of {@link Channel} and tweak its configuration.
*
* @author Kohsuke Kawaguchi
* @since 1.THU
*/
public abstract class ChannelConfigurator implements ExtensionPoint {
/**
* Called whenever a new channel is being built.
*
* @param builder
* Configures the newly built channel. The callee
* can call its methods to modify its settings.
* @param context
* The parameter that helps the callee determines what this channel is for.
* Legacy callers do not always provide this information, in which case this value might be null.
*
* Possible known values include:
*
* <dl>
* <dt>{@link SlaveComputer}
* <dd>When a channel is being established to talk to a slave.
* </dl>
*/
public void onChannelBuilding(ChannelBuilder builder, @Nullable Object context) {}
/**
* All the registered {@link ChannelConfigurator}s.
*/
public static ExtensionList<ChannelConfigurator> all() {
return Jenkins.getInstance().getExtensionList(ChannelConfigurator.class);
}
}

View File

@ -0,0 +1,106 @@
/*
* The MIT License
*
* Copyright 2014 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 jenkins.security;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
import hudson.slaves.ComputerListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.FilePathFilter;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Blocks slaves from writing to files on the master by default.
*/
@Restricted(DoNotUse.class) // impl
@Extension public class DefaultFilePathFilter extends ChannelConfigurator {
/**
* Escape hatch to disable this check completely.
*/
public static boolean BYPASS = Boolean.getBoolean(DefaultFilePathFilter.class.getName()+".allow");
private static final PrintWriter BYPASS_LOG; // TODO delete before release
static {
String log = System.getProperty("jenkins.security.DefaultFilePathFilter.log");
if (log == null) {
BYPASS_LOG = null;
} else {
try {
BYPASS_LOG = new PrintWriter(new OutputStreamWriter(new FileOutputStream(log, true)), true);
} catch (FileNotFoundException x) {
throw new ExceptionInInitializerError(x);
}
}
}
private static final Logger LOGGER = Logger.getLogger(DefaultFilePathFilter.class.getName());
@Override
public void onChannelBuilding(ChannelBuilder builder, Object context) {
new FilePathFilter() {
private void op(String op, File f) throws SecurityException {
if (BYPASS_LOG != null) {
BYPASS_LOG.println(op + " " + f);
return;
}
if (BYPASS) {
LOGGER.log(Level.FINE, "slave allowed to {0} {1}", new Object[] {op, f});
} else {
// TODO allow finer-grained control, for example by regexp (or Ant pattern) of relative path inside $JENKINS_HOME
throw new SecurityException("slave may not " + op + " " + f);
}
}
@Override public void read(File f) throws SecurityException {
op("read", f);
}
@Override public void write(File f) throws SecurityException {
op("write", f);
}
@Override public void mkdirs(File f) throws SecurityException {
op("mkdirs", f);
}
@Override public void create(File f) throws SecurityException {
op("create", f);
}
@Override public void delete(File f) throws SecurityException {
op("delete", f);
}
@Override public void stat(File f) throws SecurityException {
op("stat", f);
}
}.installTo(builder);
}
}

View File

@ -0,0 +1,22 @@
package jenkins.security;
import hudson.remoting.Callable;
import org.jenkinsci.remoting.Role;
import org.jenkinsci.remoting.RoleChecker;
import java.util.Collection;
/**
* Convenient {@link Callable} meant to be run on slave.
*
* @author Kohsuke Kawaguchi
* @since 1.THU
*/
public abstract class MasterToSlaveCallable<V, T extends Throwable> implements Callable<V,T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this,Roles.SLAVE);
}
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,19 @@
package jenkins.security;
import hudson.remoting.Callable;
import org.jenkinsci.remoting.RoleChecker;
/**
* {@link Callable} adapter for situations where Callable is not used for remoting but
* just as a convenient function that has parameterized return value and exception type.
*
* @author Kohsuke Kawaguchi
* @since 1.THU
*/
public abstract class NotReallyRoleSensitiveCallable<V,T extends Throwable> implements Callable<V,T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
// not meant to be used where this matters
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,34 @@
package jenkins.security;
import org.jenkinsci.remoting.Role;
/**
* Predefined {@link Role}s in Jenkins.
*
* <p>
* In Jenkins, there is really only one interesting role, which is the Jenkins master.
* Slaves, CLI, and Maven processes are all going to load classes from the master,
* which means it accepts anything that the master asks for, and thus they need
* not have any role.
*
* @author Kohsuke Kawaguchi
* @since 1.THU
*/
public class Roles {
/**
* Indicates that a callable runs on masters, requested by slaves/CLI/maven/whatever.
*/
public static final Role MASTER = new Role("master");
/**
* Indicates that a callable is meant to run on slaves.
*
* This isn't used to reject callables to run on the slave, but rather to allow
* the master to promptly reject callables that are really not meant to be run on
* the master (as opposed to ones that do not have that information, which gets
* {@link Role#UNKNOWN})
*/
public static final Role SLAVE = new Role("slave");
private Roles() {}
}

View File

@ -0,0 +1,22 @@
package jenkins.security;
import hudson.remoting.Callable;
import org.jenkinsci.remoting.Role;
import org.jenkinsci.remoting.RoleChecker;
import java.util.Collection;
/**
* Convenient {@link Callable} that are meant to run on the master (sent by slave/CLI/etc).
*
* @author Kohsuke Kawaguchi
* @since 1.THU
*/
public abstract class SlaveToMasterCallable<V, T extends Throwable> implements Callable<V,T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this,Roles.MASTER);
}
private static final long serialVersionUID = 1L;
}

View File

@ -4,11 +4,11 @@ import hudson.Extension;
import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.StandardOutputStream;
import hudson.slaves.ComputerListener;
import hudson.util.jna.GNUCLibrary;
import jenkins.security.MasterToSlaveCallable;
import java.io.File;
import java.io.FileDescriptor;
@ -35,7 +35,7 @@ public class StandardOutputSwapper extends ComputerListener {
}
}
private static final class ChannelSwapper implements Callable<Boolean,Exception> {
private static final class ChannelSwapper extends MasterToSlaveCallable<Boolean,Exception> {
public Boolean call() throws Exception {
if (File.pathSeparatorChar==';') return false; // Windows

View File

@ -3,7 +3,6 @@ package jenkins.slaves.restarter;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.Engine;
import hudson.remoting.EngineListener;
import hudson.remoting.EngineListenerAdapter;
@ -19,6 +18,7 @@ import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
import jenkins.security.MasterToSlaveCallable;
/**
* Actual slave restart logic.
@ -49,7 +49,7 @@ public class JnlpSlaveRestarterInstaller extends ComputerListener implements Ser
VirtualChannel ch = c.getChannel();
if (ch==null) return; // defensive check
List<SlaveRestarter> effective = ch.call(new Callable<List<SlaveRestarter>, IOException>() {
List<SlaveRestarter> effective = ch.call(new MasterToSlaveCallable<List<SlaveRestarter>, IOException>() {
public List<SlaveRestarter> call() throws IOException {
Engine e = Engine.current();
if (e == null) return null; // not running under Engine

View File

@ -41,6 +41,8 @@ import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import jenkins.MasterToSlaveFileCallable;
/**
* Abstraction over {@link File}, {@link FilePath}, or other items such as network resources or ZIP entries.
* Assumed to be read-only and makes very limited assumptions, just enough to display content and traverse directories.
@ -354,7 +356,11 @@ public abstract class VirtualFile implements Comparable<VirtualFile>, Serializab
}
}
@Override public InputStream open() throws IOException {
return f.read();
try {
return f.read();
} catch (InterruptedException x) {
throw (IOException) new IOException(x.toString()).initCause(x);
}
}
@Override public <V> V run(Callable<V,IOException> callable) throws IOException {
try {
@ -364,7 +370,7 @@ public abstract class VirtualFile implements Comparable<VirtualFile>, Serializab
}
}
}
private static final class Scanner implements FilePath.FileCallable<String[]> {
private static final class Scanner extends MasterToSlaveFileCallable<String[]> {
private final String glob;
Scanner(String glob) {
this.glob = glob;
@ -381,7 +387,7 @@ public abstract class VirtualFile implements Comparable<VirtualFile>, Serializab
}
}
private static final class Readable implements FilePath.FileCallable<Boolean> {
private static final class Readable extends MasterToSlaveFileCallable<Boolean> {
@Override public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
return f.canRead();
}

View File

@ -29,6 +29,7 @@ import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.util.ProcessTree;
import hudson.util.StreamTaskListener;
import jenkins.security.MasterToSlaveCallable;
import org.apache.commons.io.FileUtils;
import org.jvnet.hudson.test.Bug;
@ -73,7 +74,7 @@ public class LauncherTest extends ChannelTestCase {
}
}
private static final Callable<Object,RuntimeException> NOOP = new Callable<Object,RuntimeException>() {
private static final Callable<Object,RuntimeException> NOOP = new MasterToSlaveCallable<Object,RuntimeException>() {
public Object call() throws RuntimeException {
return null;
}

View File

@ -2,6 +2,7 @@ package hudson.os;
import hudson.remoting.Callable;
import hudson.util.StreamTaskListener;
import jenkins.security.MasterToSlaveCallable;
import java.io.FileOutputStream;
@ -10,7 +11,7 @@ import java.io.FileOutputStream;
*/
public class SUTester {
public static void main(String[] args) throws Throwable {
SU.execute(StreamTaskListener.fromStdout(),"kohsuke","bogus",new Callable<Object, Throwable>() {
SU.execute(StreamTaskListener.fromStdout(),"kohsuke","bogus",new MasterToSlaveCallable<Object, Throwable>() {
public Object call() throws Throwable {
System.out.println("Touching /tmp/x");
new FileOutputStream("/tmp/x").close();

View File

@ -5,6 +5,7 @@ import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.util.ProcessTree.OSProcess;
import hudson.util.ProcessTree.ProcessCallable;
import jenkins.security.MasterToSlaveCallable;
import java.io.IOException;
import java.io.Serializable;
@ -39,7 +40,7 @@ public class ProcessTreeTest extends ChannelTestCase {
t.p.act(new ProcessCallableImpl());
}
private static class MyCallable implements Callable<Tag, IOException>, Serializable {
private static class MyCallable extends MasterToSlaveCallable<Tag, IOException> implements Serializable {
public Tag call() throws IOException {
Tag t = new Tag();
t.tree = ProcessTree.get();

View File

@ -11,7 +11,7 @@ import java.io.PrintWriter;
* @author Kohsuke Kawaguchi
*/
public class ReopenableRotatingFileOutputStreamTest extends TestCase {
public void testRotation() throws IOException {
public void testRotation() throws IOException, InterruptedException {
File base = File.createTempFile("test", "log");
ReopenableRotatingFileOutputStream os = new ReopenableRotatingFileOutputStream(base,3);
PrintWriter w = new PrintWriter(os,true);

View File

@ -95,7 +95,7 @@ THE SOFTWARE.
<patch.tracker.serverId>jenkins-jira</patch.tracker.serverId>
<slf4jVersion>1.7.7</slf4jVersion> <!-- < 1.6.x version didn't specify the license (MIT) -->
<maven-plugin.version>2.5</maven-plugin.version>
<maven-plugin.version>2.6.1-SNAPSHOT</maven-plugin.version> <!-- TODO SECURITY-144 -->
<animal.sniffer.skip>${skipTests}</animal.sniffer.skip>
<java.level>6</java.level>
@ -173,7 +173,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>remoting</artifactId>
<version>2.45</version>
<version>2.46-SNAPSHOT</version><!-- SECURITY-144 branch -->
</dependency>
<dependency>

View File

@ -3,13 +3,12 @@ package hudson;
import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
import hudson.Proc.RemoteProc;
import hudson.remoting.Callable;
import hudson.remoting.Future;
import hudson.remoting.Pipe;
import hudson.remoting.VirtualChannel;
import hudson.slaves.DumbSlave;
import hudson.util.IOUtils;
import hudson.util.StreamTaskListener;
import jenkins.security.MasterToSlaveCallable;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase;
@ -70,7 +69,7 @@ public class ProcTest extends HudsonTestCase {
return ch;
}
private static class ChannelFiller implements Callable<Void,IOException> {
private static class ChannelFiller extends MasterToSlaveCallable<Void,IOException> {
private final OutputStream o;
private ChannelFiller(OutputStream o) {

View File

@ -23,6 +23,7 @@
*/
package hudson.logging;
import jenkins.security.MasterToSlaveCallable;
import org.jvnet.hudson.test.Url;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
@ -109,7 +110,7 @@ public class LogRecorderManagerTest {
assertFalse(text, text.contains("msg #4"));
}
private static final class Log implements Callable<Boolean,Error> {
private static final class Log extends MasterToSlaveCallable<Boolean,Error> {
private final Level level;
private final String logger;
private final String message;

View File

@ -35,6 +35,7 @@ import hudson.remoting.Callable;
import hudson.remoting.Which;
import hudson.util.ArgumentListBuilder;
import jenkins.security.SlaveToMasterCallable;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TestExtension;
@ -154,7 +155,7 @@ public class JNLPLauncherTest extends HudsonTestCase {
return c;
}
private static class NoopTask implements Callable<String,RuntimeException> {
private static class NoopTask extends SlaveToMasterCallable<String,RuntimeException> {
public String call() {
return "done";
}

View File

@ -0,0 +1,101 @@
/*
* The MIT License
*
* Copyright 2014 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 jenkins.security;
import hudson.FilePath;
import hudson.model.Slave;
import hudson.remoting.Callable;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import org.jenkinsci.remoting.Role;
import org.jenkinsci.remoting.RoleChecker;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.JenkinsRule;
public class DefaultFilePathFilterTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Test public void remotePath() throws Exception {
Slave s = r.createOnlineSlave();
FilePath forward = s.getRootPath().child("forward");
forward.write("hello", null);
assertEquals("hello", s.getRootPath().act(new LocalCallable(forward)));
FilePath reverse = new FilePath(new File(r.jenkins.root, "reverse"));
assertFalse(reverse.exists());
try {
s.getChannel().call(new ReverseCallable(reverse));
fail("should have failed");
} catch (SecurityException x) {
// good
// make sure that the stack trace contains the call site info to help assist diagnosis
StringWriter sw = new StringWriter();
x.printStackTrace(new PrintWriter(sw));
assertTrue(sw.toString().contains(DefaultFilePathFilterTest.class.getName()+".remotePath"));
}
assertFalse(reverse.exists());
DefaultFilePathFilter.BYPASS = true;
s.getChannel().call(new ReverseCallable(reverse));
assertTrue(reverse.exists());
assertEquals("goodbye", reverse.readToString());
}
private static class LocalCallable implements Callable<String,Exception> {
private final FilePath p;
LocalCallable(FilePath p) {
this.p = p;
}
@Override public String call() throws Exception {
assertFalse(p.isRemote());
return p.readToString();
}
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
throw new NoSuchMethodError(); // simulate legacy Callable impls
}
}
private static class ReverseCallable implements Callable<Void,Exception> {
private final FilePath p;
ReverseCallable(FilePath p) {
this.p = p;
}
@Override public Void call() throws Exception {
assertTrue(p.isRemote());
p.write("goodbye", null);
return null;
}
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
throw new NoSuchMethodError(); // simulate legacy Callable impls
}
}
}