Working SMB file share

This commit is contained in:
AlexSSD7 2023-08-26 16:26:35 +01:00
commit 76b20570ec
11 changed files with 187 additions and 7 deletions

View file

@ -1 +1,4 @@
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
addgroup -g 1000 linsk
adduser -G linsk linsk -S -u 1000

View file

@ -1,4 +1,5 @@
openssh
lvm2
util-linux
cryptsetup
cryptsetup
samba

View file

@ -27,7 +27,7 @@ var lsCmd = &cobra.Command{
fmt.Print(string(lsblkOut))
return 0
}))
}, nil))
return nil
},

View file

@ -4,9 +4,11 @@ import (
"context"
"fmt"
"log/slog"
"net"
"os"
"github.com/AlexSSD7/linsk/vm"
"github.com/sethvargo/go-password/password"
"github.com/spf13/cobra"
)
@ -19,6 +21,12 @@ var runCmd = &cobra.Command{
vmMountDevName := args[1]
fsType := args[2]
networkSharePort, err := getClosestAvailPort(9000)
if err != nil {
slog.Error("Failed to get closest available host port for network file share", "error", err)
os.Exit(1)
}
// TODO: `slog` library prints entire stack traces for errors which makes reading errors challenging.
os.Exit(runVM(args[0], func(ctx context.Context, i *vm.Instance, fm *vm.FileManager) int {
@ -31,10 +39,31 @@ var runCmd = &cobra.Command{
return 1
}
fmt.Println("Mounted! Now sleeping")
sharePWD, err := password.Generate(16, 10, 0, false, false)
if err != nil {
slog.Error("Failed to generate ephemeral password for network file share", "error", err)
return 1
}
shareURI := "smb://linsk:" + sharePWD + "@127.0.0.1:" + fmt.Sprint(networkSharePort)
fmt.Fprintf(os.Stderr, "================\n[Network File Share Config]\nThe network file share was started. Please use the credentials below to connect to the file server.\n\nType: SMB\nServer Address: smb://127.0.0.1:%v\nUsername: linsk\nPassword: %v\n\nShare URI: %v\n================\n", networkSharePort, sharePWD, shareURI)
err = fm.StartSMB([]byte(sharePWD))
if err != nil {
slog.Error("Failed to start SMB server", "error", err)
return 1
}
slog.Info("Started the network share successfully", "type", "smb")
<-ctx.Done()
return 0
}))
}, []vm.PortForwardingConfig{{
HostIP: net.ParseIP("127.0.0.1"), // TODO: Make this changeable.
HostPort: networkSharePort,
VMPort: 445,
}}))
return nil
},

View file

@ -103,7 +103,7 @@ var shellCmd = &cobra.Command{
}
return 0
}))
}, nil))
return nil
},

View file

@ -2,6 +2,8 @@ package cmd
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"os/user"
@ -36,7 +38,7 @@ func doRootCheck() {
}
}
func runVM(passthroughArg string, fn func(context.Context, *vm.Instance, *vm.FileManager) int) int {
func runVM(passthroughArg string, fn func(context.Context, *vm.Instance, *vm.FileManager) int, forwardPorts []vm.PortForwardingConfig) int {
doRootCheck()
var passthroughConfig []vm.USBDevicePassthroughConfig
@ -48,6 +50,10 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.Instance, *vm.Fil
var forwardPortsConfig []vm.PortForwardingConfig
for i, fp := range strings.Split(forwardPortsFlagStr, ",") {
if fp == "" {
continue
}
fpc, err := vm.ParsePortForwardString(fp)
if err != nil {
slog.Error("Failed to parse port forward string", "index", i, "value", fp, "error", err)
@ -57,6 +63,8 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.Instance, *vm.Fil
forwardPortsConfig = append(forwardPortsConfig, fpc)
}
forwardPortsConfig = append(forwardPortsConfig, forwardPorts...)
// TODO: Alpine image should be downloaded from somewhere.
vi, err := vm.NewInstance(slog.Default().With("caller", "vm"), "alpine-img/alpine.qcow2", passthroughConfig, vmDebugFlag, forwardPortsConfig)
if err != nil {
@ -144,3 +152,30 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.Instance, *vm.Fil
}
}
}
func getClosestAvailPort(port uint16) (uint16, error) {
for i := port; i < 65535; i++ {
ln, err := net.Listen("tcp", ":"+fmt.Sprint(i))
if err != nil {
if opErr, ok := err.(*net.OpError); ok {
if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
if sysErr.Err == syscall.EADDRINUSE {
// The port is in use.
continue
}
}
}
return 0, errors.Wrapf(err, "net listen (port %v)", port)
}
err = ln.Close()
if err != nil {
return 0, errors.Wrap(err, "close ephemeral listener")
}
return i, nil
}
return 0, fmt.Errorf("no available port found")
}

