Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use semodule -lfull --checksum instead of the datastore #81

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
34 changes: 3 additions & 31 deletions pkg/daemon/action.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package daemon

import (
"bytes"
"errors"
"fmt"

"github.com/containers/selinuxd/pkg/datastore"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -96,19 +74,13 @@ 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
}

if err := sh.Remove(policyArg); err != nil {
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
}

Expand All @@ -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
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 2 additions & 36 deletions pkg/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
71 changes: 63 additions & 8 deletions pkg/daemon/status_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
Expand All @@ -127,17 +149,50 @@ 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")
http.Error(w, "Cannot get status", http.StatusInternalServerError)
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)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting

Cross-site scripting vulnerability due to [user-provided value](1).
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this is a false positive.

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)
Expand Down
11 changes: 10 additions & 1 deletion pkg/semodule/interface/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand Down
30 changes: 25 additions & 5 deletions pkg/semodule/policycoreutils/policycoreutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion pkg/semodule/semanage/semanage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down
27 changes: 25 additions & 2 deletions pkg/semodule/test/testhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading