diff --git a/ee/agent/keys.go b/ee/agent/keys.go index 22bddb340..920f83720 100644 --- a/ee/agent/keys.go +++ b/ee/agent/keys.go @@ -10,6 +10,7 @@ import ( "github.com/kolide/launcher/ee/agent/keys" "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/pkg/backoff" + "github.com/kolide/launcher/pkg/traces" ) type keyInt interface { @@ -28,7 +29,10 @@ func LocalDbKeys() keyInt { return localDbKeys } -func SetupKeys(slogger *slog.Logger, store types.GetterSetterDeleter) error { +func SetupKeys(ctx context.Context, slogger *slog.Logger, store types.GetterSetterDeleter) error { + ctx, span := traces.StartSpan(ctx) + defer span.End() + slogger = slogger.With("component", "agentkeys") var err error @@ -40,7 +44,7 @@ func SetupKeys(slogger *slog.Logger, store types.GetterSetterDeleter) error { } err = backoff.WaitFor(func() error { - hwKeys, err := setupHardwareKeys(slogger, store) + hwKeys, err := setupHardwareKeys(ctx, slogger, store) if err != nil { return err } diff --git a/ee/agent/keys_darwin.go b/ee/agent/keys_darwin.go index e36a10f7b..891969f21 100644 --- a/ee/agent/keys_darwin.go +++ b/ee/agent/keys_darwin.go @@ -4,25 +4,24 @@ package agent import ( - "errors" + "context" "fmt" "log/slog" "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/ee/secureenclavesigner" + "github.com/kolide/launcher/pkg/traces" ) -func setupHardwareKeys(slogger *slog.Logger, store types.GetterSetterDeleter) (keyInt, error) { - ses, err := secureenclavesigner.New(slogger, store) +func setupHardwareKeys(ctx context.Context, slogger *slog.Logger, store types.GetterSetterDeleter) (keyInt, error) { + ctx, span := traces.StartSpan(ctx) + defer span.End() + + ses, err := secureenclavesigner.New(ctx, slogger, store) if err != nil { + traces.SetError(span, fmt.Errorf("creating secureenclave signer: %w", err)) return nil, fmt.Errorf("creating secureenclave signer: %w", err) } - // this is kind of weird, but we need to call public to ensure the key is generated - // it's done this way to do satisfying signer interface which doesn't return an error - if ses.Public() == nil { - return nil, errors.New("public key was not be created") - } - return ses, nil } diff --git a/ee/agent/keys_tpm.go b/ee/agent/keys_tpm.go index 330cb5a8f..ded82316b 100644 --- a/ee/agent/keys_tpm.go +++ b/ee/agent/keys_tpm.go @@ -10,10 +10,13 @@ import ( "github.com/kolide/krypto/pkg/tpm" "github.com/kolide/launcher/ee/agent/types" + "github.com/kolide/launcher/pkg/traces" ) -// nolint:unused -func setupHardwareKeys(slogger *slog.Logger, store types.GetterSetterDeleter) (keyInt, error) { +func setupHardwareKeys(ctx context.Context, slogger *slog.Logger, store types.GetterSetterDeleter) (keyInt, error) { + ctx, span := traces.StartSpan(ctx) + defer span.End() + priData, pubData, err := fetchKeyData(store) if err != nil { return nil, err @@ -28,17 +31,24 @@ func setupHardwareKeys(slogger *slog.Logger, store types.GetterSetterDeleter) (k priData, pubData, err = tpm.CreateKey() if err != nil { clearKeyData(slogger, store) + traces.SetError(span, fmt.Errorf("creating key: %w", err)) return nil, fmt.Errorf("creating key: %w", err) } + span.AddEvent("new_key_created") + if err := storeKeyData(store, priData, pubData); err != nil { clearKeyData(slogger, store) + traces.SetError(span, fmt.Errorf("storing key: %w", err)) return nil, fmt.Errorf("storing key: %w", err) } + + span.AddEvent("new_key_stored") } k, err := tpm.New(priData, pubData) if err != nil { + traces.SetError(span, fmt.Errorf("creating tpm signer: from new key: %w", err)) return nil, fmt.Errorf("creating tpm signer: from new key: %w", err) } diff --git a/ee/debug/shipper/shipper_test.go b/ee/debug/shipper/shipper_test.go index 49ea7e7b7..2e15fc57c 100644 --- a/ee/debug/shipper/shipper_test.go +++ b/ee/debug/shipper/shipper_test.go @@ -1,6 +1,7 @@ package shipper import ( + "context" "encoding/json" "fmt" "io" @@ -56,7 +57,7 @@ func TestShip(t *testing.T) { //nolint:paralleltest name: "happy path with signing keys and enroll secret", mockKnapsack: func(t *testing.T) *typesMocks.Knapsack { configStore := inmemory.NewStore() - agent.SetupKeys(multislogger.NewNopLogger(), configStore) + agent.SetupKeys(context.TODO(), multislogger.NewNopLogger(), configStore) k := typesMocks.NewKnapsack(t) k.On("EnrollSecret").Return("enroll_secret_value") diff --git a/ee/secureenclavesigner/secureenclavesigner_darwin.go b/ee/secureenclavesigner/secureenclavesigner_darwin.go index 0034bd721..7dcc1492c 100644 --- a/ee/secureenclavesigner/secureenclavesigner_darwin.go +++ b/ee/secureenclavesigner/secureenclavesigner_darwin.go @@ -8,6 +8,7 @@ import ( "crypto" "crypto/ecdsa" "encoding/json" + "errors" "fmt" "io" "log/slog" @@ -20,6 +21,7 @@ import ( "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/ee/allowedcmd" "github.com/kolide/launcher/ee/consoleuser" + "github.com/kolide/launcher/pkg/traces" ) const ( @@ -37,7 +39,10 @@ type secureEnclaveSigner struct { mux *sync.Mutex } -func New(slogger *slog.Logger, store types.GetterSetterDeleter, opts ...opt) (*secureEnclaveSigner, error) { +func New(ctx context.Context, slogger *slog.Logger, store types.GetterSetterDeleter, opts ...opt) (*secureEnclaveSigner, error) { + ctx, span := traces.StartSpan(ctx) + defer span.End() + ses := &secureEnclaveSigner{ uidPubKeyMap: make(map[string]*ecdsa.PublicKey), store: store, @@ -47,18 +52,21 @@ func New(slogger *slog.Logger, store types.GetterSetterDeleter, opts ...opt) (*s data, err := store.Get([]byte(PublicEccDataKey)) if err != nil { - return nil, fmt.Errorf("getting public ecc data: %w", err) + traces.SetError(span, fmt.Errorf("getting public ecc data from store: %w", err)) + return nil, fmt.Errorf("getting public ecc data from store: %w", err) } if data != nil { if err := json.Unmarshal(data, ses); err != nil { + traces.SetError(span, fmt.Errorf("unmarshaling secure enclave signer: %w", err)) ses.slogger.Log(context.TODO(), slog.LevelError, "unable to unmarshal secure enclave signer, data may be corrupt, wiping", "err", err, ) if err := store.Delete([]byte(PublicEccDataKey)); err != nil { - return nil, fmt.Errorf("deleting public ecc data: %w", err) + traces.SetError(span, fmt.Errorf("deleting corrupt public ecc data: %w", err)) + return nil, fmt.Errorf("deleting corrupt public ecc data: %w", err) } } } @@ -70,69 +78,41 @@ func New(slogger *slog.Logger, store types.GetterSetterDeleter, opts ...opt) (*s if ses.pathToLauncherBinary == "" { p, err := os.Executable() if err != nil { + traces.SetError(span, fmt.Errorf("getting path to launcher binary: %w", err)) return nil, fmt.Errorf("getting path to launcher binary: %w", err) } ses.pathToLauncherBinary = p } - return ses, nil -} - -// Public returns the public key of the current console user -// creating and peristing a new one if needed -func (ses *secureEnclaveSigner) Public() crypto.PublicKey { - ses.mux.Lock() - defer ses.mux.Unlock() - - c, err := firstConsoleUser() - if err != nil { + // get current console user key to make sure it's available + if _, err := ses.currentConsoleUserKey(ctx); err != nil { + traces.SetError(span, fmt.Errorf("getting current console user key: %w", err)) ses.slogger.Log(context.TODO(), slog.LevelError, - "getting first console user", + "getting current console user key", "err", err, ) - return nil - } - - if v, ok := ses.uidPubKeyMap[c.Uid]; ok { - return v + // intentionally not returning error here, because this runs on start up + // and maybe the console user or secure enclave is not available yet } - key, err := ses.createKey(context.TODO(), c) - if err != nil { - ses.slogger.Log(context.TODO(), slog.LevelError, - "creating key", - "err", err, - ) - - return nil - } - - ses.uidPubKeyMap[c.Uid] = key + return ses, nil +} - // pesist the new key - json, err := json.Marshal(ses) +// Public returns the public key of the current console user +// creating and peristing a new one if needed +func (ses *secureEnclaveSigner) Public() crypto.PublicKey { + k, err := ses.currentConsoleUserKey(context.TODO()) if err != nil { ses.slogger.Log(context.TODO(), slog.LevelError, - "marshaling secure enclave signer", + "getting public key", "err", err, ) - - delete(ses.uidPubKeyMap, c.Uid) return nil } - if err := ses.store.Set([]byte(PublicEccDataKey), json); err != nil { - ses.slogger.Log(context.TODO(), slog.LevelError, - "persisting secure enclave signer", - "err", err, - ) - delete(ses.uidPubKeyMap, c.Uid) - return nil - } - - return key + return k } func (ses *secureEnclaveSigner) Type() string { @@ -148,6 +128,44 @@ type keyData struct { PubKey string `json:"pub_key"` } +func (ses *secureEnclaveSigner) currentConsoleUserKey(ctx context.Context) (*ecdsa.PublicKey, error) { + ctx, span := traces.StartSpan(ctx) + defer span.End() + + ses.mux.Lock() + defer ses.mux.Unlock() + + cu, err := firstConsoleUser(ctx) + if err != nil { + traces.SetError(span, fmt.Errorf("getting first console user: %w", err)) + return nil, fmt.Errorf("getting first console user: %w", err) + } + + key, ok := ses.uidPubKeyMap[cu.Uid] + if ok { + span.AddEvent("found_existing_key_for_console_user") + return key, nil + } + + key, err = ses.createKey(ctx, cu) + if err != nil { + traces.SetError(span, fmt.Errorf("creating key: %w", err)) + return nil, fmt.Errorf("creating key: %w", err) + } + + span.AddEvent("created_new_key_for_console_user") + + ses.uidPubKeyMap[cu.Uid] = key + if err := ses.save(); err != nil { + delete(ses.uidPubKeyMap, cu.Uid) + traces.SetError(span, fmt.Errorf("saving secure enclave signer: %w", err)) + return nil, fmt.Errorf("saving secure enclave signer: %w", err) + } + + span.AddEvent("saved_key_for_console_user") + return key, nil +} + func (ses *secureEnclaveSigner) MarshalJSON() ([]byte, error) { var keyDatas []keyData @@ -190,6 +208,9 @@ func (ses *secureEnclaveSigner) UnmarshalJSON(data []byte) error { } func (ses *secureEnclaveSigner) createKey(ctx context.Context, u *user.User) (*ecdsa.PublicKey, error) { + ctx, span := traces.StartSpan(ctx) + defer span.End() + cmd, err := allowedcmd.Launchctl( ctx, "asuser", @@ -204,6 +225,7 @@ func (ses *secureEnclaveSigner) createKey(ctx context.Context, u *user.User) (*e ) if err != nil { + traces.SetError(span, fmt.Errorf("creating command to create key: %w", err)) return nil, fmt.Errorf("creating command to create key: %w", err) } @@ -211,12 +233,14 @@ func (ses *secureEnclaveSigner) createKey(ctx context.Context, u *user.User) (*e cmd.Env = append(cmd.Environ(), fmt.Sprintf("%s=%s", "LAUNCHER_SKIP_UPDATES", "true")) out, err := cmd.CombinedOutput() if err != nil { + traces.SetError(span, fmt.Errorf("executing launcher binary to create key: %w: %s", err, string(out))) return nil, fmt.Errorf("executing launcher binary to create key: %w: %s", err, string(out)) } pubKey, err := echelper.PublicB64DerToEcdsaKey([]byte(lastLine(out))) if err != nil { - return nil, fmt.Errorf("marshalling public key to der: %w", err) + traces.SetError(span, fmt.Errorf("converting public key to ecdsa: %w", err)) + return nil, fmt.Errorf("converting public key to ecdsa: %w", err) } return pubKey, nil @@ -239,15 +263,33 @@ func lastLine(out []byte) string { return lastLine } -func firstConsoleUser() (*user.User, error) { - c, err := consoleuser.CurrentUsers(context.TODO()) +func firstConsoleUser(ctx context.Context) (*user.User, error) { + ctx, span := traces.StartSpan(ctx) + defer span.End() + + c, err := consoleuser.CurrentUsers(ctx) if err != nil { + traces.SetError(span, fmt.Errorf("getting current users: %w", err)) return nil, fmt.Errorf("getting current users: %w", err) } if len(c) == 0 { - return nil, fmt.Errorf("no console users found") + traces.SetError(span, errors.New("no console users found")) + return nil, errors.New("no console users found") } return c[0], nil } + +func (ses *secureEnclaveSigner) save() error { + json, err := json.Marshal(ses) + if err != nil { + return fmt.Errorf("marshaling secure enclave signer: %w", err) + } + + if err := ses.store.Set([]byte(PublicEccDataKey), json); err != nil { + return fmt.Errorf("setting public ecc data: %w", err) + } + + return nil +} diff --git a/ee/secureenclavesigner/secureenclavesigner_test.go b/ee/secureenclavesigner/secureenclavesigner_test.go index b92986ad3..ab834e84d 100644 --- a/ee/secureenclavesigner/secureenclavesigner_test.go +++ b/ee/secureenclavesigner/secureenclavesigner_test.go @@ -40,9 +40,9 @@ func TestSecureEnclaveSigner(t *testing.T) { // put the root dir somewhere else if you want to persist the signed macos app bundle // should build this into make at some point - // rootDir := "/tmp/secure_enclave_test" + rootDir := "/tmp/secure_enclave_test" - rootDir := t.TempDir() + // rootDir := t.TempDir() appRoot := filepath.Join(rootDir, "launcher_test.app") // make required dirs krypto_test.app/Contents/MacOS and add files @@ -84,7 +84,7 @@ func TestSecureEnclaveSigner(t *testing.T) { // create brand new signer without existing key // ask for public first to trigger key generation - ses, err := New(multislogger.NewNopLogger(), store, WithBinaryPath(executablePath)) + ses, err := New(context.TODO(), multislogger.NewNopLogger(), store, WithBinaryPath(executablePath)) require.NoError(t, err, "should be able to create secure enclave signer", ) @@ -113,7 +113,7 @@ func TestSecureEnclaveSigner(t *testing.T) { "asking for the same public key should return the same key", ) - existingDataSes, err := New(multislogger.NewNopLogger(), store, WithBinaryPath(executablePath)) + existingDataSes, err := New(context.TODO(), multislogger.NewNopLogger(), store, WithBinaryPath(executablePath)) require.NoError(t, err, "should be able to create secure enclave signer with existing key", ) diff --git a/pkg/osquery/extension.go b/pkg/osquery/extension.go index 583b03064..5b2d51500 100644 --- a/pkg/osquery/extension.go +++ b/pkg/osquery/extension.go @@ -125,7 +125,7 @@ func NewExtension(ctx context.Context, client service.KolideService, k types.Kna return nil, fmt.Errorf("setting up initial launcher keys: %w", err) } - if err := agent.SetupKeys(slogger, configStore); err != nil { + if err := agent.SetupKeys(ctx, slogger, configStore); err != nil { return nil, fmt.Errorf("setting up agent keys: %w", err) }