Skip to content

Commit

Permalink
Added user_delete() and user_create() VQL functions (Velocidex#1187)
Browse files Browse the repository at this point in the history
This allows admin users to create or delete other users in the notebook.
  • Loading branch information
scudette authored Aug 11, 2021
1 parent c7b420a commit 3a37c25
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 16 deletions.
9 changes: 4 additions & 5 deletions artifacts/definitions/Demo/Plugins/GUI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,8 @@ sources:
{{ define "Q" }}
SELECT _ts, CPUPercent
FROM source(
source="Prometheus",
artifact="Server.Monitor.Health",
FROM monitoring(
artifact="Server.Monitor.Health/Prometheus",
start_time=now() - 10 * 60)
LIMIT 100
{{ end }}
Expand All @@ -127,15 +126,15 @@ sources:
Add a timeline from this time series data
*/
SELECT timestamp(epoch=_ts) AS Timestamp, CPUPercent
FROM source(
FROM monitoring(
source="Prometheus",
artifact="Server.Monitor.Health",
start_time=now() - 10 * 60)
LET T1 = SELECT
timestamp(epoch=_ts) AS Timestamp,
dict(X=CPUPercent, Y=1) AS Dict
FROM source(
FROM monitoring(
source="Prometheus",
artifact="Server.Monitor.Health",
start_time=now() - 10 * 60)
Expand Down
16 changes: 16 additions & 0 deletions artifacts/testdata/server/testcases/users.in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Queries:
# Should return no users
- SELECT * FROM gui_users() WHERE name =~ "TestUser"

# Lets create a user
- SELECT user_create(
user="TestUser", password="hunter2",
roles=["reader", "investigator"]) FROM scope()

- SELECT name, Permissions.roles FROM gui_users() WHERE name =~ "TestUser"

# Now delete it
- SELECT user_delete(user="TestUser") FROM scope()

# Should be gone now.
- SELECT * FROM gui_users() WHERE name =~ "TestUser"
17 changes: 17 additions & 0 deletions artifacts/testdata/server/testcases/users.out.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
SELECT * FROM gui_users() WHERE name =~ "TestUser"[]SELECT user_create( user="TestUser", password="hunter2", roles=["reader", "investigator"]) FROM scope()[
{
"user_create(user=\"TestUser\", password=\"hunter2\", roles= [\"reader\", \"investigator\"])": "TestUser"
}
]SELECT name, Permissions.roles FROM gui_users() WHERE name =~ "TestUser"[
{
"name": "TestUser",
"Permissions.roles": [
"reader",
"investigator"
]
}
]SELECT user_delete(user="TestUser") FROM scope()[
{
"user_delete(user=\"TestUser\")": "TestUser"
}
]SELECT * FROM gui_users() WHERE name =~ "TestUser"[]
2 changes: 1 addition & 1 deletion gui/velociraptor/src/components/events/timeline-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class EventTableRenderer extends Component {
for(var i=0;i<this.props.columns.length;i++) {
var name = this.props.columns[i];
let definition ={ dataField: name, text: name};
if (name == "_ts") {
if (name === "_ts") {
definition.text = "Server Time";
}
if (this.props.renderers && this.props.renderers[name]) {
Expand Down
26 changes: 18 additions & 8 deletions gui/velociraptor/src/components/hunts/hunt-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import DeleteNotebookDialog from '../notebooks/notebook-delete.js';
import ExportNotebook from '../notebooks/export-notebook.js';

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


class HuntList extends React.Component {
static propTypes = {
Expand All @@ -31,6 +33,14 @@ class HuntList extends React.Component {
updateHunts: PropTypes.func,
};

componentDidMount = () => {
this.source = axios.CancelToken.source();
}

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

state = {
showWizard: false,
showRunHuntDialog: false,
Expand All @@ -43,7 +53,7 @@ class HuntList extends React.Component {

// Launch the hunt.
setCollectionRequest = (request) => {
api.post('v1/CreateHunt', request).then((response) => {
api.post('v1/CreateHunt', request, this.source.token).then((response) => {
// Keep the wizard up until the server confirms the
// creation worked.
this.setState({showWizard: false,
Expand All @@ -63,7 +73,7 @@ class HuntList extends React.Component {
api.post("v1/ModifyHunt", {
state: "RUNNING",
hunt_id: hunt_id,
}).then((response) => {
}, this.source.token).then((response) => {
this.props.updateHunts();
this.setState({showRunHuntDialog: false});
});
Expand All @@ -78,7 +88,7 @@ class HuntList extends React.Component {
api.post("v1/ModifyHunt", {
state: "PAUSED",
hunt_id: hunt_id,
}).then((response) => {
}, this.source.token).then((response) => {
this.props.updateHunts();

// Start Cancelling all in flight collections in the
Expand All @@ -90,7 +100,7 @@ class HuntList extends React.Component {
parameters: {env: [
{key: "HuntId", value: hunt_id},
]}}],
});
}, this.source.token);
});
}

Expand All @@ -103,7 +113,7 @@ class HuntList extends React.Component {
api.post("v1/ModifyHunt", {
state: "ARCHIVED",
hunt_id: hunt_id,
}).then((response) => {
}, this.source.token).then((response) => {
this.props.updateHunts();
this.setState({showArchiveHuntDialog: false});
});
Expand All @@ -117,7 +127,7 @@ class HuntList extends React.Component {
api.post("v1/ModifyHunt", {
hunt_description: row.hunt_description || " ",
hunt_id: hunt_id,
}).then((response) => {
}, this.source.token).then((response) => {
this.props.updateHunts();
});
}
Expand All @@ -132,7 +142,7 @@ class HuntList extends React.Component {
api.post("v1/ModifyHunt", {
state: "ARCHIVED",
hunt_id: hunt_id,
}).then((response) => {
}, this.source.token).then((response) => {
this.props.updateHunts();
this.setState({showDeleteHuntDialog: false});

Expand All @@ -146,7 +156,7 @@ class HuntList extends React.Component {
{key: "HuntId", value: hunt_id},
{key: "DeleteAllFiles", value: "Y"},
]}}],
});
}, this.source.token);
});
}

Expand Down
16 changes: 15 additions & 1 deletion vql/functions/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,23 @@ func TimeFromAny(scope vfilter.Scope, timestamp vfilter.Any) (time.Time, error)
if !ok {
return time.Time{}, invalidTimeError
}

// Maybe it is in ns
if sec > 20000000000000000 { // 11 October 2603 in microsec
dec = sec
sec = 0

} else if sec > 20000000000000 { // 11 October 2603 in milliseconds
dec = sec * 1000
sec = 0

} else if sec > 20000000000 { // 11 October 2603 in seconds
dec = sec * 1000000
sec = 0
}
}

if sec == 0 {
if sec == 0 && dec == 0 {
return time.Time{}, invalidTimeError
}

Expand Down
111 changes: 111 additions & 0 deletions vql/server/users/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package users

import (
"context"
"crypto/rand"

"github.com/Velocidex/ordereddict"
"www.velocidex.com/golang/velociraptor/acls"
"www.velocidex.com/golang/velociraptor/api/authenticators"
"www.velocidex.com/golang/velociraptor/users"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
"www.velocidex.com/golang/vfilter"
"www.velocidex.com/golang/vfilter/arg_parser"
)

type UserCreateFunctionArgs struct {
Username string `vfilter:"required,field=user,docs=The user to create or update."`
Roles []string `vfilter:"required,field=roles,docs=List of roles to give the user."`
Password string `vfilter:"optional,field=password,docs=A password to set for the user (If not using SSO this might be needed)."`
}

type UserCreateFunction struct{}

func (self UserCreateFunction) Call(
ctx context.Context,
scope vfilter.Scope,
args *ordereddict.Dict) vfilter.Any {

err := vql_subsystem.CheckAccess(scope, acls.SERVER_ADMIN)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}

config_obj, ok := vql_subsystem.GetServerConfig(scope)
if !ok {
scope.Log("Command can only run on the server")
return vfilter.Null{}
}

arg := &UserCreateFunctionArgs{}
err = arg_parser.ExtractArgsWithContext(ctx, scope, args, arg)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}

// OK - Lets make the user now
user_record, err := users.NewUserRecord(arg.Username)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}

// Check the password if needed
authenticator, err := authenticators.NewAuthenticator(config_obj)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}

if authenticator.IsPasswordLess() {
// Set a random password on the account to prevent login if
// the authenticator is accidentally changed to a password
// based one.
password := make([]byte, 100)
_, err = rand.Read(password)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}
users.SetPassword(user_record, string(password))

} else if arg.Password == "" {
// Do not accept an empty password if we are using a password
// based authenticator.
scope.Log("Authentication requires a password but one was not provided.")
return vfilter.Null{}

} else {
users.SetPassword(user_record, arg.Password)
}

// Grant the roles to the user
err = acls.GrantRoles(config_obj, arg.Username, arg.Roles)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}

// Write the user record.
err = users.SetUser(config_obj, user_record)
if err != nil {
scope.Log("user_create: %s", err)
return vfilter.Null{}
}

return arg.Username
}

func (self UserCreateFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo {
return &vfilter.FunctionInfo{
Name: "user_create",
Doc: "Creates a new user from the server, or updates their permissions or reset their password.",
ArgType: type_map.AddType(scope, &UserCreateFunctionArgs{}),
}
}

func init() {
vql_subsystem.RegisterFunction(&UserCreateFunction{})
}
78 changes: 78 additions & 0 deletions vql/server/users/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package users

import (
"context"

"github.com/Velocidex/ordereddict"
"www.velocidex.com/golang/velociraptor/acls"
"www.velocidex.com/golang/velociraptor/datastore"
"www.velocidex.com/golang/velociraptor/paths"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
"www.velocidex.com/golang/vfilter"
"www.velocidex.com/golang/vfilter/arg_parser"
)

type UserDeleteFunctionArgs struct {
Username string `vfilter:"required,field=user,docs=The user to delete."`
}

type UserDeleteFunction struct{}

func (self UserDeleteFunction) Call(
ctx context.Context,
scope vfilter.Scope,
args *ordereddict.Dict) vfilter.Any {

err := vql_subsystem.CheckAccess(scope, acls.SERVER_ADMIN)
if err != nil {
scope.Log("user_delete: %s", err)
return vfilter.Null{}
}

config_obj, ok := vql_subsystem.GetServerConfig(scope)
if !ok {
scope.Log("Command can only run on the server")
return vfilter.Null{}
}

arg := &UserDeleteFunctionArgs{}
err = arg_parser.ExtractArgsWithContext(ctx, scope, args, arg)
if err != nil {
scope.Log("user_delete: %s", err)
return vfilter.Null{}
}

db, err := datastore.GetDB(config_obj)
if err != nil {
scope.Log("user_delete: %s", err)
return vfilter.Null{}
}

user_path_manager := paths.NewUserPathManager(arg.Username)
err = db.DeleteSubject(config_obj, user_path_manager.Path())
if err != nil {
scope.Log("user_delete: %s", err)
return vfilter.Null{}
}

// Also remove the ACLs for the user.
err = db.DeleteSubject(config_obj, user_path_manager.ACL())
if err != nil {
scope.Log("user_delete: %s", err)
return vfilter.Null{}
}

return arg.Username
}

func (self UserDeleteFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo {
return &vfilter.FunctionInfo{
Name: "user_delete",
Doc: "Deletes a user from the server.",
ArgType: type_map.AddType(scope, &UserDeleteFunctionArgs{}),
}
}

func init() {
vql_subsystem.RegisterFunction(&UserDeleteFunction{})
}
2 changes: 1 addition & 1 deletion vql/server/users.go → vql/server/users/users.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package server
package users

import (
"context"
Expand Down
1 change: 1 addition & 0 deletions vql_plugins/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ import (
_ "www.velocidex.com/golang/velociraptor/vql/server/hunts"
_ "www.velocidex.com/golang/velociraptor/vql/server/notebooks"
_ "www.velocidex.com/golang/velociraptor/vql/server/timelines"
_ "www.velocidex.com/golang/velociraptor/vql/server/users"
)

0 comments on commit 3a37c25

Please sign in to comment.