diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 752d63bea2d06a5a52b94e0e559da91db17e09d8..6e8d1d0bf8fcdc75f2a2b26a99f0dd72afbbd893 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -26,6 +26,7 @@ package hudson; import java.nio.charset.Charset; import java.security.interfaces.RSAPublicKey; import javax.annotation.Nullable; +import jenkins.model.Jenkins; import jenkins.model.identity.InstanceIdentityProvider; import jenkins.util.SystemProperties; import hudson.slaves.OfflineCause; @@ -50,6 +51,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; /** * Listens to incoming TCP connections from JNLP agents and CLI. @@ -127,20 +129,7 @@ public final class TcpSlaveAgentListener extends Thread { * @since FIXME */ public String getAgentProtocolNames() { - StringBuilder result = new StringBuilder(); - boolean first = true; - for (AgentProtocol p : AgentProtocol.all()) { - String name = p.getName(); - if (name != null) { - if (first) { - first = false; - } else { - result.append(", "); - } - result.append(name); - } - } - return result.toString(); + return StringUtils.join(Jenkins.getInstance().getAgentProtocols(), ", "); } @Override @@ -218,9 +207,13 @@ public final class TcpSlaveAgentListener extends Thread { if(s.startsWith("Protocol:")) { String protocol = s.substring(9); AgentProtocol p = AgentProtocol.of(protocol); - if (p!=null) - p.handle(this.s); - else + if (p!=null) { + if (Jenkins.getInstance().getAgentProtocols().contains(protocol)) { + p.handle(this.s); + } else { + error(out, "Disabled protocol:" + s); + } + } else error(out, "Unknown protocol:" + s); } else { error(out, "Unrecognized protocol: "+s); @@ -268,6 +261,18 @@ public final class TcpSlaveAgentListener extends Thread { } } + /** + * Allow essential {@link AgentProtocol} implementations (basically {@link PingAgentProtocol}) + * to be always enabled. + * + * @return {@code true} if the protocol can never be disbaled. + * @since FIXME + */ + @Override + public boolean isRequired() { + return true; + } + @Override public String getName() { return "Ping"; diff --git a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java index 36edde3f4e8527815650c103d6a008e6d72d4487..5fa446fef78979095cdd601dbb5fcb5f417ccfa0 100644 --- a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java +++ b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java @@ -35,6 +35,8 @@ import hudson.model.ManagementLink; import hudson.util.FormApply; import java.io.IOException; +import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -43,6 +45,7 @@ import javax.servlet.ServletException; import jenkins.model.GlobalConfigurationCategory; import jenkins.model.Jenkins; import jenkins.util.ServerTcpPort; +import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.jenkinsci.Symbol; @@ -69,6 +72,10 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr return Jenkins.getInstance().getSlaveAgentPort(); } + public Set getAgentProtocols() { + return Jenkins.getInstance().getAgentProtocols(); + } + public boolean isDisableRememberMe() { return Jenkins.getInstance().isDisableRememberMe(); } @@ -100,6 +107,18 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr } catch (IOException e) { throw new hudson.model.Descriptor.FormException(e, "slaveAgentPortType"); } + Set agentProtocols = new TreeSet<>(); + if (security.has("agentProtocol")) { + Object protocols = security.get("agentProtocol"); + if (protocols instanceof JSONArray) { + for (int i = 0; i < ((JSONArray) protocols).size(); i++) { + agentProtocols.add(((JSONArray) protocols).getString(i)); + } + } else { + agentProtocols.add(protocols.toString()); + } + } + j.setAgentProtocols(agentProtocols); } else { j.disableSecurity(); } diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java index 667f881cde13b604c043ddeb8b08c9ee3f82181d..14d2a6858e21f4472f9a3ce56314c730e39d73d2 100644 --- a/core/src/main/java/jenkins/AgentProtocol.java +++ b/core/src/main/java/jenkins/AgentProtocol.java @@ -21,6 +21,24 @@ import java.net.Socket; * @see TcpSlaveAgentListener */ public abstract class AgentProtocol implements ExtensionPoint { + /** + * Allow experimental {@link AgentProtocol} implementations to declare being opt-in. + * @return {@code true} if the protocol requires explicit opt-in. + * @since FIXME + */ + public boolean isOptIn() { + return false; + } + /** + * Allow essential {@link AgentProtocol} implementations (basically {@link TcpSlaveAgentListener.PingAgentProtocol}) + * to be always enabled. + * + * @return {@code true} if the protocol can never be disbaled. + * @since FIXME + */ + public boolean isRequired() { + return false; + } /** * Protocol name. * diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index e01d3085f70e58fca6fa02609f91e8cb28caac14..ca685aaabfdafb7aaf1745a604192d2b7711bf37 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -50,6 +50,7 @@ import hudson.Plugin; import hudson.PluginManager; import hudson.PluginWrapper; import hudson.ProxyConfiguration; +import jenkins.AgentProtocol; import jenkins.util.SystemProperties; import hudson.TcpSlaveAgentListener; import hudson.UDPBroadcastThread; @@ -224,6 +225,7 @@ import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.acegisecurity.ui.AbstractProcessingFilter; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.Script; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.jvnet.hudson.reactor.Executable; import org.jvnet.hudson.reactor.Milestone; @@ -598,6 +600,28 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve */ private int slaveAgentPort = SystemProperties.getInteger(Jenkins.class.getName()+".slaveAgentPort",0); + /** + * The TCP agent protocols that are explicitly disabled (we store the disabled ones so that newer protocols + * are enabled by default). + * + * @since FIXME + */ + private String disabledAgentProtocols; + + /** + * The TCP agent protocols that are {@link AgentProtocol#isOptIn()} and explicitly enabled. + * + * @since FIXME + */ + private String enabledAgentProtocols; + + /** + * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols}. + * + * @since FIXME + */ + private transient Set agentProtocols; + /** * Whitespace-separated labels assigned to the master as a {@link Node}. */ @@ -1060,6 +1084,65 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve launchTcpSlaveAgentListener(); } + /** + * Returns the enabled agent protocols. + * + * @return the enabled agent protocols. + * @since FIXME + */ + public Set getAgentProtocols() { + if (agentProtocols == null) { + // idempotent, so don't care if we do this concurrently, should all get same result + Set result = new TreeSet<>(); + Set disabled = new TreeSet<>(); + for (String p : StringUtils.split(StringUtils.defaultIfBlank(disabledAgentProtocols, ""), ",")) { + disabled.add(p.trim()); + } + Set enabled = new TreeSet<>(); + for (String p : StringUtils.split(StringUtils.defaultIfBlank(enabledAgentProtocols, ""), ",")) { + enabled.add(p.trim()); + } + for (AgentProtocol p : AgentProtocol.all()) { + String name = p.getName(); + if (name != null && (p.isRequired() + || (!disabled.contains(name) && (!p.isOptIn() || enabled.contains(name))))) { + result.add(name); + } + } + agentProtocols = result; + return result; + } + return agentProtocols; + } + + /** + * Sets the enabled agent protocols. + * + * @param protocols the enabled agent protocols. + * @since FIXME + */ + public void setAgentProtocols(Set protocols) { + Set disabled = new TreeSet<>(); + Set enabled = new TreeSet<>(); + for (AgentProtocol p : AgentProtocol.all()) { + String name = p.getName(); + if (name != null && !p.isRequired()) { + if (p.isOptIn()) { + if (protocols.contains(name)) { + enabled.add(name); + } + } else { + if (!protocols.contains(name)) { + disabled.add(name); + } + } + } + } + disabledAgentProtocols = fixEmpty(StringUtils.join(disabled, ", ")); + enabledAgentProtocols = fixEmpty(StringUtils.join(enabled, ", ")); + agentProtocols = null; + } + private void launchTcpSlaveAgentListener() throws IOException { synchronized(tcpSlaveAgentListenerLock) { // shutdown previous agent if the port has changed diff --git a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly index 63e3d8de4e82619e50e03688f57f84292d9cc383..48854e95fb91e191db00c863ddc839defae840a0 100644 --- a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly +++ b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly @@ -23,7 +23,7 @@ THE SOFTWARE. --> - +