feat: add firejailed support (#106)
Reviewed-on: #106 Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com> Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
This commit is contained in:
@ -11,7 +11,7 @@
|
|||||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||||
<text x="33.5" y="14">coverage</text>
|
<text x="33.5" y="14">coverage</text>
|
||||||
<text x="86" y="15" fill="#010101" fill-opacity=".3">17.5%</text>
|
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.0%</text>
|
||||||
<text x="86" y="14">17.5%</text>
|
<text x="86" y="14">19.0%</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B |
41
e2e-tests/firejailed_package_test.go
Normal file
41
e2e-tests/firejailed_package_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build e2e
|
||||||
|
|
||||||
|
package e2etests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/efficientgo/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestE2EFirejailedPackage(t *testing.T) {
|
||||||
|
dockerMultipleRun(
|
||||||
|
t,
|
||||||
|
"firejailed-package",
|
||||||
|
COMMON_SYSTEMS,
|
||||||
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
|
defaultPrepare(t, r)
|
||||||
|
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
|
||||||
|
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'")
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
237
internal/build/firejail.go
Normal file
237
internal/build/firejail.go
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
firejailedDir = "/usr/lib/alr/firejailed"
|
||||||
|
defaultDirMode = 0o755
|
||||||
|
defaultScriptMode = 0o755
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidDestination = errors.New("invalid destination path")
|
||||||
|
ErrMissingProfile = errors.New("default profile is missing")
|
||||||
|
ErrEmptyPackageName = errors.New("package name cannot be empty")
|
||||||
|
)
|
||||||
|
|
||||||
|
var binaryDirectories = []string{
|
||||||
|
"/usr/bin/",
|
||||||
|
"/bin/",
|
||||||
|
"/usr/local/bin/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyFirejailIntegration(
|
||||||
|
vars *alrsh.Package,
|
||||||
|
dirs types.Directories,
|
||||||
|
contents []*files.Content,
|
||||||
|
) ([]*files.Content, error) {
|
||||||
|
slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name)
|
||||||
|
|
||||||
|
if err := createFirejailedDirectory(dirs.PkgDir); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create firejailed directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newContents, err := processBinaryFiles(vars, contents, dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to process binary files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(contents, newContents...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirejailedDirectory(pkgDir string) error {
|
||||||
|
firejailedPath := filepath.Join(pkgDir, firejailedDir)
|
||||||
|
return os.MkdirAll(firejailedPath, defaultDirMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) {
|
||||||
|
var newContents []*files.Content
|
||||||
|
|
||||||
|
for _, content := range contents {
|
||||||
|
if content.Type == "dir" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isBinaryFile(content.Destination) {
|
||||||
|
slog.Debug("content not binary file", "content", content)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("process content", "content", content)
|
||||||
|
|
||||||
|
newContent, err := createFirejailedBinary(pkg, content, dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newContent != nil {
|
||||||
|
newContents = append(newContents, newContent...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newContents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBinaryFile(destination string) bool {
|
||||||
|
for _, binDir := range binaryDirectories {
|
||||||
|
if strings.HasPrefix(destination, binDir) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirejailedBinary(
|
||||||
|
pkg *alrsh.Package,
|
||||||
|
content *files.Content,
|
||||||
|
dirs types.Directories,
|
||||||
|
) ([]*files.Content, error) {
|
||||||
|
origFilePath, err := generateFirejailedPath(content.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := pkg.FireJailProfiles.Resolved()
|
||||||
|
sourceProfilePath, ok := profiles[content.Destination]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
sourceProfilePath, ok = profiles["default"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("default profile is missing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath)
|
||||||
|
dest, err := createFirejailProfilePath(content.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to move original binary: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create wrapper script
|
||||||
|
if err := createWrapperScript(content.Source, origFilePath, dest); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := getContentFromPath(dest, dirs.PkgDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bin, err := getContentFromPath(origFilePath, dirs.PkgDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*files.Content{
|
||||||
|
bin,
|
||||||
|
profile,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContentFromPath(path, base string) (*files.Content, error) {
|
||||||
|
absPath := filepath.Join(base, path)
|
||||||
|
|
||||||
|
fi, err := os.Lstat(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &files.Content{
|
||||||
|
Source: absPath,
|
||||||
|
Destination: path,
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSafeName(destination string) (string, error) {
|
||||||
|
cleanPath := strings.TrimPrefix(destination, ".")
|
||||||
|
if cleanPath == "" {
|
||||||
|
return "", fmt.Errorf("invalid destination path: %s", destination)
|
||||||
|
}
|
||||||
|
return strings.ReplaceAll(cleanPath, "/", "_"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateFirejailedPath(destination string) (string, error) {
|
||||||
|
safeName, err := generateSafeName(destination)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(firejailedDir, safeName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProfile(destProfilePath, profilePath string) error {
|
||||||
|
srcFile, err := os.Open(profilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(destProfilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, srcFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return destFile.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWrapperScript(scriptPath, origFilePath, profilePath string) error {
|
||||||
|
scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath)
|
||||||
|
return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirejailProfilePath(binaryPath string) (string, error) {
|
||||||
|
name, err := generateSafeName(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil
|
||||||
|
}
|
316
internal/build/firejail_test.go
Normal file
316
internal/build/firejail_test.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsBinaryFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
destination string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"usr/bin binary", "/usr/bin/test", true},
|
||||||
|
{"bin binary", "/bin/test", true},
|
||||||
|
{"usr/local/bin binary", "/usr/local/bin/test", true},
|
||||||
|
{"lib file", "/usr/lib/test.so", false},
|
||||||
|
{"etc file", "/etc/config", false},
|
||||||
|
{"empty destination", "", false},
|
||||||
|
{"root level file", "./test", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isBinaryFile(tt.destination)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSafeName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
destination string
|
||||||
|
expected string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false},
|
||||||
|
{"bin path", "./bin/test", "_bin_test", false},
|
||||||
|
{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false},
|
||||||
|
{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false},
|
||||||
|
{"empty after trim", ".", "", true},
|
||||||
|
{"empty string", "", "", true},
|
||||||
|
{"only dots", "..", ".", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := generateSafeName(tt.destination)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateWrapperScript(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
origFilePath string
|
||||||
|
profilePath string
|
||||||
|
expectedContent string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"basic wrapper",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_test",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_test.profile",
|
||||||
|
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path with spaces",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_my_app",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_my_app.profile",
|
||||||
|
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
scriptPath := filepath.Join(tmpDir, "wrapper.sh")
|
||||||
|
|
||||||
|
err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.FileExists(t, scriptPath)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(scriptPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedContent, string(content))
|
||||||
|
|
||||||
|
// Check file permissions
|
||||||
|
info, err := os.Stat(scriptPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, os.FileMode(defaultDirMode), info.Mode())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFirejailedBinary(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setupFunc func(string) (*alrsh.Package, *files.Content, types.Directories)
|
||||||
|
expectError bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"successful creation with default profile",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
|
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
|
||||||
|
"": {"default": "default.profile"},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{
|
||||||
|
Source: srcBinary,
|
||||||
|
Destination: "./usr/bin/test-binary",
|
||||||
|
Type: "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"successful creation with specific profile",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "special-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
|
||||||
|
|
||||||
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
|
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
|
||||||
|
|
||||||
|
specialProfile := filepath.Join(scriptDir, "special.profile")
|
||||||
|
os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
|
||||||
|
"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{
|
||||||
|
Source: srcBinary,
|
||||||
|
Destination: "./usr/bin/special-binary",
|
||||||
|
Type: "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"missing default profile",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"default profile is missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile file not found",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid destination path",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
|
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"}
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
pkg, content, dirs := tt.setupFunc(tmpDir)
|
||||||
|
|
||||||
|
err := createFirejailedDirectory(dirs.PkgDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
result, err := createFirejailedBinary(pkg, content, dirs)
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
if tt.errorMsg != "" {
|
||||||
|
assert.Contains(t, err.Error(), tt.errorMsg)
|
||||||
|
}
|
||||||
|
assert.Nil(t, result)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 2)
|
||||||
|
|
||||||
|
binContent := result[0]
|
||||||
|
assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/")
|
||||||
|
assert.FileExists(t, binContent.Source)
|
||||||
|
|
||||||
|
profileContent := result[1]
|
||||||
|
assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/")
|
||||||
|
assert.Contains(t, profileContent.Destination, ".profile")
|
||||||
|
assert.FileExists(t, profileContent.Source)
|
||||||
|
|
||||||
|
assert.FileExists(t, content.Source)
|
||||||
|
wrapperBytes, err := os.ReadFile(content.Source)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
wrapper := string(wrapperBytes)
|
||||||
|
assert.Contains(t, wrapper, "#!/bin/bash")
|
||||||
|
assert.Contains(t, wrapper, "firejail --profile=")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -248,6 +248,16 @@ func buildPkgMetadata(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeContents(contents)
|
||||||
|
|
||||||
|
if vars.FireJailed.Resolved() {
|
||||||
|
contents, err = applyFirejailIntegration(vars, dirs, contents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pkgInfo.Overridables.Contents = contents
|
pkgInfo.Overridables.Contents = contents
|
||||||
|
|
||||||
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
|
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
|
||||||
|
@ -154,6 +154,12 @@ func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents
|
|||||||
return contents, nil
|
return contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeContents(contents []*files.Content) {
|
||||||
|
for _, content := range contents {
|
||||||
|
content.Destination = filepath.Join("/", content.Destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
|
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
|
||||||
|
|
||||||
func getBasePkgInfo(vars *alrsh.Package, input interface {
|
func getBasePkgInfo(vars *alrsh.Package, input interface {
|
||||||
|
@ -220,19 +220,23 @@ msgstr ""
|
|||||||
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/firejail.go:59
|
||||||
|
msgid "Applying FireJail integration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/script_executor.go:145
|
#: internal/build/script_executor.go:145
|
||||||
msgid "Building package metadata"
|
msgid "Building package metadata"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/script_executor.go:275
|
#: internal/build/script_executor.go:285
|
||||||
msgid "Executing prepare()"
|
msgid "Executing prepare()"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/script_executor.go:284
|
#: internal/build/script_executor.go:294
|
||||||
msgid "Executing build()"
|
msgid "Executing build()"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/script_executor.go:313 internal/build/script_executor.go:333
|
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343
|
||||||
msgid "Executing %s()"
|
msgid "Executing %s()"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -5,15 +5,15 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: unnamed project\n"
|
"Project-Id-Version: unnamed project\n"
|
||||||
"PO-Revision-Date: 2025-05-16 20:47+0300\n"
|
"PO-Revision-Date: 2025-06-15 16:05+0300\n"
|
||||||
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
"X-Generator: Gtranslator 48.0\n"
|
"X-Generator: Gtranslator 48.0\n"
|
||||||
|
|
||||||
#: build.go:42
|
#: build.go:42
|
||||||
@ -231,19 +231,23 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
|
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
|
||||||
|
|
||||||
|
#: internal/build/firejail.go:59
|
||||||
|
msgid "Applying FireJail integration"
|
||||||
|
msgstr "Применение интеграции FireJail"
|
||||||
|
|
||||||
#: internal/build/script_executor.go:145
|
#: internal/build/script_executor.go:145
|
||||||
msgid "Building package metadata"
|
msgid "Building package metadata"
|
||||||
msgstr "Сборка метаданных пакета"
|
msgstr "Сборка метаданных пакета"
|
||||||
|
|
||||||
#: internal/build/script_executor.go:275
|
#: internal/build/script_executor.go:285
|
||||||
msgid "Executing prepare()"
|
msgid "Executing prepare()"
|
||||||
msgstr "Выполнение prepare()"
|
msgstr "Выполнение prepare()"
|
||||||
|
|
||||||
#: internal/build/script_executor.go:284
|
#: internal/build/script_executor.go:294
|
||||||
msgid "Executing build()"
|
msgid "Executing build()"
|
||||||
msgstr "Выполнение build()"
|
msgstr "Выполнение build()"
|
||||||
|
|
||||||
#: internal/build/script_executor.go:313 internal/build/script_executor.go:333
|
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343
|
||||||
msgid "Executing %s()"
|
msgid "Executing %s()"
|
||||||
msgstr "Выполнение %s()"
|
msgstr "Выполнение %s()"
|
||||||
|
|
||||||
@ -352,8 +356,8 @@ msgid ""
|
|||||||
"This command is deprecated and would be removed in the future, use \"%s\" "
|
"This command is deprecated and would be removed in the future, use \"%s\" "
|
||||||
"instead!"
|
"instead!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s"
|
"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
|
||||||
"\"!"
|
"\"%s\"!"
|
||||||
|
|
||||||
#: internal/db/db.go:76
|
#: internal/db/db.go:76
|
||||||
msgid "Database version mismatch; resetting"
|
msgid "Database version mismatch; resetting"
|
||||||
|
@ -68,6 +68,9 @@ type Package struct {
|
|||||||
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
|
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
|
||||||
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
|
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
|
||||||
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
|
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
|
||||||
|
|
||||||
|
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-"`
|
||||||
|
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scripts struct {
|
type Scripts struct {
|
||||||
|
Reference in New Issue
Block a user