Skip to content

Commit

Permalink
[teleport-update] needrestart and systemd drop-in (#49806)
Browse files Browse the repository at this point in the history
* wip

* Add more config

* nit

* feedback
  • Loading branch information
sclevine authored Dec 10, 2024
1 parent c6d978b commit bc68383
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 116 deletions.
156 changes: 105 additions & 51 deletions lib/autoupdate/agent/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"text/template"

"github.com/google/renameio/v2"
Expand All @@ -37,13 +38,14 @@ import (

// Base paths for constructing namespaced directories.
const (
teleportOptDir = "/opt/teleport"
versionsDirName = "versions"
systemdAdminDir = "/etc/systemd/system"
systemdPIDFile = "/run/teleport.pid"
defaultNamespace = "default"
systemNamespace = "system"
lockFileName = "update.lock"
teleportOptDir = "/opt/teleport"
systemdAdminDir = "/etc/systemd/system"
systemdPIDFile = "/run/teleport.pid"
needrestartConfDir = "/etc/needrestart/conf.d"
versionsDirName = "versions"
lockFileName = "update.lock"
defaultNamespace = "default"
systemNamespace = "system"
)

const (
Expand All @@ -68,9 +70,28 @@ RandomizedDelaySec=1m
[Install]
WantedBy={{.TeleportService}}
`
teleportDropInTemplate = `# teleport-update
# DO NOT EDIT THIS FILE
[Service]
Environment=TELEPORT_UPDATE_CONFIG_FILE={{.UpdaterConfigFile}}
`
// This configuration sets the default value for needrestart-trigger automatic restarts for teleport.service to disabled.
// Users may still choose to enable needrestart for teleport.service when installing packaging interactively (or via dpkg config),
// but doing so will result in a hard restart that disconnects the agent whenever any dependent libraries are updated.
// Other network services, like openvpn, follow this pattern.
// It is possible to configure needrestart to trigger a soft restart (via restart.d script), but given that Teleport subprocesses
// can use a wide variety of installed binaries (when executed by the user), this could trigger many unexpected reloads.
needrestartConfTemplate = `$nrconf{override_rc}{qr(^{{replace .TeleportService "." "\\."}})} = 0;
`
)

type confParams struct {
TeleportService string
UpdaterCommand string
UpdaterConfigFile string
}

// Namespace represents a namespace within various system paths for a isolated installation of Teleport.
type Namespace struct {
log *slog.Logger
Expand Down Expand Up @@ -98,6 +119,10 @@ type Namespace struct {
updaterServiceFile string
// updaterTimerFile is the systemd timer path for the updater
updaterTimerFile string
// dropInFile is the Teleport systemd drop-in path extending Teleport
dropInFile string
// needrestartConfFile is the path to needrestart configuration for Teleport
needrestartConfFile string
}

var alphanum = regexp.MustCompile("^[a-zA-Z0-9-]*$")
Expand All @@ -120,19 +145,21 @@ func NewNamespace(log *slog.Logger, name, dataDir, linkDir string) (*Namespace,
linkDir = DefaultLinkDir
}
return &Namespace{
log: log,
name: name,
dataDir: dataDir,
linkDir: linkDir,
versionsDir: filepath.Join(namespaceDir(name), versionsDirName),
serviceFile: filepath.Join("/", serviceDir, serviceName),
configFile: defaults.ConfigFilePath,
pidFile: systemdPIDFile,
updaterLockFile: filepath.Join(namespaceDir(name), lockFileName),
updaterConfigFile: filepath.Join(namespaceDir(name), updateConfigName),
updaterBinFile: filepath.Join(linkDir, BinaryName),
updaterServiceFile: filepath.Join(systemdAdminDir, BinaryName+".service"),
updaterTimerFile: filepath.Join(systemdAdminDir, BinaryName+".timer"),
log: log,
name: name,
dataDir: dataDir,
linkDir: linkDir,
versionsDir: filepath.Join(namespaceDir(name), versionsDirName),
serviceFile: filepath.Join("/", serviceDir, serviceName),
configFile: defaults.ConfigFilePath,
pidFile: systemdPIDFile,
updaterLockFile: filepath.Join(namespaceDir(name), lockFileName),
updaterConfigFile: filepath.Join(namespaceDir(name), updateConfigName),
updaterBinFile: filepath.Join(linkDir, BinaryName),
updaterServiceFile: filepath.Join(systemdAdminDir, BinaryName+".service"),
updaterTimerFile: filepath.Join(systemdAdminDir, BinaryName+".timer"),
dropInFile: filepath.Join(systemdAdminDir, "teleport.service.d", BinaryName+".conf"),
needrestartConfFile: filepath.Join(needrestartConfDir, BinaryName+".conf"),
}, nil
}

Expand All @@ -144,19 +171,21 @@ func NewNamespace(log *slog.Logger, name, dataDir, linkDir string) (*Namespace,
linkDir = filepath.Join(namespaceDir(name), "bin")
}
return &Namespace{
log: log,
name: name,
dataDir: dataDir,
linkDir: linkDir,
versionsDir: filepath.Join(namespaceDir(name), versionsDirName),
serviceFile: filepath.Join(systemdAdminDir, prefix+".service"),
configFile: filepath.Join(filepath.Dir(defaults.ConfigFilePath), prefix+".yaml"),
pidFile: filepath.Join(filepath.Dir(systemdPIDFile), prefix+".pid"),
updaterLockFile: filepath.Join(namespaceDir(name), lockFileName),
updaterConfigFile: filepath.Join(namespaceDir(name), updateConfigName),
updaterBinFile: filepath.Join(linkDir, BinaryName),
updaterServiceFile: filepath.Join(systemdAdminDir, BinaryName+"_"+name+".service"),
updaterTimerFile: filepath.Join(systemdAdminDir, BinaryName+"_"+name+".timer"),
log: log,
name: name,
dataDir: dataDir,
linkDir: linkDir,
versionsDir: filepath.Join(namespaceDir(name), versionsDirName),
serviceFile: filepath.Join(systemdAdminDir, prefix+".service"),
configFile: filepath.Join(filepath.Dir(defaults.ConfigFilePath), prefix+".yaml"),
pidFile: filepath.Join(filepath.Dir(systemdPIDFile), prefix+".pid"),
updaterLockFile: filepath.Join(namespaceDir(name), lockFileName),
updaterConfigFile: filepath.Join(namespaceDir(name), updateConfigName),
updaterBinFile: filepath.Join(linkDir, BinaryName),
updaterServiceFile: filepath.Join(systemdAdminDir, BinaryName+"_"+name+".service"),
updaterTimerFile: filepath.Join(systemdAdminDir, BinaryName+"_"+name+".timer"),
dropInFile: filepath.Join(systemdAdminDir, "teleport.service.d", BinaryName+"_"+name+".conf"),
needrestartConfFile: filepath.Join(needrestartConfDir, BinaryName+"_"+name+".conf"),
}, nil
}

Expand All @@ -178,7 +207,7 @@ func (ns *Namespace) Init() (lockFile string, err error) {
// Setup installs service and timer files for the teleport-update binary.
// Afterwords, Setup reloads systemd and enables the timer with --now.
func (ns *Namespace) Setup(ctx context.Context) error {
err := ns.writeConfigFiles()
err := ns.writeConfigFiles(ctx)
if err != nil {
return trace.Wrap(err, "failed to write teleport-update systemd config files")
}
Expand All @@ -204,11 +233,15 @@ func (ns *Namespace) Teardown(ctx context.Context) error {
if err := svc.Disable(ctx); err != nil {
return trace.Wrap(err, "failed to disable teleport-update systemd timer")
}
if err := os.Remove(ns.updaterServiceFile); err != nil && !errors.Is(err, fs.ErrNotExist) {
return trace.Wrap(err, "failed to remove teleport-update systemd service")
}
if err := os.Remove(ns.updaterTimerFile); err != nil && !errors.Is(err, fs.ErrNotExist) {
return trace.Wrap(err, "failed to remove teleport-update systemd timer")
for _, p := range []string{
ns.updaterServiceFile,
ns.updaterTimerFile,
ns.dropInFile,
ns.needrestartConfFile,
} {
if err := os.Remove(p); err != nil && !errors.Is(err, fs.ErrNotExist) {
return trace.Wrap(err, "failed to remove %s", filepath.Base(p))
}
}
if err := svc.Sync(ctx); err != nil {
return trace.Wrap(err, "failed to sync systemd config")
Expand All @@ -219,27 +252,44 @@ func (ns *Namespace) Teardown(ctx context.Context) error {
return nil
}

func (ns *Namespace) writeConfigFiles() error {
func (ns *Namespace) writeConfigFiles(ctx context.Context) error {
var args string
if ns.name != "" {
args = " --install-suffix=" + ns.name
}
err := writeTemplate(
ns.updaterServiceFile, updateServiceTemplate,
struct{ UpdaterCommand string }{
ns.updaterBinFile + args + " update",
},
)
teleportService := filepath.Base(ns.serviceFile)
params := confParams{
TeleportService: teleportService,
UpdaterCommand: ns.updaterBinFile + args + " update",
UpdaterConfigFile: ns.updaterConfigFile,
}
err := writeTemplate(ns.updaterServiceFile, updateServiceTemplate, params)
if err != nil {
return trace.Wrap(err)
}
err = writeTemplate(
ns.updaterTimerFile, updateTimerTemplate,
struct{ TeleportService string }{filepath.Base(ns.serviceFile)},
)
err = writeTemplate(ns.updaterTimerFile, updateTimerTemplate, params)
if err != nil {
return trace.Wrap(err)
}
err = writeTemplate(ns.dropInFile, teleportDropInTemplate, params)
if err != nil {
return trace.Wrap(err)
}
// Needrestart config is non-critical for updater functionality.
_, err = os.Stat(filepath.Dir(ns.needrestartConfFile))
if os.IsNotExist(err) {
return nil // needrestart is not present
}
if err != nil {
ns.log.ErrorContext(ctx, "Unable to disable needrestart.", errorKey, err)
return nil
}
ns.log.InfoContext(ctx, "Disabling needrestart.", unitKey, teleportService)
err = writeTemplate(ns.needrestartConfFile, needrestartConfTemplate, params)
if err != nil {
ns.log.ErrorContext(ctx, "Unable to disable needrestart.", errorKey, err)
return nil
}
return nil
}

Expand All @@ -258,7 +308,11 @@ func writeTemplate(path, t string, values any) error {
}
defer f.Cleanup()

tmpl, err := template.New(file).Parse(t)
tmpl, err := template.New(file).Funcs(template.FuncMap{
"replace": func(s, old, new string) string {
return strings.ReplaceAll(s, old, new)
},
}).Parse(t)
if err != nil {
return trace.Wrap(err)
}
Expand Down
Loading

0 comments on commit bc68383

Please sign in to comment.