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
Changes from all commits
81dd13c
7f9ed6b
e29faaf
2bcbe94
9372938
08632d8
8cd8b25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
package selinux | ||
|
||
import ( | ||
"io/ioutil" | ||
|
||
"gopkg.in/check.v1" | ||
) | ||
|
||
var ( | ||
GetSELinuxMount = getSELinuxMount | ||
) | ||
|
||
func MockMountInfo(c *check.C, text string) (where string, restore func()) { | ||
old := procSelfMountInfo | ||
dir := c.MkDir() | ||
f, err := ioutil.TempFile(dir, "mountinfo") | ||
c.Assert(err, check.IsNil) | ||
err = ioutil.WriteFile(f.Name(), []byte(text), 0644) | ||
c.Assert(err, check.IsNil) | ||
procSelfMountInfo = f.Name() | ||
restore = func() { | ||
procSelfMountInfo = old | ||
} | ||
return procSelfMountInfo, restore | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
package selinux | ||
|
||
// RestoreMode configures how default path context is restored | ||
type RestoreMode struct { | ||
// Recursive indicates whether the default context shall be restored | ||
// recursively | ||
Recursive bool | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
package selinux | ||
|
||
// VerifyPathContext checks whether a given path is labeled according to its default | ||
// SELinux context | ||
func VerifyPathContext(aPath string) (bool, error) { | ||
return true, nil | ||
} | ||
|
||
// RestoreContext restores the default SELinux context of given path | ||
func RestoreContext(aPath string, mode RestoreMode) error { | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
package selinux | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"regexp" | ||
|
||
"github.com/snapcore/snapd/osutil" | ||
) | ||
|
||
var ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: drop the |
||
// actual matchpathcon -V output: | ||
// /home/guest/snap has context unconfined_u:object_r:user_home_t:s0, should be unconfined_u:object_r:snappy_home_t:s0 | ||
matchIncorrectLabel = regexp.MustCompile("^.* has context .* should be .*\n$") | ||
) | ||
|
||
// VerifyPathContext checks whether a given path is labeled according to its default | ||
// SELinux context | ||
func VerifyPathContext(aPath string) (bool, error) { | ||
if _, err := os.Stat(aPath); err != nil { | ||
// path that cannot be accessed cannot be verified | ||
return false, err | ||
} | ||
// matchpathcon -V verifies whether the context of a path matches the | ||
// default | ||
cmd := exec.Command("matchpathcon", "-V", aPath) | ||
cmd.Env = append(os.Environ(), "LC_ALL=C") | ||
out, err := cmd.Output() | ||
if err == nil { | ||
// the path was verified | ||
return true, nil | ||
} | ||
exit, _ := osutil.ExitCode(err) | ||
// exits with 1 when the verification failed or other error occurred, | ||
// when verification failed a message like this will be printed to | ||
// stdout: | ||
// <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 commentThe 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 commentThe 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: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return false, nil | ||
} | ||
return false, err | ||
} | ||
|
||
// 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 commentThe 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 |
||
if _, err := os.Stat(aPath); err != nil { | ||
// path that cannot be accessed cannot be restored | ||
return err | ||
} | ||
|
||
args := make([]string, 0, 2) | ||
if mode.Recursive { | ||
// -R: recursive | ||
args = append(args, "-R") | ||
} | ||
args = append(args, aPath) | ||
|
||
return exec.Command("restorecon", args...).Run() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
package selinux_test | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"gopkg.in/check.v1" | ||
|
||
"github.com/snapcore/snapd/selinux" | ||
"github.com/snapcore/snapd/testutil" | ||
) | ||
|
||
type labelSuite struct { | ||
path string | ||
} | ||
|
||
var _ = check.Suite(&labelSuite{}) | ||
|
||
func (l *labelSuite) SetUpTest(c *check.C) { | ||
l.path = filepath.Join(c.MkDir(), "foo") | ||
ioutil.WriteFile(l.path, []byte("foo"), 0644) | ||
} | ||
|
||
func (l *labelSuite) TestVerifyHappyOk(c *check.C) { | ||
cmd := testutil.MockCommand(c, "matchpathcon", "") | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.VerifyPathContext(l.path) | ||
c.Assert(err, check.IsNil) | ||
c.Assert(ok, check.Equals, true) | ||
c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ | ||
{"matchpathcon", "-V", l.path}, | ||
}) | ||
} | ||
|
||
func (l *labelSuite) TestVerifyFailGibberish(c *check.C) { | ||
cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 1 ") | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.VerifyPathContext(l.path) | ||
c.Assert(err, check.ErrorMatches, "exit status 1") | ||
c.Assert(ok, check.Equals, false) | ||
} | ||
|
||
func (l *labelSuite) TestVerifyFailStatus(c *check.C) { | ||
cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 5 ") | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.VerifyPathContext(l.path) | ||
c.Assert(err, check.ErrorMatches, "exit status 5") | ||
c.Assert(ok, check.Equals, false) | ||
} | ||
|
||
func (l *labelSuite) TestVerifyFailNoPath(c *check.C) { | ||
cmd := testutil.MockCommand(c, "matchpathcon", ``) | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.VerifyPathContext("does-not-exist") | ||
c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") | ||
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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
if _, err := exec.LookPath("matchpathcon"); err == nil { | ||
c.Skip("matchpathcon found in $PATH") | ||
} | ||
ok, err := selinux.VerifyPathContext(l.path) | ||
c.Assert(err, check.ErrorMatches, `exec: "matchpathcon": executable file not found in \$PATH`) | ||
c.Assert(ok, check.Equals, false) | ||
} | ||
|
||
func (l *labelSuite) TestVerifyHappyMismatch(c *check.C) { | ||
cmd := testutil.MockCommand(c, "matchpathcon", fmt.Sprintf(` | ||
echo %s has context unconfined_u:object_r:user_home_t:s0, should be unconfined_u:object_r:snappy_home_t:s0 | ||
exit 1`, l.path)) | ||
defer cmd.Restore() | ||
|
||
ok, err := selinux.VerifyPathContext(l.path) | ||
c.Assert(err, check.IsNil) | ||
c.Assert(ok, check.Equals, false) | ||
} | ||
|
||
func (l *labelSuite) TestRestoreHappy(c *check.C) { | ||
cmd := testutil.MockCommand(c, "restorecon", "") | ||
defer cmd.Restore() | ||
|
||
err := selinux.RestoreContext(l.path, selinux.RestoreMode{}) | ||
c.Assert(err, check.IsNil) | ||
c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ | ||
{"restorecon", l.path}, | ||
}) | ||
|
||
cmd.ForgetCalls() | ||
|
||
err = selinux.RestoreContext(l.path, selinux.RestoreMode{Recursive: true}) | ||
c.Assert(err, check.IsNil) | ||
c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ | ||
{"restorecon", "-R", l.path}, | ||
}) | ||
} | ||
|
||
func (l *labelSuite) TestRestoreFailNoTool(c *check.C) { | ||
if _, err := exec.LookPath("matchpathcon"); err == nil { | ||
c.Skip("matchpathcon found in $PATH") | ||
} | ||
err := selinux.RestoreContext(l.path, selinux.RestoreMode{}) | ||
c.Assert(err, check.ErrorMatches, `exec: "restorecon": executable file not found in \$PATH`) | ||
} | ||
|
||
func (l *labelSuite) TestRestoreFail(c *check.C) { | ||
cmd := testutil.MockCommand(c, "restorecon", "exit 1") | ||
defer cmd.Restore() | ||
|
||
err := selinux.RestoreContext(l.path, selinux.RestoreMode{}) | ||
c.Assert(err, check.ErrorMatches, "exit status 1") | ||
|
||
err = selinux.RestoreContext("does-not-exist", selinux.RestoreMode{}) | ||
c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2018 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
package selinux | ||
|
||
// IsEnabled checks whether SELinux is enabled | ||
func IsEnabled() (bool, error) { | ||
return false, nil | ||
} | ||
|
||
// IsEnabled checks whether SELinux is in enforcing mode | ||
func IsEnforcing() (bool, error) { | ||
return false, nil | ||
} |
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