--- /dev/null
+.idea
+cryptsetup-vault
+.test_container
+*.tar.*
\ No newline at end of file
--- /dev/null
+# cryptsetup-vault
+
+__*Note:*__ `cryptsetup-vault -setkey` will save the passphrase into the token's cubbyhole. If the token expires or gets revoked, that passphrase is lost. Make a backup!
+
+## Installation
+
+__*Note:*__ Currently only Arch Linux is tested/supported, if you are on another distro, your mileage may vary.
+
+### Arch Linux
+
+- [Install cryptsetup-vault via the AUR](https://aur.archlinux.org/packages/cryptsetup-vault/).
+- Execute the Setup guide below to get a token and push your passphrase into vault
+- Create a file in `/etc/cryptsetup-vault/credentials` (chmod 0600, chown root:root) with the following content:
+```bash
+VAULT_URL=<vault URL>
+VAULT_TOKEN=<Token from previous step>
+```
+- Setup kernel parameters to set an IP or get one via DHCP on boot
+ - Use `dmesg | awk '$0~/eno1: renamed from/ {print $NF}'` (replace eno1 with your NIC) to get the original NIC name for the parameter.
+ - If your system is up a long time it might be, that the message is vanished. A safe bet would be to reboot and reexec that command after it to get the NIC
+- Add the `netconf cryptsetupvault encrypt` hooks into /etc/mkinitcpio.conf (example below)
+```bash
+#...
+HOOKS=(base udev autodetect modconf block keymap netconf cryptsetupvault encrypt resume filesystems keyboard fsck)
+#...
+```
+- execute `mkinitcpio -p linux` as root
+- Done! Reboot and enjoy!
+
+## Setup the tokens and keys
+
+__*Note:*__ This requires an existing Vault installation, with TLS and valid/trusted certificates.
+
+- Create long-lived token (valid for 10 years - You may need to tweak your vault settings for this should you receive an error):
+```bash
+VAULT_TOKEN=<root/sudo token> VAULT_ADDR=<vault URL> vault token create --display-name "$(hostname -f) cryptsetup token" -renewable --orphan --no-default-policy --period 5256000m --ttl 5256000m --explicit-max-ttl=52560000m
+```
+
+- Create the file `/etc/cryptsetup-vault` (chmod 0600, chown root:root) with the following content:
+```bash
+VAULT_URL=<vault URL>
+VAULT_TOKEN=<Token from previous step>
+```
+
+- Set key in vault for currently mounted & encrypted rootfs
+```bash
+sudo -E cryptsetup-vault -setkey
+# enter password when prompted
+```
+
+- Check if the key in vault will actually unlock the drive
+```bash
+sudo -E cryptsetup-vault -test
+```
--- /dev/null
+module gitlab.com/T4cC0re/cryptsetup-vault
+
+require (
+ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
+ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
+ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
+ github.com/hashicorp/go-multierror v1.0.0 // indirect
+ github.com/hashicorp/go-retryablehttp v0.5.0 // indirect
+ github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 // indirect
+ github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/hashicorp/vault v1.0.1
+ github.com/mitchellh/mapstructure v1.1.2 // indirect
+ github.com/pierrec/lz4 v2.0.5+incompatible // indirect
+ github.com/prometheus/common v0.0.0-20181218105931-67670fe90761
+ github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 // indirect
+ github.com/sirupsen/logrus v1.2.0 // indirect
+ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
+ golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
+ golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb // indirect
+ golang.org/x/text v0.3.0 // indirect
+ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
+ gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
+)
--- /dev/null
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-retryablehttp v0.5.0 h1:aVN0FYnPwAgZI/hVzqwfMiM86ttcHTlQKbBVeVmXPIs=
+github.com/hashicorp/go-retryablehttp v0.5.0/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
+github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
+github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
+github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/vault v1.0.1 h1:x3hcjkJLd5L4ehPhZcraokFO7dq8MJ3oKvQtrkIiIU8=
+github.com/hashicorp/vault v1.0.1/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 h1:z6tvbDJ5OLJ48FFmnksv04a78maSTRBUIhkdHYV5Y98=
+github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
+github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
--- /dev/null
+#!/usr/bin/ash
+
+run_hook () {
+ # This may happen if third party hooks do the crypt setup
+ if [ -b "/dev/mapper/${cryptname}" ]; then
+ echo "Device ${cryptname} already exists, not doing any crypt setup."
+ return 0
+ fi
+
+ # Setup PATH
+ export PATH="${PATH}:/usr/bin"
+
+ # Run the hook
+ /usr/bin/cryptsetup-vault -wait 60
+}
\ No newline at end of file
--- /dev/null
+#!/usr/bin/ash
+
+build() {
+ local mod
+
+ add_module dm-crypt
+ if [[ $CRYPTO_MODULES ]]; then
+ for mod in $CRYPTO_MODULES; do
+ add_module "$mod"
+ done
+ else
+ add_all_modules '/crypto/'
+ fi
+
+ add_binary "cryptsetup"
+ add_binary "dmsetup"
+ add_file "/usr/lib/udev/rules.d/10-dm.rules"
+ add_file "/usr/lib/udev/rules.d/13-dm-disk.rules"
+ add_file "/usr/lib/udev/rules.d/95-dm-notify.rules"
+ add_file "/usr/lib/initcpio/udev/11-dm-initramfs.rules" "/usr/lib/udev/rules.d/11-dm-initramfs.rules"
+
+ # add our magic pixie dust
+ add_binary "/usr/bin/cryptsetup-vault"
+ add_file "/etc/cryptsetup-vault"
+
+ # I can haz DNS!
+ add_file "/usr/lib/libnss_dns.so.2"
+ add_file "/usr/lib/libnss_files.so.2"
+ add_file "/etc/resolv.conf"
+
+ # I can haz TLS!
+ add_file "/etc/ssl/certs/ca-certificates.crt"
+
+ # cryptsetup calls pthread_create(), which dlopen()s libgcc_s.so.1
+ add_binary "/usr/lib/libgcc_s.so.1"
+
+ add_runscript
+}
+
+help() {
+ cat <<HELPEOF
+This hook will enable unattended unlocks of a cryptsetup device with the help
+of Vault. For this to work another hook has to establish a network connection
+(e.g. netconf) and credentials set in /etc/etc/cryptsetup-vault
+(See README.md). This hook can be supplemented with the default 'encrypt'
+hook as a fallback, as that hook will not perform a cryptsetup of the device
+is already opened.
+The hook should be placed after a network configuration hook and before
+filesystem hooks.
+HELPEOF
+}
\ No newline at end of file
--- /dev/null
+package main
+
+import (
+ "github.com/hashicorp/vault/api"
+ "flag"
+ "os"
+ "github.com/prometheus/common/log"
+ "io/ioutil"
+ "bytes"
+ "strings"
+ "syscall"
+ "os/exec"
+ "fmt"
+ "golang.org/x/crypto/ssh/terminal"
+ "time"
+ "errors"
+ "net"
+)
+
+func init() {
+ // We are handling sensitive key material. Lock it, so it's not swapped to disk in any case.
+ err := syscall.Mlockall(syscall.MCL_CURRENT | syscall.MCL_FUTURE)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+var (
+ address = flag.String("vault", getEnv("VAULT_ADDR"), "Vault address (e.g. https://my.vault.io)")
+ token = flag.String("token", "", "Vault token")
+ cryptdevice = flag.String("cryptdevice", getCryptdevice(), "cryptdevice to unlock")
+ cryptmapper = flag.String("cryptmapper", getCryptmapper(), "cryptmapper to mount -cryptdevice as")
+ cryptoptions = flag.String("cryptoptions", getCryptoptions(), "-cryptdevice options (allow-discards)")
+ waitForVault = flag.Int64("wait", 0, "Amount of seconds to retry to connect to vault. (applies to 'read' only, 0 = disable)")
+ test = flag.Bool("test", false, "Just test the passphrase retrieved from vault (but not actually luksOpen)")
+ setKey = flag.Bool("setkey", false, `(requires root) set this flag to set a key in vault for the specified UUID and exit.
+If the command is executed on a terminal, you will be prompted.
+Otherwise you can pipe in the passphrase *without newlines!* (e.g. 'echo -n')`)
+ fullString = ""
+ env = map[string]string{}
+)
+
+func main() {
+ flag.Parse()
+
+ net.DefaultResolver.PreferGo = true
+
+ cdUUID := getCryptDeviceUUID(*cryptdevice)
+ if *token == "" {
+ // Don't use the placeholder here, as that will output the token on -help
+ *token = getEnv("VAULT_TOKEN")
+ }
+ log.Info("cryptsetup-vault - secure and automated cryptsetup powered by vault")
+ log.Infof("cryptdevice:\t\t%s", *cryptdevice)
+ log.Infof("cryptdevice UUID:\t%s", cdUUID)
+ log.Infof("cryptmapper:\t\t%s", *cryptmapper)
+ log.Infof("cryptoptions:\t%s", *cryptoptions)
+ log.Infof("VAULT_ADDR:\t\t%s", *address)
+ log.Infof("VAULT_TOKEN:\t\t(redacted) length: %d", len(*token))
+
+ if len(*address) == 0 || len(*token) == 0 {
+ log.Fatal("No address or token given. Cancelling...")
+ }
+
+ if len(cdUUID) == 0 || len(*cryptdevice) == 0 {
+ log.Fatalf("Cryptdevice or mapper name missing (%s). Cancelling...", fullString)
+ }
+
+ vaultConfig := api.Config{Address: *address}
+
+ client, err := api.NewClient(&vaultConfig)
+ if err != nil {
+ log.Fatal(err)
+ }
+ client.SetToken(*token)
+
+ if *setKey {
+ var bytePassphrase []byte
+ if terminal.IsTerminal(int(syscall.Stdin)) {
+ log.Info("Reading passphrase from terminal...")
+ fmt.Println("Enter Passphrase: ")
+ bytePassphrase, err = terminal.ReadPassword(int(syscall.Stdin))
+ if err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ log.Info("Reading passphrase from stdin...")
+ bytePassphrase, _ = ioutil.ReadAll(os.Stdin)
+ }
+ passphrase := string(bytePassphrase)
+
+ validatePassphrase(cdUUID, passphrase)
+
+ data := map[string]interface{}{}
+ data["passphrase"] = passphrase
+ log.Infof("Setting key in vault...")
+ _, err = client.Logical().Write("/cubbyhole/"+cdUUID, data)
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Info("Done")
+ return
+ }
+
+ log.Infof("Reading key from vault...")
+
+ secret, err := readFromVault(client, "/cubbyhole/"+cdUUID)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if secret == nil && !*setKey {
+ log.Fatalf("No key is set in vault for disk UUID %s", cdUUID)
+ }
+
+ var passphrase string
+
+ for k, v := range secret.Data {
+ if k != "passphrase" {
+ continue
+ }
+ passphrase = v.(string)
+ }
+
+ if passphrase == "" {
+ log.Fatalf("Passphrase for disk UUID %s is empty in vault!", cdUUID)
+ }
+
+ log.Infof("Received key from vault...")
+
+ cryptArgs := compileCryptsetupArgs(*cryptoptions)
+
+ if *test {
+ validatePassphrase(cdUUID, passphrase)
+ } else {
+ unlockDisk(cdUUID, *cryptmapper, passphrase, cryptArgs)
+ }
+
+ log.Info("Done!")
+ log.Info("Have a nice day!")
+
+ os.Exit(0)
+}
+
+func loadEnvFromFile() (localenv map[string]string) {
+ if len(env) > 0 {
+ return
+ }
+
+ localenv = map[string]string{}
+
+ data, _ := ioutil.ReadFile("/etc/cryptsetup-vault")
+ lines := bytes.Split(data, []byte("\n"))
+ for _, line := range lines {
+ line = bytes.Trim(line, "\r\t ")
+ splits := bytes.SplitN(line, []byte("="), 2)
+ if len(splits) == 2 {
+ localenv[string(splits[0])] = string(splits[1])
+ }
+ }
+ env = localenv
+ return
+}
+
+func getEnv(key string) (value string) {
+ value = os.Getenv(key)
+ if value != "" {
+ return
+ }
+ loadEnvFromFile()
+
+ return env[key]
+}
+
+func readFromVault(client *api.Client, path string) (secret *api.Secret, err error) {
+ secret, err = client.Logical().Read(path)
+ if err == nil || *waitForVault == 0 {
+ // In those conditions we can return right away.
+ return
+ }
+
+ timeout := time.After(time.Duration(*waitForVault) * time.Second)
+ tick := time.Tick(500 * time.Millisecond)
+ for {
+ select {
+ case <-tick:
+ secret, err = client.Logical().Read(path)
+ if err == nil {
+ return
+ }
+ log.Error(err)
+ case <-timeout:
+ err = errors.New("reading from vault timed out")
+ return
+ }
+ }
+}
+
+/**
+will exit the program if the passphrase (and/or uuid) is invalid
+ */
+func validatePassphrase(uuid string, passphrase string) {
+ log.Infof("Validating passphrase against cryptsetup...")
+ cmd := exec.Command("cryptsetup", "--test-passphrase", "--key-file", "-", "luksOpen", "/dev/disk/by-uuid/"+uuid)
+ cmd.Stdin = strings.NewReader(passphrase)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ err := cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+/**
+will exit the program if the passphrase (and/or uuid) is invalid or the unlock failed somehow.
+ */
+func unlockDisk(uuid string, mappername string, passphrase string, opts []string) {
+ log.Infof("Unlocking disk...")
+ args := []string{
+ "--key-file", "-", "luksOpen", "/dev/disk/by-uuid/" + uuid, mappername,
+ }
+ args = append(opts, args...)
+ cmd := exec.Command("cryptsetup", args...)
+ cmd.Stdin = strings.NewReader(passphrase)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ err := cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func compileCryptsetupArgs(opts string) (cryptArgs []string) {
+ splits := strings.Split(opts, ",")
+ for _, opt := range splits {
+ switch opt {
+ case "allow-discards":
+ cryptArgs = append(cryptArgs, "--allow-discards")
+ default:
+ log.Warnf("cryptoption '%s' unknown, ignoring...", opt)
+ }
+ }
+ return
+}
+
+func getCryptdevice() string {
+ if fullString == "" {
+ fullString = getCryptdeviceString()
+ }
+
+ return strings.SplitN(fullString, ":", 3)[0]
+}
+func getCryptmapper() string {
+ if fullString == "" {
+ fullString = getCryptdeviceString()
+ }
+
+ return strings.SplitN(fullString, ":", 3)[1]
+}
+func getCryptoptions() string {
+ if fullString == "" {
+ fullString = getCryptdeviceString()
+ }
+
+ splits := strings.SplitN(fullString, ":", 3)
+ if len(splits) == 3 {
+ return splits[2]
+ }
+ return ""
+}
+
+/**
+guaranteed to always output a UUID or an empty string
+ */
+func getCryptDeviceUUID(dev string) (string) {
+
+ if strings.HasPrefix(dev, "UUID=") {
+ uuid := strings.Split(dev, "=")[1]
+ filename := "/dev/disk/by-uuid/" + strings.ToLower(uuid)
+
+ fi, _ := os.Lstat(filename)
+ if fi != nil {
+ return fi.Name()
+ }
+ }
+
+ log.Fatal("currently only 'UUID='-style cryptdevice parameters are supported")
+
+ return ""
+}
+
+func getCryptdeviceString() string {
+ line, _ := ioutil.ReadFile("/proc/cmdline")
+ splits := bytes.Split(line, []byte(" "))
+ for _, split := range splits {
+ if bytes.HasPrefix(split, []byte("cryptdevice=")) {
+ return string(bytes.SplitN(split, []byte("="), 2)[1])
+ }
+ }
+
+ return ""
+}
--- /dev/null
+#!/usr/bin/env bash
+
+set -e
+
+go build -o cryptsetup-vault ./src
+# No need to continue if this does not compile
+
+function finish {
+ rc=$?
+ set +e
+ export VAULT_TOKEN="test_token"
+ kill -9 $VAULT_PID
+ sudo cryptsetup close /dev/mapper/vault_test
+ sudo losetup -d ${DEV}
+ exit $rc
+}
+
+trap finish EXIT
+
+vault server -dev -dev-root-token-id=test_token &>/dev/null &
+VAULT_PID=$!
+
+sleep 1
+
+export VAULT_TOKEN="test_token"
+export VAULT_ADDR="http://127.0.0.1:8200"
+
+export VAULT_TOKEN=$(vault token create -display-name "$(hostname -f) cryptsetup token" -renewable --orphan -period 5256000m -ttl 5256000m -explicit-max-ttl=52560000m | jq -r .auth.client_token)
+
+dd if=/dev/zero of=./.test_container bs=1M count=250
+DEV=$(sudo losetup -f)
+echo $DEV
+sudo losetup ${DEV} ./.test_container
+
+echo -n 'vault_test_password' | sudo cryptsetup -v --type luks --iter-time 250 --use-urandom luksFormat "${DEV}" -
+
+for UUID in ls /dev/disk/by-uuid/*; do
+ LD=$(readlink -f $UUID)
+ if [ "$LD" = "$DEV" ]; then
+ LDOK=1
+ break
+ fi
+done
+
+if ! [ "$LDOK" = "1" ]; then
+ exit 128
+fi
+
+UUID=$(sudo blkid ${DEV} | awk -F '"' '$1~/UUID=/ {print $2}')
+
+PARAMS="-cryptdevice UUID=${UUID} -cryptmapper vault_test -cryptoptions allow-discards -token test_token -vault http://127.0.0.1:8200"
+
+echo -n 'vault_test_password' | sudo -E ./cryptsetup-vault ${PARAMS} -setkey
+sudo -E ./cryptsetup-vault ${PARAMS}
+test -b /dev/mapper/vault_test && echo "luksOpen successful"