gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snapd-apparmor/main.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // This tool is provided for integration with systemd on distributions where
    21  // apparmor profiles generated and managed by snapd are not loaded by the
    22  // system-wide apparmor systemd integration on early boot-up.
    23  //
    24  // Only the start operation is provided as all other activity is managed by
    25  // snapd as a part of the life-cycle of particular snaps.
    26  //
    27  // In addition this tool assumes that the system-wide apparmor service has
    28  // already executed, initializing apparmor file-systems as necessary.
    29  //
    30  // NOTE: This tool ignores failures in some scenarios as the intent is to
    31  // simply load application profiles ahead of time, as many as we can (for
    32  // performance reasons), even if for whatever reason some of those fail.
    33  
    34  package main
    35  
    36  import (
    37  	"errors"
    38  	"fmt"
    39  	"io/ioutil"
    40  	"os"
    41  	"os/exec"
    42  	"path/filepath"
    43  	"strings"
    44  
    45  	"gitee.com/mysnapcore/mysnapd/dirs"
    46  	"gitee.com/mysnapcore/mysnapd/logger"
    47  	"gitee.com/mysnapcore/mysnapd/release"
    48  	apparmor_sandbox "gitee.com/mysnapcore/mysnapd/sandbox/apparmor"
    49  	"gitee.com/mysnapcore/mysnapd/snapdtool"
    50  )
    51  
    52  // Checks to see if the current container is capable of having internal AppArmor
    53  // profiles that should be loaded.
    54  //
    55  // The only known container environments capable of supporting internal policy
    56  // are LXD and LXC environment.
    57  //
    58  // Returns true if the container environment is capable of having its own internal
    59  // policy and false otherwise.
    60  //
    61  // IMPORTANT: This function will return true in the case of a non-LXD/non-LXC
    62  // system container technology being nested inside of a LXD/LXC container that
    63  // utilized an AppArmor namespace and profile stacking. The reason true will be
    64  // returned is because .ns_stacked will be "yes" and .ns_name will still match
    65  // "lx[dc]-*" since the nested system container technology will not have set up
    66  // a new AppArmor profile namespace. This will result in the nested system
    67  // container's boot process to experience failed policy loads but the boot
    68  // process should continue without any loss of functionality. This is an
    69  // unsupported configuration that cannot be properly handled by this function.
    70  //
    71  func isContainerWithInternalPolicy() bool {
    72  	if release.OnWSL {
    73  		return true
    74  	}
    75  
    76  	var appArmorSecurityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor")
    77  	var nsStackedPath = filepath.Join(appArmorSecurityFSPath, ".ns_stacked")
    78  	var nsNamePath = filepath.Join(appArmorSecurityFSPath, ".ns_name")
    79  
    80  	contents, err := ioutil.ReadFile(nsStackedPath)
    81  	if err != nil && !errors.Is(err, os.ErrNotExist) {
    82  		logger.Noticef("Failed to read %s: %v", nsStackedPath, err)
    83  		return false
    84  	}
    85  
    86  	if strings.TrimSpace(string(contents)) != "yes" {
    87  		return false
    88  	}
    89  
    90  	contents, err = ioutil.ReadFile(nsNamePath)
    91  	if err != nil && !errors.Is(err, os.ErrNotExist) {
    92  		logger.Noticef("Failed to read %s: %v", nsNamePath, err)
    93  		return false
    94  	}
    95  
    96  	// LXD and LXC set up AppArmor namespaces starting with "lxd-" and
    97  	// "lxc-", respectively. Return false for all other namespace
    98  	// identifiers.
    99  	name := strings.TrimSpace(string(contents))
   100  	if !strings.HasPrefix(name, "lxd-") && !strings.HasPrefix(name, "lxc-") {
   101  		return false
   102  	}
   103  	return true
   104  }
   105  
   106  func loadAppArmorProfiles() error {
   107  	candidates, err := filepath.Glob(dirs.SnapAppArmorDir + "/*")
   108  	if err != nil {
   109  		return fmt.Errorf("Failed to glob profiles from snap apparmor dir %s: %v", dirs.SnapAppArmorDir, err)
   110  	}
   111  
   112  	profiles := make([]string, 0, len(candidates))
   113  	for _, profile := range candidates {
   114  		// Filter out profiles with names ending with ~, those are
   115  		// temporary files created by snapd.
   116  		if strings.HasSuffix(profile, "~") {
   117  			continue
   118  		}
   119  		profiles = append(profiles, profile)
   120  	}
   121  	if len(profiles) == 0 {
   122  		logger.Noticef("No profiles to load")
   123  		return nil
   124  	}
   125  	logger.Noticef("Loading profiles %v", profiles)
   126  	return apparmor_sandbox.LoadProfiles(profiles, apparmor_sandbox.SystemCacheDir, 0)
   127  }
   128  
   129  func isContainer() bool {
   130  	// systemd's implementation may fail on WSL2 with custom kernels
   131  	return release.OnWSL || (exec.Command("systemd-detect-virt", "--quiet", "--container").Run() == nil)
   132  }
   133  
   134  func validateArgs(args []string) error {
   135  	if len(args) != 1 || args[0] != "start" {
   136  		return errors.New("Expected to be called with a single 'start' argument.")
   137  	}
   138  	return nil
   139  }
   140  
   141  func main() {
   142  	if err := run(); err != nil {
   143  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
   144  		os.Exit(1)
   145  	}
   146  }
   147  
   148  func run() error {
   149  	snapdtool.ExecInSnapdOrCoreSnap()
   150  
   151  	if err := validateArgs(os.Args[1:]); err != nil {
   152  		return err
   153  	}
   154  
   155  	if isContainer() {
   156  		logger.Debugf("inside container environment")
   157  		// in container environment - see if container has own
   158  		// policy that we need to manage otherwise get out of the
   159  		// way
   160  		if !isContainerWithInternalPolicy() {
   161  			logger.Noticef("Inside container environment without internal policy")
   162  			return nil
   163  		}
   164  	}
   165  
   166  	return loadAppArmorProfiles()
   167  }