-
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.
Add an exec based xrdb table
- Loading branch information
Showing
6 changed files
with
310 additions
and
0 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
Empty file.
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 |
---|---|---|
@@ -0,0 +1,8 @@ | ||
WITH human_accounts AS ( | ||
SELECT username FROM users u WHERE u.uid >= 1000 AND u.uid < 60000 | ||
), | ||
user_xrdb_values AS ( | ||
SELECT * FROM kolide_xrdb kx WHERE kx.username IN (SELECT username from human_accounts) | ||
) | ||
|
||
SELECT * FROM user_xrdb_values; |
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 |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*customization: -color | ||
Xft.dpi: 96 | ||
Xft.antialias: 1 | ||
Xft.hinting: 1 | ||
Xft.hintstyle: hintslight | ||
Xft.rgba: rgb | ||
Xcursor.size: 24 | ||
Xcursor.theme: Yaru |
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 |
---|---|---|
@@ -0,0 +1,183 @@ | ||
// +build linux | ||
|
||
package xrdb | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"os/user" | ||
"strconv" | ||
"strings" | ||
"syscall" | ||
"time" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/go-kit/kit/log/level" | ||
"github.com/kolide/launcher/pkg/osquery/tables/tablehelpers" | ||
"github.com/kolide/osquery-go" | ||
"github.com/kolide/osquery-go/plugin/table" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
var xrdbPath = "/usr/bin/xrdb" | ||
|
||
const allowedUsernameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-." | ||
const allowedDisplayCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:." | ||
|
||
type execer func(ctx context.Context, display, username string, buf *bytes.Buffer) error | ||
|
||
type XRDBSettings struct { | ||
client *osquery.ExtensionManagerClient | ||
logger log.Logger | ||
getBytes execer | ||
} | ||
|
||
func TablePlugin(client *osquery.ExtensionManagerClient, logger log.Logger) *table.Plugin { | ||
columns := []table.ColumnDefinition{ | ||
table.TextColumn("key"), | ||
table.TextColumn("value"), | ||
table.TextColumn("display"), | ||
table.TextColumn("username"), | ||
} | ||
|
||
t := &XRDBSettings{ | ||
client: client, | ||
logger: logger, | ||
getBytes: execXRDB, | ||
} | ||
|
||
return table.NewPlugin("kolide_xrdb", columns, t.generate) | ||
} | ||
|
||
func (t *XRDBSettings) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { | ||
var results []map[string]string | ||
|
||
users := tablehelpers.GetConstraints(queryContext, "username", tablehelpers.WithAllowedCharacters(allowedUsernameCharacters)) | ||
if len(users) < 1 { | ||
return results, errors.New("kolide_xrdb requires at least one username to be specified") | ||
} | ||
|
||
displays := tablehelpers.GetConstraints(queryContext, "display", | ||
tablehelpers.WithAllowedCharacters(allowedDisplayCharacters), | ||
tablehelpers.WithDefaults(":0"), | ||
) | ||
for _, username := range users { | ||
for _, display := range displays { | ||
var output bytes.Buffer | ||
|
||
err := t.getBytes(ctx, display, username, &output) | ||
if err != nil { | ||
level.Info(t.logger).Log( | ||
"msg", "error getting bytes for user", | ||
"username", username, | ||
"err", err, | ||
) | ||
continue | ||
} | ||
user_results := t.parse(display, username, &output) | ||
results = append(results, user_results...) | ||
} | ||
} | ||
|
||
return results, nil | ||
} | ||
|
||
// execXRDB writes the output of running 'xrdb' command into the | ||
// supplied bytes buffer | ||
func execXRDB(ctx context.Context, displayNum, username string, buf *bytes.Buffer) error { | ||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second) | ||
defer cancel() | ||
|
||
u, err := user.Lookup(username) | ||
if err != nil { | ||
return errors.Wrapf(err, "finding user by username '%s'", username) | ||
} | ||
|
||
cmd := exec.CommandContext(ctx, xrdbPath, "-display", displayNum, "-global", "-query") | ||
|
||
// set the HOME cmd so that xrdb is exec'd properly as the new user. | ||
cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", u.HomeDir)) | ||
|
||
// Check if the supplied UID is that of the current user | ||
currentUser, err := user.Current() | ||
if err != nil { | ||
return errors.Wrap(err, "checking current user uid") | ||
} | ||
|
||
if u.Uid != currentUser.Uid { | ||
uid, err := strconv.ParseInt(u.Uid, 10, 32) | ||
if err != nil { | ||
return errors.Wrap(err, "converting uid from string to int") | ||
} | ||
gid, err := strconv.ParseInt(u.Gid, 10, 32) | ||
if err != nil { | ||
return errors.Wrap(err, "converting gid from string to int") | ||
} | ||
cmd.SysProcAttr = &syscall.SysProcAttr{} | ||
cmd.SysProcAttr.Credential = &syscall.Credential{ | ||
Uid: uint32(uid), | ||
Gid: uint32(gid), | ||
} | ||
} | ||
|
||
dir, err := ioutil.TempDir("", "osq-xrdb") | ||
if err != nil { | ||
return errors.Wrap(err, "mktemp") | ||
} | ||
defer os.RemoveAll(dir) | ||
|
||
if err := os.Chmod(dir, 0755); err != nil { | ||
return errors.Wrap(err, "chmod") | ||
} | ||
cmd.Dir = dir | ||
stderr := new(bytes.Buffer) | ||
cmd.Stderr = stderr | ||
cmd.Stdout = buf | ||
|
||
if err := cmd.Run(); err != nil { | ||
return errors.Wrapf(err, "running xrdb, err is: %s", stderr.String()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (t *XRDBSettings) parse(display, username string, input io.Reader) []map[string]string { | ||
var results []map[string]string | ||
|
||
scanner := bufio.NewScanner(input) | ||
|
||
for scanner.Scan() { | ||
line := scanner.Text() | ||
if line == "" { | ||
continue | ||
} | ||
|
||
parts := strings.SplitN(line, ":", 2) | ||
if len(parts) < 2 { | ||
level.Error(t.logger).Log( | ||
"msg", "unable to process line, not enough segments", | ||
"line", line, | ||
) | ||
continue | ||
} | ||
row := make(map[string]string) | ||
row["key"] = parts[0] | ||
row["value"] = strings.TrimSpace(parts[1]) | ||
row["display"] = display | ||
row["username"] = username | ||
|
||
results = append(results, row) | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
level.Debug(t.logger).Log("msg", "scanner error", "err", err) | ||
} | ||
|
||
return results | ||
} |
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 |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// +build linux | ||
|
||
package xrdb | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/kolide/launcher/pkg/osquery/tables/tablehelpers" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestXrdbParse(t *testing.T) { | ||
t.Parallel() | ||
|
||
var tests = []struct { | ||
filename string | ||
expected []map[string]string | ||
}{ | ||
{ | ||
filename: "blank.txt", | ||
expected: []map[string]string{}, | ||
}, | ||
{ | ||
filename: "results.txt", | ||
expected: []map[string]string{ | ||
{ | ||
"username": "tester", | ||
"key": "*customization", | ||
"value": "-color", | ||
"display": ":0", | ||
}, | ||
|
||
{ | ||
"username": "tester", | ||
"key": "Xft.dpi", | ||
"value": "96", | ||
"display": ":0", | ||
}, | ||
|
||
{ | ||
"username": "tester", | ||
"key": "Xft.antialias", | ||
"value": "1", | ||
"display": ":0", | ||
}, | ||
{ | ||
"username": "tester", | ||
"key": "Xft.hinting", | ||
"value": "1", | ||
"display": ":0", | ||
}, | ||
{ | ||
"username": "tester", | ||
"key": "Xft.hintstyle", | ||
"value": "hintslight", | ||
"display": ":0", | ||
}, | ||
{ | ||
"username": "tester", | ||
"key": "Xft.rgba", | ||
"value": "rgb", | ||
"display": ":0", | ||
}, | ||
{ | ||
"username": "tester", | ||
"key": "Xcursor.size", | ||
"value": "24", | ||
"display": ":0", | ||
}, | ||
{ | ||
"username": "tester", | ||
"key": "Xcursor.theme", | ||
"value": "Yaru", | ||
"display": ":0", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
table := XRDBSettings{ | ||
logger: log.NewNopLogger(), | ||
getBytes: func(ctx context.Context, display, username string, buf *bytes.Buffer) error { | ||
f, err := os.Open(filepath.Join("testdata", tt.filename)) | ||
require.NoError(t, err, "opening file %s", tt.filename) | ||
_, err = buf.ReadFrom(f) | ||
require.NoError(t, err, "read file %s", tt.filename) | ||
|
||
return nil | ||
}, | ||
} | ||
t.Run(tt.filename, func(t *testing.T) { | ||
ctx := context.TODO() | ||
qCon := tablehelpers.MockQueryContext(map[string][]string{ | ||
"username": {"tester"}, | ||
"display": {":0"}, | ||
}) | ||
|
||
results, err := table.generate(ctx, qCon) | ||
require.NoError(t, err, "generating results from %s", tt.filename) | ||
require.ElementsMatch(t, tt.expected, results) | ||
}) | ||
} | ||
} |