diff --git a/.gitignore b/.gitignore index 3e41e15..491e58d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ mvn.out* .settings/ .repository/ .settings.xml +.idea/ diff --git a/pom.xml b/pom.xml index d5c263a..1e448ea 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,12 @@ + 4.0.0 org.jenkins-ci.plugins plugin - 4.86 - + 5.6 + git-server @@ -15,21 +16,6 @@ Jenkins Git server Plugin https://github.com/jenkinsci/${project.artifactId}-plugin - - 999999-SNAPSHOT - - 2.440 - ${jenkins.baseline}.3 - jenkinsci/${project.artifactId}-plugin - - - - scm:git:https://github.com/${gitHubRepo}.git - scm:git:git@github.com:${gitHubRepo}.git - https://github.com/${gitHubRepo} - ${scmTag} - - The MIT license @@ -38,14 +24,29 @@ + + scm:git:https://github.com/${gitHubRepo}.git + scm:git:git@github.com:${gitHubRepo}.git + ${scmTag} + https://github.com/${gitHubRepo} + + + + 999999-SNAPSHOT + + 2.479 + ${jenkins.baseline}.1 + jenkinsci/${project.artifactId}-plugin + + io.jenkins.tools.bom bom-${jenkins.baseline}.x - 3234.v5ca_5154341ef - import + 4023.va_eeb_b_4e45f07 pom + import @@ -67,7 +68,7 @@ org.eclipse.jgit org.eclipse.jgit.ssh.apache - 6.9.0.202403050737-r + 7.0.0.202409031743-r test diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/CSRFExclusionImpl.java b/src/main/java/org/jenkinsci/plugins/gitserver/CSRFExclusionImpl.java index d946724..f56036a 100644 --- a/src/main/java/org/jenkinsci/plugins/gitserver/CSRFExclusionImpl.java +++ b/src/main/java/org/jenkinsci/plugins/gitserver/CSRFExclusionImpl.java @@ -2,31 +2,31 @@ import hudson.Extension; import hudson.security.csrf.CrumbExclusion; - -import javax.servlet.FilterChain; -import javax.servlet.ReadListener; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; import java.util.Enumeration; +import java.util.Locale; import java.util.Map; -import java.util.Vector; /** * CSRF exclusion for git-upload-pack. - * + * *

* We do some basic checks to significantly limit the scope of exclusion, but * because of the dynamic nature of the URL structure, this doesn't guarantee * that we have no leak. * + *

* So to further protect Jenkins, we pass through a fake {@link HttpServletRequest} * that masks the values of the submission. - * + * *

* If the fake request is routed to {@link HttpGitRepository}, which is * the only legitimate destination of the request, we'll unwrap this fake request @@ -35,53 +35,51 @@ *

