diff --git a/agent/agent.go b/agent/agent.go new file mode 100644 index 000000000..08e3175ee --- /dev/null +++ b/agent/agent.go @@ -0,0 +1,58 @@ +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" +) + +// 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 { + return nil, fmt.Errorf("failed to generate username and password: %w", err) + } + + 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) + } + + 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 access token: %w", err) + } + + 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, &person.ID, body.Properties); err != nil { + return nil, fmt.Errorf("failed to create a new agent: %w", err) + } + + return &api.GeneratedAgent{ + ID: person.ID.String(), + Username: username, + 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 { + 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 new file mode 100644 index 000000000..bfb3f84d4 --- /dev/null +++ b/agent/controllers.go @@ -0,0 +1,28 @@ +package agent + +import ( + "encoding/json" + "net/http" + + "github.com/flanksource/commons/logger" + "github.com/flanksource/incident-commander/api" + "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) + + 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()}) + } + + agent, err := generateAgent(ctx, body) + if err != nil { + logger.Errorf("failed to generate a new agent: %v", err) + return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"}) + } + + return c.JSON(http.StatusCreated, agent) +} diff --git a/api/agent.go b/api/agent.go new file mode 100644 index 000000000..318e4ccc6 --- /dev/null +++ b/api/agent.go @@ -0,0 +1,12 @@ +package api + +type GenerateAgentRequest struct { + Name string + Properties map[string]string +} + +type GeneratedAgent struct { + ID string `json:"id"` + Username string `json:"username"` + AccessToken string `json:"access_token"` +} 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 0406ccb1e..850a18d38 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -2,19 +2,27 @@ package auth import ( "context" + "encoding/base64" "errors" "fmt" "net/http" + "strconv" "strings" "time" "github.com/flanksource/commons/collections" + "github.com/flanksource/commons/hash" "github.com/flanksource/commons/logger" - "github.com/flanksource/incident-commander/utils" + "github.com/flanksource/commons/rand" + "github.com/flanksource/commons/utils" + "github.com/flanksource/duty/models" "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" + "golang.org/x/crypto/argon2" + "gorm.io/gorm" ) const ( @@ -22,24 +30,33 @@ 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 } 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) } return &kratosMiddleware{ client: k.client, + 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 @@ -58,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") } @@ -76,6 +100,54 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc { } } +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 + } + + var ( + password = fields[0] + salt = fields[1] + ) + + timeCost, err := strconv.ParseUint(fields[2], 10, 32) + if err != nil { + return nil, errInvalidTokenFormat + } + + memoryCost, err := strconv.ParseUint(fields[3], 10, 32) + if err != nil { + return nil, errInvalidTokenFormat + } + + parallelism, err := strconv.ParseUint(fields[4], 10, 8) + if err != nil { + return nil, errInvalidTokenFormat + } + + 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 = ?` + var acessToken models.AccessToken + if err := k.db.Raw(query, encodedHash).First(&acessToken).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + + return nil, err + } + + k.accessTokenCache.Set(token, &acessToken, time.Until(acessToken.ExpiresAt)) + + 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 +156,30 @@ func (k *kratosMiddleware) validateSession(r *http.Request) (*client.Session, er } if username, password, ok := r.BasicAuth(); ok { + if username == "TOKEN" { + accessToken, err := k.getAccessToken(r.Context(), password) + if err != nil { + return nil, err + } else if accessToken == nil { + return &client.Session{Active: utils.Ptr(false)}, nil + } + + if accessToken.ExpiresAt.Before(time.Now()) { + return nil, errTokenExpired + } + + s := &client.Session{ + Id: uuid.NewString(), + Active: utils.Ptr(true), + ExpiresAt: &accessToken.ExpiresAt, + 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) @@ -187,5 +283,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/cmd/server.go b/cmd/server.go index 94ae9a8c8..97164ed11 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" @@ -18,6 +19,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" @@ -68,7 +70,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) @@ -141,12 +143,14 @@ 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) upstreamGroup.GET("/status/:agent_name", upstream.Status) + e.POST("/agent/generate", agent.GenerateAgent, rbac.Authorization(rbac.ObjectAgentCreate, rbac.ActionWrite)) + forward(e, "/config", configDb) forward(e, "/canary", api.CanaryCheckerPath) forward(e, "/kratos", kratosAPI) @@ -240,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/agents.go b/db/agents.go index 30757c0df..c87c6c243 100644 --- a/db/agents.go +++ b/db/agents.go @@ -4,8 +4,10 @@ import ( "errors" "fmt" + "github.com/flanksource/commons/collections" "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/api" + "github.com/google/uuid" "gorm.io/gorm" ) @@ -50,3 +52,15 @@ 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 { + properties = collections.MergeMap(properties, map[string]string{"type": "agent"}) + + 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 2f1d09f47..83138457c 100644 --- a/db/people.go +++ b/db/people.go @@ -1,9 +1,17 @@ package db import ( + crand "crypto/rand" + "encoding/base64" + "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" ) func UpdateUserProperties(ctx *api.Context, userID string, newProps api.PersonProperties) error { @@ -12,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 } @@ -34,3 +42,52 @@ 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, 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 + } + + 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 = 20 + saltLength = 12 +) + +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.URLEncoding.EncodeToString(saltRaw) + + hash := argon2.IDKey([]byte(password), []byte(salt), timeCost, memoryCost, parallelism, keyLength) + encodedHash := base64.URLEncoding.EncodeToString(hash) + + accessToken := &models.AccessToken{ + Name: fmt.Sprintf("agent-%d", time.Now().Unix()), + Value: encodedHash, + PersonID: personID, + ExpiresAt: time.Now().Add(expiry), // long-lived token + } + if err := ctx.DB().Create(&accessToken).Error; err != nil { + return "", err + } + + 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 c08583dd8..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-20230728194245-b0cb94b80691 // indirect + golang.org/x/exp v0.0.0-20230807204917-050eac23e9de // 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-20230731193218-e0aa005b6bdf // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // 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.313 // 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 @@ -202,19 +202,18 @@ 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 - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + go.uber.org/zap v1.25.0 // indirect + golang.org/x/crypto v0.12.0 + 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-20230731193218-e0aa005b6bdf // 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 185c9eb78..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.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.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= @@ -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= @@ -1459,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= @@ -1477,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-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-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 +1591,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.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= @@ -1751,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= @@ -1762,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= @@ -1780,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= @@ -1936,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= @@ -2081,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-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-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-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-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-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-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/rbac/init.go b/rbac/init.go index 9a8213a44..da737745c 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,11 @@ const ( ActionCreate = "create" // Objects - ObjectRBAC = "rbac" - ObjectAuth = "auth" - ObjectDatabase = "database" + ObjectRBAC = "rbac" + ObjectAuth = "auth" + ObjectAgentPush = "agent-push" + ObjectAgentCreate = "agent-create" + ObjectDatabase = "database" ObjectDatabaseResponder = "database.responder" ObjectDatabaseIncident = "database.incident" @@ -98,6 +101,8 @@ func Init(adminUserID string) error { {RoleAdmin, ObjectDatabase, ActionWrite}, {RoleAdmin, ObjectRBAC, ActionWrite}, {RoleAdmin, ObjectAuth, ActionWrite}, + {RoleAdmin, ObjectAgentPush, ActionWrite}, + {RoleAdmin, ObjectAgentCreate, ActionWrite}, {RoleAdmin, ObjectDatabaseIdentity, ActionRead}, {RoleAdmin, ObjectDatabaseConnection, ActionRead}, {RoleAdmin, ObjectDatabaseConnection, ActionCreate}, @@ -123,6 +128,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/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/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/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/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/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) - } - }) - } -}