diff --git a/overlord/fdestate/activate_state.go b/overlord/fdestate/activate_state.go index 333e9ed8ee0..05f5221e843 100644 --- a/overlord/fdestate/activate_state.go +++ b/overlord/fdestate/activate_state.go @@ -110,6 +110,8 @@ type FDESystemState struct { // AutoRepairResult is the status of the auto-repair attempt AutoRepairResult AutoRepairResult `json:"auto-repair-result"` + Recommendations []RecommendedRemedialAction `json:"recommendations,omitempty"` + // Preinstall provides information captured during install-time checks. Preinstall FDEPreinstallInfo `json:"preinstall"` } @@ -144,6 +146,7 @@ func SystemState(st *state.State, model *asserts.Model) (*FDESystemState, error) ret.AutoRepairResult = AutoRepairNotInitialized } else { ret.AutoRepairResult = repairResult.Result + ret.Recommendations = repairResult.Recommendations } s, err := getActivateState(st) @@ -187,6 +190,9 @@ func SystemState(st *state.State, model *asserts.Model) (*FDESystemState, error) if s.NumActivatedContainersWithRecoveryKey() != 0 { ret.Status = FDEStatusRecovery } else if secboot.ActivateStateHasDegradedErrors(s) { + // TODO: we should get degraded from the autorepair + // state since there are system wide errors to look + // at. ret.Status = FDEStatusDegraded } else { ret.Status = FDEStatusActive diff --git a/overlord/fdestate/autorepair.go b/overlord/fdestate/autorepair.go index 58a8817b9c1..52a78532416 100644 --- a/overlord/fdestate/autorepair.go +++ b/overlord/fdestate/autorepair.go @@ -61,12 +61,21 @@ const ( AutoRepairSuccess AutoRepairResult = "success" ) +type RecommendedRemedialAction string + +const ( + RecommendedRemedialActionPermitManual RecommendedRemedialAction = "permit-manual" + RecommendedRemedialActionRequireReprovision RecommendedRemedialAction = "require-reprovision" + RecommendedRemedialActionRequirePlatformReset RecommendedRemedialAction = "require-platform-reset" +) + const ( postInstallCheckTimeout = 2 * time.Minute ) type repairState struct { - Result AutoRepairResult `json:"result"` + Result AutoRepairResult `json:"result"` + Recommendations []RecommendedRemedialAction `json:"recommendations,omitempty"` } type repairStateForBoot struct { @@ -248,11 +257,12 @@ func autoRepair(st *state.State, runPostInstallChecks bool) (AutoRepairResult, e } logger.Noticef("WARNING: could not auto repair keyslots due to failed platform initialization:\n%s", strings.Join(messages, "\n")) } - return AutoRepairFailedPlatformInit, nil + return AutoRepairFailedEncryptionSupport, nil } } lockoutAuthFile := device.TpmLockoutAuthUnder(boot.InstallHostFDESaveDir) + // TODO: possibly we do not need to rotate the authorization keys for a repair... if err := secbootProvisionTPM(secboot.TPMPartialReprovision, lockoutAuthFile); err != nil { logger.Noticef("WARNING: could not repair platform: %v", err) return AutoRepairFailedPlatformInit, nil @@ -287,12 +297,6 @@ func autoRepair(st *state.State, runPostInstallChecks bool) (AutoRepairResult, e // auto-repair attempted has already occurred during the current boot, // this will do nothing. func AttemptAutoRepairIfNeeded(st *state.State, lockoutResetErr error, runPostInstallChecks bool) error { - if lockoutResetErr != nil { - // FIXME: we need to either try repair in some cases and save the - // error for the status API - return lockoutResetErr - } - // let's get the result from previous attempt during the // current boot previousResult, err := getRepairAttemptResult(st) @@ -314,19 +318,32 @@ func AttemptAutoRepairIfNeeded(st *state.State, lockoutResetErr error, runPostIn } if unlockedState.UbuntuData.UnlockKey != "recovery" && unlockedState.UbuntuSave.UnlockKey != "recovery" { setRepairAttemptResult(st, &repairState{Result: AutoRepairNotAttempted}) - return nil + return lockoutResetErr } } else if os.IsNotExist(err) { logger.Noticef("WARNING: the system booted with an old initrd without unlocked status reporting") setRepairAttemptResult(st, &repairState{Result: AutoRepairNotAttempted}) - return nil + return lockoutResetErr } else if err != nil { logger.Noticef("WARNING: error while getting activation state: %v", err) setRepairAttemptResult(st, &repairState{Result: AutoRepairNotAttempted}) - return nil + return lockoutResetErr } else { - if !secbootShouldAttemptRepair(s) { - setRepairAttemptResult(st, &repairState{Result: AutoRepairNotAttempted}) + remedialActions := secbootShouldAttemptRepair(s, lockoutResetErr) + if !remedialActions.AttemptRepair { + var recommendations []RecommendedRemedialAction + + if remedialActions.RequireReprovision { + recommendations = append(recommendations, RecommendedRemedialActionRequireReprovision) + } + if remedialActions.PermitManual { + recommendations = append(recommendations, RecommendedRemedialActionPermitManual) + } + + setRepairAttemptResult(st, &repairState{ + Result: AutoRepairNotAttempted, + Recommendations: recommendations, + }) return nil } } @@ -335,7 +352,15 @@ func AttemptAutoRepairIfNeeded(st *state.State, lockoutResetErr error, runPostIn if err != nil { return err } - setRepairAttemptResult(st, &repairState{Result: result}) + + var recommendations []RecommendedRemedialAction + if result != AutoRepairSuccess { + recommendations = append(recommendations, RecommendedRemedialActionRequireReprovision) + } + setRepairAttemptResult(st, &repairState{ + Result: result, + Recommendations: recommendations, + }) return nil } diff --git a/overlord/fdestate/autorepair_test.go b/overlord/fdestate/autorepair_test.go index 8a1298a96b5..6d39dbed9e5 100644 --- a/overlord/fdestate/autorepair_test.go +++ b/overlord/fdestate/autorepair_test.go @@ -128,8 +128,10 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeeded(c *C) { return nil })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return true + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + AttemptRepair: true, + } })() s.mockBootAssetsStateForModeenv(c) @@ -185,8 +187,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNotNeeded(c *C) { return fmt.Errorf("Unexpected call") })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return false + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{} })() s.mockBootAssetsStateForModeenv(c) @@ -204,6 +206,47 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNotNeeded(c *C) { c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("not-attempted")) + c.Check(result.Recommendations, IsNil) +} + +func (s *autoRepairSuite) TestAttemptAutoRepairReprovisionRequired(c *C) { + const onClassic = false + s.startedManager(c, onClassic) + + s.st.Lock() + defer s.st.Unlock() + + c.Assert(device.StampSealedKeys(dirs.GlobalRootDir, device.SealingMethodTPM), IsNil) + + s.createUnlockedState(c, sb.ActivationSucceededWithPlatformKey) + + defer fdestate.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error { + c.Errorf("Unexpected call") + return fmt.Errorf("Unexpected call") + })() + + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + RequireReprovision: true, + } + })() + + s.mockBootAssetsStateForModeenv(c) + + defer fdestate.MockBackendResealKeyForBootChains(func(manager backend.FDEStateManager, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams) error { + c.Errorf("Unexpected call") + return fmt.Errorf("Unexpected call") + })() + + const runPostInstallChecks = true + err := fdestate.AttemptAutoRepairIfNeeded(s.st, nil, runPostInstallChecks) + c.Assert(err, IsNil) + + result, err := fdestate.GetRepairAttemptResult(s.st) + c.Assert(err, IsNil) + + c.Check(result.Result, Equals, fdestate.AutoRepairResult("not-attempted")) + c.Check(result.Recommendations, DeepEquals, []fdestate.RecommendedRemedialAction{"require-reprovision"}) } func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReprovision(c *C) { @@ -223,8 +266,10 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReprovision(c *C) { return fmt.Errorf("some error") })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return true + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + AttemptRepair: true, + } })() s.mockBootAssetsStateForModeenv(c) @@ -246,6 +291,7 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReprovision(c *C) { c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init")) + c.Check(result.Recommendations, DeepEquals, []fdestate.RecommendedRemedialAction{"require-reprovision"}) } func (s *autoRepairSuite) TestAttemptAutoRepairErrorNoActivateState(c *C) { @@ -273,6 +319,7 @@ func (s *autoRepairSuite) TestAttemptAutoRepairErrorNoActivateState(c *C) { c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("not-attempted")) + c.Check(result.Recommendations, IsNil) c.Check(logbuf.String(), testutil.Contains, `WARNING: the system booted with an old initrd without using activation API`) } @@ -291,8 +338,10 @@ func (s *autoRepairSuite) TestAttemptAutoRepairErrorNoActivateStateRecovery(c *C return nil })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return true + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + AttemptRepair: true, + } })() s.mockBootAssetsStateForModeenv(c) @@ -328,6 +377,7 @@ func (s *autoRepairSuite) TestAttemptAutoRepairErrorNoActivateStateRecovery(c *C c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("success")) + c.Check(result.Recommendations, IsNil) c.Check(logbuf.String(), testutil.Contains, `WARNING: the system booted with an old initrd without using activation API`) @@ -360,6 +410,7 @@ func (s *autoRepairSuite) TestAttemptAutoRepairErrorActivateState(c *C) { c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("not-attempted")) + c.Check(result.Recommendations, IsNil) c.Check(logbuf.String(), testutil.Contains, `WARNING: error while getting activation state: cannot read state`) } @@ -389,6 +440,7 @@ func (s *autoRepairSuite) TestAttemptAutoRepairErrorNoFileActivateState(c *C) { c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("not-attempted")) + c.Check(result.Recommendations, IsNil) c.Check(logbuf.String(), testutil.Contains, `WARNING: the system booted with an old initrd without unlocked status reporting`) } @@ -411,8 +463,10 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReseal(c *C) { return nil })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return true + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + AttemptRepair: true, + } })() s.mockBootAssetsStateForModeenv(c) @@ -437,6 +491,7 @@ func (s *autoRepairSuite) TestAttemptAutoRepairNeededBadReseal(c *C) { c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-keyslots")) + c.Check(result.Recommendations, DeepEquals, []fdestate.RecommendedRemedialAction{"require-reprovision"}) } func (s *autoRepairSuite) TestIgnoreOldAutoRepairResult(c *C) { @@ -461,6 +516,7 @@ func (s *autoRepairSuite) TestIgnoreOldAutoRepairResult(c *C) { result, err = fdestate.GetRepairAttemptResult(s.st) c.Assert(err, IsNil) c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init")) + c.Check(result.Recommendations, IsNil) } func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecks(c *C) { @@ -479,8 +535,10 @@ func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecks(c *C) { return fmt.Errorf("unexpected call") })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return true + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + AttemptRepair: true, + } })() s.mockBootAssetsStateForModeenv(c) @@ -505,7 +563,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecks(c *C) { result, err := fdestate.GetRepairAttemptResult(s.st) c.Assert(err, IsNil) - c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init")) + c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-encryption-support")) + c.Check(result.Recommendations, DeepEquals, []fdestate.RecommendedRemedialAction{"require-reprovision"}) c.Check(logbuf.String(), testutil.Contains, `WARNING: could not auto repair keyslots due to failed platform initialization: some error`) } @@ -526,8 +585,10 @@ func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecksWithDetail return fmt.Errorf("unexpected call") })() - defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState) bool { - return true + defer fdestate.MockSecbootShouldAttemptRepair(func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions { + return secboot.RemedialActions{ + AttemptRepair: true, + } })() s.mockBootAssetsStateForModeenv(c) @@ -563,7 +624,8 @@ func (s *autoRepairSuite) TestAttemptAutoRepairFailedPostinstallChecksWithDetail result, err := fdestate.GetRepairAttemptResult(s.st) c.Assert(err, IsNil) - c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-platform-init")) + c.Check(result.Result, Equals, fdestate.AutoRepairResult("failed-encryption-support")) + c.Check(result.Recommendations, DeepEquals, []fdestate.RecommendedRemedialAction{"require-reprovision"}) c.Check(logbuf.String(), testutil.Contains, "WARNING: could not auto repair keyslots due to failed platform initialization:\n- error-1\n- error-2\n") } diff --git a/overlord/fdestate/export_test.go b/overlord/fdestate/export_test.go index a76574c62d6..32ebec70ec7 100644 --- a/overlord/fdestate/export_test.go +++ b/overlord/fdestate/export_test.go @@ -190,7 +190,7 @@ func MockOsutilBootID(f func() (string, error)) (restore func()) { return testutil.Mock(&osutilBootID, f) } -func MockSecbootShouldAttemptRepair(f func(as *secboot.ActivateState) bool) (restore func()) { +func MockSecbootShouldAttemptRepair(f func(as *secboot.ActivateState, lockoutResetErr error) secboot.RemedialActions) (restore func()) { return testutil.Mock(&secbootShouldAttemptRepair, f) } diff --git a/secboot/secboot.go b/secboot/secboot.go index 20887568793..65850ab96fd 100644 --- a/secboot/secboot.go +++ b/secboot/secboot.go @@ -371,3 +371,21 @@ type Disk interface { PartitionWithFsLabel(string) (Partition, error) DiskModel() string } + +// RemedialActions is a set of actions recommended to repair detected +// issues with FDE state. +type RemedialActions struct { + // AttemptRepair tells whether an auto-repair should be attempted. + // If the attempt fails, then it means a reprovision is required. + AttemptRepair bool + // RequireReprovision tells whether issues require a reprovision + // and it was detected that auto-repair would not be enough. + RequireReprovision bool + // PermitManual signals that there are some issues that could + // be fixed by administrator. + PermitManual bool + // RequirePlatformReset tells that the platform is owned by another + // system, we lost ownership of the platform. In that case + // the security device (e.g. TPM) needs to be cleared. + RequirePlatformReset bool +} diff --git a/secboot/secboot_nosb.go b/secboot/secboot_nosb.go index ca782215a2a..3084c9ea08e 100644 --- a/secboot/secboot_nosb.go +++ b/secboot/secboot_nosb.go @@ -207,8 +207,8 @@ func (*ActivateState) NumActivatedContainersWithRecoveryKey() uint { return 0 } -func ShouldAttemptRepair(a *ActivateState) bool { - return false +func ShouldAttemptRepair(a *ActivateState, lockoutResetErr error) RemedialActions { + return RemedialActions{} } type ActivateContext interface { diff --git a/secboot/secboot_sb.go b/secboot/secboot_sb.go index e6faa7491c0..5cf0202a607 100644 --- a/secboot/secboot_sb.go +++ b/secboot/secboot_sb.go @@ -35,6 +35,7 @@ import ( sb "github.com/snapcore/secboot" sb_luks2 "github.com/snapcore/secboot/luks2" sb_plainkey "github.com/snapcore/secboot/plainkey" + sb_tpm2 "github.com/snapcore/secboot/tpm2" "github.com/snapcore/snapd/gadget/device" "github.com/snapcore/snapd/kernel/fde" @@ -109,42 +110,75 @@ func LockSealedKeys() error { type ActivateState = sb.ActivateState -func shouldAttemptRepairOnFailure(a *ActivateState) bool { +func shouldAttemptRepairOnFailure(a *ActivateState) RemedialActions { + someKeySlotsFailWithPlatformError := false + allKeySlotsFailWithIncompatibleRoleParams := true + allKeySlotsFailWithIncorrectUserAuth := true + for _, activation := range a.Activations { for _, errorType := range activation.KeyslotErrors { - switch errorType { - case sb.KeyslotErrorPlatformFailure: - return false - case sb.KeyslotErrorIncorrectUserAuth: - return false - case sb.KeyslotErrorInvalidKeyData: - // FIXME: for now when not using tokens, we get this error. We should get - // a different error to ignore when we use external keydata - // return false - case sb.KeyslotErrorInvalidPrimaryKey: - return false - case sb.KeyslotErrorUnknown: - return false - case sb.KeyslotErrorNone: - // This is not really clear if that should happen. - return false - // FIXME: add this case after updating secboot when we have this error - //case sb.KeyslotErrorIncorrectRoleParams: - // return false - case sb.KeyslotErrorIncompatibleRoleParams: - // FIXME: we should ignore this case only if the given keyslot is not expected - // to work for the boot mode. For now we just ignore it for every keyslot. + if errorType != sb.KeyslotErrorIncorrectUserAuth { + allKeySlotsFailWithIncorrectUserAuth = false + } + if errorType != sb.KeyslotErrorIncompatibleRoleParams { + allKeySlotsFailWithIncompatibleRoleParams = false + } + if errorType == sb.KeyslotErrorPlatformFailure { + someKeySlotsFailWithPlatformError = true } } } - // We only encountered IncompatibleRoleParams errors. That - // means it could be repaired. - return true + + if someKeySlotsFailWithPlatformError { + // TODO: spec is incomplete about this case + return RemedialActions{ + RequireReprovision: true, + PermitManual: true, + } + } + + if allKeySlotsFailWithIncompatibleRoleParams { + return RemedialActions{ + AttemptRepair: true, + } + } + + if allKeySlotsFailWithIncorrectUserAuth { + return RemedialActions{ + RequireReprovision: true, + PermitManual: true, + } + } + + return RemedialActions{ + RequireReprovision: true, + } } // ShouldAttemptRepair reads an activate state and decides whether -// a repair should be attempted. -func ShouldAttemptRepair(a *ActivateState) bool { +// a repair should be attempted, or what other operations need +// to be done manually in order to repair it. +func ShouldAttemptRepair(a *ActivateState, lockoutResetErr error) RemedialActions { + needsGlobalRepair := false + + // TODO: we need to verify the SRK and ask for repair if missing + switch { + case errors.Is(lockoutResetErr, sb_tpm2.ErrTPMLockout): + return RemedialActions{ + RequirePlatformReset: true, + } + case errors.Is(lockoutResetErr, sb_tpm2.ErrLockoutAuthNotInitialized): + // authorization was performed with an empty auth value, we need to repair that. + // let's continue to see if a full reprovision is needed though. + needsGlobalRepair = true + case lockoutResetErr != nil: + // Maybe post install check during reprovision will help diagnostic the issue. + // This includes errors like LockoutAuthPolicyNotSupported. + return RemedialActions{ + RequireReprovision: true, + } + } + // First case: recovery. We do attempt repair if all keyslots // of any disk unlocked with recovery key failed with // IncompatibleRoleParams @@ -161,7 +195,12 @@ func ShouldAttemptRepair(a *ActivateState) bool { // - If the role params are incompatible and we detect that this key should have been actually used. // - If the role params were incorrect. // * Other error point to more complicated issues that will need reprovision instead. - needAutoRepair := false + hasErrorsWeCannotIgnore := false + + someKeySlotsFailWithIncompatibleRoleParams := false + someKeySlotsFailWithInvalidRoleParams := false + someKeySlotsFailWithInvalidKeyData := false + for _, activation := range a.Activations { for _, errorType := range activation.KeyslotErrors { switch errorType { @@ -169,29 +208,52 @@ func ShouldAttemptRepair(a *ActivateState) bool { case sb.KeyslotErrorNone: case sb.KeyslotErrorIncorrectUserAuth: - // Require reprovision, auto-repair is not enough case sb.KeyslotErrorInvalidKeyData: - return false + hasErrorsWeCannotIgnore = true + // TODO: check if keyslot is plainkey, + // if only plainkey have this error, + // then we should not permit manual + // fix. + someKeySlotsFailWithInvalidKeyData = true case sb.KeyslotErrorInvalidPrimaryKey: - return false + hasErrorsWeCannotIgnore = true case sb.KeyslotErrorPlatformFailure: - return false + hasErrorsWeCannotIgnore = true case sb.KeyslotErrorUnknown: - return false - - // Repair + hasErrorsWeCannotIgnore = true case sb.KeyslotErrorIncompatibleRoleParams: - // FIXME: we need to verify the keyslot is expected to work in the current mode. - // For now, it is likely we attempted the "default" keyslots first and we are in run mode. - needAutoRepair = true - // FIXME: add this case after updating secboot when we have this error - //case sb.KeyslotErrorIncorrectRoleParams: - // needAutoRepair = true + hasErrorsWeCannotIgnore = true + someKeySlotsFailWithIncompatibleRoleParams = true + case sb.KeyslotErrorInvalidRoleParams: + hasErrorsWeCannotIgnore = true + someKeySlotsFailWithInvalidRoleParams = true } } } - return needAutoRepair + if !hasErrorsWeCannotIgnore { + return RemedialActions{AttemptRepair: needsGlobalRepair} + } + + if someKeySlotsFailWithIncompatibleRoleParams || someKeySlotsFailWithInvalidRoleParams { + return RemedialActions{ + AttemptRepair: true, + } + } + + if someKeySlotsFailWithInvalidKeyData { + return RemedialActions{ + RequireReprovision: true, + PermitManual: true, + } + } + + // TODO: check that we have keyslot compatible with recover + // mode. If not we should permit manual fix. + + return RemedialActions{ + RequireReprovision: true, + } } // ActivateStateHasDegradedErrors looks for any error that is not @@ -213,6 +275,8 @@ func ActivateStateHasDegradedErrors(a *ActivateState) bool { // key data files. Maybe secboot should // provide a different error code. + case sb.KeyslotErrorInvalidRoleParams: + return true case sb.KeyslotErrorInvalidPrimaryKey: return true case sb.KeyslotErrorPlatformFailure: diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go index 4e4c3798241..fb37b7d5e76 100644 --- a/secboot/secboot_sb_test.go +++ b/secboot/secboot_sb_test.go @@ -5917,23 +5917,39 @@ func (s *secbootSuite) TestShouldAttemptRepairDegraded(c *C) { // Incorrect user auth should not cause repair state.Activations["a"].KeyslotErrors["b"] = sb.KeyslotErrorIncorrectUserAuth - c.Check(secboot.ShouldAttemptRepair(state), Equals, false) + actions := secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{}) for _, failure := range []sb.KeyslotErrorType{ // No repair cases since we need reprovision instead - sb.KeyslotErrorInvalidKeyData, sb.KeyslotErrorInvalidPrimaryKey, sb.KeyslotErrorPlatformFailure, sb.KeyslotErrorUnknown, } { state.Activations["a"].KeyslotErrors["b"] = failure - c.Check(secboot.ShouldAttemptRepair(state), Equals, false) + actions = secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequireReprovision: true, + }) + } + + for _, failure := range []sb.KeyslotErrorType{ + sb.KeyslotErrorInvalidKeyData, + } { + state.Activations["a"].KeyslotErrors["b"] = failure + + actions = secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequireReprovision: true, + PermitManual: true, + }) } state.Activations["a"].KeyslotErrors["b"] = sb.KeyslotErrorIncompatibleRoleParams - c.Check(secboot.ShouldAttemptRepair(state), Equals, true) + actions = secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{AttemptRepair: true}) } func (s *secbootSuite) TestShouldAttemptRepairWithRecovery(c *C) { @@ -5953,21 +5969,103 @@ func (s *secbootSuite) TestShouldAttemptRepairWithRecovery(c *C) { }, } - c.Check(secboot.ShouldAttemptRepair(state), Equals, false) + actions := secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequireReprovision: true, + }) for _, failure := range []sb.KeyslotErrorType{ // No repair cases since we need reprovision instead - //sb.KeyslotErrorInvalidKeyData, + sb.KeyslotErrorInvalidKeyData, sb.KeyslotErrorInvalidPrimaryKey, - sb.KeyslotErrorPlatformFailure, sb.KeyslotErrorUnknown, } { state.Activations["a"].KeyslotErrors["a"] = failure - c.Check(secboot.ShouldAttemptRepair(state), Equals, false) + actions = secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequireReprovision: true, + }) + } + + for _, failure := range []sb.KeyslotErrorType{ + sb.KeyslotErrorPlatformFailure, + } { + state.Activations["a"].KeyslotErrors["a"] = failure + actions = secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequireReprovision: true, + PermitManual: true, + }) } state.Activations["a"].KeyslotErrors["a"] = sb.KeyslotErrorIncompatibleRoleParams - c.Check(secboot.ShouldAttemptRepair(state), Equals, true) + c.Check(secboot.ShouldAttemptRepair(state, nil).AttemptRepair, Equals, true) +} + +func (s *secbootSuite) TestShouldAttemptRepairNoRepair(c *C) { + state := &secboot.ActivateState{} + state.Activations = map[string]*sb.ContainerActivateState{ + "a": { + Status: sb.ActivationSucceededWithPlatformKey, + KeyslotErrors: map[string]sb.KeyslotErrorType{ + "a": sb.KeyslotErrorNone, + }, + }, + } + + actions := secboot.ShouldAttemptRepair(state, nil) + c.Check(actions, DeepEquals, secboot.RemedialActions{}) +} + +func (s *secbootSuite) TestShouldAttemptRepairLockoutErrorLocked(c *C) { + state := &secboot.ActivateState{} + state.Activations = map[string]*sb.ContainerActivateState{ + "a": { + Status: sb.ActivationSucceededWithPlatformKey, + KeyslotErrors: map[string]sb.KeyslotErrorType{ + "a": sb.KeyslotErrorNone, + }, + }, + } + + actions := secboot.ShouldAttemptRepair(state, sb_tpm2.ErrTPMLockout) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequirePlatformReset: true, + }) +} + +func (s *secbootSuite) TestShouldAttemptRepairLockoutErrorAnyError(c *C) { + state := &secboot.ActivateState{} + state.Activations = map[string]*sb.ContainerActivateState{ + "a": { + Status: sb.ActivationSucceededWithPlatformKey, + KeyslotErrors: map[string]sb.KeyslotErrorType{ + "a": sb.KeyslotErrorNone, + }, + }, + } + + actions := secboot.ShouldAttemptRepair(state, fmt.Errorf("some-unknown-error")) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + RequireReprovision: true, + }) +} + +func (s *secbootSuite) TestShouldAttemptRepairLockoutErrorUninitialized(c *C) { + state := &secboot.ActivateState{} + state.Activations = map[string]*sb.ContainerActivateState{ + "a": { + Status: sb.ActivationSucceededWithPlatformKey, + KeyslotErrors: map[string]sb.KeyslotErrorType{ + "a": sb.KeyslotErrorNone, + }, + }, + } + + actions := secboot.ShouldAttemptRepair(state, sb_tpm2.ErrLockoutAuthNotInitialized) + c.Check(actions, DeepEquals, secboot.RemedialActions{ + AttemptRepair: true, + }) } func (s *secbootSuite) TestGetPCRHandleFromToken(c *C) { diff --git a/tests/nested/manual/core-recover-from-recovery/task.yaml b/tests/nested/manual/core-recover-from-recovery/task.yaml index e427d7579cd..8590e5c74b5 100644 --- a/tests/nested/manual/core-recover-from-recovery/task.yaml +++ b/tests/nested/manual/core-recover-from-recovery/task.yaml @@ -10,7 +10,9 @@ details: | # used recovery keys, and that we can reprovision and reseal then # reboot without recovery keys. -systems: [ubuntu-24.04-64] +systems: + - ubuntu-24.04-64 + - ubuntu-26.04-64 environment: NESTED_ENABLE_TPM: "true" @@ -123,8 +125,19 @@ execute: | fi if [ "${CLEAR_TPM}" = true ]; then - # For now we do not have reprovision implemented - exit 0 + wait_for_auto_repair_state pre-repair-state.json not-attempted + api_get_v2_system_info_storage_encrypted | gojq -c '.result.recommendations' | MATCH 'require-reprovision' + + remote.exec "sudo snap debug api /v2/systems?running=true 2>/dev/null" | gojq '.result["storage-encryption"].support' | MATCH "available" + + echo '{"action":"generate-recovery-key"}' | remote.exec "sudo snap debug api -X POST -H 'Content-Type: application/json' /v2/systems" >rkey-reprovision.resp + gojq --raw-output '.result."recovery-key"' < rkey-reprovision.resp > rkey-reprovision.out + + echo '{"action":"reprovision"}' | remote.exec "sudo snap debug api -X POST -H 'Content-Type: application/json' /v2/systems" >reprovision.resp + + reprovision_change="$(gojq --raw-output '.change' "${save_state}" + gojq -r '.result."auto-repair-result"' <"${save_state}" >last-autorepair-result + if MATCH "${expected}" /dev/null" | gojq '.result["storage-encryption"].support' | MATCH "available" # TODO:FDEM: We should have a way to check fix-encryption-support, for example by not allowing VMs by default. # echo '{"action": "fix-encryption-support", "fix-action": "proceed"}' | sudo remote.exec "snap debug api -X POST -H 'Content-Type: application/json' /v2/systems" - # Second step of reprovision is generate a new recovery key + # Third step of reprovision is generate a new recovery key echo '{"action":"generate-recovery-key"}' | remote.exec "sudo snap debug api -X POST -H 'Content-Type: application/json' /v2/systems" >rkey-reprovision.resp gojq --raw-output '.result."recovery-key"' < rkey-reprovision.resp > rkey-reprovision.out @@ -278,6 +300,11 @@ execute: | api_get_v2_system_info_storage_encrypted | gojq '.result.status' | MATCH "active" # Let's do a reprovision with a provision TPM. + wait_for_auto_repair_state autorepair-just-reboot.json not-attempted + # The system is working correctly so reprovision is not recommended + api_get_v2_system_info_storage_encrypted | gojq -c '.result.recommendations' | NOMATCH 'require-reprovision' + + # ... but we are still gonna do it! remote.exec "sudo snap debug api /v2/systems?running=true 2>/dev/null" | gojq '.result["storage-encryption"].support' | MATCH "available" # TODO:FDEM: We should have a way to check fix-encryption-support, for example by not allowing VMs by default. # echo '{"action": "fix-encryption-support", "fix-action": "proceed"}' | sudo remote.exec "snap debug api -X POST -H 'Content-Type: application/json' /v2/systems"