jenkinsci / plugin-installation-manager-tool

Plugin Manager CLI tool for Jenkins
MIT License
387 stars 200 forks source link

Some features slightly fragile #247

Open pwillis-els opened 3 years ago

pwillis-els commented 3 years ago

Given the following plugins.txt file:

active-directory
aws-parameter-store
configuration-as-code
configuration-as-code-secret-ssm
git
github
job-dsl
ldap
role-strategy
timestamper

The following command returns an error:

docker run \
    --rm -it \
    -v `pwd`/plugins.txt:/app/plugins.txt \
    jenkins/jenkins \
        jenkins-plugin-cli \
            --plugin-file /app/plugins.txt \
            --view-all-security-warnings \
            --no-download
java.util.ArrayList cannot be cast to java.lang.Comparable

This seems to be a bug, can anyone confirm?

If the plugins.txt contains git:4.1.9, a different command throws an error when it cannot resolve a version dependency:

docker run \
    --rm -it \
    -v `pwd`/plugins.txt:/app/plugins.txt \
    jenkins/jenkins \
        jenkins-plugin-cli \
            --plugin-file /app/plugins.txt \
            --view-security-warnings \
            --no-download \
            --skip-failed-plugins
Downloaded file is not a valid ZIP
Unable to resolve dependencies for git
Plugin github:1.32.0 depends on git:4.5.1, but there is an older version defined on the top level - git:4.1.9

For this second error, it would be useful if there were a "--force" or "--ignore-errors" option. The "--skip-failed-plugins" option doesn't seem to change the result.

jetersen commented 3 years ago

first one is clearly a bug.

second one is not a bug, that is a design choice. Your plugins.txt is meant to pin dependencies. You could try --latest-specified

pwillis-els commented 3 years ago

Thanks for the reply. Unfortunately --latest-specified seems to result in same behavior.

This is fine for the default, but without a way to ignore this error it's not easy to scan an existing plugins.txt file for vulnerable plugins. To work around it i'd need to create a new script to only run on on plugin one at a time, which kinda defeats the purpose of the existing tool :) I ended up doing that, but it took 90 seconds to iterate through the above plugins. The benefit here is doing them individually does not result in errors due to avoiding the dependency checking, but it does find vulnerable versions.

To work around the issue for the moment I wrote the following script and added some functions to help with the immediate task I'm trying to accomplish (check for any plugins with a known security vuln and bump them to the last known secure version): https://gist.github.com/pwillis-els/369f140b1db1efe13d2b609d54579ea6

jetersen commented 3 years ago

What prevents you from bumping to minimum required versions? Jenkins would fail to load the dependencies if you did not resolve the issue anyhow.

jetersen commented 3 years ago

I am able to replicate the first bug you reported inside unit testing.

java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class java.lang.Comparable (java.util.ArrayList and java.lang.Comparable are in module java.base of loader 'bootstrap')

    at java.base/java.util.Comparators$NaturalOrderComparator.compare(Comparators.java:47)
    at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
    at java.base/java.util.TimSort.sort(TimSort.java:234)
    at java.base/java.util.Arrays.sort(Arrays.java:1515)
    at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:353)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.showAllSecurityWarnings(PluginManager.java:353)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:162)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:125)
    at io.jenkins.tools.pluginmanager.impl.PluginManagerIntegrationTest.debugArrayListComparable(PluginManagerIntegrationTest.java:292)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:54)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:54)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
pwillis-els commented 3 years ago

I figured it was better not to assume the behavior of other components outside of the immediate plugin functionality. For example, say you have an existing jenkins install with a bunch of plugins, and somebody updates a plugins.txt with a new invalid version. If you try to install that updated plugins.txt, jenkins may fail to install them, but it will also keep operating with the existing plugins. If the user has a separate task which checks the plugins.txt for vulnerable versions, the user may want that task to succeed and identify the vulnerable plugin versions, even if it's not installable yet.

In any case I think I got what you mean, using these options: --available-updates --no-download --plugins $(cat plugins.txt). For a set of pinned versions, that may give me the following:

Available updates:
active-directory (2.18) has an available update: 2.23
configuration-as-code (1.27) has an available update: 1.46
git (4.2.1) has an available update: 4.5.1
github (1.29.2) has an available update: 1.32.0
job-dsl (1.72) has an available update: 1.77
timestamper (1.11.2) has an available update: 1.11.8

