Repository: threefoldtech/zos (go)

Issue:
zdb provision: password for zdb namespace should be encrypted in the reseravtion
At the moment the password for a 0-DB namespace reservation is in clear text into the reservation.
This needs to change. The password can be encrypted with the public key of the node.

Pull request:
Encrypt zdb password
Fixes #396 

Patch:
diff --git a/pkg/provision/zdb.go b/pkg/provision/zdb.go
index 864bc3ff..79e82955 100644
--- a/pkg/provision/zdb.go
+++ b/pkg/provision/zdb.go
@@ -2,6 +2,7 @@ package provision
 
 import (
 	"context"
+	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"net"
@@ -11,6 +12,7 @@ import (
 	"time"
 
 	"github.com/cenkalti/backoff/v3"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/network/ifaceutil"
 	"github.com/threefoldtech/zos/pkg/zdb"
 
@@ -35,6 +37,8 @@ type ZDB struct {
 	Password string         `json:"password"`
 	DiskType pkg.DeviceType `json:"disk_type"`
 	Public   bool           `json:"public"`
+
+	PlainPassword string `json:"-"`
 }
 
 // ZDBResult is the information return to the BCDB
@@ -49,6 +53,21 @@ func zdbProvision(ctx context.Context, reservation *Reservation) (interface{}, e
 	return zdbProvisionImpl(ctx, reservation)
 }
 
+func decryptPassword(client zbus.Client, password string) (string, error) {
+	if len(password) == 0 {
+		return "", nil
+	}
+	identitry := stubs.NewIdentityManagerStub(client)
+
+	bytes, err := hex.DecodeString(password)
+	if err != nil {
+		return "", err
+	}
+
+	out, err := identitry.Decrypt(bytes)
+	return string(out), err
+}
+
 func zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, error) {
 	var (
 		client  = GetZBus(ctx)
@@ -62,6 +81,12 @@ func zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult,
 		return ZDBResult{}, errors.Wrap(err, "failed to decode reservation schema")
 	}
 
+	var err error
+	config.PlainPassword, err = decryptPassword(client, config.Password)
+	if err != nil {
+		return ZDBResult{}, errors.Wrap(err, "failed to decrypt namespace password")
+	}
+
 	// if we reached here, we need to create the 0-db namespace
 	log.Debug().Msg("allocating storage for namespace")
 	allocation, err := storage.Allocate(nsID, config.DiskType, config.Size*gigabyte, config.Mode)
@@ -271,8 +296,8 @@ func createZDBNamespace(containerID pkg.ContainerID, nsID string, config ZDB) er
 		}
 	}
 
