blob: 2e00a21cafebe0bb140ed3c8f5516d54bc727875 [file] [edit]
//go:build linux
package daemon
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/containerd/log"
"github.com/moby/moby/v2/daemon/internal/rootless"
aaprofile "github.com/moby/profiles/apparmor"
)
// Define constants for native driver
const (
unconfinedAppArmorProfile = "unconfined"
defaultAppArmorProfile = "docker-default"
)
// DefaultApparmorProfile returns the name of the default apparmor profile
func DefaultApparmorProfile() string {
if appArmorSupported() {
return defaultAppArmorProfile
}
return ""
}
type appArmorProfileData struct {
Abi string
Name string
DaemonProfile string
Imports []string
InnerImports []string
}
func (daemon *Daemon) loadDefaultAppArmorProfileIfMissing() error {
if !defaultAppArmorProfileSupported() {
return nil
}
loaded, err := aaprofile.IsLoaded(defaultAppArmorProfile)
if err != nil {
return fmt.Errorf("Could not check if %s AppArmor profile was loaded: %s", defaultAppArmorProfile, err)
}
if loaded {
return nil
}
return daemon.installDefaultAppArmorProfile()
}
func (daemon *Daemon) installDefaultAppArmorProfile() error {
if !defaultAppArmorProfileSupported() {
return nil
}
if daemon.appArmorProfile == nil {
if err := aaprofile.InstallDefault(defaultAppArmorProfile); err != nil {
return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded: %s", defaultAppArmorProfile, err)
}
return nil
}
if err := daemon.installCustomAppArmorProfile(); err != nil {
return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded from %s: %s", defaultAppArmorProfile, daemon.appArmorProfilePath, err)
}
return nil
}
func (daemon *Daemon) installCustomAppArmorProfile() error {
profile, err := daemon.generateCustomAppArmorProfile()
if err != nil {
return err
}
cmd := exec.CommandContext(context.Background(), "apparmor_parser", "-Kr")
cmd.Stdin = profile
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("running '%s' failed with output: %s\nerror: %w", cmd, out, err)
}
return nil
}
func (daemon *Daemon) generateCustomAppArmorProfile() (*bytes.Buffer, error) {
data := appArmorProfileData{
Name: defaultAppArmorProfile,
DaemonProfile: daemonAppArmorProfile(),
}
const abi = "abi/3.0"
if appArmorMacroExists(abi) {
data.Abi = abi
}
if appArmorMacroExists("tunables/global") {
data.Imports = append(data.Imports, "#include <tunables/global>")
} else {
data.Imports = append(data.Imports, "@{PROC}=/proc/")
}
if appArmorMacroExists("abstractions/base") {
data.InnerImports = append(data.InnerImports, "#include <abstractions/base>")
}
var profile bytes.Buffer
if err := daemon.appArmorProfile.Execute(&profile, data); err != nil {
return nil, err
}
return &profile, nil
}
func daemonAppArmorProfile() string {
currentProfile, err := os.ReadFile("/proc/self/attr/current")
if err != nil {
log.G(context.TODO()).Warnf("Could not get daemon AppArmor profile: %s", err)
return "unconfined"
}
profile, _, _ := strings.Cut(strings.TrimSpace(string(currentProfile)), " (")
if profile == "" {
return "unconfined"
}
return profile
}
func appArmorMacroExists(m string) bool {
_, err := os.Stat(filepath.Join("/etc/apparmor.d", m)) // #nosec G703 -- only used with fixed / hardcoded paths.
return err == nil
}
func defaultAppArmorProfileSupported() bool {
hostSupports := appArmorSupported()
if hostSupports {
if detachedNetNS, _ := rootless.DetachedNetNS(); detachedNetNS != "" {
// "open /sys/kernel/security/apparmor/profiles: permission denied"
// (because sysfs is netns-scoped)
hostSupports = false
}
}
return hostSupports
}