diff options
| -rw-r--r-- | cmd/fscrypt/commands.go | 70 | ||||
| -rw-r--r-- | cmd/fscrypt/errors.go | 10 | ||||
| -rw-r--r-- | cmd/fscrypt/flags.go | 57 | ||||
| -rw-r--r-- | cmd/fscrypt/prompt.go | 2 | ||||
| -rw-r--r-- | cmd/fscrypt/protector.go | 4 | ||||
| -rw-r--r-- | cmd/fscrypt/setup.go | 5 | ||||
| -rw-r--r-- | cmd/fscrypt/status.go | 2 |
7 files changed, 104 insertions, 46 deletions
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index 3e8bc98..43c9cb0 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -25,8 +25,6 @@ import ( "log" "os" - "golang.org/x/sys/unix" - "github.com/pkg/errors" "github.com/urfave/cli" @@ -34,6 +32,7 @@ import ( "github.com/google/fscrypt/filesystem" "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. @@ -60,7 +59,6 @@ var Setup = cli.Command{ func setupAction(c *cli.Context) error { var err error - switch c.NArg() { case 0: // Case (1) - global setup @@ -92,7 +90,7 @@ var Encrypt = cli.Command{ immediately be used.`, directoryArg, shortDisplay(policyFlag), shortDisplay(protectorFlag), mountpointArg), Flags: []cli.Flag{policyFlag, unlockWithFlag, protectorFlag, sourceFlag, - nameFlag, keyFileFlag, skipUnlockFlag}, + userFlag, nameFlag, keyFileFlag, skipUnlockFlag}, Action: encryptAction, } @@ -121,7 +119,11 @@ func encryptAction(c *cli.Context) error { // 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) { - ctx, err := actions.NewContextFromPath(path) + target, err := parseUserFlag() + if err != nil { + return + } + ctx, err := actions.NewContextFromPath(path, target) if err != nil { return } @@ -133,7 +135,7 @@ func encryptPath(path string) (err error) { if policyFlag.Value != "" { log.Printf("getting policy for %q", path) - policy, err = getPolicyFromFlag(policyFlag.Value) + policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser) } else { log.Printf("creating policy for %q", path) @@ -220,7 +222,7 @@ func checkEncryptable(ctx *actions.Context, path string) error { // created a new protector. func selectOrCreateProtector(ctx *actions.Context) (*actions.Protector, bool, error) { if protectorFlag.Value != "" { - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, ctx.TargetUser) return protector, false, err } @@ -263,7 +265,7 @@ var Unlock = cli.Command{ locked again upon reboot, or after running "fscrypt purge" and unmounting the corresponding filesystem.`, directoryArg, shortDisplay(unlockWithFlag)), - Flags: []cli.Flag{unlockWithFlag, keyFileFlag}, + Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag}, Action: unlockAction, } @@ -272,8 +274,12 @@ func unlockAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } + target, err := parseUserFlag() + if err != nil { + return newExitError(c, err) + } path := c.Args().Get(0) - ctx, err := actions.NewContextFromPath(path) + ctx, err := actions.NewContextFromPath(path, target) if err != nil { return newExitError(c, err) } @@ -336,7 +342,7 @@ var Purge = cli.Command{ can be eliminated by cycling the power or mitigated by using page cache and slab cache poisoning.`, mountpointArg, shortDisplay(dropCachesFlag)), - Flags: []cli.Flag{forceFlag, dropCachesFlag}, + Flags: []cli.Flag{forceFlag, dropCachesFlag, userFlag}, Action: purgeAction, } @@ -346,12 +352,17 @@ func purgeAction(c *cli.Context) error { } if dropCachesFlag.Value { - if unix.Geteuid() != 0 { + if !util.IsUserRoot() { return newExitError(c, ErrDropCachesPerm) } } - ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0)) + target, err := parseUserFlag() + if err != nil { + return newExitError(c, err) + } + mountpoint := c.Args().Get(0) + ctx, err := actions.NewContextFromMountpoint(mountpoint, target) if err != nil { return newExitError(c, err) } @@ -417,7 +428,7 @@ func statusAction(c *cli.Context) error { err = writeGlobalStatus(c.App.Writer) case 1: path := c.Args().Get(0) - ctx, mntErr := actions.NewContextFromMountpoint(path) + ctx, mntErr := actions.NewContextFromMountpoint(path, nil) switch errors.Cause(mntErr) { case nil: @@ -487,7 +498,7 @@ var createProtector = cli.Command{ applicable). As with "fscrypt encrypt", these prompts can be disabled with the appropriate flags.`, mountpointArg, shortDisplay(protectorFlag)), - Flags: []cli.Flag{sourceFlag, nameFlag, keyFileFlag}, + Flags: []cli.Flag{sourceFlag, nameFlag, keyFileFlag, userFlag}, Action: createProtectorAction, } @@ -496,7 +507,12 @@ func createProtectorAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0)) + target, err := parseUserFlag() + if err != nil { + return newExitError(c, err) + } + mountpoint := c.Args().Get(0) + ctx, err := actions.NewContextFromMountpoint(mountpoint, target) if err != nil { return newExitError(c, err) } @@ -539,7 +555,7 @@ func createPolicyAction(c *cli.Context) error { return expectedArgsErr(c, 1, false) } - ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0)) + ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0), nil) if err != nil { return newExitError(c, err) } @@ -547,7 +563,7 @@ func createPolicyAction(c *cli.Context) error { if err := checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil { return err } - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, ctx.TargetUser) if err != nil { return newExitError(c, err) } @@ -608,7 +624,7 @@ func destoryMetadataAction(c *cli.Context) error { switch { case protectorFlag.Value != "": // Case (1) - protector destroy - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } @@ -627,7 +643,7 @@ func destoryMetadataAction(c *cli.Context) error { protector.Descriptor(), protector.Context.Mount.Path) case policyFlag.Value != "": // Case (2) - policy destroy - policy, err := getPolicyFromFlag(policyFlag.Value) + policy, err := getPolicyFromFlag(policyFlag.Value, nil) if err != nil { return newExitError(c, err) } @@ -654,7 +670,7 @@ func destoryMetadataAction(c *cli.Context) error { case 1: // Case (3) - mountpoint destroy path := c.Args().Get(0) - ctx, err := actions.NewContextFromMountpoint(path) + ctx, err := actions.NewContextFromMountpoint(path, nil) if err != nil { return newExitError(c, err) } @@ -694,7 +710,7 @@ func changePassphraseAction(c *cli.Context) error { return err } - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } @@ -732,11 +748,11 @@ func addProtectorAction(c *cli.Context) error { return err } - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } - policy, err := getPolicyFromFlag(policyFlag.Value) + policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) if err != nil { return newExitError(c, err) } @@ -790,11 +806,11 @@ func removeProtectorAction(c *cli.Context) error { } // We do not need to unlock anything for this operation - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } - policy, err := getPolicyFromFlag(policyFlag.Value) + policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) if err != nil { return newExitError(c, err) } @@ -834,14 +850,14 @@ func dumpMetadataAction(c *cli.Context) error { switch { case protectorFlag.Value != "": // Case (1) - protector print - protector, err := getProtectorFromFlag(protectorFlag.Value) + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } fmt.Fprintln(c.App.Writer, protector) case policyFlag.Value != "": // Case (2) - policy print - policy, err := getPolicyFromFlag(policyFlag.Value) + policy, err := getPolicyFromFlag(policyFlag.Value, nil) if err != nil { return newExitError(c, err) } diff --git a/cmd/fscrypt/errors.go b/cmd/fscrypt/errors.go index b2aa57e..88525d1 100644 --- a/cmd/fscrypt/errors.go +++ b/cmd/fscrypt/errors.go @@ -60,6 +60,8 @@ var ( ErrNotPassphrase = errors.New("protector does not use a passphrase") ErrUnknownUser = errors.New("unknown user") ErrDropCachesPerm = errors.New("inode cache can only be dropped as root") + ErrSpecifyUser = errors.New("user must be specified when run as root") + ErrSpecifyNonRootUser = errors.New("non-root user must be specified") ) var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag)) @@ -125,6 +127,14 @@ func getErrorSuggestions(err error) string { properly clear the inode cache, or it should be run with %s=false (this may leave encrypted files and directories in an accessible state).`, shortDisplay(dropCachesFlag)) + case ErrSpecifyUser: + return fmt.Sprintf(`When running this command as root, you + usually still want to provision/remove keys for a normal + user's keyring and use a normal user's login passphrase + as a protector (so the corresponding files will be + accessible for that user). This can be done with %s. To + use the root user's keyring or passphrase, use + --%s=root.`, shortDisplay(userFlag), userFlag.GetName()) case ErrAllLoadsFailed: return loadHelpText default: diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index a06b952..e883a6d 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -25,6 +25,7 @@ import ( "flag" "fmt" "log" + "os/user" "regexp" "strconv" "time" @@ -32,6 +33,7 @@ import ( "github.com/urfave/cli" "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/util" ) // We define the types boolFlag, durationFlag, and stringFlag here instead of @@ -204,6 +206,12 @@ var ( formatted as raw binary and should be exactly 32 bytes long.`, } + userFlag = &stringFlag{ + Name: "user", + ArgName: "USERNAME", + Usage: `Specifiy which user should be used for login passphrases + or to which user's keyring keys should be provisioned.`, + } protectorFlag = &stringFlag{ Name: "protector", ArgName: "MOUNTPOINT:ID", @@ -233,27 +241,31 @@ var ( // group is required and corresponds to the descriptor. var idFlagRegex = regexp.MustCompile("^([[:print:]]+):([[:alnum:]]+)$") +func matchMetadataFlag(flagValue string) (mountpoint, descriptor string, err error) { + matches := idFlagRegex.FindStringSubmatch(flagValue) + if matches == nil { + return "", "", fmt.Errorf("flag value %q does not have format %s", + flagValue, mountpointIDArg) + } + log.Printf("parsed flag: mountpoint=%q descriptor=%s", matches[1], matches[2]) + return matches[1], matches[2], nil +} + // parseMetadataFlag takes the value of either protectorFlag or policyFlag // formatted as MOUNTPOINT:DESCRIPTOR, and returns a context for the mountpoint // and a string for the descriptor. -func parseMetadataFlag(flagValue string) (*actions.Context, string, error) { - matches := idFlagRegex.FindStringSubmatch(flagValue) - if matches == nil { - err := fmt.Errorf("flag value %q does not have format %s", flagValue, mountpointIDArg) +func parseMetadataFlag(flagValue string, target *user.User) (*actions.Context, string, error) { + mountpoint, descriptor, err := matchMetadataFlag(flagValue) + if err != nil { return nil, "", err } - - mountpoint := matches[1] - descriptor := matches[2] - log.Printf("parsed flag: mountpoint=%q descriptor=%s", mountpoint, descriptor) - - ctx, err := actions.NewContextFromMountpoint(mountpoint) + ctx, err := actions.NewContextFromMountpoint(mountpoint, target) return ctx, descriptor, err } // getProtectorFromFlag gets an existing locked protector from protectorFlag. -func getProtectorFromFlag(flagValue string) (*actions.Protector, error) { - ctx, descriptor, err := parseMetadataFlag(flagValue) +func getProtectorFromFlag(flagValue string, target *user.User) (*actions.Protector, error) { + ctx, descriptor, err := parseMetadataFlag(flagValue, target) if err != nil { return nil, err } @@ -261,10 +273,27 @@ func getProtectorFromFlag(flagValue string) (*actions.Protector, error) { } // getPolicyFromFlag gets an existing locked policy from policyFlag. -func getPolicyFromFlag(flagValue string) (*actions.Policy, error) { - ctx, descriptor, err := parseMetadataFlag(flagValue) +func getPolicyFromFlag(flagValue string, target *user.User) (*actions.Policy, error) { + ctx, descriptor, err := parseMetadataFlag(flagValue, target) if err != nil { return nil, err } return actions.GetPolicy(ctx, descriptor) } + +// parseUserFlag returns the user specified by userFlag or the current effective +// user if the flag value is missing. If the effective user is root, however, a +// user must specified in the flag. +func parseUserFlag() (*user.User, error) { + if userFlag.Value != "" { + return user.Lookup(userFlag.Value) + } + effectiveUser, err := util.EffectiveUser() + if err != nil { + return nil, err + } + if util.IsUserRoot() { + return nil, ErrSpecifyUser + } + return effectiveUser, nil +} diff --git a/cmd/fscrypt/prompt.go b/cmd/fscrypt/prompt.go index b882c08..0031e8f 100644 --- a/cmd/fscrypt/prompt.go +++ b/cmd/fscrypt/prompt.go @@ -308,7 +308,7 @@ func optionFn(policyDescriptor string, options []*actions.ProtectorOption) (int, // protector to unlock the policy. if unlockWithFlag.Value != "" { log.Printf("optionFn(%s) w/ unlock flag", policyDescriptor) - protector, err := getProtectorFromFlag(unlockWithFlag.Value) + protector, err := getProtectorFromFlag(unlockWithFlag.Value, nil) if err != nil { return 0, err } diff --git a/cmd/fscrypt/protector.go b/cmd/fscrypt/protector.go index f54d3a4..32ba4ab 100644 --- a/cmd/fscrypt/protector.go +++ b/cmd/fscrypt/protector.go @@ -119,5 +119,7 @@ func modifiedContext(ctx *actions.Context) (*actions.Context, error) { return nil, err } - return &actions.Context{Config: ctx.Config, Mount: mnt}, nil + modifiedCtx := *ctx + modifiedCtx.Mount = mnt + return &modifiedCtx, nil } diff --git a/cmd/fscrypt/setup.go b/cmd/fscrypt/setup.go index 6433b0c..72dfbdb 100644 --- a/cmd/fscrypt/setup.go +++ b/cmd/fscrypt/setup.go @@ -26,11 +26,12 @@ import ( "os" "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/util" ) // createGlobalConfig creates (or overwrites) the global config file func createGlobalConfig(w io.Writer, path string) error { - if os.Getuid() != 0 { + if !util.IsUserRoot() { return ErrMustBeRoot } @@ -61,7 +62,7 @@ func createGlobalConfig(w io.Writer, path string) error { // setupFilesystem creates the directories for a filesystem to use fscrypt. func setupFilesystem(w io.Writer, path string) error { - ctx, err := actions.NewContextFromMountpoint(path) + ctx, err := actions.NewContextFromMountpoint(path, nil) if err != nil { return err } diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go index c2adad7..1465a4e 100644 --- a/cmd/fscrypt/status.go +++ b/cmd/fscrypt/status.go @@ -166,7 +166,7 @@ func writeFilesystemStatus(w io.Writer, ctx *actions.Context) error { } func writePathStatus(w io.Writer, path string) error { - ctx, err := actions.NewContextFromPath(path) + ctx, err := actions.NewContextFromPath(path, nil) if err != nil { return err } |