docker中apparmor的加载过程

daemon

判断操作系统是否启用apparmor,如果支持就安装名为docker-default的规则。 https://github.com/moby/moby/blob/master/daemon/apparmor_default.go

const (
	unconfinedAppArmorProfile = "unconfined"
	defaultAppArmorProfile    = "docker-default"
)

func ensureDefaultAppArmorProfile() error {
	if apparmor.IsEnabled() {
		loaded, err := aaprofile.IsLoaded(defaultAppArmorProfile)
		if err != nil {
			return fmt.Errorf("Could not check if %s AppArmor profile was loaded: %s", defaultAppArmorProfile, err)
		}

		// Nothing to do.
		if loaded {
			return nil
		}

		// Load the profile.
		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
}

其中判断是否启用apparmor代码如下,主要是判断apparmor相关文件是否存在。

// IsEnabled returns true if apparmor is enabled for the host.
func IsEnabled() bool {
	if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
		if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
			buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
			return err == nil && len(buf) > 1 && buf[0] == 'Y'
		}
	}
	return false
}

docker-default规则具体位于 https://github.com/moby/moby/blob/master/profiles/apparmor/template.go

container

在容器的初始化过程中,应用AppArmor策略:

https://github.com/opencontainers/runc/blob/8bf216728cd558d736eda2dff404b34b262b8c77/libcontainer/standard_init_linux.go#L115-L117

if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil {
    return errors.Wrap(err, "apply apparmor profile")
}

所谓应用AppArmor策略,实际过程很简单,即将exec {ProfileName}写入/proc/self/attr/exec中。 https://github.com/opencontainers/runc/blob/8bf216728cd558d736eda2dff404b34b262b8c77/libcontainer/apparmor/apparmor.go#L23-L56

func setProcAttr(attr, value string) error {
	// Under AppArmor you can only change your own attr, so use /proc/self/
	// instead of /proc/<tid>/ like libapparmor does
	f, err := os.OpenFile("/proc/self/attr/"+attr, os.O_WRONLY, 0)
	if err != nil {
		return err
	}
	defer f.Close()

	if err := utils.EnsureProcHandle(f); err != nil {
		return err
	}

	_, err = f.WriteString(value)
	return err
}

// changeOnExec reimplements aa_change_onexec from libapparmor in Go
func changeOnExec(name string) error {
	if err := setProcAttr("exec", "exec "+name); err != nil {
		return fmt.Errorf("apparmor failed to apply profile: %s", err)
	}
	return nil
}

// ApplyProfile will apply the profile with the specified name to the process after
// the next exec.
func ApplyProfile(name string) error {
	if name == "" {
		return nil
	}

	return changeOnExec(name)
}

/proc/<pid>/attr会在使用主要的安全模块时使用到。

https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html

Process attributes associated with “major” security modules should be accessed and maintained using the special files in /proc/…/attr. A security module may maintain a module specific subdirectory there, named after the module. /proc/…/attr/smack is provided by the Smack security module and contains all its special files. The files directly in /proc/…/attr remain as legacy interfaces for modules that provide subdirectories.

修改/proc/self/attr/exec内容,可以在exec时修改限制策略。 https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorinterfaces#procselfattrexec

The /proc/self/attr/exec file can be written to change the tasks confinement at exec time. It is used to perform the change_onexec operation.

ubuntu manual描述了change_onexec的具体作用。 http://manpages.ubuntu.com/manpages/precise/en/man2/aa_change_profile.2.html

An AppArmor profile applies to an executable program; if a portion of the program needs different access permissions than other portions, the program can “change profile” to a different profile. To change into a new profile, it can use the aa_change_profile() function to do so. It passes in a pointer to the profile to transition to. Transitioning to another profile via aa_change_profile() is permanent and the process is not permitted to transition back to the original profile. Confined programs wanting to use aa_change_profile() need to have rules permitting changing to the named profile. See apparmor.d(8) for details. … The aa_change_onexec() function is like the aa_change_profile() function except it specifies that the profile transition should take place on the next exec instead of immediately. The delayed profile change takes precedence over any exec transition rules within the confining profile. Delaying the profile boundary has a couple of advantages, it removes the need for stub transition profiles and the exec boundary is a natural security layer where potentially sensitive memory is unmapped.

通过向/proc/self/attr/exec写入exec {profile name}, 来设置进程属性。

/proc/<pid>/attr目录下其中三个文件分别表示:

  • /proc/<pid>/attr/current - the current task context
  • /proc/<pid>/attr/exec - the task context to use at exec
  • /proc/<pid>/attr/prev - the previous task context when the task has switched hats