The NetworkStorage class manages secret keys used for authentication on dedicated servers. It provides logic to retrieve keys configured via various launch flags and constructs HTTP headers necessary for API calls.
using System;
using System.Collections.Generic;
namespace Sandbox;
public static partial class NetworkStorage
{
private const string DedicatedSecretLaunchKey = "network_storage_secret_key";
private static readonly string[] DedicatedSecretLaunchKeys =
{
DedicatedSecretLaunchKey,
"network-storage-secret-key",
"sboxcool_secret_key",
"networkStorageSecretKey",
"sboxcoolSecretKey",
"nsSecretKey",
"ns_secret_key"
};
[ConVar( "network-storage-secret-key", ConVarFlags.Hidden )]
public static string NetworkStorageSecretKeyDashedConVar { get; set; } = "";
[ConVar( "network_storage_secret_key", ConVarFlags.Hidden )]
public static string NetworkStorageSecretKeyConVar { get; set; } = "";
[ConVar( "sboxcool_secret_key", ConVarFlags.Hidden )]
public static string SboxCoolSecretKeyConVar { get; set; } = "";
[ConVar( "networkStorageSecretKey", ConVarFlags.Hidden )]
public static string NetworkStorageSecretKeyCamelConVar { get; set; } = "";
[ConVar( "sboxcoolSecretKey", ConVarFlags.Hidden )]
public static string SboxCoolSecretKeyCamelConVar { get; set; } = "";
[ConVar( "nsSecretKey", ConVarFlags.Hidden )]
public static string NsSecretKeyCamelConVar { get; set; } = "";
[ConVar( "ns_secret_key", ConVarFlags.Hidden )]
public static string NsSecretKeyConVar { get; set; } = "";
private static bool _dedicatedSecretLookupAttempted;
private static string _dedicatedServerSecretKey;
private static string _dedicatedServerSecretKeySource;
private static bool _dedicatedServerSecretKeyRejected;
private static bool _dedicatedPlayerAuthSuppressedLogged;
private static bool _dedicatedSecretMissingLogged;
/// <summary>
/// True when this process is a dedicated server and a runtime endpoint secret key was supplied.
/// The key is never read on clients or listen servers.
/// </summary>
public static bool HasDedicatedServerSecretKey => TryGetDedicatedServerSecretKey( null, out _ );
/// <summary>Where the dedicated endpoint secret key was loaded from, without exposing the key.</summary>
public static string DedicatedServerSecretKeySource
{
get
{
TryGetDedicatedServerSecretKey( null, out _ );
return _dedicatedServerSecretKeySource ?? "none";
}
}
/// <summary>
/// Manually provide a runtime endpoint secret key. This is accepted only on dedicated servers.
/// Prefer the dedicated launch flag: +network_storage_secret_key sbox_sk_...
/// </summary>
public static void ConfigureDedicatedServerSecretKey( string secretKey )
{
if ( !CanUseDedicatedServerSecretKey() )
{
if ( !string.IsNullOrWhiteSpace( secretKey ) && NetworkStorageLogConfig.LogConfig )
Log.Warning( "[NetworkStorage] Ignoring dedicated endpoint secret key because this process is not a dedicated server host." );
return;
}
_dedicatedSecretLookupAttempted = true;
_dedicatedServerSecretKey = NormalizeSecretKey( secretKey );
_dedicatedServerSecretKeySource = string.IsNullOrEmpty( _dedicatedServerSecretKey ) ? "none" : "manual";
_dedicatedServerSecretKeyRejected = false;
}
private static bool TryAddDedicatedServerSecretHeaders( Dictionary<string, string> headers, EndpointReference endpoint )
{
if ( !TryPrepareDedicatedServerSecret( headers, endpoint, out var secretKey ) )
return false;
headers["x-secret-key"] = secretKey;
headers["x-public-key"] = ApiKey ?? "";
TryAddDedicatedSteamIdHeader( headers );
LogDedicatedSecretTransportOnce( "x-secret-key" );
return true;
}
private static bool ShouldUseDedicatedServerSecret( EndpointReference endpoint = null )
{
if ( !TryGetDedicatedServerSecretKey( endpoint, out _ ) )
return false;
if ( CanTransportDedicatedServerSecretSecurely() )
return true;
LogInsecureSecretTransportOnce();
return false;
}
private static bool TryBuildDedicatedStorageHeaders( out Dictionary<string, string> headers )
{
headers = null;
if ( !TryGetDedicatedServerSecretKey( null, out var secretKey ) )
return false;
if ( !CanTransportDedicatedServerSecretSecurely() )
{
LogInsecureSecretTransportOnce();
return false;
}
headers = BuildPublicHeaders();
headers["x-api-key"] = secretKey;
headers["x-public-key"] = ApiKey ?? "";
LogDedicatedSecretTransportOnce( "x-api-key" );
return true;
}
private static void TryAddDedicatedSteamIdHeader( Dictionary<string, string> headers )
{
if ( headers is null ) return;
var steamId = Game.SteamId.ToString();
if ( !string.IsNullOrWhiteSpace( steamId ) && steamId != "0" )
headers["x-steam-id"] = steamId;
}
private static bool TryPrepareDedicatedServerSecret( Dictionary<string, string> headers, EndpointReference endpoint, out string secretKey )
{
secretKey = null;
if ( headers is null || !TryGetDedicatedServerSecretKey( endpoint, out secretKey ) )
return false;
if ( CanTransportDedicatedServerSecretSecurely() )
return true;
LogInsecureSecretTransportOnce();
secretKey = null;
return false;
}
private static bool TryGetDedicatedServerSecretKey( EndpointReference endpoint, out string secretKey )
{
secretKey = null;
if ( !CanUseDedicatedServerSecretKey() )
{
LogIgnoredEndpointUrlSecretOnce( endpoint );
return false;
}
LoadDedicatedServerSecretKey();
if ( _dedicatedServerSecretKeyRejected )
return false;
secretKey = _dedicatedServerSecretKey;
return !string.IsNullOrWhiteSpace( secretKey );
}
private static bool CanUseDedicatedServerSecretKey()
=> IsDedicatedServerHost;
private static bool IsDedicatedServerProcess => Application.IsDedicatedServer;
private static bool IsDedicatedServerHost => IsDedicatedServerProcess && IsHost;
private static string DedicatedServerAuthUnavailableMessage()
{
var suffix = HasDedicatedServerSecretKey
? " Secret keys are only sent to HTTPS or loopback API roots."
: $" Start the server with one of: {SupportedDedicatedSecretLaunchFlagsText}.";
return $"Dedicated servers cannot request s&box player auth tokens.{suffix}";
}
private static string SupportedDedicatedSecretLaunchFlagsText => "+network_storage_secret_key sbox_sk_..., +network-storage-secret-key sbox_sk_..., +sboxcool_secret_key sbox_sk_..., +networkStorageSecretKey sbox_sk_..., +sboxcoolSecretKey sbox_sk_..., +nsSecretKey sbox_sk_..., +ns_secret_key sbox_sk_...";
private static void LogDedicatedSecretMissingOnce()
{
if ( _dedicatedSecretMissingLogged ) return;
_dedicatedSecretMissingLogged = true;
Log.Warning( $"[NetworkStorage] Dedicated server secret key not found. Supported launch flags: {SupportedDedicatedSecretLaunchFlagsText}" );
}
private static bool TryRejectDedicatedServerPlayerAuth( string tag )
{
if ( !IsDedicatedServerProcess )
return false;
LogDedicatedPlayerAuthSuppressedOnce();
RecordEndpointError( tag, "DEDICATED_SECRET_REQUIRED", DedicatedServerAuthUnavailableMessage() );
return true;
}
private static void LogDedicatedPlayerAuthSuppressedOnce()
{
if ( _dedicatedPlayerAuthSuppressedLogged || !NetworkStorageLogConfig.LogTokens ) return;
_dedicatedPlayerAuthSuppressedLogged = true;
Log.Warning( $"[NetworkStorage] {DedicatedServerAuthUnavailableMessage()}" );
}
private static bool CanTransportDedicatedServerSecretSecurely()
{
if ( !Uri.TryCreate( ApiRoot, UriKind.Absolute, out var uri ) )
return false;
if ( string.Equals( uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase ) )
return true;
// Plain http is allowed only to a loopback host. Any other scheme is refused, even when its host is loopback.
return string.Equals( uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase ) && uri.IsLoopback;
}
}