- Removed redundant `gin.SetMode(gin.TestMode)` calls from individual test files. - Introduced a centralized `TestMain` function in `testmain_test.go` to set the Gin mode for all tests. - Ensured consistent test environment setup across various handler test files.
595 lines
19 KiB
Go
595 lines
19 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/config"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/internal/services"
|
|
"github.com/Wikid82/charon/backend/internal/util"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type stubPermissionChecker struct{}
|
|
|
|
type fakeNoStatFileInfo struct{}
|
|
|
|
func (fakeNoStatFileInfo) Name() string { return "fake" }
|
|
func (fakeNoStatFileInfo) Size() int64 { return 0 }
|
|
func (fakeNoStatFileInfo) Mode() os.FileMode { return 0 }
|
|
func (fakeNoStatFileInfo) ModTime() time.Time { return time.Time{} }
|
|
func (fakeNoStatFileInfo) IsDir() bool { return false }
|
|
func (fakeNoStatFileInfo) Sys() any { return nil }
|
|
|
|
func (stubPermissionChecker) Check(path, required string) util.PermissionCheck {
|
|
return util.PermissionCheck{
|
|
Path: path,
|
|
Required: required,
|
|
Exists: true,
|
|
Writable: true,
|
|
OwnerUID: 1000,
|
|
OwnerGID: 1000,
|
|
Mode: "0755",
|
|
}
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_GetPermissions_Admin(t *testing.T) {
|
|
|
|
cfg := config.Config{
|
|
DatabasePath: "/app/data/charon.db",
|
|
ConfigRoot: "/config",
|
|
CaddyLogDir: "/var/log/caddy",
|
|
CrowdSecLogDir: "/var/log/crowdsec",
|
|
PluginsDir: "/app/plugins",
|
|
}
|
|
|
|
h := NewSystemPermissionsHandler(cfg, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/system/permissions", http.NoBody)
|
|
|
|
h.GetPermissions(c)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var payload struct {
|
|
Paths []map[string]any `json:"paths"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &payload))
|
|
require.NotEmpty(t, payload.Paths)
|
|
|
|
first := payload.Paths[0]
|
|
require.NotEmpty(t, first["path"])
|
|
require.NotEmpty(t, first["required"])
|
|
require.NotEmpty(t, first["mode"])
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_GetPermissions_NonAdmin(t *testing.T) {
|
|
|
|
cfg := config.Config{}
|
|
h := NewSystemPermissionsHandler(cfg, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "user")
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/system/permissions", http.NoBody)
|
|
|
|
h.GetPermissions(c)
|
|
|
|
require.Equal(t, http.StatusForbidden, w.Code)
|
|
|
|
var payload map[string]string
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &payload))
|
|
require.Equal(t, "permissions_admin_only", payload["error_code"])
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_NonRoot(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("test requires non-root execution")
|
|
}
|
|
|
|
|
|
cfg := config.Config{SingleContainer: true}
|
|
h := NewSystemPermissionsHandler(cfg, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", http.NoBody)
|
|
|
|
h.RepairPermissions(c)
|
|
|
|
require.Equal(t, http.StatusForbidden, w.Code)
|
|
|
|
var payload map[string]string
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &payload))
|
|
require.Equal(t, "permissions_non_root", payload["error_code"])
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_HelperFunctions(t *testing.T) {
|
|
t.Run("normalizePath", func(t *testing.T) {
|
|
clean, code := normalizePath("/tmp/example")
|
|
require.Equal(t, "/tmp/example", clean)
|
|
require.Empty(t, code)
|
|
|
|
clean, code = normalizePath("")
|
|
require.Empty(t, clean)
|
|
require.Equal(t, "permissions_invalid_path", code)
|
|
|
|
clean, code = normalizePath("relative/path")
|
|
require.Empty(t, clean)
|
|
require.Equal(t, "permissions_invalid_path", code)
|
|
})
|
|
|
|
t.Run("containsParentReference", func(t *testing.T) {
|
|
require.True(t, containsParentReference(".."))
|
|
require.True(t, containsParentReference("../secrets"))
|
|
require.True(t, containsParentReference("/var/../etc"))
|
|
require.True(t, containsParentReference("/var/log/.."))
|
|
require.False(t, containsParentReference("/var/log/charon"))
|
|
})
|
|
|
|
t.Run("isWithinAllowlist", func(t *testing.T) {
|
|
allowlist := []string{"/app/data", "/config"}
|
|
require.True(t, isWithinAllowlist("/app/data/charon.db", allowlist))
|
|
require.True(t, isWithinAllowlist("/config/caddy", allowlist))
|
|
require.False(t, isWithinAllowlist("/etc/passwd", allowlist))
|
|
})
|
|
|
|
t.Run("targetMode", func(t *testing.T) {
|
|
require.Equal(t, "0700", targetMode(true, false))
|
|
require.Equal(t, "0770", targetMode(true, true))
|
|
require.Equal(t, "0600", targetMode(false, false))
|
|
require.Equal(t, "0660", targetMode(false, true))
|
|
})
|
|
|
|
t.Run("parseMode", func(t *testing.T) {
|
|
mode, err := parseMode("0640")
|
|
require.NoError(t, err)
|
|
require.Equal(t, os.FileMode(0640), mode)
|
|
|
|
_, err = parseMode("")
|
|
require.Error(t, err)
|
|
|
|
_, err = parseMode("invalid")
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("mapRepairErrorCode", func(t *testing.T) {
|
|
require.Equal(t, "", mapRepairErrorCode(nil))
|
|
require.Equal(t, "permissions_readonly", mapRepairErrorCode(syscall.EROFS))
|
|
require.Equal(t, "permissions_write_denied", mapRepairErrorCode(syscall.EACCES))
|
|
require.Equal(t, "permissions_repair_failed", mapRepairErrorCode(syscall.EINVAL))
|
|
})
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_PathHasSymlink(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
realDir := filepath.Join(root, "real")
|
|
require.NoError(t, os.Mkdir(realDir, 0o750))
|
|
|
|
plainPath := filepath.Join(realDir, "file.txt")
|
|
require.NoError(t, os.WriteFile(plainPath, []byte("ok"), 0o600))
|
|
|
|
hasSymlink, err := pathHasSymlink(plainPath)
|
|
require.NoError(t, err)
|
|
require.False(t, hasSymlink)
|
|
|
|
linkDir := filepath.Join(root, "link")
|
|
require.NoError(t, os.Symlink(realDir, linkDir))
|
|
|
|
symlinkedPath := filepath.Join(linkDir, "file.txt")
|
|
hasSymlink, err = pathHasSymlink(symlinkedPath)
|
|
require.NoError(t, err)
|
|
require.True(t, hasSymlink)
|
|
|
|
_, err = pathHasSymlink(filepath.Join(root, "missing", "file.txt"))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_NewDefaultsCheckerToOSChecker(t *testing.T) {
|
|
h := NewSystemPermissionsHandler(config.Config{}, nil, nil)
|
|
require.NotNil(t, h)
|
|
require.NotNil(t, h.checker)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_DisabledWhenNotSingleContainer(t *testing.T) {
|
|
|
|
h := NewSystemPermissionsHandler(config.Config{SingleContainer: false}, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", bytes.NewBufferString(`{"paths":["/tmp"]}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
h.RepairPermissions(c)
|
|
|
|
require.Equal(t, http.StatusForbidden, w.Code)
|
|
var payload map[string]string
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &payload))
|
|
require.Equal(t, "permissions_repair_disabled", payload["error_code"])
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_InvalidJSON(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("test requires root execution")
|
|
}
|
|
|
|
|
|
root := t.TempDir()
|
|
dataDir := filepath.Join(root, "data")
|
|
require.NoError(t, os.MkdirAll(dataDir, 0o750))
|
|
|
|
cfg := config.Config{
|
|
SingleContainer: true,
|
|
DatabasePath: filepath.Join(dataDir, "charon.db"),
|
|
ConfigRoot: dataDir,
|
|
CaddyLogDir: dataDir,
|
|
CrowdSecLogDir: dataDir,
|
|
PluginsDir: filepath.Join(root, "plugins"),
|
|
}
|
|
|
|
h := NewSystemPermissionsHandler(cfg, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", bytes.NewBufferString(`{"paths":`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
h.RepairPermissions(c)
|
|
|
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_Success(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("test requires root execution")
|
|
}
|
|
|
|
|
|
root := t.TempDir()
|
|
dataDir := filepath.Join(root, "data")
|
|
require.NoError(t, os.MkdirAll(dataDir, 0o750))
|
|
|
|
targetFile := filepath.Join(dataDir, "repair-target.txt")
|
|
require.NoError(t, os.WriteFile(targetFile, []byte("repair"), 0o600))
|
|
|
|
cfg := config.Config{
|
|
SingleContainer: true,
|
|
DatabasePath: filepath.Join(dataDir, "charon.db"),
|
|
ConfigRoot: dataDir,
|
|
CaddyLogDir: dataDir,
|
|
CrowdSecLogDir: dataDir,
|
|
PluginsDir: filepath.Join(root, "plugins"),
|
|
}
|
|
|
|
h := NewSystemPermissionsHandler(cfg, nil, stubPermissionChecker{})
|
|
|
|
body := fmt.Sprintf(`{"paths":[%q],"group_mode":false}`, targetFile)
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", bytes.NewBufferString(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
h.RepairPermissions(c)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var payload struct {
|
|
Paths []permissionsRepairResult `json:"paths"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &payload))
|
|
require.Len(t, payload.Paths, 1)
|
|
require.Equal(t, targetFile, payload.Paths[0].Path)
|
|
require.NotEqual(t, "error", payload.Paths[0].Status)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_NonAdmin(t *testing.T) {
|
|
|
|
h := NewSystemPermissionsHandler(config.Config{SingleContainer: true}, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "user")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", bytes.NewBufferString(`{"paths":["/tmp"]}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
h.RepairPermissions(c)
|
|
|
|
require.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_InvalidJSONWhenRoot(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("test requires root execution")
|
|
}
|
|
|
|
root := t.TempDir()
|
|
dataDir := filepath.Join(root, "data")
|
|
require.NoError(t, os.MkdirAll(dataDir, 0o750))
|
|
|
|
h := NewSystemPermissionsHandler(config.Config{
|
|
SingleContainer: true,
|
|
DatabasePath: filepath.Join(dataDir, "charon.db"),
|
|
ConfigRoot: dataDir,
|
|
CaddyLogDir: dataDir,
|
|
CrowdSecLogDir: dataDir,
|
|
}, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", bytes.NewBufferString(`{"paths":`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
h.RepairPermissions(c)
|
|
|
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_DefaultPathsAndAllowlistRoots(t *testing.T) {
|
|
h := NewSystemPermissionsHandler(config.Config{
|
|
DatabasePath: "/app/data/charon.db",
|
|
ConfigRoot: "/app/config",
|
|
CaddyLogDir: "/var/log/caddy",
|
|
CrowdSecLogDir: "/var/log/crowdsec",
|
|
PluginsDir: "/app/plugins",
|
|
}, nil, stubPermissionChecker{})
|
|
|
|
paths := h.defaultPaths()
|
|
require.Len(t, paths, 11)
|
|
require.Equal(t, "/app/data", paths[0].Path)
|
|
require.Equal(t, "/app/plugins", paths[len(paths)-1].Path)
|
|
|
|
roots := h.allowlistRoots()
|
|
require.Equal(t, []string{"/app/data", "/app/config", "/var/log/caddy", "/var/log/crowdsec"}, roots)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_IsOwnedByFalseWhenSysNotStat(t *testing.T) {
|
|
owned := isOwnedBy(fakeNoStatFileInfo{}, os.Geteuid(), os.Getegid())
|
|
require.False(t, owned)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_IsWithinAllowlist_RelErrorBranch(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
inAllow := filepath.Join(tmp, "a", "b")
|
|
require.NoError(t, os.MkdirAll(inAllow, 0o750))
|
|
|
|
badRoot := string([]byte{'/', 0, 'x'})
|
|
allowed := isWithinAllowlist(inAllow, []string{badRoot, tmp})
|
|
require.True(t, allowed)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_IsWithinAllowlist_AllRelErrorsReturnFalse(t *testing.T) {
|
|
badRoot1 := string([]byte{'/', 0, 'x'})
|
|
badRoot2 := string([]byte{'/', 0, 'y'})
|
|
allowed := isWithinAllowlist("/tmp/some/path", []string{badRoot1, badRoot2})
|
|
require.False(t, allowed)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_LogAudit_PersistsAuditWithUserID(t *testing.T) {
|
|
|
|
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SecurityAudit{}))
|
|
|
|
securitySvc := services.NewSecurityService(db)
|
|
h := NewSystemPermissionsHandler(config.Config{}, securitySvc, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Set("userID", 42)
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/system/permissions", http.NoBody)
|
|
|
|
require.NotPanics(t, func() {
|
|
h.logAudit(c, "permissions_diagnostics", "ok", "", 2)
|
|
})
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_LogAudit_PersistsAuditWithUnknownActor(t *testing.T) {
|
|
|
|
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SecurityAudit{}))
|
|
|
|
securitySvc := services.NewSecurityService(db)
|
|
h := NewSystemPermissionsHandler(config.Config{}, securitySvc, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/system/permissions", http.NoBody)
|
|
|
|
require.NotPanics(t, func() {
|
|
h.logAudit(c, "permissions_diagnostics", "ok", "", 1)
|
|
})
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPath_Branches(t *testing.T) {
|
|
h := NewSystemPermissionsHandler(config.Config{}, nil, stubPermissionChecker{})
|
|
allowRoot := t.TempDir()
|
|
allowlist := []string{allowRoot}
|
|
|
|
t.Run("invalid path", func(t *testing.T) {
|
|
result := h.repairPath("", false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_invalid_path", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("missing path", func(t *testing.T) {
|
|
missingPath := filepath.Join(allowRoot, "missing-file.txt")
|
|
result := h.repairPath(missingPath, false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_missing_path", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("symlink leaf rejected", func(t *testing.T) {
|
|
target := filepath.Join(allowRoot, "target.txt")
|
|
require.NoError(t, os.WriteFile(target, []byte("ok"), 0o600))
|
|
link := filepath.Join(allowRoot, "link.txt")
|
|
require.NoError(t, os.Symlink(target, link))
|
|
|
|
result := h.repairPath(link, false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_symlink_rejected", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("symlink component rejected", func(t *testing.T) {
|
|
realDir := filepath.Join(allowRoot, "real")
|
|
require.NoError(t, os.MkdirAll(realDir, 0o750))
|
|
realFile := filepath.Join(realDir, "file.txt")
|
|
require.NoError(t, os.WriteFile(realFile, []byte("ok"), 0o600))
|
|
|
|
linkDir := filepath.Join(allowRoot, "linkdir")
|
|
require.NoError(t, os.Symlink(realDir, linkDir))
|
|
|
|
result := h.repairPath(filepath.Join(linkDir, "file.txt"), false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_symlink_rejected", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("outside allowlist rejected", func(t *testing.T) {
|
|
outsideFile := filepath.Join(t.TempDir(), "outside.txt")
|
|
require.NoError(t, os.WriteFile(outsideFile, []byte("x"), 0o600))
|
|
|
|
result := h.repairPath(outsideFile, false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_outside_allowlist", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("outside allowlist rejected before stat for missing path", func(t *testing.T) {
|
|
outsideMissing := filepath.Join(t.TempDir(), "missing.txt")
|
|
|
|
result := h.repairPath(outsideMissing, false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_outside_allowlist", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("unsupported type rejected", func(t *testing.T) {
|
|
fifoPath := filepath.Join(allowRoot, "fifo")
|
|
require.NoError(t, syscall.Mkfifo(fifoPath, 0o600))
|
|
|
|
result := h.repairPath(fifoPath, false, allowlist)
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_unsupported_type", result.ErrorCode)
|
|
})
|
|
|
|
t.Run("already correct skipped", func(t *testing.T) {
|
|
filePath := filepath.Join(allowRoot, "already-correct.txt")
|
|
require.NoError(t, os.WriteFile(filePath, []byte("ok"), 0o600))
|
|
|
|
result := h.repairPath(filePath, false, allowlist)
|
|
require.Equal(t, "skipped", result.Status)
|
|
require.Equal(t, "permissions_repair_skipped", result.ErrorCode)
|
|
require.Equal(t, "0600", result.ModeAfter)
|
|
})
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_OSChecker_Check(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("test expects root-owned temp paths in CI")
|
|
}
|
|
|
|
tmp := t.TempDir()
|
|
filePath := filepath.Join(tmp, "check.txt")
|
|
require.NoError(t, os.WriteFile(filePath, []byte("ok"), 0o600))
|
|
|
|
checker := OSChecker{}
|
|
result := checker.Check(filePath, "rw")
|
|
require.Equal(t, filePath, result.Path)
|
|
require.Equal(t, "rw", result.Required)
|
|
require.True(t, result.Exists)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPermissions_InvalidRequestBody_Root(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("test requires root execution")
|
|
}
|
|
|
|
|
|
tmp := t.TempDir()
|
|
dataDir := filepath.Join(tmp, "data")
|
|
require.NoError(t, os.MkdirAll(dataDir, 0o750))
|
|
|
|
h := NewSystemPermissionsHandler(config.Config{
|
|
SingleContainer: true,
|
|
DatabasePath: filepath.Join(dataDir, "charon.db"),
|
|
ConfigRoot: dataDir,
|
|
CaddyLogDir: dataDir,
|
|
CrowdSecLogDir: dataDir,
|
|
PluginsDir: filepath.Join(tmp, "plugins"),
|
|
}, nil, stubPermissionChecker{})
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("role", "admin")
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/system/permissions/repair", bytes.NewBufferString(`{"group_mode":true}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
h.RepairPermissions(c)
|
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPath_LstatInvalidArgument(t *testing.T) {
|
|
h := NewSystemPermissionsHandler(config.Config{}, nil, stubPermissionChecker{})
|
|
allowRoot := t.TempDir()
|
|
|
|
result := h.repairPath("/tmp/\x00invalid", false, []string{allowRoot})
|
|
require.Equal(t, "error", result.Status)
|
|
require.Equal(t, "permissions_outside_allowlist", result.ErrorCode)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_RepairPath_RepairedBranch(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("test requires root execution")
|
|
}
|
|
|
|
h := NewSystemPermissionsHandler(config.Config{}, nil, stubPermissionChecker{})
|
|
allowRoot := t.TempDir()
|
|
targetFile := filepath.Join(allowRoot, "needs-repair.txt")
|
|
require.NoError(t, os.WriteFile(targetFile, []byte("ok"), 0o600))
|
|
|
|
result := h.repairPath(targetFile, true, []string{allowRoot})
|
|
require.Equal(t, "repaired", result.Status)
|
|
require.Equal(t, "0660", result.ModeAfter)
|
|
|
|
info, err := os.Stat(targetFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, os.FileMode(0o660), info.Mode().Perm())
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_NormalizePath_ParentRefBranches(t *testing.T) {
|
|
clean, code := normalizePath("/../etc")
|
|
require.Equal(t, "/etc", clean)
|
|
require.Empty(t, code)
|
|
|
|
clean, code = normalizePath("/var/../etc")
|
|
require.Equal(t, "/etc", clean)
|
|
require.Empty(t, code)
|
|
}
|
|
|
|
func TestSystemPermissionsHandler_NormalizeAllowlist(t *testing.T) {
|
|
allowlist := normalizeAllowlist([]string{"", "/tmp/data/..", "/var/log/charon"})
|
|
require.Equal(t, []string{"/tmp", "/var/log/charon"}, allowlist)
|
|
}
|