Complete validation for debora

This commit is contained in:
Jae Kwon 2015-04-13 13:14:37 -07:00
parent 5b1c1eb0e0
commit 0ffbff108f
4 changed files with 216 additions and 69 deletions

58
cmd/debora/auth.go Normal file
View File

@ -0,0 +1,58 @@
package main
import acm "github.com/tendermint/tendermint/account"
type Validator struct {
VotingPower uint64
PubKey acm.PubKey
}
func validate(signBytes []byte, validators []Validator, signatures []acm.Signature) bool {
var signedPower uint64
var totalPower uint64
for i, val := range validators {
if val.PubKey.VerifyBytes(signBytes, signatures[i]) {
signedPower += val.VotingPower
totalPower += val.VotingPower
} else {
totalPower += val.VotingPower
}
}
return signedPower > totalPower*2/3
}
/*
NOTE: Not used, just here in case we want it later.
func ValidateHandler(handler http.Handler, validators []Validator) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sigStrs := r.Header[http.CanonicalHeaderKey("debora-signatures")]
log.Debug("Woot", "sigstrs", sigStrs, "len", len(sigStrs))
// from https://medium.com/@xoen/golang-read-from-an-io-readwriter-without-loosing-its-content-2c6911805361
// Read the content
var bodyBytes []byte
if r.Body != nil {
bodyBytes, _ = ioutil.ReadAll(r.Body)
}
// Restore the io.ReadCloser to its original state
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
// Get body string
bodyString := string(bodyBytes)
// Also read the path+"?"+query
pathQuery := fmt.Sprintf("%v?%v", r.URL.Path, r.URL.RawQuery)
// Concatenate into tuple of two strings.
tuple := struct {
Body string
Path string
}{bodyString, pathQuery}
// Get sign bytes
signBytes := binary.BinaryBytes(tuple)
// Validate the sign bytes.
//if validate(signBytes, validators,
log.Debug("Should sign", "bytes", signBytes)
// If validation fails
// XXX
// If validation passes
handler.ServeHTTP(w, r)
})
}
*/

View File

