Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<a href="https://t.me/linsk_foss"><img src="https://badgen.net/static/chat/telegram/229ED9"/></a>

**Linsk** is a utility that allows you to access Linux-native file system infrastructure, including LVM and LUKS on Windows and macOS. Unlike other solutions created to access Linux filesystems on unsupported operating systems, Linsk does not reimplement any file system. Instead, Linsk utilizes a lightweight Alpine Linux VM (~130 MB only) combined with network share technologies like SMB, AFP, and FTP.
**Linsk** is a utility that allows you to access Linux-native file system infrastructure, including LVM, LUKS, and Bitlocker on Windows and macOS. Unlike other solutions created to access Linux filesystems on unsupported operating systems, Linsk does not reimplement any file system. Instead, Linsk utilizes a lightweight Alpine Linux VM (~130 MB only) combined with network share technologies like SMB, AFP, and FTP.

Because Linsk uses a native Linux VM, there are no limitations on what you can access. Anything that works on Linux will work under Linsk too (hence the Linux+Disk name).

Expand Down
51 changes: 49 additions & 2 deletions USAGE_MACOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ The network share will remain open until you close Linsk, which you can do at an

The example provided above is just a mere preview of the endless power Linsk's native Linux VM has.


## Use LVM

Linsk supports LVM2. You can mount LVM2 drives by specifying `mapper/<device name>` as the VM device name. Let's assume that you want to mount `vghdd-media` you found in the `linsk ls` output above. To do so, you may run:
Expand All @@ -153,7 +154,7 @@ sudo linsk run dev:/dev/diskX mapper/vghdd-media

## Use LUKS with `cryptsetup`

As well as with LVM2, LUKS via `cryptsetup` is natively supported by Linsk. To mount LUKS volumes, you may specify the `-l` flag in `linsk run` command. Let's assume that we want to access LUKS-encrypted volume `vghdd-archive` we found in the `linsk ls` example provided in step 2. To mount it, you may execute:
As with LVM2, LUKS via `cryptsetup` is natively supported by Linsk. To mount LUKS volumes, you may specify the `-l` flag in `linsk run` command. Let's assume that we want to access LUKS-encrypted volume `vghdd-archive` we found in the `linsk ls` example provided in step 2. To mount it, you would execute:
```sh
sudo linsk run -l dev:/dev/diskX mapper/vghdd-archive
```
Expand Down Expand Up @@ -185,6 +186,41 @@ Password: <random password>
This example showed how you can use LUKS with LVM2 volumes, but that doesn't mean that you can't use volumes without LVM. You can specify plain device paths like `vdb3` without any issue.


## Use BitLocker with `cryptsetup`

Mounting a BitLocker encrypted drive via `cryptsetup` is supported by Linsk. To mount BitLocker volumes, you may specify either the `--bitlk` or the `-b` flag in `linsk run` command. Let's assume that we want to access BITLOCKER-encrypted volume `vdb1` we found using `linsk ls`. To mount it, you would execute:
```sh
sudo linsk run -b dev:/dev/diskX vdb1
```

BitLocker drives are subject to the `cryptsetup` requirements, which require that all BitLocker drives be FULLY encrypted. If only the used disk space is encrypted, you must force the full drive to be encrypted using `manage-bde` on a Windows machine before cryptsetup can interact with it.

The `-b` flag tells Linsk that it is a BITLOCKER volume, and Linsk will prompt you for the password. Combined, your terminal will look like this:


```
# linsk command output
time=2023-09-03T11:44:55.962+01:00 level=WARN msg="Using raw block device passthrough. Please note that it's YOUR responsibility to ensure that no device is mounted in your OS and the VM at the same time. Otherwise, you run serious risks. No further warnings will be issued." caller=vm
time=2023-09-03T11:44:55.964+01:00 level=INFO msg="Booting the VM" caller=vm
time=2023-09-03T11:45:05.975+01:00 level=INFO msg="The VM is up, setting it up" caller=vm
time=2023-09-03T11:45:08.472+01:00 level=INFO msg="The VM is ready" caller=vm
time=2023-09-03T11:45:08.709+01:00 level=INFO msg="Mounting the device" dev=sda1 fs=<auto> bitlocker=true
time=2023-09-03T11:45:08.740+01:00 level=INFO msg="Attempting to open a BITLOCKER device" caller=file-manager vm-path=/dev/sda1
Enter Password: <you will get prompted for the password here>
time=2023-09-03T11:46:08.444+01:00 level=INFO msg="BITLOCKER device opened successfully" caller=file-manager vm-path=/dev/sda1
time=2023-09-03T11:46:08.642+01:00 level=INFO msg="Started the network share successfully" backend=afp
===========================
[Network File Share Config]
The network file share was started. Please use the credentials below to connect to the file server.

Type: AFP
URL: afp://127.0.0.1:9000/linsk
Username: linsk
Password: <random password>
===========================
```


## Use an LVM volume group contained inside a LUKS volume

This is a common scenario that is widely used to enable full-disk encryption on various Linux distributions. It implies having a master LUKS volume that, once decrypted, exposes an LVM volume group (vg).
Expand Down Expand Up @@ -249,6 +285,17 @@ sudo linsk run dev:/dev/diskX --luks-container vdb3 mapper/vgubuntu-lvroot

