mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-29 16:11:05 +01:00
Add minimum security level validation to the compile step.
Only serves as a one off warning at the moment. To be integrated with compile jobs and expanded upon at a later date. At the moment, this also allows for more verbosity during DMAPI validation.
This commit is contained in:
@@ -68,7 +68,7 @@
|
||||
|
||||
if(cached_json["apiValidateOnly"])
|
||||
TGS_INFO_LOG("Validating API and exiting...")
|
||||
Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = minimum_required_security_level))
|
||||
Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]"))
|
||||
del(world)
|
||||
|
||||
chat_channels_json_path = cached_json["chatChannelsJson"]
|
||||
|
||||
@@ -125,8 +125,8 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param>
|
||||
/// <param name="portToUse">The port to use for API validation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the DMAPI was successfully validated, <see langword="false"/> otherwise</returns>
|
||||
async Task<bool> VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken)
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Verifying DMAPI...");
|
||||
var launchParameters = new DreamDaemonLaunchParameters
|
||||
@@ -156,13 +156,27 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
|
||||
if (!controller.Lifetime.IsCompleted)
|
||||
{
|
||||
logger.LogDebug("API validation timed out!");
|
||||
return false;
|
||||
var validationStatus = controller.ApiValidationStatus;
|
||||
logger.LogTrace("API validation status: {0}", validationStatus);
|
||||
switch (validationStatus)
|
||||
{
|
||||
case ApiValidationStatus.Validated:
|
||||
return;
|
||||
case ApiValidationStatus.NeverValidated:
|
||||
break;
|
||||
case ApiValidationStatus.BadValidationRequest:
|
||||
throw new JobException("Recieved an unrecognized API validation request from DreamDaemon!");
|
||||
case ApiValidationStatus.RequiresSafe:
|
||||
throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!");
|
||||
case ApiValidationStatus.RequiresTrusted:
|
||||
throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!");
|
||||
case ApiValidationStatus.UnaskedValidationRequest:
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus));
|
||||
}
|
||||
}
|
||||
|
||||
var validated = controller.ApiValidated;
|
||||
logger.LogTrace("API valid: {0}", validated);
|
||||
return validated;
|
||||
throw new JobException("DMAPI validation timed out!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,13 +370,18 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
|
||||
var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var apiValidated = exitCode == 0 && await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (exitCode != 0)
|
||||
throw new JobException(String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output));
|
||||
|
||||
if (!apiValidated)
|
||||
await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (JobException)
|
||||
{
|
||||
//server never validated or compile failed
|
||||
await eventConsumer.HandleEvent(EventType.CompileFailure, new List<string> { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false);
|
||||
throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output));
|
||||
throw;
|
||||
}
|
||||
|
||||
logger.LogTrace("Running post compile event...");
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// Status of DMAPI validation
|
||||
/// </summary>
|
||||
enum ApiValidationStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The DMAPI never contacted the server for validation
|
||||
/// </summary>
|
||||
NeverValidated,
|
||||
/// <summary>
|
||||
/// The server was contacted for validation but it was never requested
|
||||
/// </summary>
|
||||
UnaskedValidationRequest,
|
||||
/// <summary>
|
||||
/// The game must be run with a minimum security level of <see cref="Api.Models.DreamDaemonSecurity.Safe"/>
|
||||
/// </summary>
|
||||
RequiresSafe,
|
||||
/// <summary>
|
||||
/// The game must be run with a security level of <see cref="Api.Models.DreamDaemonSecurity.Trusted"/>
|
||||
/// </summary>
|
||||
RequiresTrusted,
|
||||
/// <summary>
|
||||
/// The validation request was malformed
|
||||
/// </summary>
|
||||
BadValidationRequest,
|
||||
/// <summary>
|
||||
/// The DMAPI validated successfully
|
||||
/// </summary>
|
||||
Validated
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
bool TerminationWasRequested { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the DMAPI was validated. This field may only be access once <see cref="IProcessBase.Lifetime"/> completes
|
||||
/// The DMAPI <see cref="Components.Watchdog.ApiValidationStatus"/>
|
||||
/// </summary>
|
||||
bool ApiValidated { get; }
|
||||
ApiValidationStatus ApiValidationStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDmbProvider"/> being used
|
||||
|
||||
@@ -29,13 +29,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ApiValidated
|
||||
public ApiValidationStatus ApiValidationStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Lifetime.IsCompleted)
|
||||
throw new InvalidOperationException("ApiValidated cannot be checked while Lifetime is incomplete!");
|
||||
return apiValidated;
|
||||
return apiValidationStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly ILogger<SessionController> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamDaemonSecurity"/> level the <see cref="process"/> was launched with
|
||||
/// </summary>
|
||||
readonly DreamDaemonSecurity? launchSecurityLevel;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TaskCompletionSource{TResult}"/> <see cref="SetPort(ushort, CancellationToken)"/> waits on when DreamDaemon currently has it's ports closed
|
||||
/// </summary>
|
||||
@@ -151,9 +156,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// If the DMAPI was validated
|
||||
/// The <see cref="ApiValidationStatus"/> for the <see cref="SessionController"/>
|
||||
/// </summary>
|
||||
bool apiValidated;
|
||||
ApiValidationStatus apiValidationStatus;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="process"/> should be kept alive instead
|
||||
@@ -171,8 +176,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="chat">The value of <see cref="chat"/></param>
|
||||
/// <param name="chatJsonTrackingContext">The value of <see cref="chatJsonTrackingContext"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="launchSecurityLevel">The value of <see cref="launchSecurityLevel"/></param>
|
||||
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param>
|
||||
public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger<SessionController> logger, uint? startupTimeout)
|
||||
public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger<SessionController> logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout)
|
||||
{
|
||||
this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid
|
||||
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
|
||||
@@ -183,11 +189,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
this.launchSecurityLevel = launchSecurityLevel;
|
||||
|
||||
interopContext.RegisterHandler(this);
|
||||
|
||||
portClosedForReboot = false;
|
||||
disposed = false;
|
||||
apiValidated = false;
|
||||
apiValidationStatus = ApiValidationStatus.NeverValidated;
|
||||
released = false;
|
||||
|
||||
rebootTcs = new TaskCompletionSource<object>();
|
||||
@@ -289,6 +297,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/////UHHHH
|
||||
logger.LogWarning("DreamDaemon sent new port command without providing it's own!");
|
||||
content = new ErrorMessage { Message = "Missing stringified port as data parameter!" };
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -314,7 +323,30 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
break;
|
||||
case Constants.DMCommandApiValidate:
|
||||
apiValidated = true;
|
||||
if (!launchSecurityLevel.HasValue)
|
||||
{
|
||||
logger.LogWarning("DreamDaemon requested API validation but no intial security level was passed to the session controller!");
|
||||
apiValidationStatus = ApiValidationStatus.UnaskedValidationRequest;
|
||||
content = new ErrorMessage { Message = "Invalid API validation request!" };
|
||||
break;
|
||||
}
|
||||
if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevel) || !Enum.TryParse<DreamDaemonSecurity>(stringMinimumSecurityLevel, out var minimumSecurityLevel))
|
||||
apiValidationStatus = ApiValidationStatus.BadValidationRequest;
|
||||
else
|
||||
switch (minimumSecurityLevel)
|
||||
{
|
||||
case DreamDaemonSecurity.Safe:
|
||||
apiValidationStatus = launchSecurityLevel == DreamDaemonSecurity.Ultrasafe ? ApiValidationStatus.RequiresSafe : ApiValidationStatus.Validated;
|
||||
break;
|
||||
case DreamDaemonSecurity.Ultrasafe:
|
||||
apiValidationStatus = ApiValidationStatus.Validated;
|
||||
break;
|
||||
case DreamDaemonSecurity.Trusted:
|
||||
apiValidationStatus = launchSecurityLevel == DreamDaemonSecurity.Trusted ? ApiValidationStatus.Validated : ApiValidationStatus.RequiresTrusted;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Enum.TryParse failed to validate the DreamDaemonSecurity range!");
|
||||
}
|
||||
break;
|
||||
case Constants.DMCommandWorldReboot:
|
||||
if (ClosePortOnReboot)
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ChatChannelsJson = interopInfo.ChatChannelsJson,
|
||||
ChatCommandsJson = interopInfo.ChatCommandsJson,
|
||||
ServerCommandsJson = interopInfo.ServerCommandsJson,
|
||||
}, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), launchParameters.StartupTimeout);
|
||||
}, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), launchParameters.SecurityLevel, launchParameters.StartupTimeout);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -247,7 +247,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var process = processExecutor.GetProcess(reattachInformation.ProcessId);
|
||||
try
|
||||
{
|
||||
return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), null);
|
||||
return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), null, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user