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

using tpm to store node identity seed #1790

Merged
merged 12 commits into from
Sep 7, 2022
50 changes: 31 additions & 19 deletions cmds/identityd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"math/rand"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"

Expand All @@ -33,8 +32,7 @@ const (
)

const (
module = "identityd"
seedName = "seed.txt"
module = "identityd"
)

// Safe makes sure function call not interrupted
Expand Down Expand Up @@ -103,7 +101,7 @@ func main() {

// 2. Register the node to BCDB
// at this point we are running latest version
idMgr, err := getIdentityMgr(root)
idMgr, err := getIdentityMgr(root, debug)
if err != nil {
log.Fatal().Err(err).Msg("failed to create identity manager")
}
Expand All @@ -127,7 +125,12 @@ func main() {
log.Fatal().Err(err).Msg("failed to initialize upgrader")
}

installBinaries(&boot, upgrader)
err = installBinaries(&boot, upgrader)
if err == upgrade.ErrRestartNeeded {
return
} else if err != nil {
log.Error().Err(err).Msg("failed to install binaries")
}

go func() {
if err := server.Run(ctx); err != nil && err != context.Canceled {
Expand Down Expand Up @@ -180,10 +183,8 @@ func debugReinstall(boot *upgrade.Boot, up *upgrade.Upgrader) {
}()
}

func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) {

func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) error {
bins, _ := boot.CurrentBins()

env, _ := environment.Get()

repoWatcher := upgrade.FListRepo{
Expand All @@ -193,7 +194,11 @@ func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) {

current, toAdd, toDel, err := repoWatcher.Diff()
if err != nil {
log.Error().Err(err).Msg("failed to list latest binaries to install")
return errors.Wrap(err, "failed to list latest binaries to install")
}

if len(toAdd) == 0 && len(toDel) == 0 {
return nil
}

for _, pkg := range toDel {
Expand All @@ -208,7 +213,11 @@ func installBinaries(boot *upgrade.Boot, upgrader *upgrade.Upgrader) {
}
}

boot.SetBins(current)
if err := boot.SetBins(current); err != nil {
return errors.Wrap(err, "failed to commit pkg status")
}

return upgrade.ErrRestartNeeded
}

func upgradeLoop(
Expand Down Expand Up @@ -265,7 +274,13 @@ func upgradeLoop(
continue
}

installBinaries(boot, upgrader)
err = installBinaries(boot, upgrader)
if err == upgrade.ErrRestartNeeded {
log.Info().Msg("restarting upgraded")
return
} else if err != nil {
log.Error().Err(err).Msg("failed to update runtime binaries")
}

// next check for update
exp := backoff.NewExponentialBackOff()
Expand All @@ -280,12 +295,11 @@ func upgradeLoop(

if err == upgrade.ErrRestartNeeded {
return backoff.Permanent(err)
}

if err != nil {
} else if err != nil {
log.Error().Err(err).Msg("update failure. retrying")
}
return err

return nil
}, exp)

if err == upgrade.ErrRestartNeeded {
Expand All @@ -306,10 +320,8 @@ func upgradeLoop(
}
}

func getIdentityMgr(root string) (pkg.IdentityManager, error) {
seedPath := filepath.Join(root, seedName)

manager, err := identity.NewManager(seedPath)
func getIdentityMgr(root string, debug bool) (pkg.IdentityManager, error) {
manager, err := identity.NewManager(root, debug)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/dave/jennifer v1.3.0 h1:p3tl41zjjCZTNBytMwrUuiAnherNUZktlhPTKoF/sEk=
github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
3 changes: 3 additions & 0 deletions pkg/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func (s StrIdentifier) Identity() string {

// IdentityManager interface.
type IdentityManager interface {
// Store returns the key store kind
StoreKind() string

// NodeID returns the node id (public key)
NodeID() StrIdentifier

Expand Down
71 changes: 71 additions & 0 deletions pkg/identity/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package identity

import (
"fmt"
"path/filepath"

"github.com/rs/zerolog/log"
"github.com/threefoldtech/zos/pkg/identity/store"
)

const (
seedName = "seed.txt"
)

// NewStore tries to build the best key store available
// for this ndoe.
// On a machine with no tpm support, that would be a file
// store.
// If TPM is supported, TPM will be used.
// There is a special case if tpm is supported, but a file seed
// exits, this file key will be migrated to the TPM store then
// deleted (only if delete is set to true)
func NewStore(root string, delete bool) (store.Store, error) {
file := store.NewFileStore(filepath.Join(root, seedName))
if !store.IsTPMEnabled() {
return file, nil
}

// tpm is supported, but do we have a key
tpm := store.NewTPM()
exists, err := file.Exists()
if err != nil {
return nil, fmt.Errorf("failed to check for seed file: %s", err)
}

if !exists {
return tpm, nil
}

if ok, err := tpm.Exists(); err == nil && ok {
// so there is a key on disk, but tpm already has a stored key
// then we still just return no need for migration to avoid
// overriding the key in tpm
return tpm, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about old data/signatures in this case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually to avoid overriding the key inside tpm if someone decided to put another key on disk. The idea is that key migration will happen one time (and one time only) and then delete the key file. If suddenly a key appeared then it means something is fishy and the new key file is just ignored.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, got it. I think we also assume there's no other key at our chosen address, right? (not managed by us).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually hard to grantee. Maybe we should add a validation (and clearing step) if the key is invalid.

}

// if we failed to get the key from store
// may be better generate a new one?
// todo: need discussion

key, err := file.Get()
if err != nil {
return nil, fmt.Errorf("failed to load key from file: %w", err)
}

// migration of key
if err := tpm.Set(key); err != nil {
// we failed to do migration but we have a valid key.
// we shouldn't then fail instead use the file store
log.Error().Err(err).Msg("failed to migrate key to tpm store")
return file, nil
}

if delete {
if err := file.Annihilate(); err != nil {
log.Error().Err(err).Msg("failed to clear up key file")
}
}

return tpm, nil
}
53 changes: 34 additions & 19 deletions pkg/identity/identityd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ package identity

import (
"fmt"
"os"

"github.com/rs/zerolog/log"
"github.com/threefoldtech/substrate-client"
"github.com/threefoldtech/zos/pkg/crypto"
"github.com/threefoldtech/zos/pkg/identity/store"

"github.com/pkg/errors"
"github.com/threefoldtech/zos/pkg"
"github.com/threefoldtech/zos/pkg/environment"
)

type identityManager struct {
key KeyPair
sub substrate.Manager
env environment.Environment
kind string
key KeyPair
sub substrate.Manager
env environment.Environment

farm string
node uint32
Expand All @@ -25,44 +26,58 @@ type identityManager struct {
// NewManager creates an identity daemon from seed
// The daemon will auto generate a new seed if the path does
// not exist
func NewManager(path string) (pkg.IdentityManager, error) {
env, err := environment.Get()
// debug flag is used to change the behavior slightly when zos is running in debug
// mode. Right now only the key store uses this flag. In case of debug migrated keys
// to tpm are not deleted from disks. This allow switching back and forth between tpm
// and non-tpm key stores.
func NewManager(root string, debug bool) (pkg.IdentityManager, error) {
st, err := NewStore(root, !debug)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to create key store")
}
log.Info().Str("kind", st.Kind()).Msg("key store loaded")
key, err := st.Get()
var pair KeyPair
if seed, err := LoadSeed(path); os.IsNotExist(err) {
if errors.Is(err, store.ErrKeyDoesNotExist) {
pair, err = GenerateKeyPair()
if err != nil {
return nil, errors.Wrap(err, "failed to generate key pair")
}

if err := pair.Save(path); err != nil {
if err := st.Set(pair.PrivateKey); err != nil {
return nil, errors.Wrap(err, "failed to persist key seed")
}
} else if err != nil {
if err := os.Remove(path); err != nil {
log.Error().Err(err).Msg("failed to delete corrupt seed file")
log.Error().Err(err).Msg("failed to load key. to recover the key data will be deleted and regenerated")
if err := st.Annihilate(); err != nil {
log.Error().Err(err).Msg("failed to clean up key store")
}
return nil, errors.Wrap(err, "failed to load seed")
} else {
pair, err = FromSeed(seed)
if err != nil {
return nil, errors.Wrap(err, "invalid seed file")
}
pair = KeyPairFromKey(key)
}

sub, err := environment.GetSubstrate()
if err != nil {
return nil, err
}
env, err := environment.Get()
if err != nil {
return nil, err
}

return &identityManager{
key: pair,
sub: sub,
env: env,
kind: st.Kind(),
key: pair,
sub: sub,
env: env,
}, nil
}

// StoreKind returns store kind
func (d *identityManager) StoreKind() string {
return d.kind
}

// NodeID returns the node identity
func (d *identityManager) NodeID() pkg.StrIdentifier {
return pkg.StrIdentifier(d.key.Identity())
Expand Down
Loading