Skip to content

Commit

Permalink
feat(ssh): support OpenSSH config authentication
Browse files Browse the repository at this point in the history
Fixes #1649
  • Loading branch information
loicmathieu committed Sep 24, 2024
1 parent 9eb44ee commit 05284d5
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 11 deletions.
36 changes: 31 additions & 5 deletions src/main/java/io/kestra/plugin/fs/ssh/Command.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package io.kestra.plugin.fs.ssh;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.*;
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.annotations.PluginProperty;
Expand Down Expand Up @@ -78,6 +76,22 @@
privateKey: "{{ secret('SSH_RSA_PRIVATE_KEY') }}"
commands: ['touch kestra_was_here']
"""
),
@Example(
title = "Run SSH command using the local OpenSSH configuration",
full = true,
code = """
id: ssh
namespace: company.team
tasks:
- id: ssh
type: io.kestra.plugin.fs.ssh.Command
authMethod: OPEN_SSH
useOpenSSHConfig: true
host: localhost
password: pass.
commands:
- echo "Hello World\""""
)
}
)
Expand All @@ -94,6 +108,10 @@ public class Command extends Task implements SshInterface, RunnableTask<Command.
private String privateKey;
private String privateKeyPassphrase;

// OpenSSH config
@Builder.Default
private String openSSHConfigDir = "~/.ssh/config";

@Builder.Default
private AuthMethod authMethod = AuthMethod.PASSWORD;

Expand Down Expand Up @@ -148,11 +166,14 @@ public Command.ScriptOutput run(RunContext runContext) throws Exception {
if (password == null) {
throw new IllegalArgumentException("Password is necessary for given SSH auth method: " + AuthMethod.PASSWORD);
}
}
else if (authMethod == AuthMethod.PUBLIC_KEY) {
} else if (authMethod == AuthMethod.PUBLIC_KEY) {
if (privateKey == null) {
throw new IllegalArgumentException("Private key is necessary for given SSH auth method: " + AuthMethod.PASSWORD);
}
} else if(authMethod == AuthMethod.OPEN_SSH) {
if (runContext.pluginConfiguration(ALLOW_OPEN_SSH_CONFIG).isEmpty() || !Boolean.TRUE.equals(runContext.<Boolean>pluginConfiguration(ALLOW_OPEN_SSH_CONFIG).get())) {
throw new IllegalArgumentException("You need to allow access to the host OpenSSH configuration via the plugin configuration `" + ALLOW_OPEN_SSH_CONFIG + "`");
}
}

try(
Expand Down Expand Up @@ -189,7 +210,12 @@ else if (authMethod == AuthMethod.PUBLIC_KEY) {
} else {
jsch.addIdentity("primary", privateKeyBytes, null, null);
}
} else if (authMethod == AuthMethod.OPEN_SSH) {
ConfigRepository configRepository = OpenSSHConfig.parseFile(runContext.render(openSSHConfigDir));
jsch.setConfigRepository(configRepository);
session.setPassword(runContext.render(password));
}

session.setConfig("StrictHostKeyChecking", strictHostKeyChecking);
session.connect();

Expand Down
12 changes: 10 additions & 2 deletions src/main/java/io/kestra/plugin/fs/ssh/SshInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import jakarta.validation.constraints.NotNull;

public interface SshInterface {
String ALLOW_OPEN_SSH_CONFIG = "allow-open-ssh-config";

@Schema(
title = "Hostname of the remote server"
)
Expand All @@ -30,7 +32,6 @@ public interface SshInterface {
title = "Username on the remote server, required for password auth method"
)
@PluginProperty(dynamic = true)
@NotNull
String getUsername();

@Schema(
Expand All @@ -51,8 +52,15 @@ public interface SshInterface {
@PluginProperty(dynamic = true)
String getPrivateKeyPassphrase();

@Schema(
title = "OpenSSH configuration directory in case the authentication method is `OPEN_SSH`."
)
@PluginProperty(dynamic = true)
String getOpenSSHConfigDir();

enum AuthMethod {
PASSWORD,
PUBLIC_KEY
PUBLIC_KEY,
OPEN_SSH
}
}
34 changes: 30 additions & 4 deletions src/test/java/io/kestra/plugin/fs/ssh/CommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import jakarta.inject.Named;
import java.nio.charset.*;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.io.File;
Expand All @@ -27,10 +28,6 @@ class CommandTest {
@Inject
private RunContextFactory runContextFactory;

@Inject
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
private QueueInterface<LogEntry> logQueue;

@Test
void run_passwordMethod() throws Exception {
Command command = Command.builder()
Expand Down Expand Up @@ -97,6 +94,35 @@ void run_pubkeyMethod() throws Exception {
assertThat(run.getStdErrLineCount(), is(2));
assertThat(run.getVars().get("out"), is("1"));
assertThat(run.getVars().get("err"), is("2"));
}

@Test
@Disabled("Cannot work on CI")
void run_openSSHMethod() throws Exception {
Command command = Command.builder()
.id(CommandTest.class.getName())
.type(CommandTest.class.getName())
.host("localhost")
.password("password")
.authMethod(AuthMethod.OPEN_SSH)
.port("2222")
.commands(new String[] {
"echo 0",
"echo 1",
">&2 echo 2",
"echo '::{\"outputs\":{\"out\":\"1\"}}::'",
">&2 echo '::{\"outputs\":{\"err\":\"2\"}}::'",
})
.build();

Command.ScriptOutput run = command.run(TestsUtils.mockRunContext(runContextFactory, command, ImmutableMap.of()));

Thread.sleep(500);

assertThat(run.getExitCode(), is(0));
assertThat(run.getStdOutLineCount(), is(3));
assertThat(run.getStdErrLineCount(), is(2));
assertThat(run.getVars().get("out"), is("1"));
assertThat(run.getVars().get("err"), is("2"));
}
}
5 changes: 5 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ kestra:
type: local
local:
base-path: /tmp/unittest
plugins:
configurations:
- type: io.kestra.plugin.fs.ssh.Command
values:
allow-open-ssh-config: true
5 changes: 5 additions & 0 deletions src/test/resources/ssh/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Host localhost
HostName localhost
User foo
IdentityFile ./id_ed25519
IdentitiesOnly yes

0 comments on commit 05284d5

Please sign in to comment.