diff --git a/ee/tpmrunner/tpmrunner.go b/ee/tpmrunner/tpmrunner.go index 4bee80228..9b08e3c5c 100644 --- a/ee/tpmrunner/tpmrunner.go +++ b/ee/tpmrunner/tpmrunner.go @@ -10,6 +10,8 @@ import ( "fmt" "io" "log/slog" + "os" + "runtime" "sync" "sync/atomic" "time" @@ -67,7 +69,23 @@ func New(ctx context.Context, slogger *slog.Logger, store types.GetterSetterDele } // assume we have a tpm until we know otherwise - tpmRunner.machineHasTpm.Store(true) + hasTPM := true + + // on linux the TPM is at /dev/tpm0 + // if it doesn't exist, we don't have a TPM + if runtime.GOOS == "linux" { + _, err := os.Stat("/dev/tpm0") + hasTPM = err == nil + + if !hasTPM { + slogger.Log(ctx, slog.LevelInfo, + "no tpm found", + "err", err, + ) + } + } + + tpmRunner.machineHasTpm.Store(hasTPM) for _, opt := range opts { opt(tpmRunner) @@ -228,11 +246,11 @@ func (tr *tpmRunner) loadOrCreateKeys(ctx context.Context) error { priData, pubData, err = tr.signerCreator.CreateKey() if err != nil { - if isTPMNotFoundErr(err) { + if isTerminalTPMError(err) { tr.machineHasTpm.Store(false) tr.slogger.Log(ctx, slog.LevelInfo, - "tpm not found", + "terminal tpm error, not retrying", "err", err, ) diff --git a/ee/tpmrunner/tpmrunner_linux.go b/ee/tpmrunner/tpmrunner_linux.go index 8349a0dd2..54902dc97 100644 --- a/ee/tpmrunner/tpmrunner_linux.go +++ b/ee/tpmrunner/tpmrunner_linux.go @@ -3,8 +3,26 @@ package tpmrunner -// isTPMNotFoundErr always return false on linux because we don't yet how to -// detect if a TPM is not found on linux. -func isTPMNotFoundErr(err error) bool { +import ( + "errors" + "os" +) + +var terminalErrors = []error{ + // on linux, if the tpm device is not present, we get this error + // stat /dev/tpm0: no such file or directory + // we should check this when we create a new tpmrunner so this + // is maybe belt and suspenders + os.ErrNotExist, +} + +// isTerminalTPMError returns true if we should stop trying to use the TPM. +func isTerminalTPMError(err error) bool { + for _, e := range terminalErrors { + if errors.Is(err, e) { + return true + } + } + return false } diff --git a/ee/tpmrunner/tpmrunner_test.go b/ee/tpmrunner/tpmrunner_test.go index f4c65284f..652415330 100644 --- a/ee/tpmrunner/tpmrunner_test.go +++ b/ee/tpmrunner/tpmrunner_test.go @@ -37,6 +37,11 @@ func Test_tpmRunner(t *testing.T) { tpmRunner, err := New(context.TODO(), multislogger.NewNopLogger(), inmemory.NewStore(), withTpmSignerCreator(tpmSignerCreatorMock)) require.NoError(t, err) + // force the runner to think the machine has a TPM + // not using usual detection methods since were + // mocking it + tpmRunner.machineHasTpm.Store(true) + tpmSignerCreatorMock.On("CreateKey").Return(nil, nil, errors.New("not available yet")).Once() require.Nil(t, tpmRunner.Public()) @@ -66,6 +71,11 @@ func Test_tpmRunner(t *testing.T) { tpmRunner, err := New(context.TODO(), multislogger.NewNopLogger(), store, withTpmSignerCreator(tpmSignerCreatorMock)) require.NoError(t, err) + // force the runner to think the machine has a TPM + // not using usual detection methods since were + // mocking it + tpmRunner.machineHasTpm.Store(true) + tpmSignerCreatorMock.On("New", fakePrivData, fakePubData).Return(privKey, nil).Once() // the call to public should load the key from the store and signer creator should not be called any more after @@ -88,6 +98,11 @@ func Test_tpmRunner(t *testing.T) { tpmRunner, err := New(context.TODO(), multislogger.NewNopLogger(), inmemory.NewStore(), withTpmSignerCreator(tpmSignerCreatorMock)) require.NoError(t, err) + // force the runner to think the machine has a TPM + // not using usual detection methods since were + // mocking it + tpmRunner.machineHasTpm.Store(true) + tpmSignerCreatorMock.On("CreateKey").Return(nil, nil, errors.New("not available yet")).Once() require.Nil(t, tpmRunner.Public()) diff --git a/ee/tpmrunner/tpmrunner_windows.go b/ee/tpmrunner/tpmrunner_windows.go index f89489bc4..426083efc 100644 --- a/ee/tpmrunner/tpmrunner_windows.go +++ b/ee/tpmrunner/tpmrunner_windows.go @@ -6,9 +6,27 @@ package tpmrunner import ( "errors" + "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil/tbs" ) -func isTPMNotFoundErr(err error) bool { - return errors.Is(err, tbs.ErrTPMNotFound) +var terminalErrors = []error{ + tbs.ErrTPMNotFound, + + // this covers the error "integrity check failed" we dont + // believe a machine will recover from this + tpm2.Error{ + Code: tpm2.RCIntegrity, + }, +} + +// isTerminalTPMError returns true if we should stop trying to use the TPM. +func isTerminalTPMError(err error) bool { + for _, e := range terminalErrors { + if errors.Is(err, e) { + return true + } + } + + return false } diff --git a/ee/tpmrunner/tpmrunner_windows_test.go b/ee/tpmrunner/tpmrunner_windows_test.go index 6caa9829e..30358928c 100644 --- a/ee/tpmrunner/tpmrunner_windows_test.go +++ b/ee/tpmrunner/tpmrunner_windows_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil/tbs" "github.com/kolide/launcher/ee/agent/storage/inmemory" "github.com/kolide/launcher/ee/tpmrunner/mocks" @@ -41,7 +42,7 @@ func Test_tpmRunner_windows(t *testing.T) { require.Nil(t, tpmRunner.Public()) }) - t.Run("handles no tpm in Public() call", func(t *testing.T) { + t.Run("handles terminal errors Public() call", func(t *testing.T) { t.Parallel() tpmSignerCreatorMock := mocks.NewTpmSignerCreator(t) @@ -63,5 +64,38 @@ func Test_tpmRunner_windows(t *testing.T) { require.NoError(t, tpmRunner.Execute()) require.Nil(t, tpmRunner.Public()) }) +} + +func Test_isTerminalError(t *testing.T) { + t.Parallel() + tests := []struct { + name string + err error + expected bool + }{ + { + name: "tpm not found err", + err: tbs.ErrTPMNotFound, + expected: true, + }, + { + name: "integrity check failed", + err: tpm2.Error{Code: tpm2.RCIntegrity}, + expected: true, + }, + { + name: "is not terminal error", + err: errors.New("not terminal"), + expected: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.expected, isTerminalTPMError(tt.err)) + }) + } }