7 Commits

Author SHA1 Message Date
Justin Crawford
1c93b9fc0e Increment project version for sponge release 2019-10-13 01:03:48 -07:00
Justin Crawford
170a96eb94 Update mkpasswd to be slightly more secure
Try to ensure that the mkpasswd command run in ssh sessions only echos to
ssh client running that command. This gives us slightly more security
against other session users seeing the hashed password.

Fixed console sending with some of the APIs

Updated version to 1.3.7 to match for next spigot + bungeecord release.
2019-10-10 19:48:32 -07:00
Justin Crawford
ec1ed8cb9e Add new logo 2019-10-09 00:07:16 -07:00
Zachery
67146ce589 Change the load method to startup
This lets the plugin start before the world is generated, this is done so you can SSH into the server while it's still technically starting.
2019-10-08 17:37:54 -05:00
Justin Crawford
e986cacc50 Update readme. 2019-10-07 23:24:32 -07:00
Justin Crawford
0afba39d57 This should actually fix rmichela#10 2019-10-06 02:58:52 -07:00
Justin Crawford
1b4c7c2304 Update readme as it's wildly out of date 2019-10-06 01:07:39 -07:00
13 changed files with 322 additions and 139 deletions

106
README.md
View File

@@ -1,70 +1,100 @@
Spigot-SSHD
Minecraft-SSHD
===========
[![Build Status](https://travis-ci.org/Justasic/Spigot-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Spigot-SSHD)
[![Build Status](https://travis-ci.org/Justasic/Minecraft-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Minecraft-SSHD)
[![Release](https://img.shields.io/github/release/Justasic/Minecraft-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Minecraft-SSHD/releases/latest)
[![GitHub license](https://img.shields.io/github/license/Justasic/Minecraft-SSHD)](https://github.com/Justasic/Minecraft-SSHD/blob/master/LICENSE)
Have you ever wished you could remotely access your server's admin console without having to setup a complex remote access system? Now you can with SSHD.
<img align="left" width="140" height="140" src="docs/ssh_logo.png?raw=true" hspace="5" vspace="5" alt="diskover"><br/>
SSHD securely exposes your Spigot admin console using the SSH protocol - the same protocol that serves as the secure foundation for nearly all remote server administration.
**Have you ever wished you could remotely access your server's admin console without having to setup a complex remote access system? Now you can with Minecraft-SSHD!**
Minecraft-SSHD securely exposes your BungeeCord admin console and the server filesystem using the SSH protocol - the same protocol that serves as the secure foundation for nearly all remote server administration.<br/>
- Compatible with all ssh clients, regardless of operating system.
- Remotely view your server log in real-time.
- Remotely issue commands from the server console, just as if you were on the server itself.
- Supports multiple concurrent remote connections.
- Strong identity support using public key authentication.
- Audit history who is running commands in the console
- Run Spigot without using screen or tmux (by adding `-noconsole`)
- Remotely script your server by issuing one-off console commands with ssh.
## Why should I use SSHD?
### Why should I use Minecraft-SSHD?
- Your server runs on Windows.
- You are in a shared hosting environment that only gives you access to the - log files.
- You want to share access to your server console, but don't want to give anybody access to the machine its running on.
- You always wanted to use RCON, but want to see the server log as well.
- You are tired of running your server in a Screen session.
- You are tired of running your server in a GNU screen or tmux session.
- You just want to access your server console using SSH.
## Configuration
- **listenAddress** - The network interface(s) SSHD should listen on. (Default all)
- **port** - Specify the port SSHD should listen on. (Default 22)
- **username/password** - The credentials used to log into the server console. (Default blank)
Note: By default, only public key authentication is enabled. This is the most secure authentication mode! Setting a username and password will make your server less secure.
## Setting Up Public Key Authentication
Screenshots
============
Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your Spigot server.
<img align="left" width="390" src="docs/console.png?raw=true" hspace="5" vspace="5" alt="console">
<img width="400" src="docs/session.png?raw=true" alt="session"><br>
On Windows
1. TODO
Setting Up Public Key Authentication
====================================
On Linux/OS X
Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your Spigot server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files.
1. TODO
## Generating New Keys
#### On Windows
1. Ensure [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) is installed and open up `puttygen` (you can search for it in start search).
2. Click `Generate` and follow the directions.
3. When it finishes, set your key comment (if you like) and copy the text from the big `Public key for pasting into OpenSSH authorized_keys file`
4. Create a new file inside of the `plugins/SSHD/authorized_users` folder and name the file just the username (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
5. Paste the key you copied from step 3 into the file you just created.
6. SSH into the server and see if your key works
#### On Linux/OS X
1. Open a terminal and run `ssh-keygen` then follow the prompts.
2. Copy the contents of your `id_<algorithm>.pub` file (example: if your key was generated with rsa, it will be named `id_rsa.pub`). This file is usually located in `/home/YOURUSERNAME/.ssh/`
3. Paste the contents of the .pub file into a new file inside the `plugins/SSHD/authorized_users` folder and name the file just the username that the user will use to login with (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
## Using existing keys
#### On Windows
1. Ensure [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) is installed and open up `puttygen` (you can search for it in start search).
2. Click `Conversions` then click `Import Key` and select your .ppk file.
3. Copy the text from the big `Public key for pasting into OpenSSH authorized_keys file`
4. Create a new file inside of the `plugins/SSHD/authorized_users` folder and name the file just the username (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
5. Paste the key you copied from step 3 into the file you just created.
6. SSH into the server and see if your key works
#### On Linux/OS X
1. Copy the contents of your `id_<algorithm>.pub` file (example: if your key was generated with rsa, it will be named `id_rsa.pub`). This file is usually located in `/home/YOURUSERNAME/.ssh/`
2. Paste the contents of the .pub file into a new file inside the `plugins/SSHD/authorized_users` folder and name the file just the username that the user will use to login with (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
Plugin Usage
============
## Commands
None - just install and go.
/mkpasswd <hash|help> <password>
mkpasswd supports the following hash algorithms:
- bcrypt - Using the OpenBSD-style Blowfish password hash
- sha256 - Using a basic salted sha256 hash
- pbkdf2 - Using the [PBKDF2](https://en.wikipedia.org/wiki/Pbkdf2) password hash
- PLAIN - Using plain text passwords (very insecure)
## Permissions
None - SSHD uses cryptographic certificates or a secure username and password to verify remote access.
`sshd.mkpasswd` - Checks if the in-game user has access to run the mkpasswd command.
Minecraft-SSHD uses cryptographic certificates or a secure username and password to verify remote access.
## Source Code
[Get the source on GitHub](https://github.com/Justasic/Spigot-SSHD "Source Code")
## Metrics
This plugin utilizes Hidendra's plugin metrics system. the following information is collected and sent to mcstats.org unless opted out:
- A unique identifier
- The server's version of Java
- Whether the server is in offline or online mode
- Plugin's version
- Server's version
- OS version/name and architecture
- core count for the CPU
- Number of players online
- Metrics version
Opting out of this service can be done by editing plugins/Plugin Metrics/config.yml and changing opt-out to true.
[Get the source on GitHub](https://github.com/Justasic/Minecraft-SSHD "Source Code")

BIN
docs/console.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
docs/session.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
docs/ssh_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

133
docs/ssh_logo.svg Normal file
View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="800"
height="800"
viewBox="0 0 211.66666 211.66667"
version="1.1"
id="svg8"
sodipodi:docname="ssh_logo.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<defs
id="defs2">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter5273">
<feFlood
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood5263" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite5265" />
<feGaussianBlur
in="composite1"
stdDeviation="3.5"
result="blur"
id="feGaussianBlur5267" />
<feOffset
dx="1"
dy="1"
result="offset"
id="feOffset5269" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite5271" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="375.64503"
inkscape:cy="367.3313"
inkscape:document-units="mm"
inkscape:current-layer="layer3"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1920"
inkscape:window-y="119"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Background"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-85.333343)">
<circle
style="fill:#4c4c4c;fill-opacity:1;stroke-width:0.30369404;filter:url(#filter5273)"
id="path4533"
cx="105"
cy="192.00002"
r="100" />
<circle
style="fill:#242424;fill-opacity:1;stroke-width:0.27332464;"
id="path4533-3"
cx="105"
cy="192.00002"
r="90" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Text"
style="opacity:0.98999999">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:145.88342285px;line-height:0;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.31260732"
x="130.4019"
y="91.524567"
id="text5759"
transform="scale(0.84637592,1.1815081)"><tspan
sodipodi:role="line"
id="tspan5757"
x="130.4019"
y="91.524567"
style="font-size:145.88342285px;line-height:0;fill:#ffffff;fill-opacity:1;stroke-width:0.31260732">_</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:145.88342285px;line-height:0;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:0.98999999;fill:#20bb00;fill-opacity:1;stroke:none;stroke-width:0.31260732"
x="39.53738"
y="118.43275"
id="text5759-2"
transform="scale(0.84637592,1.1815081)"><tspan
sodipodi:role="line"
id="tspan5757-5"
x="39.53738"
y="118.43275"
style="font-size:123.47222137px;line-height:0;fill:#20bb00;fill-opacity:1;stroke-width:0.31260732">&gt;</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -6,8 +6,9 @@
<groupId>com.ryanmichela</groupId>
<artifactId>sshd</artifactId>
<version>1.3.6</version>
<url>https://github.com/Justasic/Bukkit-SSHD/</url>
<description>Minecraft-SSHD: The SSH daemon for Minecraft servers.</description>
<version>2.0.0</version>
<url>https://github.com/Justasic/Minecraft-SSHD/</url>
<properties>
<java.version>1.8</java.version>
@@ -171,4 +172,4 @@
</build>
<packaging>jar</packaging>
</project>
</project>

View File

@@ -67,8 +67,7 @@ public class ConsoleLogFormatter extends Formatter {
return result;
}
public
String format(LogRecord logrecord)
public String format(LogRecord logrecord)
{
try
{
@@ -97,7 +96,7 @@ public class ConsoleLogFormatter extends Formatter {
stringbuilder.append(stringwriter.toString());
}
return stringbuilder.toString();
return stringbuilder.toString().replace("\n", "\r\n");
}
private void colorize(LogRecord logrecord)

View File

@@ -136,9 +136,11 @@ public class ConsoleShellFactory implements ShellFactory {
if (command.equals("cls"))
{
this.ConsoleReader.clearScreen();
this.ConsoleReader.drawLine();
this.ConsoleReader.flush();
continue;
}
// Hide the mkpasswd command input.
// Hide the mkpasswd command input from other users.
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
Bukkit.getScheduler().runTask(
@@ -152,18 +154,21 @@ public class ConsoleShellFactory implements ShellFactory {
}
else
{
// Don't send our mkpasswd command output. This will echo passwords back
// to the console for all to see. This command is strictly between
// our plugin and the connected client.
if (!mkpasswd)
{
SshdPlugin.instance.getLogger().info("<" + this.Username + "> <" + (mkpasswd ? "True": "False") + "> " + command);
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}
else
{
Bukkit.dispatchCommand(this.SshdCommandSender, command);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}
});
}
// This should help stop one of the bugs where bytes are waiting to be written
// but the client fucked off already so the plugin throws an exception.
((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender);
}
catch (IOException e)
{

View File

@@ -1,49 +1,59 @@
package com.ryanmichela.sshd;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelClosedException;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
/**
* Copyright 2013 Ryan Michela
*/
public class FlushyOutputStream extends OutputStream {
public class FlushyOutputStream extends OutputStream
{
private OutputStream base;
private boolean isClosed = false;
public FlushyOutputStream(OutputStream base) {
public FlushyOutputStream(OutputStream base)
{
this.base = base;
}
@Override
public void write(int b) throws IOException {
if (isClosed) return;
base.write(b);
base.flush();
public void write(int b) throws IOException
{
this.write(BigInteger.valueOf(b).toByteArray());
}
@Override
public void write(byte[] b) throws IOException {
if (isClosed) return;
base.write(b);
base.flush();
public void write(byte[] b) throws IOException
{
this.write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (isClosed) return;
try {
public void write(byte[] b, int off, int len) throws IOException
{
if (isClosed)
return;
try
{
base.write(b, off, len);
base.flush();
} catch (SshException e) {
if (!e.getMessage().contains("channel already closed")) throw e;
}
catch (SshChannelClosedException e)
{
// ignored.
}
}
@Override
public void close() {
public void close() throws IOException
{
isClosed = true;
base.close();
}
}

View File

@@ -12,9 +12,28 @@ import com.ryanmichela.sshd.SshdPlugin;
class MkpasswdCommand implements CommandExecutor
{
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
// Because Spigot's failed syntax API is really less than ideal (you should be required to add a
// SendSyntax function override), we're just always going to return true even for syntax failures
// as we will handle the syntax message internally. This also lets us send the messages more
// securely to the client without people knowing we're using the command. This prevents password
// or hash leakages from the user to other connected users. Plus this syntax will show how
// to both use the command and what hashes we support which is important for people who don't
// know how to RTFM. - Justin
private void SendSyntax(CommandSender sender, boolean invalid)
{
if (invalid)
sender.sendMessage("\u00A7cInvalid Syntax\u00A7r");
sender.sendMessage("\u00A7a/mkpasswd <help|hash> <password>\u00A7r");
sender.sendMessage("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
{
// If we're not mkpasswd, just fuck off.
if (!label.equalsIgnoreCase("mkpasswd"))
return false;
String algoritm, password;
try
{
@@ -22,79 +41,52 @@ class MkpasswdCommand implements CommandExecutor
// spaces in their passwords otherwise it won't be as strong as it should be.
algoritm = args[0];
password = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (password.trim().isEmpty()) // Shortcut to the catch statement below.
throw new ArrayIndexOutOfBoundsException();
}
catch (ArrayIndexOutOfBoundsException e)
{
// ignore it.
return false;
this.SendSyntax(sender, true);
return true;
}
// If they're console, allow regardless.
if (!(sender instanceof Player))
{
if (label.equalsIgnoreCase("mkpasswd"))
boolean hasperm = (sender instanceof Player) ? ((Player)sender).hasPermission("sshd.mkpasswd") : true;
if (hasperm)
{
try
{
try
String hash = "";
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
{
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
{
sender.sendMessage("Your hash: " + password);
// I mean c'mon...
sender.sendMessage("Bro really? it's literally your unencrypted password...");
}
else if (algoritm.equalsIgnoreCase("pbkdf2"))
sender.sendMessage("Your hash: " + Cryptography.PBKDF2_HashPassword(password));
else if (algoritm.equalsIgnoreCase("bcrypt"))
sender.sendMessage("Your hash: " + Cryptography.BCrypt_HashPassword(password));
else if (algoritm.equalsIgnoreCase("sha256"))
sender.sendMessage("Your hash: " + Cryptography.SHA256_HashPassword(password));
else if (algoritm.equalsIgnoreCase("help"))
sender.sendMessage("Supported hash algorithms: pbkdf2, bcrypt, sha256, plain");
else
return false;
// I mean c'mon...
sender.sendMessage("Bro really? it's literally your unencrypted password...");
return true;
}
catch (Exception e)
else if (algoritm.equalsIgnoreCase("pbkdf2"))
hash = Cryptography.PBKDF2_HashPassword(password);
else if (algoritm.equalsIgnoreCase("bcrypt"))
hash = Cryptography.BCrypt_HashPassword(password);
else if (algoritm.equalsIgnoreCase("sha256"))
hash = Cryptography.SHA256_HashPassword(password);
else
{
// We're console, just print the stack trace.
e.printStackTrace();
return false;
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
return true;
}
return true;
sender.sendMessage("\u00A79Your Hash: " + hash + "\u00A7r");
}
catch (Exception e)
{
// We're console, just print the stack trace.
e.printStackTrace();
sender.sendMessage("\u00A7cAn error occured. Please check console for details.\u00A7r");
}
}
else
{
Player player = (Player) sender;
if (label.equalsIgnoreCase("mkpasswd"))
{
try
{
if (player.hasPermission("sshd.mkpasswd"))
{
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
sender.sendMessage(password);
else if (algoritm.equalsIgnoreCase("pbkdf2"))
sender.sendMessage(Cryptography.PBKDF2_HashPassword(password));
else if (algoritm.equalsIgnoreCase("bcrypt"))
sender.sendMessage(Cryptography.BCrypt_HashPassword(password));
else if (algoritm.equalsIgnoreCase("sha256"))
sender.sendMessage(Cryptography.SHA256_HashPassword(password));
else
return false;
}
}
catch (Exception e)
{
// since this is a player, send a failure message
sender.sendMessage("An error occured, please check console.");
e.printStackTrace();
return false;
}
return true;
}
}
return false;
}
return true;
}
}

View File

@@ -58,7 +58,6 @@ class SshdPlugin extends JavaPlugin
{
instance = this;
// pls comment this shit so I know what it does.
sshd = SshServer.setUpDefaultServer();
sshd.setPort(getConfig().getInt("Port", 1025));
String host = getConfig().getString("ListenAddress", "all");

View File

@@ -16,21 +16,23 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.ConsoleLogFormatter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
import java.util.logging.Level;
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender
{
private final PermissibleBase perm = new PermissibleBase(this);
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
// Set by the upstream allocating function
public ConsoleShellFactory.ConsoleShell console;
public void sendMessage(String message) {
this.sendRawMessage(message);
public void sendMessage(String message)
{
this.sendRawMessage(message + "\r");
}
public void sendRawMessage(String message)
@@ -40,7 +42,18 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
return;
try
{
this.console.ConsoleReader.println(ChatColor.stripColor(message));
this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r"));
this.console.ConsoleReader.print(this.console.ConsoleReader.RESET_LINE + "");
this.console.ConsoleReader.flush();
try
{
this.console.ConsoleReader.drawLine();
}
catch (Throwable ex)
{
this.console.ConsoleReader.getCursorBuffer().clear();
}
this.console.ConsoleReader.flush();
}
catch (IOException e)
{

View File

@@ -1,8 +1,9 @@
name: SSHD
version: ${project.version}
author: Ryan Michela, Haarolean, toxuin, Justin Crawford
author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman
main: com.ryanmichela.sshd.SshdPlugin
load: STARTUP
commands:
mkpasswd:
description: Make a SSHD password hash
usage: /mkpasswd <hash|help> <password>
usage: /mkpasswd <hash|help> <password>