Add basic account security

This adds a secret token associated with each account

	The token must then be sent with follow-up requests to ensure they get accepted

	This is _very_ basic security, and without TLS is completely vulnerable to MITM attacks, as well as brute force guessing (though it'd take a while to guess the a correct UUID)
This commit is contained in:
Marc Di Luzio 2020-07-07 22:20:23 +01:00
parent df30a0d689
commit 92222127a6
7 changed files with 413 additions and 232 deletions

View file

@ -80,47 +80,69 @@ func TestServer_Register(t *testing.T) {
func TestServer_Command(t *testing.T) {
acc := uuid.New().String()
err := serv.Request("POST", "register", &rove.RegisterRequest{Name: acc}, &rove.RegisterResponse{})
var resp rove.RegisterResponse
err := serv.Request("POST", "register", &rove.RegisterRequest{Name: acc}, &resp)
assert.NoError(t, err, "First register attempt should pass")
err = serv.Request("POST", "command", &rove.CommandRequest{
Account: acc,
req := &rove.CommandRequest{
Account: &rove.Account{
Name: resp.Account.Name,
},
Commands: []*rove.Command{
{
Command: "move",
Bearing: "NE",
},
},
}, &rove.CommandResponse{})
assert.NoError(t, err, "Commands should should pass")
}
assert.Error(t, serv.Request("POST", "command", req, &rove.CommandResponse{}), "Commands should fail with no secret")
req.Account.Secret = resp.Account.Secret
assert.NoError(t, serv.Request("POST", "command", req, &rove.CommandResponse{}), "Commands should pass")
}
func TestServer_Radar(t *testing.T) {
acc := uuid.New().String()
err := serv.Request("POST", "register", &rove.RegisterRequest{Name: acc}, &rove.RegisterResponse{})
var reg rove.RegisterResponse
err := serv.Request("POST", "register", &rove.RegisterRequest{Name: acc}, &reg)
assert.NoError(t, err, "First register attempt should pass")
resp := &rove.RadarResponse{}
err = serv.Request("POST", "radar", &rove.RadarRequest{
Account: acc,
}, resp)
assert.NoError(t, err, "Radar sould pass should pass")
req := &rove.RadarRequest{
Account: &rove.Account{
Name: reg.Account.Name,
},
}
assert.Error(t, serv.Request("POST", "radar", req, resp), "Radar should fail without secret")
req.Account.Secret = reg.Account.Secret
assert.NoError(t, serv.Request("POST", "radar", req, resp), "Radar should pass")
assert.NotZero(t, resp.Range, "Radar should return valid range")
w := int(resp.Range*2 + 1)
assert.Equal(t, w*w, len(resp.Tiles), "radar should return correct number of tiles")
assert.Equal(t, w*w, len(resp.Objects), "radar should return correct number of objects")
}
func TestServer_Rover(t *testing.T) {
func TestServer_Status(t *testing.T) {
acc := uuid.New().String()
err := serv.Request("POST", "register", &rove.RegisterRequest{Name: acc}, &rove.RegisterResponse{})
var reg rove.RegisterResponse
err := serv.Request("POST", "register", &rove.RegisterRequest{Name: acc}, &reg)
assert.NoError(t, err, "First register attempt should pass")
resp := &rove.StatusResponse{}
err = serv.Request("POST", "status", &rove.StatusRequest{
Account: acc,
}, resp)
assert.NoError(t, err, "Rover sould pass should pass")
req := &rove.StatusRequest{
Account: &rove.Account{
Name: reg.Account.Name,
},
}
assert.Error(t, serv.Request("POST", "status", req, resp), "Status should fail without secret")
req.Account.Secret = reg.Account.Secret
assert.NoError(t, serv.Request("POST", "status", req, resp), "Status should pass")
assert.NotZero(t, resp.Range, "Rover should return valid range")
assert.NotZero(t, len(resp.Name), "Rover should return valid name")
assert.NotZero(t, resp.Position, "Rover should return valid position")

View file

@ -34,7 +34,7 @@ func (s *Server) Register(ctx context.Context, req *rove.RegisterRequest) (*rove
return nil, fmt.Errorf("empty account name")
}
if _, err := s.accountant.RegisterAccount(req.Name); err != nil {
if acc, err := s.accountant.RegisterAccount(req.Name); err != nil {
return nil, err
} else if _, err := s.SpawnRoverForAccount(req.Name); err != nil {
@ -42,17 +42,26 @@ func (s *Server) Register(ctx context.Context, req *rove.RegisterRequest) (*rove
} else if err := s.SaveWorld(); err != nil {
return nil, fmt.Errorf("internal server error when saving world: %s", err)
}
return &rove.RegisterResponse{}, nil
} else {
return &rove.RegisterResponse{
Account: &rove.Account{
Name: acc.Name,
Secret: acc.Data["secret"],
},
}, nil
}
}
// Status returns rover information for a gRPC request
func (s *Server) Status(ctx context.Context, req *rove.StatusRequest) (response *rove.StatusResponse, err error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account name")
if valid, err := s.accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil {
return nil, err
} else if resp, err := s.accountant.GetValue(req.Account, "rover"); err != nil {
} else if !valid {
return nil, fmt.Errorf("Secret incorrect for account %s", req.Account.Name)
} else if resp, err := s.accountant.GetValue(req.Account.Name, "rover"); err != nil {
return nil, err
} else if rover, err := s.world.GetRover(resp); err != nil {
@ -101,13 +110,16 @@ func (s *Server) Status(ctx context.Context, req *rove.StatusRequest) (response
// Radar returns the radar information for a rover
func (s *Server) Radar(ctx context.Context, req *rove.RadarRequest) (*rove.RadarResponse, error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account name")
if valid, err := s.accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil {
return nil, err
} else if !valid {
return nil, fmt.Errorf("Secret incorrect for account %s", req.Account.Name)
}
response := &rove.RadarResponse{}
resp, err := s.accountant.GetValue(req.Account, "rover")
resp, err := s.accountant.GetValue(req.Account.Name, "rover")
if err != nil {
return nil, err
@ -128,10 +140,14 @@ func (s *Server) Radar(ctx context.Context, req *rove.RadarRequest) (*rove.Radar
// Command issues commands to the world based on a gRPC request
func (s *Server) Command(ctx context.Context, req *rove.CommandRequest) (*rove.CommandResponse, error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account")
if valid, err := s.accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil {
return nil, err
} else if !valid {
return nil, fmt.Errorf("Secret incorrect for account %s", req.Account.Name)
}
resp, err := s.accountant.GetValue(req.Account, "rover")
resp, err := s.accountant.GetValue(req.Account.Name, "rover")
if err != nil {
return nil, err
}

View file

@ -48,7 +48,8 @@ const gRPCport = 9090
// Account stores data for an account
type Account struct {
Name string `json:"name"`
Name string `json:"name"`
Secret string `json:"secret"`
}
// Config is used to store internal data
@ -114,10 +115,12 @@ func SaveConfig(config Config) error {
return nil
}
// verifyID will verify an account ID
func verifyID(id string) error {
if len(id) == 0 {
// checkAccount will verify an account ID
func checkAccount(a Account) error {
if len(a.Name) == 0 {
return fmt.Errorf("no account ID set, must register first")
} else if len(a.Secret) == 0 {
return fmt.Errorf("empty account secret, must register first")
}
return nil
}
@ -181,26 +184,27 @@ func InnerMain(command string, args ...string) error {
}
case "register":
if len(args) == 0 {
if len(args) == 0 || len(args[0]) == 0 {
return fmt.Errorf("must pass name to 'register'")
}
name := args[0]
d := rove.RegisterRequest{
Name: name,
}
_, err := client.Register(ctx, &d)
resp, err := client.Register(ctx, &rove.RegisterRequest{
Name: args[0],
})
switch {
case err != nil:
return err
default:
fmt.Printf("Registered account with id: %s\n", name)
config.Account.Name = name
fmt.Printf("Registered account with id: %s\n", resp.Account.Name)
config.Account.Name = resp.Account.Name
config.Account.Secret = resp.Account.Secret
}
case "command":
if len(args) == 0 {
if err := checkAccount(config.Account); err != nil {
return err
} else if len(args) == 0 {
return fmt.Errorf("must pass commands to 'commands'")
}
@ -231,16 +235,14 @@ func InnerMain(command string, args ...string) error {
}
}
d := rove.CommandRequest{
Account: config.Account.Name,
_, err := client.Command(ctx, &rove.CommandRequest{
Account: &rove.Account{
Name: config.Account.Name,
Secret: config.Account.Secret,
},
Commands: commands,
}
})
if err := verifyID(d.Account); err != nil {
return err
}
_, err := client.Command(ctx, &d)
switch {
case err != nil:
return err
@ -250,12 +252,17 @@ func InnerMain(command string, args ...string) error {
}
case "radar":
dat := rove.RadarRequest{Account: config.Account.Name}
if err := verifyID(dat.Account); err != nil {
if err := checkAccount(config.Account); err != nil {
return err
}
response, err := client.Radar(ctx, &dat)
response, err := client.Radar(ctx, &rove.RadarRequest{
Account: &rove.Account{
Name: config.Account.Name,
Secret: config.Account.Secret,
},
})
switch {
case err != nil:
return err
@ -282,11 +289,16 @@ func InnerMain(command string, args ...string) error {
}
case "status":
req := rove.StatusRequest{Account: config.Account.Name}
if err := verifyID(req.Account); err != nil {
if err := checkAccount(config.Account); err != nil {
return err
}
response, err := client.Status(ctx, &req)
response, err := client.Status(ctx, &rove.StatusRequest{
Account: &rove.Account{
Name: config.Account.Name,
Secret: config.Account.Secret,
},
})
switch {
case err != nil: