Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-12-23 23:00:35 +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:
Ursprung
254508a5cf
Commit
1f0a4a8228
@ -1,6 +1,6 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
public enum IPForwardingMode {
|
||||
public enum PlayerInfoForwarding {
|
||||
NONE,
|
||||
LEGACY,
|
||||
MODERN
|
@ -5,6 +5,7 @@ import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.api.server.Favicon;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.api.util.LegacyChatColorUtils;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -28,7 +29,7 @@ public class VelocityConfiguration {
|
||||
private final String motd;
|
||||
private final int showMaxPlayers;
|
||||
private final boolean onlineMode;
|
||||
private final IPForwardingMode ipForwardingMode;
|
||||
private final PlayerInfoForwarding playerInfoForwardingMode;
|
||||
private final Map<String, String> servers;
|
||||
private final List<String> attemptConnectionOrder;
|
||||
private final int compressionThreshold;
|
||||
@ -40,21 +41,25 @@ public class VelocityConfiguration {
|
||||
private Component motdAsComponent;
|
||||
private Favicon favicon;
|
||||
|
||||
private final byte[] forwardingSecret;
|
||||
|
||||
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,
|
||||
int compressionLevel, boolean queryEnabled, int queryPort) {
|
||||
int compressionLevel, boolean queryEnabled, int queryPort,
|
||||
byte[] forwardingSecret) {
|
||||
this.bind = bind;
|
||||
this.motd = motd;
|
||||
this.showMaxPlayers = showMaxPlayers;
|
||||
this.onlineMode = onlineMode;
|
||||
this.ipForwardingMode = ipForwardingMode;
|
||||
this.playerInfoForwardingMode = playerInfoForwardingMode;
|
||||
this.servers = servers;
|
||||
this.attemptConnectionOrder = attemptConnectionOrder;
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.queryEnabled = queryEnabled;
|
||||
this.queryPort = queryPort;
|
||||
this.forwardingSecret = forwardingSecret;
|
||||
}
|
||||
|
||||
public boolean validate() {
|
||||
@ -76,9 +81,15 @@ public class VelocityConfiguration {
|
||||
logger.info("Proxy is running in offline mode!");
|
||||
}
|
||||
|
||||
switch (ipForwardingMode) {
|
||||
switch (playerInfoForwardingMode) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -178,8 +189,8 @@ public class VelocityConfiguration {
|
||||
return onlineMode;
|
||||
}
|
||||
|
||||
public IPForwardingMode getIpForwardingMode() {
|
||||
return ipForwardingMode;
|
||||
public PlayerInfoForwarding getPlayerInfoForwardingMode() {
|
||||
return playerInfoForwardingMode;
|
||||
}
|
||||
|
||||
public Map<String, String> getServers() {
|
||||
@ -202,6 +213,10 @@ public class VelocityConfiguration {
|
||||
return favicon;
|
||||
}
|
||||
|
||||
public byte[] getForwardingSecret() {
|
||||
return forwardingSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityConfiguration{" +
|
||||
@ -209,7 +224,7 @@ public class VelocityConfiguration {
|
||||
", motd='" + motd + '\'' +
|
||||
", showMaxPlayers=" + showMaxPlayers +
|
||||
", onlineMode=" + onlineMode +
|
||||
", ipForwardingMode=" + ipForwardingMode +
|
||||
", playerInfoForwardingMode=" + playerInfoForwardingMode +
|
||||
", servers=" + servers +
|
||||
", attemptConnectionOrder=" + attemptConnectionOrder +
|
||||
", compressionThreshold=" + compressionThreshold +
|
||||
@ -218,6 +233,7 @@ public class VelocityConfiguration {
|
||||
", queryPort=" + queryPort +
|
||||
", motdAsComponent=" + motdAsComponent +
|
||||
", 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(
|
||||
toml.getString("bind"),
|
||||
toml.getString("motd"),
|
||||
toml.getLong("show-max-players").intValue(),
|
||||
toml.getBoolean("online-mode"),
|
||||
IPForwardingMode.valueOf(toml.getString("ip-forwarding").toUpperCase()),
|
||||
toml.getString("bind", "0.0.0.0:25577"),
|
||||
toml.getString("motd", "&3A Velocity Server"),
|
||||
toml.getLong("show-max-players", 500L).intValue(),
|
||||
toml.getBoolean("online-mode", true),
|
||||
PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()),
|
||||
ImmutableMap.copyOf(servers),
|
||||
toml.getTable("servers").getList("try"),
|
||||
toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(),
|
||||
toml.getTable("advanced").getLong("compression-level", -1L).intValue(),
|
||||
toml.getTable("query").getBoolean("enabled"),
|
||||
toml.getTable("query").getLong("port", 25577L).intValue());
|
||||
toml.getTable("query").getBoolean("enabled", false),
|
||||
toml.getTable("query").getLong("port", 25577L).intValue(),
|
||||
forwardingSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
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.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
@ -17,6 +18,11 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
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.TimeUnit;
|
||||
|
||||
@ -30,7 +36,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN) {
|
||||
if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN) {
|
||||
forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> {
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(),
|
||||
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!");
|
||||
} else if (packet instanceof LoginPluginMessage) {
|
||||
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)) {
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(true);
|
||||
response.setId(message.getId());
|
||||
response.setData(createForwardingData(connection.getProxyPlayer().getRemoteAddress().getHostString(),
|
||||
response.setData(createForwardingData(configuration.getForwardingSecret(),
|
||||
connection.getProxyPlayer().getRemoteAddress().getHostString(),
|
||||
connection.getProxyPlayer().getProfile()));
|
||||
connection.getMinecraftConnection().write(response);
|
||||
cancelForwardingCheck();
|
||||
@ -122,23 +130,43 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(String address, GameProfile profile) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ProtocolUtils.writeString(buf, address);
|
||||
ProtocolUtils.writeUuid(buf, profile.idAsUuid());
|
||||
ProtocolUtils.writeString(buf, profile.getName());
|
||||
ProtocolUtils.writeVarInt(buf, profile.getProperties().size());
|
||||
for (GameProfile.Property property : profile.getProperties()) {
|
||||
ProtocolUtils.writeString(buf, property.getName());
|
||||
ProtocolUtils.writeString(buf, property.getValue());
|
||||
String signature = property.getSignature();
|
||||
if (signature != null) {
|
||||
buf.writeBoolean(true);
|
||||
ProtocolUtils.writeString(buf, signature);
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) {
|
||||
ByteBuf dataToForward = Unpooled.buffer();
|
||||
ByteBuf finalData = Unpooled.buffer();
|
||||
try {
|
||||
ProtocolUtils.writeString(dataToForward, address);
|
||||
ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid());
|
||||
ProtocolUtils.writeString(dataToForward, profile.getName());
|
||||
ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size());
|
||||
for (GameProfile.Property property : profile.getProperties()) {
|
||||
ProtocolUtils.writeString(dataToForward, property.getName());
|
||||
ProtocolUtils.writeString(dataToForward, property.getValue());
|
||||
String signature = property.getSignature();
|
||||
if (signature != null) {
|
||||
dataToForward.writeBoolean(true);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
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.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
@ -97,7 +97,7 @@ public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
Handshake handshake = new Handshake();
|
||||
handshake.setNextStatus(StateRegistry.LOGIN_ID);
|
||||
handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion());
|
||||
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.LEGACY) {
|
||||
if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
|
||||
handshake.setServerAddress(createBungeeForwardingAddress());
|
||||
} else {
|
||||
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.
|
||||
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ||
|
||||
VelocityServer.getServer().getConfiguration().getIpForwardingMode() != IPForwardingMode.MODERN) {
|
||||
VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() != PlayerInfoForwarding.MODERN) {
|
||||
ServerLogin login = new ServerLogin();
|
||||
login.setUsername(proxyPlayer.getUsername());
|
||||
minecraftConnection.write(login);
|
||||
|
@ -19,7 +19,10 @@ online-mode = true
|
||||
# servers using Minecraft 1.12 or lower.
|
||||
# - "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.
|
||||
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]
|
||||
# Configure your servers here.
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren