Skip to content

Commit

Permalink
Added a send event plugin (Velocidex#1179)
Browse files Browse the repository at this point in the history
This allows any VQL to send any event to a server monitoring
query. This allows implementing server event queries that receive
information from arbitrary sources to form a kind of a service.
  • Loading branch information
scudette authored Aug 7, 2021
1 parent e92352a commit c932f8c
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 7 deletions.
38 changes: 38 additions & 0 deletions artifacts/definitions/Generic/Forensic/HashLookup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Generic.Forensic.HashLookup
description: |
This artifact is a server event artifact that collects hashes from
various sources into a central location. It is possible to follow
this artifact (e.g. with an external program using the API) to
lookup the hashes with an external service.
You can also send hashes to this artifact yourself using the
`send_event()` vql Function. For example, the following will add
hashes from the results of another artifact.
```vql
SELECT *, send_event(
artifact="Generic.Forensic.HashLookup",
row=dict(SHA256=Sha256, ClientId=ClientId))
FROM source()
```
type: SERVER_EVENT

sources:
- query: |
// You can add more queries to this chain to automatically
// collect more hashes.
SELECT ClientId, SHA256 FROM chain(
a={
SELECT * FROM foreach(
row={
SELECT ClientId, FlowId
FROM watch_monitoring(artifact="System.Flow.Completion")
WHERE Flow.artifacts_with_results =~ "System.VFS.DownloadFile"
}, query={
SELECT ClientId, Sha256 AS SHA256
FROM source(
artifact="System.VFS.DownloadFile",
client_id=ClientId, flow_id=FlowId)
})
}, async=TRUE)
12 changes: 11 additions & 1 deletion bin/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ var (
frontend_cmd_minion = frontend_cmd.Flag("minion", "This is a minion frontend").Bool()

frontend_cmd_node = frontend_cmd.Flag("node", "The name of a minion - selects from available frontend configurations").String()

frontend_disable_panic_guard = frontend_cmd.Flag("disable-panic-guard",
"Disabled the panic guard mechanism (not recommended)").Bool()
)

func doFrontendWithPanicGuard() {
if !*frontend_disable_panic_guard {
writeLogOnPanic()
}
doFrontend()
}

func doFrontend() {
config_obj, err := makeDefaultConfigLoader().
WithRequiredFrontend().
Expand Down Expand Up @@ -133,7 +143,7 @@ func startFrontend(sm *services.Service) (*api.Builder, error) {
func init() {
command_handlers = append(command_handlers, func(command string) bool {
if command == frontend_cmd.FullCommand() {
doFrontend()
doFrontendWithPanicGuard()
return true
}
return false
Expand Down
57 changes: 57 additions & 0 deletions bin/panic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"
"time"

"github.com/mitchellh/panicwrap"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"www.velocidex.com/golang/velociraptor/config"
)

func writeLogOnPanic() {
// Figure out the log directory.
config_obj, err := new(config.Loader).
WithFileLoader(*config_path).
WithEmbedded().
WithEnvLoader("VELOCIRAPTOR_CONFIG").
LoadAndValidate()
kingpin.FatalIfError(err, "Unable to load config file")

if config_obj.Logging != nil &&
config_obj.Logging.OutputDirectory != "" {
exitStatus, err := panicwrap.BasicWrap(func(output string) {
// Create a special log file in the log directory.
filename := filepath.Join(
config_obj.Logging.OutputDirectory,
fmt.Sprintf("panic-%v.log", strings.Replace(":", "_",
time.Now().Format(time.RFC3339), -1)))

fd, err := os.OpenFile(filename,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return
}
fd.Write([]byte(output))
fd.Close()
})
if err != nil {
// Something went wrong setting up the panic
// wrapper. Unlikely, but possible.
panic(err)
}

// If exitStatus >= 0, then we're the parent process
// and the panicwrap re-executed ourselves and
// completed. Just exit with the proper status.
if exitStatus >= 0 {
os.Exit(exitStatus)
}

// Otherwise, exitStatus < 0 means we're the
// child. Continue executing as normal...
}
}
16 changes: 16 additions & 0 deletions docs/references/vql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2851,6 +2851,22 @@
type: string
description: The type of search (e.g. 'key')
category: server
- name: send_event
description: |
Sends an event to a server event monitoring queue.
This is used to send an event to a waiting server event monitoring
artifact (either as a VQL query running on the server or perhaps
an external program waiting for this event via the API.
type: Function
args:
- name: artifact
type: string
required: true
- name: row
type: ordereddict.Dict
required: true
- name: serialize
description: Encode an object as a string (csv or json).
type: Function
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.6
github.com/microcosm-cc/bluemonday v1.0.15
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/panicwrap v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/oschwald/maxminddb-golang v1.8.0 // indirect
github.com/pkg/errors v0.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSO
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE=
github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
47 changes: 41 additions & 6 deletions gui/velociraptor/src/components/flows/new-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { HotKeys, ObserveKeys } from "react-hotkeys";
import { requestToParameters } from "./utils.js";

import api from '../core/api-service.js';

import axios from 'axios';

class PaginationBuilder {
PaginationSteps = ["Select Artifacts", "Configure Parameters",
Expand Down Expand Up @@ -103,7 +103,8 @@ class NewCollectionSelectArtifacts extends React.Component {
artifacts: PropTypes.array,

// Update the wizard's artifacts list.
setArtifacts: PropTypes.func,
setArtifacts: PropTypes.func.isRequired,
setParameters: PropTypes.func.isRequired,
paginator: PropTypes.object,

// Artifact type CLIENT, SERVER, CLIENT_EVENT, SERVER_EVENT
Expand All @@ -126,13 +127,18 @@ class NewCollectionSelectArtifacts extends React.Component {
}

componentDidMount = () => {
this.source = axios.CancelToken.source();
this.doSearch("...");
const el = document.getElementById("text-filter-column-name-search-for-artifact-input");
if (el) {
setTimeout(()=>el.focus(), 1000);
};
}

componentWillUnmount() {
this.source.cancel();
}

onSelect = (row, isSelect) => {
let new_artifacts = [];
if (isSelect) {
Expand All @@ -142,7 +148,6 @@ class NewCollectionSelectArtifacts extends React.Component {
}
this.props.setArtifacts(new_artifacts);
this.setState({selectedDescriptor: row});

}

onSelectAll = (isSelect, rows) => {
Expand Down Expand Up @@ -194,6 +199,35 @@ class NewCollectionSelectArtifacts extends React.Component {
});
}

setFavorite = (spec) => {
// First set all the artifacts in the spec.
let artifacts = [];
_.each(spec, x=>artifacts.push(x.artifact));

api.get("v1/GetArtifacts", {
type: this.props.artifactType,
names: artifacts}).then((response) => {
let items = response.data.items || [];
this.setState({
selectedDescriptor: items[0],
matchingDescriptors: items,
});
// Set the artifacts.
this.props.setArtifacts(items);
this.props.setParameters(requestToParameters({specs: spec}));

}, this.source.token);
}

setFavorite_ = (selection) => {
_.each(this.state.favorites, fav=>{
if(selection.value === fav.name) {
this.setFavorite(fav.spec);
}
});
return true;
}

render() {
let columns = [{dataField: "name", text: "",
formatter: (cell, row) => {
Expand Down Expand Up @@ -224,7 +258,7 @@ class NewCollectionSelectArtifacts extends React.Component {
className="favorites"
classNamePrefix="velo"
options={this.state.favorite_options}
onChange={this.setFavorite}
onChange={this.setFavorite_}
placeholder="Favorite Name"
/>}
<Button variant="default"
Expand Down Expand Up @@ -509,7 +543,6 @@ class NewCollectionRequest extends React.Component {
}
}


class NewCollectionLaunch extends React.Component {
static propTypes = {
artifacts: PropTypes.array,
Expand Down Expand Up @@ -803,7 +836,9 @@ class NewCollectionWizard extends React.Component {
paginator={new PaginationBuilder(
"Select Artifacts",
"New Collection: Select Artifacts to collect")}
setArtifacts={this.setArtifacts}/>
setArtifacts={this.setArtifacts}
setParameters={this.setParameters}
/>

<NewCollectionConfigParameters
parameters={this.state.parameters}
Expand Down
34 changes: 34 additions & 0 deletions vql/acls.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const (

type ACLManager interface {
CheckAccess(permission ...acls.ACL_PERMISSION) (bool, error)

// Extended check with extra args (Used for PUBLISH)
CheckAccessWithArgs(
permission acls.ACL_PERMISSION, args ...string) (bool, error)
}

// NullACLManager is an acl manager which allows everything. This is
Expand All @@ -29,6 +33,11 @@ func (self NullACLManager) CheckAccess(
return true, nil
}

func (self NullACLManager) CheckAccessWithArgs(
permission acls.ACL_PERMISSION, args ...string) (bool, error) {
return true, nil
}

// ServerACLManager is used when running server side VQL to control
// ACLs on various VQL plugins.
type ServerACLManager struct {
Expand All @@ -49,6 +58,11 @@ func (self *ServerACLManager) CheckAccess(
return true, nil
}

func (self *ServerACLManager) CheckAccessWithArgs(
permission acls.ACL_PERMISSION, args ...string) (bool, error) {
return acls.CheckAccessWithToken(self.Token, permission, args...)
}

// NewRoleACLManager creates an ACL manager with only the assigned
// roles. This is useful for creating limited VQL permissions
// internally.
Expand Down Expand Up @@ -102,6 +116,26 @@ func CheckAccess(scope vfilter.Scope, permissions ...acls.ACL_PERMISSION) error
return nil
}

func CheckAccessWithArgs(scope vfilter.Scope, permissions acls.ACL_PERMISSION,
args ...string) error {
manager_any, pres := scope.Resolve(ACL_MANAGER_VAR)
if !pres {
return fmt.Errorf("Permission denied: %v", permissions)
}

manager, ok := manager_any.(ACLManager)
if !ok {
return fmt.Errorf("Permission denied: %v", permissions)
}

perm, err := manager.CheckAccessWithArgs(permissions, args...)
if !perm || err != nil {
return fmt.Errorf("Permission denied: %v", permissions)
}

return nil
}

func CheckFilesystemAccess(scope vfilter.Scope, accessor string) error {
switch accessor {

Expand Down
Loading

0 comments on commit c932f8c

Please sign in to comment.