-	if config.Password != "" {
-		if err := zdbCl.NamespaceSetPassword(nsID, config.Password); err != nil {
+	if config.PlainPassword != "" {
+		if err := zdbCl.NamespaceSetPassword(nsID, config.PlainPassword); err != nil {
 			return errors.Wrapf(err, "failed to set password namespace %s in 0-db: %s", nsID, containerID)
 		}
 	}
diff --git a/tools/tfuser/cmds_provision.go b/tools/tfuser/cmds_provision.go
index e8765cb1..9b7b4bad 100644
--- a/tools/tfuser/cmds_provision.go
+++ b/tools/tfuser/cmds_provision.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
@@ -9,6 +10,8 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
+	"github.com/threefoldtech/zos/pkg"
+	"github.com/threefoldtech/zos/pkg/crypto"
 	"github.com/threefoldtech/zos/pkg/identity"
 	"github.com/threefoldtech/zos/pkg/provision"
 
@@ -20,6 +23,43 @@ var (
 	defaultDuration = day * 30
 )
 
+func encryptPassword(password, nodeID string) (string, error) {
+	if len(password) == 0 {
+		return "", nil
+	}
+
+	pubkey, err := crypto.KeyFromID(pkg.StrIdentifier(nodeID))
+	if err != nil {
+		return "", err
+	}
+
+	encrypted, err := crypto.Encrypt([]byte(password), pubkey)
+	return hex.EncodeToString(encrypted), err
+}
+
+func provisionCustomZDB(r *provision.Reservation) error {
+	var config provision.ZDB
+	if err := json.Unmarshal(r.Data, &config); err != nil {
+		return errors.Wrap(err, "failed to load zdb reservation schema")
+	}
+
+	encrypted, err := encryptPassword(config.Password, r.NodeID)
+	if err != nil {
+		return err
+	}
+
+	config.Password = encrypted
+	r.Data, err = json.Marshal(config)
+
+	return err
+}
+
+var (
+	provCustomModifiers = map[provision.ReservationType]func(r *provision.Reservation) error{
+		provision.ZDBReservation: provisionCustomZDB,
+	}
+)
+
 func cmdsProvision(c *cli.Context) error {
 	var (
 		schema   []byte
@@ -66,6 +106,13 @@ func cmdsProvision(c *cli.Context) error {
 	for _, nodeID := range nodeIDs {
 		r.NodeID = nodeID
 
+		custom, ok := provCustomModifiers[r.Type]
+		if ok {
+			if err := custom(r); err != nil {
+				return err
+			}
+		}
+
 		if err := r.Sign(keypair.PrivateKey); err != nil {
 			return errors.Wrap(err, "failed to sign the reservation")
 		}
diff --git a/tools/tfuser/main.go b/tools/tfuser/main.go
index 7fa2d679..04008ca5 100644
--- a/tools/tfuser/main.go
+++ b/tools/tfuser/main.go
@@ -76,7 +76,7 @@ func main() {
 			Flags: []cli.Flag{
 				cli.StringFlag{
 					Name: "schema,s",
-					Usage: `location of the generated schema. 
+					Usage: `location of the generated schema.
 					For the network sub-commands add-node and add-user this flag is
 					also used to read the network schema before modifying it`,
 				},
@@ -253,6 +253,10 @@ func main() {
 									Name:  "password, p",
 									Usage: "optional password",
 								},
+								cli.StringFlag{
+									Name:  "node, n",
+									Usage: "node ID. Required if password is set to encrypt the password",
+								},
 								cli.BoolFlag{
 									Name:  "public",
 									Usage: "TODO",


Test patch:
diff --git a/pkg/provision/zdb_test.go b/pkg/provision/zdb_test.go
index 4d204863..4870e8b3 100644
--- a/pkg/provision/zdb_test.go
+++ b/pkg/provision/zdb_test.go
@@ -2,6 +2,7 @@ package provision
 
 import (
 	"context"
+	"encoding/hex"
 	"fmt"
 	"net"
 	"strings"
@@ -56,10 +57,11 @@ func TestZDBProvision(t *testing.T) {
 	ctx := context.Background()
 	ctx = WithZBus(ctx, &client)
 
+	passwd := "pa$$w0rd"
 	conf := ZDB{
 		Size:     280682,
 		Mode:     pkg.ZDBModeSeq,
-		Password: "pa$$w0rd",
+		Password: hex.EncodeToString([]byte(passwd)),
 		DiskType: pkg.SSDDevice,
 		Public:   true,
 	}
@@ -98,6 +100,10 @@ func TestZDBProvision(t *testing.T) {
 			},
 		}, nil)
 
+	client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"},
+		"Decrypt", []byte(passwd)).
+		Return("password", nil)
+
 	client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"},
 		"Addrs",
 		"zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil)
@@ -105,7 +111,7 @@ func TestZDBProvision(t *testing.T) {
 	var zdbClient zdbTestClient
 	zdbClient.On("Exist", reservation.ID).Return(false, nil)
 	zdbClient.On("CreateNamespace", reservation.ID).Return(nil)
-	zdbClient.On("NamespaceSetPassword", reservation.ID, conf.Password).Return(nil)
+	zdbClient.On("NamespaceSetPassword", reservation.ID, "password").Return(nil)
 	zdbClient.On("NamespaceSetPublic", reservation.ID, conf.Public).Return(nil)
 	zdbClient.On("NamespaceSetSize", reservation.ID, conf.Size*gigabyte).Return(nil)
 
@@ -128,10 +134,11 @@ func TestZDBProvisionNoMappingContainerDoesNotExists(t *testing.T) {
 	ctx := context.Background()
 	ctx = WithZBus(ctx, &client)
 
+	passwd := "pa$$w0rd"
 	zdb := ZDB{
 		Size:     10,
 		Mode:     pkg.ZDBModeSeq,
-		Password: "pa$$w0rd",
+		Password: hex.EncodeToString([]byte(passwd)),
 		DiskType: pkg.SSDDevice,
 		Public:   true,
 	}
@@ -148,6 +155,10 @@ func TestZDBProvisionNoMappingContainerDoesNotExists(t *testing.T) {
 	// it's followed by allocation request to see if there is already a
 	// zdb instance running that has enough space for the ns
 
+	client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"},
+		"Decrypt", []byte(passwd)).
+		Return("password", nil)
+
 	client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"},
 		"Allocate",
 		reservation.ID, zdb.DiskType, zdb.Size*gigabyte, zdb.Mode,