Skip to content

Commit

Permalink
Implemented config generation commands.
Browse files Browse the repository at this point in the history
Velociraptor can now generate its own config, as well as rotate the
server keys. Velociraptor client verifies server keys are signed by CA
and serial numbers are incremented properly.
  • Loading branch information
scudette committed Aug 6, 2018
1 parent 47b7a1c commit a07d65c
Show file tree
Hide file tree
Showing 16 changed files with 837 additions and 549 deletions.
284 changes: 154 additions & 130 deletions api/proto/config.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions api/proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ message ClientConfig {
uint64 hunt_last_timestamp = 13 [(sem_type) = {
description: "The last hunt timestamp this client ran."
}];

uint64 last_server_serial_number = 14 [(sem_type) = {
description: "The last certificate serial number we saw from the server. Clients refuse to connect to servers with older serial numbers as part of server key rotation protocol."
}];
}

message APIConfig {
Expand Down Expand Up @@ -115,6 +119,10 @@ message FrontendConfig {
uint32 client_lease_time = 5 [(sem_type) = {
description: "How long to lease client messages."
}];

string dns_name = 6 [(sem_type) = {
description: "The DNS name of the frontend."
}];
}


Expand Down
86 changes: 86 additions & 0 deletions bin/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"fmt"
"gopkg.in/alecthomas/kingpin.v2"
"www.velocidex.com/golang/velociraptor/config"
"www.velocidex.com/golang/velociraptor/crypto"
"www.velocidex.com/golang/velociraptor/logging"
)

func doShowConfig() {
config_obj, err := get_config(*config_path)
kingpin.FatalIfError(err, "Unable to load config.")

res, err := config.Encode(config_obj)
if err != nil {
kingpin.FatalIfError(err, "Unable to encode config.")
}
fmt.Printf("%v", string(res))
}

func doGenerateConfig() {
config_obj := config.GetDefaultConfig()
logger := logging.NewLogger(config_obj)
ca_bundle, err := crypto.GenerateCACert(2048)
if err != nil {
logger.Error("Unable to create CA cert", err)
return
}

config_obj.Client.CaCertificate = ca_bundle.Cert
config_obj.CA.PrivateKey = ca_bundle.PrivateKey

frontend_cert, err := crypto.GenerateServerCert(config_obj)
if err != nil {
logger.Error("Unable to create Frontend cert", err)
return
}

config_obj.Frontend.Certificate = frontend_cert.Cert
config_obj.Frontend.PrivateKey = frontend_cert.PrivateKey

// Users have to updated the following fields.
config_obj.Client.ServerUrls = []string{"http://localhost:8000/"}

res, err := config.Encode(config_obj)
if err != nil {
logger.Error("Unable to create CA cert", err)
return
}
fmt.Printf("%v", string(res))
}

func doRotateKeyConfig() {
config_obj, err := get_config(*config_path)
kingpin.FatalIfError(err, "Unable to load config.")
logger := logging.NewLogger(config_obj)
frontend_cert, err := crypto.GenerateServerCert(config_obj)
if err != nil {
logger.Error("Unable to create Frontend cert", err)
return
}

config_obj.Frontend.Certificate = frontend_cert.Cert
config_obj.Frontend.PrivateKey = frontend_cert.PrivateKey

res, err := config.Encode(config_obj)
if err != nil {
kingpin.FatalIfError(err, "Unable to encode config.")
}
fmt.Printf("%v", string(res))
}

func doDumpClientConfig() {
config_obj, err := get_config(*config_path)
kingpin.FatalIfError(err, "Unable to load config.")

client_config := config.NewClientConfig()
client_config.Client = config_obj.Client

res, err := config.Encode(client_config)
if err != nil {
kingpin.FatalIfError(err, "Unable to encode config.")
}
fmt.Printf("%v", string(res))
}
17 changes: 14 additions & 3 deletions bin/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,30 @@ var (
healthy int32
)

