Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions overlord/fdestate/activate_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
53 changes: 39 additions & 14 deletions overlord/fdestate/autorepair.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does it make sense for future proofing to make it map[RecommendedRemedialAction]any? so that if we need to augment an action with args we could because we will be stuck with that API unless we deprecate the field.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In order to add "input" to the action? We can add extra fields for them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how would we add extra fields if it's a list of strings?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like: recommendation-args?

}

type repairStateForBoot struct {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
}
Expand All @@ -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
}
94 changes: 78 additions & 16 deletions overlord/fdestate/autorepair_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -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`)
}
Expand All @@ -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)
Expand Down Expand Up @@ -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`)

Expand Down Expand Up @@ -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`)
}
Expand Down Expand Up @@ -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`)
}
Expand All @@ -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)
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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`)
}
Expand All @@ -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)
Expand Down Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion overlord/fdestate/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
18 changes: 18 additions & 0 deletions secboot/secboot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can give a doc comment to this? and also to the fields?

// 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
}
4 changes: 2 additions & 2 deletions secboot/secboot_nosb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading