From 47180a352654bee9a994db714801813a59cfbb18 Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Fri, 28 Jun 2024 14:32:29 +0200 Subject: [PATCH 1/2] lxd/apparmor/feature_check: add infastructure to check AppArmor features Add some infastructure to check AppArmor features availability, as version checks can not be reliable because of backports of different features between branches. Signed-off-by: Alexander Mikhalitsyn --- lxd/apparmor/feature_check.go | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lxd/apparmor/feature_check.go diff --git a/lxd/apparmor/feature_check.go b/lxd/apparmor/feature_check.go new file mode 100644 index 000000000000..eae698d81c9f --- /dev/null +++ b/lxd/apparmor/feature_check.go @@ -0,0 +1,71 @@ +package apparmor + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/google/uuid" + + "github.com/canonical/lxd/lxd/sys" + "github.com/canonical/lxd/shared" +) + +var featureCheckProfileTpl = template.Must(template.New("featureCheckProfile").Parse(` +profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { + +} +`)) + +// FeatureCheck tries to generate feature check profile and process it with apparmor_parser. +func FeatureCheck(sysOS *sys.OS, feature string) (bool, error) { + randomUUID := uuid.New().String() + name := fmt.Sprintf("<%s-%s>", randomUUID, feature) + profileName := profileName("featurecheck", name) + profilePath := filepath.Join(aaPath, "profiles", profileName) + content, err := os.ReadFile(profilePath) + if err != nil && !os.IsNotExist(err) { + return false, err + } + + updated, err := featureCheckProfile(profileName, feature) + if err != nil { + return false, err + } + + if string(content) != string(updated) { + err = os.WriteFile(profilePath, []byte(updated), 0600) + if err != nil { + return false, err + } + } + + defer func() { + _ = deleteProfile(sysOS, profileName, profileName) + }() + + err = parseProfile(sysOS, profileName) + if err != nil { + return false, nil + } + + return true, nil +} + +// featureCheckProfile generates the AppArmor profile. +func featureCheckProfile(profileName string, feature string) (string, error) { + // Render the profile. + sb := &strings.Builder{} + err := featureCheckProfileTpl.Execute(sb, map[string]any{ + "name": profileName, + "snap": shared.InSnap(), + "feature": feature, + }) + if err != nil { + return "", err + } + + return sb.String(), nil +} From b5a4a6540e793120b85a3b280003fce236e6a56f Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Fri, 28 Jun 2024 14:34:04 +0200 Subject: [PATCH 2/2] lxd/apparmor/instance_lxc: allow nosymfollow mount flag See also: https://github.com/canonical/lxd/pull/12698 Thanks-to: Nick Rosbrook Signed-off-by: Alexander Mikhalitsyn --- lxd/apparmor/apparmor.go | 16 ++++++++++++++++ lxd/apparmor/feature_check.go | 4 ++++ lxd/apparmor/instance.go | 26 ++++++++++++++++---------- lxd/apparmor/instance_lxc.go | 20 ++++++++++++++++++++ lxd/sys/apparmor.go | 2 ++ lxd/sys/os.go | 7 +++++++ 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lxd/apparmor/apparmor.go b/lxd/apparmor/apparmor.go index fac585699e21..2f33f200f31a 100644 --- a/lxd/apparmor/apparmor.go +++ b/lxd/apparmor/apparmor.go @@ -185,6 +185,22 @@ func parserSupports(sysOS *sys.OS, feature string) (bool, error) { return ver.Compare(minVer) >= 0, nil } + if feature == "mount_nosymfollow" { + sysOS.AppArmorFeatures.Lock() + defer sysOS.AppArmorFeatures.Unlock() + supported, ok := sysOS.AppArmorFeatures.Map[feature] + if !ok { + supported, err = FeatureCheck(sysOS, feature) + if err != nil { + return false, nil + } + + sysOS.AppArmorFeatures.Map[feature] = supported + } + + return supported, nil + } + return false, nil } diff --git a/lxd/apparmor/feature_check.go b/lxd/apparmor/feature_check.go index eae698d81c9f..3043ec20c738 100644 --- a/lxd/apparmor/feature_check.go +++ b/lxd/apparmor/feature_check.go @@ -16,6 +16,10 @@ import ( var featureCheckProfileTpl = template.Must(template.New("featureCheckProfile").Parse(` profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { +{{- if eq .feature "mount_nosymfollow" }} + mount options=(nosymfollow) /, +{{- end }} + } `)) diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go index fbef81c2b3aa..895836ce5665 100644 --- a/lxd/apparmor/instance.go +++ b/lxd/apparmor/instance.go @@ -154,18 +154,24 @@ func instanceProfile(sysOS *sys.OS, inst instance) (string, error) { } // Render the profile. - var sb *strings.Builder = &strings.Builder{} + sb := &strings.Builder{} if inst.Type() == instancetype.Container { + mountNosymfollowSupported, err := parserSupports(sysOS, "mount_nosymfollow") + if err != nil { + return "", err + } + err = lxcProfileTpl.Execute(sb, map[string]any{ - "feature_cgns": sysOS.CGInfo.Namespacing, - "feature_cgroup2": sysOS.CGInfo.Layout == cgroup.CgroupsUnified || sysOS.CGInfo.Layout == cgroup.CgroupsHybrid, - "feature_stacking": sysOS.AppArmorStacking && !sysOS.AppArmorStacked, - "feature_unix": unixSupported, - "name": InstanceProfileName(inst), - "namespace": InstanceNamespaceName(inst), - "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]), - "raw": rawContent, - "unprivileged": shared.IsFalseOrEmpty(inst.ExpandedConfig()["security.privileged"]) || sysOS.RunningInUserNS, + "feature_cgns": sysOS.CGInfo.Namespacing, + "feature_cgroup2": sysOS.CGInfo.Layout == cgroup.CgroupsUnified || sysOS.CGInfo.Layout == cgroup.CgroupsHybrid, + "feature_stacking": sysOS.AppArmorStacking && !sysOS.AppArmorStacked, + "feature_unix": unixSupported, + "feature_mount_nosymfollow": mountNosymfollowSupported, + "name": InstanceProfileName(inst), + "namespace": InstanceNamespaceName(inst), + "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]), + "raw": rawContent, + "unprivileged": shared.IsFalseOrEmpty(inst.ExpandedConfig()["security.privileged"]) || sysOS.RunningInUserNS, }) if err != nil { return "", err diff --git a/lxd/apparmor/instance_lxc.go b/lxd/apparmor/instance_lxc.go index a41fd7f09358..3a49b5f5c744 100644 --- a/lxd/apparmor/instance_lxc.go +++ b/lxd/apparmor/instance_lxc.go @@ -255,6 +255,26 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { mount options=(ro,remount,bind,nosuid,noexec,strictatime) /sy[^s]*{,/**}, mount options=(ro,remount,bind,nosuid,noexec,strictatime) /sys?*{,/**}, +{{- if .feature_mount_nosymfollow }} + # see https://github.com/canonical/lxd/pull/12698 + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /[^spd]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /d[^e]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /de[^v]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.[^l]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.l[^x]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.lx[^c]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.lxc?*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/[^.]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev?*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /p[^r]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /pr[^o]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /pro[^c]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /proc?*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /s[^y]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /sy[^s]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /sys?*{,/**}, +{{- end }} + # Allow bind-mounts of anything except /proc, /sys and /dev/.lxc mount options=(rw,bind) /[^spd]*{,/**}, mount options=(rw,bind) /d[^e]*{,/**}, diff --git a/lxd/sys/apparmor.go b/lxd/sys/apparmor.go index 0c82fa7f5a14..e54e6a44adba 100644 --- a/lxd/sys/apparmor.go +++ b/lxd/sys/apparmor.go @@ -86,6 +86,8 @@ func (s *OS) initAppArmor() []cluster.Warning { s.AppArmorConfined = true } + s.AppArmorFeatures.Map = map[string]bool{} + return dbWarnings } diff --git a/lxd/sys/os.go b/lxd/sys/os.go index 12588a97a5d2..3c18e5bbf6f0 100644 --- a/lxd/sys/os.go +++ b/lxd/sys/os.go @@ -38,6 +38,12 @@ type InotifyInfo struct { Targets map[string]*InotifyTargetInfo } +// AppArmorFeaturesInfo records the AppArmor features availability. +type AppArmorFeaturesInfo struct { + sync.Mutex + Map map[string]bool +} + // OS is a high-level facade for accessing all operating-system // level functionality that LXD uses. type OS struct { @@ -69,6 +75,7 @@ type OS struct { AppArmorConfined bool AppArmorStacked bool AppArmorStacking bool + AppArmorFeatures AppArmorFeaturesInfo // Cgroup features CGInfo cgroup.Info