But actually these seem to be the latest versions rather than the minimum secure versions. If you go through all the warnings and dependencies you'll find that, for example, you could use active-directory 2.20 instead of 2.23 here. I only want to fix the security vulns, I don't want to update past that point to the latest versions (as dependency management then drags a bunch of other deps along and then you're updating 8 plugins rather than 1)

jetersen commented 3 years ago

latest is the default you can use --latest false

pwillis-els commented 3 years ago

Unfortunately --latest false doesn't change the behavior.

It also seems like you can see the available updates, but you get an error on security warnings:

$ cat plugins.txt
active-directory
aws-parameter-store
configuration-as-code
configuration-as-code-secret-ssm
git:4.1.9
github
job-dsl
ldap
role-strategy
timestamper

$ docker run --rm jenkins/jenkins jenkins-plugin-cli  --available-updates --no-download --plugins `cat plugins.txt`
Available updates:
git (4.1.9) has an available update: 4.5.1

$ docker run --rm jenkins/jenkins jenkins-plugin-cli  --view-security-warnings --no-download --plugins `cat plugins.txt`
Downloaded file is not a valid ZIP
Unable to resolve dependencies for git
Plugin github:1.32.0 depends on git:4.5.1, but there is an older version defined on the top level - git:4.1.9
jetersen commented 3 years ago

on the last command what does it print when you add --verbose

jetersen commented 3 years ago

this is the stacktrace. So the Downloaded file is not a valid ZIP is sort of misleading. The download got stopped plugin is not compatible with the top level version.

java.nio.file.NoSuchFileException: C:\Users\joseph\AppData\Local\Temp\git15863940145146328763.jpi
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:85)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(WindowsFileAttributeViews.java:53)
    at java.base/sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(WindowsFileAttributeViews.java:38)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.readAttributes(WindowsFileSystemProvider.java:198)
    at java.base/java.nio.file.Files.readAttributes(Files.java:1763)
    at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1225)
    at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:727)
    at java.base/java.util.zip.ZipFile$CleanableResource.get(ZipFile.java:844)
    at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:247)
    at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:177)
    at java.base/java.util.jar.JarFile.<init>(JarFile.java:348)
    at java.base/java.util.jar.JarFile.<init>(JarFile.java:319)
    at java.base/java.util.jar.JarFile.<init>(JarFile.java:285)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.downloadToFile(PluginManager.java:1145)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.downloadToFile(PluginManager.java:1073)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.downloadPlugin(PluginManager.java:1002)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveDependenciesFromManifest(PluginManager.java:788)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveDirectDependencies(PluginManager.java:913)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveRecursiveDependencies(PluginManager.java:943)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.findPluginsAndDependencies(PluginManager.java:570)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:167)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:126)
    at io.jenkins.tools.pluginmanager.cli.Main.main(Main.java:70)
java.nio.file.NoSuchFileException: C:\Users\joseph\AppData\Local\Temp\git15863940145146328763.jpi
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:85)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:274)
    at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105)
    at java.base/java.nio.file.Files.delete(Files.java:1141)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveDependenciesFromManifest(PluginManager.java:789)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveDirectDependencies(PluginManager.java:913)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveRecursiveDependencies(PluginManager.java:943)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.findPluginsAndDependencies(PluginManager.java:570)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:167)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:126)
    at io.jenkins.tools.pluginmanager.cli.Main.main(Main.java:70)
io.jenkins.tools.pluginmanager.impl.PluginDependencyStrategyException: Plugin github:1.32.0 depends on git:4.5.1, but there is an older version defined on the top level - git:4.1.9
    at io.jenkins.tools.pluginmanager.impl.PluginManager.resolveRecursiveDependencies(PluginManager.java:961)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.findPluginsAndDependencies(PluginManager.java:570)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:167)
    at io.jenkins.tools.pluginmanager.impl.PluginManager.start(PluginManager.java:126)
    at io.jenkins.tools.pluginmanager.cli.Main.main(Main.java:70)
Plugin github:1.32.0 depends on git:4.5.1, but there is an older version defined on the top level - git:4.1.9

Process finished with exit code 1