**Pro Tip**: If the entire passed-through volume is a LUKS container (i.e., you are attempting to run with `--luks-container vdb`), you may use the `-c` flag as a shortcut (or long `--luks-container-entire-drive`). It is equivalent to `--luks-container vdb`.


## USB Passthrough

To use a USB device instead of a disk device, specify the USB device as follows `usb:<vendor-id>,<product-id>`, replacing the placeholders with the values of the desired usb device. To find these values run `system_profiler SPUSBDataType`.

To mount a USB device with Vendor ID: 0x1337, and Product ID: 0xf001, the command would be as follows:

```sh
sudo linsk run usb:1337,f001 sda1
```

# FAQ

### How do I format disks with Linsk?
Expand All @@ -257,4 +304,4 @@ Use `linsk shell`. Please see [SHELL.md](SHELL.md).

# Troubleshooting

Please refer to [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
Please refer to [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
5 changes: 4 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ var runCmd = &cobra.Command{
mountOptionsToLog = mountOptionsFlag
}

slog.Info("Mounting the device", "dev", vmMountDevName, "fs", fsToLog, "luks", luksFlag, "mountoptions", mountOptionsToLog)
slog.Info("Mounting the device", "dev", vmMountDevName, "fs", fsToLog, "luks", luksFlag, "bitlk", bitlockerFlag, "mountoptions", mountOptionsToLog)

err := fm.Mount(vmMountDevName, vm.MountConfig{
LUKSContainerPreopen: vmRuntimeLUKSContainerDevice,
FSTypeOverride: fsTypeOverride,
LUKS: luksFlag,
BITLK: bitlockerFlag,
MountOptions: mountOptionsFlag,
})
if err != nil {
Expand Down Expand Up @@ -143,6 +144,7 @@ var runCmd = &cobra.Command{

var (
luksFlag bool
bitlockerFlag bool
shareListenIPFlag string
ftpExtIPFlag string
shareBackendFlag string
Expand All @@ -153,6 +155,7 @@ var (

func init() {
runCmd.Flags().BoolVarP(&luksFlag, "luks", "l", false, "Use cryptsetup to open a LUKS volume (password will be prompted).")
runCmd.Flags().BoolVarP(&bitlockerFlag, "bitlk", "b", false, "Use cryptsetup to open a BITLOCKER volume (password will be prompted).")
runCmd.Flags().BoolVar(&debugShellFlag, "debug-shell", false, "Start a VM shell when the network file share is active.")

initVMRuntimeFlags(runCmd.Flags())
Expand Down
89 changes: 89 additions & 0 deletions vm/filemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ type MountConfig struct {

FSTypeOverride string
LUKS bool
BITLK bool
MountOptions string
}

Expand Down Expand Up @@ -203,6 +204,85 @@ func (fm *FileManager) preopenLUKSContainerWithSSH(sc *ssh.Client, containerDevP
return nil
}

func (fm *FileManager) bitlkOpen(sc *ssh.Client, fullDevPath string, bitlkDMName string) error {
lg := fm.logger.With("vm-path", fullDevPath)

return sshutil.NewSSHSessionWithDelayedTimeout(fm.vm.ctx, time.Second*15, sc, func(sess *ssh.Session, startTimeout func(preTimeout func())) error {
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 open --type bitlk " + shellescape.Quote(fullDevPath) + " " + bitlkDMName)
if err != nil {
return errors.Wrap(err, "start cryptsetup open --type bitlk cmd")
}

lg.Info("Attempting to open a BITLOCKER 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)) //nolint:unconvert // On Windows it's a different non-int type.
if err != nil {
return errors.Wrap(err, "read BITLOCKER password")
}

fmt.Print("\n")

// We start the timeout countdown now only to avoid timing out
// while the user is entering the password, or shortly after that.
startTimeout(func() {
lg.Warn("BITLOCKER open command timed out. You can try increasing the VM memory allocation using --vm-mem-alloc flag.")
})

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")
}()

defer func() {
// Clear the memory up for security.
{
for i := 0; i < len(pwd); i++ {
pwd[i] = 0
}

// This is my paranoia.
_, _ = rand.Read(pwd)
_, _ = rand.Read(pwd)
}
}()

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.")
}

return utils.WrapErrWithLog(err, "wait for cryptsetup open --type bitlk cmd to finish", stderrBuf.String())
}

lg.Info("BITLOCKER device opened successfully")

_ = stdinPipe.Close()
wWG.Wait()

return wErr
})
}
func (fm *FileManager) Mount(devName string, mc MountConfig) error {
if devName == "" {
return fmt.Errorf("device name is empty")
Expand Down Expand Up @@ -259,6 +339,15 @@ func (fm *FileManager) Mount(devName string, mc MountConfig) error {
}

fullDevPath = "/dev/mapper/" + luksDMName
} else if mc.BITLK {
bitlkDMName := "cryptmnt"

err = fm.bitlkOpen(sc, fullDevPath, bitlkDMName)
if err != nil {
return errors.Wrap(err, "luks open")
}

fullDevPath = "/dev/mapper/" + bitlkDMName
}

cmd := "mount "
Expand Down