]> git.apps.os.sepia.ceph.com Git - fscrypt.git/commitdiff
cmd/fscrypt: add 'fscrypt lock' command
authorEric Biggers <ebiggers@google.com>
Mon, 16 Dec 2019 03:31:39 +0000 (19:31 -0800)
committerEric Biggers <ebiggers@google.com>
Sun, 5 Jan 2020 18:02:13 +0000 (10:02 -0800)
Add support for 'fscrypt lock'.  This command "locks" a directory,
undoing 'fscrypt unlock'.

When the filesystem keyring is used, 'fscrypt lock' also detects when a
directory wasn't fully locked due to some files still being in-use.  It
can then be run again later to try to finish locking the files.

README.md
actions/policy.go
cmd/fscrypt/commands.go
cmd/fscrypt/errors.go
cmd/fscrypt/fscrypt.go

index 3a86723890b9350d516eda1d3de5ffdcd8caefab..f183f2e2a95df59b60af67fe90d8d0ee0d38eea9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -91,7 +91,8 @@ Concretely, fscrypt contains the following functionality:
 *   `fscrypt setup MOUNTPOINT` - Gets a filesystem ready for use with fscrypt
 *   `fscrypt encrypt DIRECTORY` - Encrypts an empty directory
 *   `fscrypt unlock DIRECTORY` - Unlocks an encrypted directory
-*   `fscrypt purge MOUNTPOINT` - Removes keys for a filesystem before unmounting
+*   `fscrypt lock DIRECTORY` - Locks an encrypted directory
+*   `fscrypt purge MOUNTPOINT` - Locks all encrypted directories on a filesystem
 *   `fscrypt status [PATH]` - Gets detailed info about filesystems or paths
 *   `fscrypt metadata` - Manages policies or protectors directly
 
@@ -367,12 +368,10 @@ Protected with 1 protector:
 PROTECTOR         LINKED  DESCRIPTION
 7626382168311a9d  No      custom protector "Super Secret"
 
-# Purging a filesystem locks all the files.
->>>>> sudo fscrypt purge /mnt/disk --user=$USER
-WARNING: Encrypted data on this filesystem will be inaccessible until unlocked again!!
-Purge all policy keys from "/mnt/disk" and drop global inode cache? [y/N] y
-Policies purged for "/mnt/disk".
-
+# Lock the directory.
+>>>>> sudo fscrypt lock /mnt/disk/dir1 --user=$USER
+Encrypted data removed from filesystem cache.
+"/mnt/disk/dir1" is now locked.
 >>>>> fscrypt status /mnt/disk/dir1
 "/mnt/disk/dir1" is encrypted with fscrypt.
 
@@ -410,7 +409,7 @@ Hello World
 
 #### Quiet Version
 ```bash
->>>>> sudo fscrypt purge /mnt/disk --user=$USER --quiet --force
+>>>>> sudo fscrypt lock /mnt/disk/dir1 --quiet --user=$USER
 >>>>> echo "hunter2" | fscrypt unlock /mnt/disk/dir1 --quiet
 ```
 
index 6ef83ce1c80c3b6f14f3835b1b03244bbb684bd3..2d8c521cff71e53aeb0513bb091175b00c9f4985 100644 (file)
@@ -406,6 +406,12 @@ func (policy *Policy) Deprovision() error {
                policy.Context.getKeyringOptions())
 }
 
