github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/configstate/configcore/services.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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 configcore
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	"github.com/snapcore/snapd/boot"
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/osutil"
    32  	"github.com/snapcore/snapd/overlord/configstate/config"
    33  	"github.com/snapcore/snapd/sysconfig"
    34  	"github.com/snapcore/snapd/systemd"
    35  )
    36  
    37  var services = []struct{ configName, systemdName string }{
    38  	{"ssh", "ssh.service"},
    39  	{"rsyslog", "rsyslog.service"},
    40  	{"console-conf", "console-conf@*"},
    41  	{"systemd-resolved", "systemd-resolved.service"},
    42  }
    43  
    44  func init() {
    45  	for _, service := range services {
    46  		s := fmt.Sprintf("core.service.%s.disable", service.configName)
    47  		supportedConfigurations[s] = true
    48  	}
    49  }
    50  
    51  type sysdLogger struct{}
    52  
    53  func (l *sysdLogger) Notify(status string) {
    54  	fmt.Fprintf(Stderr, "sysd: %s\n", status)
    55  }
    56  
    57  // switchDisableSSHService handles the special case of disabling/enabling ssh
    58  // service on core devices.
    59  func switchDisableSSHService(sysd systemd.Systemd, serviceName string, disabled bool, opts *fsOnlyContext) error {
    60  	rootDir := dirs.GlobalRootDir
    61  	if opts != nil {
    62  		rootDir = opts.RootDir
    63  		if err := os.MkdirAll(filepath.Join(rootDir, "/etc/ssh"), 0755); err != nil {
    64  			return err
    65  		}
    66  	}
    67  
    68  	sshCanary := filepath.Join(rootDir, "/etc/ssh/sshd_not_to_be_run")
    69  
    70  	if disabled {
    71  		if err := ioutil.WriteFile(sshCanary, []byte("SSH has been disabled by snapd system configuration\n"), 0644); err != nil {
    72  			return err
    73  		}
    74  		if opts == nil {
    75  			return sysd.Stop(serviceName, 5*time.Minute)
    76  		}
    77  	} else {
    78  		err := os.Remove(sshCanary)
    79  		if err != nil && !os.IsNotExist(err) {
    80  			return err
    81  		}
    82  		// Unmask both sshd.service and ssh.service and ignore the
    83  		// errors, if any. This undoes the damage done by earlier
    84  		// versions of snapd.
    85  		sysd.Unmask("sshd.service")
    86  		sysd.Unmask("ssh.service")
    87  		if opts == nil {
    88  			return sysd.Start(serviceName)
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  // switchDisableConsoleConfService handles the special case of
    95  // disabling/enabling console-conf on core devices.
    96  //
    97  // Note that this option can only be changed via gadget defaults.
    98  // It is not possible to tune this at runtime
    99  func switchDisableConsoleConfService(sysd systemd.Systemd, serviceName string, disabled bool, opts *fsOnlyContext) error {
   100  	consoleConfDisabled := "/var/lib/console-conf/complete"
   101  
   102  	// at runtime we can not change this setting
   103  	if opts == nil {
   104  
   105  		// Special case: during install mode the
   106  		// gadget-defaults will also be set as part of the
   107  		// system install change. However during install mode
   108  		// console-conf has no "complete" file, it just never runs
   109  		// in install mode. So we need to detect this and do nothing
   110  		// or the install mode will fail.
   111  		// XXX: instead of this hack we should look at the config
   112  		//      defaults and compare with the setting and exit if
   113  		//      they are the same but that requires some more changes.
   114  		// TODO: leverage sysconfig.Device instead
   115  		mode, _, _ := boot.ModeAndRecoverySystemFromKernelCommandLine()
   116  		if mode == boot.ModeInstall {
   117  			return nil
   118  		}
   119  
   120  		hasDisabledFile := osutil.FileExists(filepath.Join(dirs.GlobalRootDir, consoleConfDisabled))
   121  		if disabled != hasDisabledFile {
   122  			return fmt.Errorf("cannot toggle console-conf at runtime, but only initially via gadget defaults")
   123  		}
   124  		return nil
   125  	}
   126  
   127  	if !disabled {
   128  		return nil
   129  	}
   130  
   131  	// disable console-conf at the gadget-defaults time
   132  	consoleConfDisabled = filepath.Join(opts.RootDir, consoleConfDisabled)
   133  	if err := os.MkdirAll(filepath.Dir(consoleConfDisabled), 0755); err != nil {
   134  		return err
   135  	}
   136  	if err := ioutil.WriteFile(consoleConfDisabled, []byte("console-conf has been disabled by the snapd system configuration\n"), 0644); err != nil {
   137  		return err
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  // switchDisableTypicalService switches a service in/out of disabled state
   144  // where "true" means disabled and "false" means enabled.
   145  func switchDisableService(serviceName string, disabled bool, opts *fsOnlyContext) error {
   146  	var sysd systemd.Systemd
   147  	if opts != nil {
   148  		sysd = systemd.NewEmulationMode(opts.RootDir)
   149  	} else {
   150  		sysd = systemd.New(systemd.SystemMode, &sysdLogger{})
   151  	}
   152  
   153  	// some services are special
   154  	switch serviceName {
   155  	case "ssh.service":
   156  		return switchDisableSSHService(sysd, serviceName, disabled, opts)
   157  	case "console-conf@*":
   158  		return switchDisableConsoleConfService(sysd, serviceName, disabled, opts)
   159  	}
   160  
   161  	if opts == nil {
   162  		// ignore the service if not installed
   163  		status, err := sysd.Status(serviceName)
   164  		if err != nil {
   165  			return err
   166  		}
   167  		if len(status) != 1 {
   168  			return fmt.Errorf("internal error: expected status of service %s, got %v", serviceName, status)
   169  		}
   170  		if !status[0].Installed {
   171  			// ignore
   172  			return nil
   173  		}
   174  	}
   175  
   176  	if disabled {
   177  		if opts == nil {
   178  			if err := sysd.Disable(serviceName); err != nil {
   179  				return err
   180  			}
   181  		}
   182  		if err := sysd.Mask(serviceName); err != nil {
   183  			return err
   184  		}
   185  		if opts == nil {
   186  			return sysd.Stop(serviceName, 5*time.Minute)
   187  		}
   188  	} else {
   189  		if err := sysd.Unmask(serviceName); err != nil {
   190  			return err
   191  		}
   192  		if opts == nil {
   193  			if err := sysd.Enable(serviceName); err != nil {
   194  				return err
   195  			}
   196  		}
   197  		if opts == nil {
   198  			return sysd.Start(serviceName)
   199  		}
   200  	}
   201  	return nil
   202  }
   203  
   204  // services that can be disabled
   205  func handleServiceDisableConfiguration(_ sysconfig.Device, tr config.ConfGetter, opts *fsOnlyContext) error {
   206  	for _, service := range services {
   207  		optionName := fmt.Sprintf("service.%s.disable", service.configName)
   208  		outputStr, err := coreCfg(tr, optionName)
   209  		if err != nil {
   210  			return err
   211  		}
   212  		if outputStr != "" {
   213  			var disabled bool
   214  			switch outputStr {
   215  			case "true":
   216  				disabled = true
   217  			case "false":
   218  				disabled = false
   219  			default:
   220  				return fmt.Errorf("option %q has invalid value %q", optionName, outputStr)
   221  			}
   222  
   223  			if err := switchDisableService(service.systemdName, disabled, opts); err != nil {
   224  				return err
   225  			}
   226  		}
   227  	}
   228  
   229  	return nil
   230  }