diff options
Diffstat (limited to 'cmd/fscrypt/commands.go')
| -rw-r--r-- | cmd/fscrypt/commands.go | 482 |
1 files changed, 380 insertions, 102 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 2f23a0f..3fc68a9 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -24,55 +24,72 @@ import ( "fmt" "log" "os" + "path/filepath" + "strings" "github.com/pkg/errors" "github.com/urfave/cli" "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/keyring" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) -// Setup is a command which can to global or per-filesystem initialization. +// Setup is a command which can do global or per-filesystem initialization. var Setup = cli.Command{ Name: "setup", ArgsUsage: fmt.Sprintf("[%s]", mountpointArg), Usage: "perform global setup or filesystem setup", Description: fmt.Sprintf(`This command creates fscrypt's global config - file or enables fscrypt on a filesystem. - - (1) When used without %[1]s, create the parameters in %[2]s. - This is primarily used to configure the passphrase hashing - parameters to the appropriate hardness (as determined by %[3]s). - Being root is required to write the config file. - - (2) When used with %[1]s, enable fscrypt on %[1]s. This involves - creating the necessary folders on the filesystem which will hold - the metadata structures. Begin root may be required to create - these folders.`, mountpointArg, actions.ConfigFileLocation, + file and/or prepares a filesystem for use with fscrypt. + + (1) When used without %[1]s, this command creates the global + config file %[2]s and the fscrypt metadata directory for the + root filesystem (i.e. /.fscrypt). This requires root privileges. + The passphrase hashing parameters in %[2]s are automatically set + to an appropriate hardness, as determined by %[3]s. The root + filesystem's metadata directory is created even if the root + filesystem doesn't support encryption itself, since it's where + login passphrase protectors are stored. + + (2) When used with %[1]s, this command creates the fscrypt + metadata directory for the filesystem mounted at %[1]s. This + allows fscrypt to be used on that filesystem, provided that any + kernel and filesystem-specific prerequisites are also met (see + the README). This may require root privileges.`, + mountpointArg, actions.ConfigFileLocation, shortDisplay(timeTargetFlag)), - Flags: []cli.Flag{timeTargetFlag, legacyFlag, forceFlag}, + Flags: []cli.Flag{timeTargetFlag, forceFlag, allUsersSetupFlag}, Action: setupAction, } func setupAction(c *cli.Context) error { - var err error switch c.NArg() { case 0: // Case (1) - global setup - err = createGlobalConfig(c.App.Writer, actions.ConfigFileLocation) + if err := createGlobalConfig(c.App.Writer, actions.ConfigFileLocation); err != nil { + return newExitError(c, err) + } + if err := setupFilesystem(c.App.Writer, actions.LoginProtectorMountpoint); err != nil { + if _, ok := err.(*filesystem.ErrAlreadySetup); !ok { + return newExitError(c, err) + } + fmt.Fprintf(c.App.Writer, + "Skipping creating %s because it already exists.\n", + filepath.Join(actions.LoginProtectorMountpoint, ".fscrypt")) + } case 1: // Case (2) - filesystem setup - err = setupFilesystem(c.App.Writer, c.Args().Get(0)) + if err := setupFilesystem(c.App.Writer, c.Args().Get(0)); err != nil { + return newExitError(c, err) + } default: return expectedArgsErr(c, 1, true) } - - if err != nil { - return newExitError(c, err) - } return nil } @@ -90,7 +107,7 @@ var Encrypt = cli.Command{ immediately be used.`, directoryArg, shortDisplay(policyFlag), shortDisplay(protectorFlag), mountpointArg), Flags: []cli.Flag{policyFlag, unlockWithFlag, protectorFlag, sourceFlag, - userFlag, nameFlag, keyFileFlag, skipUnlockFlag}, + userFlag, nameFlag, keyFileFlag, skipUnlockFlag, noRecoveryFlag}, Action: encryptAction, } @@ -104,6 +121,13 @@ func encryptAction(c *cli.Context) error { return newExitError(c, err) } + // Most people expect that other users can't see their encrypted files + // while they're unlocked, so change the directory's mode to 0700. + if err := os.Chmod(path, 0700); err != nil { + fmt.Fprintf(c.App.Writer, "Warning: unable to chmod %q to 0700 [%v]\n", path, err) + // Continue on; don't consider this a fatal error. + } + if !skipUnlockFlag.Value { fmt.Fprintf(c.App.Writer, "%q is now encrypted, unlocked, and ready for use.\n", path) @@ -115,15 +139,71 @@ func encryptAction(c *cli.Context) error { return nil } +// validateKeyringPrereqs ensures we're ready to add, remove, or get the status +// of the key for the given encryption policy (if policy != nil) or for the +// current default encryption policy (if policy == nil). +func validateKeyringPrereqs(ctx *actions.Context, policy *actions.Policy) error { + var policyVersion int64 + if policy == nil { + policyVersion = ctx.Config.Options.PolicyVersion + } else { + policyVersion = policy.Version() + } + // If it's a v2 policy, we're good to go, since non-root users can + // add/remove v2 policy keys directly to/from the filesystem, where they + // are usable by the filesystem on behalf of any process. + if policyVersion != 1 { + return nil + } + if ctx.Config.GetUseFsKeyringForV1Policies() { + // We'll be using the filesystem keyring, but it's a v1 + // encryption policy so root is required. + if !util.IsUserRoot() { + return ErrFsKeyringPerm + } + return nil + } + // We'll be using the target user's user keyring, so make sure a user + // was explicitly specified if the command is being run as root, and + // make sure that user's keyring is accessible. + if userFlag.Value == "" && util.IsUserRoot() { + return ErrSpecifyUser + } + if _, err := keyring.UserKeyringID(ctx.TargetUser, true); err != nil { + return err + } + return nil +} + +func writeRecoveryInstructions(recoveryPassphrase *crypto.Key, recoveryProtector *actions.Protector, + policy *actions.Policy, dirPath string) error { + if recoveryPassphrase == nil { + return nil + } + recoveryFile := filepath.Join(dirPath, "fscrypt_recovery_readme.txt") + if err := actions.WriteRecoveryInstructions(recoveryPassphrase, recoveryProtector, + policy, recoveryFile); err != nil { + return err + } + msg := fmt.Sprintf(`See %q for important recovery instructions. + It is *strongly recommended* to record the recovery passphrase in a + secure location; otherwise you will lose access to this directory if you + reinstall the operating system or move this filesystem to another + system.`, recoveryFile) + hdr := "IMPORTANT: " + fmt.Print("\n" + hdr + wrapText(msg, len(hdr)) + "\n\n") + return nil +} + // encryptPath sets up encryption on path and provisions the policy to the // keyring unless --skip-unlock is used. On failure, an error is returned, any // metadata creation is reverted, and the directory is unmodified. func encryptPath(path string) (err error) { - target, err := parseUserFlag(!skipUnlockFlag.Value) + targetUser, err := parseUserFlag() if err != nil { return } - ctx, err := actions.NewContextFromPath(path, target) + ctx, err := actions.NewContextFromPath(path, targetUser) if err != nil { return } @@ -132,20 +212,37 @@ func encryptPath(path string) (err error) { } var policy *actions.Policy + var recoveryPassphrase *crypto.Key + var recoveryProtector *actions.Protector if policyFlag.Value != "" { log.Printf("getting policy for %q", path) - policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser) + if policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser); err != nil { + return + } + defer policy.Lock() + + if !skipUnlockFlag.Value { + if err = validateKeyringPrereqs(ctx, policy); err != nil { + return + } + } } else { log.Printf("creating policy for %q", path) + if !skipUnlockFlag.Value { + if err = validateKeyringPrereqs(ctx, nil); err != nil { + return + } + } + protector, created, protErr := selectOrCreateProtector(ctx) - // Successfully created protector should be reverted on failure. if protErr != nil { return protErr } defer func() { protector.Lock() + // Successfully created protector should be reverted on failure. if err != nil && created { protector.Revert() } @@ -154,39 +251,70 @@ func encryptPath(path string) (err error) { if err = protector.Unlock(existingKeyFn); err != nil { return } - policy, err = actions.CreatePolicy(ctx, protector) - } - // Successfully created policy should be reverted on failure. - if err != nil { - return - } - defer func() { - policy.Lock() - if err != nil { - policy.Deprovision() - policy.Revert() + if policy, err = actions.CreatePolicy(ctx, protector); err != nil { + return } - }() + defer func() { + policy.Lock() + // Successfully created policy should be reverted on failure. + if err != nil { + policy.Revert() + } + }() - // Unlock() first, so if the Unlock() fails the directory isn't changed. - if !skipUnlockFlag.Value { + // Generate a recovery passphrase if needed. + if ctx.Mount != protector.Context.Mount && !noRecoveryFlag.Value { + if recoveryPassphrase, recoveryProtector, err = actions.AddRecoveryPassphrase( + policy, filepath.Base(path)); err != nil { + return + } + defer func() { + recoveryPassphrase.Wipe() + recoveryProtector.Lock() + // Successfully created protector should be reverted on failure. + if err != nil { + recoveryProtector.Revert() + } + }() + } + } + + // Unlock() and Provision() first, so if that if these fail the + // directory isn't changed, and also because v2 policies can't be + // applied while deprovisioned unless the process is running as root. + if !skipUnlockFlag.Value || !policy.CanBeAppliedWithoutProvisioning() { if err = policy.Unlock(optionFn, existingKeyFn); err != nil { return } if err = policy.Provision(); err != nil { return } + defer func() { + if err != nil || skipUnlockFlag.Value { + policy.Deprovision(false) + } + }() } - if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) { - // EACCES at this point indicates ownership issues. - err = errors.Wrap(ErrBadOwners, path) + if err = policy.Apply(path); err != nil { + return } - return + return writeRecoveryInstructions(recoveryPassphrase, recoveryProtector, policy, path) } // checkEncryptable returns an error if the path cannot be encrypted. func checkEncryptable(ctx *actions.Context, path string) error { - log.Printf("ensuring %s is an empty and readable directory", path) + + log.Printf("checking whether %q is already encrypted", path) + if _, err := metadata.GetPolicy(path); err == nil { + return &metadata.ErrAlreadyEncrypted{Path: path} + } + + log.Printf("checking whether filesystem %s supports encryption", ctx.Mount.Path) + if err := ctx.Mount.CheckSupport(); err != nil { + return err + } + + log.Printf("checking whether %q is an empty and readable directory", path) f, err := os.Open(path) if err != nil { return err @@ -196,29 +324,17 @@ func checkEncryptable(ctx *actions.Context, path string) error { switch names, err := f.Readdirnames(-1); { case err != nil: // Could not read directory (might not be a directory) - log.Print(errors.Wrap(err, path)) - return errors.Wrap(ErrNotEmptyDir, path) - case len(names) > 0: - log.Printf("directory %s is not empty", path) - return errors.Wrap(ErrNotEmptyDir, path) - } - - log.Printf("ensuring %s supports encryption and filesystem is using fscrypt", path) - switch _, err := actions.GetPolicyFromPath(ctx, path); errors.Cause(err) { - case metadata.ErrNotEncrypted: - // We are not encrypted. Finally, we check that the filesystem - // supports encryption - return ctx.Mount.CheckSupport() - case nil: - // We are encrypted - return errors.Wrap(metadata.ErrEncrypted, path) - default: + err = errors.Wrap(err, path) + log.Print(err) return err + case len(names) > 0: + return &ErrDirNotEmpty{path} } + return err } // selectOrCreateProtector uses user input (or flags) to either create a new -// protector or select and existing one. The boolean return value is true if we +// protector or select an existing one. The boolean return value is true if we // created a new protector. func selectOrCreateProtector(ctx *actions.Context) (*actions.Protector, bool, error) { if protectorFlag.Value != "" { @@ -262,8 +378,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, @@ -274,12 +390,12 @@ func unlockAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - target, err := parseUserFlag(true) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } path := c.Args().Get(0) - ctx, err := actions.NewContextFromPath(path, target) + ctx, err := actions.NewContextFromPath(path, targetUser) if err != nil { return newExitError(c, err) } @@ -290,10 +406,15 @@ func unlockAction(c *cli.Context) error { if err != nil { return newExitError(c, err) } + // Ensure the keyring is ready. + if err = validateKeyringPrereqs(ctx, policy); err != nil { + return newExitError(c, err) + } // Check if directory is already unlocked - if policy.IsProvisioned() { - log.Printf("policy %s is already provisioned", policy.Descriptor()) - return newExitError(c, errors.Wrapf(ErrPolicyUnlocked, path)) + if policy.IsProvisionedByTargetUser() { + log.Printf("policy %s is already provisioned by %v", + policy.Descriptor(), ctx.TargetUser.Username) + return newExitError(c, errors.Wrap(ErrDirAlreadyUnlocked, path)) } if err := policy.Unlock(optionFn, existingKeyFn); err != nil { @@ -309,6 +430,163 @@ 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. + + If the directory uses a v1 encryption policy, then the %s=true + option may be needed to properly lock it. Root is required for + this. + + If the directory uses a v2 encryption policy, then a non-root + user can lock it, but only if it's the same user who unlocked it + originally and if no other users have unlocked it too. + + 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, allUsersLockFlag}, + Action: lockAction, +} + +func lockAction(c *cli.Context) error { + if c.NArg() != 1 { + return expectedArgsErr(c, 1, false) + } + + targetUser, err := parseUserFlag() + 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) + } + // Ensure the keyring is ready. + if err = validateKeyringPrereqs(ctx, policy); err != nil { + return newExitError(c, err) + } + // Check for permission to drop caches, if it may be needed. + if policy.NeedsUserKeyring() && dropCachesFlag.Value && !util.IsUserRoot() { + return newExitError(c, ErrDropCachesPerm) + } + + if err = policy.Deprovision(allUsersLockFlag.Value); err != nil { + switch err { + case keyring.ErrKeyNotPresent: + break + case keyring.ErrKeyAddedByOtherUsers: + return newExitError(c, &ErrDirUnlockedByOtherUsers{path}) + case keyring.ErrKeyFilesOpen: + return newExitError(c, &ErrDirFilesOpen{path}) + default: + return newExitError(c, err) + } + // Key is no longer present. Normally that means the directory + // is already locked; in that case we exit with an error. But + // if the policy uses the user keyring (v1 policies only), then + // the directory might have been incompletely locked earlier, + // due to open files. Try to detect that case and finish + // locking the directory by dropping caches again. + if !policy.NeedsUserKeyring() || !isDirUnlockedHeuristic(path) { + log.Printf("policy %s is already fully deprovisioned", policy.Descriptor()) + return newExitError(c, errors.Wrap(ErrDirAlreadyLocked, path)) + } + } + + if policy.NeedsUserKeyring() { + if err = dropCachesIfRequested(c, ctx); err != nil { + return newExitError(c, err) + } + if isDirUnlockedHeuristic(path) { + return newExitError(c, &ErrDirFilesOpen{path}) + } + } + + fmt.Fprintf(c.App.Writer, "%q is now locked.\n", path) + return nil +} + +func isPossibleNoKeyName(filename string) bool { + // No-key names are at least 22 bytes long, since they are + // base64-encoded and ciphertext filenames are at least 16 bytes. + if len(filename) < 22 { + return false + } + // On the latest kernels, no-key names contain only base64url characters + // (A-Z, a-z, 0-9, -, and _). On older kernels, the + and , characters + // were used too. Allow all of these characters. + validChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_+," + for _, char := range filename { + if !strings.ContainsRune(validChars, char) { + return false + } + } + return true +} + +// isDirUnlockedHeuristic returns true if the directory is definitely still +// unlocked. This is the case if we can create a subdirectory or if the +// directory contains filenames that aren't valid no-key names. It returns +// false if the directory is probably locked (though it could also be unlocked). +// +// This is only useful if the directory's policy uses the user keyring, since +// otherwise the status can be easily found via the filesystem keyring. +func isDirUnlockedHeuristic(dirPath string) bool { + subdirPath := filepath.Join(dirPath, "fscrypt-is-dir-unlocked") + if err := os.Mkdir(subdirPath, 0700); err == nil { + os.Remove(subdirPath) + return true + } + dir, err := os.Open(dirPath) + if err != nil { + return false + } + defer dir.Close() + + names, err := dir.Readdirnames(-1) + if err != nil { + return false + } + for _, name := range names { + if !isPossibleNoKeyName(name) { + return true + } + } + return false +} + // Purge removes all the policy keys from the keyring (also need unmount). var Purge = cli.Command{ Name: "purge", @@ -358,15 +636,18 @@ func purgeAction(c *cli.Context) error { } } - target, err := parseUserFlag(true) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } mountpoint := c.Args().Get(0) - ctx, err := actions.NewContextFromMountpoint(mountpoint, target) + ctx, err := actions.NewContextFromMountpoint(mountpoint, targetUser) if err != nil { return newExitError(c, err) } + if err = validateKeyringPrereqs(ctx, nil); err != nil { + return newExitError(c, err) + } question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path) if dropCachesFlag.Value { @@ -382,13 +663,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 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 } @@ -429,17 +705,15 @@ func statusAction(c *cli.Context) error { err = writeGlobalStatus(c.App.Writer) case 1: path := c.Args().Get(0) - ctx, mntErr := actions.NewContextFromMountpoint(path, nil) - switch errors.Cause(mntErr) { - case nil: + var ctx *actions.Context + ctx, err = actions.NewContextFromMountpoint(path, nil) + if err == nil { // Case (2) - mountpoint status err = writeFilesystemStatus(c.App.Writer, ctx) - case filesystem.ErrNotAMountpoint: + } else if _, ok := err.(*filesystem.ErrNotAMountpoint); ok { // Case (3) - file or directory status err = writePathStatus(c.App.Writer, path) - default: - err = mntErr } default: return expectedArgsErr(c, 1, true) @@ -474,7 +748,7 @@ var Metadata = cli.Command{ (4) Changing the protector protecting a policy using the "add-protector-to-policy" and "remove-protector-from-policy" subcommands.`, - Subcommands: []cli.Command{createMetadata, destoryMetadata, changePassphrase, + Subcommands: []cli.Command{createMetadata, destroyMetadata, changePassphrase, addProtectorToPolicy, removeProtectorFromPolicy, dumpMetadata}, } @@ -508,18 +782,18 @@ func createProtectorAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - target, err := parseUserFlag(false) + targetUser, err := parseUserFlag() if err != nil { return newExitError(c, err) } mountpoint := c.Args().Get(0) - ctx, err := actions.NewContextFromMountpoint(mountpoint, target) + ctx, err := actions.NewContextFromMountpoint(mountpoint, targetUser) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Create new protector on %q", ctx.Mount.Path) - if err := askConfirmation(prompt, true, ""); err != nil { + if err = askConfirmation(prompt, true, ""); err != nil { return newExitError(c, err) } @@ -537,8 +811,8 @@ func createProtectorAction(c *cli.Context) error { var createPolicy = cli.Command{ Name: "policy", ArgsUsage: fmt.Sprintf("%s %s", mountpointArg, shortDisplay(protectorFlag)), - Usage: "create a new protector on a filesystem", - Description: fmt.Sprintf(`This command creates a new protector on %s + Usage: "create a new policy on a filesystem", + Description: fmt.Sprintf(`This command creates a new policy on %s that has not (yet) been applied to any directory. After creation, the user can use %s with "fscrypt encrypt" to encrypt a directory with this new policy. As all policies must be @@ -561,20 +835,20 @@ func createPolicyAction(c *cli.Context) error { return newExitError(c, err) } - if err := checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil { + if err = checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil { return err } protector, err := getProtectorFromFlag(protectorFlag.Value, ctx.TargetUser) if err != nil { return newExitError(c, err) } - if err := protector.Unlock(existingKeyFn); err != nil { + if err = protector.Unlock(existingKeyFn); err != nil { return newExitError(c, err) } defer protector.Lock() prompt := fmt.Sprintf("Create new policy on %q", ctx.Mount.Path) - if err := askConfirmation(prompt, true, ""); err != nil { + if err = askConfirmation(prompt, true, ""); err != nil { return newExitError(c, err) } @@ -589,7 +863,7 @@ func createPolicyAction(c *cli.Context) error { return nil } -var destoryMetadata = cli.Command{ +var destroyMetadata = cli.Command{ Name: "destroy", ArgsUsage: fmt.Sprintf("[%s | %s | %s]", shortDisplay(protectorFlag), shortDisplay(policyFlag), mountpointArg), @@ -616,10 +890,10 @@ var destoryMetadata = cli.Command{ shortDisplay(protectorFlag), shortDisplay(policyFlag), mountpointArg), Flags: []cli.Flag{protectorFlag, policyFlag, forceFlag}, - Action: destoryMetadataAction, + Action: destroyMetadataAction, } -func destoryMetadataAction(c *cli.Context) error { +func destroyMetadataAction(c *cli.Context) error { switch c.NArg() { case 0: switch { @@ -759,6 +1033,9 @@ func addProtectorAction(c *cli.Context) error { } // Sanity check before unlocking everything if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked { + if err == nil { + err = errors.New("policy and protector are not locked") + } return newExitError(c, err) } @@ -806,29 +1083,30 @@ func removeProtectorAction(c *cli.Context) error { return err } - // We do not need to unlock anything for this operation - protector, err := getProtectorFromFlag(protectorFlag.Value, nil) + // We only need the protector descriptor, not the protector itself. + ctx, protectorDescriptor, err := parseMetadataFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } - policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) + // We don't need to unlock the policy for this operation. + policy, err := getPolicyFromFlag(policyFlag.Value, ctx.TargetUser) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?", - policy.Descriptor(), protector.Descriptor()) + policy.Descriptor(), protectorDescriptor) warning := "All files using this policy will NO LONGER be accessible with this protector!!" if err := askConfirmation(prompt, false, warning); err != nil { return newExitError(c, err) } - if err := policy.RemoveProtector(protector); err != nil { + if err := policy.RemoveProtector(protectorDescriptor); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Protector %s no longer protecting policy %s.\n", - protector.Descriptor(), policy.Descriptor()) + protectorDescriptor, policy.Descriptor()) return nil } |