scala-ssh

repository·master·Indexed 42 days ago

https://github.com/sirthias/scala-ssh

A Scala library providing a high-level, idiomatic API for remote shell access via SSH, built on top of the SSHJ library. It supports executing remote commands, SCP file transfers, and SFTP operations. Features include a hierarchical host configuration lookup mechanism, support for password and keyfile authentication, and host key verification. Compatible with Scala 2.12, 2.13, and 3.

Tokens
5.4K
Snippets
19
Records
25
Agent score
54%

What's inside scala-ssh

  1. How host configuration lookup works

    master

    When using the default HostConfigProvider, scala-ssh attempts to find configuration files (either in ~/.scala-ssh/ or the classpath) using a hierarchical fallback mechanism based on the hostname.

    For a host like node42.tier1.example.com, it tries the following in order:

    1. node42.tier1.example.com
    2. node4X.tier1.example.com (replacing digits with 'X')
    3. nodeXX.tier1.example.com
    4. tier1.example.com (dropping segments up to the first dot)
    5. tierX.example.com
    6. example.com
    7. com
  2. Install scala-ssh via SBT

    master

    Add the scala-ssh dependency to your SBT project. The library is built against Scala 2.12, 2.13, and 3. It is recommended to also add logback-classic for logging, as the underlying SSHJ library uses SLF4J.

    libraryDependencies += "com.decodified" %% "scala-ssh" % "0.11.1"
    
    // Recommended for logging
    libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
  3. Supported Authentication Methods in SshClient

    master

    The SshClient supports three primary authentication strategies defined via the Login type in HostConfig:

    1. Password Authentication (PasswordLogin): Uses a username and a PasswordProducer to provide the password.
    2. Public Key Authentication (PublicKeyLogin): Uses a username, a PasswordProducer (for passphrase-protected keys), and a list of keyfileLocations.
      • Key locations can be absolute file paths or prefixed with classpath: to load keys from the application resources.
    3. Agent Authentication (AgentLogin): Uses an SSH agent proxy to authenticate using the identities available in the local agent.
  4. Configure SSH authentication types

    master

    Authentication is handled via the SshLogin type (implied by the login field in HostConfig). The configuration supports three main modes:

    1. Password: Requires username and password.
    2. Keyfile: Requires username and either a keyfile path or uses default locations. Supports an optional passphrase.
    3. Agent: Uses an SSH agent. Requires username (defaults to user home) and host (defaults to localhost).
  5. Troubleshoot Java Cryptography Extension (JCE) issues

    master

    If you encounter net.schmizz.sshj.common.SSHRuntimeException: null or java.security.spec.InvalidKeySpecException: key spec not recognised, it is likely because your JDK lacks the necessary cryptographic strength.

    Solution: Install the Java Cryptography Extension (JCE) Policy Files from the JDK additional downloads section.

  6. Configure remote hosts using HostConfig

    master

    You can provide custom connection details by passing a HostConfigProvider to SSH.apply. A HostConfig defines the connection parameters:

    case class HostConfig(
      login: SshLogin,
      hostName: String = "",
      port: Int = 22,
      connectTimeout: Option[Int] = None,
      connectionTimeout: Option[Int] = None,
      commandTimeout: Option[Int] = None,
      enableCompression: Boolean = false,
      hostKeyVerifier: HostKeyVerifier = ...,
      sshjConfig: Config = ...
    )

    If no provider is specified, scala-ssh uses a default provider that looks for configuration files in ~/.scala-ssh/{hostname} or classpath resources.

  7. Execute remote commands with the SSH object

    master

    The SSH object is the highest-level API. You can use it to establish a connection to a host and execute commands using an SshClient instance provided in a callback. The exec method returns a result containing access to stdOutAsString(), stdErrAsString(), and the exit code.

    SSH("example.com") { client =>
      for {
        result <- client.exec("ls -a")
      } println("Result:\n" + result.stdOutAsString())
    }
  8. Host Config File Format Reference

    master

    Host configuration files are UTF8-encoded text files using key = value pairs. Blank lines and lines starting with # are ignored.

    Supported keys:

    KeyRequirementDescription
    login-typeRequiredpassword or keyfile
    usernameRequiredThe remote user
    host-nameOptionalAssumed to be the filename if not provided
    portOptionalDefault is 22
    passwordRequired for password typeThe user password
    keyfileOptional for keyfile typePath to private key. Use + prefix to search in addition to defaults (~/.ssh/id_ed25519, etc.), or classpath: prefix for classpath resources
    passphraseOptionalPassphrase for the keyfile
    connect-timeoutOptionalConnection request timeout in milliseconds
    connection-timeoutOptionalIdle connection timeout in milliseconds
    command-timeoutOptionalCommand response timeout in milliseconds
    enable-compressionOptionalEnables zlib compression (requires jzlib on classpath). Default is no
    fingerprintOptionalPublic host key fingerprint (e.g. 4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21). Use any to disable verification. Otherwise, searches ~/.ssh/known_hosts
    # example config
    login-type = password
    username = bob
    password = 123
    command-timeout = 5000
    enable-compression = yes
  9. Initialize SshClient using SshClient.apply

    master

    You can create an SshClient instance using the companion object's apply method. This is the recommended way to instantiate a client as it integrates with HostConfigProvider to resolve host settings.

    SshClient.apply(host: String, configProvider: HostConfigProvider = HostFileConfig()): Try[SshClient]

    • host: The hostname or IP address of the remote machine.
    • configProvider: An implementation of HostConfigProvider (defaults to HostFileConfig()) used to look up the HostConfig for the given host.

    Returns a Try[SshClient] which will be a Failure if the configuration for the host cannot be found or loaded.

    import com.decodified.scalassh.SshClient
    
    // Creates a client based on settings found in the default host config file
    val clientTry: Try[SshClient] = SshClient("example.com")
  10. Use HostConfigProvider to load configurations

    master

    Instead of manual construction, you can use HostConfigProvider to resolve configurations for a given host string. This is useful when host settings are stored in files or resources.

    • HostConfigProvider.fromLogin(login: SshLogin): Creates a provider that always returns the same login for any host.
    • HostConfigProvider.fromHostConfig(config: HostConfig): Creates a provider that returns the provided config (updating the hostname if necessary).
    • HostFileConfig: Searches for host-specific configuration files in ~/.scala-ssh/ using a pattern-matching logic (e.g., searching for web.example.com, then web.X.example.com, then X.example.com).
    • HostResourceConfig: Searches for host configuration files within the application's classpath resources.
  11. Execute commands with SshClient

    master

    The SshClient class provides methods to execute commands on a remote host. It handles connection, authentication, and session management automatically via the authenticatedClient lazy value.

    Standard Execution

    Use exec(command: Command) to run a command. This method returns a Try[CommandResult]. It is suitable for non-interactive commands.

    Pseudo-Terminal (PTY) Execution

    Use execPTY(command: Command) if the command requires a pseudo-terminal (e.g., interactive programs, tools that expect a TTY like top or vim). This method uses the ptyConfig defined in your HostConfig to allocate the terminal dimensions and modes.

    Command Input and Timeouts

    • Input: The Command object contains an input field (an InputStream) used to pipe data to the remote command's standard input.
    • Timeout: The command will wait for the duration specified in command.timeout. If that is not provided, it falls back to config.commandTimeout from the HostConfig.
    // Example usage pattern
    val client = SshClient("my-remote-host")
    
    val cmd = Command("ls -la", input = new java.io.ByteArrayInputStream(b""))
    val result = client.exec(cmd)
    
    result match {
      case Success(res) => println(s"Output: ${res.output}")
      case Failure(err) => println(s"Error: $err")
    }