github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/dbus/backend.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 dbus implements interaction between snappy and dbus.
    21  //
    22  // Snappy creates dbus configuration files that describe how various
    23  // services on the system bus can communicate with other peers.
    24  //
    25  // Each configuration is an XML file containing <busconfig>...</busconfig>.
    26  // Particular security snippets define whole <policy>...</policy> entires.
    27  // This is explained in detail in https://dbus.freedesktop.org/doc/dbus-daemon.1.html
    28  package dbus
    29  
    30  import (
    31  	"bytes"
    32  	"fmt"
    33  	"os"
    34  	"path/filepath"
    35  
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/interfaces"
    38  	"github.com/snapcore/snapd/logger"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/release"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snapdtool"
    43  	"github.com/snapcore/snapd/timings"
    44  	"github.com/snapcore/snapd/wrappers"
    45  )
    46  
    47  // Backend is responsible for maintaining DBus policy files.
    48  type Backend struct{}
    49  
    50  // Initialize does nothing.
    51  func (b *Backend) Initialize(*interfaces.SecurityBackendOptions) error {
    52  	return nil
    53  }
    54  
    55  // Name returns the name of the backend.
    56  func (b *Backend) Name() interfaces.SecuritySystem {
    57  	return "dbus"
    58  }
    59  
    60  func shouldCopyConfigFiles(snapInfo *snap.Info) bool {
    61  	// Only copy config files on classic distros
    62  	if !release.OnClassic {
    63  		return false
    64  	}
    65  	// Only copy config files if we have been reexecuted
    66  	if reexecd, _ := snapdtool.IsReexecd(); !reexecd {
    67  		return false
    68  	}
    69  	switch snapInfo.Type() {
    70  	case snap.TypeOS:
    71  		// XXX: ugly but we need to make sure that the content
    72  		// of the "snapd" snap wins
    73  		//
    74  		// TODO: this is also racy but the content of the
    75  		// files in core and snapd is identical.  Cleanup
    76  		// after link-snap and setup-profiles are unified
    77  		return !osutil.FileExists(filepath.Join(snapInfo.MountDir(), "../..", "snapd/current"))
    78  	case snap.TypeSnapd:
    79  		return true
    80  	default:
    81  		return false
    82  	}
    83  }
    84  
    85  // setupDbusServiceForUserd will setup the service file for the new
    86  // `snap userd` instance on re-exec
    87  func setupDbusServiceForUserd(snapInfo *snap.Info) error {
    88  	coreOrSnapdRoot := snapInfo.MountDir()
    89  
    90  	for _, srv := range []string{
    91  		"io.snapcraft.Launcher.service",
    92  		"io.snapcraft.Settings.service",
    93  	} {
    94  		dst := filepath.Join("/usr/share/dbus-1/services/", srv)
    95  		src := filepath.Join(coreOrSnapdRoot, dst)
    96  
    97  		// we only need the GlobalRootDir for testing
    98  		dst = filepath.Join(dirs.GlobalRootDir, dst)
    99  		if !osutil.FilesAreEqual(src, dst) {
   100  			if err := osutil.CopyFile(src, dst, osutil.CopyFlagPreserveAll); err != nil {
   101  				return err
   102  			}
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  func setupHostDBusConf(snapInfo *snap.Info) error {
   109  	sessionContent, systemContent, err := wrappers.DeriveSnapdDBusConfig(snapInfo)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	// We don't use `dirs.SnapDBusSessionPolicyDir because we want
   115  	// to match the path the package on the host system uses.
   116  	dest := filepath.Join(dirs.GlobalRootDir, "/usr/share/dbus-1/session.d")
   117  	if err = os.MkdirAll(dest, 0755); err != nil {
   118  		return err
   119  	}
   120  	_, _, err = osutil.EnsureDirState(dest, "snapd.*.conf", sessionContent)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	dest = filepath.Join(dirs.GlobalRootDir, "/usr/share/dbus-1/system.d")
   126  	if err = os.MkdirAll(dest, 0755); err != nil {
   127  		return err
   128  	}
   129  	_, _, err = osutil.EnsureDirState(dest, "snapd.*.conf", systemContent)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  // Setup creates dbus configuration files specific to a given snap.
   138  //
   139  // DBus has no concept of a complain mode so confinment type is ignored.
   140  func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
   141  	snapName := snapInfo.InstanceName()
   142  	// Get the snippets that apply to this snap
   143  	spec, err := repo.SnapSpecification(b.Name(), snapName)
   144  	if err != nil {
   145  		return fmt.Errorf("cannot obtain dbus specification for snap %q: %s", snapName, err)
   146  	}
   147  
   148  	// copy some config files when installing core/snapd if we reexec
   149  	if shouldCopyConfigFiles(snapInfo) {
   150  		if err := setupDbusServiceForUserd(snapInfo); err != nil {
   151  			logger.Noticef("cannot create host `snap userd` dbus service file: %s", err)
   152  		}
   153  		// TODO: Make this conditional on the dbus-activation
   154  		// feature flag.
   155  		if err := setupHostDBusConf(snapInfo); err != nil {
   156  			logger.Noticef("cannot create host dbus config: %s", err)
   157  		}
   158  	}
   159  
   160  	// Get the files that this snap should have
   161  	content, err := b.deriveContent(spec.(*Specification), snapInfo)
   162  	if err != nil {
   163  		return fmt.Errorf("cannot obtain expected DBus configuration files for snap %q: %s", snapName, err)
   164  	}
   165  	glob := fmt.Sprintf("%s.conf", interfaces.SecurityTagGlob(snapName))
   166  	dir := dirs.SnapDBusSystemPolicyDir
   167  	if err := os.MkdirAll(dir, 0755); err != nil {
   168  		return fmt.Errorf("cannot create directory for DBus configuration files %q: %s", dir, err)
   169  	}
   170  	_, _, err = osutil.EnsureDirState(dir, glob, content)
   171  	if err != nil {
   172  		return fmt.Errorf("cannot synchronize DBus configuration files for snap %q: %s", snapName, err)
   173  	}
   174  	return nil
   175  }
   176  
   177  // Remove removes dbus configuration files of a given snap.
   178  //
   179  // This method should be called after removing a snap.
   180  func (b *Backend) Remove(snapName string) error {
   181  	glob := fmt.Sprintf("%s.conf", interfaces.SecurityTagGlob(snapName))
   182  	_, _, err := osutil.EnsureDirState(dirs.SnapDBusSystemPolicyDir, glob, nil)
   183  	if err != nil {
   184  		return fmt.Errorf("cannot synchronize DBus configuration files for snap %q: %s", snapName, err)
   185  	}
   186  	return nil
   187  }
   188  
   189  // deriveContent combines security snippets collected from all the interfaces
   190  // affecting a given snap into a content map applicable to EnsureDirState.
   191  func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info) (content map[string]osutil.FileState, err error) {
   192  	for _, appInfo := range snapInfo.Apps {
   193  		securityTag := appInfo.SecurityTag()
   194  		appSnippets := spec.SnippetForTag(securityTag)
   195  		if appSnippets == "" {
   196  			continue
   197  		}
   198  		if content == nil {
   199  			content = make(map[string]osutil.FileState)
   200  		}
   201  
   202  		addContent(securityTag, appSnippets, content)
   203  	}
   204  
   205  	for _, hookInfo := range snapInfo.Hooks {
   206  		securityTag := hookInfo.SecurityTag()
   207  		hookSnippets := spec.SnippetForTag(securityTag)
   208  		if hookSnippets == "" {
   209  			continue
   210  		}
   211  		if content == nil {
   212  			content = make(map[string]osutil.FileState)
   213  		}
   214  
   215  		addContent(securityTag, hookSnippets, content)
   216  	}
   217  
   218  	return content, nil
   219  }
   220  
   221  func addContent(securityTag string, snippet string, content map[string]osutil.FileState) {
   222  	var buffer bytes.Buffer
   223  	buffer.Write(xmlHeader)
   224  	buffer.WriteString(snippet)
   225  	buffer.Write(xmlFooter)
   226  
   227  	content[fmt.Sprintf("%s.conf", securityTag)] = &osutil.MemoryFileState{
   228  		Content: buffer.Bytes(),
   229  		Mode:    0644,
   230  	}
   231  }
   232  
   233  func (b *Backend) NewSpecification() interfaces.Specification {
   234  	return &Specification{}
   235  }
   236  
   237  // SandboxFeatures returns list of features supported by snapd for dbus communication.
   238  func (b *Backend) SandboxFeatures() []string {
   239  	return []string{"mediated-bus-access"}
   240  }