2023-08-25 15:12:19 +01:00
|
|
|
package vm
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
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-25 15:12:19 +01:00
|
|
|
|
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() }()
|
|
|
|
|
|
|
|
|
|
_, err = runSSHCmd(sc, "apk add util-linux lvm2")
|
2023-08-25 15:12:19 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "install utilities")
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 19:55:11 +01:00
|
|
|
_, err = runSSHCmd(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-25 19:55:11 +01:00
|
|
|
defer func() { _ = sc.Close() }()
|
|
|
|
|
|
2023-08-25 16:54:58 +01:00
|
|
|
sess, err := sc.NewSession()
|
2023-08-25 15:12:19 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "create new vm ssh session")
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 19:55:11 +01:00
|
|
|
defer func() { _ = sess.Close() }()
|
|
|
|
|
|
2023-08-25 15:12:19 +01:00
|
|
|
ret := new(bytes.Buffer)
|
|
|
|
|
|
|
|
|
|
sess.Stdout = ret
|
|
|
|
|
|
|
|
|
|
err = sess.Run("lsblk -o NAME,SIZE,FSTYPE,LABEL -e 7,11,2,253")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "run lsblk")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret.Bytes(), nil
|
|
|
|
|
}
|
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)
|
|
|
|
|
|
|
|
|
|
sess, err := sc.NewSession()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "create new vm ssh session")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() { sess.Close() }()
|
|
|
|
|
|
|
|
|
|
stdinPipe, err := sess.StdinPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "create vm ssh session stdin pipe")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stderrBuf := bytes.NewBuffer(nil)
|
|
|
|
|
sess.Stderr = stderrBuf
|
|
|
|
|
|
|
|
|
|
err = sess.Start("cryptsetup luksOpen " + shellescape.Quote(fullDevPath) + " " + luksDMName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "start cryptsetup luksopen cmd")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lg.Info("Attempting to open LUKS device")
|
|
|
|
|
|
|
|
|
|
_, err = os.Stderr.Write([]byte("Enter Password: "))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "write prompt to stderr")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pwd, err := term.ReadPassword(int(syscall.Stdin))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "read luks password")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Print("\n")
|
|
|
|
|
|
|
|
|
|
var wErr error
|
|
|
|
|
var wWG sync.WaitGroup
|
|
|
|
|
|
|
|
|
|
wWG.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer wWG.Done()
|
|
|
|
|
|
|
|
|
|
_, err := stdinPipe.Write(pwd)
|
|
|
|
|
_, err2 := stdinPipe.Write([]byte("\n"))
|
|
|
|
|
wErr = errors.Wrap(multierr.Combine(err, err2), "write password to stdin")
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// TODO: Timeout for this command
|
|
|
|
|
|
|
|
|
|
err = sess.Wait()
|
|
|
|
|
if err != nil {
|
2023-08-27 15:53:44 +01:00
|
|
|
return utils.WrapErrWithLog(err, "wait for cryptsetup luksopen cmd to finish", stderrBuf.String())
|
2023-08-25 19:55:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lg.Info("LUKS device opened successfully")
|
|
|
|
|
|
|
|
|
|
// Clear the memory up
|
|
|
|
|
{
|
|
|
|
|
for i := 0; i < len(pwd); i++ {
|
|
|
|
|
pwd[i] = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < 16; i++ {
|
|
|
|
|
_, _ = rand.Read(pwd)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ = stdinPipe.Close()
|
|
|
|
|
wWG.Wait()
|
|
|
|
|
|
|
|
|
|
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-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-25 19:55:11 +01:00
|
|
|
_, err = runSSHCmd(sc, "mount -t "+shellescape.Quote(mo.FSType)+" "+shellescape.Quote(fullDevPath)+" /mnt")
|
2023-08-25 16:54:58 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "run mount cmd")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2023-08-26 16:26:35 +01:00
|
|
|
|
2023-08-29 10:00:12 +01:00
|
|
|
func (fm *FileManager) StartFTP(pwd []byte, passivePortStart uint16, passivePortCount uint16) error {
|
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
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
err = scpClient.CopyFile(fm.vm.ctx, 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 10:00:12 +01:00
|
|
|
_, err = runSSHCmd(sc, "rc-update add vsftpd && rc-service vsftpd start")
|
2023-08-26 16:26:35 +01:00
|
|
|
if err != nil {
|
2023-08-29 10:00:12 +01:00
|
|
|
return errors.Wrap(err, "add and start ftpd service")
|
2023-08-26 16:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sess, err := sc.NewSession()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "create new ssh session")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pwd = append(pwd, '\n')
|
|
|
|
|
|
|
|
|
|
stderr := bytes.NewBuffer(nil)
|
|
|
|
|
sess.Stderr = stderr
|
|
|
|
|
|
|
|
|
|
stdinPipe, err := sess.StdinPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "stdin pipe")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Timeout for this command
|
|
|
|
|
|
2023-08-29 10:00:12 +01:00
|
|
|
err = sess.Start("passwd linsk")
|
2023-08-26 16:26:35 +01:00
|
|
|
if err != nil {
|
2023-08-29 10:00:12 +01:00
|
|
|
return errors.Wrap(err, "start change user password cmd")
|
2023-08-26 16:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
_, err = stdinPipe.Write(pwd)
|
|
|
|
|
if err != nil {
|
2023-08-29 10:00:12 +01:00
|
|
|
fm.vm.logger.Error("Failed to write FTP password to passwd stdin", "error", err)
|
2023-08-26 16:26:35 +01:00
|
|
|
}
|
|
|
|
|
_, err = stdinPipe.Write(pwd)
|
|
|
|
|
if err != nil {
|
2023-08-29 10:00:12 +01:00
|
|
|
fm.vm.logger.Error("Failed to write repeated FTP password to passwd stdin", "error", err)
|
2023-08-26 16:26:35 +01:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
err = sess.Wait()
|
|
|
|
|
if err != nil {
|
2023-08-29 10:00:12 +01:00
|
|
|
return utils.WrapErrWithLog(err, "wait for change user password cmd", stderr.String())
|
2023-08-26 16:26:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|