提交 c7cf2180 编写于 作者: J Jesse Glick

Merge branch 'security' into security-rc

......@@ -156,7 +156,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler-adjunct-zeroclipboard</artifactId>
<version>1.1.7-1</version>
<version>1.3.5-1</version>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
......
......@@ -37,6 +37,7 @@ import hudson.remoting.ChannelProperty;
import hudson.security.CliAuthenticator;
import hudson.security.SecurityRealm;
import org.acegisecurity.Authentication;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.discovery.ResourceClassIterator;
......@@ -63,6 +64,7 @@ import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -240,6 +242,13 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
// signals an error without stack trace
stderr.println(e.getMessage());
return -1;
} catch (BadCredentialsException e) {
// to the caller, we can't reveal whether the user didn't exist or the password didn't match.
// do that to the server log instead
String id = UUID.randomUUID().toString();
LOGGER.log(Level.INFO, "CLI login attempt failed: "+id, e);
stderr.println("Bad Credentials. Search the server log for "+id+" for more details.");
return -1;
} catch (Exception e) {
e.printStackTrace(stderr);
return -1;
......
......@@ -36,6 +36,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -78,9 +80,14 @@ abstract public class FullDuplexHttpChannel {
out.write("Starting HTTP duplex channel".getBytes());
out.flush();
// wait until we have the other channel
while(upload==null)
wait();
{// wait until we have the other channel
long end = System.currentTimeMillis() + CONNECTION_TIMEOUT;
while (upload == null && System.currentTimeMillis()<end)
wait(1000);
if (upload==null)
throw new IOException("HTTP full-duplex channel timeout: "+uuid);
}
try {
channel = new Channel("HTTP full-duplex channel " + uuid,
......@@ -145,4 +152,9 @@ abstract public class FullDuplexHttpChannel {
* Set to true if the servlet container doesn't support chunked encoding.
*/
public static boolean DIY_CHUNKING = Boolean.getBoolean("hudson.diyChunking");
/**
* Controls the time out of waiting for the 2nd HTTP request to arrive.
*/
private static long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(15);
}
......@@ -11,11 +11,16 @@ import org.kohsuke.stapler.StaplerRequest;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import static hudson.Util.fixNull;
/**
* Stores the location of Jenkins (e-mail address and the HTTP URL.)
*
......@@ -63,6 +68,8 @@ public class JenkinsLocationConfiguration extends GlobalConfiguration {
} else {
super.load();
}
updateSecureSessionFlag();
}
public String getAdminAddress() {
......@@ -91,6 +98,31 @@ public class JenkinsLocationConfiguration extends GlobalConfiguration {
url += '/';
this.jenkinsUrl = url;
save();
updateSecureSessionFlag();
}
/**
* If the Jenkins URL starts from "https", force the secure session flag
*
* @see <a href="https://www.owasp.org/index.php/SecureFlag">discussion of this topic in OWASP</a>
*/
private void updateSecureSessionFlag() {
try {
boolean v = fixNull(jenkinsUrl).startsWith("https");
ServletContext context = Jenkins.getInstance().servletContext;
Method m = context.getClass().getMethod("getSessionCookieConfig");
Object sessionCookieConfig = m.invoke(context);
// not exposing session cookie to JavaScript to mitigate damage caused by XSS
Class scc = Class.forName("javax.servlet.SessionCookieConfig");
Method setHttpOnly = scc.getMethod("setHttpOnly",boolean.class);
setHttpOnly.invoke(sessionCookieConfig,true);
Method setSecure = scc.getMethod("setSecure",boolean.class);
setSecure.invoke(sessionCookieConfig,v);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Failed to set secure cookie flag. Maybe running on Servlet 2.5 and younger?", e);
}
}
@Override
......
......@@ -29,7 +29,16 @@ THE SOFTWARE.
<l:icon class="icon-monitor icon-xlg"/>
${%title(it.displayName)}
</h1>
<j:set var="type" value="${request.getParameter('type') ?: 'min'}" />
<j:set var="type" value="${request.getParameter('type')}" />
<j:choose>
<j:when test="${type == 'sec10'}" />
<j:when test="${type == 'min'}" />
<j:when test="${type == 'hour'}" />
<j:otherwise>
<j:set var="type" value="min" />
</j:otherwise>
</j:choose>
<div>
${%Timespan}:
<j:choose>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<f:entry title="${it.name}" description="${it.formattedDescription}">
<div name="parameter">
<input type="hidden" name="name" value="${it.name}" />
<f:password name="value" value="${it.defaultValue}" />
<f:password name="value" value="${it.defaultValueAsSecret}" />
</div>
</f:entry>
</j:jelly>
\ No newline at end of file
.copy-button {
display: inline-block;
}
.copy-button BUTTON {
background: url('clipboard.png') center center no-repeat;
width: 20px;
......
......@@ -5,8 +5,7 @@ Behaviour.specify("span.copy-button", 'copyButton', 0, function(e) {
var id = "copy-button"+(iota++);
btn.id = id;
var clip = new ZeroClipboard.Client();
clip.setText(e.getAttribute("text"));
var clip = new ZeroClipboard(e);
makeButton(btn);
clip.setHandCursor(true);
......@@ -14,24 +13,24 @@ Behaviour.specify("span.copy-button", 'copyButton', 0, function(e) {
if (container) {
container = $(e).up(container);
container.style.position = "relative";
clip.glue(id,container);
} else {
clip.glue(id);
}
clip.addEventListener('onComplete',function() {
clip.on('datarequested',function() {
clip.setText(e.getAttribute("text"));
});
clip.on('complete',function() {
notificationBar.show(e.getAttribute("message"));
});
clip.addEventListener('onMouseOver',function() {
clip.on('mouseOver',function() {
$(id).addClassName('yui-button-hover')
});
clip.addEventListener('onMouseOut',function() {
clip.on('mouseOut',function() {
$(id).removeClassName('yui-button-hover')
});
clip.addEventListener('onMouseDown',function() {
clip.on('mouseDown',function() {
$(id).addClassName('yui-button-active')
});
clip.addEventListener('onMouseUp',function() {
clip.on('mouseUp',function() {
$(id).removeClassName('yui-button-active')
});
});
......@@ -7,11 +7,15 @@ import hudson.cli.LoginCommand;
import hudson.cli.LogoutCommand;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.apache.commons.io.input.NullInputStream;
import org.junit.Assert;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TestExtension;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
/**
* @author Kohsuke Kawaguchi
*/
......@@ -36,6 +40,17 @@ public class CliAuthenticationTest extends HudsonTestCase {
}
}
private String commandAndOutput(String... args) throws Exception {
CLI cli = new CLI(getURL());
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cli.execute(Arrays.asList(args), new NullInputStream(0), baos, baos);
return baos.toString();
} finally {
cli.close();
}
}
@TestExtension
public static class TestCommand extends CLICommand {
@Override
......@@ -76,4 +91,20 @@ public class CliAuthenticationTest extends HudsonTestCase {
successfulCommand("logout");
successfulCommand("anonymous"); // now we should run as anonymous
}
/**
* Login failure shouldn't reveal information about the existence of user
*/
public void testSecurity110() throws Exception {
HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false,false,null);
jenkins.setSecurityRealm(realm);
jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy());
realm.createAccount("alice","alice");
String out1 = commandAndOutput("help", "--username", "alice", "--password", "bogus");
String out2 = commandAndOutput("help", "--username", "bob", "--password", "bogus");
assertTrue(out1.contains("Bad Credentials. Search the server log for"));
assertTrue(out2.contains("Bad Credentials. Search the server log for"));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册