package jenkins.slaves;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.slaves.SlaveComputer;
import hudson.util.Secret;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.ResponseImpl;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.compression.FilterServletOutputStream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
/**
* Serves the JNLP file.
*
* The client can request an encrypted payload (with JNLP MAC code as the key) or if the client has a suitable permission,
* it can request a plain text payload.
*
* @author Kohsuke Kawaguchi
* @since 1.560
*/
public class EncryptedSlaveAgentJnlpFile implements HttpResponse {
/**
* The object that owns the Jelly view that renders JNLP file.
* For example {@link SlaveComputer}.
*/
private final AccessControlled it;
/**
* Name of the view that renders JNLP file that belongs to {@link #it}.
*/
private final String viewName;
/**
* Name of the agent, which is used to determine secret HMAC code.
*/
private final String slaveName;
/**
* Permission that allows plain text access. Checked against {@link #it}.
*/
private final Permission connectPermission;
public EncryptedSlaveAgentJnlpFile(AccessControlled it, String viewName, String slaveName, Permission connectPermission) {
this.it = it;
this.viewName = viewName;
this.slaveName = slaveName;
this.connectPermission = connectPermission;
}
@Override
public void generateResponse(StaplerRequest req, StaplerResponse res, Object node) throws IOException, ServletException {
RequestDispatcher view = req.getView(it, viewName);
if ("true".equals(req.getParameter("encrypt"))) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
StaplerResponse temp = new ResponseImpl(req.getStapler(), new HttpServletResponseWrapper(res) {
@Override public ServletOutputStream getOutputStream() throws IOException {
return new FilterServletOutputStream(baos);
}
@Override public PrintWriter getWriter() throws IOException {
throw new IllegalStateException();
}
});
view.forward(req, temp);
byte[] iv = new byte[128/8];
new SecureRandom().nextBytes(iv);
byte[] jnlpMac = JnlpSlaveAgentProtocol.SLAVE_SECRET.mac(slaveName.getBytes("UTF-8"));
SecretKey key = new SecretKeySpec(jnlpMac, 0, /* export restrictions */ 128 / 8, "AES");
byte[] encrypted;
try {
Cipher c = Secret.getCipher("AES/CFB8/NoPadding");
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
encrypted = c.doFinal(baos.toByteArray());
} catch (GeneralSecurityException x) {
throw new IOException(x);
}
res.setContentType("application/octet-stream");
res.getOutputStream().write(iv);
res.getOutputStream().write(encrypted);
} else {
it.checkPermission(connectPermission);
view.forward(req, res);
}
}
}