gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/common_files.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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  package builtin
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"gitee.com/mysnapcore/mysnapd/interfaces"
    29  	"gitee.com/mysnapcore/mysnapd/interfaces/apparmor"
    30  	apparmor_sandbox "gitee.com/mysnapcore/mysnapd/sandbox/apparmor"
    31  	"gitee.com/mysnapcore/mysnapd/snap"
    32  )
    33  
    34  type commonFilesInterface struct {
    35  	commonInterface
    36  
    37  	apparmorHeader    string
    38  	extraPathValidate func(string) error
    39  }
    40  
    41  // filesAAPerm can either be files{Read,Write} and converted to a string
    42  // expands into the right apparmor permissions for the files interface.
    43  type filesAAPerm int
    44  
    45  const (
    46  	filesRead filesAAPerm = iota
    47  	filesWrite
    48  )
    49  
    50  func (a filesAAPerm) String() string {
    51  	switch a {
    52  	case filesRead:
    53  		return "rk" // [r]ead and loc[k]
    54  	case filesWrite:
    55  		return "rwkl" // [r]ead, [w]rite, loc[k] and [l]ink//
    56  	}
    57  	panic(fmt.Sprintf("invalid perm: %d", a))
    58  }
    59  
    60  func formatPath(ip interface{}) (string, error) {
    61  	p, ok := ip.(string)
    62  	if !ok {
    63  		return "", fmt.Errorf("%[1]v (%[1]T) is not a string", ip)
    64  	}
    65  	prefix := ""
    66  	// Note that the {personal,system}-files interface impose
    67  	// limitations on the $HOME usage - system-files forbids it,
    68  	// personal only allows starting with $HOME in the path.
    69  	if strings.Contains(p, "$HOME") {
    70  		p = strings.Replace(p, "$HOME", "@{HOME}", -1)
    71  		prefix = "owner "
    72  	}
    73  	p = filepath.Clean(p)
    74  	p += "{,/,/**}"
    75  
    76  	return fmt.Sprintf("%s%q", prefix, p), nil
    77  }
    78  
    79  func allowPathAccess(buf *bytes.Buffer, perm filesAAPerm, paths []interface{}) error {
    80  	for _, rawPath := range paths {
    81  		p, err := formatPath(rawPath)
    82  		if err != nil {
    83  			return err
    84  		}
    85  		fmt.Fprintf(buf, "%s %s,\n", p, perm)
    86  	}
    87  	return nil
    88  }
    89  
    90  func (iface *commonFilesInterface) validatePaths(attrName string, paths []interface{}) error {
    91  	for _, npp := range paths {
    92  		np, ok := npp.(string)
    93  		if !ok {
    94  			return fmt.Errorf("%q must be a list of strings", attrName)
    95  		}
    96  		if err := iface.validateSinglePath(np); err != nil {
    97  			return err
    98  		}
    99  	}
   100  	return nil
   101  }
   102  
   103  func (iface *commonFilesInterface) validateSinglePath(np string) error {
   104  	if strings.HasSuffix(np, "/") {
   105  		return fmt.Errorf(`%q cannot end with "/"`, np)
   106  	}
   107  	p := filepath.Clean(np)
   108  	if p != np {
   109  		return fmt.Errorf("cannot use %q: try %q", np, filepath.Clean(np))
   110  	}
   111  	if strings.Contains(p, "~") {
   112  		return fmt.Errorf(`%q cannot contain "~"`, p)
   113  	}
   114  	if err := apparmor_sandbox.ValidateNoAppArmorRegexp(p); err != nil {
   115  		return err
   116  	}
   117  
   118  	// extraPathValidation must be implemented by the interface
   119  	// that build on top of the abstract commonFilesInterface
   120  	if iface.extraPathValidate == nil {
   121  		panic("extraPathValidate must be set when using the commonFilesInterface")
   122  	}
   123  	if err := iface.extraPathValidate(np); err != nil {
   124  		return err
   125  	}
   126  	return nil
   127  }
   128  
   129  func (iface *commonFilesInterface) BeforePreparePlug(plug *snap.PlugInfo) error {
   130  	hasValidAttr := false
   131  	for _, att := range []string{"read", "write"} {
   132  		if _, ok := plug.Attrs[att]; !ok {
   133  			continue
   134  		}
   135  		paths, ok := plug.Attrs[att].([]interface{})
   136  		if !ok {
   137  			return fmt.Errorf("cannot add %s plug: %q must be a list of strings", iface.name, att)
   138  		}
   139  		if err := iface.validatePaths(att, paths); err != nil {
   140  			return fmt.Errorf("cannot add %s plug: %s", iface.name, err)
   141  		}
   142  		hasValidAttr = true
   143  	}
   144  	if !hasValidAttr {
   145  		return fmt.Errorf(`cannot add %s plug: needs valid "read" or "write" attribute`, iface.name)
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  func (iface *commonFilesInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   152  	var reads, writes []interface{}
   153  	_ = plug.Attr("read", &reads)
   154  	_ = plug.Attr("write", &writes)
   155  
   156  	errPrefix := fmt.Sprintf(`cannot connect plug %s: `, plug.Name())
   157  	buf := bytes.NewBufferString(iface.apparmorHeader)
   158  	if err := allowPathAccess(buf, filesRead, reads); err != nil {
   159  		return fmt.Errorf("%s%v", errPrefix, err)
   160  	}
   161  	if err := allowPathAccess(buf, filesWrite, writes); err != nil {
   162  		return fmt.Errorf("%s%v", errPrefix, err)
   163  	}
   164  	spec.AddSnippet(buf.String())
   165  
   166  	return nil
   167  }