From 0899cb0efa8dc42aa1a9f18e6c9077f571289412 Mon Sep 17 00:00:00 2001 From: Bill Church Date: Wed, 21 Aug 2024 20:51:33 +0000 Subject: [PATCH] chore: template keyboard-interactive --- app/ssh.js | 85 +++++++++++++++++-- tests/servers/keyboard-interactive/Dockerfile | 27 ++++++ tests/servers/keyboard-interactive/README.md | 58 +++++++++++++ 3 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 tests/servers/keyboard-interactive/Dockerfile create mode 100644 tests/servers/keyboard-interactive/README.md diff --git a/app/ssh.js b/app/ssh.js index 255c844..62c360b 100644 --- a/app/ssh.js +++ b/app/ssh.js @@ -8,12 +8,24 @@ const { maskSensitiveData } = require("./utils") const debug = createNamespacedDebug("ssh") +/** + * SSHConnection class handles SSH connections and operations. + * @class + * @param {Object} config - Configuration object for the SSH connection. + */ function SSHConnection(config) { this.config = config this.conn = null this.stream = null } +/** + * Connects to the SSH server using the provided credentials. + * @function + * @memberof SSHConnection + * @param {Object} creds - The credentials object containing host, port, username, and password. + * @returns {Promise} - A promise that resolves with the SSH connection instance. + */ SSHConnection.prototype.connect = function(creds) { debug("connect: %O", maskSensitiveData(creds)) return new Promise((resolve, reject) => { @@ -38,14 +50,66 @@ SSHConnection.prototype.connect = function(creds) { reject(error) }) + this.conn.on( + "keyboard-interactive", + (name, instructions, lang, prompts, finish) => { + this.handleKeyboardInteractive( + creds, + name, + instructions, + lang, + prompts, + finish + ) + } + ) + this.conn.connect(sshConfig) }) } /** - * Gets the SSH configuration - * @param {Object} creds - The credentials object - * @returns {Object} The SSH configuration object + * Handles keyboard-interactive authentication prompts. + * @function + * @memberof SSHConnection + * @param {Object} creds - The credentials object containing password. + * @param {string} name - The name of the authentication request. + * @param {string} instructions - The instructions for the keyboard-interactive prompt. + * @param {string} lang - The language of the prompt. + * @param {Array} prompts - The list of prompts provided by the server. + * @param {Function} finish - The callback to complete the keyboard-interactive authentication. + */ +SSHConnection.prototype.handleKeyboardInteractive = function( + creds, + name, + instructions, + lang, + prompts, + finish +) { + debug("handleKeyboardInteractive: Keyboard-interactive auth %O", prompts) + const responses = [] + + for (let i = 0; i < prompts.length; i += 1) { + if (prompts[i].prompt.toLowerCase().includes("password")) { + responses.push(creds.password) + } else { + // todo: For any non-password prompts, we meed to implement a way to + // get responses from the user through a modal. For now, we'll just + // send an empty string + responses.push("") + } + } + + finish(responses) +} + +/** + * Generates the SSH configuration object based on credentials. + * @function + * @memberof SSHConnection + * @param {Object} creds - The credentials object containing host, port, username, and password. + * @returns {Object} - The SSH configuration object. */ SSHConnection.prototype.getSSHConfig = function(creds) { return { @@ -62,6 +126,13 @@ SSHConnection.prototype.getSSHConfig = function(creds) { } } +/** + * Opens an interactive shell session over the SSH connection. + * @function + * @memberof SSHConnection + * @param {Object} [options] - Optional parameters for the shell. + * @returns {Promise} - A promise that resolves with the SSH shell stream. + */ SSHConnection.prototype.shell = function(options) { return new Promise((resolve, reject) => { this.conn.shell(options, (err, stream) => { @@ -76,9 +147,11 @@ SSHConnection.prototype.shell = function(options) { } /** - * Resizes the terminal - * @param {number} rows - The number of rows - * @param {number} cols - The number of columns + * Resizes the terminal window for the current SSH session. + * @function + * @memberof SSHConnection + * @param {number} rows - The number of rows for the terminal. + * @param {number} cols - The number of columns for the terminal. */ SSHConnection.prototype.resizeTerminal = function(rows, cols) { if (this.stream) { diff --git a/tests/servers/keyboard-interactive/Dockerfile b/tests/servers/keyboard-interactive/Dockerfile new file mode 100644 index 0000000..07a2fb5 --- /dev/null +++ b/tests/servers/keyboard-interactive/Dockerfile @@ -0,0 +1,27 @@ +# Use the Debian Bullseye Slim image as the base +FROM debian:bullseye-slim + +# Install the necessary packages +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + openssh-server && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Configure SSH server +RUN mkdir /var/run/sshd && \ + sed -i 's/^ChallengeResponseAuthentication no/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config && \ + echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config && \ + echo 'Port 4444' >> /etc/ssh/sshd_config && \ + echo 'UsePAM yes' >> /etc/ssh/sshd_config && \ + echo 'AuthenticationMethods keyboard-interactive' >> /etc/ssh/sshd_config + +# Add a test user with a password +RUN useradd -m testuser && \ + echo "testuser:testpassword" | chpasswd + +# Expose port 4444 +EXPOSE 4444 + +# Start the SSH server +CMD ["/usr/sbin/sshd", "-D", "-e", "-d", "-d", "-d"] \ No newline at end of file diff --git a/tests/servers/keyboard-interactive/README.md b/tests/servers/keyboard-interactive/README.md new file mode 100644 index 0000000..dc42f34 --- /dev/null +++ b/tests/servers/keyboard-interactive/README.md @@ -0,0 +1,58 @@ +# Keyboard Interactive SSH Server +A test SSH server that uses keyboard-interactive authentication and listens on port 4444: + +```Dockerfile +# Use the Debian Bullseye Slim image as the base +FROM debian:bullseye-slim + +# Install the necessary packages +# Use the Debian Bullseye Slim image as the base +FROM debian:bullseye-slim + +# Install the necessary packages +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + openssh-server && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Configure SSH server +RUN mkdir /var/run/sshd && \ + sed -i 's/^ChallengeResponseAuthentication no/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config && \ + echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config && \ + echo 'Port 4444' >> /etc/ssh/sshd_config && \ + echo 'UsePAM yes' >> /etc/ssh/sshd_config && \ + echo 'AuthenticationMethods keyboard-interactive' >> /etc/ssh/sshd_config + +# Add a test user with a password +RUN useradd -m testuser && \ + echo "testuser:testpassword" | chpasswd + +# Expose port 4444 +EXPOSE 4444 + +# Start the SSH server +CMD ["/usr/sbin/sshd", "-D", "-e", "-d -d -d"] +``` + +### Instructions: + +1. **Build the Docker image**: + ```bash + docker build -t keyboard-ssh-server . + ``` + +2. **Run the container**: + ```bash + docker run --rm -p 4444:4444 --name keyboard-ssh-server keyboard-ssh-server + ``` + +This Dockerfile sets up an SSH server that listens on port 4444 and uses keyboard-interactive authentication. The `testuser` has been created with the password `testpassword`. + +You can connect to this SSH server using the following command: + +```bash +ssh -p 4444 testuser@localhost +``` + +You'll be prompted for a password as part of the keyboard-interactive authentication process.