From 997cc0440ec3d2d0383bc57f026b9683b9f772d6 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 22:11:19 +0545 Subject: [PATCH 01/15] draft: agent create api [skip ci] --- agent/controllers.go | 73 +++++++++++++++++++++++++++++++++++++++ agent/controllers_test.go | 29 ++++++++++++++++ api/agent.go | 14 ++++++++ cmd/server.go | 3 ++ db/people.go | 36 +++++++++++++++++++ go.mod | 2 ++ 6 files changed, 157 insertions(+) create mode 100644 agent/controllers.go create mode 100644 agent/controllers_test.go create mode 100644 api/agent.go diff --git a/agent/controllers.go b/agent/controllers.go new file mode 100644 index 000000000..e2fbbd6ce --- /dev/null +++ b/agent/controllers.go @@ -0,0 +1,73 @@ +package agent + +import ( + "crypto/rand" + "encoding/binary" + "encoding/json" + "net/http" + + "github.com/flanksource/incident-commander/api" + "github.com/flanksource/incident-commander/db" + "github.com/flanksource/incident-commander/rbac" + "github.com/labstack/echo/v4" +) + +func GenerateAgent(c echo.Context) error { + ctx := c.(*api.Context) + + var body api.GenerateAgentRequest + if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil { + return c.JSON(http.StatusBadRequest, api.HTTPError{Error: err.Error()}) + } + + req := db.CreateUserRequest{ + Username: generateRandomString(10), + Password: generateRandomString(32), + Properties: body.Properties, + } + + id, err := db.CreatePerson(ctx, req) + if err != nil { + return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) + } + + if ok, err := rbac.Enforcer.AddRoleForUser(id, "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.GetOrCreateAgent(ctx, body.Name); err != nil { + return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) + } + + return c.JSON(http.StatusCreated, api.GeneratedAgent{ + ID: id, + Username: req.Username, + Password: req.Password, + }) +} + +// 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] + } + 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) + if err != nil { + return 0, err + } + return int(n) % max, nil +} diff --git a/agent/controllers_test.go b/agent/controllers_test.go new file mode 100644 index 000000000..c16dde20f --- /dev/null +++ b/agent/controllers_test.go @@ -0,0 +1,29 @@ +package agent + +import "testing" + +func Test_generateRandomString(t *testing.T) { + type args struct { + length int + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test1", + args: args{ + length: 8, + }, + want: "1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := generateRandomString(tt.args.length); got != tt.want { + t.Errorf("generateRandomString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/api/agent.go b/api/agent.go new file mode 100644 index 000000000..a52953259 --- /dev/null +++ b/api/agent.go @@ -0,0 +1,14 @@ +package api + +import "github.com/flanksource/duty/models" + +type GenerateAgentRequest struct { + Name string + Properties models.PersonProperties +} + +type GeneratedAgent struct { + ID string `json:"id"` + Username string `json:"username"` + Password string `json:"password"` +} diff --git a/cmd/server.go b/cmd/server.go index 94ae9a8c8..f527da0f3 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -18,6 +18,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "github.com/flanksource/duty/models" + "github.com/flanksource/incident-commander/agent" "github.com/flanksource/incident-commander/api" v1 "github.com/flanksource/incident-commander/api/v1" "github.com/flanksource/incident-commander/auth" @@ -147,6 +148,8 @@ func createHTTPServer(gormDB *gorm.DB) *echo.Echo { upstreamGroup.GET("/canary/pull/:agent_name", canary.Pull) upstreamGroup.GET("/status/:agent_name", upstream.Status) + e.POST("/agent/generate", agent.GenerateAgent) + forward(e, "/config", configDb) forward(e, "/canary", api.CanaryCheckerPath) forward(e, "/kratos", kratosAPI) diff --git a/db/people.go b/db/people.go index 2f1d09f47..9f4da788b 100644 --- a/db/people.go +++ b/db/people.go @@ -1,9 +1,13 @@ package db import ( + "time" + + "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/api" "github.com/flanksource/incident-commander/utils" "github.com/google/uuid" + "gorm.io/gorm/clause" ) func UpdateUserProperties(ctx *api.Context, userID string, newProps api.PersonProperties) error { @@ -34,3 +38,35 @@ func GetOrCreateUser(ctx *api.Context, user api.Person) (api.Person, error) { err := ctx.DB().Table("people").Create(&user).Error return user, err } + +type CreateUserRequest struct { + Username string + Password string + Properties models.PersonProperties +} + +func CreatePerson(ctx *api.Context, request CreateUserRequest) (string, error) { + tx := ctx.DB().Begin() + defer tx.Rollback() + + person := models.Person{ + Name: request.Username, + Type: "agent", + Properties: request.Properties, + } + + if err := tx.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).Create(&person).Error; err != nil { + return "", err + } + + accessToken := models.AccessToken{ + Value: request.Password, // TODO: bcrypt + PersonID: person.ID, + ExpiresAt: time.Now().Add(time.Hour), // TODO: decide on this one + } + if err := tx.Create(&accessToken).Error; err != nil { + return "", err + } + + return person.ID.String(), tx.Commit().Error +} diff --git a/go.mod b/go.mod index c08583dd8..83bd9b1e8 100644 --- a/go.mod +++ b/go.mod @@ -223,3 +223,5 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +replace "github.com/flanksource/duty" => ../duty \ No newline at end of file From 6d58512cfac4a8f8fa0357266241485280ed9a22 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 22:13:28 +0545 Subject: [PATCH 02/15] chore: rbac integration and validation of access token [skip ci] --- agent/controllers.go | 23 ++++++++++++----------- api/agent.go | 4 +--- auth/middleware.go | 36 ++++++++++++++++++++++++++++++++++++ cmd/server.go | 2 +- db/agents.go | 11 +++++++++++ db/people.go | 17 ++++++----------- rbac/init.go | 10 +++++++--- utils/utils.go | 5 +++++ 8 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 utils/utils.go diff --git a/agent/controllers.go b/agent/controllers.go index e2fbbd6ce..d0c1ef326 100644 --- a/agent/controllers.go +++ b/agent/controllers.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/binary" "encoding/json" + "fmt" "net/http" "github.com/flanksource/incident-commander/api" @@ -20,31 +21,31 @@ func GenerateAgent(c echo.Context) error { return c.JSON(http.StatusBadRequest, api.HTTPError{Error: err.Error()}) } - req := db.CreateUserRequest{ - Username: generateRandomString(10), - Password: generateRandomString(32), - Properties: body.Properties, - } + var ( + username = fmt.Sprintf("agent-%s", generateRandomString(8)) + password = generateRandomString(32) + ) - id, err := db.CreatePerson(ctx, req) + // TODO: Only if unauthenticated, we need to create a user + id, err := db.CreatePerson(ctx, username, password) if err != nil { return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) } - if ok, err := rbac.Enforcer.AddRoleForUser(id, "agent"); !ok { + 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.GetOrCreateAgent(ctx, body.Name); err != nil { + 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"}) } return c.JSON(http.StatusCreated, api.GeneratedAgent{ - ID: id, - Username: req.Username, - Password: req.Password, + ID: id.String(), + Username: username, + Password: password, }) } diff --git a/api/agent.go b/api/agent.go index a52953259..acd192ffa 100644 --- a/api/agent.go +++ b/api/agent.go @@ -1,10 +1,8 @@ package api -import "github.com/flanksource/duty/models" - type GenerateAgentRequest struct { Name string - Properties models.PersonProperties + Properties map[string]string } type GeneratedAgent struct { diff --git a/auth/middleware.go b/auth/middleware.go index 0406ccb1e..239540653 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -10,11 +10,14 @@ import ( "github.com/flanksource/commons/collections" "github.com/flanksource/commons/logger" + "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/utils" "github.com/golang-jwt/jwt/v4" + "github.com/google/uuid" "github.com/labstack/echo/v4" client "github.com/ory/client-go" "github.com/patrickmn/go-cache" + "gorm.io/gorm" ) const ( @@ -28,6 +31,7 @@ type kratosMiddleware struct { tokenCache *cache.Cache authSessionCache *cache.Cache basicAuthSeparator string + db *gorm.DB } func (k *KratosHandler) KratosMiddleware() (*kratosMiddleware, error) { @@ -76,6 +80,19 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc { } } +func (k *kratosMiddleware) validateAccessToken(ctx context.Context, password string) (*models.AccessToken, error) { + 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 errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + + return nil, err + } + + return &acessToken, nil +} + func (k *kratosMiddleware) validateSession(r *http.Request) (*client.Session, error) { // Skip all kratos calls if strings.HasPrefix(r.URL.Path, "/kratos") { @@ -84,6 +101,25 @@ 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) + if err != nil { + return nil, fmt.Errorf("failed to validate agent: %w", err) + } else if accessToken == nil { + return &client.Session{Active: utils.Ptr(false)}, nil + } + + s := &client.Session{ + Id: uuid.NewString(), + Active: utils.Ptr(true), + Identity: client.Identity{ + Id: accessToken.PersonID.String(), + }, + } + + return s, nil + } + sess, err := k.kratosLoginWithCache(r.Context(), username, password) if err != nil { return nil, fmt.Errorf("failed to login: %w", err) diff --git a/cmd/server.go b/cmd/server.go index f527da0f3..698c8cff2 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -142,7 +142,7 @@ func createHTTPServer(gormDB *gorm.DB) *echo.Echo { if api.UpstreamConf.IsPartiallyFilled() { logger.Warnf("Please ensure that all the required flags for upstream is supplied.") } - upstreamGroup := e.Group("/upstream") + upstreamGroup := e.Group("/upstream", rbac.Authorization(rbac.ObjectAgentPush, rbac.ActionWrite)) upstreamGroup.POST("/push", upstream.PushUpstream) upstreamGroup.GET("/pull/:agent_name", upstream.Pull) upstreamGroup.GET("/canary/pull/:agent_name", canary.Pull) diff --git a/db/agents.go b/db/agents.go index 30757c0df..44c2ce465 100644 --- a/db/agents.go +++ b/db/agents.go @@ -6,6 +6,7 @@ import ( "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/api" + "github.com/google/uuid" "gorm.io/gorm" ) @@ -50,3 +51,13 @@ func GetOrCreateAgent(ctx *api.Context, name string) (*models.Agent, error) { return a, nil } + +func CreateAgent(ctx *api.Context, name string, personID *uuid.UUID, properties map[string]string) error { + a := models.Agent{ + Name: name, + PersonID: personID, + Properties: properties, + } + + return ctx.DB().Create(&a).Error +} diff --git a/db/people.go b/db/people.go index 9f4da788b..ccdc44457 100644 --- a/db/people.go +++ b/db/people.go @@ -45,28 +45,23 @@ type CreateUserRequest struct { Properties models.PersonProperties } -func CreatePerson(ctx *api.Context, request CreateUserRequest) (string, error) { +func CreatePerson(ctx *api.Context, username, password string) (uuid.UUID, error) { tx := ctx.DB().Begin() defer tx.Rollback() - person := models.Person{ - Name: request.Username, - Type: "agent", - Properties: request.Properties, - } - + person := models.Person{Name: username, Type: "agent"} if err := tx.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).Create(&person).Error; err != nil { - return "", err + return uuid.Nil, err } accessToken := models.AccessToken{ - Value: request.Password, // TODO: bcrypt + Value: password, // TODO: bcrypt PersonID: person.ID, ExpiresAt: time.Now().Add(time.Hour), // TODO: decide on this one } if err := tx.Create(&accessToken).Error; err != nil { - return "", err + return uuid.Nil, err } - return person.ID.String(), tx.Commit().Error + return person.ID, tx.Commit().Error } diff --git a/rbac/init.go b/rbac/init.go index 9a8213a44..2865614db 100644 --- a/rbac/init.go +++ b/rbac/init.go @@ -34,6 +34,7 @@ const ( RoleViewer = "viewer" RoleCommander = "commander" RoleResponder = "responder" + RoleAgent = "agent" // Actions ActionRead = "read" @@ -42,9 +43,10 @@ const ( ActionCreate = "create" // Objects - ObjectRBAC = "rbac" - ObjectAuth = "auth" - ObjectDatabase = "database" + ObjectRBAC = "rbac" + ObjectAuth = "auth" + ObjectAgentPush = "agent-push" + ObjectDatabase = "database" ObjectDatabaseResponder = "database.responder" ObjectDatabaseIncident = "database.incident" @@ -123,6 +125,8 @@ func Init(adminUserID string) error { {RoleResponder, ObjectDatabaseIncident, ActionUpdate}, {RoleViewer, ObjectDatabase, ActionRead}, + + {RoleAgent, ObjectAgentPush, ActionWrite}, } // Adding policies in a loop is important diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 000000000..bec85d467 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,5 @@ +package utils + +func Ptr[T any](value T) *T { + return &value +} From a94f8907074fdff3b3c804d880d50bbf519c8eb5 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Thu, 3 Aug 2023 20:13:12 +0545 Subject: [PATCH 03/15] chore: remove agent/controllers_test.go [skip ci] --- agent/controllers_test.go | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 agent/controllers_test.go diff --git a/agent/controllers_test.go b/agent/controllers_test.go deleted file mode 100644 index c16dde20f..000000000 --- a/agent/controllers_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package agent - -import "testing" - -func Test_generateRandomString(t *testing.T) { - type args struct { - length int - } - tests := []struct { - name string - args args - want string - }{ - { - name: "test1", - args: args{ - length: 8, - }, - want: "1", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := generateRandomString(tt.args.length); got != tt.want { - t.Errorf("generateRandomString() = %v, want %v", got, tt.want) - } - }) - } -} From 5c51c52ca623524d888759c85da31bbd2a66bdb0 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 22:13:59 +0545 Subject: [PATCH 04/15] 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 239540653..2edf716c0 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 ccdc44457..971a7bbc3 100644 --- a/db/people.go +++ b/db/people.go @@ -45,7 +45,7 @@ type CreateUserRequest struct { Properties models.PersonProperties } -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() @@ -55,7 +55,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 2865614db..02f5900df 100644 --- a/rbac/init.go +++ b/rbac/init.go @@ -100,6 +100,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}, From 923e30a01a4b778ebabf99897b0fe7663cdaa8b0 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 22:14:23 +0545 Subject: [PATCH 05/15] refactor: separate generateAgent func [skip ci] --- agent/agent.go | 24 ++++++++++++++++++++---- agent/controllers.go | 16 ---------------- db/people.go | 10 +++++----- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 7d7f3aa99..63ae84a11 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -3,12 +3,14 @@ package agent import ( "fmt" + "github.com/flanksource/commons/rand" "github.com/flanksource/incident-commander/api" "github.com/flanksource/incident-commander/db" "github.com/flanksource/incident-commander/rbac" "golang.org/x/crypto/bcrypt" ) +// generateAgent creates a new person and a new agent and associates them. func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.GeneratedAgent, error) { username, password, err := genUsernamePassword() if err != nil { @@ -20,22 +22,36 @@ func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.Genera return nil, fmt.Errorf("failed to hash password: %w", err) } - id, err := db.CreatePerson(ctx, username, string(hashedPassword)) + person, 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 { + if _, err := rbac.Enforcer.AddRoleForUser(person.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 { + if err := db.CreateAgent(ctx, body.Name, &person.ID, body.Properties); err != nil { return nil, fmt.Errorf("failed to create a new agent: %w", err) } return &api.GeneratedAgent{ - ID: id.String(), + ID: person.ID.String(), Username: username, AccessToken: password, }, nil } + +func genUsernamePassword() (username, password string, err error) { + username, err = rand.GenerateRandHex(8) + if err != nil { + return "", "", err + } + + password, err = rand.GenerateRandHex(32) + if err != nil { + return "", "", err + } + + return fmt.Sprintf("agent-%s", username), password, nil +} diff --git a/agent/controllers.go b/agent/controllers.go index ad2f967e0..93989d19c 100644 --- a/agent/controllers.go +++ b/agent/controllers.go @@ -2,11 +2,9 @@ package agent import ( "encoding/json" - "fmt" "net/http" "github.com/flanksource/commons/logger" - crand "github.com/flanksource/commons/rand" "github.com/flanksource/incident-commander/api" "github.com/labstack/echo/v4" ) @@ -28,17 +26,3 @@ func GenerateAgent(c echo.Context) error { return c.JSON(http.StatusCreated, agent) } - -func genUsernamePassword() (username, password string, err error) { - username, err = crand.GenerateRandHex(8) - if err != nil { - return "", "", err - } - - password, err = crand.GenerateRandHex(32) - if err != nil { - return "", "", err - } - - return fmt.Sprintf("agent-%s", username), password, nil -} diff --git a/db/people.go b/db/people.go index 971a7bbc3..a7b5ee1c8 100644 --- a/db/people.go +++ b/db/people.go @@ -45,13 +45,13 @@ type CreateUserRequest struct { Properties models.PersonProperties } -func CreatePerson(ctx *api.Context, username, hashedPassword string) (uuid.UUID, error) { +func CreatePerson(ctx *api.Context, username, hashedPassword string) (*models.Person, error) { tx := ctx.DB().Begin() defer tx.Rollback() person := models.Person{Name: username, Type: "agent"} - if err := tx.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).Create(&person).Error; err != nil { - return uuid.Nil, err + if err := tx.Clauses(clause.Returning{}).Create(&person).Error; err != nil { + return nil, err } accessToken := models.AccessToken{ @@ -60,8 +60,8 @@ func CreatePerson(ctx *api.Context, username, hashedPassword string) (uuid.UUID, ExpiresAt: time.Now().Add(time.Hour), // TODO: decide on this one } if err := tx.Create(&accessToken).Error; err != nil { - return uuid.Nil, err + return nil, err } - return person.ID, tx.Commit().Error + return &person, tx.Commit().Error } From 54a75f51a9708c4838c62cff6442081bb18c7442 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 22:15:02 +0545 Subject: [PATCH 06/15] chore: bump duty --- db/people.go | 3 ++- go.mod | 19 ++++++++----------- go.sum | 29 ++++++++++++++--------------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/db/people.go b/db/people.go index a7b5ee1c8..1b319c87b 100644 --- a/db/people.go +++ b/db/people.go @@ -55,9 +55,10 @@ func CreatePerson(ctx *api.Context, username, hashedPassword string) (*models.Pe } accessToken := models.AccessToken{ + Name: "default", Value: hashedPassword, PersonID: person.ID, - ExpiresAt: time.Now().Add(time.Hour), // TODO: decide on this one + ExpiresAt: time.Now().Add(time.Hour * 24 * 90), // long-lived token } if err := tx.Create(&accessToken).Error; err != nil { return nil, err diff --git a/go.mod b/go.mod index 83bd9b1e8..bebb43469 100644 --- a/go.mod +++ b/go.mod @@ -106,13 +106,13 @@ require ( go.opentelemetry.io/otel v1.16.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect - golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 // indirect + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/term v0.10.0 // indirect golang.org/x/tools v0.11.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/flanksource/yaml.v3 v3.2.3 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect @@ -143,7 +143,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/TomOnTime/utfutil v0.0.0-20230223141146-125e65197b36 github.com/antonmedv/expr v1.12.7 // indirect - github.com/aws/aws-sdk-go v1.44.313 // indirect + github.com/aws/aws-sdk-go v1.44.316 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -202,11 +202,10 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/net v0.12.0 // indirect + go.uber.org/zap v1.25.0 // indirect + golang.org/x/crypto v0.11.0 + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect @@ -214,7 +213,7 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.134.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf // indirect + google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -223,5 +222,3 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) - -replace "github.com/flanksource/duty" => ../duty \ No newline at end of file diff --git a/go.sum b/go.sum index 185c9eb78..c5daf4f07 100644 --- a/go.sum +++ b/go.sum @@ -665,8 +665,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.313 h1:u6EuNQqgAmi09GEZ5g/XGHLF0XV31WcdU5rnHyIBHBc= -github.com/aws/aws-sdk-go v1.44.313/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.316 h1:UC3alCEyzj2XU13ZFGIOHW3yjCNLGTIGVauyetl9fwE= +github.com/aws/aws-sdk-go v1.44.316/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -1428,8 +1428,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -1438,8 +1436,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1477,8 +1475,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw= -golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1592,8 +1590,9 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2081,16 +2080,16 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf h1:v5Cf4E9+6tawYrs/grq1q1hFpGtzlGFzgWHqwt6NFiU= -google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf h1:xkVZ5FdZJF4U82Q/JS+DcZA83s/GRVL+QrFMlexk9Yo= -google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From 88aab09585e37e285cbcb403ca9440c1cb804518 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 09:54:03 +0545 Subject: [PATCH 07/15] chore: address review comment --- agent/controllers.go | 2 +- db/agents.go | 3 +++ db/people.go | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/agent/controllers.go b/agent/controllers.go index 93989d19c..bfb3f84d4 100644 --- a/agent/controllers.go +++ b/agent/controllers.go @@ -21,7 +21,7 @@ func GenerateAgent(c echo.Context) error { agent, err := generateAgent(ctx, body) if err != nil { 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.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) } return c.JSON(http.StatusCreated, agent) diff --git a/db/agents.go b/db/agents.go index 44c2ce465..c87c6c243 100644 --- a/db/agents.go +++ b/db/agents.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/flanksource/commons/collections" "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/api" "github.com/google/uuid" @@ -53,6 +54,8 @@ func GetOrCreateAgent(ctx *api.Context, name string) (*models.Agent, error) { } func CreateAgent(ctx *api.Context, name string, personID *uuid.UUID, properties map[string]string) error { + properties = collections.MergeMap(properties, map[string]string{"type": "agent"}) + a := models.Agent{ Name: name, PersonID: personID, diff --git a/db/people.go b/db/people.go index 1b319c87b..b43f0ca57 100644 --- a/db/people.go +++ b/db/people.go @@ -1,6 +1,7 @@ package db import ( + "fmt" "time" "github.com/flanksource/duty/models" @@ -55,10 +56,10 @@ func CreatePerson(ctx *api.Context, username, hashedPassword string) (*models.Pe } accessToken := models.AccessToken{ - Name: "default", + Name: fmt.Sprintf("agent-%d", time.Now().Unix()), Value: hashedPassword, PersonID: person.ID, - ExpiresAt: time.Now().Add(time.Hour * 24 * 90), // long-lived token + ExpiresAt: time.Now().Add(time.Hour * 24 * 365), // long-lived token } if err := tx.Create(&accessToken).Error; err != nil { return nil, err From 016b211228ab22f6f0d9633f61e31caac9f1c2a0 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Aug 2023 23:34:32 +0545 Subject: [PATCH 08/15] feat: use argon2 instead of bcrypt argon2 allows us to supply the salt whereas bcrypt doesn't. --- agent/agent.go | 13 +++++----- auth/kratos_client.go | 5 +++- auth/middleware.go | 60 ++++++++++++++++++++++++++++++------------- cmd/server.go | 2 +- db/people.go | 50 ++++++++++++++++++++++++++---------- go.mod | 8 +++--- go.sum | 12 ++++++--- 7 files changed, 103 insertions(+), 47 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 63ae84a11..8c5ee3157 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -2,12 +2,12 @@ package agent import ( "fmt" + "time" "github.com/flanksource/commons/rand" "github.com/flanksource/incident-commander/api" "github.com/flanksource/incident-commander/db" "github.com/flanksource/incident-commander/rbac" - "golang.org/x/crypto/bcrypt" ) // generateAgent creates a new person and a new agent and associates them. @@ -17,14 +17,14 @@ func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.Genera return nil, fmt.Errorf("failed to generate username and password: %w", err) } - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + person, err := db.CreatePerson(ctx, username, "agent") if err != nil { - return nil, fmt.Errorf("failed to hash password: %w", err) + return nil, fmt.Errorf("failed to create a new person: %w", err) } - person, err := db.CreatePerson(ctx, username, string(hashedPassword)) + token, err := db.CreateAccessToken(ctx, person.ID, "default", password, time.Hour*24*365) if err != nil { - return nil, fmt.Errorf("failed to create a new person: %w", err) + return nil, fmt.Errorf("failed to create a new access token: %w", err) } if _, err := rbac.Enforcer.AddRoleForUser(person.ID.String(), "agent"); err != nil { @@ -38,10 +38,11 @@ func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.Genera return &api.GeneratedAgent{ ID: person.ID.String(), Username: username, - AccessToken: password, + AccessToken: token, }, nil } +// genUsernamePassword generates a random pair of username and password func genUsernamePassword() (username, password string, err error) { username, err = rand.GenerateRandHex(8) if err != nil { diff --git a/auth/kratos_client.go b/auth/kratos_client.go index ea91a2ac0..10b1b96b9 100644 --- a/auth/kratos_client.go +++ b/auth/kratos_client.go @@ -6,16 +6,19 @@ import ( "github.com/flanksource/incident-commander/db" client "github.com/ory/client-go" + "gorm.io/gorm" ) type KratosHandler struct { client *client.APIClient adminClient *client.APIClient jwtSecret string + db *gorm.DB } -func NewKratosHandler(kratosAPI, kratosAdminAPI, jwtSecret string) *KratosHandler { +func NewKratosHandler(db *gorm.DB, kratosAPI, kratosAdminAPI, jwtSecret string) *KratosHandler { return &KratosHandler{ + db: db, client: newAPIClient(kratosAPI), adminClient: newAdminAPIClient(kratosAdminAPI), jwtSecret: jwtSecret, diff --git a/auth/middleware.go b/auth/middleware.go index 2edf716c0..578294ff9 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -2,9 +2,11 @@ package auth import ( "context" + "encoding/base64" "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -17,7 +19,7 @@ import ( "github.com/labstack/echo/v4" client "github.com/ory/client-go" "github.com/patrickmn/go-cache" - "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/argon2" "gorm.io/gorm" ) @@ -43,6 +45,7 @@ func (k *KratosHandler) KratosMiddleware() (*kratosMiddleware, error) { return &kratosMiddleware{ client: k.client, + db: k.db, jwtSecret: k.jwtSecret, tokenCache: cache.New(3*24*time.Hour, 12*time.Hour), authSessionCache: cache.New(30*time.Minute, time.Hour), @@ -81,19 +84,40 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc { } } -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 errInvalidTokenFormat = errors.New("invalid token format") +func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*models.AccessToken, error) { + fields := strings.Split(token, ".") + if len(fields) != 5 { + return nil, errInvalidTokenFormat + } + + var ( + password = fields[0] + salt = fields[1] + ) + + timeCost, err := strconv.Atoi(fields[2]) + if err != nil { + return nil, errInvalidTokenFormat + } + + memoryCost, err := strconv.Atoi(fields[3]) + if err != nil { + return nil, errInvalidTokenFormat + } + + parallelism, err := strconv.Atoi(fields[4]) + if err != nil { + return nil, errInvalidTokenFormat + } + + hash := argon2.IDKey([]byte(password), []byte(salt), uint32(timeCost), uint32(memoryCost), uint8(parallelism), 32) + encodedHash := base64.RawStdEncoding.EncodeToString(hash) + + query := `SELECT access_tokens.* FROM access_tokens WHERE value = ?` var acessToken models.AccessToken - if err := k.db.Raw(query, username).First(&acessToken).Error; err != nil { + if err := k.db.Raw(query, encodedHash).First(&acessToken).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } @@ -101,10 +125,6 @@ WHERE return nil, err } - if err := bcrypt.CompareHashAndPassword([]byte(acessToken.Value), []byte(password)); err != nil { - return nil, nil - } - return &acessToken, nil } @@ -116,14 +136,18 @@ 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(), username, password) + if username == "TOKEN" { + accessToken, err := k.getAccessToken(r.Context(), password) if err != nil { return nil, fmt.Errorf("failed to validate agent: %w", err) } else if accessToken == nil { return &client.Session{Active: utils.Ptr(false)}, nil } + if accessToken.ExpiresAt.Before(time.Now()) { + return &client.Session{Active: utils.Ptr(false)}, nil + } + s := &client.Session{ Id: uuid.NewString(), Active: utils.Ptr(true), diff --git a/cmd/server.go b/cmd/server.go index 698c8cff2..e4071e33e 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -69,7 +69,7 @@ func createHTTPServer(gormDB *gorm.DB) *echo.Echo { switch authMode { case "kratos": - kratosHandler := auth.NewKratosHandler(kratosAPI, kratosAdminAPI, db.PostgRESTJWTSecret) + kratosHandler := auth.NewKratosHandler(gormDB, kratosAPI, kratosAdminAPI, db.PostgRESTJWTSecret) adminUserID, err = kratosHandler.CreateAdminUser(context.Background()) if err != nil { logger.Fatalf("Failed to created admin user: %v", err) diff --git a/db/people.go b/db/people.go index b43f0ca57..8d5b29866 100644 --- a/db/people.go +++ b/db/people.go @@ -1,6 +1,8 @@ package db import ( + crand "crypto/rand" + "encoding/base64" "fmt" "time" @@ -8,6 +10,7 @@ import ( "github.com/flanksource/incident-commander/api" "github.com/flanksource/incident-commander/utils" "github.com/google/uuid" + "golang.org/x/crypto/argon2" "gorm.io/gorm/clause" ) @@ -46,24 +49,45 @@ type CreateUserRequest struct { Properties models.PersonProperties } -func CreatePerson(ctx *api.Context, username, hashedPassword string) (*models.Person, error) { - tx := ctx.DB().Begin() - defer tx.Rollback() - - person := models.Person{Name: username, Type: "agent"} - if err := tx.Clauses(clause.Returning{}).Create(&person).Error; err != nil { +func CreatePerson(ctx *api.Context, name, personType string) (*models.Person, error) { + person := models.Person{Name: name, Type: personType} + if err := ctx.DB().Clauses(clause.Returning{}).Create(&person).Error; err != nil { return nil, err } - accessToken := models.AccessToken{ + return &person, nil +} + +const ( + // The draft RFC(https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-9.3) recommends + // the following time and memory cost as sensible defaults. + timeCost = 1 + memoryCost = 64 * 1024 + parallelism = 4 + keyLength = 32 + saltLength = 16 +) + +func CreateAccessToken(ctx *api.Context, personID uuid.UUID, name, password string, expiry time.Duration) (string, error) { + saltRaw := make([]byte, saltLength) + if _, err := crand.Read(saltRaw); err != nil { + return "", err + } + salt := base64.RawStdEncoding.EncodeToString(saltRaw) + + hash := argon2.IDKey([]byte(password), []byte(salt), timeCost, memoryCost, parallelism, keyLength) + encodedHash := base64.RawStdEncoding.EncodeToString(hash) + + accessToken := &models.AccessToken{ Name: fmt.Sprintf("agent-%d", time.Now().Unix()), - Value: hashedPassword, - PersonID: person.ID, - ExpiresAt: time.Now().Add(time.Hour * 24 * 365), // long-lived token + Value: encodedHash, + PersonID: personID, + ExpiresAt: time.Now().Add(expiry), // long-lived token } - if err := tx.Create(&accessToken).Error; err != nil { - return nil, err + if err := ctx.DB().Create(&accessToken).Error; err != nil { + return "", err } - return &person, tx.Commit().Error + formattedHash := fmt.Sprintf("%s.%s.%d.%d.%d", password, salt, timeCost, memoryCost, parallelism) + return formattedHash, nil } diff --git a/go.mod b/go.mod index bebb43469..7de0c64ed 100644 --- a/go.mod +++ b/go.mod @@ -108,7 +108,7 @@ require ( go.opentelemetry.io/otel/trace v1.16.0 // indirect golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/term v0.10.0 // indirect + golang.org/x/term v0.11.0 // indirect golang.org/x/tools v0.11.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect @@ -204,11 +204,11 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.12.0 golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.134.0 // indirect diff --git a/go.sum b/go.sum index c5daf4f07..27f5b3619 100644 --- a/go.sum +++ b/go.sum @@ -1457,8 +1457,9 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1750,8 +1751,9 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1761,8 +1763,9 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1779,8 +1782,9 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 17143d09cb00d57f99493ca56c2c0910a790ea8a Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 8 Aug 2023 10:05:50 +0545 Subject: [PATCH 09/15] fix: uint parsing * Removed rand & hash utils because it's in commons --- auth/middleware.go | 12 ++++++---- snapshot/dump.go | 4 ++-- teams/teams.go | 5 ++-- utils/hash.go | 27 --------------------- utils/rand.go | 44 ----------------------------------- utils/rand_test.go | 58 ---------------------------------------------- 6 files changed, 12 insertions(+), 138 deletions(-) delete mode 100644 utils/hash.go delete mode 100644 utils/rand.go delete mode 100644 utils/rand_test.go diff --git a/auth/middleware.go b/auth/middleware.go index 578294ff9..7ec398ca1 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -11,7 +11,9 @@ import ( "time" "github.com/flanksource/commons/collections" + "github.com/flanksource/commons/hash" "github.com/flanksource/commons/logger" + "github.com/flanksource/commons/rand" "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/utils" "github.com/golang-jwt/jwt/v4" @@ -38,7 +40,7 @@ type kratosMiddleware struct { } func (k *KratosHandler) KratosMiddleware() (*kratosMiddleware, error) { - randString, err := utils.GenerateRandString(30) + randString, err := rand.GenerateRandString(30) if err != nil { return nil, fmt.Errorf("failed to generate random string: %w", err) } @@ -97,17 +99,17 @@ func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*m salt = fields[1] ) - timeCost, err := strconv.Atoi(fields[2]) + timeCost, err := strconv.ParseUint(fields[2], 10, 32) if err != nil { return nil, errInvalidTokenFormat } - memoryCost, err := strconv.Atoi(fields[3]) + memoryCost, err := strconv.ParseUint(fields[3], 10, 32) if err != nil { return nil, errInvalidTokenFormat } - parallelism, err := strconv.Atoi(fields[4]) + parallelism, err := strconv.ParseUint(fields[4], 10, 8) if err != nil { return nil, errInvalidTokenFormat } @@ -262,5 +264,5 @@ func (k *kratosMiddleware) getDBToken(sessionID, userID string) (string, error) } func basicAuthCacheKey(username, separator, password string) string { - return utils.Sha256Hex(fmt.Sprintf("%s:%s:%s", username, separator, password)) + return hash.Sha256Hex(fmt.Sprintf("%s:%s:%s", username, separator, password)) } diff --git a/snapshot/dump.go b/snapshot/dump.go index 357c3ebbe..93713927e 100644 --- a/snapshot/dump.go +++ b/snapshot/dump.go @@ -6,13 +6,13 @@ import ( "fmt" "strings" + "github.com/flanksource/commons/hash" "github.com/flanksource/commons/logger" "github.com/flanksource/duty/models" "github.com/flanksource/duty" "github.com/flanksource/incident-commander/components" "github.com/flanksource/incident-commander/db" - "github.com/flanksource/incident-commander/utils" ) func getColumnNames(table string) (string, error) { @@ -162,7 +162,7 @@ func dumpLogs(ctx SnapshotContext, componentIDs []string) error { return fmt.Errorf("invalid logFormat: %s", ctx.LogFormat) } - logFilename := fmt.Sprintf("logs-%s-%s-%s.%s", logResult.Type, logResult.Name, utils.GetHash(componentID), ctx.LogFormat) + logFilename := fmt.Sprintf("logs-%s-%s-%s.%s", logResult.Type, logResult.Name, hash.Sha256Hex(componentID), ctx.LogFormat) err = writeToLogFile(ctx.Directory, logFilename, logDump) if err != nil { return err diff --git a/teams/teams.go b/teams/teams.go index 08a83dc71..84c3b9e5f 100644 --- a/teams/teams.go +++ b/teams/teams.go @@ -4,10 +4,10 @@ import ( "encoding/json" "time" + "github.com/flanksource/commons/hash" "github.com/flanksource/incident-commander/api" "github.com/flanksource/incident-commander/db" "github.com/flanksource/incident-commander/db/models" - "github.com/flanksource/incident-commander/utils" "github.com/google/uuid" "github.com/patrickmn/go-cache" ) @@ -17,7 +17,8 @@ var teamSpecCache = cache.New(time.Hour*1, time.Hour*1) func GetTeamComponentsFromSelectors(teamID uuid.UUID, componentSelectors []api.ComponentSelector) []api.TeamComponent { var selectedComponents = make(map[string][]uuid.UUID) for _, compSelector := range componentSelectors { - selectedComponents[utils.GetHash(compSelector)] = db.GetComponentsWithSelector(compSelector) + h, _ := hash.JSONMD5Hash(compSelector) + selectedComponents[h] = db.GetComponentsWithSelector(compSelector) } var teamComps []api.TeamComponent diff --git a/utils/hash.go b/utils/hash.go deleted file mode 100644 index d288d2cd6..000000000 --- a/utils/hash.go +++ /dev/null @@ -1,27 +0,0 @@ -package utils - -import ( - "crypto/md5" - "crypto/sha256" - "encoding/hex" - "encoding/json" - - "github.com/flanksource/commons/logger" -) - -func GetHash(obj any) string { - data, err := json.Marshal(obj) - if err != nil { - logger.Debugf("error marshalling the given input: %v", err) - return "" - } - hash := md5.Sum(data) - return hex.EncodeToString(hash[:]) -} - -func Sha256Hex(in string) string { - hash := sha256.New() - hash.Write([]byte(in)) - hashVal := hash.Sum(nil) - return hex.EncodeToString(hashVal[:]) -} diff --git a/utils/rand.go b/utils/rand.go deleted file mode 100644 index d26ed7c54..000000000 --- a/utils/rand.go +++ /dev/null @@ -1,44 +0,0 @@ -package utils - -import ( - "crypto/rand" - "encoding/hex" - "errors" - "fmt" - "math/big" -) - -// GenerateRandHex generates a random hex string of given length in hex format -func GenerateRandHex(length int) (string, error) { - if length%2 != 0 { - return "", fmt.Errorf("please provide an even number. Hex strings cannot be unevenly long.") - } - - b := make([]byte, length/2) - if _, err := rand.Read(b); err != nil { - return "", err - } - - return hex.EncodeToString(b), nil -} - -var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+") - -// GenerateRandString generates a random string of given length -func GenerateRandString(length int) (string, error) { - if length < 1 { - return "", errors.New("please provide a postive number") - } - - b := make([]rune, length) - for i := range b { - r, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) - if err != nil { - return "", err - } - - b[i] = letters[r.Int64()] - } - - return string(b), nil -} diff --git a/utils/rand_test.go b/utils/rand_test.go deleted file mode 100644 index 787e91f23..000000000 --- a/utils/rand_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestGenerateRandHex(t *testing.T) { - tests := []struct { - name string - length int - wantLen int - wantErr bool - }{ - {name: "odd", length: 1, wantErr: true}, - {name: "negative", length: -1, wantErr: true}, - {name: "even", length: 2, wantLen: 2}, - {name: "even-long", length: 200, wantLen: 200}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GenerateRandHex(tt.length) - if (err != nil) != tt.wantErr { - t.Errorf("error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != tt.wantLen { - t.Errorf("Got = %v, want %v", len(got), tt.wantLen) - } - }) - } -} - -func TestGenerateRandString(t *testing.T) { - tests := []struct { - name string - length int - wantLen int - wantErr bool - }{ - {name: "negative", length: -1, wantErr: true}, - {name: "odd", length: 1, wantLen: 1}, - {name: "even", length: 2, wantLen: 2}, - {name: "even-long", length: 200, wantLen: 200}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GenerateRandString(tt.length) - if (err != nil) != tt.wantErr { - t.Errorf("error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(got) != tt.wantLen { - t.Errorf("Got = %v, want %v", len(got), tt.wantLen) - } - }) - } -} From 6fe89e44af99c0d5712d05b06f35245a67de2d35 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 8 Aug 2023 10:32:26 +0545 Subject: [PATCH 10/15] chore: use base64.URLEncoding --- auth/middleware.go | 2 +- db/people.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/auth/middleware.go b/auth/middleware.go index 7ec398ca1..9f0244a48 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -115,7 +115,7 @@ func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*m } hash := argon2.IDKey([]byte(password), []byte(salt), uint32(timeCost), uint32(memoryCost), uint8(parallelism), 32) - encodedHash := base64.RawStdEncoding.EncodeToString(hash) + encodedHash := base64.URLEncoding.EncodeToString(hash) query := `SELECT access_tokens.* FROM access_tokens WHERE value = ?` var acessToken models.AccessToken diff --git a/db/people.go b/db/people.go index 8d5b29866..d1f97b2e8 100644 --- a/db/people.go +++ b/db/people.go @@ -64,8 +64,8 @@ const ( timeCost = 1 memoryCost = 64 * 1024 parallelism = 4 - keyLength = 32 - saltLength = 16 + keyLength = 20 + saltLength = 12 ) func CreateAccessToken(ctx *api.Context, personID uuid.UUID, name, password string, expiry time.Duration) (string, error) { @@ -73,10 +73,10 @@ func CreateAccessToken(ctx *api.Context, personID uuid.UUID, name, password stri if _, err := crand.Read(saltRaw); err != nil { return "", err } - salt := base64.RawStdEncoding.EncodeToString(saltRaw) + salt := base64.URLEncoding.EncodeToString(saltRaw) hash := argon2.IDKey([]byte(password), []byte(salt), timeCost, memoryCost, parallelism, keyLength) - encodedHash := base64.RawStdEncoding.EncodeToString(hash) + encodedHash := base64.URLEncoding.EncodeToString(hash) accessToken := &models.AccessToken{ Name: fmt.Sprintf("agent-%d", time.Now().Unix()), From 0edf6a1572a6693050593852a17cd6dfef872e36 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 8 Aug 2023 11:36:05 +0545 Subject: [PATCH 11/15] feat: access token cache and better errors for the user --- auth/middleware.go | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/auth/middleware.go b/auth/middleware.go index 9f0244a48..c89cc1d21 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -30,10 +30,16 @@ const ( UserIDHeaderKey = "X-User-ID" ) +var ( + errInvalidTokenFormat = errors.New("invalid access token format") + errTokenExpired = errors.New("access token has expired") +) + type kratosMiddleware struct { client *client.APIClient jwtSecret string tokenCache *cache.Cache + accessTokenCache *cache.Cache authSessionCache *cache.Cache basicAuthSeparator string db *gorm.DB @@ -50,6 +56,7 @@ func (k *KratosHandler) KratosMiddleware() (*kratosMiddleware, error) { db: k.db, jwtSecret: k.jwtSecret, tokenCache: cache.New(3*24*time.Hour, 12*time.Hour), + accessTokenCache: cache.New(3*24*time.Hour, 24*time.Hour), authSessionCache: cache.New(30*time.Minute, time.Hour), basicAuthSeparator: randString, }, nil @@ -68,8 +75,15 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc { } session, err := k.validateSession(c.Request()) if err != nil { + if errors.Is(err, errInvalidTokenFormat) { + return c.String(http.StatusBadRequest, "invalid access token") + } else if errors.Is(err, errTokenExpired) { + return c.String(http.StatusUnauthorized, "access token has expired") + } + return c.String(http.StatusUnauthorized, "Unauthorized") } + if !*session.Active { return c.String(http.StatusUnauthorized, "Unauthorized") } @@ -86,9 +100,11 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc { } } -var errInvalidTokenFormat = errors.New("invalid token format") - func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*models.AccessToken, error) { + if token, ok := k.accessTokenCache.Get(token); ok { + return token.(*models.AccessToken), nil + } + fields := strings.Split(token, ".") if len(fields) != 5 { return nil, errInvalidTokenFormat @@ -114,7 +130,7 @@ func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*m return nil, errInvalidTokenFormat } - hash := argon2.IDKey([]byte(password), []byte(salt), uint32(timeCost), uint32(memoryCost), uint8(parallelism), 32) + hash := argon2.IDKey([]byte(password), []byte(salt), uint32(timeCost), uint32(memoryCost), uint8(parallelism), 20) encodedHash := base64.URLEncoding.EncodeToString(hash) query := `SELECT access_tokens.* FROM access_tokens WHERE value = ?` @@ -127,6 +143,8 @@ func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*m return nil, err } + k.accessTokenCache.Set(token, &acessToken, acessToken.ExpiresAt.Sub(time.Now())) + return &acessToken, nil } @@ -141,18 +159,19 @@ func (k *kratosMiddleware) validateSession(r *http.Request) (*client.Session, er if username == "TOKEN" { accessToken, err := k.getAccessToken(r.Context(), password) if err != nil { - return nil, fmt.Errorf("failed to validate agent: %w", err) + return nil, err } else if accessToken == nil { return &client.Session{Active: utils.Ptr(false)}, nil } if accessToken.ExpiresAt.Before(time.Now()) { - return &client.Session{Active: utils.Ptr(false)}, nil + return nil, errTokenExpired } s := &client.Session{ - Id: uuid.NewString(), - Active: utils.Ptr(true), + Id: uuid.NewString(), + Active: utils.Ptr(true), + ExpiresAt: &accessToken.ExpiresAt, Identity: client.Identity{ Id: accessToken.PersonID.String(), }, From ccf1090846aa75376147e8b1be2ccc35fdd8a97c Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 8 Aug 2023 11:49:48 +0545 Subject: [PATCH 12/15] chore: lint fix --- auth/middleware.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/middleware.go b/auth/middleware.go index c89cc1d21..7ae0ced93 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -143,7 +143,7 @@ func (k *kratosMiddleware) getAccessToken(ctx context.Context, token string) (*m return nil, err } - k.accessTokenCache.Set(token, &acessToken, acessToken.ExpiresAt.Sub(time.Now())) + k.accessTokenCache.Set(token, &acessToken, time.Until(acessToken.ExpiresAt)) return &acessToken, nil } From 1a19d83b102e67f9bf8d3202090ec1592b3ada6f Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 8 Aug 2023 13:55:44 +0545 Subject: [PATCH 13/15] chore: bump commons and removed some utils that are in commons --- auth/middleware.go | 2 +- cmd/server.go | 3 ++- db/people.go | 4 ++-- go.mod | 18 ++++++++--------- go.sum | 36 ++++++++++++++++----------------- snapshot/snapshot.go | 8 ++++---- utils/collections.go | 48 -------------------------------------------- utils/utils.go | 5 ----- 8 files changed, 36 insertions(+), 88 deletions(-) delete mode 100644 utils/collections.go delete mode 100644 utils/utils.go diff --git a/auth/middleware.go b/auth/middleware.go index 7ae0ced93..850a18d38 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -14,8 +14,8 @@ import ( "github.com/flanksource/commons/hash" "github.com/flanksource/commons/logger" "github.com/flanksource/commons/rand" + "github.com/flanksource/commons/utils" "github.com/flanksource/duty/models" - "github.com/flanksource/incident-commander/utils" "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/labstack/echo/v4" diff --git a/cmd/server.go b/cmd/server.go index e4071e33e..5fc89c7b7 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/flanksource/commons/logger" + cutils "github.com/flanksource/commons/utils" "github.com/flanksource/duty/schema/openapi" "github.com/flanksource/kopper" "github.com/labstack/echo-contrib/echoprometheus" @@ -243,7 +244,7 @@ func ModifyKratosRequestHeaders(next echo.HandlerFunc) echo.HandlerFunc { if strings.HasPrefix(c.Request().URL.Path, "/kratos") { // Kratos requires the header X-Forwarded-Proto but Nginx sets it as "https,http" // This leads to URL malformation further upstream - val := utils.Coalesce( + val := cutils.Coalesce( c.Request().Header.Get("X-Forwarded-Scheme"), c.Request().Header.Get("X-Scheme"), "https", diff --git a/db/people.go b/db/people.go index d1f97b2e8..cf601341c 100644 --- a/db/people.go +++ b/db/people.go @@ -6,9 +6,9 @@ import ( "fmt" "time" + "github.com/flanksource/commons/collections" "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/api" - "github.com/flanksource/incident-commander/utils" "github.com/google/uuid" "golang.org/x/crypto/argon2" "gorm.io/gorm/clause" @@ -20,7 +20,7 @@ func UpdateUserProperties(ctx *api.Context, userID string, newProps api.PersonPr return err } - props, err := utils.MergeStructs(current.Properties, newProps) + props, err := collections.MergeStructs(current.Properties, newProps) if err != nil { return err } diff --git a/go.mod b/go.mod index 7de0c64ed..661301974 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/clerkinc/clerk-sdk-go v1.47.0 github.com/containrrr/shoutrrr v0.7.1 github.com/fergusstrange/embedded-postgres v1.23.0 - github.com/flanksource/commons v1.10.2 + github.com/flanksource/commons v1.11.0 github.com/flanksource/duty v1.0.150 github.com/flanksource/kopper v1.0.6 github.com/google/cel-go v0.17.1 @@ -106,13 +106,13 @@ require ( go.opentelemetry.io/otel v1.16.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect - golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect + golang.org/x/exp v0.0.0-20230807204917-050eac23e9de // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/term v0.11.0 // indirect golang.org/x/tools v0.11.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/flanksource/yaml.v3 v3.2.3 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect @@ -143,7 +143,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/TomOnTime/utfutil v0.0.0-20230223141146-125e65197b36 github.com/antonmedv/expr v1.12.7 // indirect - github.com/aws/aws-sdk-go v1.44.316 // indirect + github.com/aws/aws-sdk-go v1.44.318 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -205,15 +205,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect golang.org/x/crypto v0.12.0 - golang.org/x/net v0.13.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.134.0 // indirect + google.golang.org/api v0.135.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 // indirect google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 27f5b3619..eafe65ef7 100644 --- a/go.sum +++ b/go.sum @@ -665,8 +665,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.316 h1:UC3alCEyzj2XU13ZFGIOHW3yjCNLGTIGVauyetl9fwE= -github.com/aws/aws-sdk-go v1.44.316/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.318 h1:Yl66rpbQHFUbxe9JBKLcvOvRivhVgP6+zH0b9KzARX8= +github.com/aws/aws-sdk-go v1.44.318/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -764,8 +764,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fergusstrange/embedded-postgres v1.23.0 h1:ZYRD89nammxQDWDi6taJE2CYjDuAoVc1TpEqRIYQryc= github.com/fergusstrange/embedded-postgres v1.23.0/go.mod h1:wL562t1V+iuFwq0UcgMi2e9rp8CROY9wxWZEfP8Y874= -github.com/flanksource/commons v1.10.2 h1:Nw9foNBAt6QVylbgfaDojRGgRUAyQAHOfBv9qk9G714= -github.com/flanksource/commons v1.10.2/go.mod h1:zYEhi6E2+diQ+loVcROUHo/Bgv+Tn61W2NYmrb5MgVI= +github.com/flanksource/commons v1.11.0 h1:ThP3hnX4Xh4thxVl2GjQ92WvQ93jq5VqzJh46jbW23A= +github.com/flanksource/commons v1.11.0/go.mod h1:zYEhi6E2+diQ+loVcROUHo/Bgv+Tn61W2NYmrb5MgVI= github.com/flanksource/duty v1.0.150 h1:4nz/ieerg9nIhIEdNucTCjJ7Z0T9UCUQfgyVTOpKstE= github.com/flanksource/duty v1.0.150/go.mod h1:RJ/kcZ7dbL8/52tem757szVIA3IomS8bOAZIK0xb4rk= github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc= @@ -1476,8 +1476,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= -golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230807204917-050eac23e9de h1:l5Za6utMv/HsBWWqzt4S8X17j+kt1uVETUX5UFhn2rE= +golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1592,8 +1592,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1623,8 +1623,8 @@ golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1939,8 +1939,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw= -google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk= +google.golang.org/api v0.135.0 h1:6Vgfj6uPMXcyy66waYWBwmkeNB+9GmUlJDOzkukPQYQ= +google.golang.org/api v0.135.0/go.mod h1:Bp77uRFgwsSKI0BWH573F5Q6wSlznwI2NFayLOp/7mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2084,16 +2084,16 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 h1:Tyk/35yqszRCvaragTn5NnkY6IiKk/XvHzEWepo71N0= +google.golang.org/genproto v0.0.0-20230807174057-1744710a1577/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= -google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 h1:xv8KoglAClYGkprUSmDTKaILtzfD8XzG9NYVXMprjKo= +google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 287b19376..297e445b0 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -1,11 +1,11 @@ package snapshot import ( + "github.com/flanksource/commons/collections" "github.com/flanksource/commons/files" "github.com/flanksource/commons/logger" "github.com/flanksource/incident-commander/db" - "github.com/flanksource/incident-commander/utils" ) type resource struct { @@ -21,9 +21,9 @@ func (src *resource) merge(dst resource) { } func (r *resource) dedup() { - r.componentIDs = utils.Dedup(r.componentIDs) - r.configIDs = utils.Dedup(r.configIDs) - r.incidentIDs = utils.Dedup(r.incidentIDs) + r.componentIDs = collections.Dedup(r.componentIDs) + r.configIDs = collections.Dedup(r.configIDs) + r.incidentIDs = collections.Dedup(r.incidentIDs) } func (r *resource) dump(ctx SnapshotContext) error { diff --git a/utils/collections.go b/utils/collections.go deleted file mode 100644 index 9a5bd19a1..000000000 --- a/utils/collections.go +++ /dev/null @@ -1,48 +0,0 @@ -package utils - -import "encoding/json" - -func Dedup[T comparable](arr []T) []T { - set := make(map[T]bool) - retArr := []T{} - for _, item := range arr { - if _, value := set[item]; !value { - set[item] = true - retArr = append(retArr, item) - } - } - return retArr -} - -// MergeStructs merges two structs where patch is applied on top of base -func MergeStructs[T any](base, patch T) (T, error) { - jb, err := json.Marshal(patch) - if err != nil { - return base, err - } - err = json.Unmarshal(jb, &base) - if err != nil { - return base, err - } - - return base, nil -} - -// Coalesce returns the first non-zero element -func Coalesce[T comparable](arr ...T) T { - var zeroVal T - for _, item := range arr { - if item != zeroVal { - return item - } - } - return zeroVal -} - -func MapKeys(m map[string]any) []string { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - return keys -} diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index bec85d467..000000000 --- a/utils/utils.go +++ /dev/null @@ -1,5 +0,0 @@ -package utils - -func Ptr[T any](value T) *T { - return &value -} From 5227153925fd18b39d1f4a026706b0242c19e7ac Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 8 Aug 2023 13:59:48 +0545 Subject: [PATCH 14/15] chore: create and save a dummy email for agent person --- agent/agent.go | 2 +- db/people.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 8c5ee3157..08e3175ee 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -17,7 +17,7 @@ func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.Genera return nil, fmt.Errorf("failed to generate username and password: %w", err) } - person, err := db.CreatePerson(ctx, username, "agent") + person, err := db.CreatePerson(ctx, username, fmt.Sprintf("%s@local", username), "agent") if err != nil { return nil, fmt.Errorf("failed to create a new person: %w", err) } diff --git a/db/people.go b/db/people.go index cf601341c..83138457c 100644 --- a/db/people.go +++ b/db/people.go @@ -49,8 +49,8 @@ type CreateUserRequest struct { Properties models.PersonProperties } -func CreatePerson(ctx *api.Context, name, personType string) (*models.Person, error) { - person := models.Person{Name: name, Type: personType} +func CreatePerson(ctx *api.Context, name, email, personType string) (*models.Person, error) { + person := models.Person{Name: name, Email: email, Type: personType} if err := ctx.DB().Clauses(clause.Returning{}).Create(&person).Error; err != nil { return nil, err } From 211f5a55763fe80e26c73298f3f7455b6ebd903c Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Fri, 18 Aug 2023 10:05:24 +0545 Subject: [PATCH 15/15] feat: only allow admins to generate new agents --- cmd/server.go | 2 +- rbac/init.go | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index 5fc89c7b7..97164ed11 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -149,7 +149,7 @@ func createHTTPServer(gormDB *gorm.DB) *echo.Echo { upstreamGroup.GET("/canary/pull/:agent_name", canary.Pull) upstreamGroup.GET("/status/:agent_name", upstream.Status) - e.POST("/agent/generate", agent.GenerateAgent) + e.POST("/agent/generate", agent.GenerateAgent, rbac.Authorization(rbac.ObjectAgentCreate, rbac.ActionWrite)) forward(e, "/config", configDb) forward(e, "/canary", api.CanaryCheckerPath) diff --git a/rbac/init.go b/rbac/init.go index 02f5900df..da737745c 100644 --- a/rbac/init.go +++ b/rbac/init.go @@ -43,10 +43,11 @@ const ( ActionCreate = "create" // Objects - ObjectRBAC = "rbac" - ObjectAuth = "auth" - ObjectAgentPush = "agent-push" - ObjectDatabase = "database" + ObjectRBAC = "rbac" + ObjectAuth = "auth" + ObjectAgentPush = "agent-push" + ObjectAgentCreate = "agent-create" + ObjectDatabase = "database" ObjectDatabaseResponder = "database.responder" ObjectDatabaseIncident = "database.incident" @@ -101,6 +102,7 @@ func Init(adminUserID string) error { {RoleAdmin, ObjectRBAC, ActionWrite}, {RoleAdmin, ObjectAuth, ActionWrite}, {RoleAdmin, ObjectAgentPush, ActionWrite}, + {RoleAdmin, ObjectAgentCreate, ActionWrite}, {RoleAdmin, ObjectDatabaseIdentity, ActionRead}, {RoleAdmin, ObjectDatabaseConnection, ActionRead}, {RoleAdmin, ObjectDatabaseConnection, ActionCreate},