Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions actions/vql.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func (self VQLClientAction) StartQuery(

builder := services.ScopeBuilder{
Config: &config_proto.Config{
Client: config_obj.Client,
Remappings: config_obj.Remappings,
},
// Only provide the client config since we are running in
Expand Down
47 changes: 39 additions & 8 deletions artifacts/definitions/SUSE/Linux/Events/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,48 @@ name: SUSE.Linux.Events.Services
description: |
This artifact collects new systemd services events.

required_permissions:
- EXECVE

precondition: |
SELECT OS From info() where OS = 'linux'

type: CLIENT_EVENT

sources:
- query: |
- precondition: |
SELECT OS FROM info()
WHERE OS = 'linux' AND version(plugin='systemctl') != Null

query: |
LET serviceDetails(name) = SELECT
ExecMainPID, Description, ActiveState,
get(item=ExecStart, member="0.0") AS Process
FROM systemctl(command="show", unit=name,
properties=["ExecStart", "Description", "ExecMainPID", "ActiveState"])

LET serviceStartEvents = SELECT
timestamp(epoch=REALTIME_TIMESTAMP) AS Timestamp,
UNIT AS Service,
_UID AS UID,
{ SELECT * FROM serviceDetails(name=UNIT) } AS details
FROM watch_journal()
WHERE SYSLOG_IDENTIFIER = "systemd"
AND JOB_TYPE = "start"
AND JOB_RESULT = "done"
AND _PID = "1"
AND UNIT =~ "service$"

SELECT
Timestamp,
Service,
"root" AS User,
details.ExecMainPID AS PID,
details.Process AS Process,
details.Description AS Description,
details.ActiveState AS State
FROM serviceStartEvents


- precondition: |
SELECT OS FROM info()
WHERE OS = 'linux' AND version(plugin='systemctl') = Null

query: |
-- grok pattern to parse systemctl show output
LET pattern = "%{NUMBER:pid}\n\{ path\=%{DATA:process} .*\n%{DATA:description}\n%{DATA:state}\n"

Expand Down Expand Up @@ -44,4 +76,3 @@ sources:
details.description AS Description,
details.state AS State
FROM serviceStartEvents

28 changes: 22 additions & 6 deletions artifacts/definitions/SUSE/Linux/Events/Timers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ description: |
type: CLIENT_EVENT

sources:
- name: TimerStateChange
precondition: SELECT OS From info() where OS = 'linux'
- precondition: SELECT OS From info() where OS = 'linux'
name: TimerStateChange
description: Collect event when a new timer is started or stopped
query: |
SELECT timestamp(string=REALTIME_TIMESTAMP) as Time,
Expand All @@ -15,16 +15,32 @@ sources:
FROM watch_journal()
WHERE SYSLOG_IDENTIFIER = "systemd" AND CODE_FUNC = "job_emit_done_message" AND UNIT =~ ".*timer"

- name: TimerExecs
precondition: SELECT OS From info() where OS = 'linux'
- precondition: SELECT OS From info() where OS = 'linux' AND version(plugin='systemctl') != Null
name: TimerExecs
description: Collect systemd timer executions from journal
query: |
-- Returns a row if `service` was triggered by a timer.
LET timer_triggered(service) = SELECT *
FROM systemctl(command="show", unit=service, properties=["TriggeredBy"])
WHERE len(list=TriggeredBy) > 0

LET timer_execs = SELECT *
FROM query(query='SELECT * FROM Artifact.SUSE.Linux.Events.Services()')
WHERE timer_triggered(service=Service)

SELECT Timestamp, PID, User, Process as Cmd, Description
FROM timer_execs

- precondition: SELECT OS From info() where OS = 'linux' AND version(plugin='systemctl') = Null
name: TimerExecs
description: Collect systemd timer executions from journal
query: |
LET timers = SELECT parse_json_array(data=Stdout) AS list
FROM execve(argv=['systemctl', 'list-timers', '--all', '-o', 'json', '--no-pager'])

LET timer_execs = SELECT *, {SELECT activates from timers.list} AS activates
FROM Artifact.SUSE.Linux.Events.Services()
WHERE format(format="%s%s" , args=[Service, ".service"]) in activates
FROM query(query='SELECT * FROM Artifact.SUSE.Linux.Events.Services()')
WHERE Service in activates

SELECT Timestamp, PID, User, Process as Cmd, Description
FROM timer_execs
25 changes: 25 additions & 0 deletions docs/references/vql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5712,6 +5712,31 @@
are emitted. Any further queries are ignored.
type: Plugin
category: plugin
- name: systemctl
type: plugin
description: |
Get information about systemd services via dbus.
#### Example
```
SELECT Description, ActiveState
FROM systemctl(command="show", unit="bluetooth.service", properties=["Description", "ActiveState"])'
```
args:
- name: command
required: true
description: Command to run. Only "show" implemented.
type: string
- name: Unit
required: false
description: Unit to show. Required for show command.
type: string
- name: properties
required: false
description: List of properties to show with the show command.
repeated: true
category: linux
metadata:
permissions: MACHINE_STATE
- name: tcpsnoop
description: Report incoming/outgoing tcp connections
type: Plugin
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ require (
github.com/aquasecurity/libbpfgo/helpers v0.0.0-00010101000000-000000000000
github.com/clayscode/Go-Splunk-HTTP/splunk/v2 v2.0.1-0.20221027171526-76a36be4fa02
github.com/coreos/go-oidc/v3 v3.9.0
github.com/coreos/go-systemd/v22 v22.5.0
github.com/djoreilly/go-rpmdb v0.0.0-20250618160408-ec049dd092e4
github.com/elastic/go-libaudit/v2 v2.3.1-0.20221118223002-d56d27cfa498
github.com/evanphx/json-patch/v5 v5.6.0
Expand Down Expand Up @@ -175,6 +176,7 @@ require (
github.com/eapache/queue v1.1.0 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
Expand Down Expand Up @@ -238,6 +240,9 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
Expand Down
107 changes: 107 additions & 0 deletions vql/linux/systemctl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//go:build linux

package linux

import (
"context"

"github.com/Velocidex/ordereddict"
"github.com/coreos/go-systemd/v22/dbus"
"www.velocidex.com/golang/velociraptor/acls"
"www.velocidex.com/golang/velociraptor/vql"
"www.velocidex.com/golang/vfilter"
"www.velocidex.com/golang/vfilter/arg_parser"
)

type SystemctlPluginArgs struct {
Command string `vfilter:"required,field=command,doc=command to run"`
Unit string `vfilter:"optional,field=unit,doc=unit to show"`
Properties []string `vfilter:"optional,field=properties,doc=Properties to show"`
}

type SystemctlPlugin struct{}

func (s SystemctlPlugin) Info(scope vfilter.Scope, typeMap *vfilter.TypeMap) *vfilter.PluginInfo {
return &vfilter.PluginInfo{
Name: "systemctl",
Doc: "Get information about systemd services via dbus.",
ArgType: typeMap.AddType(scope, &SystemctlPluginArgs{}),
Metadata: vql.VQLMetadata().Permissions(acls.MACHINE_STATE).Build(),
}
}

func (s SystemctlPlugin) Call(
ctx context.Context, scope vfilter.Scope, args *ordereddict.Dict,
) <-chan vfilter.Row {
outputCh := make(chan vfilter.Row)

go func() {
defer close(outputCh)

err := vql.CheckAccess(scope, acls.MACHINE_STATE)
if err != nil {
scope.Log("systemctl plugin: checking access: %s", err)
return
}

arg := SystemctlPluginArgs{}
err = arg_parser.ExtractArgsWithContext(ctx, scope, args, &arg)
if err != nil {
scope.Log("systemctl plugin: extracting args: %s", err)
return
}

switch arg.Command {
case "show":
if arg.Unit == "" {
scope.Log("systemctl plugin: unit required for command show")
return
}
err := showProperties(ctx, scope, arg, outputCh)
if err != nil {
scope.Log("systemctl plugin: error showing properties: %v", err)
return
}
default:
scope.Log("systemctl plugin: invalid command: %s", arg.Command)
}
}()

return outputCh
}

func showProperties(ctx context.Context, scope vfilter.Scope,
arg SystemctlPluginArgs, outputCh chan vfilter.Row,
) error {
conn, err := dbus.NewSystemConnectionContext(ctx)
if err != nil {
return err
}
defer conn.Close()

props, err := conn.GetUnitPropertiesContext(ctx, arg.Unit)
if err != nil {
return err
}
typeProps, err := conn.GetUnitTypePropertiesContext(ctx, arg.Unit, "Service")
if err != nil {
return err
}

row := ordereddict.NewDict()
for _, p := range arg.Properties {
if v, ok := props[p]; ok {
row.Set(p, v)
continue
}
if v, ok := typeProps[p]; ok {
row.Set(p, v)
}
}
outputCh <- row
return nil
}

func init() {
vql.RegisterPlugin(&SystemctlPlugin{})
}
Loading