-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
14719ad
commit c293f51
Showing
1 changed file
with
78 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,12 +8,12 @@ import ( | |
"context" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"time" | ||
"os/user" | ||
"strconv" | ||
"syscall" | ||
|
||
"github.com/kolide/launcher/ee/allowedcmd" | ||
"github.com/kolide/launcher/ee/consoleuser" | ||
) | ||
|
||
type gnomeExtensions struct { | ||
|
@@ -25,10 +25,6 @@ var expectedExtensions = []string{ | |
"[email protected]", | ||
} | ||
|
||
const ( | ||
runDir = "/run/user" | ||
) | ||
|
||
func (c *gnomeExtensions) Name() string { | ||
return "Gnome Extensions" | ||
} | ||
|
@@ -40,36 +36,95 @@ func (c *gnomeExtensions) ExtraFileName() string { | |
func (c *gnomeExtensions) Run(ctx context.Context, extraWriter io.Writer) error { | ||
fmt.Fprintf(extraWriter, "# Checking Gnome Extensions\n\n") | ||
|
||
rundirs, err := os.ReadDir(runDir) | ||
currentUser, err := user.Current() | ||
if err != nil { | ||
return fmt.Errorf("reading %s: %w", runDir, err) | ||
return fmt.Errorf("getting current user: %w", err) | ||
} | ||
|
||
usersToCheck := []*user.User{currentUser} | ||
|
||
// running as root so we need to get the console users | ||
if currentUser.Uid == "0" { | ||
// replace users to check with console users | ||
usersToCheck, err = consoleuser.CurrentUsers(ctx) | ||
if err != nil { | ||
return fmt.Errorf("getting console users: %w", err) | ||
} | ||
} | ||
|
||
for _, dir := range rundirs { | ||
if !dir.IsDir() { | ||
atLeastOneGnomeUser := false | ||
atleastOneUserHasExtensions := false | ||
|
||
for _, consoleUser := range usersToCheck { | ||
fmt.Fprintf(extraWriter, "## Checking user %s\n\n", consoleUser.Uid) | ||
|
||
cmd, err := allowedcmd.GnomeExtensions(ctx, "list", "--enabled") | ||
if err != nil { | ||
return fmt.Errorf("creating gnome-extensions list command: %w", err) | ||
} | ||
|
||
// if we are root, need to execute as the user | ||
if currentUser.Uid == "0" { | ||
runningUserUid, err := strconv.ParseUint(consoleUser.Uid, 10, 32) | ||
if err != nil { | ||
return fmt.Errorf("converting uid %s to int: %w", consoleUser.Uid, err) | ||
} | ||
|
||
runningUserGid, err := strconv.ParseUint(consoleUser.Gid, 10, 32) | ||
if err != nil { | ||
return fmt.Errorf("converting gid %s to int: %w", consoleUser.Gid, err) | ||
} | ||
|
||
cmd.SysProcAttr = &syscall.SysProcAttr{ | ||
Credential: &syscall.Credential{ | ||
Uid: uint32(runningUserUid), | ||
Gid: uint32(runningUserGid), | ||
}, | ||
} | ||
} | ||
|
||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
fmt.Fprintf(extraWriter, "Error running gnome-extensions list, assuming user not using gnome, out: %s, err: %s\n\n", string(out), err) | ||
continue | ||
} | ||
|
||
status, summary := checkRundir(ctx, extraWriter, filepath.Join(runDir, dir.Name())) | ||
atLeastOneGnomeUser = true | ||
|
||
thisUserHasAllExtensions := true | ||
|
||
for _, ext := range expectedExtensions { | ||
|
||
fmt.Fprintf(extraWriter, "### checking for extension %s\n\n", ext) | ||
|
||
if !bytes.Contains(out, []byte(ext)) { | ||
fmt.Fprintf(extraWriter, "User %s does not have extension %s enabled\n\n", consoleUser.Uid, ext) | ||
thisUserHasAllExtensions = false | ||
continue | ||
} | ||
|
||
if status != Passing { | ||
c.status = status | ||
fmt.Fprintf(extraWriter, "User %s has extension %s enabled\n\n", consoleUser.Uid, ext) | ||
} | ||
|
||
if c.summary == "" { | ||
c.summary = fmt.Sprintf("uid:%s: %s", dir.Name(), summary) | ||
} else { | ||
c.summary = fmt.Sprintf("%s; uid:%s: %s", c.summary, dir.Name(), summary) | ||
if thisUserHasAllExtensions { | ||
atleastOneUserHasExtensions = true | ||
} | ||
} | ||
|
||
if !atLeastOneGnomeUser { | ||
c.status = Unknown | ||
c.summary = "no gnome users found" | ||
return nil | ||
} | ||
|
||
// If we got here, without setting c.status, it must be passing. It feels not great assuming that, | ||
// but it's a low risk place, and the code is cleaner. | ||
if c.status == "" || c.status == Unknown { | ||
c.status = Passing | ||
if !atleastOneUserHasExtensions { | ||
c.status = Failing | ||
c.summary = "no user has all extensions enabled" | ||
return nil | ||
} | ||
|
||
c.status = Passing | ||
c.summary = "at least 1 user has all extensions enabled" | ||
return nil | ||
} | ||
|
||
|
@@ -84,76 +139,3 @@ func (c *gnomeExtensions) Summary() string { | |
func (c *gnomeExtensions) Data() any { | ||
return nil | ||
} | ||
|
||
func execGnomeExtension(ctx context.Context, extraWriter io.Writer, rundir string, args ...string) ([]byte, error) { | ||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second) | ||
defer cancel() | ||
|
||
// TODO: Need to figure out how to make this run per user | ||
// ee/tables/gsettings/gsettings.go probably has appropriate prior art. | ||
// But do we really want the forloop? | ||
|
||
cmd, err := allowedcmd.GnomeExtensions(ctx, args...) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating gnome-extensions command: %w", err) | ||
} | ||
|
||
// gnome seems to do things through this env | ||
cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", rundir)) | ||
|
||
buf := &bytes.Buffer{} | ||
cmd.Stderr = io.MultiWriter(extraWriter, buf) | ||
cmd.Stdout = cmd.Stderr | ||
|
||
// A bit of an experiment in output formatting. Make it look like a markdown command block | ||
fmt.Fprintf(extraWriter, "```\n$ %s\n", cmd.String()) | ||
defer fmt.Fprintf(extraWriter, "```\n\n") | ||
|
||
if err := cmd.Run(); err != nil { | ||
// reset the buffer so we don't return the error code | ||
return nil, fmt.Errorf(`running "%s", err is: %s: %w`, cmd.String(), buf.String(), err) | ||
} | ||
|
||
return buf.Bytes(), nil | ||
} | ||
|
||
func checkRundir(ctx context.Context, extraWriter io.Writer, rundir string) (Status, string) { | ||
fmt.Fprintf(extraWriter, "## Checking rundir %s\n\n", rundir) | ||
|
||
status := Unknown | ||
summary := "unknown" | ||
|
||
missing := []string{} | ||
|
||
for _, ext := range expectedExtensions { | ||
fmt.Fprintf(extraWriter, "### %s\n\n", ext) | ||
|
||
output, err := execGnomeExtension(ctx, extraWriter, rundir, "show", ext) | ||
if err != nil { | ||
// Errors running this command are probably fatal, may as well bail | ||
return Erroring, fmt.Sprintf("error running gnome-extensions: %s", err) | ||
} | ||
|
||
// Is it enabled? | ||
if !bytes.Contains(output, []byte("State: ENABLED")) { | ||
missing = append(missing, ext) | ||
} | ||
} | ||
|
||
if len(missing) > 0 { | ||
status = Failing | ||
summary = fmt.Sprintf("missing (or screenlocked) extensions: %s", strings.Join(missing, ", ")) | ||
} else { | ||
status = Passing | ||
summary = fmt.Sprintf("enabled extensions: %s", strings.Join(expectedExtensions, ", ")) | ||
} | ||
|
||
if extraWriter != io.Discard { | ||
// We can ignore the response, because it's tee'ed into extraWriter | ||
_, _ = execGnomeExtension(ctx, extraWriter, rundir, "list") | ||
|
||
} | ||
|
||
return status, summary | ||
|
||
} |