Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ keys/shared/git-readonly-key # Shared read-only access key
- For GitHub/GitLab, ensure the public key is added to your account
- Try with `Strict Host Key Checking = no` for initial testing

**Problem: "You're using an RSA key with SHA-1, which is no longer allowed" (GitHub)**
- This plugin supports modern SSH algorithms including RSA with SHA-2 (`rsa-sha2-256`, `rsa-sha2-512`)
- Your existing RSA keys will work - the plugin automatically uses SHA-2 signatures with Apache MINA SSHD
- Alternatively, generate a more modern key type: `ssh-keygen -t ed25519 -C "rundeck@example.com"`
- Supported key types: RSA (with SHA-2), Ed25519, ECDSA

**Problem: Key Storage path not found**
- Key Storage paths should start with `keys/` (e.g., `keys/git/password`)
- Use the Key Storage browser in the UI to select the correct path
Expand Down
4 changes: 1 addition & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,11 @@ dependencies {

pluginLibs(libs.jgit) {
exclude module: 'slf4j-api'
exclude module: 'jsch'
exclude module: 'commons-logging'
}

pluginLibs(libs.jgitSsh) {
pluginLibs(libs.jgitSshApache) {
exclude module: 'slf4j-api'
exclude group: 'org.bouncycastle'
}

testImplementation libs.bundles.testLibs
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ junit = "4.13.2"
rundeckCore = "5.16.0-20251006"
slf4j = "1.7.36"
jgit = "6.6.1.202309021850-r"
jgitSsh = "6.6.1.202309021850-r"
jgitSshApache = "6.6.1.202309021850-r"
spock = "2.0-groovy-3.0"
cglib = "3.3.0"
objenesis = "1.4"
Expand All @@ -23,7 +23,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
rundeckCore = { group = "org.rundeck", name = "rundeck-core", version.ref = "rundeckCore" }
slf4jApi = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
jgit = { group = "org.eclipse.jgit", name = "org.eclipse.jgit", version.ref = "jgit" }
jgitSsh = { group = "org.eclipse.jgit", name = "org.eclipse.jgit.ssh.jsch", version.ref = "jgitSsh" }
jgitSshApache = { group = "org.eclipse.jgit", name = "org.eclipse.jgit.ssh.apache", version.ref = "jgitSshApache" }
spockCore = { group = "org.spockframework", name = "spock-core", version.ref = "spock" }
cglibNodep = { group = "cglib", name = "cglib-nodep", version.ref = "cglib" }
objenesis = { group = "org.objenesis", name = "objenesis", version.ref = "objenesis" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package com.rundeck.plugin.util

import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException
import com.jcraft.jsch.Session
import org.eclipse.jgit.api.TransportConfigCallback
import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory
import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.SshTransport
import org.eclipse.jgit.transport.Transport
import org.eclipse.jgit.util.FS
import org.eclipse.jgit.transport.sshd.ServerKeyDatabase
import org.eclipse.jgit.transport.sshd.SshdSessionFactory

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.PosixFilePermissions
import java.security.PublicKey

/**
* Created by luistoledo on 12/20/17.
* SSH session factory using Apache MINA SSHD instead of JSch.
* Provides support for modern SSH algorithms including RSA with SHA-2 signatures.
*/
class PluginSshSessionFactory extends JschConfigSessionFactory implements TransportConfigCallback {
class PluginSshSessionFactory implements TransportConfigCallback {
private byte[] privateKey
Map<String, String> sshConfig

Expand All @@ -22,41 +25,62 @@ class PluginSshSessionFactory extends JschConfigSessionFactory implements Trans
}

@Override
protected void configure(final OpenSshConfig.Host hc, final Session session) {
if (sshConfig) {
sshConfig.each { k, v ->
session.setConfig(k, v)
}
void configure(final Transport transport) {
if (transport in SshTransport) {
SshTransport sshTransport = (SshTransport) transport
sshTransport.setSshSessionFactory(buildSessionFactory())
}
}

@Override
protected JSch createDefaultJSch(final FS fs) throws JSchException {
JSch jsch = super.createDefaultJSch(fs)
jsch.removeAllIdentity()
jsch.addIdentity("private", privateKey, null, null)
//todo: explicitly set known host keys?
return jsch
private SshdSessionFactory buildSessionFactory() {
return new CustomSshdSessionFactory(privateKey, sshConfig)
}

@Override
protected Session createSession(
final OpenSshConfig.Host hc,
final String user,
final String host,
final int port,
final FS fs
) throws JSchException
{
return super.createSession(hc, user, host, port, fs)
}
private static class CustomSshdSessionFactory extends SshdSessionFactory {
private final byte[] privateKey
private final Map<String, String> sshConfig
private Path cachedKeyFile

@Override
void configure(final Transport transport) {
if (transport instanceof SshTransport) {
SshTransport sshTransport = (SshTransport) transport
sshTransport.setSshSessionFactory(this)
CustomSshdSessionFactory(byte[] privateKey, Map<String, String> sshConfig) {
super(null, null)
this.privateKey = privateKey
this.sshConfig = sshConfig
}

@Override
protected List<Path> getDefaultIdentities(File sshDir) {
if (privateKey) {
if (cachedKeyFile == null || !Files.exists(cachedKeyFile)) {
cachedKeyFile = Files.createTempFile("rundeck-git-key-", ".pem")
try {
Files.setPosixFilePermissions(cachedKeyFile, PosixFilePermissions.fromString("rw-------"))
} catch (UnsupportedOperationException ignored) {
// Non-POSIX filesystem (e.g. Windows)
}
Files.write(cachedKeyFile, privateKey)
cachedKeyFile.toFile().deleteOnExit()
}
return [cachedKeyFile]
}
return super.getDefaultIdentities(sshDir)
}

@Override
protected ServerKeyDatabase getServerKeyDatabase(File homeDir, File sshDir) {
if (sshConfig?.get('StrictHostKeyChecking') == 'no') {
return new ServerKeyDatabase() {
@Override
List<PublicKey> lookup(String connectAddress, InetSocketAddress remoteAddress, ServerKeyDatabase.Configuration config) {
return Collections.emptyList()
}

@Override
boolean accept(String connectAddress, InetSocketAddress remoteAddress, PublicKey serverKey, ServerKeyDatabase.Configuration config, CredentialsProvider provider) {
return true
}
}
}
return super.getServerKeyDatabase(homeDir, sshDir)
}
}
}

Loading
Loading