feat(api): Add authenticated Caddy management API
All checks were successful
Caddy Manager CI build / docker (push) Successful in 4m24s

This commit is contained in:
2026-07-26 18:34:02 +07:00
parent a94abca127
commit 9f7cb79bad
9 changed files with 430 additions and 0 deletions

View File

@@ -436,6 +436,37 @@ public class CaddyServiceTests : IDisposable
File.Exists(filePath).Should().BeTrue();
}
/// <summary>
/// Tests that the Caddy service refuses to save configurations whose file name escapes the configuration directory.
/// Setup: Provides save requests with traversal segments and directory separators in the file name.
/// Expectation: The service should reject the request and write nothing outside the configuration directory, so an untrusted caller such as the HTTP API cannot write arbitrary files on the host.
/// </summary>
[Theory]
[InlineData("../escape")]
[InlineData("../../etc/escape")]
[InlineData("sub/escape")]
[InlineData("sub\\escape")]
public void SaveCaddyConfiguration_WithFileNameEscapingConfigDir_ReturnsFailureAndWritesNothing(string fileName)
{
// Arrange
var request = new CaddySaveConfigurationRequest
{
FileName = fileName,
Content = TestHelper.SampleCaddyfiles.SimpleReverseProxy,
IsNew = true
};
var escapedPath = Path.GetFullPath(Path.Combine(_tempConfigDir, $"{fileName}.caddy"));
// Act
var result = _service.SaveCaddyConfiguration(request);
// Assert
result.Success.Should().BeFalse();
result.Message.Should().Be("The configuration file name contains invalid characters");
File.Exists(escapedPath).Should().BeFalse();
Directory.GetFiles(_tempConfigDir).Should().BeEmpty();
}
#endregion
#region SaveCaddyGlobalConfiguration Tests
@@ -755,6 +786,37 @@ public class CaddyServiceTests : IDisposable
result.DeletedConfigurations.Should().BeEmpty();
}
/// <summary>
/// Tests that the Caddy service refuses to delete configurations whose file name escapes the configuration directory.
/// Setup: Creates a file outside the configuration directory and asks the service to delete it through a traversal file name.
/// Expectation: The service should report the name as failed and leave the outside file untouched, so an untrusted caller such as the HTTP API cannot delete arbitrary files on the host.
/// </summary>
[Fact]
public void DeleteCaddyConfigurations_WithFileNameEscapingConfigDir_ReportsFailureAndDeletesNothing()
{
// Arrange
var outsideDir = TestHelper.CreateTempDirectory();
try
{
var outsidePath = Path.Combine(outsideDir, "victim.caddy");
File.WriteAllText(outsidePath, "content");
var traversalName = Path.Combine("..", Path.GetFileName(outsideDir), "victim");
// Act
var result = _service.DeleteCaddyConfigurations([traversalName]);
// Assert
result.Success.Should().BeFalse();
result.Message.Should().Contain(traversalName);
result.DeletedConfigurations.Should().BeEmpty();
File.Exists(outsidePath).Should().BeTrue();
}
finally
{
TestHelper.CleanupDirectory(outsideDir);
}
}
#endregion
#region GetCaddyConfigurationInfo Tests