2
go.mod
View file

@ -4,8 +4,10 @@ go 1.21
require (
github.com/alessio/shellescape v1.4.2
github.com/bramvdbogaerde/go-scp v1.2.1
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1
github.com/sethvargo/go-password v0.2.0
github.com/spf13/cobra v1.7.0
go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.12.0

11
go.sum
View file

@ -1,5 +1,7 @@
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw=
github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -12,6 +14,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -20,12 +24,19 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -7,6 +7,7 @@ import (
"log/slog"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
@ -202,3 +203,84 @@ func (fm *FileManager) Mount(devName string, mo MountOptions) error {
return nil
}
func (fm *FileManager) StartSMB(pwd []byte) error {
scpClient, err := fm.vi.DialSCP()
if err != nil {
return errors.Wrap(err, "dial scp")
}
defer scpClient.Close()
sambaCfg := `[global]
workgroup = WORKGROUP
dos charset = cp866
unix charset = utf-8
[linsk]
browseable = yes
writeable = yes
path = /mnt
force user = linsk
force group = linsk
create mask = 0664`
err = scpClient.CopyFile(fm.vi.ctx, strings.NewReader(sambaCfg), "/etc/samba/smb.conf", "0400")
if err != nil {
return errors.Wrap(err, "copy samba config file")
}
scpClient.Close()
sc, err := fm.vi.DialSSH()
if err != nil {
return errors.Wrap(err, "dial ssh")
}
defer func() { _ = sc.Close() }()
_, err = runSSHCmd(sc, "rc-update add samba && rc-service samba start")
if err != nil {
return errors.Wrap(err, "add and start samba service")
}
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
err = sess.Start("smbpasswd -a linsk")
if err != nil {
return errors.Wrap(err, "start change samba password cmd")
}
go func() {
_, err = stdinPipe.Write(pwd)
if err != nil {
fm.vi.logger.Error("Failed to write SMB password to smbpasswd stdin", "error", err)
}
_, err = stdinPipe.Write(pwd)
if err != nil {
fm.vi.logger.Error("Failed to write repeated SMB password to smbpasswd stdin", "error", err)
}
}()
err = sess.Wait()
if err != nil {
return wrapErrWithLog(err, "wait for change samba password cmd", stderr.String())
}
return nil
}

View file

@ -155,6 +155,8 @@ func runSSHCmd(c *ssh.Client, cmd string) ([]byte, error) {
return nil, errors.Wrap(err, "create new vm ssh session")
}
// TODO: Timeouts
defer func() { _ = sess.Close() }()
stdout := bytes.NewBuffer(nil)

View file

@ -19,6 +19,7 @@ import (
"log/slog"
"github.com/alessio/shellescape"
"github.com/bramvdbogaerde/go-scp"
"github.com/phayes/freeport"
"github.com/pkg/errors"
"go.uber.org/multierr"
@ -330,6 +331,20 @@ func (vi *Instance) DialSSH() (*ssh.Client, error) {
return ssh.Dial("tcp", "localhost:"+fmt.Sprint(vi.sshMappedPort), vi.sshConf)
}
func (vi *Instance) DialSCP() (*scp.Client, error) {
if vi.sshConf == nil {
return nil, ErrSSHUnavailable
}
sc := scp.NewClient("localhost:"+fmt.Sprint(vi.sshMappedPort), vi.sshConf)
err := sc.Connect()
if err != nil {
return nil, err
}
return &sc, nil
}
func (vi *Instance) SSHUpNotifyChan() chan struct{} {
return vi.sshReadyCh
}