func validateConfig(configuration *config.Config) error {
func validateServerConfig(configuration *config.Config) error {
if configuration.Frontend.Certificate == "" {
return errors.New("Configuration does not specify a frontend certificate.")
}

return nil
}

func get_config(config_path string) (*config.Config, error) {
func get_server_config(config_path string) (*config.Config, error) {
config_obj := config.GetDefaultConfig()
err := config.LoadConfig(config_path, config_obj)
if err == nil {
err = validateConfig(config_obj)
err = validateServerConfig(config_obj)
}

return config_obj, err
}

func get_config(config_path string) (*config.Config, error) {
config_obj := config.GetDefaultConfig()
err := config.LoadConfig(config_path, config_obj)
return config_obj, err
}

func start_frontend(config_obj *config.Config) {
server_obj, err := server.NewServer(config_obj)
kingpin.FatalIfError(err, "Unable to create server")
Expand Down Expand Up @@ -135,6 +141,11 @@ func control(server_obj *server.Server) http.Handler {
}

message_info, err := server_obj.Decrypt(req.Context(), body)
if err != nil {
server_obj.Error("Unable to decrypt body", err)
http.Error(w, "", http.StatusServiceUnavailable)
return
}
message_info.RemoteAddr = req.RemoteAddr

// Very few Unauthenticated client messages are valid
Expand Down
185 changes: 45 additions & 140 deletions bin/main.go
Original file line number Diff line number Diff line change
@@ -1,180 +1,85 @@
package main

import (
"context"
"encoding/json"
"fmt"
"github.com/olekukonko/tablewriter"
"gopkg.in/alecthomas/kingpin.v2"
"log"
"os"
"strings"
"www.velocidex.com/golang/velociraptor/api"
"www.velocidex.com/golang/velociraptor/inspect"
"www.velocidex.com/golang/velociraptor/utils"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
"www.velocidex.com/golang/vfilter"
)

var (
app = kingpin.New("velociraptor", "An advanced incident response agent.")
config_path = app.Flag("config", "The configuration file.").String()
// Command line interface for VQL commands.
query = kingpin.Command("query", "Run a VQL query")
queries = query.Arg("query", "The VQL Query to run.").
query = app.Command("query", "Run a VQL query")
queries = query.Arg("queries", "The VQL Query to run.").
Required().Strings()
format = query.Flag("format", "Output format to use.").
Default("json").Enum("text", "json")
dump_dir = query.Flag("dump_dir", "Directory to dump output files.").
Default(".").String()

explain = kingpin.Command("explain", "Explain the output from a plugin")
explain = app.Command("explain", "Explain the output from a plugin")
explain_plugin = explain.Arg("plugin", "Plugin to explain").Required().String()

// Run the client.
client = kingpin.Command("client", "Run the velociraptor client")
client_config_path = client.Arg("config", "The client's config file.").String()
show_config = client.Flag("show_config", "Display the client's configuration").Bool()
client = app.Command("client", "Run the velociraptor client")

// Run the server.
frontend = kingpin.Command("frontend", "Run the frontend.")
fe_config_path = frontend.Arg("config", "The Configuration file").String()
frontend = app.Command("frontend", "Run the frontend and GUI.")

// Inspect the filestore
inspect_command = kingpin.Command("inspect", "Inspect datastore files.")
inspect_config_path = inspect_command.Arg("config", "The Configuration file").
Required().String()
inspect_filename = inspect_command.Arg("filename", "The filename from the filestore").
Required().String()
inspect_command = app.Command(
"inspect", "Inspect datastore files.")
inspect_filename = inspect_command.Arg(
"filename", "The filename from the filestore").
Required().String()

config_command = app.Command(
"config", "Manipulate the configuration.")
config_show_command = config_command.Command(
"show", "Show the current config.")
config_client_command = config_command.Command(
"client", "Dump the client's config file.")
config_generate_command = config_command.Command(
"generate",
"Generate a new config file to stdout (with new keys).")
config_rotate_server_key = config_command.Command(
"rotate_key",
"Generate a new config file with a rotates server key.")
)

func outputJSON(scope *vfilter.Scope, vql *vfilter.VQL) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

result_chan := vfilter.GetResponseChannel(vql, ctx, scope, 10)
for {
result, ok := <-result_chan
if !ok {
return
}
os.Stdout.Write(result.Payload)
}
}

func hard_wrap(text string, colBreak int) string {
text = strings.TrimSpace(text)
wrapped := ""
var i int
for i = 0; len(text[i:]) > colBreak; i += colBreak {

wrapped += text[i:i+colBreak] + "\n"

}
wrapped += text[i:]

return wrapped
}

func evalQuery(scope *vfilter.Scope, vql *vfilter.VQL) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

output_chan := vql.Eval(ctx, scope)
table := tablewriter.NewWriter(os.Stdout)
defer table.Render()

columns := vql.Columns(scope)
table.SetHeader(*columns)
table.SetCaption(true, vql.ToString(scope))
func main() {
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
case "config show":
doShowConfig()

for {
row, ok := <-output_chan
if !ok {
return
}
string_row := []string{}
if len(*columns) == 0 {
members := scope.GetMembers(row)
table.SetHeader(members)
columns = &members
}
case "config generate":
doGenerateConfig()

for _, key := range *columns {
cell := ""
value, pres := scope.Associative(row, key)
if pres && !utils.IsNil(value) {
switch t := value.(type) {
case vfilter.StringProtocol:
cell = t.ToString(scope)
case fmt.Stringer:
cell = hard_wrap(t.String(), 30)
case []byte:
cell = hard_wrap(string(t), 30)
case string:
cell = hard_wrap(t, 30)
default:
if k, err := json.Marshal(value); err == nil {
cell = hard_wrap(string(k), 30)
}
}
}
string_row = append(string_row, cell)
}
case "config rotate_key":
doRotateKeyConfig()

table.Append(string_row)
}
}
case "config client":
doDumpClientConfig()

func doExplain(plugin string) {
result := vfilter.NewDict()
type_map := make(vfilter.TypeMap)
scope := vql_subsystem.MakeScope()
if pslist_info, pres := scope.Info(&type_map, plugin); pres {
result.Set(plugin+"_info", pslist_info)
result.Set("type_map", type_map)
}
case client.FullCommand():
RunClient(config_path)

s, err := json.MarshalIndent(result, "", " ")
if err == nil {
os.Stdout.Write(s)
}
}

func main() {
switch kingpin.Parse() {
case "client":
RunClient(client_config_path)

case "explain":
case explain.FullCommand():
doExplain(*explain_plugin)

case "query":
env := vfilter.NewDict().
Set("$uploader", &vql_subsystem.FileBasedUploader{*dump_dir})
scope := vql_subsystem.MakeScope().AppendVars(env)

scope.Logger = log.New(os.Stderr, "velociraptor: ", log.Lshortfile)
for _, query := range *queries {
vql, err := vfilter.Parse(query)
if err != nil {
kingpin.FatalIfError(err, "Unable to parse VQL Query")
}

switch *format {
case "text":
evalQuery(scope, vql)
case "json":
outputJSON(scope, vql)
}
}
case query.FullCommand():
doQuery()

case "repack":
case repack.FullCommand():
err := RepackClient(*repack_binary, *repack_config)
if err != nil {
kingpin.FatalIfError(err, "Can not repack client")
}

case "frontend":
config_obj, err := get_config(*fe_config_path)
case frontend.FullCommand():
config_obj, err := get_server_config(*config_path)
kingpin.FatalIfError(err, "Unable to load config file")
go func() {
err := api.StartServer(config_obj)
Expand All @@ -187,8 +92,8 @@ func main() {

start_frontend(config_obj)

case "inspect":
config_obj, err := get_config(*inspect_config_path)
case inspect_command.FullCommand():
config_obj, err := get_server_config(*config_path)
kingpin.FatalIfError(err, "Unable to load config file")
err = inspect.Inspect(config_obj, *inspect_filename)
kingpin.FatalIfError(err, "Unable to parse datastore item.")
Expand Down
Loading

0 comments on commit a07d65c

Please sign in to comment.