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 }