3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Add support for HMACed player forwarding data.

This provides a small degree of security but also makes Velocity "secure
by default", especially on shared hosts.
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-08-09 05:24:47 -04:00
Ursprung 254508a5cf
Commit 1f0a4a8228
5 geänderte Dateien mit 92 neuen und 41 gelöschten Zeilen

Datei anzeigen

@ -1,6 +1,6 @@
package com.velocitypowered.proxy.config; package com.velocitypowered.proxy.config;
public enum IPForwardingMode { public enum PlayerInfoForwarding {
NONE, NONE,
LEGACY, LEGACY,
MODERN MODERN

Datei anzeigen

@ -5,6 +5,7 @@ import com.moandjiezana.toml.Toml;
import com.velocitypowered.api.server.Favicon; import com.velocitypowered.api.server.Favicon;
import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.api.util.LegacyChatColorUtils; import com.velocitypowered.api.util.LegacyChatColorUtils;
import io.netty.buffer.ByteBufUtil;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.ComponentSerializers;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -28,7 +29,7 @@ public class VelocityConfiguration {
private final String motd; private final String motd;
private final int showMaxPlayers; private final int showMaxPlayers;
private final boolean onlineMode; private final boolean onlineMode;
private final IPForwardingMode ipForwardingMode; private final PlayerInfoForwarding playerInfoForwardingMode;
private final Map<String, String> servers; private final Map<String, String> servers;
private final List<String> attemptConnectionOrder; private final List<String> attemptConnectionOrder;
private final int compressionThreshold; private final int compressionThreshold;
@ -40,21 +41,25 @@ public class VelocityConfiguration {
private Component motdAsComponent; private Component motdAsComponent;
private Favicon favicon; private Favicon favicon;
private final byte[] forwardingSecret;
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
IPForwardingMode ipForwardingMode, Map<String, String> servers, PlayerInfoForwarding playerInfoForwardingMode, Map<String, String> servers,
List<String> attemptConnectionOrder, int compressionThreshold, List<String> attemptConnectionOrder, int compressionThreshold,
int compressionLevel, boolean queryEnabled, int queryPort) { int compressionLevel, boolean queryEnabled, int queryPort,
byte[] forwardingSecret) {
this.bind = bind; this.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
this.onlineMode = onlineMode; this.onlineMode = onlineMode;
this.ipForwardingMode = ipForwardingMode; this.playerInfoForwardingMode = playerInfoForwardingMode;
this.servers = servers; this.servers = servers;
this.attemptConnectionOrder = attemptConnectionOrder; this.attemptConnectionOrder = attemptConnectionOrder;
this.compressionThreshold = compressionThreshold; this.compressionThreshold = compressionThreshold;
this.compressionLevel = compressionLevel; this.compressionLevel = compressionLevel;
this.queryEnabled = queryEnabled; this.queryEnabled = queryEnabled;
this.queryPort = queryPort; this.queryPort = queryPort;
this.forwardingSecret = forwardingSecret;
} }
public boolean validate() { public boolean validate() {
@ -76,9 +81,15 @@ public class VelocityConfiguration {
logger.info("Proxy is running in offline mode!"); logger.info("Proxy is running in offline mode!");
} }
switch (ipForwardingMode) { switch (playerInfoForwardingMode) {
case NONE: case NONE:
logger.info("IP forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); logger.info("Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs.");
break;
case MODERN:
if (forwardingSecret.length == 0) {
logger.error("You don't have a forwarding secret set.");
valid = false;
}
break; break;
} }
@ -178,8 +189,8 @@ public class VelocityConfiguration {
return onlineMode; return onlineMode;
} }
public IPForwardingMode getIpForwardingMode() { public PlayerInfoForwarding getPlayerInfoForwardingMode() {
return ipForwardingMode; return playerInfoForwardingMode;
} }
public Map<String, String> getServers() { public Map<String, String> getServers() {
@ -202,6 +213,10 @@ public class VelocityConfiguration {
return favicon; return favicon;
} }
public byte[] getForwardingSecret() {
return forwardingSecret;
}
@Override @Override
public String toString() { public String toString() {
return "VelocityConfiguration{" + return "VelocityConfiguration{" +
@ -209,7 +224,7 @@ public class VelocityConfiguration {
", motd='" + motd + '\'' + ", motd='" + motd + '\'' +
", showMaxPlayers=" + showMaxPlayers + ", showMaxPlayers=" + showMaxPlayers +
", onlineMode=" + onlineMode + ", onlineMode=" + onlineMode +
", ipForwardingMode=" + ipForwardingMode + ", playerInfoForwardingMode=" + playerInfoForwardingMode +
", servers=" + servers + ", servers=" + servers +
", attemptConnectionOrder=" + attemptConnectionOrder + ", attemptConnectionOrder=" + attemptConnectionOrder +
", compressionThreshold=" + compressionThreshold + ", compressionThreshold=" + compressionThreshold +
@ -218,6 +233,7 @@ public class VelocityConfiguration {
", queryPort=" + queryPort + ", queryPort=" + queryPort +
", motdAsComponent=" + motdAsComponent + ", motdAsComponent=" + motdAsComponent +
", favicon=" + favicon + ", favicon=" + favicon +
", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) +
'}'; '}';
} }
@ -236,18 +252,22 @@ public class VelocityConfiguration {
} }
} }
byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t")
.getBytes(StandardCharsets.UTF_8);
return new VelocityConfiguration( return new VelocityConfiguration(
toml.getString("bind"), toml.getString("bind", "0.0.0.0:25577"),
toml.getString("motd"), toml.getString("motd", "&3A Velocity Server"),
toml.getLong("show-max-players").intValue(), toml.getLong("show-max-players", 500L).intValue(),
toml.getBoolean("online-mode"), toml.getBoolean("online-mode", true),
IPForwardingMode.valueOf(toml.getString("ip-forwarding").toUpperCase()), PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()),
ImmutableMap.copyOf(servers), ImmutableMap.copyOf(servers),
toml.getTable("servers").getList("try"), toml.getTable("servers").getList("try"),
toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(), toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(),
toml.getTable("advanced").getLong("compression-level", -1L).intValue(), toml.getTable("advanced").getLong("compression-level", -1L).intValue(),
toml.getTable("query").getBoolean("enabled"), toml.getTable("query").getBoolean("enabled", false),
toml.getTable("query").getLong("port", 25577L).intValue()); toml.getTable("query").getLong("port", 25577L).intValue(),
forwardingSecret);
} }
} }
} }

