Old kernels removed by the box (R-1368, wrapper mode kernel-cleanup); security profiles refreshed after a kernel change (R-1388, mode profile-refresh); Go 1.27.4 (R-1391)
gates / gates (push) Successful in 40s
gates / gates (push) Successful in 40s
This commit is contained in:
1 parent
91a2924ad0
commit
cc63dd5204
11 files changed
+1542
-10
No files matched your search
@@ -0,0 +1,101 @@
|
||||
package osupdate
|
||||
|
||||
// The security-profile refresh (R-1388, R-1301, `11` §5.12). After a kernel went back and forth a box can load an OLD
|
||||
// compiled AppArmor profile for a program (on the N100 this stopped the time service). The root wrapper's mode
|
||||
// profile-refresh loads every profile from a fresh compile, once per kernel the box starts on, and restarts only a
|
||||
// failed service whose own program's profile was stale. This file decides WHEN to ask: at every agent start and after
|
||||
// a config bundle was installed (a new wrapper may know the mode the old one refused) — the wrapper itself answers
|
||||
// "not needed" when this kernel was already done. Nothing is sent to the hub (no hub field exists for it; R-1383).
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProfileRefreshResult is the wrapper's profile_refresh object.
|
||||
type ProfileRefreshResult struct {
|
||||
Outcome string `json:"outcome"` // refreshed | not_needed | dry_run | failed
|
||||
Reason string `json:"reason"`
|
||||
Kernel string `json:"kernel"`
|
||||
Rewritten []string `json:"rewritten"`
|
||||
Restarted []struct {
|
||||
Unit string `json:"unit"`
|
||||
Program string `json:"program"`
|
||||
RC int `json:"rc"`
|
||||
Active bool `json:"active"`
|
||||
} `json:"restarted"`
|
||||
Seconds float64 `json:"seconds"`
|
||||
}
|
||||
|
||||
// ProfileGate takes the heavy-op gate (a backup, a restore-test or an OS step must not run beside the refresh).
|
||||
type ProfileGate func() (release func(), busy string, ok bool)
|
||||
|
||||
// ProfileRefresh asks the wrapper once. It returns the outcome ("" when the wrapper could not be asked or refused).
|
||||
// It waits for the gate: up to `tries` attempts, `wait` apart. why is the log's short reason for this call.
|
||||
func (l *Leg) ProfileRefresh(ctx context.Context, why string, gate ProfileGate, tries int, wait time.Duration) string {
|
||||
lg := l.log().With("mode", "profile-refresh", "why", why)
|
||||
if !l.Appliance {
|
||||
lg.Info("osupdate: profile refresh skipped — not an appliance install (a BYO host belongs to its owner)")
|
||||
return ""
|
||||
}
|
||||
var release func()
|
||||
for i := 1; ; i++ {
|
||||
if gate == nil {
|
||||
break
|
||||
}
|
||||
r, busy, ok := gate()
|
||||
if ok {
|
||||
release = r
|
||||
break
|
||||
}
|
||||
if i >= tries || ctx.Err() != nil {
|
||||
lg.Warn("osupdate: profile refresh NOT run — the box stayed busy; the next agent start asks again", "busy", busy, "tries", i)
|
||||
return ""
|
||||
}
|
||||
lg.Info("osupdate: profile refresh waits — a heavy operation runs", "busy", busy, "try", i)
|
||||
l.sleep(ctx, wait)
|
||||
}
|
||||
if release != nil {
|
||||
defer release()
|
||||
}
|
||||
unlock := l.lockPass(true) // never beside an OS-update pass
|
||||
defer unlock()
|
||||
start := l.now()
|
||||
wr, err := l.call(ctx, "prefresh"+l.now().UTC().Format("150405"), map[string]any{"release_id": "profile-refresh",
|
||||
"layer": LayerHost, "mode": "profile-refresh"})
|
||||
took := l.now().Sub(start).Round(100 * time.Millisecond).String()
|
||||
switch {
|
||||
case err != nil:
|
||||
lg.Warn("osupdate: profile refresh — the wrapper gave no answer; the next agent start asks again", "err", err, "took", took)
|
||||
return ""
|
||||
case wr.refused():
|
||||
lg.Info("osupdate: profile refresh refused (an older wrapper that does not know the mode, or a BYO host)", "refused", string(wr.Refused))
|
||||
return ""
|
||||
case wr.failed():
|
||||
lg.Error("osupdate: profile refresh FAILED — the profiles are as before; the next agent start asks again", "failed", string(wr.Failed), "took", took)
|
||||
return "failed"
|
||||
}
|
||||
var res ProfileRefreshResult
|
||||
_ = json.Unmarshal(wr.ProfileRefresh, &res)
|
||||
switch res.Outcome {
|
||||
case "refreshed":
|
||||
units := make([]string, 0, len(res.Restarted))
|
||||
for _, u := range res.Restarted {
|
||||
units = append(units, u.Unit)
|
||||
}
|
||||
if len(units) > 0 {
|
||||
lg.Warn("osupdate: profile refresh — security profiles reloaded from a fresh compile; a FAILED service whose profile was stale was restarted",
|
||||
"kernel", res.Kernel, "restarted", strings.Join(units, ","), "rewritten", len(res.Rewritten), "wrapper_seconds", res.Seconds, "took", took)
|
||||
} else {
|
||||
lg.Info("osupdate: profile refresh — security profiles reloaded from a fresh compile", "kernel", res.Kernel,
|
||||
"rewritten", len(res.Rewritten), "wrapper_seconds", res.Seconds, "took", took)
|
||||
}
|
||||
case "not_needed":
|
||||
lg.Info("osupdate: profile refresh not needed", "kernel", res.Kernel, "reason", res.Reason)
|
||||
default:
|
||||
lg.Warn("osupdate: profile refresh — the wrapper answered with no outcome this agent knows", "outcome", res.Outcome)
|
||||
}
|
||||
return res.Outcome
|
||||
}
|
||||
Reference in new issue
Block a user