From e9c550abedfc91caa29c039d901d3c80296b3f9d Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Mon, 19 Jul 2021 11:52:48 +0530 Subject: [PATCH 01/86] Adding dependency The SSHPrivateKey binding will depend on Credential Binding Plugin --- pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 346aa913cf..d72b768370 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,12 @@ 3.7 test - + + + org.jenkins-ci.plugins + credentials-binding + 1.25 + org.jenkins-ci.plugins parameterized-trigger From 4fd5c4ccb547678936f939e01430e093e6753cae Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Mon, 19 Jul 2021 11:53:15 +0530 Subject: [PATCH 02/86] Bumping git-client plugin version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d72b768370..21f5527aed 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ org.jenkins-ci.plugins git-client - 3.7.2 + 3.8.0 org.jenkins-ci.plugins From e2125673a2c29e36f326e0edec066e7a21ab0c67 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Mon, 19 Jul 2021 12:30:32 +0530 Subject: [PATCH 03/86] SSHPrivateKey Binding Support Git SSH protocol authentication --- .../plugins/git/GitSSHPrivateKeyBinding.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java new file mode 100644 index 0000000000..3a9d9afa41 --- /dev/null +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -0,0 +1,192 @@ +package jenkins.plugins.git; + +import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.*; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.plugins.git.GitTool; +import hudson.util.Secret; +import org.jenkinsci.Symbol; +import org.jenkinsci.plugins.credentialsbinding.BindingDescriptor; +import org.jenkinsci.plugins.credentialsbinding.MultiBinding; +import org.jenkinsci.plugins.credentialsbinding.impl.AbstractOnDiskBinding; +import org.jenkinsci.plugins.credentialsbinding.impl.UnbindableDir; +import org.jenkinsci.plugins.gitclient.CliGitAPIImpl; +import org.jenkinsci.plugins.gitclient.Git; +import org.jenkinsci.plugins.gitclient.GitClient; +import org.kohsuke.stapler.DataBoundConstructor; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.*; + +public class GitSSHPrivateKeyBinding extends MultiBinding implements GitCredentialBindings, SSHKeyUtils { + final static private String PRIVATE_KEY = "PRIVATE_KEY"; + final static private String PASSPHRASE = "PASSPHRASE"; + final private String gitToolName; + private transient boolean unixNodeType; + + @DataBoundConstructor + public GitSSHPrivateKeyBinding(String gitToolName, String credentialsId) { + super(credentialsId); + this.gitToolName = gitToolName; + //Variables could be added if needed + } + + public String getGitToolName(){ + return this.gitToolName; + } + + private void setUnixNodeType(boolean value) { + this.unixNodeType = value; + } + + @Override + public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath, + @Nullable Launcher launcher, @NonNull TaskListener taskListener) throws IOException, InterruptedException { + final Map secretValues = new LinkedHashMap<>(); + final Map publicValues = new LinkedHashMap<>(); + SSHUserPrivateKey credentials = getCredentials(run); + setCredentialPairBindings(credentials,secretValues,publicValues); + GitTool cliGitTool = getCliGitTool(run, this.gitToolName, taskListener); + if(cliGitTool != null && filePath != null){ + final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); + setUnixNodeType(isCurrentNodeOSUnix(launcher)); + setGitEnvironmentVariables(getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), + new EnvVars(), taskListener), publicValues); + + } else { + + } + } + + @Override + public Set variables(@NonNull Run run) { + Set keys = new LinkedHashSet<>(); + keys.add(PRIVATE_KEY); + keys.add(PASSPHRASE); + return keys; + } + + @Override + public void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues) { + SSHUserPrivateKey sshUserPrivateKey = (SSHUserPrivateKey) credentials; + if(sshUserPrivateKey.isUsernameSecret()){ + secretValues.put(PRIVATE_KEY, sshUserPrivateKey.getUsername()); + }else{ + publicValues.put(PRIVATE_KEY, sshUserPrivateKey.getUsername()); + } + secretValues.put(PASSPHRASE, Secret.toString(((SSHUserPrivateKey) credentials).getPassphrase())); + } + + /*package*/void setGitEnvironmentVariables(@NonNull GitClient git, Map publicValues) throws IOException, InterruptedException { + setGitEnvironmentVariables(git,null,publicValues); + } + + @Override + public void setGitEnvironmentVariables(@NonNull GitClient git, Map secretValues, Map publicValues) throws IOException, InterruptedException { + if (unixNodeType && ((CliGitAPIImpl) git).isCliGitVerAtLeast(2,3,0,0)) + { + publicValues.put("GIT_TERMINAL_PROMPT", "false"); + } else { + publicValues.put("GCM_INTERACTIVE", "false"); + } + } + + @Override + public GitClient getGitClientInstance(String gitToolExe, FilePath repository, EnvVars env, TaskListener listener) throws IOException, InterruptedException { + Git gitInstance = Git.with(listener, env).using(gitToolExe); + return gitInstance.getClient(); + } + + @Override + protected Class type() { + return SSHUserPrivateKey.class; + } + +// private void putGitSSHEnvironmentVariable(SSHUserPrivateKey credentials, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { +// if(((CliGitAPIImpl) getGitClientInstance(listener)).isAtLeastVersion(2,3,0,0)){ +// if(Functions.isWindows()){ +// credMap.put("GIT_SSH_COMMAND","\"" + getSSHExePath(listener) + "\" -i " + "\"" + +// SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + "\" -o StrictHostKeyChecking=no"); +// } +// else { +// credMap.put("GIT_SSH_COMMAND","ssh -i "+ "\"" + +// SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + "\" -o StrictHostKeyChecking=no $@"); +// } +// }else { +// GenerateSSHScript sshScript = new GenerateSSHScript(credentials,getSSHExePath(listener)); +// FilePath tempScript = sshScript.write(credentials,workspace); +// credMap.put("GIT_SSH",tempScript.getRemote()); +// } +// } + + protected static final class GenerateSSHScript extends AbstractOnDiskBinding { + + private final String privateKeyVariable; + private final String passphraseVariable; + private final String sshExePath; + + protected GenerateSSHScript(SSHUserPrivateKey credentials,String sshExePath) { + super(SSHKeyUtils.getPrivateKey(credentials)+":"+SSHKeyUtils.getPassphrase(credentials), credentials.getId()); + this.privateKeyVariable = SSHKeyUtils.getPrivateKey(credentials); + this.passphraseVariable = SSHKeyUtils.getPassphrase(credentials); + this.sshExePath = sshExePath; + } + + @Override + protected FilePath write(SSHUserPrivateKey credentials, FilePath workspace) throws IOException, InterruptedException { + FilePath tempFile; + if(Functions.isWindows()){ + tempFile = workspace.createTempFile("gitSSHScript",".bat"); + tempFile.write("@echo off\r\n" + + "\"" + + this.sshExePath + + "\"" + + " -i " + + "\"" + + SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + + "\"" + + " -o StrictHostKeyChecking=no" , null); + }else { + tempFile = workspace.createTempFile("gitSSHScript",".sh"); + tempFile.write("ssh -i " + + SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + +" -o StrictHostKeyChecking=no $@",null); + tempFile.chmod(0500); + } + return tempFile; + } + + @Override + protected Class type() { + return SSHUserPrivateKey.class; + } + } + + @Symbol("GitSSHPrivateKey") + @Extension + public static final class DescriptorImpl extends BindingDescriptor { + + @NonNull + @Override + public String getDisplayName() { + return Messages.GitSSHPrivateKeyBinding_DisplayName(); + } + + @Override + protected Class type() { + return SSHUserPrivateKey.class; + } + + @Override + public boolean requiresWorkspace() { + return true; + } + } +} From 78a47fb2f1cd17716804426da52f074b1d650dc5 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Mon, 19 Jul 2021 12:31:21 +0530 Subject: [PATCH 04/86] Git Binding interface --- .../plugins/git/GitCredentialBindings.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/main/java/jenkins/plugins/git/GitCredentialBindings.java diff --git a/src/main/java/jenkins/plugins/git/GitCredentialBindings.java b/src/main/java/jenkins/plugins/git/GitCredentialBindings.java new file mode 100644 index 0000000000..e362e1f53d --- /dev/null +++ b/src/main/java/jenkins/plugins/git/GitCredentialBindings.java @@ -0,0 +1,86 @@ +package jenkins.plugins.git; + +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Executor; +import hudson.model.Node; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.plugins.git.GitTool; +import hudson.plugins.git.util.GitUtils; +import org.jenkinsci.plugins.gitclient.GitClient; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public interface GitCredentialBindings { + + /** + * Sets secret or public pair value(s) + * @param credentials The credentials {@link com.cloudbees.plugins.credentials.common.StandardCredentials}. Cannot be null + * @param secretValues The values{@link java.util.Map} to be hidden in build logs + * @param publicValues The values{@link java.util.Map} to be visible in build logs + **/ + void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues); + + /** + * Set Git specific environment variable + * @param git GitClient {@link org.jenkinsci.plugins.gitclient.GitClient}. Cannot be null. + * @param secretValues The values{@link java.util.Map} to be hidden in build logs + * @param publicValues The values{@link java.util.Map} to be visible in build logs + **/ + void setGitEnvironmentVariables(@NonNull GitClient git, Map secretValues, Map publicValues) throws IOException, InterruptedException; + + /** + * Performed operations on a git repository. Using Git implementations JGit/JGit Apache/Cli Git + * @param gitExe The path {@link java.lang.String} to git executable {@link org.jenkinsci.plugins.gitclient.Git#using(String)} + * @param repository The path {@link java.lang.String} to working directory {@link org.jenkinsci.plugins.gitclient.Git#in(File)} + * @param env The environment values {@link hudson.EnvVars} + * @param listener The task listener. + * @return a GitClient implementation {@link org.jenkinsci.plugins.gitclient.GitClient} + **/ + GitClient getGitClientInstance(String gitExe, FilePath repository, + EnvVars env, TaskListener listener) throws IOException, InterruptedException; + /** + * Checks the OS environment of the node/controller + * @param launcher The launcher.Cannot be null + * @return false if current node/controller is not running in windows environment + **/ + default boolean isCurrentNodeOSUnix(@NonNull Launcher launcher){ + return launcher.isUnix(); + } + + /** + * Ensures that the gitTool available is of type cli git/GitTool.class {@link hudson.plugins.git.GitTool}. + * @param run The build {@link hudson.model.Run}. Cannot be null + * @param gitToolName The name of the git tool {@link java.lang.String} + * @param listener The task listener. Cannot be null. + * @return A git tool of type GitTool.class {@link hudson.plugins.git.GitTool} or null + **/ + default GitTool getCliGitTool(Run run, String gitToolName, + TaskListener listener) throws IOException, InterruptedException { + + Executor buildExecutor = run.getExecutor(); + if (buildExecutor != null) { + Node currentNode = buildExecutor.getOwner().getNode(); + //Check node is not null + if (currentNode != null) { + GitTool nameSpecificGitTool = GitUtils.resolveGitTool(gitToolName,currentNode,new EnvVars(),listener); + if(nameSpecificGitTool != null){ + GitTool typeSpecificGitTool = nameSpecificGitTool.getDescriptor().getInstallation(nameSpecificGitTool.getName()); + if(typeSpecificGitTool != null) { + boolean check = typeSpecificGitTool.getClass().equals(GitTool.class); + if (check) { + return nameSpecificGitTool; + } + } + } + } + } + return null; + } +} From 6eeecee5b339c5551635c197fb8cf3cadb2a9c74 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:47:36 +0530 Subject: [PATCH 05/86] Adding dependency to support Git SSH binding Two dependencies are added namely Bouncycastle API Plugin and SSHJ library --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index e5094ce976..19a9d1f381 100644 --- a/pom.xml +++ b/pom.xml @@ -168,6 +168,17 @@ credentials-binding 1.25 + + + org.jenkins-ci.plugins + bouncycastle-api + + + + com.hierynomus + sshj + 0.31.0 + org.jenkins-ci.plugins parameterized-trigger From 2bc31408aad7d88ee407247cceda97de6f14b5b8 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:48:47 +0530 Subject: [PATCH 06/86] Contract to support Git SSH binding implementation --- .../java/jenkins/plugins/git/SSHKeyUtils.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/jenkins/plugins/git/SSHKeyUtils.java diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java new file mode 100644 index 0000000000..b87a6c4513 --- /dev/null +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -0,0 +1,54 @@ +package jenkins.plugins.git; + +import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import hudson.FilePath; +import hudson.util.Secret; +import jenkins.bouncycastle.api.PEMEncodable; +import org.jenkinsci.plugins.gitclient.GitClient; + +import java.io.IOException; +import java.security.UnrecoverableKeyException; + +public interface SSHKeyUtils { + + static String getPrivateKey(SSHUserPrivateKey credentials) { + return credentials.getPrivateKeys().get(0); + } + + static String getPassphrase(SSHUserPrivateKey credentials) { + return Secret.toString(credentials.getPassphrase()); + } + + static boolean isPrivateKeyEncrypted(String passphrase) { + return passphrase.isEmpty() ? false : true; + } + + //TODO Use getSSHExecutable method + default String getSSHExePathInWin(GitClient git) throws IOException, InterruptedException { +// return ((CliGitAPIImpl) git).getSSHExecutable()); + return "ssh"; + } + + default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath workspace) throws InterruptedException, IOException { + FilePath tempKeyFile = workspace.createTempFile("private", ".key"); + final String privateKeyValue = getPassphrase(credentials); + final String passphraseValue = getPassphrase(credentials); + try { + if (isPrivateKeyEncrypted(privateKeyValue)) { + if (OpenSSHKeyFormatImpl.isOpenSSHFormat(privateKeyValue)) { + OpenSSHKeyFormatImpl openSSHKeyFormat = new OpenSSHKeyFormatImpl(privateKeyValue, passphraseValue); + tempKeyFile.write(openSSHKeyFormat.getDecodedPrivateKey(), null); + } else { + tempKeyFile.write(PEMEncodable.decode(privateKeyValue, passphraseValue.toCharArray()).encode(), null); + } + } else { + tempKeyFile.write(privateKeyValue, null); + } + tempKeyFile.chmod(0500); + return tempKeyFile; + } catch (UnrecoverableKeyException e) { + e.printStackTrace(); + return null; + } + } +} From e45ed3301de3d63e6f687d5b823b8fb7ad45b9b9 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:51:15 +0530 Subject: [PATCH 07/86] Support for OpenSSH key format This class provides the methods to decrypt a passphrase protected private key in openssh format --- .../plugins/git/OpenSSHKeyFormatImpl.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java new file mode 100644 index 0000000000..be6fd30fc2 --- /dev/null +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -0,0 +1,56 @@ +package jenkins.plugins.git; + +import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; +import net.schmizz.sshj.userauth.password.PasswordFinder; +import net.schmizz.sshj.userauth.password.Resource; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.IOException; +import java.io.StringWriter; + +public class OpenSSHKeyFormatImpl { + + private final String privateKey; + private final String passphrase; + + public OpenSSHKeyFormatImpl(final String privateKey, final String passphrase) { + this.privateKey = privateKey; + this.passphrase = passphrase; + } + + public static boolean isOpenSSHFormat(String privateKey) { + final String HEADER = "-----BEGIN OPENSSH PRIVATE KEY-----"; + return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); + } + + public String getDecodedPrivateKey() throws IOException { + OpenSSHKeyV1KeyFile openSSHProtectedKey = new OpenSSHKeyV1KeyFile(); + openSSHProtectedKey.init(privateKey, "", new AcquirePassphrase(passphrase.toCharArray())); + byte[] content = openSSHProtectedKey.getPrivate().getEncoded(); + StringWriter sw = new StringWriter(); + PemWriter pemWriter = new PemWriter(sw); + PemObject pemObject = new PemObject("PRIVATE KEY", content); + pemWriter.writeObject(pemObject); + return sw.toString(); + } + + private final static class AcquirePassphrase implements PasswordFinder { + + char[] p; + + AcquirePassphrase(char[] passphrase) { + this.p = passphrase; + } + + @Override + public char[] reqPassword(Resource resource) { + return p; + } + + @Override + public boolean shouldRetry(Resource resource) { + return false; + } + } +} From 9030a2c6d41ddc3f0b52075f00c9c467ae4cd4d6 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:51:57 +0530 Subject: [PATCH 08/86] Adding display-name property for Git SSH binding --- src/main/resources/jenkins/plugins/git/Messages.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/jenkins/plugins/git/Messages.properties b/src/main/resources/jenkins/plugins/git/Messages.properties index eeb968c080..8a22bf9544 100644 --- a/src/main/resources/jenkins/plugins/git/Messages.properties +++ b/src/main/resources/jenkins/plugins/git/Messages.properties @@ -26,3 +26,4 @@ GitStep.git=Git within.Repository=Within Repository additional=Additional GitUsernamePasswordBinding.DisplayName=Git Username and Password +GitSSHPrivateKeyBinding.DisplayName=Git SSH Private Key From 9312a70b49cc0c36c94d1c6595eb0765fdad2279 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:53:02 +0530 Subject: [PATCH 09/86] Inline help for Git SSH credentials-id --- .../help-credentialsId.html | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html new file mode 100644 index 0000000000..dda94e78a7 --- /dev/null +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html @@ -0,0 +1,31 @@ +
+ Set the git private key for SSH protocol. + +

+Shell example +

+withCredentials([GitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+  sh 'git fetch --all'
+}
+
+

+ +

+Batch example +

+withCredentials([GitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+  bat 'git submodule update --init --recursive'
+}
+
+

+ +

+Powershell example +

+withCredentials([GitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+  powershell 'git push'
+}
+
+

+ +
From a8f38f39e155729b49a840de8c59478f595060bf Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:53:24 +0530 Subject: [PATCH 10/86] Inline help for Git SSH git-Tool-name --- .../git/GitSSHPrivateKeyBinding/help-gitToolName.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html new file mode 100644 index 0000000000..0d36f771e9 --- /dev/null +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html @@ -0,0 +1,5 @@ +
+

+ Specify the Git tool installation name +

+
\ No newline at end of file From 9e17779785feb6c0d4ea790b047d2993bb5edccf Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:54:11 +0530 Subject: [PATCH 11/86] Adding config file for Git SSH binding --- .../plugins/git/GitSSHPrivateKeyBinding/config.jelly | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly new file mode 100644 index 0000000000..2a018cfe86 --- /dev/null +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file From b53385b6ddf8ab476980e89ee9048916c0e76335 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 21 Jul 2021 23:58:40 +0530 Subject: [PATCH 12/86] ListBox for git-tool-name --- .../jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 3a9d9afa41..432fba78e9 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -179,6 +179,17 @@ public String getDisplayName() { return Messages.GitSSHPrivateKeyBinding_DisplayName(); } + public ListBoxModel doFillGitToolNameItems() { + ListBoxModel items = new ListBoxModel(); + List toolList = Jenkins.get().getDescriptorByType(GitSCM.DescriptorImpl.class).getGitTools(); + for (GitTool t : toolList){ + if(t.getClass().equals(GitTool.class)){ + items.add(t.getName()); + } + } + return items; + } + @Override protected Class type() { return SSHUserPrivateKey.class; From 588c389cb39d2b7045102804a12c369c0b5904b0 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:01:34 +0530 Subject: [PATCH 13/86] Check current node os environment Use launcher.isUnix method value to check the current node os environment. Add some formatting --- .../plugins/git/GitSSHPrivateKeyBinding.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 432fba78e9..b30304eedb 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -142,23 +142,24 @@ protected GenerateSSHScript(SSHUserPrivateKey credentials,String sshExePath) { @Override protected FilePath write(SSHUserPrivateKey credentials, FilePath workspace) throws IOException, InterruptedException { FilePath tempFile; - if(Functions.isWindows()){ - tempFile = workspace.createTempFile("gitSSHScript",".bat"); + if (unixNodeType) { + tempFile = workspace.createTempFile("gitSSHScript", ".sh"); + tempFile.write( + "ssh -i " + + getPrivateKeyFile(credentials, workspace).getRemote() + + " -o StrictHostKeyChecking=no $@", null); + tempFile.chmod(0500); + } else { + tempFile = workspace.createTempFile("gitSSHScript", ".bat"); tempFile.write("@echo off\r\n" + "\"" + this.sshExePath + "\"" + " -i " + "\"" - + SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + + getPrivateKeyFile(credentials, workspace).getRemote() + "\"" - + " -o StrictHostKeyChecking=no" , null); - }else { - tempFile = workspace.createTempFile("gitSSHScript",".sh"); - tempFile.write("ssh -i " - + SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() - +" -o StrictHostKeyChecking=no $@",null); - tempFile.chmod(0500); + + " -o StrictHostKeyChecking=no", null); } return tempFile; } From d70831c30ee4c951e57e49c52b3924770fb3b3a1 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:06:39 +0530 Subject: [PATCH 14/86] Change to non-static inner class Removing static keyword to support SSHkeyUtils methods --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index b30304eedb..c7bca42b0c 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -128,8 +128,8 @@ protected Class type() { protected static final class GenerateSSHScript extends AbstractOnDiskBinding { - private final String privateKeyVariable; - private final String passphraseVariable; + protected final class SSHScriptFile extends AbstractOnDiskBinding { + private final String sshExePath; protected GenerateSSHScript(SSHUserPrivateKey credentials,String sshExePath) { From 443c0f73949c56421e148aa6587f250b236fd067 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:08:17 +0530 Subject: [PATCH 15/86] Check if current git version is at-least the required version --- .../plugins/git/GitSSHPrivateKeyBinding.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index c7bca42b0c..3b60eed596 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -90,8 +90,7 @@ public void setCredentialPairBindings(@NonNull StandardCredentials credentials, @Override public void setGitEnvironmentVariables(@NonNull GitClient git, Map secretValues, Map publicValues) throws IOException, InterruptedException { - if (unixNodeType && ((CliGitAPIImpl) git).isCliGitVerAtLeast(2,3,0,0)) - { + if (unixNodeType && isGitVersionAtLeast(git, 2, 3, 0, 0)) { publicValues.put("GIT_TERMINAL_PROMPT", "false"); } else { publicValues.put("GCM_INTERACTIVE", "false"); @@ -109,22 +108,9 @@ protected Class type() { return SSHUserPrivateKey.class; } -// private void putGitSSHEnvironmentVariable(SSHUserPrivateKey credentials, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { -// if(((CliGitAPIImpl) getGitClientInstance(listener)).isAtLeastVersion(2,3,0,0)){ -// if(Functions.isWindows()){ -// credMap.put("GIT_SSH_COMMAND","\"" + getSSHExePath(listener) + "\" -i " + "\"" + -// SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + "\" -o StrictHostKeyChecking=no"); -// } -// else { -// credMap.put("GIT_SSH_COMMAND","ssh -i "+ "\"" + -// SSHKeyUtils.getDecodedPrivateKey(credentials,workspace).getRemote() + "\" -o StrictHostKeyChecking=no $@"); -// } -// }else { -// GenerateSSHScript sshScript = new GenerateSSHScript(credentials,getSSHExePath(listener)); -// FilePath tempScript = sshScript.write(credentials,workspace); -// credMap.put("GIT_SSH",tempScript.getRemote()); -// } -// } + private boolean isGitVersionAtLeast(GitClient git, int major, int minor, int rev, int bugfix) { + return ((CliGitAPIImpl) git).isCliGitVerAtLeast(major, minor, rev, bugfix); + } protected static final class GenerateSSHScript extends AbstractOnDiskBinding { From 65624c0e056efcf918ac51b63023b98c35887889 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:09:05 +0530 Subject: [PATCH 16/86] Adding imports --- .../plugins/git/GitSSHPrivateKeyBinding.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 3b60eed596..801dece030 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -3,11 +3,17 @@ import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; import com.cloudbees.plugins.credentials.common.StandardCredentials; import edu.umd.cs.findbugs.annotations.NonNull; -import hudson.*; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; import hudson.model.Run; import hudson.model.TaskListener; +import hudson.plugins.git.GitSCM; import hudson.plugins.git.GitTool; +import hudson.util.ListBoxModel; import hudson.util.Secret; +import hudson.Extension; +import jenkins.model.Jenkins; import org.jenkinsci.Symbol; import org.jenkinsci.plugins.credentialsbinding.BindingDescriptor; import org.jenkinsci.plugins.credentialsbinding.MultiBinding; @@ -19,11 +25,13 @@ import org.kohsuke.stapler.DataBoundConstructor; import javax.annotation.Nullable; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + public class GitSSHPrivateKeyBinding extends MultiBinding implements GitCredentialBindings, SSHKeyUtils { final static private String PRIVATE_KEY = "PRIVATE_KEY"; From a55e3dd979fe7c70eeb5e51c1b566618af8d22b8 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:10:15 +0530 Subject: [PATCH 17/86] Assert launcher If workspace is provided launcher should not be null --- src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 801dece030..918f422ca5 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -62,7 +62,7 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath SSHUserPrivateKey credentials = getCredentials(run); setCredentialPairBindings(credentials,secretValues,publicValues); GitTool cliGitTool = getCliGitTool(run, this.gitToolName, taskListener); - if(cliGitTool != null && filePath != null){ + if (cliGitTool != null && filePath != null && launcher != null) { final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); setUnixNodeType(isCurrentNodeOSUnix(launcher)); setGitEnvironmentVariables(getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), From d38eea03fb53bbb5ac70c10896234358492fff7d Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:11:26 +0530 Subject: [PATCH 18/86] Code formatting --- .../jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 918f422ca5..87081a27ab 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -46,7 +46,7 @@ public GitSSHPrivateKeyBinding(String gitToolName, String credentialsId) { //Variables could be added if needed } - public String getGitToolName(){ + public String getGitToolName() { return this.gitToolName; } @@ -60,7 +60,7 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath final Map secretValues = new LinkedHashMap<>(); final Map publicValues = new LinkedHashMap<>(); SSHUserPrivateKey credentials = getCredentials(run); - setCredentialPairBindings(credentials,secretValues,publicValues); + setCredentialPairBindings(credentials, secretValues, publicValues); GitTool cliGitTool = getCliGitTool(run, this.gitToolName, taskListener); if (cliGitTool != null && filePath != null && launcher != null) { final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); @@ -74,7 +74,7 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath } @Override - public Set variables(@NonNull Run run) { + public Set variables(@NonNull Run run) { Set keys = new LinkedHashSet<>(); keys.add(PRIVATE_KEY); keys.add(PASSPHRASE); @@ -84,9 +84,9 @@ public Set variables(@NonNull Run run) { @Override public void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues) { SSHUserPrivateKey sshUserPrivateKey = (SSHUserPrivateKey) credentials; - if(sshUserPrivateKey.isUsernameSecret()){ + if (sshUserPrivateKey.isUsernameSecret()) { secretValues.put(PRIVATE_KEY, sshUserPrivateKey.getUsername()); - }else{ + } else { publicValues.put(PRIVATE_KEY, sshUserPrivateKey.getUsername()); } secretValues.put(PASSPHRASE, Secret.toString(((SSHUserPrivateKey) credentials).getPassphrase())); From f57be59d49953c3a295b8ca6aefdaf47d9d6650b Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:13:31 +0530 Subject: [PATCH 19/86] Create SSH command argument Only supported for linux distro --- .../plugins/git/GitSSHPrivateKeyBinding.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 87081a27ab..2a77268f68 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -120,7 +120,18 @@ private boolean isGitVersionAtLeast(GitClient git, int major, int minor, int rev return ((CliGitAPIImpl) git).isCliGitVerAtLeast(major, minor, rev, bugfix); } - protected static final class GenerateSSHScript extends AbstractOnDiskBinding { + private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir) throws IOException, InterruptedException { + if (unixNodeType) { + return "ssh -i " + + "\"" + + getPrivateKeyFile(credentials, tempDir).getRemote() + + "\" " + + "-o StrictHostKeyChecking=no $@"; + } else { + //TODO Using getSSHExePath + return null; + } + } protected final class SSHScriptFile extends AbstractOnDiskBinding { From df3d33cd6c0903fa00e72cabdd6c4c77a4cc83fb Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:14:55 +0530 Subject: [PATCH 20/86] Additional parameter to tell current node os type --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 2a77268f68..86d1b15249 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -136,12 +136,12 @@ private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir) throws protected final class SSHScriptFile extends AbstractOnDiskBinding { private final String sshExePath; + private final boolean unixNodeType; - protected GenerateSSHScript(SSHUserPrivateKey credentials,String sshExePath) { - super(SSHKeyUtils.getPrivateKey(credentials)+":"+SSHKeyUtils.getPassphrase(credentials), credentials.getId()); - this.privateKeyVariable = SSHKeyUtils.getPrivateKey(credentials); - this.passphraseVariable = SSHKeyUtils.getPassphrase(credentials); + protected SSHScriptFile(SSHUserPrivateKey credentials, String sshExePath, boolean unixNodeType) { + super(SSHKeyUtils.getPrivateKey(credentials) + ":" + SSHKeyUtils.getPassphrase(credentials), credentials.getId()); this.sshExePath = sshExePath; + this.unixNodeType = unixNodeType; } @Override From 0996235b89d24af65fde33039b9fc7d80960821a Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:17:50 +0530 Subject: [PATCH 21/86] Bind Git SSH environment variable Return Git SSH environment variables, perform SSH authentication on behalf of the user --- .../plugins/git/GitSSHPrivateKeyBinding.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 86d1b15249..1dda4e910e 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -64,12 +64,19 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath GitTool cliGitTool = getCliGitTool(run, this.gitToolName, taskListener); if (cliGitTool != null && filePath != null && launcher != null) { final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); + final GitClient git = getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), new EnvVars(), taskListener); setUnixNodeType(isCurrentNodeOSUnix(launcher)); - setGitEnvironmentVariables(getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), - new EnvVars(), taskListener), publicValues); - + setGitEnvironmentVariables(git, publicValues); + if (isGitVersionAtLeast(git, 2, 3, 0, 0)) { + secretValues.put("GIT_SSH_COMMAND", getSSHCmd(credentials, unbindTempDir.getDirPath())); + } else { + SSHScriptFile sshScript = new SSHScriptFile(credentials, getSSHExePathInWin(git), unixNodeType); + secretValues.put("GIT_SSH", sshScript.write(credentials, unbindTempDir.getDirPath()).getRemote()); + } + return new MultiEnvironment(secretValues, publicValues, unbindTempDir.getUnbinder()); } else { - + taskListener.getLogger().println("JGit and JGitApache type Git tools are not supported by this binding"); + return new MultiEnvironment(secretValues, publicValues); } } From 47588ae03838196389870d4456785d21890a0c12 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 22 Jul 2021 00:18:09 +0530 Subject: [PATCH 22/86] Code formatting --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 1dda4e910e..e6639af926 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -99,8 +99,8 @@ public void setCredentialPairBindings(@NonNull StandardCredentials credentials, secretValues.put(PASSPHRASE, Secret.toString(((SSHUserPrivateKey) credentials).getPassphrase())); } - /*package*/void setGitEnvironmentVariables(@NonNull GitClient git, Map publicValues) throws IOException, InterruptedException { - setGitEnvironmentVariables(git,null,publicValues); + /*package*/void setGitEnvironmentVariables(@NonNull GitClient git, Map publicValues) throws IOException, InterruptedException { + setGitEnvironmentVariables(git, null, publicValues); } @Override From d1eeb220a6eabd4a1812b680278623a5b72192db Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:02:36 +0530 Subject: [PATCH 23/86] Downgrading plugin artifact version from 20 to 19 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f38cb443e3..0189fbc76b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.jenkins-ci.plugins plugin - 4.20 + 4.19 From 96c0103336bc514bbb529100199d8e662e349975 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:03:51 +0530 Subject: [PATCH 24/86] Consuming git-client incremental Using newly updated getSSHExecutable method --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0189fbc76b..442cbf09d1 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ org.jenkins-ci.plugins git-client - 3.8.0 + 3.8.1-rc2855.492d1e3eeeaf org.jenkins-ci.plugins From d3ee23a0c7d448085ea92510c86f4d27f6587672 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:06:07 +0530 Subject: [PATCH 25/86] Using getSSHExecutable api This method provides path to ssh executable --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index b87a6c4513..21673b5fdd 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -4,6 +4,7 @@ import hudson.FilePath; import hudson.util.Secret; import jenkins.bouncycastle.api.PEMEncodable; +import org.jenkinsci.plugins.gitclient.CliGitAPIImpl; import org.jenkinsci.plugins.gitclient.GitClient; import java.io.IOException; @@ -23,10 +24,8 @@ static boolean isPrivateKeyEncrypted(String passphrase) { return passphrase.isEmpty() ? false : true; } - //TODO Use getSSHExecutable method default String getSSHExePathInWin(GitClient git) throws IOException, InterruptedException { -// return ((CliGitAPIImpl) git).getSSHExecutable()); - return "ssh"; + return ((CliGitAPIImpl) git).getSSHExecutable().getAbsolutePath(); } default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath workspace) throws InterruptedException, IOException { From c218b0bd4ac02a72610b64a13debe76aa97dd4c2 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:08:34 +0530 Subject: [PATCH 26/86] Replacing PEMWriter with JcaPEMWriter PEMWriter is deprecated --- src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index be6fd30fc2..8bbce5d890 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -3,8 +3,8 @@ import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import net.schmizz.sshj.userauth.password.PasswordFinder; import net.schmizz.sshj.userauth.password.Resource; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemWriter; import java.io.IOException; import java.io.StringWriter; @@ -29,9 +29,11 @@ public String getDecodedPrivateKey() throws IOException { openSSHProtectedKey.init(privateKey, "", new AcquirePassphrase(passphrase.toCharArray())); byte[] content = openSSHProtectedKey.getPrivate().getEncoded(); StringWriter sw = new StringWriter(); - PemWriter pemWriter = new PemWriter(sw); + JcaPEMWriter pemWriter = new JcaPEMWriter(sw); PemObject pemObject = new PemObject("PRIVATE KEY", content); pemWriter.writeObject(pemObject); + pemWriter.flush(); + pemWriter.close(); return sw.toString(); } From d596240ab89c5293664149c8cceb6b984fb5309a Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:09:28 +0530 Subject: [PATCH 27/86] Adding support for SSH binding in Windows --- .../jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index e6639af926..53f40beeda 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -127,7 +127,7 @@ private boolean isGitVersionAtLeast(GitClient git, int major, int minor, int rev return ((CliGitAPIImpl) git).isCliGitVerAtLeast(major, minor, rev, bugfix); } - private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir) throws IOException, InterruptedException { + private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir,String sshExePath) throws IOException, InterruptedException { if (unixNodeType) { return "ssh -i " + "\"" @@ -135,8 +135,12 @@ private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir) throws + "\" " + "-o StrictHostKeyChecking=no $@"; } else { - //TODO Using getSSHExePath - return null; + return "\"" + sshExePath + "\"" + + " -i " + + "\"" + + getPrivateKeyFile(credentials, tempDir).getRemote() + + "\"" + + " -o StrictHostKeyChecking=no"; } } From 8922adacf548bfe3eab0d1bc918b0e3ce6d998f5 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:12:15 +0530 Subject: [PATCH 28/86] Store SSH exe path in local variable Changing method getSSHCmd signature, ssh exe path papmeter added --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 53f40beeda..c3ffc6b233 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -65,12 +65,13 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath if (cliGitTool != null && filePath != null && launcher != null) { final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); final GitClient git = getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), new EnvVars(), taskListener); + final String sshExePath = getSSHExePathInWin(git); setUnixNodeType(isCurrentNodeOSUnix(launcher)); setGitEnvironmentVariables(git, publicValues); if (isGitVersionAtLeast(git, 2, 3, 0, 0)) { - secretValues.put("GIT_SSH_COMMAND", getSSHCmd(credentials, unbindTempDir.getDirPath())); + secretValues.put("GIT_SSH_COMMAND", getSSHCmd(credentials, unbindTempDir.getDirPath(), sshExePath)); } else { - SSHScriptFile sshScript = new SSHScriptFile(credentials, getSSHExePathInWin(git), unixNodeType); + SSHScriptFile sshScript = new SSHScriptFile(credentials, sshExePath, unixNodeType); secretValues.put("GIT_SSH", sshScript.write(credentials, unbindTempDir.getDirPath()).getRemote()); } return new MultiEnvironment(secretValues, publicValues, unbindTempDir.getUnbinder()); From a44b388840edbbd47c9b146a5a0d2d07754d1c0d Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:13:05 +0530 Subject: [PATCH 29/86] Removing redundant code and fixing errors --- .../jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index c3ffc6b233..09bf028ccc 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -91,13 +91,9 @@ public Set variables(@NonNull Run run) { @Override public void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues) { - SSHUserPrivateKey sshUserPrivateKey = (SSHUserPrivateKey) credentials; - if (sshUserPrivateKey.isUsernameSecret()) { - secretValues.put(PRIVATE_KEY, sshUserPrivateKey.getUsername()); - } else { - publicValues.put(PRIVATE_KEY, sshUserPrivateKey.getUsername()); - } - secretValues.put(PASSPHRASE, Secret.toString(((SSHUserPrivateKey) credentials).getPassphrase())); + SSHUserPrivateKey sshUserCredentials = (SSHUserPrivateKey) credentials; + secretValues.put(PRIVATE_KEY, SSHKeyUtils.getPrivateKey(sshUserCredentials)); + secretValues.put(PASSPHRASE, SSHKeyUtils.getPassphrase(sshUserCredentials)); } /*package*/void setGitEnvironmentVariables(@NonNull GitClient git, Map publicValues) throws IOException, InterruptedException { From a42ac940dcd4ea4d5366aa0ccd023763a632d320 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:14:30 +0530 Subject: [PATCH 30/86] Fixing private key value --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 21673b5fdd..c881edff7e 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -30,7 +30,7 @@ default String getSSHExePathInWin(GitClient git) throws IOException, Interrupted default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath workspace) throws InterruptedException, IOException { FilePath tempKeyFile = workspace.createTempFile("private", ".key"); - final String privateKeyValue = getPassphrase(credentials); + final String privateKeyValue = getPrivateKey(credentials); final String passphraseValue = getPassphrase(credentials); try { if (isPrivateKeyEncrypted(privateKeyValue)) { From 598eb80f92bac853b2ab81bc8b90c0f779e610e1 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 24 Jul 2021 00:15:53 +0530 Subject: [PATCH 31/86] Replacing private key with passphrase value Passphrase should be checked, to tell is private key is encrypted or not --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index c881edff7e..ca6d22941c 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -33,7 +33,7 @@ default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath works final String privateKeyValue = getPrivateKey(credentials); final String passphraseValue = getPassphrase(credentials); try { - if (isPrivateKeyEncrypted(privateKeyValue)) { + if (isPrivateKeyEncrypted(passphraseValue)) { if (OpenSSHKeyFormatImpl.isOpenSSHFormat(privateKeyValue)) { OpenSSHKeyFormatImpl openSSHKeyFormat = new OpenSSHKeyFormatImpl(privateKeyValue, passphraseValue); tempKeyFile.write(openSSHKeyFormat.getDecodedPrivateKey(), null); From 5c47934ce686da7b46da2cc04135d0ea8ed25e38 Mon Sep 17 00:00:00 2001 From: arpoch Date: Sun, 25 Jul 2021 13:55:18 +0530 Subject: [PATCH 32/86] Set node type before calling getSSHExePathInWin --- src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 09bf028ccc..27fb0cdf51 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -63,6 +63,7 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath setCredentialPairBindings(credentials, secretValues, publicValues); GitTool cliGitTool = getCliGitTool(run, this.gitToolName, taskListener); if (cliGitTool != null && filePath != null && launcher != null) { + setUnixNodeType(isCurrentNodeOSUnix(launcher)); final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); final GitClient git = getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), new EnvVars(), taskListener); final String sshExePath = getSSHExePathInWin(git); From 752a94a6717cf555df2755228a9a0552acac9df3 Mon Sep 17 00:00:00 2001 From: arpoch Date: Sun, 25 Jul 2021 13:58:29 +0530 Subject: [PATCH 33/86] Wrapper method for getSSHExePathInWin The getSSHPath method check the current node's os environment and sets the ssh executable path accordingly. --- .../plugins/git/GitSSHPrivateKeyBinding.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 27fb0cdf51..ccf6ac7fbf 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -66,8 +66,7 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath setUnixNodeType(isCurrentNodeOSUnix(launcher)); final UnbindableDir unbindTempDir = UnbindableDir.create(filePath); final GitClient git = getGitClientInstance(cliGitTool.getGitExe(), unbindTempDir.getDirPath(), new EnvVars(), taskListener); - final String sshExePath = getSSHExePathInWin(git); - setUnixNodeType(isCurrentNodeOSUnix(launcher)); + final String sshExePath = getSSHPath(git); setGitEnvironmentVariables(git, publicValues); if (isGitVersionAtLeast(git, 2, 3, 0, 0)) { secretValues.put("GIT_SSH_COMMAND", getSSHCmd(credentials, unbindTempDir.getDirPath(), sshExePath)); @@ -125,9 +124,18 @@ private boolean isGitVersionAtLeast(GitClient git, int major, int minor, int rev return ((CliGitAPIImpl) git).isCliGitVerAtLeast(major, minor, rev, bugfix); } + private String getSSHPath(GitClient git) throws IOException, InterruptedException { + if(unixNodeType){ + return "ssh"; + }else { + return getSSHExePathInWin(git); + } + } + private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir,String sshExePath) throws IOException, InterruptedException { if (unixNodeType) { - return "ssh -i " + return sshExePath + + " -i " + "\"" + getPrivateKeyFile(credentials, tempDir).getRemote() + "\" " From 69fafe5250da02dc2d69faa2c2e6c9588dc50af2 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 31 Jul 2021 18:23:41 +0530 Subject: [PATCH 34/86] Format private key to retrieve base64 encoded --- .../java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 8bbce5d890..3c7ba4f7ab 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -19,6 +19,16 @@ public OpenSSHKeyFormatImpl(final String privateKey, final String passphrase) { this.passphrase = passphrase; } + private byte[] getEncData(String privateKey){ + String data = privateKey + .replace("\n","") + .replace(" ","") + .replace("-----BEGINOPENSSHPRIVATEKEY-----","") + .replace("-----BEGINOPENSSHPRIVATEKEY-----",""); + Base64.Decoder decoder = Base64.getDecoder(); + return decoder.decode(data); + } + public static boolean isOpenSSHFormat(String privateKey) { final String HEADER = "-----BEGIN OPENSSH PRIVATE KEY-----"; return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); From ee925a71b1a1d320cc18d85eaa7f5b94c1addec4 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sat, 31 Jul 2021 18:59:25 +0530 Subject: [PATCH 35/86] Convert private key string to keypair Using SSHD library to extract KeyPair from private key string --- .../plugins/git/OpenSSHKeyFormatImpl.java | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 3c7ba4f7ab..d8d1b68e89 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -6,8 +6,15 @@ import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.util.io.pem.PemObject; +import javax.naming.SizeLimitExceededException; import java.io.IOException; import java.io.StringWriter; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; public class OpenSSHKeyFormatImpl { @@ -29,6 +36,22 @@ private byte[] getEncData(String privateKey){ return decoder.decode(data); } + private KeyPair getOpenSSHKeyPair(SessionContext session, NamedResource resourceKey, + String beginMarker, String endMarker, + FilePasswordProvider passwordProvider, + byte[] bytes, Map headers ) + throws IOException, GeneralSecurityException, SizeLimitExceededException { + OpenSSHKeyPairResourceParser openSSHParser = new OpenSSHKeyPairResourceParser(); + Collection keyPairs = openSSHParser.extractKeyPairs(session,resourceKey,beginMarker, + endMarker, passwordProvider, + bytes, headers); + if(keyPairs.size() > 1){ + throw new SizeLimitExceededException("Expected KeyPair size to be 1"); + }else { + return Collections.unmodifiableCollection(keyPairs).iterator().next(); + } + } + public static boolean isOpenSSHFormat(String privateKey) { final String HEADER = "-----BEGIN OPENSSH PRIVATE KEY-----"; return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); @@ -47,22 +70,17 @@ public String getDecodedPrivateKey() throws IOException { return sw.toString(); } - private final static class AcquirePassphrase implements PasswordFinder { + private final static class AcquirePassphrase implements FilePasswordProvider { - char[] p; + String passphrase; - AcquirePassphrase(char[] passphrase) { - this.p = passphrase; - } - - @Override - public char[] reqPassword(Resource resource) { - return p; + AcquirePassphrase(String passphrase) { + this.passphrase = passphrase; } @Override - public boolean shouldRetry(Resource resource) { - return false; + public String getPassword(SessionContext session, NamedResource resourceKey, int retryIndex) throws IOException { + return this.passphrase; } } } From 4a5e7de6658c05b46ca079126e1f8659db9da0c6 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 22:54:10 +0530 Subject: [PATCH 36/86] Bumping Jenkins version to 2.289.1 The current Jenkins version required by git plugin is atleast 2.263.1, which implicitly depends on sshd-plugin v2.7.0, making it difficult to support OpenSSH private format due to dependency conflict. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3a2baf9a58..e9c4ab9f34 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ jenkinsci/${project.artifactId}-plugin UTF-8 -Dfile.encoding=${project.build.sourceEncoding} - 2.263.1 + 2.289.1 8 false true From 63680a03b68cf8f610d191c5e099ee482dfdd58a Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 22:56:55 +0530 Subject: [PATCH 37/86] Adding sshd-core dependency to support git SSH binding Defining sshd-core library explicitly will implicitly bump the version of SSHD-plugin to v3.1.0 --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index e9c4ab9f34..ed373444b9 100644 --- a/pom.xml +++ b/pom.xml @@ -172,6 +172,12 @@ org.jenkins-ci.plugins bouncycastle-api + + + org.apache.sshd + sshd-core + 2.7.0 + org.jenkins-ci.plugins parameterized-trigger From 8c525eb1cd850b6566145348fa79d687f0c78306 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 22:58:11 +0530 Subject: [PATCH 38/86] Set file permission to readonly --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index ca6d22941c..73e76cac30 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -43,7 +43,7 @@ default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath works } else { tempKeyFile.write(privateKeyValue, null); } - tempKeyFile.chmod(0500); + tempKeyFile.chmod(0400); return tempKeyFile; } catch (UnrecoverableKeyException e) { e.printStackTrace(); From 4ec3a3dd644e025493f3ecb5c257c18034354fc4 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:08:09 +0530 Subject: [PATCH 39/86] Accessing methods as class methods --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 73e76cac30..e36ecf0d80 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -30,8 +30,8 @@ default String getSSHExePathInWin(GitClient git) throws IOException, Interrupted default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath workspace) throws InterruptedException, IOException { FilePath tempKeyFile = workspace.createTempFile("private", ".key"); - final String privateKeyValue = getPrivateKey(credentials); - final String passphraseValue = getPassphrase(credentials); + final String privateKeyValue = SSHKeyUtils.getPrivateKey(credentials); + final String passphraseValue = SSHKeyUtils.getPassphrase(credentials); try { if (isPrivateKeyEncrypted(passphraseValue)) { if (OpenSSHKeyFormatImpl.isOpenSSHFormat(privateKeyValue)) { From 638989b5e0be5a552ca7966215407f1bca080f14 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:08:59 +0530 Subject: [PATCH 40/86] Handling null point exception --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index e36ecf0d80..2a98dbd774 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -47,7 +47,7 @@ default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath works return tempKeyFile; } catch (UnrecoverableKeyException e) { e.printStackTrace(); - return null; } + return null; } } From e830aef97ba6b2fd55789643aef18df8c0158fb5 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:11:59 +0530 Subject: [PATCH 41/86] Adhering to lower case letter consistency in symbol --- src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index ccf6ac7fbf..29b6e5805e 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -11,7 +11,6 @@ import hudson.plugins.git.GitSCM; import hudson.plugins.git.GitTool; import hudson.util.ListBoxModel; -import hudson.util.Secret; import hudson.Extension; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; @@ -192,7 +191,7 @@ protected Class type() { } } - @Symbol("GitSSHPrivateKey") + @Symbol("gitSSHPrivateKey") @Extension public static final class DescriptorImpl extends BindingDescriptor { From be399fb76efe91768d3a67ee6621a02b0877b4bc Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:14:58 +0530 Subject: [PATCH 42/86] Header markers for openssh private key format --- src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index d8d1b68e89..8a3ed42352 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -20,6 +20,9 @@ public class OpenSSHKeyFormatImpl { private final String privateKey; private final String passphrase; + private static final String BEGIN_MARKER = OpenSSHKeyPairResourceParser.BEGIN_MARKER; + private static final String END_MARKER = OpenSSHKeyPairResourceParser.END_MARKER; + private static final String DASH_MARKER = "-----"; public OpenSSHKeyFormatImpl(final String privateKey, final String passphrase) { this.privateKey = privateKey; From 73b01ba3189be0ed997d45df1a36340e3af2a12c Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:19:32 +0530 Subject: [PATCH 43/86] Using header markers to check if private key openssh formatted --- src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 8a3ed42352..a80f1ef62f 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -56,7 +56,7 @@ private KeyPair getOpenSSHKeyPair(SessionContext session, NamedResource resource } public static boolean isOpenSSHFormat(String privateKey) { - final String HEADER = "-----BEGIN OPENSSH PRIVATE KEY-----"; + final String HEADER = DASH_MARKER+BEGIN_MARKER+DASH_MARKER; return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); } From 7ef7820aa5b55e25365dcfc327ab7b03ed7ba83c Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:27:15 +0530 Subject: [PATCH 44/86] Write decrypted openssh formatted private key The writePrivateKeyOpenSSHFormatted method performs write operation on a decrypted openssh formatted key. The key written in a temp file is openssh formatted. The shaded sshd-plugin dependency is used to carry of private key decryption operation. --- .../plugins/git/OpenSSHKeyFormatImpl.java | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index a80f1ef62f..f7a8aab7aa 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -1,14 +1,20 @@ package jenkins.plugins.git; -import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; -import net.schmizz.sshj.userauth.password.PasswordFinder; -import net.schmizz.sshj.userauth.password.Resource; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.util.io.pem.PemObject; +import hudson.FilePath; +import io.jenkins.cli.shaded.org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser; +import io.jenkins.cli.shaded.org.apache.sshd.common.NamedResource; +import io.jenkins.cli.shaded.org.apache.sshd.common.config.keys.FilePasswordProvider; +import io.jenkins.cli.shaded.org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter; +import io.jenkins.cli.shaded.org.apache.sshd.common.session.SessionContext; +import io.jenkins.cli.shaded.org.apache.sshd.common.util.io.SecureByteArrayOutputStream; import javax.naming.SizeLimitExceededException; + +import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; -import java.io.StringWriter; +import java.io.InputStream; +import java.io.FileOutputStream; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Base64; @@ -42,12 +48,12 @@ private byte[] getEncData(String privateKey){ private KeyPair getOpenSSHKeyPair(SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - byte[] bytes, Map headers ) + InputStream stream, Map headers ) throws IOException, GeneralSecurityException, SizeLimitExceededException { OpenSSHKeyPairResourceParser openSSHParser = new OpenSSHKeyPairResourceParser(); Collection keyPairs = openSSHParser.extractKeyPairs(session,resourceKey,beginMarker, endMarker, passwordProvider, - bytes, headers); + stream, headers); if(keyPairs.size() > 1){ throw new SizeLimitExceededException("Expected KeyPair size to be 1"); }else { @@ -55,6 +61,25 @@ private KeyPair getOpenSSHKeyPair(SessionContext session, NamedResource resource } } + private File writePrivateKeyOpenSSHFormatted(File tempFile) { + OpenSSHKeyPairResourceWriter privateKeyWriter = new OpenSSHKeyPairResourceWriter(); + SecureByteArrayOutputStream privateKeyBuffer = new SecureByteArrayOutputStream(); + ByteArrayInputStream stream = new ByteArrayInputStream(getEncData(this.privateKey)); + KeyPair sshKeyPair = null; + try { + sshKeyPair = getOpenSSHKeyPair(null,null,"","", + new AcquirePassphrase(this.passphrase), + stream,null); + privateKeyWriter.writePrivateKey(sshKeyPair, "", null, privateKeyBuffer); + FileOutputStream privateKeyFileStream = new FileOutputStream(tempFile); + privateKeyBuffer.writeTo(privateKeyFileStream); + privateKeyFileStream.close(); + } catch (IOException | SizeLimitExceededException | GeneralSecurityException e) { + e.printStackTrace(); + } + return tempFile; + } + public static boolean isOpenSSHFormat(String privateKey) { final String HEADER = DASH_MARKER+BEGIN_MARKER+DASH_MARKER; return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); From b419b4c36c38e2ab807cc617821d912e1637af24 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:31:55 +0530 Subject: [PATCH 45/86] Using header markers to retrieve key body --- .../java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index f7a8aab7aa..833a1f5328 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -37,10 +37,9 @@ public OpenSSHKeyFormatImpl(final String privateKey, final String passphrase) { private byte[] getEncData(String privateKey){ String data = privateKey - .replace("\n","") - .replace(" ","") - .replace("-----BEGINOPENSSHPRIVATEKEY-----","") - .replace("-----BEGINOPENSSHPRIVATEKEY-----",""); + .replace(DASH_MARKER+BEGIN_MARKER+DASH_MARKER,"") + .replace(DASH_MARKER+END_MARKER+DASH_MARKER,"") + .replaceAll("\\s",""); Base64.Decoder decoder = Base64.getDecoder(); return decoder.decode(data); } From 9bc6d3ac0bf5eef052b63b4028d732f31cadd252 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:32:39 +0530 Subject: [PATCH 46/86] Wrapper method to use writePrivateKeyOpenSSHFormatted method --- .../jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 833a1f5328..ddb5a88f09 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -84,17 +84,10 @@ public static boolean isOpenSSHFormat(String privateKey) { return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); } - public String getDecodedPrivateKey() throws IOException { - OpenSSHKeyV1KeyFile openSSHProtectedKey = new OpenSSHKeyV1KeyFile(); - openSSHProtectedKey.init(privateKey, "", new AcquirePassphrase(passphrase.toCharArray())); - byte[] content = openSSHProtectedKey.getPrivate().getEncoded(); - StringWriter sw = new StringWriter(); - JcaPEMWriter pemWriter = new JcaPEMWriter(sw); - PemObject pemObject = new PemObject("PRIVATE KEY", content); - pemWriter.writeObject(pemObject); - pemWriter.flush(); - pemWriter.close(); - return sw.toString(); + public FilePath getOpenSSHKeyFile(FilePath tempKeyFile) throws IOException, InterruptedException, GeneralSecurityException, SizeLimitExceededException { + File tempFile = new File(tempKeyFile.toURI()); + writePrivateKeyOpenSSHFormatted(tempFile); + return new FilePath(tempFile); } private final static class AcquirePassphrase implements FilePasswordProvider { From 82022ef95c6c5926c8dd6f5bbc710632a17afa3a Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Wed, 4 Aug 2021 23:33:18 +0530 Subject: [PATCH 47/86] Using new getOpenSSHKeyFile method --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 2a98dbd774..6a0804f9dd 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -7,8 +7,9 @@ import org.jenkinsci.plugins.gitclient.CliGitAPIImpl; import org.jenkinsci.plugins.gitclient.GitClient; +import javax.naming.SizeLimitExceededException; import java.io.IOException; -import java.security.UnrecoverableKeyException; +import java.security.GeneralSecurityException; public interface SSHKeyUtils { @@ -36,7 +37,7 @@ default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath works if (isPrivateKeyEncrypted(passphraseValue)) { if (OpenSSHKeyFormatImpl.isOpenSSHFormat(privateKeyValue)) { OpenSSHKeyFormatImpl openSSHKeyFormat = new OpenSSHKeyFormatImpl(privateKeyValue, passphraseValue); - tempKeyFile.write(openSSHKeyFormat.getDecodedPrivateKey(), null); + openSSHKeyFormat.getOpenSSHKeyFile(tempKeyFile); } else { tempKeyFile.write(PEMEncodable.decode(privateKeyValue, passphraseValue.toCharArray()).encode(), null); } @@ -45,7 +46,7 @@ default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath works } tempKeyFile.chmod(0400); return tempKeyFile; - } catch (UnrecoverableKeyException e) { + } catch (IOException | InterruptedException | GeneralSecurityException | SizeLimitExceededException e) { e.printStackTrace(); } return null; From 319442e02ccdefd5345616d646a0a97c6a528573 Mon Sep 17 00:00:00 2001 From: arpoch Date: Fri, 6 Aug 2021 19:19:17 +0530 Subject: [PATCH 48/86] Removing shell script $@ --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 29b6e5805e..ea2502c038 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -133,12 +133,12 @@ private String getSSHPath(GitClient git) throws IOException, InterruptedExceptio private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir,String sshExePath) throws IOException, InterruptedException { if (unixNodeType) { - return sshExePath + + return + sshExePath + + " -i " - + "\"" + getPrivateKeyFile(credentials, tempDir).getRemote() - + "\" " - + "-o StrictHostKeyChecking=no $@"; + + " -o StrictHostKeyChecking=no"; } else { return "\"" + sshExePath + "\"" + " -i " From ea86cda8097df52ae64a816a77919cfa4b32e774 Mon Sep 17 00:00:00 2001 From: Mark Waite Date: Fri, 6 Aug 2021 13:12:27 -0600 Subject: [PATCH 49/86] Update example capitalization --- .../git/GitSSHPrivateKeyBinding/help-credentialsId.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html index dda94e78a7..2f0f084315 100644 --- a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html @@ -4,7 +4,7 @@

Shell example

-withCredentials([GitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   sh 'git fetch --all'
 }
 
@@ -13,7 +13,7 @@

Batch example

-withCredentials([GitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   bat 'git submodule update --init --recursive'
 }
 
@@ -22,7 +22,7 @@

Powershell example

-withCredentials([GitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   powershell 'git push'
 }
 
From dcc83b348fd3a8e5c1fd633ee6a2a758010d5286 Mon Sep 17 00:00:00 2001 From: arpoch Date: Sat, 7 Aug 2021 18:08:45 +0530 Subject: [PATCH 50/86] Renaming symbol name for ssh-binding inline-help --- .../git/GitSSHPrivateKeyBinding/help-credentialsId.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html index 2f0f084315..bd2c552e67 100644 --- a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html @@ -4,7 +4,7 @@

Shell example

-withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitSSHPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   sh 'git fetch --all'
 }
 
@@ -13,7 +13,7 @@

Batch example

-withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitSSHPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   bat 'git submodule update --init --recursive'
 }
 
@@ -22,7 +22,7 @@

Powershell example

-withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitSSHPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   powershell 'git push'
 }
 
From e13d6691ee0ed190d5559068fb003b373baef9d9 Mon Sep 17 00:00:00 2001 From: arpoch Date: Wed, 11 Aug 2021 08:14:02 +0530 Subject: [PATCH 51/86] Depending on sshd plugin v3.1.0 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index d24d6f8dc0..3f41e0730d 100644 --- a/pom.xml +++ b/pom.xml @@ -172,11 +172,11 @@ org.jenkins-ci.plugins bouncycastle-api
- + - org.apache.sshd - sshd-core - 2.7.0 + org.jenkins-ci.modules + sshd + 3.1.0 org.jenkins-ci.plugins From dc83e1c5e168071e06e4a69acd08d2295bb902f9 Mon Sep 17 00:00:00 2001 From: Mark Waite Date: Tue, 10 Aug 2021 21:30:23 -0600 Subject: [PATCH 52/86] Use 2.289.x bom --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3f41e0730d..f2e08fc7ad 100644 --- a/pom.xml +++ b/pom.xml @@ -278,7 +278,7 @@ io.jenkins.tools.bom - bom-2.263.x + bom-2.289.x 923.v08bdc07cd40f import pom From 583ee18d8d69191a9502f8d62521681a919c2dd7 Mon Sep 17 00:00:00 2001 From: arpoch Date: Wed, 11 Aug 2021 12:22:18 +0530 Subject: [PATCH 53/86] Discarding shaded sshd-core api imports Replacing the shaded sshd-core api provided by Jenkins core with sshd-core api provided form SSHD plugin --- .../jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index ddb5a88f09..7b23048755 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -1,12 +1,12 @@ package jenkins.plugins.git; import hudson.FilePath; -import io.jenkins.cli.shaded.org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser; -import io.jenkins.cli.shaded.org.apache.sshd.common.NamedResource; -import io.jenkins.cli.shaded.org.apache.sshd.common.config.keys.FilePasswordProvider; -import io.jenkins.cli.shaded.org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter; -import io.jenkins.cli.shaded.org.apache.sshd.common.session.SessionContext; -import io.jenkins.cli.shaded.org.apache.sshd.common.util.io.SecureByteArrayOutputStream; +import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.io.SecureByteArrayOutputStream; import javax.naming.SizeLimitExceededException; From cbf621546929fec5b014c9752f7c25f969e5a34e Mon Sep 17 00:00:00 2001 From: arpoch Date: Wed, 11 Aug 2021 14:02:27 +0530 Subject: [PATCH 54/86] Code formatting --- .../plugins/git/GitSSHPrivateKeyBinding.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index ea2502c038..5843336bcb 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -124,23 +124,22 @@ private boolean isGitVersionAtLeast(GitClient git, int major, int minor, int rev } private String getSSHPath(GitClient git) throws IOException, InterruptedException { - if(unixNodeType){ + if (unixNodeType) { return "ssh"; - }else { + } else { return getSSHExePathInWin(git); } } - private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir,String sshExePath) throws IOException, InterruptedException { + private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir, String sshExePath) throws IOException, InterruptedException { if (unixNodeType) { return sshExePath - + - " -i " - + getPrivateKeyFile(credentials, tempDir).getRemote() - + " -o StrictHostKeyChecking=no"; + + " -i " + + getPrivateKeyFile(credentials, tempDir).getRemote() + + " -o StrictHostKeyChecking=no"; } else { - return "\"" + sshExePath + "\"" + return "\"" + sshExePath + "\"" + " -i " + "\"" + getPrivateKeyFile(credentials, tempDir).getRemote() @@ -204,8 +203,8 @@ public String getDisplayName() { public ListBoxModel doFillGitToolNameItems() { ListBoxModel items = new ListBoxModel(); List toolList = Jenkins.get().getDescriptorByType(GitSCM.DescriptorImpl.class).getGitTools(); - for (GitTool t : toolList){ - if(t.getClass().equals(GitTool.class)){ + for (GitTool t : toolList) { + if (t.getClass().equals(GitTool.class)) { items.add(t.getName()); } } From 74500c193832489bdc079b67ea1b45d22d876bb1 Mon Sep 17 00:00:00 2001 From: arpoch Date: Wed, 11 Aug 2021 23:59:23 +0530 Subject: [PATCH 55/86] Api name change for isOpenSSHFormat --- src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 7b23048755..3c3757c947 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -79,7 +79,7 @@ private File writePrivateKeyOpenSSHFormatted(File tempFile) { return tempFile; } - public static boolean isOpenSSHFormat(String privateKey) { + public static boolean isOpenSSHFormatted(String privateKey) { final String HEADER = DASH_MARKER+BEGIN_MARKER+DASH_MARKER; return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); } From 1ff2fcb803065e93388b426163d2119bac9a239b Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 00:00:06 +0530 Subject: [PATCH 56/86] Api name change for getOpenSSHKeyFile --- src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 3c3757c947..8cb5003179 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -84,7 +84,7 @@ public static boolean isOpenSSHFormatted(String privateKey) { return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); } - public FilePath getOpenSSHKeyFile(FilePath tempKeyFile) throws IOException, InterruptedException, GeneralSecurityException, SizeLimitExceededException { + public FilePath writeDecryptedOpenSSHKey(FilePath tempKeyFile) throws IOException, InterruptedException, GeneralSecurityException, SizeLimitExceededException { File tempFile = new File(tempKeyFile.toURI()); writePrivateKeyOpenSSHFormatted(tempFile); return new FilePath(tempFile); From fc81ba359fe6721592e6593000d4b8f6630f1514 Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 00:02:34 +0530 Subject: [PATCH 57/86] Using `NonNull` annotation to avoid `NullPointException` --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 6a0804f9dd..1d035e256c 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -1,6 +1,7 @@ package jenkins.plugins.git; import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.FilePath; import hudson.util.Secret; import jenkins.bouncycastle.api.PEMEncodable; @@ -13,23 +14,23 @@ public interface SSHKeyUtils { - static String getPrivateKey(SSHUserPrivateKey credentials) { + static String getSinglePrivateKey(@NonNull SSHUserPrivateKey credentials) { return credentials.getPrivateKeys().get(0); } - static String getPassphrase(SSHUserPrivateKey credentials) { + static String getPassphrase(@NonNull SSHUserPrivateKey credentials) { return Secret.toString(credentials.getPassphrase()); } - static boolean isPrivateKeyEncrypted(String passphrase) { + static boolean isPrivateKeyEncrypted(@NonNull String passphrase) { return passphrase.isEmpty() ? false : true; } - default String getSSHExePathInWin(GitClient git) throws IOException, InterruptedException { + default String getSSHExePathInWin(@NonNull GitClient git) throws IOException, InterruptedException { return ((CliGitAPIImpl) git).getSSHExecutable().getAbsolutePath(); } - default FilePath getPrivateKeyFile(SSHUserPrivateKey credentials, FilePath workspace) throws InterruptedException, IOException { + default FilePath getPrivateKeyFile(@NonNull SSHUserPrivateKey credentials, @NonNull FilePath workspace) throws InterruptedException, IOException { FilePath tempKeyFile = workspace.createTempFile("private", ".key"); final String privateKeyValue = SSHKeyUtils.getPrivateKey(credentials); final String passphraseValue = SSHKeyUtils.getPassphrase(credentials); From c6d9b3bffbed823e866402a57e6121beb573d386 Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 00:03:56 +0530 Subject: [PATCH 58/86] Renaming api's in SSHKeyUtils --- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 1d035e256c..8580c847a8 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -32,13 +32,13 @@ default String getSSHExePathInWin(@NonNull GitClient git) throws IOException, In default FilePath getPrivateKeyFile(@NonNull SSHUserPrivateKey credentials, @NonNull FilePath workspace) throws InterruptedException, IOException { FilePath tempKeyFile = workspace.createTempFile("private", ".key"); - final String privateKeyValue = SSHKeyUtils.getPrivateKey(credentials); + final String privateKeyValue = SSHKeyUtils.getSinglePrivateKey(credentials); final String passphraseValue = SSHKeyUtils.getPassphrase(credentials); try { if (isPrivateKeyEncrypted(passphraseValue)) { - if (OpenSSHKeyFormatImpl.isOpenSSHFormat(privateKeyValue)) { + if (OpenSSHKeyFormatImpl.isOpenSSHFormatted(privateKeyValue)) { OpenSSHKeyFormatImpl openSSHKeyFormat = new OpenSSHKeyFormatImpl(privateKeyValue, passphraseValue); - openSSHKeyFormat.getOpenSSHKeyFile(tempKeyFile); + openSSHKeyFormat.writeDecryptedOpenSSHKey(tempKeyFile); } else { tempKeyFile.write(PEMEncodable.decode(privateKeyValue, passphraseValue.toCharArray()).encode(), null); } From 607a5abf92f6ab46b154ba49eb230ab2a2f0a6dd Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 00:04:53 +0530 Subject: [PATCH 59/86] Renaming api's in GitSSHPrivateKeyBinding --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 5843336bcb..ec49e7bfb1 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -91,7 +91,7 @@ public Set variables(@NonNull Run run) { @Override public void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues) { SSHUserPrivateKey sshUserCredentials = (SSHUserPrivateKey) credentials; - secretValues.put(PRIVATE_KEY, SSHKeyUtils.getPrivateKey(sshUserCredentials)); + secretValues.put(PRIVATE_KEY, SSHKeyUtils.getSinglePrivateKey(sshUserCredentials)); secretValues.put(PASSPHRASE, SSHKeyUtils.getPassphrase(sshUserCredentials)); } @@ -154,7 +154,7 @@ protected final class SSHScriptFile extends AbstractOnDiskBinding Date: Thu, 12 Aug 2021 00:07:48 +0530 Subject: [PATCH 60/86] Adding test resource for ssh binding test cases The resource added is a zip file which archives the private keys in various encryption algorithms and formats both passphrase protected and not. --- .../git/GitSSHPrivateKeyBindingTest/sshKeys.zip | Bin 0 -> 4331 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/test/resources/jenkins/plugins/git/GitSSHPrivateKeyBindingTest/sshKeys.zip diff --git a/src/test/resources/jenkins/plugins/git/GitSSHPrivateKeyBindingTest/sshKeys.zip b/src/test/resources/jenkins/plugins/git/GitSSHPrivateKeyBindingTest/sshKeys.zip new file mode 100644 index 0000000000000000000000000000000000000000..cedb3d965eed341b02263a97d026c1b7f6d82703 GIT binary patch literal 4331 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 Y{|8@atKs1O{RHNps`)c{$^O3n0fosN6951J literal 0 HcmV?d00001 From c0d14aad08f511538628ae2b25d4efe9001c21f3 Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 00:09:16 +0530 Subject: [PATCH 61/86] Test cases for git ssh binding --- .../git/GitSSHPrivateKeyBindingTest.java | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java diff --git a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java new file mode 100644 index 0000000000..6f257441dd --- /dev/null +++ b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java @@ -0,0 +1,191 @@ +package jenkins.plugins.git; + +import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.domains.Domain; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Item; +import hudson.plugins.git.GitTool; +import hudson.tasks.BatchFile; +import hudson.tasks.Shell; +import jenkins.model.Jenkins; +import org.apache.commons.codec.digest.DigestUtils; +import org.jenkinsci.plugins.credentialsbinding.MultiBinding; +import org.jenkinsci.plugins.credentialsbinding.impl.SecretBuildWrapper; +import org.jenkinsci.plugins.gitclient.JGitApacheTool; +import org.jenkinsci.plugins.gitclient.JGitTool; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.JenkinsRule; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static java.util.zip.ZipFile.OPEN_READ; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; + +@RunWith(Parameterized.class) +public class GitSSHPrivateKeyBindingTest { + @Parameterized.Parameters(name = "PrivateKey-Name {0}: Passphrase {1}: GitToolInstance {2}") + public static Collection data() throws IOException { + return Arrays.asList(new Object[][]{ + //algorithm_format_enc/uenc + {privateKeyFileRead("rsa_openssh_enc"), "Dummy", new GitTool("git", "git", null)}, + {privateKeyFileRead("rsa_openssh_uenc"), "", new GitTool("Default", "git", null)}, + {privateKeyFileRead("rsa_openssh_enc"), "Dummy", new JGitTool()}, + {privateKeyFileRead("rsa_openssh_uenc"), "Dummy", new JGitApacheTool()}, + }); + } + + private final String privateKey; + private final String privatekeyPassphrase; + private final GitTool gitToolInstance; + private final String credentialID = DigestUtils.sha256Hex(("Git SSH Private Key Binding").getBytes(StandardCharsets.UTF_8)); + + private SSHUserPrivateKey credentials = null; + private BasicSSHUserPrivateKey.DirectEntryPrivateKeySource privateKeySource = null; + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Rule + public GitSampleRepoRule g = new GitSampleRepoRule(); + + public GitSSHPrivateKeyBindingTest(String privateKey, String passphrase, GitTool gitToolInstance){ + this.privateKey = privateKey; + this.privatekeyPassphrase = passphrase; + this.gitToolInstance = gitToolInstance; + } + + private static String privateKeyFileRead(String privatekeyFilename) throws IOException { + File zipFilePath = new File("src/test/resources/jenkins/plugins/git/GitSSHPrivateKeyBindingTest/sshKeys.zip"); + ZipFile zipFile = new ZipFile(zipFilePath,OPEN_READ); + ZipEntry zipEntry = zipFile.getEntry(privatekeyFilename); + InputStream stream = zipFile.getInputStream(zipEntry); + int arraySize = (int) zipEntry.getSize(); + byte[] keyBytes = new byte[arraySize]; + stream.read(keyBytes); + stream.close(); + zipFile.close(); + return new String(keyBytes, StandardCharsets.UTF_8); + } + + @Before + public void basicSetup() throws IOException { + //Private Key + privateKeySource = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(privateKey); + + //Credential init + credentials = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, credentialID, "GitSSHPrivateKey" + ,privateKeySource,privatekeyPassphrase,"Git SSH Private Key Binding"); + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), credentials); + + //Setting Git Tool + Jenkins.get().getDescriptorByType(GitTool.DescriptorImpl.class).getDefaultInstallers().clear(); + Jenkins.get().getDescriptorByType(GitTool.DescriptorImpl.class).setInstallations(gitToolInstance); + } + + private String batchCheck(boolean includeCliCheck) { + return includeCliCheck + ? "set | findstr PRIVATE_KEY > sshAuth.txt & set | findstr PASSPHRASE >> sshAuth.txt & set | findstr GCM_INTERACTIVE >> sshAuth.txt" + : "set | findstr PRIVATE_KEY > sshAuth.txt & set | findstr PASSPHRASE >> sshAuth.txt"; + } + + private String shellCheck() { + return "env | grep -zE \"PRIVATE_KEY|PASSPHRASE|GIT_TERMINAL_PROMPT\" > sshAuth.txt"; + } + + @Test + public void test_SSHBinding_FreeStyleProject() throws Exception { + FreeStyleProject prj = r.createFreeStyleProject(); + prj.getBuildWrappersList().add(new SecretBuildWrapper(Collections.> + singletonList(new GitSSHPrivateKeyBinding(gitToolInstance.getName(),credentialID)))); + prj.getBuildersList().add(isWindows() ? new BatchFile(batchCheck(isCliGitTool())) : new Shell(shellCheck())); + r.configRoundtrip((Item) prj); + + SecretBuildWrapper wrapper = prj.getBuildWrappersList().get(SecretBuildWrapper.class); + assertThat(wrapper, is(notNullValue())); + List> bindings = wrapper.getBindings(); + assertThat(bindings.size(), is(1)); + MultiBinding binding = bindings.get(0); + if(isCliGitTool()) { + assertThat(((GitSSHPrivateKeyBinding) binding).getGitToolName(), equalTo(gitToolInstance.getName())); + }else { + assertThat(((GitSSHPrivateKeyBinding) binding).getGitToolName(), equalTo("")); + } + + FreeStyleBuild b = r.buildAndAssertSuccess(prj); + + String fileContents = b.getWorkspace().child("sshAuth.txt").readToString().trim(); + //Assert Git specific env variables + if (isCliGitTool()) { + if (isWindows()) { + assertThat(fileContents, containsString("GCM_INTERACTIVE=false")); + } else if (g.gitVersionAtLeast(2, 3, 0)) { + assertThat(fileContents, containsString("GIT_TERMINAL_PROMPT=false")); + } + } + } + + @Test + public void test_SSHBinding_PipelineJob() throws Exception { + WorkflowJob project = r.createProject(WorkflowJob.class); + + // Use default tool if JGit or JGitApache + String gitToolNameArg = !isCliGitTool() ? "" : ", gitToolName: '" + gitToolInstance.getName() + "'"; + + String pipeline = "" + + "node {\n" + + " withCredentials([gitSSHPrivateKey(credentialsId: '" + credentialID + "'" + gitToolNameArg + ")]) {\n" + + " if (isUnix()) {\n" + + " sh '" + shellCheck() + "'\n" + + " } else {\n" + + " bat '" + batchCheck(isCliGitTool()) + "'\n" + + " }\n" + + " }\n" + + "}"; + project.setDefinition(new CpsFlowDefinition(pipeline, true)); + WorkflowRun b = project.scheduleBuild2(0).waitForStart(); + r.waitForCompletion(b); + r.assertBuildStatusSuccess(b); + + String fileContents = r.jenkins.getWorkspaceFor(project).child("sshAuth.txt").readToString().trim(); + // Assert Git version specific env variables + if (isCliGitTool()) { + if (isWindows()) { + assertThat(fileContents, containsString("GCM_INTERACTIVE=false")); + } else if (g.gitVersionAtLeast(2, 3, 0)) { + assertThat(fileContents, containsString("GIT_TERMINAL_PROMPT=false")); + } + } + } + + /** + * inline ${@link hudson.Functions#isWindows()} to prevent a transient + * remote classloader issue + */ + private static boolean isWindows() { + return File.pathSeparatorChar == ';'; + } + + private boolean isCliGitTool() { + return gitToolInstance.getClass().equals(GitTool.class); + } +} From 99535a89faf167a7dbb85037b0bda424d21a55c4 Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 00:10:40 +0530 Subject: [PATCH 62/86] Test cases for `SSHKeyUtils` --- .../jenkins/plugins/git/SSHKeyUtilsTest.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java diff --git a/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java b/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java new file mode 100644 index 0000000000..9a296d9e64 --- /dev/null +++ b/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java @@ -0,0 +1,108 @@ +package jenkins.plugins.git; + +import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.domains.Domain; +import hudson.plugins.git.GitTool; +import jenkins.model.Jenkins; +import org.apache.commons.codec.digest.DigestUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.JenkinsRule; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static java.util.zip.ZipFile.OPEN_READ; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; + +@RunWith(Parameterized.class) +public class SSHKeyUtilsTest { + @Parameterized.Parameters(name = "PrivateKey-Name {0}: Passphrase {1}") + public static Collection data() throws IOException { + return Arrays.asList(new Object[][]{ + //algorithm_format_enc/uenc + {privateKeyFileRead("rsa_openssh_enc"), "Dummy"}, + {privateKeyFileRead("rsa_openssh_uenc"), ""}, + }); + } + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Rule + public GitSampleRepoRule g = new GitSampleRepoRule(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final String privateKey; + private final String privatekeyPassphrase; + private final String credentialID = DigestUtils.sha256Hex(("Git SSH Private Key Binding").getBytes(StandardCharsets.UTF_8)); + + private SSHUserPrivateKey credentials = null; + private BasicSSHUserPrivateKey.DirectEntryPrivateKeySource privateKeySource = null; + private File workspace = temporaryFolder.newFolder(); + + + public SSHKeyUtilsTest(String privateKey, String passphrase) throws IOException { + this.privateKey = privateKey; + this.privatekeyPassphrase = passphrase; + } + + private static String privateKeyFileRead(String privatekeyFilename) throws IOException { + File zipFilePath = new File("src/test/resources/jenkins/plugins/git/GitSSHPrivateKeyBindingTest/sshKeys.zip"); + ZipFile zipFile = new ZipFile(zipFilePath,OPEN_READ); + ZipEntry zipEntry = zipFile.getEntry(privatekeyFilename); + InputStream stream = zipFile.getInputStream(zipEntry); + int arraySize = (int) zipEntry.getSize(); + byte[] keyBytes = new byte[arraySize]; + stream.read(keyBytes); + stream.close(); + zipFile.close(); + return new String(keyBytes, StandardCharsets.UTF_8); + } + + @Before + public void basicSetup() throws IOException { + //Private Key + privateKeySource = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(privateKey); + + //Credential init + credentials = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, credentialID, "GitSSHPrivateKey" + ,privateKeySource,privatekeyPassphrase,"Git SSH Private Key Binding"); + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), credentials); + } + + @Test + public void test_SSHKeyUtils_StaticMethods(){ + String tempKey = SSHKeyUtils.getSinglePrivateKey(credentials); + assertThat(tempKey, equalTo(this.privateKey)); + String tempPassphrase = SSHKeyUtils.getPassphrase(credentials); + assertThat(tempPassphrase, equalTo(this.privatekeyPassphrase)); + boolean flag = SSHKeyUtils.isPrivateKeyEncrypted(this.privatekeyPassphrase); + assertThat(flag, not(this.privatekeyPassphrase.isEmpty())); + } + + private GitSSHPrivateKeyBinding getGitSSHBindingInstance() { + //Setting Git Tool + Jenkins.get().getDescriptorByType(GitTool.DescriptorImpl.class).getDefaultInstallers().clear(); + Jenkins.get().getDescriptorByType(GitTool.DescriptorImpl.class).setInstallations(new GitTool("Default", "git", null)); + return new GitSSHPrivateKeyBinding("Default",credentialID); + } +} From 85dfeedc548698d84be35611e4d86f1664ca5fc6 Mon Sep 17 00:00:00 2001 From: arpoch Date: Thu, 12 Aug 2021 23:07:04 +0530 Subject: [PATCH 63/86] Create new folder for each test case --- src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java b/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java index 9a296d9e64..d9176ab33a 100644 --- a/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java +++ b/src/test/java/jenkins/plugins/git/SSHKeyUtilsTest.java @@ -57,7 +57,7 @@ public static Collection data() throws IOException { private SSHUserPrivateKey credentials = null; private BasicSSHUserPrivateKey.DirectEntryPrivateKeySource privateKeySource = null; - private File workspace = temporaryFolder.newFolder(); + private File workspace = null; public SSHKeyUtilsTest(String privateKey, String passphrase) throws IOException { @@ -80,6 +80,9 @@ private static String privateKeyFileRead(String privatekeyFilename) throws IOExc @Before public void basicSetup() throws IOException { + //Create Folder + workspace = temporaryFolder.newFolder(); + //Private Key privateKeySource = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(privateKey); From d7f3b559425f169ab716c37894e9444e26c98dfd Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Fri, 13 Aug 2021 13:25:19 +0530 Subject: [PATCH 64/86] Using FilePath to provide remoting support Unlike File, which always implies a file path on the current computer, FilePath represents a file path on a specific agent or the controller. --- .../jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 8cb5003179..5cb8fae2bf 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -60,7 +60,7 @@ private KeyPair getOpenSSHKeyPair(SessionContext session, NamedResource resource } } - private File writePrivateKeyOpenSSHFormatted(File tempFile) { + private FilePath writePrivateKeyOpenSSHFormatted(FilePath tempFile) { OpenSSHKeyPairResourceWriter privateKeyWriter = new OpenSSHKeyPairResourceWriter(); SecureByteArrayOutputStream privateKeyBuffer = new SecureByteArrayOutputStream(); ByteArrayInputStream stream = new ByteArrayInputStream(getEncData(this.privateKey)); @@ -70,10 +70,8 @@ private File writePrivateKeyOpenSSHFormatted(File tempFile) { new AcquirePassphrase(this.passphrase), stream,null); privateKeyWriter.writePrivateKey(sshKeyPair, "", null, privateKeyBuffer); - FileOutputStream privateKeyFileStream = new FileOutputStream(tempFile); - privateKeyBuffer.writeTo(privateKeyFileStream); - privateKeyFileStream.close(); - } catch (IOException | SizeLimitExceededException | GeneralSecurityException e) { + tempFile.write(privateKeyBuffer.toString(),null); + } catch (IOException | SizeLimitExceededException | GeneralSecurityException | InterruptedException e) { e.printStackTrace(); } return tempFile; @@ -85,9 +83,7 @@ public static boolean isOpenSSHFormatted(String privateKey) { } public FilePath writeDecryptedOpenSSHKey(FilePath tempKeyFile) throws IOException, InterruptedException, GeneralSecurityException, SizeLimitExceededException { - File tempFile = new File(tempKeyFile.toURI()); - writePrivateKeyOpenSSHFormatted(tempFile); - return new FilePath(tempFile); + return writePrivateKeyOpenSSHFormatted(tempKeyFile); } private final static class AcquirePassphrase implements FilePasswordProvider { From 2efc0150f74d88380b3f11957b878e985756c5b5 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Fri, 13 Aug 2021 17:50:44 +0530 Subject: [PATCH 65/86] Adhering to lowercase starting letter de facto --- src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index ec49e7bfb1..75977df856 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -190,7 +190,7 @@ protected Class type() { } } - @Symbol("gitSSHPrivateKey") + @Symbol("gitSshPrivateKey") @Extension public static final class DescriptorImpl extends BindingDescriptor { From e9d572d18b3197dab5d28ec6f1e9d39bb85d9341 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Fri, 13 Aug 2021 23:38:29 +0530 Subject: [PATCH 66/86] Changing git ssh binding symbol name in GitSSHPrivateKeyBindingTest class --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java index 6f257441dd..8b3622d1e1 100644 --- a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java +++ b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java @@ -153,7 +153,7 @@ public void test_SSHBinding_PipelineJob() throws Exception { String pipeline = "" + "node {\n" - + " withCredentials([gitSSHPrivateKey(credentialsId: '" + credentialID + "'" + gitToolNameArg + ")]) {\n" + + " withCredentials([gitSshPrivateKey(credentialsId: '" + credentialID + "'" + gitToolNameArg + ")]) {\n" + " if (isUnix()) {\n" + " sh '" + shellCheck() + "'\n" + " } else {\n" From be8b2e915ae3dd4bc9bdeef79ba43f268c71efa5 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Fri, 13 Aug 2021 23:41:48 +0530 Subject: [PATCH 67/86] Changing git ssh binding symbol name in help-credentialsId --- .../git/GitSSHPrivateKeyBinding/help-credentialsId.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html index bd2c552e67..9051124164 100644 --- a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-credentialsId.html @@ -4,7 +4,7 @@

Shell example

-withCredentials([gitSSHPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitSshPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   sh 'git fetch --all'
 }
 
@@ -13,7 +13,7 @@

Batch example

-withCredentials([gitSSHPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitSshPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   bat 'git submodule update --init --recursive'
 }
 
@@ -22,7 +22,7 @@

Powershell example

-withCredentials([gitSSHPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
+withCredentials([gitSshPrivateKey(credentialsId: 'my-credentials-id', gitToolName: 'git-tool')]) {
   powershell 'git push'
 }
 
From a98514f813d2adf10802cf8c286dc9e6e8441827 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Fri, 13 Aug 2021 23:42:13 +0530 Subject: [PATCH 68/86] Adding new line --- .../jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly | 2 +- .../plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly index 2a018cfe86..60358eb3fc 100644 --- a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/config.jelly @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html index 0d36f771e9..c216d2c77c 100644 --- a/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html +++ b/src/main/resources/jenkins/plugins/git/GitSSHPrivateKeyBinding/help-gitToolName.html @@ -2,4 +2,4 @@

Specify the Git tool installation name

- \ No newline at end of file + From 649cd85cb1474c1ab6f4ee6e6b083290deb8f066 Mon Sep 17 00:00:00 2001 From: arpoch Date: Sat, 14 Aug 2021 11:44:53 +0530 Subject: [PATCH 69/86] Handling exceptions for operations on a private key using try/catch --- .../jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 11 +++++------ .../jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 12 ++++-------- src/main/java/jenkins/plugins/git/SSHKeyUtils.java | 4 ++-- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 75977df856..de326933be 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -131,13 +131,12 @@ private String getSSHPath(GitClient git) throws IOException, InterruptedExceptio } } - private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir, String sshExePath) throws IOException, InterruptedException { + private String getSSHCmd(SSHUserPrivateKey credentials, FilePath tempDir, String sshExePath) { if (unixNodeType) { - return - sshExePath - + " -i " - + getPrivateKeyFile(credentials, tempDir).getRemote() - + " -o StrictHostKeyChecking=no"; + return sshExePath + + " -i " + + getPrivateKeyFile(credentials, tempDir).getRemote() + + " -o StrictHostKeyChecking=no"; } else { return "\"" + sshExePath + "\"" + " -i " diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 5cb8fae2bf..e4aa53e462 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -60,20 +60,16 @@ private KeyPair getOpenSSHKeyPair(SessionContext session, NamedResource resource } } - private FilePath writePrivateKeyOpenSSHFormatted(FilePath tempFile) { + private FilePath writePrivateKeyOpenSSHFormatted(FilePath tempFile) throws SizeLimitExceededException, GeneralSecurityException, IOException, InterruptedException { OpenSSHKeyPairResourceWriter privateKeyWriter = new OpenSSHKeyPairResourceWriter(); SecureByteArrayOutputStream privateKeyBuffer = new SecureByteArrayOutputStream(); ByteArrayInputStream stream = new ByteArrayInputStream(getEncData(this.privateKey)); KeyPair sshKeyPair = null; - try { - sshKeyPair = getOpenSSHKeyPair(null,null,"","", + sshKeyPair = getOpenSSHKeyPair(null,null,"","", new AcquirePassphrase(this.passphrase), stream,null); - privateKeyWriter.writePrivateKey(sshKeyPair, "", null, privateKeyBuffer); - tempFile.write(privateKeyBuffer.toString(),null); - } catch (IOException | SizeLimitExceededException | GeneralSecurityException | InterruptedException e) { - e.printStackTrace(); - } + privateKeyWriter.writePrivateKey(sshKeyPair, "", null, privateKeyBuffer); + tempFile.write(privateKeyBuffer.toString(),null); return tempFile; } diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 8580c847a8..eec9979c12 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -30,11 +30,11 @@ default String getSSHExePathInWin(@NonNull GitClient git) throws IOException, In return ((CliGitAPIImpl) git).getSSHExecutable().getAbsolutePath(); } - default FilePath getPrivateKeyFile(@NonNull SSHUserPrivateKey credentials, @NonNull FilePath workspace) throws InterruptedException, IOException { - FilePath tempKeyFile = workspace.createTempFile("private", ".key"); + default FilePath getPrivateKeyFile(@NonNull SSHUserPrivateKey credentials, @NonNull FilePath workspace) { final String privateKeyValue = SSHKeyUtils.getSinglePrivateKey(credentials); final String passphraseValue = SSHKeyUtils.getPassphrase(credentials); try { + FilePath tempKeyFile = workspace.createTempFile("private", ".key"); if (isPrivateKeyEncrypted(passphraseValue)) { if (OpenSSHKeyFormatImpl.isOpenSSHFormatted(privateKeyValue)) { OpenSSHKeyFormatImpl openSSHKeyFormat = new OpenSSHKeyFormatImpl(privateKeyValue, passphraseValue); From 1766ec2d44e632326c0a05569308d7420d45e4ec Mon Sep 17 00:00:00 2001 From: arpoch Date: Sat, 14 Aug 2021 11:45:28 +0530 Subject: [PATCH 70/86] Adding RequirePOST annotation --- src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index de326933be..6662bf7057 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -22,6 +22,7 @@ import org.jenkinsci.plugins.gitclient.Git; import org.jenkinsci.plugins.gitclient.GitClient; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.interceptor.RequirePOST; import javax.annotation.Nullable; import java.io.IOException; @@ -199,6 +200,7 @@ public String getDisplayName() { return Messages.GitSSHPrivateKeyBinding_DisplayName(); } + @RequirePOST public ListBoxModel doFillGitToolNameItems() { ListBoxModel items = new ListBoxModel(); List toolList = Jenkins.get().getDescriptorByType(GitSCM.DescriptorImpl.class).getGitTools(); From 74b4f164c42836cfd2c35999cd31df97cfdd3925 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sun, 15 Aug 2021 14:16:18 +0530 Subject: [PATCH 71/86] Removing default variable bindings for git ssh private key binding The PRIVATE_KEY and PASSPHRASE variable bindings are removed in this commit since this sensitive information shouldn't be binded to environment variables. Workarounds with these env bindings will potentially be unsafe. --- .../plugins/git/GitSSHPrivateKeyBinding.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 75977df856..010c4ed8ee 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -26,15 +26,13 @@ import javax.annotation.Nullable; import java.io.IOException; import java.util.LinkedHashMap; +import java.util.Collections; import java.util.Map; -import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class GitSSHPrivateKeyBinding extends MultiBinding implements GitCredentialBindings, SSHKeyUtils { - final static private String PRIVATE_KEY = "PRIVATE_KEY"; - final static private String PASSPHRASE = "PASSPHRASE"; final private String gitToolName; private transient boolean unixNodeType; @@ -59,7 +57,6 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath final Map secretValues = new LinkedHashMap<>(); final Map publicValues = new LinkedHashMap<>(); SSHUserPrivateKey credentials = getCredentials(run); - setCredentialPairBindings(credentials, secretValues, publicValues); GitTool cliGitTool = getCliGitTool(run, this.gitToolName, taskListener); if (cliGitTool != null && filePath != null && launcher != null) { setUnixNodeType(isCurrentNodeOSUnix(launcher)); @@ -82,23 +79,18 @@ public MultiEnvironment bind(@NonNull Run run, @Nullable FilePath filePath @Override public Set variables(@NonNull Run run) { - Set keys = new LinkedHashSet<>(); - keys.add(PRIVATE_KEY); - keys.add(PASSPHRASE); - return keys; - } - - @Override - public void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues) { - SSHUserPrivateKey sshUserCredentials = (SSHUserPrivateKey) credentials; - secretValues.put(PRIVATE_KEY, SSHKeyUtils.getSinglePrivateKey(sshUserCredentials)); - secretValues.put(PASSPHRASE, SSHKeyUtils.getPassphrase(sshUserCredentials)); + return Collections.emptySet(); } /*package*/void setGitEnvironmentVariables(@NonNull GitClient git, Map publicValues) throws IOException, InterruptedException { setGitEnvironmentVariables(git, null, publicValues); } + @Override + public void setCredentialPairBindings(@NonNull StandardCredentials credentials, Map secretValues, Map publicValues) { + //Private Key credentials not required to bind with environment variables + } + @Override public void setGitEnvironmentVariables(@NonNull GitClient git, Map secretValues, Map publicValues) throws IOException, InterruptedException { if (unixNodeType && isGitVersionAtLeast(git, 2, 3, 0, 0)) { From b6eebfd1c0efef11a859315205d7066d20eb45bb Mon Sep 17 00:00:00 2001 From: arpoch Date: Sun, 15 Aug 2021 19:35:21 +0530 Subject: [PATCH 72/86] Documentation for git ssh private key binding in a pipeline job --- README.adoc | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index c9e2211cbf..85b8d548c1 100644 --- a/README.adoc +++ b/README.adoc @@ -54,7 +54,10 @@ image:images/multibranch-pipeline.png[link=https://youtu.be/B_2FXWI6CWg] [#credential-binding] === Git Credentials Binding -The git plugin provides `Git Username and Password` binding that allows authenticated git operations over *HTTP* and *HTTPS* protocols using command line git in a Pipeline job. +The git plugin provides two credential bindings + +* `Git Username and Password` binding that allows authenticated git operations over *HTTP* and *HTTPS* protocols using command line git in a Pipeline job. +* `Git SSH Private Key` binding that allows authenticated git operations over *SSH* protocol using command line git for sh, bat, powershell in a Pipeline job. The git credential bindings are accessible through the link:https://www.jenkins.io/doc/pipeline/steps/credentials-binding/#withcredentials-bind-credentials-to-variables[`withCredentials`] step of the link:https://plugins.jenkins.io/credentials-binding/[Credentials Binding] plugin. The binding retrieves credentials from the link:https://plugins.jenkins.io/credentials/[Credentials] plugin. @@ -96,6 +99,45 @@ withCredentials([gitUsernamePassword(credentialsId: 'my-credentials-id', powershell 'git push' } ``` + +==== Git SSH Private Key + +The binding provides *SSH* protocol support for git operation which requires authentication. + +Procedure:: + +. Click the Pipeline Syntax _Snippet Generator_ and choose the `withCredentials` step, add Git SSH Private Key binding. +. Choose the required credentials and Git tool name, specific to the generated Pipeline snippet. + +image:TODO + +Unlike `Git Username and Password` binding, variable bindings are not provided in `Git SSH Private Key` binding. + + +.Shell example +```groovy +withCredentials([gitSshPrivateKey(credentialsId: 'my-credentials-id', + gitToolName: 'git-tool')]) { + sh 'git fetch --all' +} +``` + +.Batch example +```groovy +withCredentials([gitSshPrivateKey(credentialsId: 'my-credentials-id', + gitToolName: 'git-tool')]) { + bat 'git submodule update --init --recursive' +} +``` + +.Powershell example +```groovy +withCredentials([gitSshPrivateKey(credentialsId: 'my-credentials-id', + gitToolName: 'git-tool')]) { + powershell 'git push' +} +``` + [#configuration] == [[GitPlugin-ProjectConfiguration]]Configuration From b6eeac26a203bdadf80d9a59b37828bb6a72b77b Mon Sep 17 00:00:00 2001 From: arpoch Date: Sun, 15 Aug 2021 19:35:47 +0530 Subject: [PATCH 73/86] Documentation for git ssh private key binding in a freestyle project --- README.adoc | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 85b8d548c1..b6a6c10b24 100644 --- a/README.adoc +++ b/README.adoc @@ -634,13 +634,18 @@ Project Name in ViewGit:: [#git-bindings] == Git Credential Binding -The git plugin provides one binding to support authenticated git operations over *HTTP* or *HTTPS* protocol, namely `Git Username and Password`. -The git plugin depends on the Credential Binding Plugin to support these bindings. +The git plugin provides two binding to support authenticated git operations- -To access the `Git Username and Password` binding in a Pipeline job, visit <> +* `Git Username and Password` supports git authentication over *HTTP* or *HTTPS* protocol. +* `Git SSH Private Key` supports git authentication over *SSH* protocol. -Freestyle projects can use git credential binding with the following steps: +The git plugin depends on the https://plugins.jenkins.io/credentials-binding/[Credential Binding Plugin] to support these bindings. +To access the `Git Username and Password` and `Git SSH Private Key` binding in a Pipeline job, visit <> + +Freestyle projects can use git credentials binding + +Git Username and Password:: . Check the box _Use secret text(s) or file(s)_, add Git Username and Password binding. . Choose the required credentials and Git tool name. @@ -650,6 +655,14 @@ image:images/git-credentials-usernamepassword-binding-freestyle-project.png[Git- Two variable bindings are used, `GIT_USERNAME` and `GIT_PASSWORD`, to pass the username and password to shell, batch, and powershell steps in a Freestyle job. The variable bindings are available even if the `JGit` or `JGit with Apache HTTP Client` git implementation is being used. +Git SSH Private Key:: +. Check the box _Use secret text(s) or file(s)_, add Git SSH Private Key binding. + +. Choose the required credentials and Git tool name. + +image:TODO + +Variable bindings are not provided in this binding. [#extensions] == Extensions From f168ecb6abc0c81aed24a276c402f209da3414ea Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Sun, 15 Aug 2021 15:18:08 +0530 Subject: [PATCH 74/86] Adding git ssh binding images --- README.adoc | 4 ++-- ...inding-git-ssh-private-key-freestyle-job.png | Bin 0 -> 35167 bytes ...binding-git-ssh-private-key-pipeline-job.png | Bin 0 -> 17139 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 images/git-credentials-binding-git-ssh-private-key-freestyle-job.png create mode 100644 images/git-credentials-binding-git-ssh-private-key-pipeline-job.png diff --git a/README.adoc b/README.adoc index b6a6c10b24..1a614d83ab 100644 --- a/README.adoc +++ b/README.adoc @@ -109,7 +109,7 @@ Procedure:: . Click the Pipeline Syntax _Snippet Generator_ and choose the `withCredentials` step, add Git SSH Private Key binding. . Choose the required credentials and Git tool name, specific to the generated Pipeline snippet. -image:TODO +image:images/git-credentials-binding-git-ssh-private-key-pipeline-job.png[Git-SSH-Private-Key-Binding] Unlike `Git Username and Password` binding, variable bindings are not provided in `Git SSH Private Key` binding. @@ -660,7 +660,7 @@ Git SSH Private Key:: . Choose the required credentials and Git tool name. -image:TODO +image:images/git-credentials-binding-git-ssh-private-key-freestyle-job.png[Git-SSH-Private-Key-Freestyle-Job] Variable bindings are not provided in this binding. [#extensions] diff --git a/images/git-credentials-binding-git-ssh-private-key-freestyle-job.png b/images/git-credentials-binding-git-ssh-private-key-freestyle-job.png new file mode 100644 index 0000000000000000000000000000000000000000..d8bf24a4b2051446c5043f1a7e2a3c25133df219 GIT binary patch literal 35167 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 literal 0 HcmV?d00001 From 06c80738b1e01626318508bbddceba46b4fcd40b Mon Sep 17 00:00:00 2001 From: arpoch Date: Tue, 17 Aug 2021 16:58:24 +0530 Subject: [PATCH 75/86] Adhering to code style guideline --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java index 8b3622d1e1..3225de16f3 100644 --- a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java +++ b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java @@ -27,7 +27,9 @@ import org.junit.runners.Parameterized; import org.jvnet.hudson.test.JenkinsRule; -import java.io.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; From 187b399662adab20781fa880f92585b35f37df4a Mon Sep 17 00:00:00 2001 From: arpoch Date: Tue, 17 Aug 2021 17:01:57 +0530 Subject: [PATCH 76/86] Removing redundant environment variables from batch script --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java index 3225de16f3..2a282dbc73 100644 --- a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java +++ b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java @@ -106,8 +106,8 @@ public void basicSetup() throws IOException { private String batchCheck(boolean includeCliCheck) { return includeCliCheck - ? "set | findstr PRIVATE_KEY > sshAuth.txt & set | findstr PASSPHRASE >> sshAuth.txt & set | findstr GCM_INTERACTIVE >> sshAuth.txt" - : "set | findstr PRIVATE_KEY > sshAuth.txt & set | findstr PASSPHRASE >> sshAuth.txt"; + ? "set | findstr GCM_INTERACTIVE >> sshAuth.txt" + : "set > sshAuth.txt"; } private String shellCheck() { From e12419b54e99809947e0a8f274e096f6f1ca253d Mon Sep 17 00:00:00 2001 From: arpoch Date: Tue, 17 Aug 2021 17:02:47 +0530 Subject: [PATCH 77/86] Removing redundant environment variables from shell script --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java index 2a282dbc73..ea0e53d19d 100644 --- a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java +++ b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java @@ -111,7 +111,7 @@ private String batchCheck(boolean includeCliCheck) { } private String shellCheck() { - return "env | grep -zE \"PRIVATE_KEY|PASSPHRASE|GIT_TERMINAL_PROMPT\" > sshAuth.txt"; + return "env | grep -zE \"GIT_TERMINAL_PROMPT\" > sshAuth.txt"; } @Test From 828cc946442a0e6ab6944d2e2315c8c9d0a7b9d0 Mon Sep 17 00:00:00 2001 From: arpoch Date: Tue, 17 Aug 2021 17:12:50 +0530 Subject: [PATCH 78/86] Adding |(or) Prevents failure in case the git is not CLI-git --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java index ea0e53d19d..d8aaa32a28 100644 --- a/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java +++ b/src/test/java/jenkins/plugins/git/GitSSHPrivateKeyBindingTest.java @@ -111,7 +111,7 @@ private String batchCheck(boolean includeCliCheck) { } private String shellCheck() { - return "env | grep -zE \"GIT_TERMINAL_PROMPT\" > sshAuth.txt"; + return "env | grep -zE \"GIT_TERMINAL_PROMPT|\" > sshAuth.txt"; } @Test From dd86551cda93447090584407304f83ca3030f154 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Thu, 19 Aug 2021 19:11:57 +0530 Subject: [PATCH 79/86] Using `ssh` as ssh path in windows environment The getSSHExePathInWin(GitClient git) always returns ssh path of controller environment even if running build on an agent. --- .../java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index f5b7112aa9..4204ad3f91 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -120,7 +120,9 @@ private String getSSHPath(GitClient git) throws IOException, InterruptedExceptio if (unixNodeType) { return "ssh"; } else { - return getSSHExePathInWin(git); + //Use getSSHExePathInWin(GitClient git), when support for finding ssh executable is provided for agents. + //ssh, will work only if OpenSSH is installed on the system being used to execute the build + return "ssh"; } } From 1ce0079d9344506f014671d8796ac9351b0f03f1 Mon Sep 17 00:00:00 2001 From: arpoch Date: Fri, 20 Aug 2021 23:05:33 +0530 Subject: [PATCH 80/86] Storing passphrase as a secret The passphrase value of a private key is stored as secret rather than in plaintext. --- .../plugins/git/GitSSHPrivateKeyBinding.java | 2 +- .../plugins/git/OpenSSHKeyFormatImpl.java | 13 ++++++------ .../java/jenkins/plugins/git/SSHKeyUtils.java | 20 ++++++++++++++++--- .../jenkins/plugins/git/SSHKeyUtilsTest.java | 7 +++++-- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 4204ad3f91..9c4fd1e501 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -148,7 +148,7 @@ protected final class SSHScriptFile extends AbstractOnDiskBinding Date: Fri, 20 Aug 2021 23:07:44 +0530 Subject: [PATCH 81/86] Adding javadocs for API's in SSHKeyUtils --- .../java/jenkins/plugins/git/SSHKeyUtils.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java index 380ec1c7f6..8248513b21 100644 --- a/src/main/java/jenkins/plugins/git/SSHKeyUtils.java +++ b/src/main/java/jenkins/plugins/git/SSHKeyUtils.java @@ -14,6 +14,12 @@ public interface SSHKeyUtils { + /** + * Get a single private key + * @param credentials Credentials{@link com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey}. Can't be null + * @return Private key at index 0 + * @exception IndexOutOfBoundsException thrown when no private key if found/available + **/ static String getSinglePrivateKey(@NonNull SSHUserPrivateKey credentials) { return credentials.getPrivateKeys().get(0); } @@ -36,14 +42,32 @@ static String getPassphraseAsString(@NonNull SSHUserPrivateKey credentials) { return Secret.toString(credentials.getPassphrase()); } + /** + * Check if private key is encrypted by using the passphrase + * @param passphrase Passphrase{@link java.lang.String}. Can't be null + * @return True if passphrase is not an empty string value + **/ static boolean isPrivateKeyEncrypted(@NonNull String passphrase) { return passphrase.isEmpty() ? false : true; } + /** + * Get SSH executable absolute path{@link java.lang.String} on a Windows system + * @param git Git Client{@link org.jenkinsci.plugins.gitclient.GitClient}. Can't be null + * @return SSH executable absolute path{@link java.lang.String} + * @exception InterruptedException If no ssh executable path is found + **/ default String getSSHExePathInWin(@NonNull GitClient git) throws IOException, InterruptedException { return ((CliGitAPIImpl) git).getSSHExecutable().getAbsolutePath(); } + /** + * Get a file{@link hudson.FilePath} stored on the file-system used during the build, with private key written in it. + * The private key if encrypted will be decrypted first and then written in the file{@link hudson.FilePath}. + * @param credentials Credentials{@link com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey}. Can't be null + * @param workspace Work directory{@link hudson.FilePath} for storing private key file + * @return Private Key file + **/ default FilePath getPrivateKeyFile(@NonNull SSHUserPrivateKey credentials, @NonNull FilePath workspace) { final String privateKeyValue = SSHKeyUtils.getSinglePrivateKey(credentials); final String passphraseValue = SSHKeyUtils.getPassphraseAsString(credentials); From 1354b1f3d18ee8d0995342025d15a6e944e44e5b Mon Sep 17 00:00:00 2001 From: arpoch Date: Fri, 20 Aug 2021 23:08:32 +0530 Subject: [PATCH 82/86] Adding javadocs for public API's in OpenSSHKeyFormatImpl --- .../java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java index 54f92c250e..f5abda52f8 100644 --- a/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java +++ b/src/main/java/jenkins/plugins/git/OpenSSHKeyFormatImpl.java @@ -72,11 +72,21 @@ private FilePath writePrivateKeyOpenSSHFormatted(FilePath tempFile) throws SizeL return tempFile; } + /** + * Check the format of private key using HEADERS + * @param privateKey The private key{@link java.lang.String} + * @return true is the privater key is in OpenSSH format + **/ public static boolean isOpenSSHFormatted(String privateKey) { final String HEADER = DASH_MARKER+BEGIN_MARKER+DASH_MARKER; return privateKey.regionMatches(false, 0, HEADER, 0, HEADER.length()); } + /** + * Decrypts the passphrase protected OpenSSH formatted private key + * @param tempKeyFile Decrypted private key file{@link hudson.FilePath} on agents/controller file-system + * @return Decrypted private key file{@link hudson.FilePath} OpenSSH Formatted + **/ public FilePath writeDecryptedOpenSSHKey(FilePath tempKeyFile) throws IOException, InterruptedException, GeneralSecurityException, SizeLimitExceededException { return writePrivateKeyOpenSSHFormatted(tempKeyFile); } From eefb41d4bce43238aa5d268b73c395f469bd43af Mon Sep 17 00:00:00 2001 From: arpoch Date: Fri, 20 Aug 2021 23:09:08 +0530 Subject: [PATCH 83/86] Removing redundant throw statements --- src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java index 9c4fd1e501..3c2c41098d 100644 --- a/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java +++ b/src/main/java/jenkins/plugins/git/GitSSHPrivateKeyBinding.java @@ -116,7 +116,7 @@ private boolean isGitVersionAtLeast(GitClient git, int major, int minor, int rev return ((CliGitAPIImpl) git).isCliGitVerAtLeast(major, minor, rev, bugfix); } - private String getSSHPath(GitClient git) throws IOException, InterruptedException { + private String getSSHPath(GitClient git) { if (unixNodeType) { return "ssh"; } else { From 3a238c0d133ac76ee73b307a906921ea3af8d915 Mon Sep 17 00:00:00 2001 From: Mark Waite Date: Sat, 21 Aug 2021 15:19:30 -0600 Subject: [PATCH 84/86] Use latest 2.289.x bom --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6289494ea0..8e3856ff4c 100644 --- a/pom.xml +++ b/pom.xml @@ -279,7 +279,7 @@ io.jenkins.tools.bom bom-2.289.x - 923.v08bdc07cd40f + 924.vda78166e6655 import pom From b097fb50420ae529849b0b2180f5ec374a4e3394 Mon Sep 17 00:00:00 2001 From: Mark Waite Date: Sun, 26 Sep 2021 10:00:53 -0600 Subject: [PATCH 85/86] Fix build break from merge accident --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index bb522f0c04..e68d10b7f1 100644 --- a/pom.xml +++ b/pom.xml @@ -167,6 +167,17 @@ org.jenkins-ci.plugins credentials-binding
+ + + org.jenkins-ci.plugins + bouncycastle-api + + + + org.jenkins-ci.modules + sshd + 3.1.0 + org.jenkins-ci.plugins parameterized-trigger From 78651aeed8d3e8839a33fee9fcdca7de733a3b82 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Mon, 25 Oct 2021 23:54:46 +0530 Subject: [PATCH 86/86] Resolving merge conflicts --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ba8558c203..040f09ada4 100644 --- a/pom.xml +++ b/pom.xml @@ -277,8 +277,8 @@ io.jenkins.tools.bom - bom-2.263.x - 950.v396cb834de1e + bom-2.289.x + 987.v4ade2e49fe70 import pom