This commit is contained in:
Jordan Brown
2020-04-26 20:02:23 -04:00
10 changed files with 56 additions and 27 deletions
+2
View File
@@ -69,4 +69,6 @@ COPY --from=build /repo/build/tgs.docker.sh tgs.sh
VOLUME ["/config_data", "/tgs_logs", "/app/lib"]
ENV General__ValidInstancePaths__0 /tgs4_instances
ENTRYPOINT ["./tgs.sh"]
+1 -1
View File
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// Channels the Discord bot should listen/announce in
/// </summary>
public List<ChatChannel> Channels { get; set; }
public ICollection<ChatChannel> Channels { get; set; }
/// <summary>
/// Validates <see cref="Channels"/> are correct for the <see cref="Internal.ChatBot.Provider"/>
@@ -459,5 +459,11 @@ namespace Tgstation.Server.Api.Models
/// </summary>
[Description("Encountered merge conflicts while test merging one or more pull requests!")]
RepoTestMergeConflict,
/// <summary>
/// Attempted to create an instance outside of the <see cref="Internal.ServerInformation.ValidInstancePaths"/>.
/// </summary>
[Description("The new instance's path is not under a white-listed path.")]
InstanceNotAtWhitelistedPath,
}
}
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Api.Models.Internal
using System.Collections.Generic;
namespace Tgstation.Server.Api.Models.Internal
{
/// <summary>
/// Base class for <see cref="Models.ServerInformation"/>.
@@ -19,5 +21,10 @@
/// The maximum number of <see cref="Models.User"/>s allowed.
/// </summary>
public uint UserLimit { get; set; }
/// <summary>
/// Limits the locations instances may be created or attached from.
/// </summary>
public ICollection<string> ValidInstancePaths { get; set; }
}
}
@@ -13,11 +13,11 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// The <see cref="TestMerge"/>s active in the <see cref="RevisionInformation"/>
/// </summary>
public List<TestMerge> ActiveTestMerges { get; set; }
public ICollection<TestMerge> ActiveTestMerges { get; set; }
/// <summary>
/// The <see cref="CompileJob"/>s made from the <see cref="RevisionInformation"/>
/// </summary>
public List<CompileJob> CompileJobs { get; set; }
public ICollection<CompileJob> CompileJobs { get; set; }
}
}
@@ -126,7 +126,8 @@ namespace Tgstation.Server.Host.Controllers
ApiVersion = ApiHeaders.Version,
MinimumPasswordLength = generalConfiguration.MinimumPasswordLength,
InstanceLimit = generalConfiguration.InstanceLimit,
UserLimit = generalConfiguration.UserLimit
UserLimit = generalConfiguration.UserLimit,
ValidInstancePaths = generalConfiguration.ValidInstancePaths
});
// if we are using a browser and the control panel, soft redirect to the app page
@@ -151,28 +151,24 @@ namespace Tgstation.Server.Host.Controllers
var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory);
IActionResult CheckInstanceNotChildOf(string conflictingPath)
bool InstanceIsChildOf(string otherPath)
{
if (targetInstancePath.StartsWith(conflictingPath, StringComparison.Ordinal))
{
bool sameLength = targetInstancePath.Length == conflictingPath.Length;
char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(conflictingPath.Length, targetInstancePath.Length - 1)];
if (sameLength
|| dirSeparatorChar == Path.DirectorySeparatorChar
|| dirSeparatorChar == Path.AltDirectorySeparatorChar)
return Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
}
if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal))
return false;
return null;
bool sameLength = targetInstancePath.Length == otherPath.Length;
char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)];
return sameLength
|| dirSeparatorChar == Path.DirectorySeparatorChar
|| dirSeparatorChar == Path.AltDirectorySeparatorChar;
}
var earlyOut = CheckInstanceNotChildOf(installationDirectoryPath);
if (earlyOut != null)
return earlyOut;
ulong countOfOtherInstances = 0;
if (InstanceIsChildOf(installationDirectoryPath))
return Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
// Validate it's not a child of any other instance
IActionResult earlyOut = null;
ulong countOfOtherInstances = 0;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
var newCancellationToken = cts.Token;
@@ -182,9 +178,9 @@ namespace Tgstation.Server.Host.Controllers
otherInstance =>
{
if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
earlyOut = Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
else
earlyOut = earlyOut ?? CheckInstanceNotChildOf(otherInstance.Path);
earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
else if (InstanceIsChildOf(otherInstance.Path))
earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
cts.Cancel();
@@ -201,6 +197,12 @@ namespace Tgstation.Server.Host.Controllers
if (earlyOut != null)
return earlyOut;
// Last test, ensure it's in the list of valid paths
if (!(generalConfiguration.ValidInstancePaths?
.Select(path => NormalizePath(path))
.Any(path => InstanceIsChildOf(path)) ?? true))
return BadRequest(new ErrorMessage(ErrorCode.InstanceNotAtWhitelistedPath));
async Task<bool> DirExistsAndIsNotEmpty()
{
if (!await ioManager.DirectoryExists(model.Path, cancellationToken).ConfigureAwait(false))
+2 -1
View File
@@ -8,7 +8,8 @@
"UseExperimentalWatchdog": false,
"UseBasicWatchdogOnWindows": true,
"UserLimit": 100,
"InstanceLimit": 10
"InstanceLimit": 10,
"ValidInstancePaths": null
},
"FileLogging": {
"Directory": null,
@@ -84,6 +84,15 @@ namespace Tgstation.Server.Tests
Path = testNonEmpty
}, cancellationToken), ErrorCode.InstanceAtExistingPath).ConfigureAwait(false);
// test can't create instance outside of whitelist
await ApiAssert.ThrowsException<ApiConflictException>(() => instanceManagerClient.CreateOrAttach(new Api.Models.Instance
{
Name = "TestInstanceOutsideOfWhitelist",
Path = Path.Combine(testRootPath, "..", Guid.NewGuid().ToString()),
Online = true,
ChatBotLimit = 1
}, cancellationToken), ErrorCode.InstanceNotAtWhitelistedPath);
//test basic move
Directory.Delete(testNonEmpty);
var initialPath = firstTest.Path;
@@ -58,7 +58,7 @@ namespace Tgstation.Server.Tests
Console.WriteLine("WARNING: No GitHub access token configured, test may fail due to rate limits!");
dumpOpenAPISpecpath = !String.IsNullOrEmpty(dumpOpenAPISpecPathEnvVar);
var args = new List<string>()
{
String.Format(CultureInfo.InvariantCulture, "Kestrel:EndPoints:Http:Url={0}", UrlString),
@@ -67,7 +67,8 @@ namespace Tgstation.Server.Tests
String.Format(CultureInfo.InvariantCulture, "Database:DropDatabase={0}", true),
String.Format(CultureInfo.InvariantCulture, "General:SetupWizardMode={0}", SetupWizardMode.Never),
String.Format(CultureInfo.InvariantCulture, "General:InstanceLimit={0}", 10),
String.Format(CultureInfo.InvariantCulture, "General:UserLimit={0}", 150)
String.Format(CultureInfo.InvariantCulture, "General:UserLimit={0}", 150),
String.Format(CultureInfo.InvariantCulture, "General:ValidInstancePaths:0={0}", Directory)
};
if (!String.IsNullOrEmpty(gitHubAccessToken))