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
selinux: package to query SELinux status and verify/restore file contexts #6285
selinux: package to query SELinux status and verify/restore file contexts #6285
Conversation
…exts Adding a new package for working with SELinux. There is basic functionality to query SELinux state, enabled and permissive/enforcing. The code founds out where the selinux filesystem is mounted and uses that path to query the status, rather than hardcoding /sys/fs/selinux. The package has additional helpers to verify whether a path is labeled accroding it its default context and possibly restore the context. Trying to avoid calling to libselinux directly, the command line tools `matchpathcon` and `restorecon` are used. Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
fee58a3
to
81dd13c
Compare
…arwin Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
selinux/label_darwin.go
Outdated
|
||
// Verifypathcon checks whether a given path is labeled according to its default | ||
// SELinux context | ||
func Verifypathcon(aPath string) (bool, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func Verifypathcon(aPath string) (bool, error) { | |
func VerifyPathContext(aPath string) (bool, error) { |
selinux/label_darwin.go
Outdated
*/ | ||
package selinux | ||
|
||
// Verifypathcon checks whether a given path is labeled according to its default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Verifypathcon checks whether a given path is labeled according to its default | |
// VerifyPathContext checks whether a given path is labeled according to its default |
selinux/label_darwin.go
Outdated
} | ||
|
||
// Restorecon restores the default SELinux context of given path | ||
func Restorecon(aPath string, recursive bool) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func Restorecon(aPath string, recursive bool) error { | |
func RestoreContext(aPath string, recursive bool) error { |
"github.com/snapcore/snapd/osutil" | ||
) | ||
|
||
var ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: drop the var ( ... )
and use just one-liner var ...
selinux/label_linux.go
Outdated
|
||
// Verifypathcon checks whether a given path is labeled according to its default | ||
// SELinux context | ||
func Verifypathcon(aPath string) (bool, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func Verifypathcon(aPath string) (bool, error) { | |
func VerifyPathContext(aPath string) (bool, error) { |
selinux/label_linux.go
Outdated
matchIncorrectLabel = regexp.MustCompile("^.* has context .* should be .*\n$") | ||
) | ||
|
||
// Verifypathcon checks whether a given path is labeled according to its default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Verifypathcon checks whether a given path is labeled according to its default | |
// VerifyPathContext checks whether a given path is labeled according to its default |
selinux/label_linux.go
Outdated
return false, err | ||
} | ||
|
||
// Restorecon restores the default SELinux context of given path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Restorecon restores the default SELinux context of given path | |
// RestoreContext restores the default SELinux context of given path |
selinux/label_linux.go
Outdated
} | ||
|
||
// Restorecon restores the default SELinux context of given path | ||
func Restorecon(aPath string, recursive bool) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func Restorecon(aPath string, recursive bool) error { | |
func RestoreContext(aPath string, recursive bool) error { |
selinux/label_linux.go
Outdated
return err | ||
} | ||
// if restorecon is found we may restore SELinux context | ||
restoreconPath, err := exec.LookPath("restorecon") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you are going through the hook of calling LookPath
yourself? exec.Command
will do that for you unless you provide an absolute path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was left behind after some earlier refactoring. I'll drop it in the next push.
selinux/label_linux_test.go
Outdated
cmd := testutil.MockCommand(c, "matchpathcon", "") | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.Verifypathcon(l.path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, err := selinux.Verifypathcon(l.path) | |
ok, err := selinux.VerifyPathContext(l.path) |
selinux/label_linux_test.go
Outdated
cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 1 ") | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.Verifypathcon(l.path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, err := selinux.Verifypathcon(l.path) | |
ok, err := selinux.VerifyPathContext(l.path) |
selinux/label_linux_test.go
Outdated
cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 5 ") | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.Verifypathcon(l.path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, err := selinux.Verifypathcon(l.path) | |
ok, err := selinux.VerifyPathContext(l.path) |
selinux/label_linux_test.go
Outdated
cmd := testutil.MockCommand(c, "matchpathcon", ``) | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.Verifypathcon("does-not-exist") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, err := selinux.Verifypathcon("does-not-exist") | |
ok, err := selinux.VerifyPathContext("does-not-exist") |
selinux/label_linux_test.go
Outdated
} | ||
|
||
func (l *labelSuite) TestVerifyFailNoTool(c *check.C) { | ||
ok, err := selinux.Verifypathcon(l.path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, err := selinux.Verifypathcon(l.path) | |
ok, err := selinux.VerifyPathContext(l.path) |
selinux/label_linux_test.go
Outdated
exit 1`, l.path)) | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.Verifypathcon(l.path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, err := selinux.Verifypathcon(l.path) | |
ok, err := selinux.VerifyPathContext(l.path) |
selinux/label_linux_test.go
Outdated
cmd := testutil.MockCommand(c, "restorecon", "") | ||
defer cmd.Restore() | ||
|
||
err := selinux.Restorecon(l.path, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err := selinux.Restorecon(l.path, false) | |
err := selinux.RestoreContext(l.path, false) |
selinux/label_linux_test.go
Outdated
|
||
cmd.ForgetCalls() | ||
|
||
err = selinux.Restorecon(l.path, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err = selinux.Restorecon(l.path, true) | |
err = selinux.RestoreContext(l.path, true) |
selinux/label_linux_test.go
Outdated
} | ||
|
||
func (l *labelSuite) TestRestoreFail(c *check.C) { | ||
err := selinux.Restorecon(l.path, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err := selinux.Restorecon(l.path, false) | |
err := selinux.RestoreContext(l.path, false) |
selinux/label_linux_test.go
Outdated
cmd := testutil.MockCommand(c, "restorecon", "exit 1") | ||
defer cmd.Restore() | ||
|
||
err = selinux.Restorecon(l.path, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err = selinux.Restorecon(l.path, false) | |
err = selinux.RestoreContext(l.path, false) |
selinux/label_linux_test.go
Outdated
err = selinux.Restorecon(l.path, false) | ||
c.Assert(err, check.ErrorMatches, "exit status 1") | ||
|
||
err = selinux.Restorecon("does-not-exist", false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err = selinux.Restorecon("does-not-exist", false) | |
err = selinux.RestoreContext("does-not-exist", false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very nice overall.
I made a small swarm of suggestions for names that are more natural and not match the underlaying commands.
LGTM
Renames suggested during the review (thanks @zyga!) Extend the comment on matchpathcon. Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good overall but left some comments
selinux/label_linux.go
Outdated
} | ||
|
||
// RestoreContext restores the default SELinux context of given path | ||
func RestoreContext(aPath string, recursive bool) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we have a on element option struct to avoid the unclear bool only argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed
// <the-path> has context <some-context>, should be <some-other-context> | ||
// match the message so that we can distinguish a failed verification | ||
// case from other errors | ||
if exit == 1 && matchIncorrectLabel.Match(out) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems we need a test that calls the real tool if available to test this bit, otherwise we are just checking the expected output against again our own expectations, if the tool output changes nothing breaks here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling the actual tool we'd have to have the policy module installed in the host system. The only way to verify this reasonably is a spread test that does the same thing I'm doing locally: mkdir ~/snap/foo && chcon -t user_home_t -R ~/snap && snap run <snap> && ls -RZ ~/snap
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We definitely want some kind of test (here or in the one using this) that tests this bit as directly/with as little layers as possible. Otherwise the other approach would be to write our own tool in C using libselinux, then we control the format completely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,30 @@ | |||
// -*- Mode: Go; indent-tabs-mode: t -*- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need the per-plaform impls because we will use selinux in the cmd/snap code, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
c.Assert(ok, check.Equals, false) | ||
} | ||
|
||
func (l *labelSuite) TestVerifyFailNoTool(c *check.C) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will fail on fedora like systems, no? aren't we running unit tests there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I'll need to skip this iff the tool is already present. AFAIK we run unit tests only Ubuntu and Debian
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Replace a naked bool passed to RestoreContext with a struct to make the code easier to read/follow. Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
} | ||
|
||
// RestoreContext restores the default SELinux context of given path | ||
func RestoreContext(aPath string, mode RestoreMode) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we tend to use pointer for option structs so that nil can be passed and mean the default as well but no strong opinion for this particular case
Adding a new package for working with SELinux.
There is basic functionality to query SELinux state, enabled and
permissive/enforcing. The code founds out where the selinux filesystem is
mounted and uses that path to query the status, rather than hardcoding
/sys/fs/selinux.
The package has additional helpers to verify whether a path is labeled accroding
it its default context and possibly restore the context. Trying to avoid calling
to libselinux directly, the command line tools
matchpathcon
andrestorecon
are used.