1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
package apparmor
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/google/uuid"
"github.com/lxc/incus/v6/internal/server/sys"
internalUtil "github.com/lxc/incus/v6/internal/util"
"github.com/lxc/incus/v6/shared/revert"
)
// ArchiveWrapper is used as a RunWrapper in the rsync package.
func ArchiveWrapper(sysOS *sys.OS, cmd *exec.Cmd, output string, allowedCmds []string) (func(), error) {
if !sysOS.AppArmorAvailable {
return func() {}, nil
}
reverter := revert.New()
defer reverter.Fail()
// Load the profile.
profileName, err := archiveProfileLoad(sysOS, output, allowedCmds)
if err != nil {
return nil, fmt.Errorf("Failed to load apparmor profile: %w", err)
}
reverter.Add(func() { _ = deleteProfile(sysOS, profileName, profileName) })
// Resolve aa-exec.
execPath, err := exec.LookPath("aa-exec")
if err != nil {
return nil, err
}
// Override the command.
newArgs := []string{"aa-exec", "-p", profileName}
newArgs = append(newArgs, cmd.Args...)
cmd.Args = newArgs
cmd.Path = execPath
// All done, setup a cleanup function and disarm reverter.
cleanup := func() {
_ = deleteProfile(sysOS, profileName, profileName)
}
reverter.Success()
return cleanup, nil
}
func archiveProfileLoad(sysOS *sys.OS, output string, allowedCommandPaths []string) (string, error) {
reverter := revert.New()
defer reverter.Fail()
// Generate a temporary profile name.
name := profileName("archive", uuid.New().String())
profilePath := filepath.Join(aaPath, "profiles", name)
// Generate the profile
content, err := archiveProfile(name, output, allowedCommandPaths)
if err != nil {
return "", err
}
// Write it to disk.
err = os.WriteFile(profilePath, []byte(content), 0o600)
if err != nil {
return "", err
}
reverter.Add(func() { os.Remove(profilePath) })
// Load it.
err = loadProfile(sysOS, name)
if err != nil {
return "", err
}
reverter.Success()
return name, nil
}
// archiveProfile generates the AppArmor profile template from the given destination path.
func archiveProfile(name string, outputPath string, allowedCommandPaths []string) (string, error) {
// Attempt to deref all paths.
outputPathFull, err := filepath.EvalSymlinks(outputPath)
if err != nil {
outputPathFull = outputPath // Use requested path if cannot resolve it.
}
backupsPath := internalUtil.VarPath("backups")
backupsPathFull, err := filepath.EvalSymlinks(backupsPath)
if err == nil {
backupsPath = backupsPathFull
}
imagesPath := internalUtil.VarPath("images")
imagesPathFull, err := filepath.EvalSymlinks(imagesPath)
if err == nil {
imagesPath = imagesPathFull
}
derefCommandPaths := make([]string, len(allowedCommandPaths))
for i, cmd := range allowedCommandPaths {
cmdPath, err := exec.LookPath(cmd)
if err == nil {
cmd = cmdPath
}
cmdFull, err := filepath.EvalSymlinks(cmd)
if err == nil {
derefCommandPaths[i] = cmdFull
} else {
derefCommandPaths[i] = cmd
}
}
// Render the profile.
var sb *strings.Builder = &strings.Builder{}
err = archiveProfileTpl.Execute(sb, map[string]any{
"name": name,
"outputPath": outputPathFull, // Use deferenced path in AppArmor profile.
"backupsPath": backupsPath,
"imagesPath": imagesPath,
"allowedCommandPaths": derefCommandPaths,
})
if err != nil {
return "", err
}
return sb.String(), nil
}
|