2 Commits

Author SHA1 Message Date
9efebbc02a Исправление функционала создания дирректорий для работы ALR
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m11s
Create Release / changelog (push) Successful in 3m11s
2025-09-21 15:31:51 +03:00
ef41d682a1 Исправление функционала повышения привилегий
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m12s
Create Release / changelog (push) Successful in 3m8s
2025-09-21 15:04:42 +03:00
6 changed files with 116 additions and 88 deletions

View File

@@ -63,7 +63,7 @@ func BuildCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil { if err := utils.CheckUserPrivileges(); err != nil {
return err return err
} }

32
fix.go
View File

@@ -131,22 +131,22 @@ func FixCmd() *cli.Command {
} }
} }
// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 775 // Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 2775
err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o775) err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o2775)
if err != nil { if err != nil {
slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err) slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err)
} }
// Создаем каталог dl с правами для группы wheel // Создаем каталог dl с правами для группы wheel
dlDir := filepath.Join(tmpDir, "dl") dlDir := filepath.Join(tmpDir, "dl")
err = utils.EnsureTempDirWithRootOwner(dlDir, 0o775) err = utils.EnsureTempDirWithRootOwner(dlDir, 0o2775)
if err != nil { if err != nil {
slog.Warn(gotext.Get("Unable to create download directory"), "error", err) slog.Warn(gotext.Get("Unable to create download directory"), "error", err)
} }
// Создаем каталог pkgs с правами для группы wheel // Создаем каталог pkgs с правами для группы wheel
pkgsDir := filepath.Join(tmpDir, "pkgs") pkgsDir := filepath.Join(tmpDir, "pkgs")
err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o775) err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o2775)
if err != nil { if err != nil {
slog.Warn(gotext.Get("Unable to create packages directory"), "error", err) slog.Warn(gotext.Get("Unable to create packages directory"), "error", err)
} }
@@ -158,7 +158,8 @@ func FixCmd() *cli.Command {
// Проверяем, есть ли файлы в директории // Проверяем, есть ли файлы в директории
entries, err := os.ReadDir(tmpDir) entries, err := os.ReadDir(tmpDir)
if err == nil && len(entries) > 0 { if err == nil && len(entries) > 0 {
fixCmd := execWithPrivileges("chown", "-R", "root:wheel", tmpDir) group := utils.GetPrivilegedGroup()
fixCmd := execWithPrivileges("chown", "-R", "root:"+group, tmpDir)
if fixErr := fixCmd.Run(); fixErr != nil { if fixErr := fixCmd.Run(); fixErr != nil {
slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr) slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr)
} }
@@ -172,28 +173,13 @@ func FixCmd() *cli.Command {
slog.Info(gotext.Get("Rebuilding cache")) slog.Info(gotext.Get("Rebuilding cache"))
// Пробуем создать директорию кэша // Создаем директорию кэша с правильными правами
err = os.MkdirAll(paths.CacheDir, 0o775) slog.Info(gotext.Get("Creating cache directory"))
err = utils.EnsureTempDirWithRootOwner(paths.CacheDir, 0o2775)
if err != nil { if err != nil {
// Если не получилось, пробуем через sudo с правильными правами для группы wheel
slog.Info(gotext.Get("Creating cache directory with sudo"))
sudoCmd := execWithPrivileges("mkdir", "-p", paths.CacheDir)
if sudoErr := sudoCmd.Run(); sudoErr != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
} }
// Устанавливаем права 775 и группу wheel
chmodCmd := execWithPrivileges("chmod", "775", paths.CacheDir)
if chmodErr := chmodCmd.Run(); chmodErr != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory permissions"), chmodErr)
}
chgrpCmd := execWithPrivileges("chgrp", "wheel", paths.CacheDir)
if chgrpErr := chgrpCmd.Run(); chgrpErr != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory group"), chgrpErr)
}
}
deps, err = appbuilder. deps, err = appbuilder.
New(ctx). New(ctx).
WithConfig(). WithConfig().

View File

