Implemented access control for SFTP
This commit is contained in:
parent
3ad2a810f8
commit
ae8d5bff1d
6
pom.xml
6
pom.xml
@ -53,6 +53,12 @@
|
|||||||
<version>2.3.0</version>
|
<version>2.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.sshd</groupId>
|
||||||
|
<artifactId>sshd-contrib</artifactId>
|
||||||
|
<version>2.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.sshd</groupId>
|
<groupId>org.apache.sshd</groupId>
|
||||||
<artifactId>sshd-common</artifactId>
|
<artifactId>sshd-common</artifactId>
|
||||||
|
@ -20,7 +20,7 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
|||||||
{
|
{
|
||||||
// Depending on our hash type, we have to try and figure out what we're doing.
|
// Depending on our hash type, we have to try and figure out what we're doing.
|
||||||
String HashType = SshdPlugin.instance.getConfig().getString("PasswordType");
|
String HashType = SshdPlugin.instance.getConfig().getString("PasswordType");
|
||||||
String ConfigHash = SshdPlugin.instance.getConfig().getString("Credentials." + username.trim());
|
String ConfigHash = SshdPlugin.instance.getConfig().getString("Credentials." + username.trim() + ".password");
|
||||||
|
|
||||||
if (ConfigHash == null)
|
if (ConfigHash == null)
|
||||||
SshdPlugin.instance.getLogger().warning("Config has no such user: " + username);
|
SshdPlugin.instance.getLogger().warning("Config has no such user: " + username);
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package com.ryanmichela.sshd;
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||||
|
import org.apache.sshd.common.session.helpers.AbstractSession;
|
||||||
import org.apache.sshd.server.SshServer;
|
import org.apache.sshd.server.SshServer;
|
||||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||||
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
|
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
|
||||||
|
import org.apache.sshd.server.subsystem.sftp.SimpleAccessControlSftpEventListener;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import com.ryanmichela.sshd.ConsoleShellFactory;
|
import com.ryanmichela.sshd.ConsoleShellFactory;
|
||||||
@ -14,18 +18,35 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013 Ryan Michela
|
* Copyright 2013 Ryan Michela
|
||||||
*/
|
*/
|
||||||
public
|
public class SshdPlugin extends JavaPlugin
|
||||||
class SshdPlugin extends JavaPlugin
|
|
||||||
{
|
{
|
||||||
|
private SshServer sshd;
|
||||||
|
public static SshdPlugin instance;
|
||||||
|
|
||||||
private SshServer sshd;
|
public static List<ConfigurationSection> GetSections(ConfigurationSection source)
|
||||||
public static SshdPlugin instance;
|
{
|
||||||
|
if (source == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<ConfigurationSection> nodes = new ArrayList<ConfigurationSection>();
|
||||||
|
for (String key : source.getKeys(false))
|
||||||
|
{
|
||||||
|
if (source.isConfigurationSection(key))
|
||||||
|
nodes.add(source.getConfigurationSection(key));
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void onLoad()
|
@Override public void onLoad()
|
||||||
{
|
{
|
||||||
@ -73,9 +94,90 @@ class SshdPlugin extends JavaPlugin
|
|||||||
|
|
||||||
if (getConfig().getBoolean("EnableSFTP"))
|
if (getConfig().getBoolean("EnableSFTP"))
|
||||||
{
|
{
|
||||||
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
// Handle access control for SFTP.
|
||||||
sshd.setFileSystemFactory(
|
SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
|
||||||
new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
|
builder.addSftpEventListener(new SimpleAccessControlSftpEventListener() {
|
||||||
|
protected boolean isAccessAllowed(ServerSession session, String remote, Path localpath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConfigurationSection UsernameNamespace = getConfig().getConfigurationSection("Credentials." + session.getUsername() + ".sftp");
|
||||||
|
|
||||||
|
// They don't have SFTP enabled so deny them.
|
||||||
|
if (UsernameNamespace == null || !UsernameNamespace.getBoolean("enabled"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
List<ConfigurationSection> rules = GetSections(UsernameNamespace.getConfigurationSection("rules"));
|
||||||
|
if (rules != null)
|
||||||
|
{
|
||||||
|
for (ConfigurationSection path : rules)
|
||||||
|
{
|
||||||
|
// Check if the requesting path matches
|
||||||
|
if (localpath.toString().matches(path.getName()))
|
||||||
|
{
|
||||||
|
// Check if they have read permissions
|
||||||
|
if (path.getBoolean("readable"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
getLogger().info(String.format("Denied %s read access to \"%s\" matching rule \"%s\"", session.getUsername(), localpath.toString(), path.getName()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UsernameNamespace.getString("default").equalsIgnoreCase("allow");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
// Automatically deny.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isModificationAllowed(ServerSession session, String remote, Path localpath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConfigurationSection UsernameNamespace = getConfig().getConfigurationSection("Credentials." + session.getUsername() + ".sftp");
|
||||||
|
|
||||||
|
// They don't have SFTP enabled so deny them.
|
||||||
|
if (UsernameNamespace == null || !UsernameNamespace.getBoolean("enabled"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check a list of files against a path trying to be accessed.
|
||||||
|
List<ConfigurationSection> rules = GetSections(UsernameNamespace.getConfigurationSection("rules"));
|
||||||
|
if (rules != null)
|
||||||
|
{
|
||||||
|
for (ConfigurationSection path : rules)
|
||||||
|
{
|
||||||
|
// Check if the requesting path matches
|
||||||
|
if (localpath.toString().matches(path.getName()))
|
||||||
|
{
|
||||||
|
// Check if they have read permissions
|
||||||
|
if (path.getBoolean("writeable"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
getLogger().info(String.format("Denied %s modifications to \"%s\" matching rule \"%s\"", session.getUsername(), localpath.toString(), path.getName()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UsernameNamespace.getString("default").equalsIgnoreCase("allow");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
// Automatically deny.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sshd.setSubsystemFactories(Collections.singletonList(builder.build()));
|
||||||
|
sshd.setFileSystemFactory(new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getCommand("mkpasswd").setExecutor(new MkpasswdCommand());
|
this.getCommand("mkpasswd").setExecutor(new MkpasswdCommand());
|
||||||
@ -95,11 +197,16 @@ class SshdPlugin extends JavaPlugin
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sshd.stop();
|
// Terminate any active sessions
|
||||||
|
for (AbstractSession as : sshd.getActiveSessions())
|
||||||
|
as.close(true);
|
||||||
|
// Pass "true" to stop immediately!
|
||||||
|
sshd.stop(true);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,5 +37,34 @@ PasswordType: bcrypt
|
|||||||
|
|
||||||
# Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)
|
# Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)
|
||||||
Credentials:
|
Credentials:
|
||||||
# user1: password1
|
# Username (should match SSH key if using key-based authentication)
|
||||||
# user2: password2
|
justasic:
|
||||||
|
# Password hash from /mkpasswd command
|
||||||
|
password: $2a$10$Oqk83FrypRrMF35EDeoQDuidJOQEWBE0joEQ7MJFi/Oeg26wQ3fm2
|
||||||
|
# Whether they can read, write, or have read/write permissions to console.
|
||||||
|
console: RW
|
||||||
|
# SFTP access for this user.
|
||||||
|
sftp:
|
||||||
|
# Whether SFTP is enabled for this user.
|
||||||
|
enabled: true
|
||||||
|
# Whether to deny by default or allow by default
|
||||||
|
default: allow
|
||||||
|
# Rules regarding their SFTP access.
|
||||||
|
# These rules are relative to the server root.
|
||||||
|
# This acts as a chroot for the server root.
|
||||||
|
# Each path can be an absolute path or a regular expression.
|
||||||
|
rules:
|
||||||
|
"/path/to/file":
|
||||||
|
# Whether the user can read the file over SFTP
|
||||||
|
readable: true
|
||||||
|
# Whether the user can write/modify the file over SFTP
|
||||||
|
writeable: true
|
||||||
|
"/path/to/regex/*":
|
||||||
|
readable: true
|
||||||
|
writeable: false
|
||||||
|
"/path/to/directory/":
|
||||||
|
readable: false
|
||||||
|
writeable: true
|
||||||
|
"/another/example/path":
|
||||||
|
readable: false
|
||||||
|
writeable: false
|
||||||
|
Loading…
Reference in New Issue
Block a user