Datei anzeigen

@ -2,7 +2,8 @@ package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.IPForwardingMode; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
@ -17,6 +18,11 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -30,7 +36,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { public void activated() {
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN) { if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN) {
forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> { forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> {
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(),
TextComponent.of("Your server did not send the forwarding request in time. Is it set up correctly?")); TextComponent.of("Your server did not send the forwarding request in time. Is it set up correctly?"));
@ -44,12 +50,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
throw new IllegalStateException("Backend server is online-mode!"); throw new IllegalStateException("Backend server is online-mode!");
} else if (packet instanceof LoginPluginMessage) { } else if (packet instanceof LoginPluginMessage) {
LoginPluginMessage message = (LoginPluginMessage) packet; LoginPluginMessage message = (LoginPluginMessage) packet;
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN && VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration();
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN &&
message.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) { message.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
LoginPluginResponse response = new LoginPluginResponse(); LoginPluginResponse response = new LoginPluginResponse();
response.setSuccess(true); response.setSuccess(true);
response.setId(message.getId()); response.setId(message.getId());
response.setData(createForwardingData(connection.getProxyPlayer().getRemoteAddress().getHostString(), response.setData(createForwardingData(configuration.getForwardingSecret(),
connection.getProxyPlayer().getRemoteAddress().getHostString(),
connection.getProxyPlayer().getProfile())); connection.getProxyPlayer().getProfile()));
connection.getMinecraftConnection().write(response); connection.getMinecraftConnection().write(response);
cancelForwardingCheck(); cancelForwardingCheck();
@ -122,23 +130,43 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
} }
} }
private static ByteBuf createForwardingData(String address, GameProfile profile) { static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) {
ByteBuf buf = Unpooled.buffer(); ByteBuf dataToForward = Unpooled.buffer();
ProtocolUtils.writeString(buf, address); ByteBuf finalData = Unpooled.buffer();
ProtocolUtils.writeUuid(buf, profile.idAsUuid()); try {
ProtocolUtils.writeString(buf, profile.getName()); ProtocolUtils.writeString(dataToForward, address);
ProtocolUtils.writeVarInt(buf, profile.getProperties().size()); ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid());
for (GameProfile.Property property : profile.getProperties()) { ProtocolUtils.writeString(dataToForward, profile.getName());
ProtocolUtils.writeString(buf, property.getName()); ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size());
ProtocolUtils.writeString(buf, property.getValue()); for (GameProfile.Property property : profile.getProperties()) {
String signature = property.getSignature(); ProtocolUtils.writeString(dataToForward, property.getName());
if (signature != null) { ProtocolUtils.writeString(dataToForward, property.getValue());
buf.writeBoolean(true); String signature = property.getSignature();
ProtocolUtils.writeString(buf, signature); if (signature != null) {
} else { dataToForward.writeBoolean(true);
buf.writeBoolean(false); ProtocolUtils.writeString(dataToForward, signature);
} else {
dataToForward.writeBoolean(false);
}
} }
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes());
byte[] sig = mac.doFinal();
finalData.writeBytes(sig);
finalData.writeBytes(dataToForward);
return finalData;
} catch (InvalidKeyException e) {
finalData.release();
throw new RuntimeException("Unable to authenticate data", e);
} catch (NoSuchAlgorithmException e) {
// Should never happen
finalData.release();
throw new AssertionError(e);
} finally {
dataToForward.release();
} }
return buf;
} }
} }

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.proxy.config.IPForwardingMode; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
@ -97,7 +97,7 @@ public class ServerConnection implements MinecraftConnectionAssociation {
Handshake handshake = new Handshake(); Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setNextStatus(StateRegistry.LOGIN_ID);
handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion());
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.LEGACY) { if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
handshake.setServerAddress(createBungeeForwardingAddress()); handshake.setServerAddress(createBungeeForwardingAddress());
} else { } else {
handshake.setServerAddress(serverInfo.getAddress().getHostString()); handshake.setServerAddress(serverInfo.getAddress().getHostString());
@ -111,7 +111,7 @@ public class ServerConnection implements MinecraftConnectionAssociation {
// Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding. // Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding.
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 || if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ||
VelocityServer.getServer().getConfiguration().getIpForwardingMode() != IPForwardingMode.MODERN) { VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() != PlayerInfoForwarding.MODERN) {
ServerLogin login = new ServerLogin(); ServerLogin login = new ServerLogin();
login.setUsername(proxyPlayer.getUsername()); login.setUsername(proxyPlayer.getUsername());
minecraftConnection.write(login); minecraftConnection.write(login);

Datei anzeigen

@ -19,7 +19,10 @@ online-mode = true
# servers using Minecraft 1.12 or lower. # servers using Minecraft 1.12 or lower.
# - "modern": Forward player IPs and UUIDs as part of the login process using Velocity's native # - "modern": Forward player IPs and UUIDs as part of the login process using Velocity's native
# forwarding. Only applicable for Minecraft 1.13 or higher. # forwarding. Only applicable for Minecraft 1.13 or higher.
ip-forwarding = "modern" player-info-forwarding = "modern"
# If you are using modern IP forwarding, configure an unique secret here.
player-info-forwarding-secret = "5up3r53cr3t"
[servers] [servers]
# Configure your servers here. # Configure your servers here.