add BungeeGuard authentication (#1502)

This commit is contained in:
tahmid-23 2022-11-28 00:13:09 -05:00 committed by GitHub
parent 84f4ae040e
commit e5d0a43ba4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 93 additions and 9 deletions

View File

@ -1,12 +1,22 @@
package net.minestom.server.extras.bungee; package net.minestom.server.extras.bungee;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/** /**
* BungeeCord forwarding support. This does not count as a security feature, and you will still be required to manage your firewall. * BungeeCord forwarding support. Enabling BungeeGuard support with {@link #setBungeeGuardTokens(Set)} helps to secure the server,
* but managing your firewall is still recommended.
* <p> * <p>
* Please consider using {@link net.minestom.server.extras.velocity.VelocityProxy} instead. * Please consider using {@link net.minestom.server.extras.velocity.VelocityProxy} instead.
*/ */
public final class BungeeCordProxy { public final class BungeeCordProxy {
private static Set<String> bungeeGuardTokens = null;
private static volatile boolean enabled; private static volatile boolean enabled;
/** /**
@ -24,4 +34,35 @@ public final class BungeeCordProxy {
public static boolean isEnabled() { public static boolean isEnabled() {
return enabled; return enabled;
} }
/**
* Sets the tokens used by BungeeGuard authentication.
* Setting the tokens to a not-null value enables BungeeGuard authentication,
* and setting it to a null value disables BungeeGuard authentication.
*
* @param tokens The new BungeeGuard authentication tokens
*/
public static void setBungeeGuardTokens(@Nullable Set<String> tokens) {
bungeeGuardTokens = tokens;
}
/**
* Checks whether BungeeGuard authentication is enabled.
*
* @return Whether BungeeGuard authentication is enabled
*/
public static boolean isBungeeGuardEnabled() {
return bungeeGuardTokens != null;
}
/**
* Checks whether a token is one of the valid BungeeGuard tokens
*
* @param token The token to test
* @return Whether the token is a valid BungeeGuard token
*/
public static boolean isValidBungeeGuardToken(@NotNull String token) {
return isBungeeGuardEnabled() && bungeeGuardTokens.contains(token);
}
} }

View File

@ -16,6 +16,8 @@ import net.minestom.server.network.player.GameProfile;
import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.network.player.PlayerSocketConnection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
@ -27,14 +29,20 @@ import static net.minestom.server.network.NetworkBuffer.*;
public record HandshakePacket(int protocolVersion, @NotNull String serverAddress, public record HandshakePacket(int protocolVersion, @NotNull String serverAddress,
int serverPort, int nextState) implements ClientPreplayPacket { int serverPort, int nextState) implements ClientPreplayPacket {
private final static Logger LOGGER = LoggerFactory.getLogger(HandshakePacket.class);
/** /**
* Text sent if a player tries to connect with an invalid version of the client * Text sent if a player tries to connect with an invalid version of the client
*/ */
private static final Component INVALID_VERSION_TEXT = Component.text("Invalid Version, please use " + MinecraftServer.VERSION_NAME, NamedTextColor.RED); private static final Component INVALID_VERSION_TEXT = Component.text("Invalid Version, please use " + MinecraftServer.VERSION_NAME, NamedTextColor.RED);
private static final Component INVALID_BUNGEE_FORWARDING = Component.text("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!", NamedTextColor.RED);
/**
* Indicates that a BungeeGuard authentication was invalid due to missing, multiple, or invalid tokens.
*/
private static final Component INVALID_BUNGEE_FORWARDING = Component.text("Invalid connection, please connect through the BungeeCord proxy. If you believe this is an error, contact a server administrator.", NamedTextColor.RED);
public HandshakePacket { public HandshakePacket {
if (serverAddress.length() > (BungeeCordProxy.isEnabled() ? Short.MAX_VALUE : 255)) { if (serverAddress.length() > getMaxHandshakeLength()) {
throw new IllegalArgumentException("Server address too long: " + serverAddress.length()); throw new IllegalArgumentException("Server address too long: " + serverAddress.length());
} }
} }
@ -47,7 +55,7 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress
@Override @Override
public void write(@NotNull NetworkBuffer writer) { public void write(@NotNull NetworkBuffer writer) {
writer.write(VAR_INT, protocolVersion); writer.write(VAR_INT, protocolVersion);
int maxLength = BungeeCordProxy.isEnabled() ? Short.MAX_VALUE : 255; int maxLength = getMaxHandshakeLength();
if (serverAddress.length() > maxLength) { if (serverAddress.length() > maxLength) {
throw new IllegalArgumentException("serverAddress is " + serverAddress.length() + " characters long, maximum allowed is " + maxLength); throw new IllegalArgumentException("serverAddress is " + serverAddress.length() + " characters long, maximum allowed is " + maxLength);
} }
@ -64,6 +72,12 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress
final String[] split = address.split("\00"); final String[] split = address.split("\00");
if (split.length == 3 || split.length == 4) { if (split.length == 3 || split.length == 4) {
boolean hasProperties = split.length == 4;
if (BungeeCordProxy.isBungeeGuardEnabled() && !hasProperties) {
bungeeDisconnect(socketConnection);
return;
}
address = split[0]; address = split[0];
final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1], final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1],
@ -78,7 +92,8 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress
); );
List<GameProfile.Property> properties = new ArrayList<>(); List<GameProfile.Property> properties = new ArrayList<>();
if (split.length == 4) { if (hasProperties) {
boolean foundBungeeGuardToken = false;
final String rawPropertyJson = split[3]; final String rawPropertyJson = split[3];
final JsonArray propertyJson = JsonParser.parseString(rawPropertyJson).getAsJsonArray(); final JsonArray propertyJson = JsonParser.parseString(rawPropertyJson).getAsJsonArray();
for (JsonElement element : propertyJson) { for (JsonElement element : propertyJson) {
@ -92,15 +107,28 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress
final String valueString = value.getAsString(); final String valueString = value.getAsString();
final String signatureString = signature == null ? null : signature.getAsString(); final String signatureString = signature == null ? null : signature.getAsString();
if (BungeeCordProxy.isBungeeGuardEnabled() && nameString.equals("bungeeguard-token")) {
if (foundBungeeGuardToken || !BungeeCordProxy.isValidBungeeGuardToken(valueString)) {
bungeeDisconnect(socketConnection);
return;
}
foundBungeeGuardToken = true;
}
properties.add(new GameProfile.Property(nameString, valueString, signatureString)); properties.add(new GameProfile.Property(nameString, valueString, signatureString));
} }
if (BungeeCordProxy.isBungeeGuardEnabled() && !foundBungeeGuardToken) {
bungeeDisconnect(socketConnection);
return;
}
} }
final GameProfile gameProfile = new GameProfile(playerUuid, "test", properties); final GameProfile gameProfile = new GameProfile(playerUuid, "test", properties);
socketConnection.UNSAFE_setProfile(gameProfile); socketConnection.UNSAFE_setProfile(gameProfile);
} else { } else {
socketConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING)); bungeeDisconnect(socketConnection);
socketConnection.disconnect();
return; return;
} }
} }
@ -117,8 +145,7 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress
connection.setConnectionState(ConnectionState.LOGIN); connection.setConnectionState(ConnectionState.LOGIN);
} else { } else {
// Incorrect client version // Incorrect client version
connection.sendPacket(new LoginDisconnectPacket(INVALID_VERSION_TEXT)); disconnect(connection, INVALID_VERSION_TEXT);
connection.disconnect();
} }
} }
default -> { default -> {
@ -126,4 +153,20 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress
} }
} }
} }
private static int getMaxHandshakeLength() {
// BungeeGuard limits handshake length to 2500 characters, while vanilla limits it to 255
return BungeeCordProxy.isEnabled() ? (BungeeCordProxy.isBungeeGuardEnabled() ? 2500 : Short.MAX_VALUE) : 255;
}
private void disconnect(@NotNull PlayerConnection connection, @NotNull Component reason) {
connection.sendPacket(new LoginDisconnectPacket(reason));
connection.disconnect();
}
private void bungeeDisconnect(@NotNull PlayerConnection connection) {
LOGGER.warn("{} tried to log in without valid BungeeGuard forwarding information.", connection.getIdentifier());
disconnect(connection, INVALID_BUNGEE_FORWARDING);
}
} }