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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"fmt"
    24  	"strings"
    25  
    26  	"gitee.com/mysnapcore/mysnapd/interfaces"
    27  	"gitee.com/mysnapcore/mysnapd/interfaces/apparmor"
    28  	"gitee.com/mysnapcore/mysnapd/interfaces/mount"
    29  	"gitee.com/mysnapcore/mysnapd/osutil"
    30  	"gitee.com/mysnapcore/mysnapd/snap"
    31  )
    32  
    33  // On systems where the slot is provided by an app snap, the cups interface is
    34  // the companion interface to the cups-control interface. The design of these
    35  // interfaces is based on the idea that the slot implementation (eg cupsd) is
    36  // expected to query snapd to determine if the cups-control interface is
    37  // connected or not for the peer client process and the print service will
    38  // mediate admin functionality (ie, the rules in these interfaces allow
    39  // connecting to the print service, but do not implement enforcement rules; it
    40  // is up to the print service to provide enforcement).
    41  const cupsSummary = `allows access to the CUPS socket for printing`
    42  
    43  // cups is currently only available via a providing app snap and this interface
    44  // assumes that the providing app snap also slots 'cups-control' (the current
    45  // design allows the snap provider to slots both cups-control and cups or just
    46  // cups-control (like with implicit classic or any slot provider without
    47  // mediation patches), but not just cups).
    48  const cupsBaseDeclarationSlots = `
    49    cups:
    50      allow-installation:
    51        slot-snap-type:
    52          - app
    53      deny-connection: true
    54      deny-auto-connection: true
    55  `
    56  
    57  const cupsConnectedPlugAppArmor = `
    58  # Allow communicating with the cups server
    59  
    60  # Do not allow reading the user or global client.conf for this snap, as this may
    61  # allow a user to point an application at an unconfined cupsd which could be 
    62  # used to load printer drivers etc. We only want client snaps with the cups 
    63  # interface plug connected to be able to talk to a version of cupsd which is
    64  # strictly confined and performs mediation. This means only allowing to talk to
    65  # /var/cups/cups.sock and not /run/cups/cups.sock since snapd has no way to know
    66  # if the latter cupsd is confined and performs mediation, but the upstream 
    67  # maintained cups snap providing a cups slot will always perform mediation.
    68  # As such, do not use the <abstractions/cups-client> include file here.
    69  
    70  # Allow reading the personal settings for cups like default printer, etc.
    71  owner @{HOME}/.cups/lpoptions r,
    72  
    73  /{,var/}run/cups/printcap r,
    74  
    75  # Allow talking to the snap version of cupsd socket that we expose via bind 
    76  # mounts from a snap providing the cups slot to this snap.
    77  /var/cups/cups.sock rw,
    78  `
    79  
    80  type cupsInterface struct {
    81  	commonInterface
    82  }
    83  
    84  func (iface *cupsInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
    85  	return nil
    86  }
    87  
    88  func validateCupsSocketDirSlotAttr(a interfaces.Attrer, snapInfo *snap.Info) (string, error) {
    89  	// Allow an empty specification for the slot, in which case we don't perform
    90  	// any mounts, etc. This is mainly to prevent errors in systems which still
    91  	// have the old cups snap installed that haven't been updated to use the new
    92  	// snap with the new slot declaration
    93  	if _, ok := a.Lookup("cups-socket-directory"); !ok {
    94  		return "", nil
    95  	}
    96  
    97  	var cupsdSocketSourceDir string
    98  	if err := a.Attr("cups-socket-directory", &cupsdSocketSourceDir); err != nil {
    99  		return "", err
   100  	}
   101  
   102  	// make sure that the cups socket dir is not an AppArmor Regular expression
   103  	if err := apparmor.ValidateNoAppArmorRegexp(cupsdSocketSourceDir); err != nil {
   104  		return "", fmt.Errorf("cups-socket-directory is not usable: %v", err)
   105  	}
   106  
   107  	if !cleanSubPath(cupsdSocketSourceDir) {
   108  		return "", fmt.Errorf("cups-socket-directory is not clean: %q", cupsdSocketSourceDir)
   109  	}
   110  
   111  	// validate that the setting for cups-socket-directory is in $SNAP_DATA or
   112  	// $SNAP_COMMON, we don't allow any other directories for the slot socket
   113  	// dir
   114  	// TODO: should we also allow /run/$SNAP_INSTANCE_NAME/ too ?
   115  	if !strings.HasPrefix(cupsdSocketSourceDir, "$SNAP_COMMON") && !strings.HasPrefix(cupsdSocketSourceDir, "$SNAP_DATA") {
   116  		return "", fmt.Errorf("cups-socket-directory must be a directory of $SNAP_COMMON or $SNAP_DATA")
   117  	}
   118  	// otherwise it must have a prefix of either SNAP_COMMON or SNAP_DATA,
   119  	// validate that it has no other variables in it
   120  	err := snap.ValidatePathVariables(cupsdSocketSourceDir)
   121  	if err != nil {
   122  		return "", err
   123  	}
   124  
   125  	// The path starts with $ and ValidatePathVariables() ensures
   126  	// path contains only $SNAP, $SNAP_DATA, $SNAP_COMMON, and no
   127  	// other $VARs are present. It is ok to use
   128  	// ExpandSnapVariables() since it only expands $SNAP, $SNAP_DATA
   129  	// and $SNAP_COMMON
   130  	return snapInfo.ExpandSnapVariables(cupsdSocketSourceDir), nil
   131  }
   132  
   133  func (iface *cupsInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
   134  	// verify that the snap has a cups-socket-directory interface attribute, which is
   135  	// needed to identify where to find the cups socket is located in the snap
   136  	// providing the cups socket
   137  	_, err := validateCupsSocketDirSlotAttr(slot, slot.Snap)
   138  	return err
   139  }
   140  
   141  func (iface *cupsInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   142  	cupsdSocketSourceDir, err := validateCupsSocketDirSlotAttr(slot, slot.Snap())
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	// add the base snippet
   148  	spec.AddSnippet(cupsConnectedPlugAppArmor)
   149  
   150  	if cupsdSocketSourceDir == "" {
   151  		// no other rules, this is the legacy slot without the additional
   152  		// attribute
   153  		return nil
   154  	}
   155  
   156  	// add rules to access the socket dir from the slot location directly
   157  	// this is necessary otherwise clients get denials like this:
   158  	// apparmor="DENIED" operation="connect"
   159  	// profile="snap.test-snapd-cups-consumer.bin"
   160  	// name="/var/snap/test-snapd-cups-provider/common/cups.sock"
   161  	// pid=3195747 comm="nc" requested_mask="wr" denied_mask="wr" fsuid=0 ouid=0
   162  	// this denial is the same that would happen for the content interface, so
   163  	// we employ the same workaround from the content interface here too
   164  	spec.AddSnippet(fmt.Sprintf(`
   165  # In addition to the bind mount, add any AppArmor rules so that
   166  # snaps may directly access the slot implementation's files. Due
   167  # to a limitation in the kernel's LSM hooks for AF_UNIX, these
   168  # are needed for using named sockets within the exported
   169  # directory.
   170  "%s/**" mrwklix,`, cupsdSocketSourceDir))
   171  
   172  	// setup the snap-update-ns rules for bind mounting for the plugging snap
   173  	emit := spec.AddUpdateNSf
   174  
   175  	emit("  # Mount cupsd socket from cups snap to client snap\n")
   176  	// note the trailing "/" is needed - we ensured that cupsdSocketSourceDir is
   177  	// clean when we validated it, so it will not have a trailing "/" so we are
   178  	// safe to add this here
   179  	emit("  mount options=(rw bind) \"%s/\" -> /var/cups/,\n", cupsdSocketSourceDir)
   180  	emit("  umount /var/cups/,\n")
   181  
   182  	apparmor.GenWritableProfile(emit, cupsdSocketSourceDir, 1)
   183  	apparmor.GenWritableProfile(emit, "/var/cups", 1)
   184  
   185  	return nil
   186  }
   187  
   188  func (iface *cupsInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   189  	cupsdSocketSourceDir, err := validateCupsSocketDirSlotAttr(slot, slot.Snap())
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	if cupsdSocketSourceDir == "" {
   195  		// no other rules, this is the legacy slot without the additional
   196  		// attribute
   197  		return nil
   198  	}
   199  
   200  	// add a bind mount of the cups-socket-directory to /var/cups of the plugging snap
   201  	return spec.AddMountEntry(osutil.MountEntry{
   202  		Name:    cupsdSocketSourceDir,
   203  		Dir:     "/var/cups/",
   204  		Options: []string{"bind", "rw"},
   205  	})
   206  }
   207  
   208  func init() {
   209  	registerIface(&cupsInterface{
   210  		commonInterface: commonInterface{
   211  			name:                 "cups",
   212  			summary:              cupsSummary,
   213  			implicitOnCore:       false,
   214  			implicitOnClassic:    false,
   215  			baseDeclarationSlots: cupsBaseDeclarationSlots,
   216  		},
   217  	})
   218  }