diff --git a/README.md b/README.md
index 030f8d0..213efd0 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-**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).
diff --git a/USAGE_MACOS.md b/USAGE_MACOS.md
index 9a4bd12..71aa769 100644
--- a/USAGE_MACOS.md
+++ b/USAGE_MACOS.md
@@ -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/` 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:
@@ -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
```
@@ -185,6 +186,41 @@ 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= 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:
+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:
+===========================
+```
+
+
## 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).
@@ -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:,`, 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?
@@ -257,4 +304,4 @@ Use `linsk shell`. Please see [SHELL.md](SHELL.md).
# Troubleshooting
-Please refer to [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
\ No newline at end of file
+Please refer to [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
diff --git a/cmd/run.go b/cmd/run.go
index 0b8a011..e3a9a33 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -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 {
@@ -143,6 +144,7 @@ var runCmd = &cobra.Command{
var (
luksFlag bool
+ bitlockerFlag bool
shareListenIPFlag string
ftpExtIPFlag string
shareBackendFlag string
@@ -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())
diff --git a/vm/filemanager.go b/vm/filemanager.go
index b8afd05..dd1c419 100644
--- a/vm/filemanager.go
+++ b/vm/filemanager.go
@@ -87,6 +87,7 @@ type MountConfig struct {
FSTypeOverride string
LUKS bool
+ BITLK bool
MountOptions string
}
@@ -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")
@@ -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 "