@ -1,11 +1,15 @@
package main
// TODO: Nonrepudiable command log
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"reflect"
"sync"
acm "github.com/tendermint/tendermint/account"
@ -16,28 +20,23 @@ import (
)
var Routes = map[string]*rpc.RPCFunc{
"RunProcess": rpc.NewRPCFunc(RunProcess, []string{"wait", "label", "execPath", "args"}),
"ListProcesses": rpc.NewRPCFunc(ListProcesses, []string{}),
"StopProcess": rpc.NewRPCFunc(StopProcess, []string{"label", "kill"}),
// NOTE: also, two special non-JSONRPC routes called
// "download" and "upload".
}
type Validator struct {
VotingPower uint64
PubKey acm.PubKey
"run_auth_command": rpc.NewRPCFunc(Run, []string{"auth_command"}),
// NOTE: also, two special non-JSONRPC routes called "download" and "upload"
}
type Options struct {
Validators []Validator
ListenAddress string
StartNonce uint64
}
// Global instance
var debora = struct {
mtx sync.Mutex
processes map[string]*pcm.Process
}{sync.Mutex{}, make(map[string]*pcm.Process)}
mtx sync.Mutex
processes map[string]*pcm.Process
validators []Validator
nonce uint64
}{sync.Mutex{}, make(map[string]*pcm.Process), nil, 0}
func main() {
@ -51,11 +50,14 @@ func main() {
if err != nil {
panic(Fmt("Error parsing input: %v", err))
}
debora.nonce = options.StartNonce
debora.validators = options.Validators
// Debug.
fmt.Printf("Validators: %v\n", options.Validators)
fmt.Printf("Options: %v\n", options)
fmt.Printf("Debora: %v\n", debora)
// start rpc server.
// Start rpc server.
mux := http.NewServeMux()
mux.HandleFunc("/download", ServeFile)
// TODO: mux.HandleFunc("/upload", UploadFile)
@ -68,7 +70,102 @@ func main() {
}
//------------------------------------------------------------------------------
// RPC functions
// RPC main function
func Run(authCommand AuthCommand) (interface{}, error) {
command, err := parseValidateCommand(authCommand)
if err != nil {
return nil, err
}
log.Info(Fmt("Run() received command %v", reflect.TypeOf(command)))
// Issue command
switch c := command.(type) {
case CommandRunProcess:
return RunProcess(c.Wait, c.Label, c.ExecPath, c.Args, c.Input)
case CommandStopProcess:
return StopProcess(c.Label, c.Kill)
case CommandListProcesses:
return ListProcesses()
default:
return nil, errors.New("Invalid endpoint for command")
}
}
func parseValidateCommandStr(authCommandStr string) (Command, error) {
var err error
authCommand := binary.ReadJSON(AuthCommand{}, []byte(authCommandStr), &err).(AuthCommand)
if err != nil {
fmt.Printf("Failed to parse auth_command")
return nil, errors.New("AuthCommand parse error")
}
return parseValidateCommand(authCommand)
}
func parseValidateCommand(authCommand AuthCommand) (Command, error) {
commandJSONStr := authCommand.CommandJSONStr
signatures := authCommand.Signatures
// Validate commandJSONStr
if !validate([]byte(commandJSONStr), debora.validators, signatures) {
fmt.Printf("Failed validation attempt")
return nil, errors.New("Validation error")
}
// Parse command
var err error
command := binary.ReadJSON(NoncedCommand{}, []byte(commandJSONStr), &err).(NoncedCommand)
if err != nil {
fmt.Printf("Failed to parse command")
return nil, errors.New("Command parse error")
}
// Prevent replays
if debora.nonce+1 != command.Nonce {
return nil, errors.New("Replay error")
} else {
debora.nonce += 1
}
return command.Command, nil
}
type AuthCommand struct {
CommandJSONStr string
Signatures []acm.Signature
}
type NoncedCommand struct {
Nonce uint64
Command
}
type Command interface{}
// for binary.readReflect
var _ = binary.RegisterInterface(
struct{ Command }{},
binary.ConcreteType{CommandRunProcess{}},
binary.ConcreteType{CommandStopProcess{}},
binary.ConcreteType{CommandListProcesses{}},
binary.ConcreteType{CommandServeFile{}},
)
const (
typeByteRunProcess = 0x01
typeByteStopProcess = 0x02
typeByteListProcesses = 0x03
typeByteServeFile = 0x04
)
//------------------------------------------------------------------------------
// RPC base commands
// WARNING Not validated, do not export to routes.
type CommandRunProcess struct {
Wait bool
Label string
ExecPath string
Args []string
Input string
}
func (_ CommandRunProcess) TypeByte() byte { return typeByteRunProcess }
type ResponseRunProcess struct {
}
@ -98,24 +195,12 @@ func RunProcess(wait bool, label string, execPath string, args []string, input s
//--------------------------------------
type ResponseListProcesses struct {
Processes []*pcm.Process
type CommandStopProcess struct {
Label string
Kill bool
}
func ListProcesses() (*ResponseListProcesses, error) {
var procs = []*pcm.Process{}
debora.mtx.Lock()
for _, proc := range debora.processes {
procs = append(procs, proc)
}
debora.mtx.Unlock()
return &ResponseListProcesses{
Processes: procs,
}, nil
}
//--------------------------------------
func (_ CommandStopProcess) TypeByte() byte { return typeByteStopProcess }
type ResponseStopProcess struct {
}
@ -133,10 +218,49 @@ func StopProcess(label string, kill bool) (*ResponseStopProcess, error) {
return &ResponseStopProcess{}, err
}
//--------------------------------------
type CommandListProcesses struct{}
func (_ CommandListProcesses) TypeByte() byte { return typeByteListProcesses }
type ResponseListProcesses struct {
Processes []*pcm.Process
}
func ListProcesses() (*ResponseListProcesses, error) {
var procs = []*pcm.Process{}
debora.mtx.Lock()
for _, proc := range debora.processes {
procs = append(procs, proc)
}
debora.mtx.Unlock()
return &ResponseListProcesses{
Processes: procs,
}, nil
}
//------------------------------------------------------------------------------
type CommandServeFile struct {
Path string
}
func (_ CommandServeFile) TypeByte() byte { return typeByteServeFile }
func ServeFile(w http.ResponseWriter, req *http.Request) {
path := req.FormValue("path")
authCommandStr := req.FormValue("auth_command")
command, err := parseValidateCommandStr(authCommandStr)
if err != nil {
http.Error(w, Fmt("Invalid command: %v", err), 400)
}
serveCommand, ok := command.(CommandServeFile)
if !ok {
http.Error(w, "Invalid command", 400)
}
path := serveCommand.Path
if path == "" {
http.Error(w, "Must specify path", 400)
return

View File

@ -152,8 +152,7 @@ func (n *Node) StartRPC() {
mux := http.NewServeMux()
rpc.RegisterEventsHandler(mux, n.evsw)
rpc.RegisterRPCFuncs(mux, core.Routes)
handler := rpc.AuthenticateHandler(mux)
rpc.StartHTTPServer(listenAddr, handler)
rpc.StartHTTPServer(listenAddr, mux)
}
func (n *Node) Switch() *p2p.Switch {

View File

@ -4,7 +4,6 @@ package rpc
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"runtime/debug"
"time"
@ -38,39 +37,6 @@ func WriteRPCResponse(w http.ResponseWriter, res RPCResponse) {
//-----------------------------------------------------------------------------
func AuthenticateHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// from https://medium.com/@xoen/golang-read-from-an-io-readwriter-without-loosing-its-content-2c6911805361
// Read the content
var bodyBytes []byte
if r.Body != nil {
bodyBytes, _ = ioutil.ReadAll(r.Body)
}
// Restore the io.ReadCloser to its original state
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
// Get body string
bodyString := string(bodyBytes)
// Also read the path+"?"+query
pathQuery := Fmt("%v?%v", r.URL.Path, r.URL.RawQuery)
// Concatenate into tuple
tuple := struct {
Body string
Path string
}{bodyString, pathQuery}
// Get sign bytes
signBytes := binary.BinaryBytes(tuple)
// Validate the sign bytes.
// XXX
log.Debug("Should sign", "bytes", signBytes)
// If validation fails
// XXX
// If validation passes
handler.ServeHTTP(w, r)
})
}
//-----------------------------------------------------------------------------
// Wraps an HTTP handler, adding error logging.
// If the inner function panics, the outer function recovers, logs, sends an
// HTTP 500 error response.