Disable Jenkins CLI across all controllers

3 minute read

Issue

  • I would like to disable the Jenkins CLI across all my controllers.

Resolution

Please make sure that all controllers are online and connected to your operations center.

Create a New Item of type Cluster Operation:

new item cluster op

Under the Operation center configuration screen select Add Operation and select controllers.

Then select From Operations Center Root and under the Filters select Is Online (note that offline controllers will still have the CLI enabled if you add this filter):

cluster op is online

In the Steps section click on Add Step and select Execute Groovy Script on controller:

cluster op groovy script

Then paste the following:

import java.nio.file.Files import java.nio.file.Paths def jenkins_home = Jenkins.get().getRootDir().absolutePath def initDirPath = Paths.get(jenkins_home, "init.groovy.d") if (!Files.exists(initDirPath)) { println "Creating Post-Initialization directory '${initDirPath}'..." Files.createDirectories(initDirPath); } def groovyFile = initDirPath.toRealPath().toString() + "/cli-shutdown.groovy" def codestr=""" // disable CLI access over HTTP / Websocket def removal = { lst -> lst.each { x -> if (x.getClass().getName()?.contains("CLIAction")) lst.remove(x) } } def j = jenkins.model.Jenkins.get(); removal(j.getExtensionList(hudson.cli.CLIAction.class)) removal(j.getExtensionList(hudson.ExtensionPoint.class)) removal(j.getExtensionList(hudson.model.Action.class)) removal(j.getExtensionList(hudson.model.ModelObject.class)) removal(j.getExtensionList(hudson.model.RootAction.class)) removal(j.getExtensionList(hudson.model.UnprotectedRootAction.class)) removal(j.getExtensionList(java.lang.Object.class)) removal(j.getExtensionList(org.kohsuke.stapler.StaplerProxy.class)) removal(j.actions) // disable CLI access over SSH if (j.getPlugin('sshd')) { hudson.ExtensionList.lookupSingleton(org.jenkinsci.main.modules.sshd.SSHD.class).setPort(-1) } """ try { println "Creating Post-Initialization script '${groovyFile}'..." File file = new File(groovyFile) file.newWriter().withWriter {it << codestr}; } catch (Exception ex) { println "Unable to create '$groovyFile': " + ex.getMessage() + ". If controller restarts, this script must be applied again" } // Execute the same logic in place GroovyShell shell = new GroovyShell(Jenkins.get().getPluginManager().uberClassLoader); shell.evaluate(codestr);
When using High Availability (active/active) replicas, as of product version 2.426.3.3, the groovy script above will only disable the CLI on one of the replicas, to disable the CLI across all replicas, a restart (or rolling restart) of the controllers is required. A forceful restart can be done as part of the cluster operation by using the Restart Now step.

Then click Save.

Finally execute the cluster operation by clicking Run:

run cluster op

This should then run the script on all of the controllers.

If you would like to run this on the CJOC instance as well, then please follow the guide Disable Jenkins CLI.

Known limitations

There are some limitations on the provided script as it will not work as expected in the following cases:

  • 2.276.x and earlier: The script doesn’t disable the SSH port providing access to the CLI via SSH.

  • 2.222.x and earlier: The line including java.lang.Object needs to be removed for it to work. Note that removing this line will result in incomplete protection when applied to newer releases.

  • 2.164.x and earlier: The script doesn’t disable the remoting-based CLI removed in later releases.

  • When using High Availability (active/active) replicas, as of product version 2.426.3.3, the groovy script above will only disable the CLI on one of the replicas, to disable the CLI across all replicas, a restart, or rolling restart of the controllers is required.

Validate if the mitigation script is in place

To validate if the mitigation script is in the correct place, please run this script in the script console of your Operations center, this will run the script across the connected controllers and will return a list with the ones that are not protected.

import com.cloudbees.opscenter.server.model.* import com.cloudbees.opscenter.server.clusterops.steps.* import hudson.remoting.* controllers = [] scriptToRun = ''' import java.nio.file.Files import java.nio.file.Paths def jenkins_home = Jenkins.get().getRootDir().absolutePath def initDirPath = Paths.get(jenkins_home, "init.groovy.d") def groovyFile = initDirPath.toRealPath().toString() + "/cli-shutdown.groovy" File file = new File(groovyFile) if (!file.exists()) { println "Warning: This instance does not have the mitigation file in the correct path '${groovyFile}'..." }else{ println "This instance is protected by the mitigation script" } ''' Jenkins.instance.getAllItems(ConnectedMaster.class).each { controllers.add(getHost(it.channel, it.class.simpleName, it.encodedName)) } def getHost(channel, type, name){ def host if(channel){ def stream = new ByteArrayOutputStream() def listener = new StreamBuildListener(stream) channel.call(new MasterGroovyClusterOpStep.Script( scriptToRun, listener, "host-script.groovy", [:])) host = "Instance: "+name+" "+ stream.toString().minus("Result: ") } else { host = "\t"+[type:type, name:name, offline:true] } return host } controllers.each(){ println it }