From 4743d4244a9c149ba1d3c06485123f5ac6993cc3 Mon Sep 17 00:00:00 2001 From: Petr Lautrbach Date: Tue, 29 Nov 2022 10:43:08 +0100 Subject: [PATCH] Use `semodule -lfull --checksum` instead of the datastore SELinux userspace release 3.4 introduced a new command line option [-m|--checksum] to `semodule` which adds sha256 checksum of modules to its output. It can be used to check whether the same module is already installed or not. Given that selinuxd installed modules use priority 350 we can use semodule checksum and priority 350 as an indicator whether a module was already installed by selinuxd or not and therefore there's no need to track the state of modules in a separate datastore. `semodule --checksum` is supported since Red Hat Enterprise Linux 8.6 Signed-off-by: Petr Lautrbach --- pkg/daemon/action.go | 34 +-------- pkg/daemon/daemon.go | 2 +- pkg/daemon/daemon_test.go | 38 +--------- pkg/daemon/status_server.go | 71 ++++++++++++++++--- pkg/semodule/interface/interface.go | 11 ++- .../policycoreutils/policycoreutils.go | 30 ++++++-- pkg/semodule/semanage/semanage.go | 3 +- pkg/semodule/test/testhandler.go | 27 ++++++- pkg/utils/utils.go | 13 ++-- 9 files changed, 138 insertions(+), 91 deletions(-) diff --git a/pkg/daemon/action.go b/pkg/daemon/action.go index 1018db88..3cf628c2 100644 --- a/pkg/daemon/action.go +++ b/pkg/daemon/action.go @@ -1,8 +1,6 @@ package daemon import ( - "bytes" - "errors" "fmt" "github.com/containers/selinuxd/pkg/datastore" @@ -40,34 +38,14 @@ func (pi *policyInstall) do(modulePath string, sh seiface.Handler, ds datastore. return "", fmt.Errorf("installing policy: %w", csErr) } - p, getErr := ds.Get(policyName) + module, getErr := sh.GetPolicyModule(policyName) // If the checksums are equal, the policy is already installed // and in an appropriate state - if getErr == nil && bytes.Equal(p.Checksum, cs) { + if getErr == nil && module.Checksum == cs { return "", nil - } else if getErr != nil && !errors.Is(getErr, datastore.ErrPolicyNotFound) { - return "", fmt.Errorf("installing policy: couldn't access datastore: %w", getErr) } installErr := sh.Install(pi.path) - status := datastore.InstalledStatus - var msg string - - if installErr != nil { - status = datastore.FailedStatus - msg = installErr.Error() - } - - ps := datastore.PolicyStatus{ - Policy: policyName, - Status: status, - Message: msg, - Checksum: cs, - } - puterr := ds.Put(ps) - if puterr != nil { - return "", fmt.Errorf("failed persisting status in datastore: %w", puterr) - } if installErr != nil { return "", fmt.Errorf("failed executing install action: %w", installErr) @@ -96,9 +74,6 @@ func (pi *policyRemove) do(modulePath string, sh seiface.Handler, ds datastore.D } if !pi.moduleInstalled(sh, policyArg) { - if err := ds.Remove(policyArg); err != nil { - return "Module is not in the system", fmt.Errorf("failed removing policy from datastore: %w", err) - } return "No action needed; Module is not in the system", nil } @@ -106,9 +81,6 @@ func (pi *policyRemove) do(modulePath string, sh seiface.Handler, ds datastore.D return "", fmt.Errorf("failed executing remove action: %w", err) } - if err := ds.Remove(policyArg); err != nil { - return "", fmt.Errorf("failed removing policy from datastore: %w", err) - } return "", nil } @@ -119,7 +91,7 @@ func (pi *policyRemove) moduleInstalled(sh seiface.Handler, policy string) bool } for _, mod := range currentModules { - if policy == mod { + if policy == mod.Name { return true } } diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index c176c1c6..c6a6f596 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -39,7 +39,7 @@ func Daemon(opts *SelinuxdOptions, mPath string, sh seiface.Handler, ds datastor defer ds.Close() } - ss, err := initStatusServer(opts.StatusServerConfig, ds.GetReadOnly(), l) + ss, err := initStatusServer(opts.StatusServerConfig, ds.GetReadOnly(), sh, l, mPath) if err != nil { l.Error(err, "Unable initialize status server") panic(err) diff --git a/pkg/daemon/daemon_test.go b/pkg/daemon/daemon_test.go index 8a877f8a..cf11fd7c 100644 --- a/pkg/daemon/daemon_test.go +++ b/pkg/daemon/daemon_test.go @@ -25,9 +25,8 @@ const ( ) var ( - errModuleNotInstalled = fmt.Errorf("the module wasn't installed") - errModuleInstalled = fmt.Errorf("the module was installed when it shouldn't") - errInstallNotPerfomedYet = fmt.Errorf("install action not performed yet") + errModuleNotInstalled = fmt.Errorf("the module wasn't installed") + errModuleInstalled = fmt.Errorf("the module was installed when it shouldn't") ) func getPolicyPath(module, path string) string { @@ -136,39 +135,6 @@ func TestDaemon(t *testing.T) { } }) - t.Run("Should skip policy installation if it's already installed", func(t *testing.T) { - initPolicyGets := ds.GetCalls() - initPolicyPuts := ds.PutCalls() - time.Sleep(1 * time.Second) - // Overwritting a policy with the same contents should not - // trigger another PUT - installPolicy(moduleName, moddir, t) - - var currentGetCalls, currentPutCalls int32 - - // Module has to be installed... eventually - err := backoff.Retry(func() error { - // "touching" the policy will trigger an inotify - // event which will attempt to install it again. - // The action interface will "get" the policy - // and compare the checksum - currentGetCalls = ds.GetCalls() - currentPutCalls = ds.PutCalls() - if initPolicyGets == currentGetCalls { - return errInstallNotPerfomedYet - } - return nil - }, backoff.WithMaxRetries(backoff.NewConstantBackOff(defaultPollBackOff), 5)) - if err != nil { - t.Fatalf("%s. Got GET calls %d - Started with %d", err, currentGetCalls, initPolicyGets) - } - - if currentPutCalls != initPolicyPuts { - t.Fatalf("The policy was updated unexpectedly. Got put calls: %d - Expected: %d", - currentGetCalls, initPolicyPuts) - } - }) - t.Run("Sending a GET to the socket's /ready/ path should return the ready status", func(t *testing.T) { req := getReadyRequest(ctx, t) response, err := httpc.Do(req) diff --git a/pkg/daemon/status_server.go b/pkg/daemon/status_server.go index aabab0f2..de672451 100644 --- a/pkg/daemon/status_server.go +++ b/pkg/daemon/status_server.go @@ -8,9 +8,12 @@ import ( "net/http" "net/http/pprof" "os" + "path/filepath" "time" "github.com/containers/selinuxd/pkg/datastore" + seiface "github.com/containers/selinuxd/pkg/semodule/interface" + "github.com/containers/selinuxd/pkg/utils" "github.com/go-logr/logr" "github.com/gorilla/mux" ) @@ -31,12 +34,27 @@ type StatusServerConfig struct { type statusServer struct { cfg StatusServerConfig ds datastore.ReadOnlyDataStore + sh seiface.Handler l logr.Logger + mPath string lst net.Listener ready bool } -func initStatusServer(cfg StatusServerConfig, ds datastore.ReadOnlyDataStore, l logr.Logger) (*statusServer, error) { +type policyStatus struct { + Policy string `json:"-"` + Status string `json:"status"` + Message string `json:"msg"` + Checksum string `json:"-"` +} + +func initStatusServer( + cfg StatusServerConfig, + ds datastore.ReadOnlyDataStore, + sh seiface.Handler, + l logr.Logger, + mPath string, +) (*statusServer, error) { if cfg.Path == "" { cfg.Path = DefaultUnixSockAddr } @@ -48,7 +66,7 @@ func initStatusServer(cfg StatusServerConfig, ds datastore.ReadOnlyDataStore, l return nil, fmt.Errorf("setting up socket: %w", err) } - ss := &statusServer{cfg, ds, l, lst, false} + ss := &statusServer{cfg, ds, sh, l, mPath, lst, false} return ss, nil } @@ -111,13 +129,17 @@ func (ss *statusServer) initializeRoutes(r *mux.Router) { } func (ss *statusServer) listPoliciesHandler(w http.ResponseWriter, r *http.Request) { - modules, err := ss.ds.List() + modules, err := ss.sh.List() if err != nil { http.Error(w, "Cannot list modules", http.StatusInternalServerError) return } - err = json.NewEncoder(w).Encode(modules) + moduleList := []string{} + for _, module := range modules { + moduleList = append(moduleList, module.Name) + } + err = json.NewEncoder(w).Encode(moduleList) if err != nil { ss.l.Error(err, "error writing list response") http.Error(w, "Cannot list modules", http.StatusInternalServerError) @@ -127,9 +149,9 @@ func (ss *statusServer) listPoliciesHandler(w http.ResponseWriter, r *http.Reque func (ss *statusServer) getPolicyStatusHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) policy := vars["policy"] - status, err := ss.ds.Get(policy) - if errors.Is(err, datastore.ErrPolicyNotFound) { - http.Error(w, "couldn't find requested policy", http.StatusNotFound) + module, err := ss.sh.GetPolicyModule(policy) + if errors.Is(err, seiface.ErrPolicyNotFound) { + http.Error(w, "policy is not installed", http.StatusNotFound) return } else if err != nil { ss.l.Error(err, "error getting status") @@ -137,7 +159,40 @@ func (ss *statusServer) getPolicyStatusHandler(w http.ResponseWriter, r *http.Re return } - err = json.NewEncoder(w).Encode(status) + var policyFile string + err = filepath.Walk(ss.mPath, func(path string, info os.FileInfo, err error) error { + if info == nil { + return nil + } + if !info.IsDir() && (filepath.Base(path) == policy+".cil" || filepath.Base(path) == policy+".pp") { + policyFile = path + return nil + } + return nil + }) + if err != nil { + ss.l.Error(err, "error getting status") + http.Error(w, "Cannot get status", http.StatusInternalServerError) + return + } + + cs, csErr := utils.Checksum(policyFile) + + if csErr != nil { + http.Error(w, "cannot find policy file "+policyFile, http.StatusNotFound) + return + } + + if cs != module.Checksum { + http.Error(w, "Installed policy "+module.Name+" does not much policy file "+policyFile, http.StatusNotFound) + return + } + err = json.NewEncoder(w).Encode(policyStatus{ + Policy: policy, + Status: "Installed", + Message: "", + Checksum: module.Checksum, + }) if err != nil { ss.l.Error(err, "error writing status response") http.Error(w, "Cannot get status", http.StatusInternalServerError) diff --git a/pkg/semodule/interface/interface.go b/pkg/semodule/interface/interface.go index 3a0eb14c..ea3fd6cb 100644 --- a/pkg/semodule/interface/interface.go +++ b/pkg/semodule/interface/interface.go @@ -5,6 +5,12 @@ import ( "fmt" ) +type PolicyModule struct { + Name string + Ext string + Checksum string +} + // errors var ( // ErrHandleCreate is an error when getting a handle to semanage @@ -23,6 +29,8 @@ var ( ErrCannotInstallModule = errors.New("cannot install module") // ErrCommit is an error when committing the changes to the SELinux policy ErrCommit = errors.New("cannot commit changes to policy") + // ErrPolicyNotFound is an error policy is not found in SELinux policy modules store + ErrPolicyNotFound = errors.New("policy not found in SELinux store") ) func NewErrCannotRemoveModule(mName string) error { @@ -42,7 +50,8 @@ func NewErrCommit(origErrVal int, msg string) error { type Handler interface { SetAutoCommit(bool) Install(string) error - List() ([]string, error) + List() ([]PolicyModule, error) + GetPolicyModule(string) (PolicyModule, error) Remove(string) error Commit() error Close() error diff --git a/pkg/semodule/policycoreutils/policycoreutils.go b/pkg/semodule/policycoreutils/policycoreutils.go index 80446c87..1466d5b7 100644 --- a/pkg/semodule/policycoreutils/policycoreutils.go +++ b/pkg/semodule/policycoreutils/policycoreutils.go @@ -46,22 +46,42 @@ func (smt *SEModulePcuHandler) Install(modulePath string) error { return nil } -func (smt *SEModulePcuHandler) List() ([]string, error) { - out, err := runSemodule("-lfull") +func (smt *SEModulePcuHandler) List() ([]seiface.PolicyModule, error) { + out, err := runSemodule("-lfull", "--checksum") if err != nil { smt.logger.Error(err, "Listing policies") return nil, seiface.ErrList } - modules := make([]string, 0) + modules := make([]seiface.PolicyModule, 0) for _, line := range strings.Split(string(out), "\n") { - module := strings.Split(line, " ") + module := strings.Fields(line) + if len(module) != 4 { + continue + } if module[0] == "350" { - modules = append(modules, module[1]) + policyModule := seiface.PolicyModule{module[1], module[2], module[3]} + modules = append(modules, policyModule) } } return modules, nil } +func (smt *SEModulePcuHandler) GetPolicyModule(moduleName string) (seiface.PolicyModule, error) { + modules, err := smt.List() + if err != nil { + smt.logger.Error(err, "Getting module checksum") + return seiface.PolicyModule{}, seiface.ErrList + } + for _, module := range modules { + // 350 module cil sha256:dadb16b11a1d298e57cbd965f5fc060b7a9263b8d6b23af7763e68ac22fb5265 + if module.Name == moduleName { + smt.logger.Info(moduleName, "checksum", module.Checksum) + return module, nil + } + } + return seiface.PolicyModule{}, seiface.ErrPolicyNotFound +} + func (smt *SEModulePcuHandler) Remove(modToRemove string) error { out, err := runSemodule("-X", "350", "-r", modToRemove) if err != nil { diff --git a/pkg/semodule/semanage/semanage.go b/pkg/semodule/semanage/semanage.go index 4f1ce38c..ba9c1459 100644 --- a/pkg/semodule/semanage/semanage.go +++ b/pkg/semodule/semanage/semanage.go @@ -14,12 +14,13 @@ void wrap_set_cb(semanage_handle_t *handle, void *arg); */ import "C" + import ( "bytes" - "github.com/containers/selinuxd/pkg/semodule/interface" "sync" "unsafe" + "github.com/containers/selinuxd/pkg/semodule/interface" "github.com/go-logr/logr" ) diff --git a/pkg/semodule/test/testhandler.go b/pkg/semodule/test/testhandler.go index d5aed22d..36d4e6d1 100644 --- a/pkg/semodule/test/testhandler.go +++ b/pkg/semodule/test/testhandler.go @@ -49,11 +49,34 @@ func (smt *SEModuleTestHandler) IsModuleInstalled(module string) bool { return false } -func (smt *SEModuleTestHandler) List() ([]string, error) { +func (smt *SEModuleTestHandler) List() ([]seiface.PolicyModule, error) { // Return a copy smt.mu.Lock() defer smt.mu.Unlock() - return append([]string(nil), smt.modules...), nil + policyModules := []seiface.PolicyModule{} + for _, module := range smt.modules { + m, err := smt.GetPolicyModule(module) + if err != nil { + return policyModules, err + } + policyModules = append(policyModules, m) + } + return policyModules, nil +} + +func (smt *SEModuleTestHandler) GetPolicyModule(moduleName string) (seiface.PolicyModule, error) { + for _, mod := range smt.modules { + // The module had already been installed. + // Nothing to do + if mod == moduleName { + return seiface.PolicyModule{ + Name: moduleName, + Ext: "cil", + Checksum: "sha256:e6c4d9c235af5d3ca50f660fc2283ecc330ebea73ec35241cc9cc47878dab68c", + }, nil + } + } + return seiface.PolicyModule{}, seiface.ErrPolicyNotFound } func (smt *SEModuleTestHandler) Remove(modToRemove string) error { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 81fe65f3..f2b497ff 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,7 +1,8 @@ package utils import ( - "crypto/sha512" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "io" @@ -35,17 +36,17 @@ func PolicyNameFromPath(path string) (string, error) { } // Checksum returns a checksum for a file on a given path -func Checksum(path string) ([]byte, error) { +func Checksum(path string) (string, error) { f, err := os.Open(path) if err != nil { - return nil, fmt.Errorf("unable to calculate checksum: %w", err) + return "", fmt.Errorf("unable to calculate checksum: %w", err) } defer f.Close() - h := sha512.New() + h := sha256.New() if _, err := io.Copy(h, f); err != nil { - return nil, fmt.Errorf("unable to calculate checksum: %w", err) + return "", fmt.Errorf("unable to calculate checksum: %w", err) } - return h.Sum(nil), nil + return fmt.Sprintf("sha256:%s", hex.EncodeToString(h.Sum(nil))), nil }