Skip to content

Commit

Permalink
Add xrdb table (#738)
Browse files Browse the repository at this point in the history
Add an exec based xrdb table
  • Loading branch information
blaedj authored Jun 14, 2021
1 parent e3b8857 commit e2bb730
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/osquery/table/platform_tables_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/kolide/launcher/pkg/osquery/tables/cryptsetup"
"github.com/kolide/launcher/pkg/osquery/tables/dataflattentable"
"github.com/kolide/launcher/pkg/osquery/tables/gsettings"
"github.com/kolide/launcher/pkg/osquery/tables/xrdb"
osquery "github.com/kolide/osquery-go"
"github.com/kolide/osquery-go/plugin/table"
)
Expand All @@ -16,6 +17,7 @@ func platformTables(client *osquery.ExtensionManagerClient, logger log.Logger, c
cryptsetup.TablePlugin(client, logger),
gsettings.Settings(client, logger),
gsettings.Metadata(client, logger),
xrdb.TablePlugin(client, logger),
dataflattentable.TablePluginExec(client, logger,
"kolide_nmcli_wifi", dataflattentable.KeyValueType,
[]string{"/usr/bin/nmcli", "--mode=multiline", "--fields=all", "device", "wifi", "list"},
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions pkg/osquery/tables/xrdb/testdata/example_query.sql
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;
8 changes: 8 additions & 0 deletions pkg/osquery/tables/xrdb/testdata/results.txt
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
183 changes: 183 additions & 0 deletions pkg/osquery/tables/xrdb/xrdb.go
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
}
109 changes: 109 additions & 0 deletions pkg/osquery/tables/xrdb/xrdb_test.go
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)
})
}
}

0 comments on commit e2bb730

Please sign in to comment.