Skip to content

Commit

Permalink
zsysctl: add flag to support creating an encrypted dataset
Browse files Browse the repository at this point in the history
Create an encrypted dataset with raw type keyfile when particular
flag is given to userdata create.

For example:
  $ zsysctl userdata create -e myuser /home/myuser

Signed-off-by: Crag Wang <crag.wang@dell.com>
  • Loading branch information
CragW committed Nov 4, 2020
1 parent 307ac9d commit 6c2e48a
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 15 deletions.
10 changes: 6 additions & 4 deletions cmd/zsysd/client/userdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var (
Use: "create USER HOME_DIRECTORY",
Short: i18n.G("Create a new home user dataset via an user dataset (if doesn't exist) creation"),
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) { cmdErr = createUserData(args[0], args[1]) },
Run: func(cmd *cobra.Command, args []string) { cmdErr = createUserData(args[0], args[1], encryptHome) },
}
userdataRenameCmd = &cobra.Command{
Use: "set-home OLD_HOME NEW_HOME",
Expand All @@ -40,20 +40,22 @@ var (
)

var (
removeHome bool
removeHome bool
encryptHome bool
)

func init() {
rootCmd.AddCommand(userdataCmd)
userdataCmd.AddCommand(userdataCreateCmd)
userdataCmd.AddCommand(userdataRenameCmd)
userdataCmd.AddCommand(userdataDissociateCmd)
userdataCreateCmd.Flags().BoolVarP(&encryptHome, "encrypt", "e", false, i18n.G("Encrypt home directory in associatation with keyfile"))
userdataDissociateCmd.Flags().BoolVarP(&removeHome, "remove", "r", false, i18n.G("Empty home directory content if not associated to any machine state"))
}

// createUserData creates a new userdata for user and set it to homepath on current zsys system.
// if the user already exists for a dataset attached to the current system, set its mountpoint to homepath.
func createUserData(user, homepath string) (err error) {
func createUserData(user, homepath string, encryptHome bool) (err error) {
client, err := newClient()
if err != nil {
return err
Expand All @@ -63,7 +65,7 @@ func createUserData(user, homepath string) (err error) {
ctx, cancel, reset := contextWithResettableTimeout(client.Ctx, config.DefaultClientTimeout)
defer cancel()

stream, err := client.CreateUserData(ctx, &zsys.CreateUserDataRequest{User: user, Homepath: homepath})
stream, err := client.CreateUserData(ctx, &zsys.CreateUserDataRequest{User: user, Homepath: homepath, EncryptHome: encryptHome})
if err = checkConn(err, reset); err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion internal/daemon/userdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ func (s *Server) CreateUserData(req *zsys.CreateUserDataRequest, stream zsys.Zsy
}

user := req.GetUser()
encryptHome := req.GetEncryptHome()
homepath := req.GetHomepath()
s.RWRequest.Lock()
defer s.RWRequest.Unlock()

log.Infof(stream.Context(), i18n.G("Create user dataset for %q on %q"), user, homepath)

if err := s.Machines.CreateUserData(stream.Context(), user, homepath); err != nil {
if err := s.Machines.CreateUserData(stream.Context(), user, homepath, encryptHome); err != nil {
return fmt.Errorf(i18n.G("couldn't create userdataset for %q: ")+config.ErrorFormat, homepath, err)
}
return nil
Expand Down
12 changes: 7 additions & 5 deletions internal/machines/machines_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,10 +617,11 @@ func TestUpdateLastUsed(t *testing.T) {
func TestCreateUserData(t *testing.T) {
t.Parallel()
tests := map[string]struct {
def string
user string
homePath string
cmdline string
def string
user string
homePath string
encryptHome bool
cmdline string

setPropertyErr bool
createErr bool
Expand All @@ -633,6 +634,7 @@ func TestCreateUserData(t *testing.T) {
"One machine add user dataset without userdata": {def: "m_without_userdata.yaml"},
"One machine with no user, only userdata": {def: "m_with_userdata_only.yaml"},
"No attached userdata": {def: "m_no_attached_userdata_first_pool.yaml"},
"One machine add user encrypted home dataset": {def: "m_with_userdata.yaml", encryptHome: true},

// Second pool cases
"User dataset on other pool": {def: "m_with_userdata_on_other_pool.yaml"},
Expand Down Expand Up @@ -685,7 +687,7 @@ func TestCreateUserData(t *testing.T) {
lzfs.ErrOnScan(tc.scanErr)
lzfs.ErrOnSetProperty(tc.setPropertyErr)

err = ms.CreateUserData(context.Background(), getDefaultValue(tc.user, "userfoo"), getDefaultValue(tc.homePath, "/home/foo"))
err = ms.CreateUserData(context.Background(), getDefaultValue(tc.user, "userfoo"), getDefaultValue(tc.homePath, "/home/foo"), tc.encryptHome)
if err != nil {
if !tc.wantErr {
t.Fatalf("expected no error but got: %v", err)
Expand Down
10 changes: 7 additions & 3 deletions internal/machines/userdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

// CreateUserData creates a new dataset for homepath and attach to current system.
// It creates intermediates user datasets if needed.
func (ms *Machines) CreateUserData(ctx context.Context, user, homepath string) error {
func (ms *Machines) CreateUserData(ctx context.Context, user, homepath string, encryptHome bool) error {
if !ms.current.isZsys() {
return errors.New(i18n.G("Current machine isn't Zsys, nothing to create"))
}
Expand Down Expand Up @@ -81,14 +81,18 @@ selectUserDataset:
userdatasetRoot = filepath.Join(p, zfs.UserdataPrefix)

// Create parent USERDATA
if err := t.Create(userdatasetRoot, "/", "off"); err != nil {
if err := t.Create(userdatasetRoot, "/", "off", false); err != nil {
cancel()
return fmt.Errorf(i18n.G("couldn't create user data embedder dataset: ")+config.ErrorFormat, err)
}
}

userdataset := filepath.Join(userdatasetRoot, fmt.Sprintf("%s_%s", user, t.Zfs.GenerateID(6)))
if err := t.Create(userdataset, homepath, "on"); err != nil {
var canmountProp string = "on"
if encryptHome {
canmountProp = "noauto"
}
if err := t.Create(userdataset, homepath, canmountProp, encryptHome); err != nil {
cancel()
return err
}
Expand Down
6 changes: 6 additions & 0 deletions internal/zfs/libzfs/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ const (
DatasetPropCanmount = golibzfs.DatasetPropCanmount
// DatasetPropMountpoint is the mountpoint of the dataset
DatasetPropMountpoint = golibzfs.DatasetPropMountpoint
// DatasetPropEncryption is the encryption of the dataste
DatasetPropEncryption = golibzfs.DatasetPropEncryption
// DatasetPropEncryption is the encryption of the dataste
DatasetPropKeyLocation = golibzfs.DatasetPropKeyLocation
// DatasetPropEncryption is the encryption of the dataste
DatasetPropKeyFormat = golibzfs.DatasetPropKeyFormat
// DatasetPropOrigin is the origin of the dataset
DatasetPropOrigin = golibzfs.DatasetPropOrigin
// DatasetPropMounted is the mounted property for the dataset
Expand Down
25 changes: 23 additions & 2 deletions internal/zfs/zfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package zfs

import (
"context"
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"

Expand Down Expand Up @@ -315,16 +318,34 @@ func (t *nestedTransaction) Done(err *error) {
t.parent.reverts = append(t.parent.reverts, t.reverts...)
}

func genRandBytes(keyLength int) []byte {
keytext := make([]byte, keyLength)
if _, err := io.ReadFull(rand.Reader, keytext); err != nil {
panic(err.Error())
}
return keytext
}

// Create creates a dataset for that path.
func (t *Transaction) Create(path, mountpoint, canmount string) error {
func (t *Transaction) Create(path, mountpoint, canmount string, encryption bool) error {
t.checkValid()

log.Debugf(t.ctx, i18n.G("ZFS: trying to Create %q with mountpoint %q"), path, mountpoint)
log.Debugf(t.ctx, i18n.G("ZFS: trying to Create %q with mountpoint %q; encryption=%q"), path, mountpoint, fmt.Sprint(encryption))

props := make(map[libzfs.Prop]libzfs.Property)
if mountpoint != "" {
props[libzfs.DatasetPropMountpoint] = libzfs.Property{Value: mountpoint}
}
if encryption {
var keyfilePath string = filepath.Join(filepath.Dir(mountpoint), filepath.Base(mountpoint)+".keyfile")
if err := ioutil.WriteFile(keyfilePath, genRandBytes(32), 0400); err != nil {
panic(err.Error())
}

props[libzfs.DatasetPropEncryption] = libzfs.Property{Value: "aes-256-gcm"}
props[libzfs.DatasetPropKeyLocation] = libzfs.Property{Value: "file://" + keyfilePath}
props[libzfs.DatasetPropKeyFormat] = libzfs.Property{Value: "raw"}
}
props[libzfs.DatasetPropCanmount] = libzfs.Property{Value: canmount}

dZFS, err := t.Zfs.libzfs.DatasetCreate(path, libzfs.DatasetTypeFilesystem, props)
Expand Down
8 changes: 8 additions & 0 deletions zsys.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zsys.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ message VersionResponse {
message CreateUserDataRequest {
string user = 1;
string homepath = 2;
bool encryptHome = 3;
}

message ChangeHomeOnUserDataRequest {
Expand Down

0 comments on commit 6c2e48a

Please sign in to comment.