From fc3faafec9f80aaeb9875bb3ca9747040fd173fd Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Fri, 4 Aug 2023 10:52:07 +0545 Subject: [PATCH] impl: bcrypt [skip ci] --- agent/agent.go | 41 ++++++++++++++++++++++++++++++ agent/controllers.go | 60 +++++++++++--------------------------------- api/agent.go | 6 ++--- auth/middleware.go | 21 +++++++++++++--- db/people.go | 4 +-- rbac/init.go | 1 + 6 files changed, 80 insertions(+), 53 deletions(-) create mode 100644 agent/agent.go diff --git a/agent/agent.go b/agent/agent.go new file mode 100644 index 000000000..7d7f3aa99 --- /dev/null +++ b/agent/agent.go @@ -0,0 +1,41 @@ +package agent + +import ( + "fmt" + + "github.com/flanksource/incident-commander/api" + "github.com/flanksource/incident-commander/db" + "github.com/flanksource/incident-commander/rbac" + "golang.org/x/crypto/bcrypt" +) + +func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.GeneratedAgent, error) { + username, password, err := genUsernamePassword() + if err != nil { + return nil, fmt.Errorf("failed to generate username and password: %w", err) + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return nil, fmt.Errorf("failed to hash password: %w", err) + } + + id, err := db.CreatePerson(ctx, username, string(hashedPassword)) + if err != nil { + return nil, fmt.Errorf("failed to create a new person: %w", err) + } + + if _, err := rbac.Enforcer.AddRoleForUser(id.String(), "agent"); err != nil { + return nil, fmt.Errorf("failed to add 'agent' role to the new person: %w", err) + } + + if err := db.CreateAgent(ctx, body.Name, &id, body.Properties); err != nil { + return nil, fmt.Errorf("failed to create a new agent: %w", err) + } + + return &api.GeneratedAgent{ + ID: id.String(), + Username: username, + AccessToken: password, + }, nil +} diff --git a/agent/controllers.go b/agent/controllers.go index d0c1ef326..ad2f967e0 100644 --- a/agent/controllers.go +++ b/agent/controllers.go @@ -1,18 +1,17 @@ package agent import ( - "crypto/rand" - "encoding/binary" "encoding/json" "fmt" "net/http" + "github.com/flanksource/commons/logger" + crand "github.com/flanksource/commons/rand" "github.com/flanksource/incident-commander/api" - "github.com/flanksource/incident-commander/db" - "github.com/flanksource/incident-commander/rbac" "github.com/labstack/echo/v4" ) +// GenerateAgent creates a new person and a new agent and associates them. func GenerateAgent(c echo.Context) error { ctx := c.(*api.Context) @@ -21,54 +20,25 @@ func GenerateAgent(c echo.Context) error { return c.JSON(http.StatusBadRequest, api.HTTPError{Error: err.Error()}) } - var ( - username = fmt.Sprintf("agent-%s", generateRandomString(8)) - password = generateRandomString(32) - ) - - // TODO: Only if unauthenticated, we need to create a user - id, err := db.CreatePerson(ctx, username, password) + agent, err := generateAgent(ctx, body) if err != nil { - return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) - } - - if ok, err := rbac.Enforcer.AddRoleForUser(id.String(), "agent"); !ok { - return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) - } else if err != nil { - return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) - } - - if err := db.CreateAgent(ctx, body.Name, &id, body.Properties); err != nil { - return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) + logger.Errorf("failed to generate a new agent: %v", err) + c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) } - return c.JSON(http.StatusCreated, api.GeneratedAgent{ - ID: id.String(), - Username: username, - Password: password, - }) + return c.JSON(http.StatusCreated, agent) } -// generateRandomString generates a random alphanumeric string of the given length. -func generateRandomString(length int) string { - const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - result := make([]byte, length) - for i := range result { - val, err := generateRandomInt(len(letters)) - if err != nil { - panic(err) // Handle error in a way that suits your needs - } - result[i] = letters[val] +func genUsernamePassword() (username, password string, err error) { + username, err = crand.GenerateRandHex(8) + if err != nil { + return "", "", err } - return string(result) -} -// generateRandomInt generates a random integer up to max. -func generateRandomInt(max int) (int, error) { - var n uint32 - err := binary.Read(rand.Reader, binary.LittleEndian, &n) + password, err = crand.GenerateRandHex(32) if err != nil { - return 0, err + return "", "", err } - return int(n) % max, nil + + return fmt.Sprintf("agent-%s", username), password, nil } diff --git a/api/agent.go b/api/agent.go index acd192ffa..318e4ccc6 100644 --- a/api/agent.go +++ b/api/agent.go @@ -6,7 +6,7 @@ type GenerateAgentRequest struct { } type GeneratedAgent struct { - ID string `json:"id"` - Username string `json:"username"` - Password string `json:"password"` + ID string `json:"id"` + Username string `json:"username"` + AccessToken string `json:"access_token"` } diff --git a/auth/middleware.go b/auth/middleware.go index 8ed9dba42..7972b7477 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -17,6 +17,7 @@ import ( "github.com/labstack/echo/v4" client "github.com/ory/client-go" "github.com/patrickmn/go-cache" + "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) @@ -80,9 +81,19 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc { } } -func (k *kratosMiddleware) validateAccessToken(ctx context.Context, password string) (*models.AccessToken, error) { +func (k *kratosMiddleware) validateAccessToken(ctx context.Context, username, password string) (*models.AccessToken, error) { + query := ` +SELECT + access_tokens.* +FROM + access_tokens + LEFT JOIN people ON access_tokens.person_id = people.id +WHERE + people.name = ? + AND access_tokens.expires_at > NOW()` + var acessToken models.AccessToken - if err := k.db.Raw("SELECT * FROM access_tokens WHERE value = ? AND expires_at > NOW()", password).First(&acessToken).Error; err != nil { + if err := k.db.Raw(query, username).First(&acessToken).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } @@ -90,6 +101,10 @@ func (k *kratosMiddleware) validateAccessToken(ctx context.Context, password str return nil, err } + if err := bcrypt.CompareHashAndPassword([]byte(acessToken.Value), []byte(password)); err != nil { + return nil, nil + } + return &acessToken, nil } @@ -102,7 +117,7 @@ func (k *kratosMiddleware) validateSession(r *http.Request) (*client.Session, er if username, password, ok := r.BasicAuth(); ok { if strings.HasPrefix(username, "agent-") { - accessToken, err := k.validateAccessToken(r.Context(), password) + accessToken, err := k.validateAccessToken(r.Context(), username, password) if err != nil { return nil, fmt.Errorf("failed to validate agent: %w", err) } else if accessToken == nil { diff --git a/db/people.go b/db/people.go index af8529a8d..057f15a6d 100644 --- a/db/people.go +++ b/db/people.go @@ -28,7 +28,7 @@ func UpdateIdentityState(ctx *api.Context, id, state string) error { return ctx.DB().Table("identities").Where("id = ?", id).Update("state", state).Error } -func CreatePerson(ctx *api.Context, username, password string) (uuid.UUID, error) { +func CreatePerson(ctx *api.Context, username, hashedPassword string) (uuid.UUID, error) { tx := ctx.DB().Begin() defer tx.Rollback() @@ -38,7 +38,7 @@ func CreatePerson(ctx *api.Context, username, password string) (uuid.UUID, error } accessToken := models.AccessToken{ - Value: password, // TODO: bcrypt + Value: hashedPassword, PersonID: person.ID, ExpiresAt: time.Now().Add(time.Hour), // TODO: decide on this one } diff --git a/rbac/init.go b/rbac/init.go index 6b207b425..645f2fb73 100644 --- a/rbac/init.go +++ b/rbac/init.go @@ -101,6 +101,7 @@ func Init(adminUserID string) error { {RoleAdmin, ObjectDatabase, ActionWrite}, {RoleAdmin, ObjectRBAC, ActionWrite}, {RoleAdmin, ObjectAuth, ActionWrite}, + {RoleAdmin, ObjectAgentPush, ActionWrite}, {RoleAdmin, ObjectDatabaseIdentity, ActionRead}, {RoleAdmin, ObjectDatabaseConnection, ActionRead}, {RoleAdmin, ObjectDatabaseConnection, ActionCreate},