+// NeedsUserKeyring returns true if Provision and Deprovision for this policy
+// will use a user keyring, not a filesystem keyring.
+func (policy *Policy) NeedsUserKeyring() bool {
+       return !policy.Context.Config.GetUseFsKeyringForV1Policies()
+}
+
 // commitData writes the Policy's current data to the filesystem.
 func (policy *Policy) commitData() error {
        return policy.Context.Mount.AddPolicy(policy.data)
index a3bfef255614a7a0e038ed0a282947b4e5bf29e7..8f2d21b4693dda82895d92f35849f3a5f11cd91b 100644 (file)
@@ -281,8 +281,8 @@ var Unlock = cli.Command{
                appropriate key into the keyring. This requires unlocking one of
                the protectors protecting this directory (either by selecting a
                protector or specifying one with %s). This directory will be
-               locked again upon reboot, or after running "fscrypt purge" and
-               unmounting the corresponding filesystem.`, directoryArg,
+               locked again upon reboot, or after running "fscrypt lock" or
+               "fscrypt purge".`, directoryArg,
                shortDisplay(unlockWithFlag)),
        Flags:  []cli.Flag{unlockWithFlag, keyFileFlag, userFlag},
        Action: unlockAction,
@@ -328,6 +328,88 @@ func unlockAction(c *cli.Context) error {
        return nil
 }
 
+func dropCachesIfRequested(c *cli.Context, ctx *actions.Context) error {
+       if dropCachesFlag.Value {
+               if err := security.DropFilesystemCache(); err != nil {
+                       return err
+               }
+               fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n")
+       } else {
+               fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path)
+       }
+       return nil
+}
+
+// Lock takes an encrypted directory and locks it, undoing Unlock.
+var Lock = cli.Command{
+       Name:      "lock",
+       ArgsUsage: directoryArg,
+       Usage:     "lock an encrypted directory",
+       Description: fmt.Sprintf(`This command takes %s, an encrypted directory
+               which has been unlocked by fscrypt, and locks the directory by
+               removing the encryption key from the kernel. I.e., it undoes the
+               effect of 'fscrypt unlock'.
+
+               For this to be effective, all files in the directory must first
+               be closed.
+
+               The %s=true option may be needed to properly lock the directory.
+               Root is required for this.
+
+               WARNING: even after the key has been removed, decrypted data may
+               still be present in freed memory, where it may still be
+               recoverable by an attacker who compromises system memory. To be
+               fully safe, you must reboot with a power cycle.`,
+               directoryArg, shortDisplay(dropCachesFlag)),
+       Flags:  []cli.Flag{dropCachesFlag, userFlag},
+       Action: lockAction,
+}
+
+func lockAction(c *cli.Context) error {
+       if c.NArg() != 1 {
+               return expectedArgsErr(c, 1, false)
+       }
+
+       targetUser, err := parseUserFlag(true)
+       if err != nil {
+               return newExitError(c, err)
+       }
+       path := c.Args().Get(0)
+       ctx, err := actions.NewContextFromPath(path, targetUser)
+       if err != nil {
+               return newExitError(c, err)
+       }
+
+       log.Printf("performing sanity checks")
+       // Ensure path is encrypted and filesystem is using fscrypt.
+       policy, err := actions.GetPolicyFromPath(ctx, path)
+       if err != nil {
+               return newExitError(c, err)
+       }
+       // Check if directory is already locked
+       if policy.IsFullyDeprovisioned() {
+               log.Printf("policy %s is already fully deprovisioned", policy.Descriptor())
+               return newExitError(c, errors.Wrapf(ErrPolicyLocked, path))
+       }
+       // Check for permission to drop caches, if it will be needed.
+       if policy.NeedsUserKeyring() && dropCachesFlag.Value && !util.IsUserRoot() {
+               return newExitError(c, ErrDropCachesPerm)
+       }
+
+       if err = policy.Deprovision(); err != nil {
+               return newExitError(c, err)
+       }
+
+       if policy.NeedsUserKeyring() {
+               if err = dropCachesIfRequested(c, ctx); err != nil {
+                       return newExitError(c, err)
+               }
+       }
+
+       fmt.Fprintf(c.App.Writer, "%q is now locked.\n", path)
+       return nil
+}
+
 // Purge removes all the policy keys from the keyring (also need unmount).
 var Purge = cli.Command{
        Name:      "purge",
@@ -401,13 +483,8 @@ func purgeAction(c *cli.Context) error {
        }
        fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path)
 
-       if dropCachesFlag.Value {
-               if err = security.DropFilesystemCache(); err != nil {
-                       return newExitError(c, err)
-               }
-               fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n")
-       } else {
-               fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path)
+       if err = dropCachesIfRequested(c, ctx); err != nil {
+               return newExitError(c, err)
        }
        return nil
 }
index ed57dbe99a4a29f29848fb3db9ba575e90ae53e9..68135fe1dc901878465343cd760926b91c008b99 100644 (file)
@@ -56,6 +56,7 @@ var (
        ErrAllLoadsFailed     = errors.New("could not load any protectors")
        ErrMustBeRoot         = errors.New("this command must be run as root")
        ErrPolicyUnlocked     = errors.New("this file or directory is already unlocked")
+       ErrPolicyLocked       = errors.New("this file or directory is already locked")
        ErrBadOwners          = errors.New("you do not own this directory")
        ErrNotEmptyDir        = errors.New("not an empty directory")
        ErrNotPassphrase      = errors.New("protector does not use a passphrase")
@@ -94,6 +95,11 @@ func getErrorSuggestions(err error) string {
                        needs to be enabled for this filesystem. See the
                        documentation on how to enable encryption on ext4
                        systems (and the risks of doing so).`
+       case keyring.ErrKeyFilesOpen:
+               return `Directory was incompletely locked because some files are
+                       still open. These files remain accessible. Try killing
+                       any processes using files in the directory, then
+                       re-running 'fscrypt lock'.`
        case keyring.ErrSessionUserKeying:
                return `This is usually the result of a bad PAM configuration.
                        Either correct the problem in your PAM stack, enable
index 9ac8e2f73f108ecb9f921fe3ea084bba83478533..b6549f47b3993ccdf6b87491780698e4fe1b5e89 100644 (file)
@@ -76,7 +76,7 @@ func main() {
 
        // Initialize command list and setup all of the commands.
        app.Action = defaultAction
-       app.Commands = []cli.Command{Setup, Encrypt, Unlock, Purge, Status, Metadata}
+       app.Commands = []cli.Command{Setup, Encrypt, Unlock, Lock, Purge, Status, Metadata}
        for i := range app.Commands {
                setupCommand(&app.Commands[i])
        }