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:
Ursprung
254508a5cf
Commit
1f0a4a8228
@ -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
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
ProtocolUtils.writeString(dataToForward, profile.getName());
|
||||||
|
ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size());
|
||||||
for (GameProfile.Property property : profile.getProperties()) {
|
for (GameProfile.Property property : profile.getProperties()) {
|
||||||
ProtocolUtils.writeString(buf, property.getName());
|
ProtocolUtils.writeString(dataToForward, property.getName());
|
||||||
ProtocolUtils.writeString(buf, property.getValue());
|
ProtocolUtils.writeString(dataToForward, property.getValue());
|
||||||
String signature = property.getSignature();
|
String signature = property.getSignature();
|
||||||
if (signature != null) {
|
if (signature != null) {
|
||||||
buf.writeBoolean(true);
|
dataToForward.writeBoolean(true);
|
||||||
ProtocolUtils.writeString(buf, signature);
|
ProtocolUtils.writeString(dataToForward, signature);
|
||||||
} else {
|
} else {
|
||||||
buf.writeBoolean(false);
|
dataToForward.writeBoolean(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buf;
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren