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 }