2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2023-01-19 09:57:50 -05:00
|
|
|
// Package systemd configures systemd units for encrypted volumes.
|
2022-08-15 08:50:03 -04:00
|
|
|
package systemd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"github.com/spf13/afero"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
systemdRuntimeUnitPath = "/run/systemd/system"
|
|
|
|
systemdUnitName = "systemd-cryptsetup@state.service"
|
|
|
|
systemdDeviceRequires = "dev-mapper-state.device.requires"
|
|
|
|
systemdCryptsetupTargetRequires = "cryptsetup.target.requires"
|
|
|
|
)
|
|
|
|
|
|
|
|
// CryptsetupUnitGenerator generates systemd-cryptsetup@.service unit files.
|
|
|
|
type CryptsetupUnitGenerator struct {
|
|
|
|
fs afero.Afero
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new CryptsetupUnitGenerator.
|
|
|
|
func New(fs afero.Afero) CryptsetupUnitGenerator {
|
|
|
|
return CryptsetupUnitGenerator{fs: fs}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate generates a systemd-cryptsetup@.service unit file and its dependencies.
|
|
|
|
func (g CryptsetupUnitGenerator) Generate(volumeName, encryptedDevice, keyFile, options string) error {
|
|
|
|
unitContents, err := g.configureUnit(volumeName, encryptedDevice, keyFile, options)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return g.writeUnits(unitContents)
|
|
|
|
}
|
|
|
|
|
|
|
|
// configureUnit generates the systemd-cryptsetup@.service unit file contents.
|
|
|
|
func (g CryptsetupUnitGenerator) configureUnit(volumeName, encryptedDevice, keyFile, options string) (string, error) {
|
|
|
|
deviceUnit := strings.ReplaceAll(encryptedDevice, "/", "-") + ".device"
|
|
|
|
deviceUnit = strings.TrimPrefix(deviceUnit, "-")
|
|
|
|
templ, err := template.New("").Parse(`[Unit]
|
|
|
|
Description=Cryptography Setup for %I
|
|
|
|
Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)
|
|
|
|
DefaultDependencies=no
|
|
|
|
IgnoreOnIsolate=true
|
|
|
|
After=cryptsetup-pre.target systemd-udevd-kernel.socket
|
|
|
|
Before=blockdev@dev-mapper-%i.target
|
|
|
|
Wants=blockdev@dev-mapper-%i.target
|
|
|
|
Conflicts=umount.target
|
|
|
|
Before=cryptsetup.target
|
|
|
|
RequiresMountsFor={{.keyFile}}
|
|
|
|
BindsTo={{.deviceUnit}}
|
|
|
|
After={{.deviceUnit}}
|
|
|
|
Before=umount.target
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
Type=oneshot
|
|
|
|
RemainAfterExit=yes
|
|
|
|
TimeoutSec=0
|
|
|
|
KeyringMode=shared
|
|
|
|
OOMScoreAdjust=500
|
|
|
|
ExecStart=/usr/lib/systemd/systemd-cryptsetup attach '{{.volumeName}}' '{{.encryptedDevice}}' '{{.keyFile}}' '{{.options}}'
|
|
|
|
ExecStop=/usr/lib/systemd/systemd-cryptsetup detach '{{.volumeName}}'
|
|
|
|
`)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err = templ.Execute(&buf, map[string]string{
|
|
|
|
"volumeName": volumeName,
|
|
|
|
"encryptedDevice": encryptedDevice,
|
|
|
|
"deviceUnit": deviceUnit,
|
|
|
|
"keyFile": keyFile,
|
|
|
|
"options": options,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return buf.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeUnits writes the unit file and its dependencies to the filesystem.
|
|
|
|
func (g CryptsetupUnitGenerator) writeUnits(unitContents string) error {
|
|
|
|
if err := g.fs.MkdirAll(systemdRuntimeUnitPath, os.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := g.fs.Mkdir(filepath.Join(systemdRuntimeUnitPath, systemdDeviceRequires), os.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := g.fs.Mkdir(filepath.Join(systemdRuntimeUnitPath, systemdCryptsetupTargetRequires), os.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
unitPath := filepath.Join(systemdRuntimeUnitPath, systemdUnitName)
|
|
|
|
if err := g.fs.WriteFile(unitPath, []byte(unitContents), 0o444); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if symlinker, ok := g.fs.Fs.(afero.Symlinker); ok {
|
|
|
|
if err := symlinker.SymlinkIfPossible(unitPath, filepath.Join(systemdRuntimeUnitPath, systemdDeviceRequires, systemdUnitName)); err != nil {
|
|
|
|
return fmt.Errorf("creating device symlink: %w", err)
|
|
|
|
}
|
|
|
|
if err := symlinker.SymlinkIfPossible(unitPath, filepath.Join(systemdRuntimeUnitPath, systemdCryptsetupTargetRequires, systemdUnitName)); err != nil {
|
|
|
|
return fmt.Errorf("creating cryptsetup target symlink: %w", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return errors.New("fs does not support symlinks")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|