* In this way, even if an attacker manages to route the request to elsewhere in Jenkins, * that request will not be interpreted as a POST request. - * + * * @author Kohsuke Kawaguchi */ @Extension public class CSRFExclusionImpl extends CrumbExclusion { + private static final String BOGUS = "bogus"; - public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - if (!"application/x-git-receive-pack-request".equals(request.getHeader("Content-Type"))) - return false; - -// String path = request.getPathInfo(); -// if(!path.contains("/repo.git/") || !path.endsWith("/git-receive-pack")) -// return false; + @Override + public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (!"application/x-git-receive-pack-request".equals(request.getHeader("Content-Type"))) return false; HttpServletRequestWrapper w = new HttpServletRequestWrapper(request) { @Override public String getQueryString() { - return "bogus"; + return BOGUS; } @Override public String getParameter(String name) { - return "bogus"; + return BOGUS; } @Override - public Map getParameterMap() { + public Map getParameterMap() { return Collections.emptyMap(); } @Override - public Enumeration getParameterNames() { - return new Vector().elements(); + public Enumeration getParameterNames() { + return Collections.emptyEnumeration(); } @Override public String[] getParameterValues(String name) { - return new String[]{"bogus"}; + return new String[] {BOGUS}; } @Override public String getMethod() { - return "BOGUS"; + return BOGUS.toUpperCase(Locale.ROOT); } @Override - public ServletInputStream getInputStream() throws IOException { + public ServletInputStream getInputStream() { return new ServletInputStream() { @Override public boolean isFinished() { @@ -94,7 +92,7 @@ public boolean isReady() { } @Override - public int read() throws IOException { + public int read() { return -1; } @@ -105,13 +103,13 @@ public void setReadListener(ReadListener readListener) { }; } }; - w.setAttribute(ORIGINAL_REQUEST,request); - - chain.doFilter(w,response); + w.setAttribute(ORIGINAL_REQUEST, request); + + chain.doFilter(w, response); return true; } - static final String ORIGINAL_REQUEST = CSRFExclusionImpl.class.getName()+".originalRequest"; + static final String ORIGINAL_REQUEST = CSRFExclusionImpl.class.getName() + ".originalRequest"; public static HttpServletRequest unwrapRequest(HttpServletRequest req) { return (HttpServletRequest) req.getAttribute(CSRFExclusionImpl.ORIGINAL_REQUEST); diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/ChannelTransport.java b/src/main/java/org/jenkinsci/plugins/gitserver/ChannelTransport.java index 06d82e7..e48216e 100644 --- a/src/main/java/org/jenkinsci/plugins/gitserver/ChannelTransport.java +++ b/src/main/java/org/jenkinsci/plugins/gitserver/ChannelTransport.java @@ -1,9 +1,13 @@ package org.jenkinsci.plugins.gitserver; import hudson.FilePath; -import hudson.FilePath.FileCallable; import hudson.remoting.Pipe; import hudson.remoting.VirtualChannel; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; import jenkins.MasterToSlaveFileCallable; import org.apache.commons.io.IOUtils; import org.eclipse.jgit.errors.NotSupportedException; @@ -20,12 +24,6 @@ import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.UploadPack; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; - /** * {@link Transport} implementation across pipes. * @@ -34,15 +32,14 @@ public class ChannelTransport extends Transport implements PackTransport { private final FilePath remoteRepository; - public static Transport open(Repository local, FilePath remoteRepository) throws NotSupportedException, URISyntaxException, TransportException { - if (remoteRepository.isRemote()) - return new ChannelTransport(local,remoteRepository); - else - return Transport.open(local,remoteRepository.getRemote()); + public static Transport open(Repository local, FilePath remoteRepository) + throws NotSupportedException, URISyntaxException, TransportException { + if (remoteRepository.isRemote()) return new ChannelTransport(local, remoteRepository); + else return Transport.open(local, remoteRepository.getRemote()); } public ChannelTransport(Repository local, FilePath remoteRepository) throws URISyntaxException { - super(local, new URIish("channel:"+remoteRepository.getRemote())); + super(local, new URIish("channel:" + remoteRepository.getRemote())); this.remoteRepository = remoteRepository; } @@ -54,15 +51,18 @@ public FetchConnection openFetch() throws NotSupportedException, TransportExcept try { remoteRepository.actAsync(new GitFetchTask(l2r, r2l)); } catch (IOException e) { - throw new TransportException("Failed to open a fetch connection",e); + throw new TransportException("Failed to open a fetch connection", e); } catch (InterruptedException e) { - throw new TransportException("Failed to open a fetch connection",e); + Thread.currentThread().interrupt(); + throw new TransportException("Failed to open a fetch connection", e); } - return new BasePackFetchConnection(this) {{ - init(new BufferedInputStream(r2l.getIn()), new BufferedOutputStream(l2r.getOut())); - readAdvertisedRefs(); - }}; + return new BasePackFetchConnection(this) { + { + init(new BufferedInputStream(r2l.getIn()), new BufferedOutputStream(l2r.getOut())); + readAdvertisedRefs(); + } + }; } @Override @@ -73,15 +73,18 @@ public PushConnection openPush() throws NotSupportedException, TransportExceptio try { remoteRepository.actAsync(new GitPushTask(l2r, r2l)); } catch (IOException e) { - throw new TransportException("Failed to open a fetch connection",e); + throw new TransportException("Failed to open a fetch connection", e); } catch (InterruptedException e) { - throw new TransportException("Failed to open a fetch connection",e); + Thread.currentThread().interrupt(); + throw new TransportException("Failed to open a fetch connection", e); } - return new BasePackPushConnection(this) {{ - init(new BufferedInputStream(r2l.getIn()), new BufferedOutputStream(l2r.getOut())); - readAdvertisedRefs(); - }}; + return new BasePackPushConnection(this) { + { + init(new BufferedInputStream(r2l.getIn()), new BufferedOutputStream(l2r.getOut())); + readAdvertisedRefs(); + } + }; } @Override @@ -99,15 +102,13 @@ public GitFetchTask(Pipe l2r, Pipe r2l) { } public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - Repository repo = new FileRepositoryBuilder().setWorkTree(f).build(); - try { + try (Repository repo = new FileRepositoryBuilder().setWorkTree(f).build()) { final UploadPack rp = new UploadPack(repo); rp.upload(new BufferedInputStream(l2r.getIn()), new BufferedOutputStream(r2l.getOut()), null); return null; } finally { IOUtils.closeQuietly(l2r.getIn()); IOUtils.closeQuietly(r2l.getOut()); - repo.close(); } } } @@ -122,15 +123,13 @@ public GitPushTask(Pipe l2r, Pipe r2l) { } public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - Repository repo = new FileRepositoryBuilder().setWorkTree(f).build(); - try { + try (Repository repo = new FileRepositoryBuilder().setWorkTree(f).build()) { final ReceivePack rp = new ReceivePack(repo); rp.receive(new BufferedInputStream(l2r.getIn()), new BufferedOutputStream(r2l.getOut()), null); return null; } finally { IOUtils.closeQuietly(l2r.getIn()); IOUtils.closeQuietly(r2l.getOut()); - repo.close(); } } } diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/FileBackedHttpGitRepository.java b/src/main/java/org/jenkinsci/plugins/gitserver/FileBackedHttpGitRepository.java index ea64f95..09202d8 100644 --- a/src/main/java/org/jenkinsci/plugins/gitserver/FileBackedHttpGitRepository.java +++ b/src/main/java/org/jenkinsci/plugins/gitserver/FileBackedHttpGitRepository.java @@ -1,7 +1,16 @@ package org.jenkinsci.plugins.gitserver; +import jakarta.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.Git; @@ -11,21 +20,11 @@ import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.transport.PostReceiveHook; -import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; - -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collection; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.springframework.security.core.Authentication; /** * Convenient subtype of {@link HttpGitRepository} where the repository @@ -39,19 +38,28 @@ public abstract class FileBackedHttpGitRepository extends HttpGitRepository { * Directory of the local workspace on the controller. * There will be "./.git" that hosts the actual repository. */ - public final File workspace; + public final Path workspace; - protected FileBackedHttpGitRepository(File workspace) { + protected FileBackedHttpGitRepository(Path workspace) { this.workspace = workspace; - if (!workspace.exists() && !workspace.mkdirs()) { - LOGGER.log(Level.WARNING, "Cannot create a workspace in {0}", workspace); + try { + Files.createDirectory(workspace); + } catch (FileAlreadyExistsException ignored) { + // don't need to worry about this; if it already exists, we don't care! + } catch (IOException e) { + LOGGER.log(Level.WARNING, e, () -> "Cannot create a workspace in " + workspace); } } + protected FileBackedHttpGitRepository(File workspace) { + this(workspace.toPath()); + } + @Override public Repository openRepository() throws IOException { checkPullPermission(); - Repository r = new FileRepositoryBuilder().setWorkTree(workspace).build(); + Repository r = + new FileRepositoryBuilder().setWorkTree(workspace.toFile()).build(); // if the repository doesn't exist, create it if (!r.getObjectDatabase().exists()) { @@ -63,6 +71,7 @@ public Repository openRepository() throws IOException { /** * Called when there's no .git directory to create one. * + *

* This implementation also imports whatever currently in there into the repository. */ protected void createInitialRepository(Repository r) throws IOException { @@ -76,11 +85,11 @@ protected void createInitialRepository(Repository r) throws IOException { cmd.call(); CommitCommand co = git.commit(); - co.setAuthor("Jenkins","noreply@jenkins-ci.org"); + co.setAuthor("Jenkins", "noreply@jenkins-ci.org"); co.setMessage("Initial import of the existing contents"); co.call(); } catch (GitAPIException e) { - LOGGER.log(Level.WARNING, "Initial import of "+workspace+" into Git repository failed",e); + LOGGER.log(Level.WARNING, e, () -> "Initial import of " + workspace + " into Git repository failed"); } } @@ -88,13 +97,15 @@ protected void createInitialRepository(Repository r) throws IOException { * This default implementation allows read access to anyone * who can access the HTTP URL this repository is bound to. * + *

* For example, if this object is used as a project action, * and the project isn't readable to Alice, then Alice won't be * able to pull from this repository (think of a POSIX file system * where /foo/bar is rwx------ and /foo/bar/zot is rwxrwxrwx.) */ @Override - public UploadPack createUploadPack(HttpServletRequest context, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { + public UploadPack createUploadPack(HttpServletRequest context, Repository db) + throws ServiceNotEnabledException, ServiceNotAuthorizedException { return new UploadPack(db); } @@ -102,12 +113,13 @@ public UploadPack createUploadPack(HttpServletRequest context, Repository db) th * Requires the admin access to be able to push */ @Override - public ReceivePack createReceivePack(HttpServletRequest context, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException { - Authentication a = Jenkins.getAuthentication(); + public ReceivePack createReceivePack(HttpServletRequest context, Repository db) + throws ServiceNotEnabledException, ServiceNotAuthorizedException { + Authentication a = Jenkins.getAuthentication2(); ReceivePack rp = createReceivePack(db); - rp.setRefLogIdent(new PersonIdent(a.getName(), a.getName()+"@"+context.getRemoteAddr())); + rp.setRefLogIdent(new PersonIdent(a.getName(), a.getName() + "@" + context.getRemoteAddr())); return rp; } @@ -118,15 +130,13 @@ public ReceivePack createReceivePack(Repository db) { ReceivePack rp = new ReceivePack(db); // update userContent after the push - rp.setPostReceiveHook(new PostReceiveHook() { - public void onPostReceive(ReceivePack rp, Collection commands) { - try { - updateWorkspace(rp.getRepository()); - } catch (Exception e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - rp.sendMessage("Failed to update workspace: "+sw); - } + rp.setPostReceiveHook((rp1, commands) -> { + try { + updateWorkspace(rp1.getRepository()); + } catch (Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + rp1.sendMessage("Failed to update workspace: " + sw); } }); return rp; diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/HttpGitRepository.java b/src/main/java/org/jenkinsci/plugins/gitserver/HttpGitRepository.java index 9c050a7..0c6d01f 100644 --- a/src/main/java/org/jenkinsci/plugins/gitserver/HttpGitRepository.java +++ b/src/main/java/org/jenkinsci/plugins/gitserver/HttpGitRepository.java @@ -1,6 +1,17 @@ package org.jenkinsci.plugins.gitserver; +import hudson.Util; import hudson.model.Action; +import io.jenkins.servlet.http.HttpServletRequestWrapper; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; import jenkins.model.Jenkins; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.http.server.GitServlet; @@ -12,18 +23,8 @@ import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.transport.resolver.UploadPackFactory; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.Enumeration; -import java.util.Vector; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.kohsuke.stapler.StaplerRequest2; +import org.kohsuke.stapler.StaplerResponse2; /** * UI-bound object that exposes a Git repository via HTTP. @@ -38,8 +39,7 @@ public abstract class HttpGitRepository { private GitServlet g; private Exception causeOfDeath; - public HttpGitRepository() { - } + protected HttpGitRepository() {} /** * Opens the repository this UI-bound object holds on to. @@ -49,6 +49,7 @@ public HttpGitRepository() { /** * Returns the {@link ReceivePack} that handles "git push" from client. * + *

* The most basic implementation is the following, which allows anyone to push to this repository, * so normally you want some kind of access check before that. {@link DefaultReceivePackFactory} isn't suitable * here because it requires that the user has non-empty name, which isn't necessarily true in Jenkins @@ -60,11 +61,39 @@ public HttpGitRepository() { * * @see ReceivePackFactory#create(Object, Repository) */ - public abstract ReceivePack createReceivePack(HttpServletRequest context, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException; + @SuppressWarnings({"deprecated", "java:S1874"}) + public ReceivePack createReceivePack(HttpServletRequest context, Repository db) + throws ServiceNotEnabledException, ServiceNotAuthorizedException { + if (Util.isOverridden( + HttpGitRepository.class, + getClass(), + "createReceivePack", + javax.servlet.http.HttpServletRequest.class, + Repository.class)) { + return createReceivePack(HttpServletRequestWrapper.fromJakartaHttpServletRequest(context), db); + } + throw new AbstractMethodError("Implementing class '" + this.getClass().getName() + "' does not override " + + "either overload of the createReceivePack method."); + } + + /** + * @deprecated Override {@link #createReceivePack(HttpServletRequest, Repository)} instead. + */ + @Deprecated(since = "134") + public ReceivePack createReceivePack(javax.servlet.http.HttpServletRequest context, Repository db) + throws ServiceNotEnabledException, ServiceNotAuthorizedException { + if (Util.isOverridden( + HttpGitRepository.class, getClass(), "createReceivePack", HttpServletRequest.class, Repository.class)) { + return createReceivePack(HttpServletRequestWrapper.toJakartaHttpServletRequest(context), db); + } + throw new AbstractMethodError("Implementing class '" + this.getClass().getName() + "' does not override " + + "either overload of the createReceivePack method."); + } /** * Returns the {@link UploadPack} that handles "git fetch" from client. * + *

* The most basic implementation is the following, which exposes this repository to everyone. * *

@@ -73,39 +102,56 @@ public HttpGitRepository() {
      *
      * @see UploadPackFactory#create(Object, Repository)
      */
-    public abstract UploadPack createUploadPack(HttpServletRequest context, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException;
+    @SuppressWarnings({"deprecated", "java:S1874"})
+    public UploadPack createUploadPack(HttpServletRequest context, Repository db)
+            throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+        if (Util.isOverridden(
+                HttpGitRepository.class,
+                getClass(),
+                "createUploadPack",
+                javax.servlet.http.HttpServletRequest.class,
+                Repository.class)) {
+            return createUploadPack(HttpServletRequestWrapper.fromJakartaHttpServletRequest(context), db);
+        }
+        throw new AbstractMethodError("Implementing class '" + this.getClass().getName() + "' does not override "
+                + "either overload of the createUploadPack method.");
+    }
 
-     /**
+    /**
+     * @deprecated Override {@link #createUploadPack(HttpServletRequest, Repository)} instead.
+     */
+    @Deprecated(since = "134")
+    public UploadPack createUploadPack(javax.servlet.http.HttpServletRequest context, Repository db)
+            throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+        if (Util.isOverridden(
+                HttpGitRepository.class, getClass(), "createUploadPack", HttpServletRequest.class, Repository.class)) {
+            return createUploadPack(HttpServletRequestWrapper.toJakartaHttpServletRequest(context), db);
+        }
+        throw new AbstractMethodError("Implementing class '" + this.getClass().getName() + "' does not override "
+                + "either overload of the createUploadPack method.");
+    }
+
+    /**
      * to make sure the user has the permission to pull.
      */
     public void checkPullPermission() {
-        Jenkins.getInstance().checkPermission(Jenkins.READ);
+        Jenkins.get().checkPermission(Jenkins.READ);
     }
 
     protected GitServlet init() {
         GitServlet g = new GitServlet();
-        g.setRepositoryResolver(new org.eclipse.jgit.transport.resolver.RepositoryResolver() {
-            public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException {
-                try {
-                    return openRepository();
-                } catch (IOException e) {
-                    throw new RepositoryNotFoundException(req.getRequestURI(),e);
-                }
+        g.setRepositoryResolver((req, name) -> {
+            try {
+                return openRepository();
+            } catch (IOException e) {
+                throw new RepositoryNotFoundException(req.getRequestURI(), e);
             }
         });
 
         // this creates (and thus configures) the receiver program
-        g.setReceivePackFactory(new ReceivePackFactory() {
-            public ReceivePack create(HttpServletRequest req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException {
-                return createReceivePack(req,db);
-            }
-        });
+        g.setReceivePackFactory(this::createReceivePack);
 
-        g.setUploadPackFactory(new UploadPackFactory() {
-            public UploadPack create(HttpServletRequest req, Repository db) throws ServiceNotEnabledException, ServiceNotAuthorizedException {
-                return createUploadPack(req,db);
-            }
-        });
+        g.setUploadPackFactory(this::createUploadPack);
 
         try {
             g.init(new ServletConfig() {
@@ -114,23 +160,19 @@ public String getServletName() {
                 }
 
                 public ServletContext getServletContext() throws IllegalStateException {
-                    Jenkins j = Jenkins.getInstance();
-                    if (j == null) {
-                        throw new IllegalStateException();
-                    }
-                    return j.servletContext;
+                    return Jenkins.get().getServletContext();
                 }
 
                 public String getInitParameter(String name) {
                     return null;
                 }
 
-                public Enumeration getInitParameterNames() {
-                    return new Vector().elements();
+                public Enumeration getInitParameterNames() {
+                    return Collections.emptyEnumeration();
                 }
             });
         } catch (ServletException e) {
-            LOGGER.log(Level.WARNING,"Failed to initialize GitServlet for " + this,e);
+            LOGGER.log(Level.WARNING, e, () -> "Failed to initialize GitServlet for " + this);
             causeOfDeath = e;
         }
         return g;
@@ -139,28 +181,25 @@ public Enumeration getInitParameterNames() {
     /**
      * Handles git smart HTTP protocol.
      */
-    public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
-        if (g==null)
-            g=init();
+    public void doDynamic(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException, ServletException {
+        if (g == null) g = init();
 
-        if (causeOfDeath!=null)
-            throw new ServletException(causeOfDeath);
+        if (causeOfDeath != null) throw new ServletException(causeOfDeath);
 
         // This is one place where we allow POST without CSRF headers
         HttpServletRequest realRequest = CSRFExclusionImpl.unwrapRequest(req);
-        if (realRequest==null)
-            realRequest = req;
+        if (realRequest == null) realRequest = req;
 
         /*
-            GitServlet uses getPathInfo() to determine the current request, whereas in Stapler's sense it should be using
-            getRestOfPath().
+           GitServlet uses getPathInfo() to determine the current request, whereas in Stapler's sense it should be using
+           getRestOfPath().
 
-            However, this nicely cancels out with the the GitServlet behavior of wanting one token that specifies
-            the repository to work with.
+           However, this nicely cancels out with the the GitServlet behavior of wanting one token that specifies
+           the repository to work with.
 
-            So in this case one bug cancels out another and it works out well.
-         */
-        g.service(realRequest,rsp);
+           So in this case one bug cancels out another and it works out well.
+        */
+        g.service(realRequest, rsp);
     }
 
     private static final Logger LOGGER = Logger.getLogger(HttpGitRepository.class.getName());
diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/RepositoryResolver.java b/src/main/java/org/jenkinsci/plugins/gitserver/RepositoryResolver.java
index 7485f8a..728cc47 100644
--- a/src/main/java/org/jenkinsci/plugins/gitserver/RepositoryResolver.java
+++ b/src/main/java/org/jenkinsci/plugins/gitserver/RepositoryResolver.java
@@ -1,14 +1,14 @@
 package org.jenkinsci.plugins.gitserver;
 
+import edu.umd.cs.findbugs.annotations.CheckForNull;
 import hudson.ExtensionList;
 import hudson.ExtensionPoint;
+import java.io.IOException;
 import jenkins.model.Jenkins;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.UploadPack;
 
-import java.io.IOException;
-
 /**
  * Resolves the full name of the repository as given by Git clients to actual {@link Repository}.
  *
@@ -48,6 +48,7 @@ public abstract class RepositoryResolver implements ExtensionPoint {
      *      null if this resolver doesn't recognize the given path name.
      *      This will allow other {@link RepositoryResolver}s to get a shot at the repository.
      */
+    @CheckForNull
     public abstract ReceivePack createReceivePack(String fullRepositoryName) throws IOException, InterruptedException;
 
     /**
@@ -61,13 +62,10 @@ public abstract class RepositoryResolver implements ExtensionPoint {
      *      null if this resolver doesn't recognize the given path name.
      *      This will allow other {@link RepositoryResolver}s to get a shot at the repository.
      */
+    @CheckForNull
     public abstract UploadPack createUploadPack(String fullRepositoryName) throws IOException, InterruptedException;
 
     public static ExtensionList all() throws IllegalStateException {
-        Jenkins j = Jenkins.getInstance();
-        if (j == null) {
-            throw new IllegalStateException();
-        }
-        return j.getExtensionList(RepositoryResolver.class);
+        return Jenkins.get().getExtensionList(RepositoryResolver.class);
     }
 }
diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/AbstractGitCommand.java b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/AbstractGitCommand.java
index bcb05f2..f549055 100644
--- a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/AbstractGitCommand.java
+++ b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/AbstractGitCommand.java
@@ -15,7 +15,7 @@
  * @author Kohsuke Kawaguchi
  */
 abstract class AbstractGitCommand extends AsynchronousCommand {
-    @Argument(index=0, metaVar="REPO", required=true, usage="repository name")
+    @Argument(index = 0, metaVar = "REPO", required = true, usage = "repository name")
     protected String repoName;
 
     AbstractGitCommand(CommandLine cmdLine) {
@@ -24,15 +24,16 @@ abstract class AbstractGitCommand extends AsynchronousCommand {
 
     @Override
     protected final int runCommand() throws Exception {
-            try {
-                ParserProperties properties = ParserProperties.defaults().withAtSyntax(false);
-                new CmdLineParser(this, properties).parseArgument(getCmdLine().subList(1,getCmdLine().size()));
-            } catch (CmdLineException e) {
-                throw new AbortException(e.getMessage());
-            }
+        try {
+            ParserProperties properties = ParserProperties.defaults().withAtSyntax(false);
+            new CmdLineParser(this, properties)
+                    .parseArgument(getCmdLine().subList(1, getCmdLine().size()));
+        } catch (CmdLineException e) {
+            throw new AbortException(e.getMessage());
+        }
 
-            return doRun();
+        return doRun();
     }
-    
+
     protected abstract int doRun() throws Exception;
 }
diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommand.java b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommand.java
index 386ee59..839a2ea 100644
--- a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommand.java
+++ b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommand.java
@@ -1,6 +1,7 @@
 package org.jenkinsci.plugins.gitserver.ssh;
 
 import hudson.AbortException;
+import java.io.IOException;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.jenkinsci.main.modules.sshd.SshCommandFactory.CommandLine;
 import org.jenkinsci.plugins.gitserver.RepositoryResolver;
@@ -16,16 +17,15 @@ public ReceivePackCommand(CommandLine cmdLine) {
     }
 
     @Override
-    protected int doRun() throws Exception {
+    protected int doRun() throws IOException, InterruptedException {
         for (RepositoryResolver rr : RepositoryResolver.all()) {
             ReceivePack rp = rr.createReceivePack(repoName);
-            if (rp!=null) {
-                rp.receive(getInputStream(),getOutputStream(),getErrorStream());
+            if (rp != null) {
+                rp.receive(getInputStream(), getOutputStream(), getErrorStream());
                 return 0;
             }
         }
 
-        throw new AbortException("No such repository exists:"+repoName);
-
+        throw new AbortException("No such repository exists:" + repoName);
     }
 }
diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/SshCommandFactoryImpl.java b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/SshCommandFactoryImpl.java
index 59368e4..da2ab39 100644
--- a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/SshCommandFactoryImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/SshCommandFactoryImpl.java
@@ -11,12 +11,10 @@
 public class SshCommandFactoryImpl extends SshCommandFactory {
     @Override
     public Command create(CommandLine cmds) {
-        if (cmds.size()<1)  return null;
+        if (cmds.isEmpty()) return null;
 
-        if (cmds.get(0).equals("git-receive-pack"))
-            return new ReceivePackCommand(cmds);
-        if (cmds.get(0).equals("git-upload-pack"))
-            return new UploadPackCommand(cmds);
+        if (cmds.get(0).equals("git-receive-pack")) return new ReceivePackCommand(cmds);
+        if (cmds.get(0).equals("git-upload-pack")) return new UploadPackCommand(cmds);
 
         return null;
     }
diff --git a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/UploadPackCommand.java b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/UploadPackCommand.java
index a91be13..1ddb04f 100644
--- a/src/main/java/org/jenkinsci/plugins/gitserver/ssh/UploadPackCommand.java
+++ b/src/main/java/org/jenkinsci/plugins/gitserver/ssh/UploadPackCommand.java
@@ -1,6 +1,7 @@
 package org.jenkinsci.plugins.gitserver.ssh;
 
 import hudson.AbortException;
+import java.io.IOException;
 import org.eclipse.jgit.transport.UploadPack;
 import org.jenkinsci.main.modules.sshd.SshCommandFactory.CommandLine;
 import org.jenkinsci.plugins.gitserver.RepositoryResolver;
@@ -17,15 +18,17 @@ public UploadPackCommand(CommandLine cmdLine) {
     }
 
     @Override
-    protected int doRun() throws Exception {
+    protected int doRun() throws IOException, InterruptedException {
         for (RepositoryResolver rr : RepositoryResolver.all()) {
             UploadPack up = rr.createUploadPack(repoName);
-            if (up!=null) {
-                up.upload(getInputStream(),getOutputStream(),getErrorStream());
+            if (up != null) {
+                try (up) {
+                    up.upload(getInputStream(), getOutputStream(), getErrorStream());
+                }
                 return 0;
             }
         }
 
-        throw new AbortException("No such repository exists:"+repoName);
+        throw new AbortException("No such repository exists:" + repoName);
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommandTest.java b/src/test/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommandTest.java
index 6d26c9b..826a1fa 100644
--- a/src/test/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommandTest.java
+++ b/src/test/java/org/jenkinsci/plugins/gitserver/ssh/ReceivePackCommandTest.java
@@ -1,10 +1,19 @@
 package org.jenkinsci.plugins.gitserver.ssh;
 
-import hudson.Functions;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import hudson.model.User;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
 import java.util.EnumSet;
+import java.util.concurrent.TimeUnit;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.channel.ClientChannel;
 import org.apache.sshd.client.channel.ClientChannelEvent;
@@ -14,43 +23,23 @@
 import org.jenkinsci.main.modules.cli.auth.ssh.PublicKeySignatureWriter;
 import org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl;
 import org.jenkinsci.main.modules.sshd.SSHD;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 import org.jvnet.hudson.test.Issue;
 import org.jvnet.hudson.test.JenkinsRule;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.util.concurrent.TimeUnit;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
+import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
 
 /**
  * This class contains code borrowed and adapted from the Jenkins SSHD plugin.
  * Original source: org.jenkinsci.main.modules.sshd.SSHDTest.java
  */
-public class ReceivePackCommandTest {
-
-    @Rule
-    public JenkinsRule j = new JenkinsRule();
-
-    @Before
-    public void setUp() {
-        assumeFalse(Functions.isWindows());
-    }
+@WithJenkins
+class ReceivePackCommandTest {
+    private static final String USER = "enabled";
 
     @Test
     @Issue("SECURITY-3319")
-    public void shouldNotParseAtChar() throws Exception {
-        hudson.model.User enabled = hudson.model.User.getOrCreateByIdOrFullName("enabled");
+    void shouldNotParseAtChar(JenkinsRule ignored) throws Exception {
+        User enabled = User.getOrCreateByIdOrFullName(USER);
         KeyPair keyPair = generateKeys(enabled);
         SSHD server = SSHD.get();
         server.setPort(0);
@@ -64,7 +53,7 @@ public void shouldNotParseAtChar() throws Exception {
         try (SshClient client = SshClient.setUpDefaultClient()) {
             client.setServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE);
             client.start();
-            ConnectFuture future = client.connect("enabled", new InetSocketAddress(server.getActualPort()));
+            ConnectFuture future = client.connect(USER, new InetSocketAddress("localhost", server.getActualPort()));
             try (ClientSession session = future.verify(10, TimeUnit.SECONDS).getSession()) {
                 session.addPublicKeyIdentity(keyPair);
                 assertTrue(session.auth().await(10, TimeUnit.SECONDS));
@@ -77,14 +66,14 @@ public void shouldNotParseAtChar() throws Exception {
                     channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(5));
 
                     String errorMessage = errorStream.toString();
-                    assertThat(errorMessage, containsString("@" + tempPath));
-                    assertThat(errorMessage, not(containsString(content)));
+                    assertTrue(errorMessage.contains("@" + tempPath));
+                    assertFalse(errorMessage.contains(content));
                 }
             }
         }
     }
 
-    private static KeyPair generateKeys(hudson.model.User user) throws NoSuchAlgorithmException, IOException {
+    private static KeyPair generateKeys(User user) throws NoSuchAlgorithmException, IOException {
         // I'd prefer to generate Ed25519 keys here, but the API is too awkward currently
         // ECDSA keys would be even more awkward as we'd need a copy of the curve parameters
         KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
diff --git a/src/test/java/org/jenkinsci/plugins/gitserver/ssh/Security3342Test.java b/src/test/java/org/jenkinsci/plugins/gitserver/ssh/Security3342Test.java
index 6cd6b66..2dbec2b 100644
--- a/src/test/java/org/jenkinsci/plugins/gitserver/ssh/Security3342Test.java
+++ b/src/test/java/org/jenkinsci/plugins/gitserver/ssh/Security3342Test.java
@@ -1,13 +1,27 @@
 package org.jenkinsci.plugins.gitserver.ssh;
 
-import hudson.Functions;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import hudson.model.User;
+import java.io.File;
+import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
-import java.util.Collections;
 import java.util.List;
 import jenkins.model.Jenkins;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.InvalidRemoteException;
+import org.eclipse.jgit.errors.NoRemoteRepositoryException;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.SshSessionFactory;
 import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
@@ -15,50 +29,31 @@
 import org.jenkinsci.main.modules.cli.auth.ssh.PublicKeySignatureWriter;
 import org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl;
 import org.jenkinsci.main.modules.sshd.SSHD;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.jvnet.hudson.test.Issue;
 import org.jvnet.hudson.test.JenkinsRule;
 import org.jvnet.hudson.test.MockAuthorizationStrategy;
+import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
 
-import java.io.File;
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-
-public class Security3342Test {
-
-    @Rule
-    public JenkinsRule j = new JenkinsRule();
-
-    @Rule
-    public TemporaryFolder tmp = new TemporaryFolder();
-
-    @Before
-    public void setUp() {
-        assumeFalse(Functions.isWindows());
-    }
+@WithJenkins
+class Security3342Test {
+    private static final String USER = "tester";
 
     @Test
     @Issue("SECURITY-3342")
-    public void openRepositoryPermissionCheckTest() throws Exception {
+    void openRepositoryPermissionCheckTest(@TempDir Path tmp, JenkinsRule j) throws Exception {
 
         j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
-        j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.READ).everywhere().to("tester"));
-        hudson.model.User tester = hudson.model.User.getOrCreateByIdOrFullName("tester");
+        j.jenkins.setAuthorizationStrategy(
+                new MockAuthorizationStrategy().grant(Jenkins.READ).everywhere().to(USER));
+        User tester = User.getOrCreateByIdOrFullName(USER);
         KeyPair keyPair = generateKeys(tester);
         j.jenkins.save();
 
         // Fixed ssh port for Jenkins ssh server
         SSHD server = SSHD.get();
-        server.setPort(2222);
+        server.setPort(0);
 
         final SshdSessionFactory instance = new SshdSessionFactory() {
             @Override
@@ -70,12 +65,18 @@ protected Iterable getDefaultKeys(File sshDir) {
             protected ServerKeyDatabase getServerKeyDatabase(File homeDir, File sshDir) {
                 return new ServerKeyDatabase() {
                     @Override
-                    public List lookup(String connectAddress, InetSocketAddress remoteAddress, Configuration config) {
-                        return Collections.emptyList();
+                    public List lookup(
+                            String connectAddress, InetSocketAddress remoteAddress, Configuration config) {
+                        return List.of();
                     }
 
                     @Override
-                    public boolean accept(String connectAddress, InetSocketAddress remoteAddress, PublicKey serverKey, Configuration config, CredentialsProvider provider) {
+                    public boolean accept(
+                            String connectAddress,
+                            InetSocketAddress remoteAddress,
+                            PublicKey serverKey,
+                            Configuration config,
+                            CredentialsProvider provider) {
                         return true;
                     }
                 };
@@ -84,35 +85,35 @@ public boolean accept(String connectAddress, InetSocketAddress remoteAddress, Pu
         SshSessionFactory.setInstance(instance);
 
         CloneCommand clone = Git.cloneRepository();
-        clone.setURI("ssh://tester@localhost:2222/userContent.git");
-        File dir1 = tmp.newFolder();
-        clone.setDirectory(dir1);
+        clone.setURI("ssh://" + USER + "@localhost:" + server.getActualPort() + "/userContent.git");
+        Path dir1 = Files.createTempDirectory(tmp, null);
+        clone.setDirectory(dir1.toFile());
 
         // Do the git clone for a user with Jenkins.READ permission
-        Git gitClone1 = clone.call();
-        File gitDir1 = new File(dir1, ".git");
-        assertTrue(".git directory exist, clone operation succeed", gitDir1.exists());
+        assertDoesNotThrow(clone::call).close();
+        Path gitDir1 = dir1.resolve(".git");
+        assertTrue(Files.isDirectory(gitDir1), ".git directory exist, clone operation succeed");
 
         // Do the git clone for a user without Jenkins.READ permission
         j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy());
         j.jenkins.save();
 
-        File dir2 = tmp.newFolder();
-        clone.setDirectory(dir2);
+        Path dir2 = Files.createTempDirectory(tmp, null);
+        clone.setDirectory(dir2.toFile());
 
-        try {
-            Git gitClone2 = clone.call();
-        } catch (Exception e) {
-            // Verify that the expected exception was caught with the correct message
-            assertTrue(e.getCause() != null && e.getCause().getMessage().contains("hudson.security.AccessDeniedException3: tester is missing the Overall/Read permission"));
-        }
-        // Verify that the .git directory is not created
-        File gitDir2 = new File(dir2, ".git");
-        assertFalse(".git directory exist, clone operation succeed", gitDir2.exists());
+        // Verify that the expected exception was caught with the correct message
+        InvalidRemoteException e = assertThrows(InvalidRemoteException.class, clone::call);
+        assertInstanceOf(NoRemoteRepositoryException.class, e.getCause());
+        NoRemoteRepositoryException e2 = (NoRemoteRepositoryException) e.getCause();
+        assertTrue(e2.getMessage()
+                .contains("hudson.security.AccessDeniedException3: tester is missing the Overall/Read permission"));
 
+        // Verify that the .git directory is not created
+        Path gitDir2 = dir2.resolve(".git");
+        assertFalse(Files.isDirectory(gitDir2), ".git directory exist, clone operation succeed");
     }
 
-    private static KeyPair generateKeys(hudson.model.User user) throws NoSuchAlgorithmException, IOException {
+    private static KeyPair generateKeys(User user) throws NoSuchAlgorithmException, IOException {
         // I'd prefer to generate Ed25519 keys here, but the API is too awkward currently
         // ECDSA keys would be even more awkward as we'd need a copy of the curve parameters
         KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");