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:
parent
df30a0d689
commit
92222127a6
7 changed files with 413 additions and 232 deletions
|
@ -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}, ®)
|
||||
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}, ®)
|
||||
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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue