linsk/vm/filemanager.go

248 lines
5.6 KiB
Go
Raw Normal View History

2023-08-25 15:12:19 +01:00
package vm
import (
"bytes"
2023-08-29 13:29:46 +01:00
"context"
2023-08-25 19:55:11 +01:00
"crypto/rand"
2023-08-25 16:54:58 +01:00
"fmt"
2023-08-25 19:55:11 +01:00
"log/slog"
"os"
2023-08-26 16:26:35 +01:00
"strings"
2023-08-25 19:55:11 +01:00
"sync"
"syscall"
2023-08-29 13:29:46 +01:00
"time"
2023-08-25 15:12:19 +01:00
2023-08-29 14:24:18 +01:00
"github.com/AlexSSD7/linsk/sshutil"
2023-08-26 09:16:52 +01:00
"github.com/AlexSSD7/linsk/utils"
2023-08-25 16:54:58 +01:00
"github.com/alessio/shellescape"
2023-08-25 15:12:19 +01:00
"github.com/pkg/errors"
2023-08-25 19:55:11 +01:00
"go.uber.org/multierr"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
2023-08-25 15:12:19 +01:00
)
type FileManager struct {
2023-08-25 19:55:11 +01:00
logger *slog.Logger
2023-08-27 13:44:57 +01:00
vm *VM
2023-08-25 15:12:19 +01:00
}
2023-08-27 13:44:57 +01:00
func NewFileManager(logger *slog.Logger, vm *VM) *FileManager {
2023-08-25 15:12:19 +01:00
return &FileManager{
2023-08-25 19:55:11 +01:00
logger: logger,
2023-08-27 13:44:57 +01:00
vm: vm,
2023-08-25 15:12:19 +01:00
}
}
func (fm *FileManager) Init() error {
2023-08-27 13:44:57 +01:00
sc, err := fm.vm.DialSSH()
2023-08-25 15:12:19 +01:00
if err != nil {
return errors.Wrap(err, "dial vm ssh")
}
2023-08-25 19:55:11 +01:00
defer func() { _ = sc.Close() }()
2023-08-29 14:24:18 +01:00
_, err = sshutil.RunSSHCmd(fm.vm.ctx, sc, "vgchange -ay")
2023-08-25 15:12:19 +01:00
if err != nil {
return errors.Wrap(err, "run vgchange cmd")
}
return nil
}
func (fm *FileManager) Lsblk() ([]byte, error) {
2023-08-27 13:44:57 +01:00
sc, err := fm.vm.DialSSH()
2023-08-25 15:12:19 +01:00
if err != nil {
return nil, errors.Wrap(err, "dial vm ssh")
}
2023-08-29 14:24:18 +01:00
ret, err := sshutil.RunSSHCmd(fm.vm.ctx, sc, "lsblk -o NAME,SIZE,FSTYPE,LABEL -e 7,11,2,253")
2023-08-25 15:12:19 +01:00
if err != nil {
return nil, errors.Wrap(err, "run lsblk")
}
2023-08-29 14:24:18 +01:00
return ret, nil
2023-08-25 15:12:19 +01:00
}
2023-08-25 16:54:58 +01:00
type MountOptions struct {
FSType string
2023-08-25 19:55:11 +01:00
LUKS bool
}
const luksDMName = "cryptmnt"
func (fm *FileManager) luksOpen(sc *ssh.Client, fullDevPath string) error {
lg := fm.logger.With("vm-path", fullDevPath)
2023-08-29 14:24:18 +01:00
return sshutil.NewSSHSessionWithDelayedTimeout(fm.vm.ctx, time.Second*15, sc, func(sess *ssh.Session, startTimeout func()) error {
stdinPipe, err := sess.StdinPipe()
if err != nil {
return errors.Wrap(err, "create vm ssh session stdin pipe")
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
stderrBuf := bytes.NewBuffer(nil)
sess.Stderr = stderrBuf
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
err = sess.Start("cryptsetup luksOpen " + shellescape.Quote(fullDevPath) + " " + luksDMName)
if err != nil {
return errors.Wrap(err, "start cryptsetup luksopen cmd")
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
lg.Info("Attempting to open a LUKS device")
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
_, err = os.Stderr.Write([]byte("Enter Password: "))
if err != nil {
return errors.Wrap(err, "write prompt to stderr")
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
pwd, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return errors.Wrap(err, "read luks password")
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
fmt.Print("\n")
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
// We start the timeout countdown now only to avoid timing out
// while the user is entering the password, or shortly after that.
startTimeout()
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
var wErr error
var wWG sync.WaitGroup
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
wWG.Add(1)
go func() {
defer wWG.Done()
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
_, err := stdinPipe.Write(pwd)
_, err2 := stdinPipe.Write([]byte("\n"))
wErr = errors.Wrap(multierr.Combine(err, err2), "write password to stdin")
}()
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
defer func() {
// Clear the memory up for security.
{
for i := 0; i < len(pwd); i++ {
pwd[i] = 0
}
2023-08-29 13:29:46 +01:00
2023-08-29 14:24:18 +01:00
// This is my paranoia.
_, _ = rand.Read(pwd)
2023-08-29 13:29:46 +01:00
_, _ = rand.Read(pwd)
}
2023-08-29 14:24:18 +01:00
}()
2023-08-29 13:29:46 +01:00
2023-08-29 14:24:18 +01:00
err = sess.Wait()
if err != nil {
if strings.Contains(stderrBuf.String(), "Not enough available memory to open a keyslot.") {
fm.logger.Warn("Detected not enough memory to open a LUKS device, please allocate more memory using --vm-mem-alloc flag.")
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
return utils.WrapErrWithLog(err, "wait for cryptsetup luksopen cmd to finish", stderrBuf.String())
2023-08-29 10:59:50 +01:00
}
2023-08-29 14:24:18 +01:00
lg.Info("LUKS device opened successfully")
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
_ = stdinPipe.Close()
wWG.Wait()
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
return wErr
})
2023-08-25 16:54:58 +01:00
}
func (fm *FileManager) Mount(devName string, mo MountOptions) error {
if devName == "" {
return fmt.Errorf("device name is empty")
}
// It does allow mapper/ prefix for mapped devices.
// This is to enable the support for LVM and LUKS.
if !utils.ValidateDevName(devName) {
return fmt.Errorf("bad device name")
}
2023-08-29 14:24:18 +01:00
// We're intentionally not calling filepath.Clean() as
// this causes unintended consequences when run on Windows.
// (Windows Go standard library treats the path as it's for
// Windows, but we're targeting a Linux VM.)
2023-08-28 11:35:57 +02:00
fullDevPath := "/dev/" + devName
2023-08-25 16:54:58 +01:00
if mo.FSType == "" {
return fmt.Errorf("fs type is empty")
}
2023-08-27 13:44:57 +01:00
sc, err := fm.vm.DialSSH()
2023-08-25 16:54:58 +01:00
if err != nil {
return errors.Wrap(err, "dial vm ssh")
}
2023-08-25 19:55:11 +01:00
defer func() { _ = sc.Close() }()
if mo.LUKS {
err = fm.luksOpen(sc, fullDevPath)
if err != nil {
return errors.Wrap(err, "luks open")
}
fullDevPath = "/dev/mapper/" + luksDMName
2023-08-25 16:54:58 +01:00
}
2023-08-29 14:24:18 +01:00
_, err = sshutil.RunSSHCmd(fm.vm.ctx, sc, "mount -t "+shellescape.Quote(mo.FSType)+" "+shellescape.Quote(fullDevPath)+" /mnt")
2023-08-25 16:54:58 +01:00
if err != nil {
2023-08-29 14:24:18 +01:00
return errors.Wrap(err, "run mount cmd")
2023-08-25 16:54:58 +01:00
}
return nil
}
2023-08-26 16:26:35 +01:00
2023-08-29 14:24:18 +01:00
func (fm *FileManager) StartFTP(pwd string, passivePortStart uint16, passivePortCount uint16) error {
// This timeout is for the SCP client exclusively.
2023-08-29 13:29:46 +01:00
scpCtx, scpCtxCancel := context.WithTimeout(fm.vm.ctx, time.Second*5)
defer scpCtxCancel()
2023-08-27 13:44:57 +01:00
scpClient, err := fm.vm.DialSCP()
2023-08-26 16:26:35 +01:00
if err != nil {
return errors.Wrap(err, "dial scp")
}
defer scpClient.Close()
2023-08-29 10:00:12 +01:00
ftpdCfg := `anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
chroot_local_user=YES
allow_writeable_chroot=YES
listen=YES
seccomp_sandbox=NO
pasv_min_port=` + fmt.Sprint(passivePortStart) + `
pasv_max_port=` + fmt.Sprint(passivePortStart+passivePortCount) + `
pasv_address=127.0.0.1
`
2023-08-29 13:29:46 +01:00
err = scpClient.CopyFile(scpCtx, strings.NewReader(ftpdCfg), "/etc/vsftpd/vsftpd.conf", "0400")
2023-08-26 16:26:35 +01:00
if err != nil {
2023-08-29 10:00:12 +01:00
return errors.Wrap(err, "copy ftpd .conf file")
2023-08-26 16:26:35 +01:00
}
scpClient.Close()
2023-08-27 13:44:57 +01:00
sc, err := fm.vm.DialSSH()
2023-08-26 16:26:35 +01:00
if err != nil {
return errors.Wrap(err, "dial ssh")
}
defer func() { _ = sc.Close() }()
2023-08-29 14:24:18 +01:00
_, err = sshutil.RunSSHCmd(fm.vm.ctx, sc, "rc-update add vsftpd && rc-service vsftpd start")
2023-08-26 16:26:35 +01:00
if err != nil {
2023-08-29 14:24:18 +01:00
return errors.Wrap(err, "add and start ftpd service")
2023-08-26 16:26:35 +01:00
}
2023-08-29 14:24:18 +01:00
err = sshutil.ChangeUnixPass(fm.vm.ctx, sc, "linsk", pwd)
2023-08-26 16:26:35 +01:00
if err != nil {
2023-08-29 14:24:18 +01:00
return errors.Wrap(err, "change unix pass")
2023-08-26 16:26:35 +01:00
}
return nil
}