Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zsysctl: add flag to support creating an encrypted dataset #173

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
14 changes: 8 additions & 6 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,12 +634,13 @@ 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 dataset with encryption": {def: "m_with_userdata.yaml", encryptHome: true},

// Second pool cases
"User dataset on other pool": {def: "m_with_userdata_on_other_pool.yaml"},
"User dataset with no user on other pool": {def: "m_with_userdata_only_on_other_pool.yaml"},
"Prefer system pool for userdata": {def: "m_without_userdata_prefer_system_pool.yaml"},
"Prefer system pool (try other pool) for userdata": {def: "m_without_userdata_prefer_system_pool.yaml", cmdline: generateCmdLine("rpool2/ROOT/ubuntu_1234")},
"Prefer system pool (try other pool) for userdata": {def: "m_without_userdata_prefer_system_pool.yaml", encryptHome: false, cmdline: generateCmdLine("rpool2/ROOT/ubuntu_1234")},
"No attached userdata on second pool": {def: "m_no_attached_userdata_second_pool.yaml"},

// User or home edge cases
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
5 changes: 3 additions & 2 deletions internal/zfs/zfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func TestCreate(t *testing.T) {
path string
mountpoint string
canmount string
encryption bool

wantErr bool
}{
Expand Down Expand Up @@ -194,7 +195,7 @@ func TestCreate(t *testing.T) {
trans, _ := z.NewTransaction(context.Background())
defer trans.Done()

err = trans.Create(tc.path, tc.mountpoint, tc.canmount)
err = trans.Create(tc.path, tc.mountpoint, tc.canmount, tc.encryption)

if err != nil && !tc.wantErr {
t.Fatalf("expected no error but got: %v", err)
Expand Down Expand Up @@ -976,7 +977,7 @@ func TestTransactionsWithZFS(t *testing.T) {
// create a dataset without its parent will make it fail
datasetName = "rpool/ROOT/ubuntu_4242/opt"
}
err := trans.Create(datasetName, "/home/foo", "on")
err := trans.Create(datasetName, "/home/foo", "on", false)
if !tc.shouldErr && err != nil {
t.Fatalf("create %q shouldn't have failed but it did: %v", datasetName, err)
} else if tc.shouldErr && err == nil {
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