@@ -62,11 +62,9 @@ func (d *Database) Connect() error {
dbDir := filepath.Dir(dsn) dbDir := filepath.Dir(dsn)
if _, err := os.Stat(dbDir); err != nil { if _, err := os.Stat(dbDir); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
// Директория не существует - пытаемся создать // Директория не существует - не пытаемся создать
if mkErr := os.MkdirAll(dbDir, 0775); mkErr != nil { // Пользователь должен использовать alr fix для создания системных каталогов
// Не смогли создать - вернём ошибку, пользователь должен использовать alr fix return fmt.Errorf("cache directory does not exist, please run 'sudo alr fix' to create it")
return fmt.Errorf("cache directory does not exist, please run 'alr fix' to create it: %w", mkErr)
}
} else { } else {
return fmt.Errorf("failed to check database directory: %w", err) return fmt.Errorf("failed to check database directory: %w", err)
} }

View File

@@ -19,7 +19,6 @@ package utils
import ( import (
"os" "os"
"os/exec" "os/exec"
"os/user"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@@ -33,40 +32,10 @@ func IsNotRoot() bool {
return os.Getuid() != 0 return os.Getuid() != 0
} }
// EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel) // EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel/sudo)
// DEPRECATED: используйте CheckUserPrivileges() из utils.go
func EnuseIsPrivilegedGroupMember() error { func EnuseIsPrivilegedGroupMember() error {
// В CI пропускаем проверку группы wheel return CheckUserPrivileges()
if os.Getenv("CI") == "true" {
return nil
}
// Если пользователь root, пропускаем проверку
if os.Geteuid() == 0 {
return nil
}
currentUser, err := user.Current()
if err != nil {
return err
}
privilegedGroup := GetPrivilegedGroup()
group, err := user.LookupGroup(privilegedGroup)
if err != nil {
return err
}
groups, err := currentUser.GroupIds()
if err != nil {
return err
}
for _, gid := range groups {
if gid == group.Gid {
return nil
}
}
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", privilegedGroup), nil)
} }
func RootNeededAction(f cli.ActionFunc) cli.ActionFunc { func RootNeededAction(f cli.ActionFunc) cli.ActionFunc {

View File

@@ -17,8 +17,10 @@
package utils package utils
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"os/user"
"strings" "strings"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -28,23 +30,23 @@ func NoNewPrivs() error {
return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
} }
// EnsureTempDirWithRootOwner создает каталог в /tmp/alr с правами для привилегированной группы // EnsureTempDirWithRootOwner создает каталог в /tmp/alr или /var/cache/alr с правами для привилегированной группы
// Все каталоги в /tmp/alr принадлежат root:привилегированная_группа с правами 775 // Все каталоги в /tmp/alr и /var/cache/alr принадлежат root:привилегированная_группа с правами 2775
// Для других каталогов использует стандартные права // Для других каталогов использует стандартные права
func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
if strings.HasPrefix(path, "/tmp/alr") { needsElevation := strings.HasPrefix(path, "/tmp/alr") || strings.HasPrefix(path, "/var/cache/alr")
// Сначала создаем директорию обычным способом
err := os.MkdirAll(path, mode)
if err != nil {
return err
}
if needsElevation {
// В CI или если мы уже root, не нужно использовать sudo // В CI или если мы уже root, не нужно использовать sudo
isRoot := os.Geteuid() == 0 isRoot := os.Geteuid() == 0
isCI := os.Getenv("CI") == "true" isCI := os.Getenv("CI") == "true"
// В CI создаем директории с обычными правами // В CI создаем директории с обычными правами
if isCI { if isCI {
err := os.MkdirAll(path, mode)
if err != nil {
return err
}
// В CI не используем группу wheel и не меняем права // В CI не используем группу wheel и не меняем права
// Устанавливаем базовые права 777 для временных каталогов // Устанавливаем базовые права 777 для временных каталогов
chmodCmd := exec.Command("chmod", "777", path) chmodCmd := exec.Command("chmod", "777", path)
@@ -56,32 +58,44 @@ func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
permissions := "2775" permissions := "2775"
group := GetPrivilegedGroup() group := GetPrivilegedGroup()
var chmodCmd, chownCmd *exec.Cmd var mkdirCmd, chmodCmd, chownCmd *exec.Cmd
if isRoot { if isRoot {
// Выполняем команды напрямую без sudo // Выполняем команды напрямую без sudo
mkdirCmd = exec.Command("mkdir", "-p", path)
chmodCmd = exec.Command("chmod", permissions, path) chmodCmd = exec.Command("chmod", permissions, path)
chownCmd = exec.Command("chown", "root:"+group, path) chownCmd = exec.Command("chown", "root:"+group, path)
} else { } else {
// Используем sudo для обычных пользователей // Используем sudo для всех операций с привилегированными каталогами
mkdirCmd = exec.Command("sudo", "mkdir", "-p", path)
chmodCmd = exec.Command("sudo", "chmod", permissions, path) chmodCmd = exec.Command("sudo", "chmod", permissions, path)
chownCmd = exec.Command("sudo", "chown", "root:"+group, path) chownCmd = exec.Command("sudo", "chown", "root:"+group, path)
} }
// Устанавливаем права с setgid битом // Создаем директорию через sudo если нужно
err = chmodCmd.Run() err := mkdirCmd.Run()
if err != nil { if err != nil {
// Для root игнорируем ошибки, если группа не существует // Игнорируем ошибку если директория уже существует
if !isRoot { if !isRoot {
return err // Проверяем существует ли директория
if _, statErr := os.Stat(path); statErr != nil {
return fmt.Errorf("не удалось создать директорию %s: %w", path, err)
}
} }
} }
// Устанавливаем владельца root:wheel // Устанавливаем права с setgid битом для наследования группы
err = chmodCmd.Run()
if err != nil {
if !isRoot {
return fmt.Errorf("не удалось установить права на %s: %w", path, err)
}
}
// Устанавливаем владельца root:группа
err = chownCmd.Run() err = chownCmd.Run()
if err != nil { if err != nil {
// Для root игнорируем ошибки, если группа не существует
if !isRoot { if !isRoot {
return err return fmt.Errorf("не удалось установить владельца на %s: %w", path, err)
} }
} }
@@ -91,3 +105,60 @@ func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
// Для остальных каталогов обычное создание // Для остальных каталогов обычное создание
return os.MkdirAll(path, mode) return os.MkdirAll(path, mode)
} }
// IsUserInGroup проверяет, состоит ли пользователь в указанной группе
func IsUserInGroup(username, groupname string) bool {
u, err := user.Lookup(username)
if err != nil {
return false
}
groups, err := u.GroupIds()
if err != nil {
return false
}
targetGroup, err := user.LookupGroup(groupname)
if err != nil {
return false
}
for _, gid := range groups {
if gid == targetGroup.Gid {
return true
}
}
return false
}
// CheckUserPrivileges проверяет, что пользователь имеет необходимые привилегии для работы с ALR
// Пользователь должен быть root или состоять в группе wheel/sudo
func CheckUserPrivileges() error {
// Если пользователь root - все в порядке
if os.Geteuid() == 0 {
return nil
}
// В CI не проверяем привилегии
if os.Getenv("CI") == "true" {
return nil
}
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("не удалось получить информацию о текущем пользователе: %w", err)
}
privilegedGroup := GetPrivilegedGroup()
// Проверяем членство в привилегированной группе
if !IsUserInGroup(currentUser.Username, privilegedGroup) {
return fmt.Errorf("пользователь %s не имеет необходимых привилегий для работы с ALR.\n"+
"Для работы с ALR необходимо быть пользователем root или состоять в группе %s.\n"+
"Выполните команду: sudo usermod -a -G %s %s\n"+
"Затем перезайдите в систему или выполните: newgrp %s",
currentUser.Username, privilegedGroup, privilegedGroup, currentUser.Username, privilegedGroup)
}
return nil
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
) )
func RefreshCmd() *cli.Command { func RefreshCmd() *cli.Command {
@@ -29,6 +30,9 @@ func RefreshCmd() *cli.Command {
Usage: gotext.Get("Pull all repositories that have changed"), Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"}, Aliases: []string{"ref"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.CheckUserPrivileges(); err != nil {
return err
}
ctx := c.Context ctx := c.Context