Initial implementation of arca, a LUKS volume manager.
Go CLI using cobra with mount, unmount, status, and init subcommands. Unlocks via udisks2 D-Bus (passphrase/keyfile) or cryptsetup (FIDO2/TPM2) with ordered method fallback. Includes NixOS-specific LD_LIBRARY_PATH injection for systemd cryptsetup token plugins. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
100
internal/config/config.go
Normal file
100
internal/config/config.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config holds the arca configuration.
|
||||
type Config struct {
|
||||
Devices map[string]DeviceConfig `yaml:"devices"`
|
||||
}
|
||||
|
||||
// DeviceConfig holds per-device settings.
|
||||
type DeviceConfig struct {
|
||||
UUID string `yaml:"uuid"`
|
||||
Mountpoint string `yaml:"mountpoint,omitempty"`
|
||||
Methods []string `yaml:"methods,omitempty"`
|
||||
Keyfile string `yaml:"keyfile,omitempty"`
|
||||
}
|
||||
|
||||
// ResolvedDevice holds the effective settings for a device lookup.
|
||||
type ResolvedDevice struct {
|
||||
UUID string
|
||||
Mountpoint string
|
||||
Methods []string
|
||||
Keyfile string
|
||||
}
|
||||
|
||||
// Load reads the config file. Returns an empty config if the file doesn't exist.
|
||||
func Load() *Config {
|
||||
cfg := &Config{
|
||||
Devices: make(map[string]DeviceConfig),
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath())
|
||||
if err != nil {
|
||||
return cfg
|
||||
}
|
||||
|
||||
yaml.Unmarshal(data, cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// ResolveDevice looks up by alias name first, then checks if the argument
|
||||
// is a device path matching a known alias (e.g. "/dev/sda1" matches "sda1").
|
||||
// If nothing matches, returns defaults for a bare device path.
|
||||
func (c *Config) ResolveDevice(nameOrPath string) ResolvedDevice {
|
||||
// Direct alias match.
|
||||
if dev, ok := c.Devices[nameOrPath]; ok {
|
||||
return resolvedFrom(dev)
|
||||
}
|
||||
|
||||
// Match device path against aliases: "/dev/sda1" matches alias "sda1".
|
||||
base := filepath.Base(nameOrPath)
|
||||
if dev, ok := c.Devices[base]; ok {
|
||||
return resolvedFrom(dev)
|
||||
}
|
||||
|
||||
return ResolvedDevice{
|
||||
Methods: []string{"passphrase"},
|
||||
}
|
||||
}
|
||||
|
||||
func resolvedFrom(dev DeviceConfig) ResolvedDevice {
|
||||
methods := dev.Methods
|
||||
if len(methods) == 0 {
|
||||
methods = []string{"passphrase"}
|
||||
}
|
||||
return ResolvedDevice{
|
||||
UUID: dev.UUID,
|
||||
Mountpoint: dev.Mountpoint,
|
||||
Methods: methods,
|
||||
Keyfile: dev.Keyfile,
|
||||
}
|
||||
}
|
||||
|
||||
// AliasFor returns the config alias for a given UUID, or "" if none.
|
||||
func (c *Config) AliasFor(uuid string) string {
|
||||
for name, dev := range c.Devices {
|
||||
if dev.UUID == uuid {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Path returns the config file path, respecting XDG_CONFIG_HOME.
|
||||
func Path() string {
|
||||
return configPath()
|
||||
}
|
||||
|
||||
func configPath() string {
|
||||
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
|
||||
return filepath.Join(dir, "arca", "config.yaml")
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".config", "arca", "config.yaml")
|
||||
}
|
||||
133
internal/cryptsetup/cryptsetup.go
Normal file
133
internal/cryptsetup/cryptsetup.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package cryptsetup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Open opens a LUKS device using cryptsetup with token-based unlock.
|
||||
func Open(devicePath, mapperName string) error {
|
||||
args := withTokenPluginEnv([]string{"cryptsetup", "open", devicePath, mapperName, "--token-only"})
|
||||
args = withPrivilege(args)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("cryptsetup open --token-only: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes a LUKS mapping.
|
||||
func Close(mapperName string) error {
|
||||
args := []string{"cryptsetup", "close", mapperName}
|
||||
args = withPrivilege(args)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("cryptsetup close: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount mounts a device at the given mountpoint using privileged mount.
|
||||
// If mountpoint is empty, mounts under /mnt/<basename>.
|
||||
func Mount(devicePath, mountpoint string) (string, error) {
|
||||
if mountpoint == "" {
|
||||
mountpoint = "/mnt/" + filepath.Base(devicePath)
|
||||
}
|
||||
|
||||
mkdirArgs := withPrivilege([]string{"mkdir", "-p", mountpoint})
|
||||
if err := exec.Command(mkdirArgs[0], mkdirArgs[1:]...).Run(); err != nil {
|
||||
return "", fmt.Errorf("creating mountpoint: %w", err)
|
||||
}
|
||||
|
||||
args := withPrivilege([]string{"mount", devicePath, mountpoint})
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("mount %s: %w", devicePath, err)
|
||||
}
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
// Unmount unmounts the given mountpoint using privileged umount.
|
||||
func Unmount(mountpoint string) error {
|
||||
args := withPrivilege([]string{"umount", mountpoint})
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("umount %s: %w", mountpoint, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapperName returns "arca-<dev>" from a device path, e.g. "/dev/sda1" -> "arca-sda1".
|
||||
func MapperName(devicePath string) string {
|
||||
return "arca-" + filepath.Base(devicePath)
|
||||
}
|
||||
|
||||
// withTokenPluginEnv wraps the command with `env LD_LIBRARY_PATH=...` if
|
||||
// the systemd cryptsetup token plugins are found.
|
||||
//
|
||||
// On NixOS, cryptsetup uses dlopen to load token plugins, but glibc only
|
||||
// searches the binary's baked-in RUNPATH — which doesn't include the
|
||||
// systemd plugin directory. LD_LIBRARY_PATH is the only reliable way to
|
||||
// make dlopen find them.
|
||||
//
|
||||
// We inject it via `env` rather than cmd.Env so it survives privilege
|
||||
// escalation through doas/sudo.
|
||||
func withTokenPluginEnv(args []string) []string {
|
||||
pluginDir := findTokenPluginDir()
|
||||
if pluginDir == "" {
|
||||
return args
|
||||
}
|
||||
return append([]string{"env", "LD_LIBRARY_PATH=" + pluginDir}, args...)
|
||||
}
|
||||
|
||||
// findTokenPluginDir locates the directory containing libcryptsetup-token-*.so.
|
||||
// It checks the NixOS system profile first, then falls back to resolving
|
||||
// from the systemd-cryptenroll binary.
|
||||
func findTokenPluginDir() string {
|
||||
// NixOS stable symlink — survives rebuilds.
|
||||
const nixSystemPath = "/run/current-system/sw/lib/cryptsetup"
|
||||
if hasTokenPlugins(nixSystemPath) {
|
||||
return nixSystemPath
|
||||
}
|
||||
|
||||
// Fallback: derive from systemd-cryptenroll location.
|
||||
if bin, err := exec.LookPath("systemd-cryptenroll"); err == nil {
|
||||
if resolved, err := filepath.EvalSymlinks(bin); err == nil {
|
||||
dir := filepath.Join(filepath.Dir(filepath.Dir(resolved)), "lib", "cryptsetup")
|
||||
if hasTokenPlugins(dir) {
|
||||
return dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func hasTokenPlugins(dir string) bool {
|
||||
matches, _ := filepath.Glob(filepath.Join(dir, "libcryptsetup-token-*.so"))
|
||||
return len(matches) > 0
|
||||
}
|
||||
|
||||
func withPrivilege(args []string) []string {
|
||||
if _, err := exec.LookPath("doas"); err == nil {
|
||||
return append([]string{"doas"}, args...)
|
||||
}
|
||||
if _, err := exec.LookPath("sudo"); err == nil {
|
||||
return append([]string{"sudo"}, args...)
|
||||
}
|
||||
return args
|
||||
}
|
||||
177
internal/udisks/client.go
Normal file
177
internal/udisks/client.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package udisks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Client communicates with udisks2 over D-Bus.
|
||||
type Client struct {
|
||||
conn *dbus.Conn
|
||||
}
|
||||
|
||||
// NewClient connects to the system D-Bus and returns a udisks2 client.
|
||||
func NewClient() (*Client, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connecting to system bus: %w", err)
|
||||
}
|
||||
return &Client{conn: conn}, nil
|
||||
}
|
||||
|
||||
// Close closes the D-Bus connection.
|
||||
func (c *Client) Close() {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
// FindDevice finds a block device by UUID (if non-empty) or device path.
|
||||
func (c *Client) FindDevice(uuid, pathOrName string) (*BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uuid != "" {
|
||||
for i := range devices {
|
||||
if devices[i].UUID == uuid {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no device with UUID %s", uuid)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
if devices[i].DevicePath == pathOrName {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("device %s not found", pathOrName)
|
||||
}
|
||||
|
||||
// ListEncryptedDevices returns all block devices with the Encrypted interface.
|
||||
func (c *Client) ListEncryptedDevices() ([]BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var encrypted []BlockDevice
|
||||
for _, d := range devices {
|
||||
if d.HasEncrypted {
|
||||
encrypted = append(encrypted, d)
|
||||
}
|
||||
}
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// CleartextDevice finds the unlocked cleartext device for a LUKS device.
|
||||
func (c *Client) CleartextDevice(dev *BlockDevice) (*BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
if devices[i].CryptoBackingDevice == dev.ObjectPath {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no cleartext device for %s (is it unlocked?)", dev.DevicePath)
|
||||
}
|
||||
|
||||
// MountPoint returns the current mount point of a device, or "" if not mounted.
|
||||
func (c *Client) MountPoint(dev *BlockDevice) (string, error) {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
variant, err := obj.GetProperty(ifaceFilesystem + ".MountPoints")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mountPoints, ok := variant.Value().([][]byte)
|
||||
if !ok || len(mountPoints) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return string(bytes.TrimRight(mountPoints[0], "\x00")), nil
|
||||
}
|
||||
|
||||
// DeviceAtPath returns the block device at the given D-Bus object path.
|
||||
func (c *Client) DeviceAtPath(path dbus.ObjectPath) (*BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range devices {
|
||||
if devices[i].ObjectPath == path {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("device at %s not found", path)
|
||||
}
|
||||
|
||||
// RootBackingDevices returns the object paths of LUKS devices that back
|
||||
// the root filesystem. Used to exclude them from init config generation.
|
||||
func (c *Client) RootBackingDevices() ([]dbus.ObjectPath, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var backing []dbus.ObjectPath
|
||||
for i := range devices {
|
||||
if !devices[i].HasFilesystem {
|
||||
continue
|
||||
}
|
||||
mp, err := c.MountPoint(&devices[i])
|
||||
if err != nil || mp != "/" {
|
||||
continue
|
||||
}
|
||||
if devices[i].CryptoBackingDevice != "" && devices[i].CryptoBackingDevice != "/" {
|
||||
backing = append(backing, devices[i].CryptoBackingDevice)
|
||||
}
|
||||
}
|
||||
return backing, nil
|
||||
}
|
||||
|
||||
func (c *Client) listBlockDevices() ([]BlockDevice, error) {
|
||||
obj := c.conn.Object(busName, dbus.ObjectPath(objectPrefix))
|
||||
var managed map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
err := obj.Call(ifaceObjectManager+".GetManagedObjects", 0).Store(&managed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing devices: %w", err)
|
||||
}
|
||||
|
||||
var devices []BlockDevice
|
||||
for path, ifaces := range managed {
|
||||
blockProps, ok := ifaces[ifaceBlock]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
dev := BlockDevice{ObjectPath: path}
|
||||
|
||||
if v, ok := blockProps["Device"]; ok {
|
||||
if bs, ok := v.Value().([]byte); ok {
|
||||
dev.DevicePath = string(bytes.TrimRight(bs, "\x00"))
|
||||
}
|
||||
}
|
||||
if v, ok := blockProps["IdUUID"]; ok {
|
||||
if s, ok := v.Value().(string); ok {
|
||||
dev.UUID = s
|
||||
}
|
||||
}
|
||||
if v, ok := blockProps["CryptoBackingDevice"]; ok {
|
||||
if p, ok := v.Value().(dbus.ObjectPath); ok {
|
||||
dev.CryptoBackingDevice = p
|
||||
}
|
||||
}
|
||||
|
||||
_, dev.HasEncrypted = ifaces[ifaceEncrypted]
|
||||
_, dev.HasFilesystem = ifaces[ifaceFilesystem]
|
||||
|
||||
devices = append(devices, dev)
|
||||
}
|
||||
return devices, nil
|
||||
}
|
||||
43
internal/udisks/encrypt.go
Normal file
43
internal/udisks/encrypt.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package udisks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Unlock unlocks a LUKS device with a passphrase and returns the cleartext device.
|
||||
func (c *Client) Unlock(dev *BlockDevice, passphrase string) (*BlockDevice, error) {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
|
||||
var cleartextPath dbus.ObjectPath
|
||||
err := obj.Call(ifaceEncrypted+".Unlock", 0, passphrase, noOptions()).Store(&cleartextPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unlocking %s: %w", dev.DevicePath, err)
|
||||
}
|
||||
|
||||
return c.DeviceAtPath(cleartextPath)
|
||||
}
|
||||
|
||||
// UnlockWithKeyfile unlocks a LUKS device with keyfile contents.
|
||||
func (c *Client) UnlockWithKeyfile(dev *BlockDevice, keyContents []byte) (*BlockDevice, error) {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
|
||||
options := map[string]dbus.Variant{
|
||||
"keyfile_contents": dbus.MakeVariant(keyContents),
|
||||
}
|
||||
|
||||
var cleartextPath dbus.ObjectPath
|
||||
err := obj.Call(ifaceEncrypted+".Unlock", 0, "", options).Store(&cleartextPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unlocking %s with keyfile: %w", dev.DevicePath, err)
|
||||
}
|
||||
|
||||
return c.DeviceAtPath(cleartextPath)
|
||||
}
|
||||
|
||||
// Lock locks a LUKS device.
|
||||
func (c *Client) Lock(dev *BlockDevice) error {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
return obj.Call(ifaceEncrypted+".Lock", 0, noOptions()).Err
|
||||
}
|
||||
23
internal/udisks/mount.go
Normal file
23
internal/udisks/mount.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package udisks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Mount mounts a filesystem and returns the mount point chosen by udisks2.
|
||||
func (c *Client) Mount(dev *BlockDevice) (string, error) {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
|
||||
var mountpoint string
|
||||
err := obj.Call(ifaceFilesystem+".Mount", 0, noOptions()).Store(&mountpoint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("mounting %s: %w", dev.DevicePath, err)
|
||||
}
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
// Unmount unmounts a filesystem.
|
||||
func (c *Client) Unmount(dev *BlockDevice) error {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
return obj.Call(ifaceFilesystem+".Unmount", 0, noOptions()).Err
|
||||
}
|
||||
27
internal/udisks/types.go
Normal file
27
internal/udisks/types.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package udisks
|
||||
|
||||
import "github.com/godbus/dbus/v5"
|
||||
|
||||
const (
|
||||
busName = "org.freedesktop.UDisks2"
|
||||
objectPrefix = "/org/freedesktop/UDisks2"
|
||||
|
||||
ifaceObjectManager = "org.freedesktop.DBus.ObjectManager"
|
||||
ifaceBlock = "org.freedesktop.UDisks2.Block"
|
||||
ifaceEncrypted = "org.freedesktop.UDisks2.Encrypted"
|
||||
ifaceFilesystem = "org.freedesktop.UDisks2.Filesystem"
|
||||
)
|
||||
|
||||
// BlockDevice represents a udisks2 block device.
|
||||
type BlockDevice struct {
|
||||
ObjectPath dbus.ObjectPath
|
||||
DevicePath string // e.g., "/dev/sda1"
|
||||
UUID string
|
||||
HasEncrypted bool
|
||||
HasFilesystem bool
|
||||
CryptoBackingDevice dbus.ObjectPath
|
||||
}
|
||||
|
||||
func noOptions() map[string]dbus.Variant {
|
||||
return make(map[string]dbus.Variant)
|
||||
}
|
||||
111
internal/unlock/unlock.go
Normal file
111
internal/unlock/unlock.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package unlock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.wntrmute.dev/kyle/arca/internal/cryptsetup"
|
||||
"git.wntrmute.dev/kyle/arca/internal/udisks"
|
||||
)
|
||||
|
||||
// Result holds the outcome of a successful unlock.
|
||||
type Result struct {
|
||||
Device *udisks.BlockDevice
|
||||
Privileged bool // true if unlock required root (cryptsetup path)
|
||||
}
|
||||
|
||||
// Options configures the unlock behavior.
|
||||
type Options struct {
|
||||
ReadPassphrase func() (string, error)
|
||||
KeyfilePath string
|
||||
}
|
||||
|
||||
// Unlocker tries unlock methods in sequence.
|
||||
type Unlocker struct {
|
||||
client *udisks.Client
|
||||
opts Options
|
||||
}
|
||||
|
||||
// New creates a new Unlocker.
|
||||
func New(client *udisks.Client, opts Options) *Unlocker {
|
||||
return &Unlocker{client: client, opts: opts}
|
||||
}
|
||||
|
||||
// Unlock tries each method in order and returns the result on first success.
|
||||
func (u *Unlocker) Unlock(dev *udisks.BlockDevice, methods []string) (*Result, error) {
|
||||
var errs []error
|
||||
for _, method := range methods {
|
||||
res, err := u.tryMethod(dev, method)
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
errs = append(errs, fmt.Errorf("%s: %w", method, err))
|
||||
}
|
||||
return nil, fmt.Errorf("all unlock methods failed:\n%w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
func (u *Unlocker) tryMethod(dev *udisks.BlockDevice, method string) (*Result, error) {
|
||||
switch method {
|
||||
case "passphrase":
|
||||
ct, err := u.unlockPassphrase(dev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Result{Device: ct, Privileged: false}, nil
|
||||
case "keyfile":
|
||||
ct, err := u.unlockKeyfile(dev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Result{Device: ct, Privileged: false}, nil
|
||||
case "fido2", "tpm2":
|
||||
ct, err := u.unlockCryptsetup(dev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Result{Device: ct, Privileged: true}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown unlock method: %s", method)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unlocker) unlockPassphrase(dev *udisks.BlockDevice) (*udisks.BlockDevice, error) {
|
||||
if u.opts.ReadPassphrase == nil {
|
||||
return nil, fmt.Errorf("no passphrase reader configured")
|
||||
}
|
||||
pass, err := u.opts.ReadPassphrase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u.client.Unlock(dev, pass)
|
||||
}
|
||||
|
||||
func (u *Unlocker) unlockKeyfile(dev *udisks.BlockDevice) (*udisks.BlockDevice, error) {
|
||||
if u.opts.KeyfilePath == "" {
|
||||
return nil, fmt.Errorf("no keyfile configured")
|
||||
}
|
||||
contents, err := os.ReadFile(u.opts.KeyfilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading keyfile: %w", err)
|
||||
}
|
||||
return u.client.UnlockWithKeyfile(dev, contents)
|
||||
}
|
||||
|
||||
func (u *Unlocker) unlockCryptsetup(dev *udisks.BlockDevice) (*udisks.BlockDevice, error) {
|
||||
name := cryptsetup.MapperName(dev.DevicePath)
|
||||
if err := cryptsetup.Open(dev.DevicePath, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for udisks2 to pick up the new dm device.
|
||||
for i := 0; i < 10; i++ {
|
||||
ct, err := u.client.CleartextDevice(dev)
|
||||
if err == nil {
|
||||
return ct, nil
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
return nil, fmt.Errorf("timed out waiting for udisks2 to discover cleartext device")
|
||||
}
|
||||
Reference in New Issue
Block a user