mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-11 12:40:36 +01:00
Compare commits
30 Commits
issue_4863
...
platform-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
948eab368c | ||
|
|
d84327e08e | ||
|
|
ac280d9b9c | ||
|
|
cfeae11ade | ||
|
|
c0af4e1135 | ||
|
|
cd82a63369 | ||
|
|
ef293e1505 | ||
|
|
2eab9474c0 | ||
|
|
132bf8478a | ||
|
|
68ab3917ee | ||
|
|
52516eeec5 | ||
|
|
93eb8c7419 | ||
|
|
47e5507490 | ||
|
|
f9e296de9f | ||
|
|
4ff9323dc8 | ||
|
|
f8570e9713 | ||
|
|
ecc748c811 | ||
|
|
a5db2c1fca | ||
|
|
8653dd3c5c | ||
|
|
aa3f45a2c7 | ||
|
|
cb7b40cd7c | ||
|
|
9ebcf765d0 | ||
|
|
e5ebd86ad6 | ||
|
|
72e1d1aace | ||
|
|
b396b16a1e | ||
|
|
7a90ca5e9e | ||
|
|
46a7b1b1ff | ||
|
|
23ee24732d | ||
|
|
ed1ab29307 | ||
|
|
a2b4a685ad |
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/SigNoz/signoz/cmd"
|
||||
"github.com/SigNoz/signoz/ee/auditor/fileauditor"
|
||||
"github.com/SigNoz/signoz/ee/auditor/otlphttpauditor"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
|
||||
@@ -155,6 +156,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
if err := factories.Add(otlphttpauditor.NewFactory(licensing, version.Info)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := factories.Add(fileauditor.NewFactory(licensing, version.Info)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return factories
|
||||
},
|
||||
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
|
||||
|
||||
33
ee/auditor/fileauditor/export.go
Normal file
33
ee/auditor/fileauditor/export.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package fileauditor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
)
|
||||
|
||||
func (provider *provider) export(ctx context.Context, events []audittypes.AuditEvent) error {
|
||||
logs := audittypes.NewPLogsFromAuditEvents(events, "signoz", provider.build.Version(), "signoz.audit")
|
||||
|
||||
payload, err := provider.marshaler.MarshalLogs(logs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Combine the payload and trailing newline into one Write call so the line
|
||||
// is emitted in a single syscall — concurrent readers see either the full
|
||||
// line or nothing, never a torn JSON object.
|
||||
payload = append(payload, '\n')
|
||||
|
||||
provider.mu.Lock()
|
||||
defer provider.mu.Unlock()
|
||||
|
||||
if _, err := provider.file.Write(payload); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "audit export failed", errors.Attr(err), slog.Int("dropped_log_records", len(events)))
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.file.Sync()
|
||||
}
|
||||
100
ee/auditor/fileauditor/provider.go
Normal file
100
ee/auditor/fileauditor/provider.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package fileauditor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/auditor"
|
||||
"github.com/SigNoz/signoz/pkg/auditor/auditorserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"go.opentelemetry.io/collector/pdata/plog"
|
||||
)
|
||||
|
||||
var _ auditor.Auditor = (*provider)(nil)
|
||||
|
||||
type provider struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
config auditor.Config
|
||||
licensing licensing.Licensing
|
||||
build version.Build
|
||||
server *auditorserver.Server
|
||||
marshaler plog.JSONMarshaler
|
||||
file *os.File
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewFactory(licensing licensing.Licensing, build version.Build) factory.ProviderFactory[auditor.Auditor, auditor.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("file"), func(ctx context.Context, providerSettings factory.ProviderSettings, config auditor.Config) (auditor.Auditor, error) {
|
||||
return newProvider(ctx, providerSettings, config, licensing, build)
|
||||
})
|
||||
}
|
||||
|
||||
func newProvider(_ context.Context, providerSettings factory.ProviderSettings, config auditor.Config, licensing licensing.Licensing, build version.Build) (auditor.Auditor, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/auditor/fileauditor")
|
||||
|
||||
file, err := os.OpenFile(config.File.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, auditor.ErrCodeAuditExportFailed, "failed to open audit file %q", config.File.Path)
|
||||
}
|
||||
|
||||
provider := &provider{
|
||||
settings: settings,
|
||||
config: config,
|
||||
licensing: licensing,
|
||||
build: build,
|
||||
marshaler: plog.JSONMarshaler{},
|
||||
file: file,
|
||||
}
|
||||
|
||||
server, err := auditorserver.New(settings,
|
||||
auditorserver.Config{
|
||||
BufferSize: config.BufferSize,
|
||||
BatchSize: config.BatchSize,
|
||||
FlushInterval: config.FlushInterval,
|
||||
},
|
||||
provider.export,
|
||||
)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider.server = server
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
return provider.server.Start(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) Audit(ctx context.Context, event audittypes.AuditEvent) {
|
||||
if event.PrincipalAttributes.PrincipalOrgID.IsZero() {
|
||||
provider.settings.Logger().WarnContext(ctx, "audit event dropped as org_id is zero")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := provider.licensing.GetActive(ctx, event.PrincipalAttributes.PrincipalOrgID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
provider.server.Add(ctx, event)
|
||||
}
|
||||
|
||||
func (provider *provider) Healthy() <-chan struct{} {
|
||||
return provider.server.Healthy()
|
||||
}
|
||||
|
||||
func (provider *provider) Stop(ctx context.Context) error {
|
||||
serverErr := provider.server.Stop(ctx)
|
||||
fileErr := provider.file.Close()
|
||||
if serverErr != nil {
|
||||
return serverErr
|
||||
}
|
||||
|
||||
return fileErr
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -44,54 +46,80 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateChannel), handler.OpenAPIDef{
|
||||
ID: "CreateChannel",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Create notification channel",
|
||||
Description: "This endpoint creates a notification channel",
|
||||
Request: new(alertmanagertypes.PostableChannel),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.Channel),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/channels", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateChannel),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateChannel",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Create notification channel",
|
||||
Description: "This endpoint creates a notification channel",
|
||||
Request: new(alertmanagertypes.PostableChannel),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.Channel),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesNotificationChannel,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateChannelByID), handler.OpenAPIDef{
|
||||
ID: "UpdateChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Update notification channel",
|
||||
Description: "This endpoint updates a notification channel by ID",
|
||||
Request: new(alertmanagertypes.Receiver),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateChannelByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Update notification channel",
|
||||
Description: "This endpoint updates a notification channel by ID",
|
||||
Request: new(alertmanagertypes.Receiver),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteChannelByID), handler.OpenAPIDef{
|
||||
ID: "DeleteChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Delete notification channel",
|
||||
Description: "This endpoint deletes a notification channel by ID",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/channels/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteChannelByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteChannelByID",
|
||||
Tags: []string{"channels"},
|
||||
Summary: "Delete notification channel",
|
||||
Description: "This endpoint deletes a notification channel by ID",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -163,54 +191,91 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateRoutePolicy), handler.OpenAPIDef{
|
||||
ID: "CreateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Create route policy",
|
||||
Description: "This endpoint creates a route policy",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateRoutePolicy),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Create route policy",
|
||||
Description: "This endpoint creates a route policy",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(
|
||||
handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesRoutePolicy,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
},
|
||||
handler.AttachManyAuditDef{
|
||||
AttachedResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
AttachedResourceIDs: handler.BodyJSONArray("channels"),
|
||||
TargetResource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
TargetResourceID: handler.ResponseJSONPath("data.id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateRoutePolicy), handler.OpenAPIDef{
|
||||
ID: "UpdateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Update route policy",
|
||||
Description: "This endpoint updates a route policy by ID",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateRoutePolicy),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Update route policy",
|
||||
Description: "This endpoint updates a route policy by ID",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteRoutePolicyByID), handler.OpenAPIDef{
|
||||
ID: "DeleteRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Delete route policy",
|
||||
Description: "This endpoint deletes a route policy by ID",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteRoutePolicyByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Delete route policy",
|
||||
Description: "This endpoint deletes a route policy by ID",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -27,20 +29,28 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Create), handler.OpenAPIDef{
|
||||
ID: "CreateAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Create auth domain",
|
||||
Description: "This endpoint creates an auth domain",
|
||||
Request: new(authtypes.PostableAuthDomain),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/domains", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Create),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Create auth domain",
|
||||
Description: "This endpoint creates an auth domain",
|
||||
Request: new(authtypes.PostableAuthDomain),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesAuthDomain,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -61,37 +71,55 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Update), handler.OpenAPIDef{
|
||||
ID: "UpdateAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Update auth domain",
|
||||
Description: "This endpoint updates an auth domain",
|
||||
Request: new(authtypes.UpdatableAuthDomain),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Update),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Update auth domain",
|
||||
Description: "This endpoint updates an auth domain",
|
||||
Request: new(authtypes.UpdatableAuthDomain),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceAuthDomain,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Delete), handler.OpenAPIDef{
|
||||
ID: "DeleteAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Delete auth domain",
|
||||
Description: "This endpoint deletes an auth domain",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/domains/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authDomainHandler.Delete),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteAuthDomain",
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Delete auth domain",
|
||||
Description: "This endpoint deletes an auth domain",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceAuthDomain,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -46,6 +48,11 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesCloudIntegration,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,6 +113,12 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceCloudIntegration,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,6 +139,12 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceCloudIntegration,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -188,6 +207,12 @@ func (provider *provider) addCloudIntegrationRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceCloudIntegrationService,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("service_id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
@@ -14,20 +15,29 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.CreatePublic), handler.OpenAPIDef{
|
||||
ID: "CreatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Create public dashboard",
|
||||
Description: "This endpoint creates public sharing config and enables public sharing of the dashboard",
|
||||
Request: new(dashboardtypes.PostablePublicDashboard),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.dashboardHandler.CreatePublic),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Create public dashboard",
|
||||
Description: "This endpoint creates public sharing config and enables public sharing of the dashboard",
|
||||
Request: new(dashboardtypes.PostablePublicDashboard),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesPublicDashboard,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -48,37 +58,55 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.UpdatePublic), handler.OpenAPIDef{
|
||||
ID: "UpdatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Update public dashboard",
|
||||
Description: "This endpoint updates the public sharing config for a dashboard",
|
||||
Request: new(dashboardtypes.UpdatablePublicDashboard),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.dashboardHandler.UpdatePublic),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Update public dashboard",
|
||||
Description: "This endpoint updates the public sharing config for a dashboard",
|
||||
Request: new(dashboardtypes.UpdatablePublicDashboard),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcePublicDashboard,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authzMiddleware.AdminAccess(provider.dashboardHandler.DeletePublic), handler.OpenAPIDef{
|
||||
ID: "DeletePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Delete public dashboard",
|
||||
Description: "This endpoint deletes the public sharing config and disables the public sharing of a dashboard",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.dashboardHandler.DeletePublic),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeletePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Delete public dashboard",
|
||||
Description: "This endpoint deletes the public sharing config and disables the public sharing of a dashboard",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcePublicDashboard,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/gatewaytypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -46,105 +48,169 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKey), handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create ingestion key for workspace",
|
||||
Description: "This endpoint creates an ingestion key for the workspace",
|
||||
Request: new(gatewaytypes.PostableIngestionKey),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(gatewaytypes.GettableCreatedIngestionKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKey),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create ingestion key for workspace",
|
||||
Description: "This endpoint creates an ingestion key for the workspace",
|
||||
Request: new(gatewaytypes.PostableIngestionKey),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(gatewaytypes.GettableCreatedIngestionKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesIngestionKey,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKey), handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update ingestion key for workspace",
|
||||
Description: "This endpoint updates an ingestion key for the workspace",
|
||||
Request: new(gatewaytypes.PostableIngestionKey),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKey),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update ingestion key for workspace",
|
||||
Description: "This endpoint updates an ingestion key for the workspace",
|
||||
Request: new(gatewaytypes.PostableIngestionKey),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceIngestionKey,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("keyId"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKey), handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete ingestion key for workspace",
|
||||
Description: "This endpoint deletes an ingestion key for the workspace",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKey),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionKey",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete ingestion key for workspace",
|
||||
Description: "This endpoint deletes an ingestion key for the workspace",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceIngestionKey,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("keyId"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create limit for the ingestion key",
|
||||
Description: "This endpoint creates an ingestion key limit",
|
||||
Request: new(gatewaytypes.PostableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(gatewaytypes.GettableCreatedIngestionKeyLimit),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/{keyId}/limits", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.gatewayHandler.CreateIngestionKeyLimit),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Create limit for the ingestion key",
|
||||
Description: "This endpoint creates an ingestion key limit",
|
||||
Request: new(gatewaytypes.PostableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(gatewaytypes.GettableCreatedIngestionKeyLimit),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(
|
||||
handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesIngestionLimit,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
},
|
||||
handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceMetaResourceIngestionLimit,
|
||||
AttachedResourceID: handler.ResponseJSONPath("data.id"),
|
||||
TargetResource: coretypes.ResourceMetaResourceIngestionKey,
|
||||
TargetResourceID: handler.PathParam("keyId"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update limit for the ingestion key",
|
||||
Description: "This endpoint updates an ingestion key limit",
|
||||
Request: new(gatewaytypes.UpdatableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.gatewayHandler.UpdateIngestionKeyLimit),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Update limit for the ingestion key",
|
||||
Description: "This endpoint updates an ingestion key limit",
|
||||
Request: new(gatewaytypes.UpdatableIngestionKeyLimit),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceIngestionLimit,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("limitId"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKeyLimit), handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete limit for the ingestion key",
|
||||
Description: "This endpoint deletes an ingestion key limit",
|
||||
Request: nil,
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/gateway/ingestion_keys/limits/{limitId}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.gatewayHandler.DeleteIngestionKeyLimit),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteIngestionKeyLimit",
|
||||
Tags: []string{"gateway"},
|
||||
Summary: "Delete limit for the ingestion key",
|
||||
Description: "This endpoint deletes an ingestion key limit",
|
||||
Request: nil,
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceIngestionLimit,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("limitId"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -45,6 +47,11 @@ func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesLLMPricingRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -85,6 +92,12 @@ func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceLLMPricingRule,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metricsexplorertypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -122,7 +124,14 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceMetricField,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("metric_name"),
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -26,20 +28,28 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/orgs/me", handler.New(provider.authzMiddleware.AdminAccess(provider.orgHandler.Update), handler.OpenAPIDef{
|
||||
ID: "UpdateMyOrganization",
|
||||
Tags: []string{"orgs"},
|
||||
Summary: "Update my organization",
|
||||
Description: "This endpoint updates the organization I belong to",
|
||||
Request: new(types.Organization),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusConflict, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/orgs/me", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.orgHandler.Update),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateMyOrganization",
|
||||
Tags: []string{"orgs"},
|
||||
Summary: "Update my organization",
|
||||
Description: "This endpoint updates the organization I belong to",
|
||||
Request: new(types.Organization),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusConflict, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceOrganization,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -44,20 +46,29 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(provider.authzMiddleware.ViewAccess(provider.preferenceHandler.UpdateByUser), handler.OpenAPIDef{
|
||||
ID: "UpdateUserPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Update user preference",
|
||||
Description: "This endpoint updates the user preference by name",
|
||||
Request: new(preferencetypes.UpdatablePreference),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/user/preferences/{name}", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.preferenceHandler.UpdateByUser),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateUserPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Update user preference",
|
||||
Description: "This endpoint updates the user preference by name",
|
||||
Request: new(preferencetypes.UpdatablePreference),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceUserPreference,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("name"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -95,20 +106,29 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(provider.authzMiddleware.AdminAccess(provider.preferenceHandler.UpdateByOrg), handler.OpenAPIDef{
|
||||
ID: "UpdateOrgPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Update org preference",
|
||||
Description: "This endpoint updates the org preference by name",
|
||||
Request: new(preferencetypes.UpdatablePreference),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/org/preferences/{name}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.preferenceHandler.UpdateByOrg),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateOrgPreference",
|
||||
Tags: []string{"preferences"},
|
||||
Summary: "Update org preference",
|
||||
Description: "This endpoint updates the org preference by name",
|
||||
Request: new(preferencetypes.UpdatablePreference),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceOrgPreference,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("name"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,24 +5,34 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/promotetypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addPromoteRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authzMiddleware.EditAccess(provider.promoteHandler.HandlePromoteAndIndexPaths), handler.OpenAPIDef{
|
||||
ID: "HandlePromoteAndIndexPaths",
|
||||
Tags: []string{"logs"},
|
||||
Summary: "Promote and index paths",
|
||||
Description: "This endpoints promotes and indexes paths",
|
||||
Request: new([]*promotetypes.PromotePath),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.promoteHandler.HandlePromoteAndIndexPaths),
|
||||
handler.OpenAPIDef{
|
||||
ID: "HandlePromoteAndIndexPaths",
|
||||
Tags: []string{"logs"},
|
||||
Summary: "Promote and index paths",
|
||||
Description: "This endpoints promotes and indexes paths",
|
||||
Request: new([]*promotetypes.PromotePath),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesLogsField,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ func (handler *healthOpenAPIHandler) ServeOpenAPI(opCtx openapi.OperationContext
|
||||
)
|
||||
}
|
||||
|
||||
func (handler *healthOpenAPIHandler) AuditDef() *pkghandler.AuditDef {
|
||||
func (handler *healthOpenAPIHandler) AuditDefs() []pkghandler.AuditDef {
|
||||
// Health endpoints are not audited since they don't represent user actions and are called frequently by monitoring systems, which would create noise in the audit logs.
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,26 +5,35 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Create), handler.OpenAPIDef{
|
||||
ID: "CreateRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Create role",
|
||||
Description: "This endpoint creates a role",
|
||||
Request: new(authtypes.PostableRole),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/roles", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authzHandler.Create),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Create role",
|
||||
Description: "This endpoint creates a role",
|
||||
Request: new(authtypes.PostableRole),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesRole,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -79,54 +88,81 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Patch), handler.OpenAPIDef{
|
||||
ID: "PatchRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch role",
|
||||
Description: "This endpoint patches a role",
|
||||
Request: new(authtypes.PatchableRole),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authzHandler.Patch),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PatchRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch role",
|
||||
Description: "This endpoint patches a role",
|
||||
Request: new(authtypes.PatchableRole),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceRole,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.PatchObjects), handler.OpenAPIDef{
|
||||
ID: "PatchObjects",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch objects for a role by relation",
|
||||
Description: "Patches the objects connected to the specified role via a given relation type",
|
||||
Request: new(coretypes.PatchableObjects),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/roles/{id}/relations/{relation}/objects", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authzHandler.PatchObjects),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PatchObjects",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch objects for a role by relation",
|
||||
Description: "Patches the objects connected to the specified role via a given relation type",
|
||||
Request: new(coretypes.PatchableObjects),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceRole,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.authzHandler.Delete), handler.OpenAPIDef{
|
||||
ID: "DeleteRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Delete role",
|
||||
Description: "This endpoint deletes a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.authzHandler.Delete),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Delete role",
|
||||
Description: "This endpoint deletes a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceRole,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -37,64 +39,110 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateRule), handler.OpenAPIDef{
|
||||
ID: "CreateRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create alert rule",
|
||||
Description: "This endpoint creates a new alert rule",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateRule),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create alert rule",
|
||||
Description: "This endpoint creates a new alert rule",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(
|
||||
handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesRule,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
},
|
||||
handler.AttachManyAuditDef{
|
||||
AttachedResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
AttachedResourceIDs: handler.BodyJSONArray("preferredChannels"),
|
||||
TargetResource: coretypes.ResourceMetaResourceRule,
|
||||
TargetResourceID: handler.ResponseJSONPath("data.id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateRuleByID), handler.OpenAPIDef{
|
||||
ID: "UpdateRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update alert rule",
|
||||
Description: "This endpoint updates an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateRuleByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update alert rule",
|
||||
Description: "This endpoint updates an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteRuleByID), handler.OpenAPIDef{
|
||||
ID: "DeleteRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete alert rule",
|
||||
Description: "This endpoint deletes an alert rule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteRuleByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete alert rule",
|
||||
Description: "This endpoint deletes an alert rule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.PatchRuleByID), handler.OpenAPIDef{
|
||||
ID: "PatchRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Patch alert rule",
|
||||
Description: "This endpoint applies a partial update to an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.PatchRuleByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PatchRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Patch alert rule",
|
||||
Description: "This endpoint applies a partial update to an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -143,45 +191,82 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateDowntimeSchedule), handler.OpenAPIDef{
|
||||
ID: "CreateDowntimeSchedule",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Create downtime schedule",
|
||||
Description: "This endpoint creates a new planned maintenance / downtime schedule",
|
||||
Request: new(ruletypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.PlannedMaintenance),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateDowntimeSchedule),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateDowntimeSchedule",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Create downtime schedule",
|
||||
Description: "This endpoint creates a new planned maintenance / downtime schedule",
|
||||
Request: new(ruletypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.PlannedMaintenance),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(
|
||||
handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesPlannedMaintenance,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
},
|
||||
handler.AttachManyAuditDef{
|
||||
AttachedResource: coretypes.ResourceMetaResourceRule,
|
||||
AttachedResourceIDs: handler.BodyJSONArray("alertIds"),
|
||||
TargetResource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
TargetResourceID: handler.ResponseJSONPath("data.id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "UpdateDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Update downtime schedule",
|
||||
Description: "This endpoint updates a downtime schedule by ID",
|
||||
Request: new(ruletypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateDowntimeScheduleByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Update downtime schedule",
|
||||
Description: "This endpoint updates a downtime schedule by ID",
|
||||
Request: new(ruletypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "DeleteDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Delete downtime schedule",
|
||||
Description: "This endpoint deletes a downtime schedule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(
|
||||
provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteDowntimeScheduleByID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Delete downtime schedule",
|
||||
Description: "This endpoint deletes a downtime schedule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
|
||||
@@ -17,22 +18,31 @@ import (
|
||||
)
|
||||
|
||||
func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/service_accounts", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Create, authtypes.Relation{Verb: coretypes.VerbCreate}, coretypes.ResourceMetaResourcesServiceAccount, serviceAccountCollectionSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create service account",
|
||||
Description: "This endpoint creates a service account",
|
||||
Request: new(serviceaccounttypes.PostableServiceAccount),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcesServiceAccount.Scope(coretypes.VerbCreate)}),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/service_accounts", handler.New(
|
||||
provider.authzMiddleware.Check(provider.serviceAccountHandler.Create, authtypes.Relation{Verb: coretypes.VerbCreate}, coretypes.ResourceMetaResourcesServiceAccount, serviceAccountCollectionSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create service account",
|
||||
Description: "This endpoint creates a service account",
|
||||
Request: new(serviceaccounttypes.PostableServiceAccount),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcesServiceAccount.Scope(coretypes.VerbCreate)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesServiceAccount,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -110,125 +120,192 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles", handler.New(provider.authzMiddleware.CheckAll(provider.serviceAccountHandler.SetRole, []middleware.AuthZCheckGroup{
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceServiceAccount, SelectorCallback: serviceAccountInstanceSelectorCallback, Roles: []string{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles", handler.New(
|
||||
provider.authzMiddleware.CheckAll(provider.serviceAccountHandler.SetRole, []middleware.AuthZCheckGroup{
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceServiceAccount, SelectorCallback: serviceAccountInstanceSelectorCallback, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceRole, SelectorCallback: provider.roleAttachSelectorFromBody, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccountRole",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create service account role",
|
||||
Description: "This endpoint assigns a role to a service account",
|
||||
Request: new(serviceaccounttypes.PostableServiceAccountRole),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceRole,
|
||||
AttachedResourceID: handler.BodyJSONPath("id"),
|
||||
TargetResource: coretypes.ResourceServiceAccount,
|
||||
TargetResourceID: handler.PathParam("id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles/{rid}", handler.New(
|
||||
provider.authzMiddleware.CheckAll(provider.serviceAccountHandler.DeleteRole, []middleware.AuthZCheckGroup{
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceServiceAccount, SelectorCallback: serviceAccountInstanceSelectorCallback, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceRole, SelectorCallback: provider.roleAttachSelectorFromPath, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteServiceAccountRole",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Delete service account role",
|
||||
Description: "This endpoint revokes a role from service account",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceRole,
|
||||
AttachedResourceID: handler.PathParam("rid"),
|
||||
TargetResource: coretypes.ResourceServiceAccount,
|
||||
TargetResourceID: handler.PathParam("id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.UpdateMe),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateMyServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates my service account",
|
||||
Description: "This endpoint gets my service account",
|
||||
Request: new(serviceaccounttypes.UpdatableServiceAccount),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: nil,
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceServiceAccount,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(
|
||||
provider.authzMiddleware.Check(provider.serviceAccountHandler.Update, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceRole, SelectorCallback: provider.roleAttachSelectorFromBody, Roles: []string{
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates a service account",
|
||||
Description: "This endpoint updates an existing service account",
|
||||
Request: new(serviceaccounttypes.UpdatableServiceAccount),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceServiceAccount,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(
|
||||
provider.authzMiddleware.Check(provider.serviceAccountHandler.Delete, authtypes.Relation{Verb: coretypes.VerbDelete}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccountRole",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create service account role",
|
||||
Description: "This endpoint assigns a role to a service account",
|
||||
Request: new(serviceaccounttypes.PostableServiceAccountRole),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Deletes a service account",
|
||||
Description: "This endpoint deletes an existing service account",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbDelete)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceServiceAccount,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/roles/{rid}", handler.New(provider.authzMiddleware.CheckAll(provider.serviceAccountHandler.DeleteRole, []middleware.AuthZCheckGroup{
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceServiceAccount, SelectorCallback: serviceAccountInstanceSelectorCallback, Roles: []string{
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(
|
||||
provider.authzMiddleware.Check(provider.serviceAccountHandler.CreateFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
{{Relation: authtypes.Relation{Verb: coretypes.VerbAttach}, Resource: coretypes.ResourceRole, SelectorCallback: provider.roleAttachSelectorFromPath, Roles: []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}}},
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "DeleteServiceAccountRole",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Delete service account role",
|
||||
Description: "This endpoint revokes a role from service account",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbAttach), coretypes.ResourceRole.Scope(coretypes.VerbAttach)}),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/me", handler.New(provider.authzMiddleware.OpenAccess(provider.serviceAccountHandler.UpdateMe), handler.OpenAPIDef{
|
||||
ID: "UpdateMyServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates my service account",
|
||||
Description: "This endpoint gets my service account",
|
||||
Request: new(serviceaccounttypes.UpdatableServiceAccount),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: nil,
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Update, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "UpdateServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates a service account",
|
||||
Description: "This endpoint updates an existing service account",
|
||||
Request: new(serviceaccounttypes.UpdatableServiceAccount),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.Delete, authtypes.Relation{Verb: coretypes.VerbDelete}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "DeleteServiceAccount",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Deletes a service account",
|
||||
Description: "This endpoint deletes an existing service account",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbDelete)}),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.CreateFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create a service account key",
|
||||
Description: "This endpoint creates a service account key",
|
||||
Request: new(serviceaccounttypes.PostableFactorAPIKey),
|
||||
RequestContentType: "",
|
||||
Response: new(serviceaccounttypes.GettableFactorAPIKeyWithKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Create a service account key",
|
||||
Description: "This endpoint creates a service account key",
|
||||
Request: new(serviceaccounttypes.PostableFactorAPIKey),
|
||||
RequestContentType: "",
|
||||
Response: new(serviceaccounttypes.GettableFactorAPIKeyWithKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithAuditDef(
|
||||
handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesFactorAPIKey,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
},
|
||||
handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceMetaResourceFactorAPIKey,
|
||||
AttachedResourceID: handler.ResponseJSONPath("data.id"),
|
||||
TargetResource: coretypes.ResourceServiceAccount,
|
||||
TargetResourceID: handler.PathParam("id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -251,41 +328,59 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.UpdateFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "UpdateServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates a service account key",
|
||||
Description: "This endpoint updates an existing service account key",
|
||||
Request: new(serviceaccounttypes.UpdatableFactorAPIKey),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(
|
||||
provider.authzMiddleware.Check(provider.serviceAccountHandler.UpdateFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Updates a service account key",
|
||||
Description: "This endpoint updates an existing service account key",
|
||||
Request: new(serviceaccounttypes.UpdatableFactorAPIKey),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceFactorAPIKey,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("fid"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authzMiddleware.Check(provider.serviceAccountHandler.RevokeFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "RevokeServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Revoke a service account key",
|
||||
Description: "This endpoint revokes an existing service account key",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(
|
||||
provider.authzMiddleware.Check(provider.serviceAccountHandler.RevokeFactorAPIKey, authtypes.Relation{Verb: coretypes.VerbUpdate}, coretypes.ResourceServiceAccount, serviceAccountInstanceSelectorCallback, []string{
|
||||
authtypes.SigNozAdminRoleName,
|
||||
}),
|
||||
handler.OpenAPIDef{
|
||||
ID: "RevokeServiceAccountKey",
|
||||
Tags: []string{"serviceaccount"},
|
||||
Summary: "Revoke a service account key",
|
||||
Description: "This endpoint revokes an existing service account key",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceServiceAccount.Scope(coretypes.VerbUpdate)}),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceFactorAPIKey,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,25 +4,35 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/sessions/email_password", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByEmailPassword), handler.OpenAPIDef{
|
||||
ID: "CreateSessionByEmailPassword",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by email and password",
|
||||
Description: "This endpoint creates a session for a user using email and password.",
|
||||
Request: new(authtypes.PostableEmailPasswordSession),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(authtypes.GettableToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/sessions/email_password", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.sessionHandler.CreateSessionByEmailPassword),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateSessionByEmailPassword",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Create session by email and password",
|
||||
Description: "This endpoint creates a session for a user using email and password.",
|
||||
Request: new(authtypes.PostableEmailPasswordSession),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(authtypes.GettableToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesSession,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,37 +53,53 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.RotateSession), handler.OpenAPIDef{
|
||||
ID: "RotateSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Rotate session",
|
||||
Description: "This endpoint rotates the session",
|
||||
Request: new(authtypes.PostableRotateToken),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(authtypes.GettableToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.sessionHandler.RotateSession),
|
||||
handler.OpenAPIDef{
|
||||
ID: "RotateSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Rotate session",
|
||||
Description: "This endpoint rotates the session",
|
||||
Request: new(authtypes.PostableRotateToken),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(authtypes.GettableToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceSession,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(provider.authzMiddleware.OpenAccess(provider.sessionHandler.DeleteSession), handler.OpenAPIDef{
|
||||
ID: "DeleteSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Delete session",
|
||||
Description: "This endpoint deletes the session",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.sessionHandler.DeleteSession),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteSession",
|
||||
Tags: []string{"sessions"},
|
||||
Summary: "Delete session",
|
||||
Description: "This endpoint deletes the session",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceSession,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/spantypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -47,6 +49,12 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesSpanMapperGroup,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,6 +73,12 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceSpanMapperGroup,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("groupId"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -85,6 +99,12 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceSpanMapperGroup,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("groupId"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,6 +145,22 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(
|
||||
handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourcesSpanMapper,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.ResponseJSONPath("data.id"),
|
||||
},
|
||||
handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceMetaResourceSpanMapper,
|
||||
AttachedResourceID: handler.ResponseJSONPath("data.id"),
|
||||
TargetResource: coretypes.ResourceMetaResourceSpanMapperGroup,
|
||||
TargetResourceID: handler.PathParam("groupId"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +179,12 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceSpanMapper,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("mapperId"),
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -163,6 +205,12 @@ func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceSpanMapper,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("mapperId"),
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -111,20 +113,28 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.UpdateMyUser), handler.OpenAPIDef{
|
||||
ID: "UpdateMyUserV2",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update my user v2",
|
||||
Description: "This endpoint updates the user I belong to",
|
||||
Request: new(types.UpdatableUser),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.userHandler.UpdateMyUser),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateMyUserV2",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update my user v2",
|
||||
Description: "This endpoint updates the user I belong to",
|
||||
Request: new(types.UpdatableUser),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceUser,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -179,20 +189,29 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.UpdateUser), handler.OpenAPIDef{
|
||||
ID: "UpdateUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update user v2",
|
||||
Description: "This endpoint updates the user by id",
|
||||
Request: new(types.UpdatableUser),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.userHandler.UpdateUser),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update user v2",
|
||||
Description: "This endpoint updates the user by id",
|
||||
Request: new(types.UpdatableUser),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceUser,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -247,20 +266,29 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.CreateResetPasswordToken), handler.OpenAPIDef{
|
||||
ID: "CreateResetPasswordToken",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create or regenerate reset password token for a user",
|
||||
Description: "This endpoint creates or regenerates a reset password token for a user. If a valid token exists, it is returned. If expired, a new one is created.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.ResetPasswordToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/users/{id}/reset_password_tokens", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.userHandler.CreateResetPasswordToken),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateResetPasswordToken",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create or regenerate reset password token for a user",
|
||||
Description: "This endpoint creates or regenerates a reset password token for a user. If a valid token exists, it is returned. If expired, a new one is created.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.ResetPasswordToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceFactorPassword,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceID: handler.PathParam("id"),
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -281,37 +309,53 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ChangePassword), handler.OpenAPIDef{
|
||||
ID: "UpdateMyPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Updates my password",
|
||||
Description: "This endpoint updates the password of the user I belong to",
|
||||
Request: new(types.ChangePasswordRequest),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.userHandler.ChangePassword),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateMyPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Updates my password",
|
||||
Description: "This endpoint updates the password of the user I belong to",
|
||||
Request: new(types.ChangePasswordRequest),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceFactorPassword,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.ForgotPassword), handler.OpenAPIDef{
|
||||
ID: "ForgotPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Forgot password",
|
||||
Description: "This endpoint initiates the forgot password flow by sending a reset password email",
|
||||
Request: new(types.PostableForgotPassword),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnprocessableEntity},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(
|
||||
provider.authzMiddleware.OpenAccess(provider.userHandler.ForgotPassword),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ForgotPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Forgot password",
|
||||
Description: "This endpoint initiates the forgot password flow by sending a reset password email",
|
||||
Request: new(types.PostableForgotPassword),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnprocessableEntity},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceFactorPassword,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -332,37 +376,59 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.SetRoleByUserID), handler.OpenAPIDef{
|
||||
ID: "SetRoleByUserID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Set user roles",
|
||||
Description: "This endpoint assigns the role to the user roles by user id",
|
||||
Request: new(types.PostableRole),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.userHandler.SetRoleByUserID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "SetRoleByUserID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Set user roles",
|
||||
Description: "This endpoint assigns the role to the user roles by user id",
|
||||
Request: new(types.PostableRole),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceRole,
|
||||
AttachedResourceID: handler.BodyJSONPath("name"),
|
||||
TargetResource: coretypes.ResourceUser,
|
||||
TargetResourceID: handler.PathParam("id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles/{roleId}", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.RemoveUserRoleByRoleID), handler.OpenAPIDef{
|
||||
ID: "RemoveUserRoleByUserIDAndRoleID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Remove a role from user",
|
||||
Description: "This endpoint removes a role from the user by user id and role id",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/users/{id}/roles/{roleId}", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.userHandler.RemoveUserRoleByRoleID),
|
||||
handler.OpenAPIDef{
|
||||
ID: "RemoveUserRoleByUserIDAndRoleID",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Remove a role from user",
|
||||
Description: "This endpoint removes a role from the user by user id and role id",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.AttachAuditDef{
|
||||
AttachedResource: coretypes.ResourceRole,
|
||||
AttachedResourceID: handler.PathParam("roleId"),
|
||||
TargetResource: coretypes.ResourceUser,
|
||||
TargetResourceID: handler.PathParam("id"),
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,25 +5,35 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/zeustypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addZeusRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/zeus/profiles", handler.New(provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutProfile), handler.OpenAPIDef{
|
||||
ID: "PutProfile",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Put profile in Zeus for a deployment.",
|
||||
Description: "This endpoint saves the profile of a deployment to zeus.",
|
||||
Request: new(zeustypes.PostableProfile),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/zeus/profiles", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutProfile),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PutProfile",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Put profile in Zeus for a deployment.",
|
||||
Description: "This endpoint saves the profile of a deployment to zeus.",
|
||||
Request: new(zeustypes.PostableProfile),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceZeusProfile,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategorySystemEvent,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -44,20 +54,28 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutHost), handler.OpenAPIDef{
|
||||
ID: "PutHost",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Put host in Zeus for a deployment.",
|
||||
Description: "This endpoint saves the host of a deployment to zeus.",
|
||||
Request: new(zeustypes.PostableHost),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/zeus/hosts", handler.New(
|
||||
provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutHost),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PutHost",
|
||||
Tags: []string{"zeus"},
|
||||
Summary: "Put host in Zeus for a deployment.",
|
||||
Description: "This endpoint saves the host of a deployment to zeus.",
|
||||
Request: new(zeustypes.PostableHost),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.BasicAuditDef{
|
||||
Resource: coretypes.ResourceMetaResourceZeusHost,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: audittypes.ActionCategorySystemEvent,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
package auditorserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newTestSettings() factory.ScopedProviderSettings {
|
||||
return factory.NewScopedProviderSettings(instrumentationtest.New().ToProviderSettings(), "auditorserver_test")
|
||||
}
|
||||
|
||||
func newTestEvent(resource string, action coretypes.Verb) audittypes.AuditEvent {
|
||||
return audittypes.AuditEvent{
|
||||
Timestamp: time.Now(),
|
||||
EventName: audittypes.NewEventName(coretypes.MustNewKind(resource), action),
|
||||
AuditAttributes: audittypes.AuditAttributes{
|
||||
Action: action,
|
||||
Outcome: audittypes.OutcomeSuccess,
|
||||
},
|
||||
ResourceAttributes: audittypes.ResourceAttributes{
|
||||
ResourceKind: coretypes.MustNewKind(resource),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 10, BatchSize: 5, FlushInterval: time.Second}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, _ []audittypes.AuditEvent) error { return nil })
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, server)
|
||||
}
|
||||
|
||||
func TestStart_Stop(t *testing.T) {
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 10, BatchSize: 5, FlushInterval: time.Second}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, _ []audittypes.AuditEvent) error { return nil })
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- server.Start(context.Background()) }()
|
||||
|
||||
require.NoError(t, server.Stop(context.Background()))
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
assert.NoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
assert.Fail(t, "Start did not return after Stop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd_FlushesOnBatchSize(t *testing.T) {
|
||||
var exported []audittypes.AuditEvent
|
||||
var mu sync.Mutex
|
||||
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 100, BatchSize: 3, FlushInterval: time.Hour}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, events []audittypes.AuditEvent) error {
|
||||
mu.Lock()
|
||||
exported = append(exported, events...)
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbCreate))
|
||||
}
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return len(exported) == 3
|
||||
}, 2*time.Second, 10*time.Millisecond)
|
||||
|
||||
require.NoError(t, server.Stop(ctx))
|
||||
}
|
||||
|
||||
func TestAdd_FlushesOnInterval(t *testing.T) {
|
||||
var exported atomic.Int64
|
||||
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 100, BatchSize: 1000, FlushInterval: 50 * time.Millisecond}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, events []audittypes.AuditEvent) error {
|
||||
exported.Add(int64(len(events)))
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
server.Add(ctx, newTestEvent("user", coretypes.VerbUpdate))
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
return exported.Load() == 1
|
||||
}, 2*time.Second, 10*time.Millisecond)
|
||||
|
||||
require.NoError(t, server.Stop(ctx))
|
||||
}
|
||||
|
||||
func TestAdd_DropsWhenBufferFull(t *testing.T) {
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 2, BatchSize: 100, FlushInterval: time.Hour}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, _ []audittypes.AuditEvent) error { return nil })
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbCreate))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbUpdate))
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbDelete))
|
||||
|
||||
assert.Equal(t, 2, server.queueLen())
|
||||
}
|
||||
|
||||
func TestStop_DrainsRemainingEvents(t *testing.T) {
|
||||
var exported atomic.Int64
|
||||
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 100, BatchSize: 100, FlushInterval: time.Hour}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, events []audittypes.AuditEvent) error {
|
||||
exported.Add(int64(len(events)))
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
server.Add(ctx, newTestEvent("alert-rule", coretypes.VerbCreate))
|
||||
}
|
||||
|
||||
require.NoError(t, server.Stop(ctx))
|
||||
|
||||
assert.Equal(t, int64(5), exported.Load())
|
||||
}
|
||||
|
||||
func TestAdd_ContinuesAfterExportFailure(t *testing.T) {
|
||||
var calls atomic.Int64
|
||||
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 100, BatchSize: 2, FlushInterval: time.Hour}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, _ []audittypes.AuditEvent) error {
|
||||
calls.Add(1)
|
||||
return errors.New(errors.TypeInternal, errors.CodeInternal, "connection refused")
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
server.Add(ctx, newTestEvent("user", coretypes.VerbDelete))
|
||||
server.Add(ctx, newTestEvent("user", coretypes.VerbDelete))
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
return calls.Load() >= 1
|
||||
}, 2*time.Second, 10*time.Millisecond)
|
||||
|
||||
require.NoError(t, server.Stop(ctx))
|
||||
}
|
||||
|
||||
func TestAdd_ConcurrentSafety(t *testing.T) {
|
||||
var exported atomic.Int64
|
||||
|
||||
settings := newTestSettings()
|
||||
config := Config{BufferSize: 1000, BatchSize: 10, FlushInterval: 50 * time.Millisecond}
|
||||
|
||||
server, err := New(settings, config, func(_ context.Context, events []audittypes.AuditEvent) error {
|
||||
exported.Add(int64(len(events)))
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() { _ = server.Start(ctx) }()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
server.Add(ctx, newTestEvent("dashboard", coretypes.VerbCreate))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
require.NoError(t, server.Stop(ctx))
|
||||
|
||||
assert.Equal(t, int64(100), exported.Load())
|
||||
}
|
||||
@@ -25,6 +25,8 @@ type Config struct {
|
||||
FlushInterval time.Duration `mapstructure:"flush_interval"`
|
||||
|
||||
OTLPHTTP OTLPHTTPConfig `mapstructure:"otlphttp"`
|
||||
|
||||
File FileConfig `mapstructure:"file"`
|
||||
}
|
||||
|
||||
// OTLPHTTPConfig holds configuration for the OTLP HTTP exporter provider.
|
||||
@@ -46,6 +48,12 @@ type OTLPHTTPConfig struct {
|
||||
Retry RetryConfig `mapstructure:"retry"`
|
||||
}
|
||||
|
||||
type FileConfig struct {
|
||||
// Path is the absolute path to the audit log file. The file is opened with
|
||||
// O_APPEND|O_CREATE|O_WRONLY; existing contents are preserved across runs.
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
// RetryConfig configures exponential backoff for the OTLP HTTP exporter.
|
||||
type RetryConfig struct {
|
||||
// Enabled controls whether retries are attempted on transient failures.
|
||||
@@ -111,5 +119,11 @@ func (c Config) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Provider == "file" {
|
||||
if c.File.Path == "" {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "auditor::file::path must be set when provider is file")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ type ServeOpenAPIFunc func(openapi.OperationContext)
|
||||
type Handler interface {
|
||||
http.Handler
|
||||
ServeOpenAPI(openapi.OperationContext)
|
||||
AuditDef() *AuditDef
|
||||
AuditDefs() []AuditDef
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
handlerFunc http.HandlerFunc
|
||||
openAPIDef OpenAPIDef
|
||||
auditDef *AuditDef
|
||||
auditDefs []AuditDef
|
||||
}
|
||||
|
||||
func New(handlerFunc http.HandlerFunc, openAPIDef OpenAPIDef, opts ...Option) Handler {
|
||||
@@ -130,6 +130,6 @@ func (handler *handler) ServeOpenAPI(opCtx openapi.OperationContext) {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *handler) AuditDef() *AuditDef {
|
||||
return handler.auditDef
|
||||
func (handler *handler) AuditDefs() []AuditDef {
|
||||
return handler.auditDefs
|
||||
}
|
||||
|
||||
@@ -1,25 +1,139 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// Option configures optional behaviour on a handler created by New.
|
||||
type Option func(*handler)
|
||||
|
||||
type AuditDef struct {
|
||||
ResourceKind coretypes.Kind // Typeable.Kind() value, e.g. "dashboard", "user".
|
||||
Action coretypes.Verb // create, update, delete, etc.
|
||||
Category audittypes.ActionCategory // access_control, configuration_change, etc.
|
||||
ResourceIDParam string // Gorilla mux path param name for the resource ID.
|
||||
// ExtractorContext carries everything a ResourceIDExtractor might read out of
|
||||
// a request/response cycle. The middleware pre-buffers the request body and
|
||||
// captures the response body so post-handler extraction works on both sides.
|
||||
type ExtractorContext struct {
|
||||
Request *http.Request
|
||||
RequestBody []byte
|
||||
ResponseBody []byte
|
||||
}
|
||||
|
||||
// WithAudit attaches an AuditDef to the handler. The actual audit event
|
||||
// emission is handled by the middleware layer, which reads the AuditDef
|
||||
// from the matched route's handler.
|
||||
func WithAuditDef(def AuditDef) Option {
|
||||
return func(h *handler) {
|
||||
h.auditDef = &def
|
||||
// ResourceIDExtractor pulls a resource id from an incoming request and/or its
|
||||
// response. Returns an empty string with no error when the id source is
|
||||
// genuinely absent (e.g. "me" routes that act on the caller without an id).
|
||||
type ResourceIDExtractor func(ExtractorContext) (string, error)
|
||||
|
||||
// ResourceIDsExtractor pulls a list of resource ids. Used by AttachManyAuditDef
|
||||
// to fan out one audit event per attached entity referenced in a request body.
|
||||
type ResourceIDsExtractor func(ExtractorContext) ([]string, error)
|
||||
|
||||
// PathParam returns an extractor that reads a Gorilla mux path variable.
|
||||
func PathParam(name string) ResourceIDExtractor {
|
||||
return func(ctx ExtractorContext) (string, error) {
|
||||
vars := mux.Vars(ctx.Request)
|
||||
if vars == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return vars[name], nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodyJSONPath returns an extractor that reads a JSON path from the request
|
||||
// body via gjson. The middleware buffers the request body before forwarding
|
||||
// to the handler, so this extractor still works after the handler runs.
|
||||
func BodyJSONPath(path string) ResourceIDExtractor {
|
||||
return func(ctx ExtractorContext) (string, error) {
|
||||
return gjson.GetBytes(ctx.RequestBody, path).String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseJSONPath returns an extractor that reads a JSON path from the
|
||||
// response body via gjson. Useful for Create routes where the new resource id
|
||||
// is only known after the handler runs and writes the response payload.
|
||||
func ResponseJSONPath(path string) ResourceIDExtractor {
|
||||
return func(ctx ExtractorContext) (string, error) {
|
||||
return gjson.GetBytes(ctx.ResponseBody, path).String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodyJSONArray returns a multi-id extractor that reads a JSON array of
|
||||
// strings out of the request body at the given gjson path.
|
||||
func BodyJSONArray(path string) ResourceIDsExtractor {
|
||||
return func(ctx ExtractorContext) ([]string, error) {
|
||||
result := gjson.GetBytes(ctx.RequestBody, path)
|
||||
if !result.Exists() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
array := result.Array()
|
||||
ids := make([]string, 0, len(array))
|
||||
for _, r := range array {
|
||||
ids = append(ids, r.String())
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AuditDef is a sealed interface implemented by BasicAuditDef and
|
||||
// AttachAuditDef. The middleware type-switches over its implementations to
|
||||
// build the audit event for the matched route.
|
||||
type AuditDef interface {
|
||||
sealAuditDef()
|
||||
}
|
||||
|
||||
// BasicAuditDef declares audit metadata for routes that operate on a single
|
||||
// resource. EventName is derived as Resource.Kind() + "." + Verb.PastTense().
|
||||
type BasicAuditDef struct {
|
||||
Resource coretypes.Resource
|
||||
Verb coretypes.Verb
|
||||
Category audittypes.ActionCategory
|
||||
ResourceID ResourceIDExtractor // nil for collection routes with no addressable id
|
||||
}
|
||||
|
||||
func (BasicAuditDef) sealAuditDef() {}
|
||||
|
||||
// AttachAuditDef declares audit metadata for routes that attach one resource
|
||||
// to another (e.g. role attached to a user). The event subject is the
|
||||
// attached resource; the target carries where it was attached. EventName is
|
||||
// derived as AttachedResource.Kind() + "." + Verb.PastTense().
|
||||
type AttachAuditDef struct {
|
||||
AttachedResource coretypes.Resource
|
||||
AttachedResourceID ResourceIDExtractor
|
||||
TargetResource coretypes.Resource
|
||||
TargetResourceID ResourceIDExtractor
|
||||
Verb coretypes.Verb
|
||||
Category audittypes.ActionCategory
|
||||
}
|
||||
|
||||
func (AttachAuditDef) sealAuditDef() {}
|
||||
|
||||
// AttachManyAuditDef declares that a single request attaches many of the same
|
||||
// kind of resource to one target. The middleware fans out one attach event per
|
||||
// id returned by AttachedResourceIDs. Used for routes whose body carries a
|
||||
// list of references (e.g. rule preferredChannels, planned-maintenance
|
||||
// alertIds, route-policy channels).
|
||||
type AttachManyAuditDef struct {
|
||||
AttachedResource coretypes.Resource
|
||||
AttachedResourceIDs ResourceIDsExtractor
|
||||
TargetResource coretypes.Resource
|
||||
TargetResourceID ResourceIDExtractor
|
||||
Verb coretypes.Verb
|
||||
Category audittypes.ActionCategory
|
||||
}
|
||||
|
||||
func (AttachManyAuditDef) sealAuditDef() {}
|
||||
|
||||
// WithAuditDef attaches one or more AuditDef declarations to the handler. A
|
||||
// single route can produce multiple audit events — e.g. creating a resource
|
||||
// that is simultaneously attached to a parent emits one BasicAuditDef and one
|
||||
// AttachAuditDef. The middleware emits one event per def in declaration order.
|
||||
func WithAuditDef(defs ...AuditDef) Option {
|
||||
return func(h *handler) {
|
||||
h.auditDefs = append(h.auditDefs, defs...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -16,6 +18,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,6 +62,14 @@ func (middleware *Audit) Wrap(next http.Handler) http.Handler {
|
||||
string(semconv.HTTPRouteKey), path,
|
||||
}
|
||||
|
||||
// Pre-buffer the request body if the route declares any AuditDefs that
|
||||
// might want to extract from it after the handler has consumed the body.
|
||||
var requestBody []byte
|
||||
if len(auditDefsFromRequest(req)) > 0 && req.Body != nil {
|
||||
requestBody, _ = io.ReadAll(req.Body)
|
||||
req.Body = io.NopCloser(bytes.NewReader(requestBody))
|
||||
}
|
||||
|
||||
responseBuffer := &byteBuffer{}
|
||||
writer := newResponseCapture(rw, responseBuffer)
|
||||
next.ServeHTTP(writer, req)
|
||||
@@ -70,7 +81,7 @@ func (middleware *Audit) Wrap(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
middleware.emitAuditEvent(req, writer, path)
|
||||
middleware.emitAuditEvent(req, writer, path, requestBody)
|
||||
|
||||
fields = append(fields,
|
||||
string(semconv.HTTPResponseStatusCodeKey), statusCode,
|
||||
@@ -89,51 +100,66 @@ func (middleware *Audit) Wrap(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *Audit) emitAuditEvent(req *http.Request, writer responseCapture, routeTemplate string) {
|
||||
func (middleware *Audit) emitAuditEvent(req *http.Request, writer responseCapture, routeTemplate string, requestBody []byte) {
|
||||
if middleware.auditor == nil {
|
||||
return
|
||||
}
|
||||
|
||||
def := auditDefFromRequest(req)
|
||||
if def == nil {
|
||||
defs := auditDefsFromRequest(req)
|
||||
if len(defs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// extract claims
|
||||
claims, _ := authtypes.ClaimsFromContext(req.Context())
|
||||
|
||||
// extract status code
|
||||
statusCode := writer.StatusCode()
|
||||
|
||||
// extract traces.
|
||||
span := trace.SpanFromContext(req.Context())
|
||||
|
||||
// extract error details.
|
||||
var errorType, errorCode string
|
||||
if statusCode >= 400 {
|
||||
errorType = render.ErrorTypeFromStatusCode(statusCode)
|
||||
errorCode = render.ErrorCodeFromBody(writer.BodyBytes())
|
||||
}
|
||||
|
||||
event := audittypes.NewAuditEventFromHTTPRequest(
|
||||
req,
|
||||
routeTemplate,
|
||||
statusCode,
|
||||
span.SpanContext().TraceID(),
|
||||
span.SpanContext().SpanID(),
|
||||
def.Action,
|
||||
def.Category,
|
||||
claims,
|
||||
resourceIDFromRequest(req, def.ResourceIDParam),
|
||||
def.ResourceKind,
|
||||
errorType,
|
||||
errorCode,
|
||||
)
|
||||
extractorCtx := handler.ExtractorContext{
|
||||
Request: req,
|
||||
RequestBody: requestBody,
|
||||
ResponseBody: writer.BodyBytes(),
|
||||
}
|
||||
|
||||
middleware.auditor.Audit(req.Context(), event)
|
||||
for _, def := range defs {
|
||||
resolved, err := resolveAuditDef(extractorCtx, def)
|
||||
if err != nil {
|
||||
middleware.logger.WarnContext(req.Context(), "audit event dropped — resource id extraction failed", errors.Attr(err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, r := range resolved {
|
||||
event := audittypes.NewAuditEventFromHTTPRequest(
|
||||
req,
|
||||
routeTemplate,
|
||||
statusCode,
|
||||
span.SpanContext().TraceID(),
|
||||
span.SpanContext().SpanID(),
|
||||
r.Verb,
|
||||
r.Category,
|
||||
claims,
|
||||
r.ResourceAttributes,
|
||||
errorType,
|
||||
errorCode,
|
||||
)
|
||||
|
||||
middleware.auditor.Audit(req.Context(), event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func auditDefFromRequest(req *http.Request) *handler.AuditDef {
|
||||
type resolvedAuditDef struct {
|
||||
Verb coretypes.Verb
|
||||
Category audittypes.ActionCategory
|
||||
ResourceAttributes audittypes.ResourceAttributes
|
||||
}
|
||||
|
||||
func auditDefsFromRequest(req *http.Request) []handler.AuditDef {
|
||||
route := mux.CurrentRoute(req)
|
||||
if route == nil {
|
||||
return nil
|
||||
@@ -152,18 +178,76 @@ func auditDefFromRequest(req *http.Request) *handler.AuditDef {
|
||||
return nil
|
||||
}
|
||||
|
||||
return provider.AuditDef()
|
||||
return provider.AuditDefs()
|
||||
}
|
||||
|
||||
func resourceIDFromRequest(req *http.Request, param string) string {
|
||||
if param == "" {
|
||||
return ""
|
||||
func resolveAuditDef(ctx handler.ExtractorContext, def handler.AuditDef) ([]resolvedAuditDef, error) {
|
||||
switch d := def.(type) {
|
||||
case handler.BasicAuditDef:
|
||||
resourceID, err := extractResourceID(ctx, d.ResourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []resolvedAuditDef{{
|
||||
Verb: d.Verb,
|
||||
Category: d.Category,
|
||||
ResourceAttributes: audittypes.NewResourceAttributes(d.Resource, resourceID),
|
||||
}}, nil
|
||||
case handler.AttachAuditDef:
|
||||
attachedID, err := extractResourceID(ctx, d.AttachedResourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetID, err := extractResourceID(ctx, d.TargetResourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []resolvedAuditDef{{
|
||||
Verb: d.Verb,
|
||||
Category: d.Category,
|
||||
ResourceAttributes: audittypes.NewAttachResourceAttributes(d.AttachedResource, attachedID, d.TargetResource, targetID),
|
||||
}}, nil
|
||||
case handler.AttachManyAuditDef:
|
||||
ids, err := extractResourceIDs(ctx, d.AttachedResourceIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetID, err := extractResourceID(ctx, d.TargetResourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolved := make([]resolvedAuditDef, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
resolved = append(resolved, resolvedAuditDef{
|
||||
Verb: d.Verb,
|
||||
Category: d.Category,
|
||||
ResourceAttributes: audittypes.NewAttachResourceAttributes(d.AttachedResource, id, d.TargetResource, targetID),
|
||||
})
|
||||
}
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
if vars == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return vars[param]
|
||||
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "unknown AuditDef implementation %T", def)
|
||||
}
|
||||
|
||||
func extractResourceID(ctx handler.ExtractorContext, extractor handler.ResourceIDExtractor) (string, error) {
|
||||
if extractor == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return extractor(ctx)
|
||||
}
|
||||
|
||||
func extractResourceIDs(ctx handler.ExtractorContext, extractor handler.ResourceIDsExtractor) ([]string, error) {
|
||||
if extractor == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return extractor(ctx)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func auditFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
|
||||
return map[string][]*telemetrytypes.TelemetryFieldKey{
|
||||
"service.name": {key("service.name", res, str, false)},
|
||||
"signoz.audit.action": {key("signoz.audit.action", attr, str, true)},
|
||||
"signoz.audit.verb": {key("signoz.audit.verb", attr, str, true)},
|
||||
"signoz.audit.outcome": {key("signoz.audit.outcome", attr, str, true)},
|
||||
"signoz.audit.principal.email": {key("signoz.audit.principal.email", attr, str, true)},
|
||||
"signoz.audit.principal.id": {key("signoz.audit.principal.id", attr, str, true)},
|
||||
@@ -131,20 +131,20 @@ func TestStatementBuilder(t *testing.T) {
|
||||
Args: []any{"dashboard", "%signoz.audit.resource.kind%", "%signoz.audit.resource.kind\":\"dashboard%", "019b-5678-efgh-9012", "%signoz.audit.resource.id%", "%signoz.audit.resource.id\":\"019b-5678-efgh-9012%", uint64(1747945619), uint64(1747983448), "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 100},
|
||||
},
|
||||
},
|
||||
// List: all dashboard deletions (compliance — resource.kind + action AND)
|
||||
// List: all dashboard deletions (compliance — resource.kind + verb AND)
|
||||
{
|
||||
name: "ListByResourceKindAndAction",
|
||||
name: "ListByResourceKindAndVerb",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Source: telemetrytypes.SourceAudit,
|
||||
Filter: &qbtypes.Filter{
|
||||
Expression: "signoz.audit.resource.kind = 'dashboard' AND signoz.audit.action = 'delete'",
|
||||
Expression: "signoz.audit.resource.kind = 'dashboard' AND signoz.audit.verb = 'delete'",
|
||||
},
|
||||
Limit: 100,
|
||||
},
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_audit.distributed_logs_resource WHERE (simpleJSONExtractString(labels, 'signoz.audit.resource.kind') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, event_name, attributes_string, attributes_number, attributes_bool, resource, scope_string FROM signoz_audit.distributed_logs WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (`attribute_string_signoz$$audit$$action` = ? AND `attribute_string_signoz$$audit$$action_exists` = ?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_audit.distributed_logs_resource WHERE (simpleJSONExtractString(labels, 'signoz.audit.resource.kind') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, event_name, attributes_string, attributes_number, attributes_bool, resource, scope_string FROM signoz_audit.distributed_logs WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (`attribute_string_signoz$$audit$$verb` = ? AND `attribute_string_signoz$$audit$$verb_exists` = ?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{"dashboard", "%signoz.audit.resource.kind%", "%signoz.audit.resource.kind\":\"dashboard%", uint64(1747945619), uint64(1747983448), "delete", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 100},
|
||||
},
|
||||
},
|
||||
@@ -165,23 +165,23 @@ func TestStatementBuilder(t *testing.T) {
|
||||
Args: []any{"service_account", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 100},
|
||||
},
|
||||
},
|
||||
// Scalar: alert — count forbidden errors (outcome + action AND)
|
||||
// Scalar: alert — count forbidden errors (outcome + verb AND)
|
||||
{
|
||||
name: "ScalarCountByOutcomeAndAction",
|
||||
name: "ScalarCountByOutcomeAndVerb",
|
||||
requestType: qbtypes.RequestTypeScalar,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Source: telemetrytypes.SourceAudit,
|
||||
StepInterval: qbtypes.Step{Duration: 60 * time.Second},
|
||||
Filter: &qbtypes.Filter{
|
||||
Expression: "signoz.audit.outcome = 'failure' AND signoz.audit.action = 'update'",
|
||||
Expression: "signoz.audit.outcome = 'failure' AND signoz.audit.verb = 'update'",
|
||||
},
|
||||
Aggregations: []qbtypes.LogAggregation{
|
||||
{Expression: "count()"},
|
||||
},
|
||||
},
|
||||
expected: qbtypes.Statement{
|
||||
Query: "SELECT count() AS __result_0 FROM signoz_audit.distributed_logs WHERE ((`attribute_string_signoz$$audit$$outcome` = ? AND `attribute_string_signoz$$audit$$outcome_exists` = ?) AND (`attribute_string_signoz$$audit$$action` = ? AND `attribute_string_signoz$$audit$$action_exists` = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? ORDER BY __result_0 DESC",
|
||||
Query: "SELECT count() AS __result_0 FROM signoz_audit.distributed_logs WHERE ((`attribute_string_signoz$$audit$$outcome` = ? AND `attribute_string_signoz$$audit$$outcome_exists` = ?) AND (`attribute_string_signoz$$audit$$verb` = ? AND `attribute_string_signoz$$audit$$verb_exists` = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? ORDER BY __result_0 DESC",
|
||||
Args: []any{"failure", true, "update", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,22 +11,22 @@ import (
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
)
|
||||
|
||||
// Audit attributes — Action (What).
|
||||
// Audit attributes — Verb (What).
|
||||
type AuditAttributes struct {
|
||||
Action coretypes.Verb // guaranteed to be present
|
||||
Verb coretypes.Verb // guaranteed to be present
|
||||
ActionCategory ActionCategory // guaranteed to be present
|
||||
Outcome Outcome // guaranteed to be present
|
||||
IdentNProvider authtypes.IdentNProvider
|
||||
}
|
||||
|
||||
func NewAuditAttributesFromHTTP(statusCode int, action coretypes.Verb, category ActionCategory, claims authtypes.Claims) AuditAttributes {
|
||||
func NewAuditAttributesFromHTTP(statusCode int, verb coretypes.Verb, category ActionCategory, claims authtypes.Claims) AuditAttributes {
|
||||
outcome := OutcomeFailure
|
||||
if statusCode >= 200 && statusCode < 400 {
|
||||
outcome = OutcomeSuccess
|
||||
}
|
||||
|
||||
return AuditAttributes{
|
||||
Action: action,
|
||||
Verb: verb,
|
||||
ActionCategory: category,
|
||||
Outcome: outcome,
|
||||
IdentNProvider: claims.IdentNProvider,
|
||||
@@ -34,7 +34,7 @@ func NewAuditAttributesFromHTTP(statusCode int, action coretypes.Verb, category
|
||||
}
|
||||
|
||||
func (attributes AuditAttributes) Put(dest pcommon.Map) {
|
||||
dest.PutStr("signoz.audit.action", attributes.Action.StringValue())
|
||||
dest.PutStr("signoz.audit.verb", attributes.Verb.StringValue())
|
||||
dest.PutStr("signoz.audit.action_category", attributes.ActionCategory.StringValue())
|
||||
dest.PutStr("signoz.audit.outcome", attributes.Outcome.StringValue())
|
||||
putStrIfNotEmpty(dest, "signoz.audit.identn_provider", attributes.IdentNProvider.StringValue())
|
||||
@@ -70,24 +70,47 @@ func (attributes PrincipalAttributes) Put(dest pcommon.Map) {
|
||||
|
||||
// Audit attributes — Resource (On What).
|
||||
// These are OTel resource attributes (placed on the Resource, not event attributes).
|
||||
// For attach events, Target carries the resource the primary was attached to.
|
||||
type ResourceAttributes struct {
|
||||
ResourceID string
|
||||
ResourceKind coretypes.Kind // guaranteed to be present
|
||||
Resource coretypes.Resource // guaranteed to be present
|
||||
ResourceID string
|
||||
TargetResource coretypes.Resource // present only for attach events
|
||||
TargetResourceID string
|
||||
}
|
||||
|
||||
func NewResourceAttributes(resourceID string, resourceKind coretypes.Kind) ResourceAttributes {
|
||||
func NewResourceAttributes(resource coretypes.Resource, resourceID string) ResourceAttributes {
|
||||
return ResourceAttributes{
|
||||
ResourceID: resourceID,
|
||||
ResourceKind: resourceKind,
|
||||
Resource: resource,
|
||||
ResourceID: resourceID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAttachResourceAttributes(attachedResource coretypes.Resource, attachedResourceID string, targetResource coretypes.Resource, targetResourceID string) ResourceAttributes {
|
||||
return ResourceAttributes{
|
||||
Resource: attachedResource,
|
||||
ResourceID: attachedResourceID,
|
||||
TargetResource: targetResource,
|
||||
TargetResourceID: targetResourceID,
|
||||
}
|
||||
}
|
||||
|
||||
// PutResource writes the resource attributes to an OTel Resource's attribute map.
|
||||
// These are resource-level attributes (stored in the resource JSON column),
|
||||
// not event-level attributes (stored in attributes_string).
|
||||
func (attributes ResourceAttributes) PutResource(dest pcommon.Map) {
|
||||
putStrIfNotEmpty(dest, "signoz.audit.resource.kind", attributes.ResourceKind.String())
|
||||
func (attributes ResourceAttributes) PutResource(dest pcommon.Map, orgID valuer.UUID) {
|
||||
putStrIfNotEmpty(dest, "signoz.audit.resource.kind", attributes.Resource.Kind().String())
|
||||
putStrIfNotEmpty(dest, "signoz.audit.resource.id", attributes.ResourceID)
|
||||
if attributes.ResourceID != "" {
|
||||
dest.PutStr("signoz.audit.resource.object", attributes.Resource.Object(orgID, attributes.ResourceID))
|
||||
}
|
||||
|
||||
if attributes.TargetResource != nil {
|
||||
putStrIfNotEmpty(dest, "signoz.audit.target.kind", attributes.TargetResource.Kind().String())
|
||||
putStrIfNotEmpty(dest, "signoz.audit.target.id", attributes.TargetResourceID)
|
||||
if attributes.TargetResourceID != "" {
|
||||
dest.PutStr("signoz.audit.target.object", attributes.TargetResource.Object(orgID, attributes.TargetResourceID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audit attributes — Error (When outcome is failure)
|
||||
@@ -180,26 +203,37 @@ func newBody(auditAttributes AuditAttributes, principalAttributes PrincipalAttri
|
||||
b.WriteString(principalAttributes.PrincipalID.StringValue())
|
||||
}
|
||||
|
||||
// Action: " created" or " failed to create".
|
||||
// Verb: " created" or " failed to create".
|
||||
if b.Len() > 0 {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
if auditAttributes.Outcome == OutcomeSuccess {
|
||||
b.WriteString(auditAttributes.Action.PastTense())
|
||||
b.WriteString(auditAttributes.Verb.PastTense())
|
||||
} else {
|
||||
b.WriteString("failed to ")
|
||||
b.WriteString(auditAttributes.Action.StringValue())
|
||||
b.WriteString(auditAttributes.Verb.StringValue())
|
||||
}
|
||||
|
||||
// Resource: " kind (id)" or " kind".
|
||||
b.WriteString(" ")
|
||||
b.WriteString(resourceAttributes.ResourceKind.String())
|
||||
b.WriteString(resourceAttributes.Resource.Kind().String())
|
||||
if resourceAttributes.ResourceID != "" {
|
||||
b.WriteString(" (")
|
||||
b.WriteString(resourceAttributes.ResourceID)
|
||||
b.WriteString(")")
|
||||
}
|
||||
|
||||
// Target (attach events): " to kind (id)" or " to kind".
|
||||
if resourceAttributes.TargetResource != nil {
|
||||
b.WriteString(" to ")
|
||||
b.WriteString(resourceAttributes.TargetResource.Kind().String())
|
||||
if resourceAttributes.TargetResourceID != "" {
|
||||
b.WriteString(" (")
|
||||
b.WriteString(resourceAttributes.TargetResourceID)
|
||||
b.WriteString(")")
|
||||
}
|
||||
}
|
||||
|
||||
// Error suffix (failure only): ": type (code)" or ": type" or ": (code)" or omitted.
|
||||
if auditAttributes.Outcome == OutcomeFailure {
|
||||
errorType := errorAttributes.ErrorType
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
package audittypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewAuditAttributesFromHTTP_OutcomeBoundary(t *testing.T) {
|
||||
claims := authtypes.Claims{IdentNProvider: authtypes.IdentNProviderTokenizer}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
statusCode int
|
||||
expectedOutcome Outcome
|
||||
}{
|
||||
{
|
||||
name: "200_Success",
|
||||
statusCode: 200,
|
||||
expectedOutcome: OutcomeSuccess,
|
||||
},
|
||||
{
|
||||
name: "399_Success",
|
||||
statusCode: 399,
|
||||
expectedOutcome: OutcomeSuccess,
|
||||
},
|
||||
{
|
||||
name: "400_Failure",
|
||||
statusCode: 400,
|
||||
expectedOutcome: OutcomeFailure,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
attrs := NewAuditAttributesFromHTTP(testCase.statusCode, coretypes.VerbUpdate, ActionCategoryConfigurationChange, claims)
|
||||
assert.Equal(t, testCase.expectedOutcome, attrs.Outcome)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBody(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
auditAttributes AuditAttributes
|
||||
principalAttributes PrincipalAttributes
|
||||
resourceAttributes ResourceAttributes
|
||||
errorAttributes ErrorAttributes
|
||||
expectedBody string
|
||||
}{
|
||||
{
|
||||
name: "Success_EmptyResourceID",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbDelete,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.MustNewUUID("019a1234-abcd-7000-8000-567800000001"),
|
||||
PrincipalEmail: valuer.MustNewEmail("test@acme.com"),
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "test@acme.com (019a1234-abcd-7000-8000-567800000001) deleted dashboard",
|
||||
},
|
||||
{
|
||||
name: "Success_EmptyPrincipalEmail",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbDelete,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.MustNewUUID("019a1234-abcd-7000-8000-567800000001"),
|
||||
PrincipalEmail: valuer.Email{},
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "abd",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "019a1234-abcd-7000-8000-567800000001 deleted dashboard (abd)",
|
||||
},
|
||||
{
|
||||
name: "Success_EmptyPrincipalIDandEmail",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbDelete,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.UUID{},
|
||||
PrincipalEmail: valuer.Email{},
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "abd",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "deleted dashboard (abd)",
|
||||
},
|
||||
{
|
||||
name: "Success_AllPresent",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbCreate,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.MustNewUUID("019a1234-abcd-7000-8000-567800000001"),
|
||||
PrincipalEmail: valuer.MustNewEmail("alice@acme.com"),
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "019b-5678",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "alice@acme.com (019a1234-abcd-7000-8000-567800000001) created dashboard (019b-5678)",
|
||||
},
|
||||
{
|
||||
name: "Success_EmptyEverythingOptional",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbUpdate,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceKind: coretypes.MustNewKind("alert-rule"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "updated alert-rule",
|
||||
},
|
||||
{
|
||||
name: "Failure_AllPresent",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbUpdate,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeFailure,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.MustNewUUID("019aaaaa-bbbb-7000-8000-cccc00000002"),
|
||||
PrincipalEmail: valuer.MustNewEmail("viewer@acme.com"),
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "019b-5678",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{
|
||||
ErrorType: "forbidden",
|
||||
ErrorCode: "authz_forbidden",
|
||||
},
|
||||
expectedBody: "viewer@acme.com (019aaaaa-bbbb-7000-8000-cccc00000002) failed to update dashboard (019b-5678): forbidden (authz_forbidden)",
|
||||
},
|
||||
{
|
||||
name: "Failure_ErrorTypeOnly",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbDelete,
|
||||
Outcome: OutcomeFailure,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.MustNewUUID("019a1234-abcd-7000-8000-567800000001"),
|
||||
PrincipalEmail: valuer.MustNewEmail("test@acme.com"),
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceKind: coretypes.MustNewKind("user"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{
|
||||
ErrorType: "not-found",
|
||||
},
|
||||
expectedBody: "test@acme.com (019a1234-abcd-7000-8000-567800000001) failed to delete user: not-found",
|
||||
},
|
||||
{
|
||||
name: "Failure_NoErrorDetails",
|
||||
auditAttributes: AuditAttributes{
|
||||
Action: coretypes.VerbCreate,
|
||||
Outcome: OutcomeFailure,
|
||||
},
|
||||
principalAttributes: PrincipalAttributes{
|
||||
PrincipalID: valuer.MustNewUUID("019a1234-abcd-7000-8000-567800000001"),
|
||||
PrincipalEmail: valuer.MustNewEmail("test@acme.com"),
|
||||
},
|
||||
resourceAttributes: ResourceAttributes{
|
||||
ResourceID: "019b-5678",
|
||||
ResourceKind: coretypes.MustNewKind("dashboard"),
|
||||
},
|
||||
errorAttributes: ErrorAttributes{},
|
||||
expectedBody: "test@acme.com (019a1234-abcd-7000-8000-567800000001) failed to create dashboard (019b-5678)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
body := newBody(testCase.auditAttributes, testCase.principalAttributes, testCase.resourceAttributes, testCase.errorAttributes)
|
||||
assert.Equal(t, testCase.expectedBody, body)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ type AuditEvent struct {
|
||||
// OTel LogRecord Intrinsic
|
||||
EventName EventName
|
||||
|
||||
// Custom Audit Attributes - Action
|
||||
// Custom Audit Attributes - Verb
|
||||
AuditAttributes AuditAttributes
|
||||
|
||||
// Custom Audit Attributes - Principal
|
||||
@@ -50,17 +50,15 @@ func NewAuditEventFromHTTPRequest(
|
||||
statusCode int,
|
||||
traceID oteltrace.TraceID,
|
||||
spanID oteltrace.SpanID,
|
||||
action coretypes.Verb,
|
||||
verb coretypes.Verb,
|
||||
actionCategory ActionCategory,
|
||||
claims authtypes.Claims,
|
||||
resourceID string,
|
||||
resourceKind coretypes.Kind,
|
||||
resourceAttributes ResourceAttributes,
|
||||
errorType string,
|
||||
errorCode string,
|
||||
) AuditEvent {
|
||||
auditAttributes := NewAuditAttributesFromHTTP(statusCode, action, actionCategory, claims)
|
||||
auditAttributes := NewAuditAttributesFromHTTP(statusCode, verb, actionCategory, claims)
|
||||
principalAttributes := NewPrincipalAttributesFromClaims(claims)
|
||||
resourceAttributes := NewResourceAttributes(resourceID, resourceKind)
|
||||
errorAttributes := NewErrorAttributes(errorType, errorCode)
|
||||
transportAttributes := NewTransportAttributesFromHTTP(req, route, statusCode)
|
||||
|
||||
@@ -69,7 +67,7 @@ func NewAuditEventFromHTTPRequest(
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
Body: newBody(auditAttributes, principalAttributes, resourceAttributes, errorAttributes),
|
||||
EventName: NewEventName(resourceAttributes.ResourceKind, auditAttributes.Action),
|
||||
EventName: NewEventName(resourceAttributes.Resource.Kind(), auditAttributes.Verb),
|
||||
AuditAttributes: auditAttributes,
|
||||
PrincipalAttributes: principalAttributes,
|
||||
ResourceAttributes: resourceAttributes,
|
||||
@@ -89,7 +87,7 @@ func NewPLogsFromAuditEvents(events []AuditEvent, name string, version string, s
|
||||
groups := make(map[resourceKey][]int)
|
||||
order := make([]resourceKey, 0)
|
||||
for i, event := range events {
|
||||
key := resourceKey{kind: event.ResourceAttributes.ResourceKind.String(), id: event.ResourceAttributes.ResourceID}
|
||||
key := resourceKey{kind: event.ResourceAttributes.Resource.Kind().String(), id: event.ResourceAttributes.ResourceID}
|
||||
if _, exists := groups[key]; !exists {
|
||||
order = append(order, key)
|
||||
}
|
||||
@@ -101,7 +99,8 @@ func NewPLogsFromAuditEvents(events []AuditEvent, name string, version string, s
|
||||
resourceAttrs := resourceLogs.Resource().Attributes()
|
||||
resourceAttrs.PutStr(string(semconv.ServiceNameKey), name)
|
||||
resourceAttrs.PutStr(string(semconv.ServiceVersionKey), version)
|
||||
events[groups[key][0]].ResourceAttributes.PutResource(resourceAttrs)
|
||||
head := events[groups[key][0]]
|
||||
head.ResourceAttributes.PutResource(resourceAttrs, head.PrincipalAttributes.PrincipalOrgID)
|
||||
|
||||
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
|
||||
scopeLogs.Scope().SetName(scope)
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
package audittypes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
testDashboardKind = coretypes.MustNewKind("dashboard")
|
||||
)
|
||||
|
||||
func TestNewAuditEventFromHTTPRequest(t *testing.T) {
|
||||
traceID := oteltrace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
|
||||
spanID := oteltrace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
route string
|
||||
statusCode int
|
||||
action coretypes.Verb
|
||||
category ActionCategory
|
||||
claims authtypes.Claims
|
||||
resourceID string
|
||||
resourceKind coretypes.Kind
|
||||
errorType string
|
||||
errorCode string
|
||||
expectedOutcome Outcome
|
||||
expectedBody string
|
||||
}{
|
||||
{
|
||||
name: "Success_DashboardCreated",
|
||||
method: http.MethodPost,
|
||||
path: "/api/v1/dashboards",
|
||||
route: "/api/v1/dashboards",
|
||||
statusCode: http.StatusOK,
|
||||
action: coretypes.VerbCreate,
|
||||
category: ActionCategoryConfigurationChange,
|
||||
claims: authtypes.Claims{UserID: "019a1234-abcd-7000-8000-567800000001", Email: "alice@acme.com", OrgID: "019a-0000-0000-0001", IdentNProvider: authtypes.IdentNProviderTokenizer},
|
||||
resourceID: "019b-5678-efgh-9012",
|
||||
resourceKind: testDashboardKind,
|
||||
expectedOutcome: OutcomeSuccess,
|
||||
expectedBody: "alice@acme.com (019a1234-abcd-7000-8000-567800000001) created dashboard (019b-5678-efgh-9012)",
|
||||
},
|
||||
{
|
||||
name: "Failure_ForbiddenDashboardUpdate",
|
||||
method: http.MethodPut,
|
||||
path: "/api/v1/dashboards/019b-5678-efgh-9012",
|
||||
route: "/api/v1/dashboards/{id}",
|
||||
statusCode: http.StatusForbidden,
|
||||
action: coretypes.VerbUpdate,
|
||||
category: ActionCategoryConfigurationChange,
|
||||
claims: authtypes.Claims{UserID: "019aaaaa-bbbb-7000-8000-cccc00000002", Email: "viewer@acme.com", OrgID: "019a-0000-0000-0001", IdentNProvider: authtypes.IdentNProviderTokenizer},
|
||||
resourceID: "019b-5678-efgh-9012",
|
||||
resourceKind: testDashboardKind,
|
||||
errorType: "forbidden",
|
||||
errorCode: "authz_forbidden",
|
||||
expectedOutcome: OutcomeFailure,
|
||||
expectedBody: "viewer@acme.com (019aaaaa-bbbb-7000-8000-cccc00000002) failed to update dashboard (019b-5678-efgh-9012): forbidden (authz_forbidden)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(testCase.method, testCase.path, nil)
|
||||
|
||||
event := NewAuditEventFromHTTPRequest(
|
||||
req,
|
||||
testCase.route,
|
||||
testCase.statusCode,
|
||||
traceID,
|
||||
spanID,
|
||||
testCase.action,
|
||||
testCase.category,
|
||||
testCase.claims,
|
||||
testCase.resourceID,
|
||||
testCase.resourceKind,
|
||||
testCase.errorType,
|
||||
testCase.errorCode,
|
||||
)
|
||||
|
||||
assert.Equal(t, testCase.expectedOutcome, event.AuditAttributes.Outcome)
|
||||
assert.Equal(t, testCase.expectedBody, event.Body)
|
||||
assert.Equal(t, testCase.resourceKind, event.ResourceAttributes.ResourceKind)
|
||||
assert.Equal(t, testCase.resourceID, event.ResourceAttributes.ResourceID)
|
||||
assert.Equal(t, testCase.action, event.AuditAttributes.Action)
|
||||
assert.Equal(t, testCase.category, event.AuditAttributes.ActionCategory)
|
||||
assert.Equal(t, testCase.route, event.TransportAttributes.HTTPRoute)
|
||||
assert.Equal(t, testCase.statusCode, event.TransportAttributes.HTTPStatusCode)
|
||||
assert.Equal(t, testCase.method, event.TransportAttributes.HTTPMethod)
|
||||
assert.Equal(t, traceID, event.TraceID)
|
||||
assert.Equal(t, spanID, event.SpanID)
|
||||
assert.Equal(t, testCase.errorType, event.ErrorAttributes.ErrorType)
|
||||
assert.Equal(t, testCase.errorCode, event.ErrorAttributes.ErrorCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newTestEvent(resourceKind coretypes.Kind, resourceID string, action coretypes.Verb) AuditEvent {
|
||||
return AuditEvent{
|
||||
Body: resourceKind.String() + "." + action.PastTense(),
|
||||
EventName: NewEventName(resourceKind, action),
|
||||
AuditAttributes: AuditAttributes{
|
||||
Action: action,
|
||||
ActionCategory: ActionCategoryConfigurationChange,
|
||||
Outcome: OutcomeSuccess,
|
||||
},
|
||||
ResourceAttributes: ResourceAttributes{
|
||||
ResourceKind: resourceKind,
|
||||
ResourceID: resourceID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPLogsFromAuditEvents(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
events []AuditEvent
|
||||
expectedResourceLogs int
|
||||
expectedResourceKinds []string
|
||||
expectedResourceIDs []string
|
||||
expectedLogRecordCounts []int
|
||||
}{
|
||||
{
|
||||
name: "Empty",
|
||||
events: []AuditEvent{},
|
||||
expectedResourceLogs: 0,
|
||||
},
|
||||
{
|
||||
name: "SingleEvent",
|
||||
events: []AuditEvent{
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbCreate),
|
||||
},
|
||||
expectedResourceLogs: 1,
|
||||
expectedResourceKinds: []string{"dashboard"},
|
||||
expectedResourceIDs: []string{"d-001"},
|
||||
expectedLogRecordCounts: []int{1},
|
||||
},
|
||||
{
|
||||
name: "SameResource_MultipleEvents",
|
||||
events: []AuditEvent{
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbCreate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 1,
|
||||
expectedResourceKinds: []string{"dashboard"},
|
||||
expectedResourceIDs: []string{"d-001"},
|
||||
expectedLogRecordCounts: []int{3},
|
||||
},
|
||||
{
|
||||
name: "DifferentResources_SeparateGroups",
|
||||
events: []AuditEvent{
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(coretypes.MustNewKind("user"), "u-001", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 2,
|
||||
expectedResourceKinds: []string{"dashboard", "user"},
|
||||
expectedResourceIDs: []string{"d-001", "u-001"},
|
||||
expectedLogRecordCounts: []int{1, 1},
|
||||
},
|
||||
{
|
||||
name: "SameKind_DifferentIDs_SeparateGroups",
|
||||
events: []AuditEvent{
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(testDashboardKind, "d-002", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 2,
|
||||
expectedResourceKinds: []string{"dashboard", "dashboard"},
|
||||
expectedResourceIDs: []string{"d-001", "d-002"},
|
||||
expectedLogRecordCounts: []int{1, 1},
|
||||
},
|
||||
{
|
||||
name: "InterleavedResources_GroupedCorrectly",
|
||||
events: []AuditEvent{
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbCreate),
|
||||
newTestEvent(coretypes.MustNewKind("user"), "u-001", coretypes.VerbUpdate),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbUpdate),
|
||||
newTestEvent(coretypes.MustNewKind("user"), "u-001", coretypes.VerbDelete),
|
||||
newTestEvent(testDashboardKind, "d-001", coretypes.VerbDelete),
|
||||
},
|
||||
expectedResourceLogs: 2,
|
||||
expectedResourceKinds: []string{"dashboard", "user"},
|
||||
expectedResourceIDs: []string{"d-001", "u-001"},
|
||||
expectedLogRecordCounts: []int{3, 2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
logs := NewPLogsFromAuditEvents(testCase.events, "signoz", "0.90.0", "signoz.audit")
|
||||
|
||||
assert.Equal(t, testCase.expectedResourceLogs, logs.ResourceLogs().Len())
|
||||
|
||||
for i := 0; i < logs.ResourceLogs().Len(); i++ {
|
||||
resourceLogs := logs.ResourceLogs().At(i)
|
||||
resourceAttrs := resourceLogs.Resource().Attributes()
|
||||
|
||||
// Verify service resource attributes
|
||||
serviceName, exists := resourceAttrs.Get("service.name")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, "signoz", serviceName.Str())
|
||||
|
||||
serviceVersion, exists := resourceAttrs.Get("service.version")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, "0.90.0", serviceVersion.Str())
|
||||
|
||||
// Verify audit resource attributes on Resource (not event attributes)
|
||||
kind, exists := resourceAttrs.Get("signoz.audit.resource.kind")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, testCase.expectedResourceKinds[i], kind.Str())
|
||||
|
||||
id, exists := resourceAttrs.Get("signoz.audit.resource.id")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, testCase.expectedResourceIDs[i], id.Str())
|
||||
|
||||
// Verify scope
|
||||
assert.Equal(t, 1, resourceLogs.ScopeLogs().Len())
|
||||
assert.Equal(t, "signoz.audit", resourceLogs.ScopeLogs().At(0).Scope().Name())
|
||||
|
||||
// Verify log record count per group
|
||||
assert.Equal(t, testCase.expectedLogRecordCounts[i], resourceLogs.ScopeLogs().At(0).LogRecords().Len())
|
||||
|
||||
// Verify resource attrs are NOT in log record event attributes
|
||||
for j := 0; j < resourceLogs.ScopeLogs().At(0).LogRecords().Len(); j++ {
|
||||
recordAttrs := resourceLogs.ScopeLogs().At(0).LogRecords().At(j).Attributes()
|
||||
_, hasKind := recordAttrs.Get("signoz.audit.resource.kind")
|
||||
_, hasID := recordAttrs.Get("signoz.audit.resource.id")
|
||||
assert.False(t, hasKind, "resource.kind must not be in log record attributes")
|
||||
assert.False(t, hasID, "resource.id must not be in log record attributes")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@ var Kinds = []Kind{
|
||||
KindMeterMetrics,
|
||||
KindLogsField,
|
||||
KindTracesField,
|
||||
KindLLMPricingRule,
|
||||
KindSpanMapperGroup,
|
||||
KindSpanMapper,
|
||||
KindZeusProfile,
|
||||
KindZeusHost,
|
||||
KindMetricField,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -78,4 +84,10 @@ var (
|
||||
KindMeterMetrics = MustNewKind("meter-metrics")
|
||||
KindLogsField = MustNewKind("logs-field")
|
||||
KindTracesField = MustNewKind("traces-field")
|
||||
KindLLMPricingRule = MustNewKind("llm-pricing-rule")
|
||||
KindSpanMapperGroup = MustNewKind("span-mapper-group")
|
||||
KindSpanMapper = MustNewKind("span-mapper")
|
||||
KindZeusProfile = MustNewKind("zeus-profile")
|
||||
KindZeusHost = MustNewKind("zeus-host")
|
||||
KindMetricField = MustNewKind("metric-field")
|
||||
)
|
||||
|
||||
@@ -69,6 +69,18 @@ var Resources = []Resource{
|
||||
ResourceMetaResourcesLogsField,
|
||||
ResourceMetaResourceTracesField,
|
||||
ResourceMetaResourcesTracesField,
|
||||
ResourceMetaResourceLLMPricingRule,
|
||||
ResourceMetaResourcesLLMPricingRule,
|
||||
ResourceMetaResourceSpanMapperGroup,
|
||||
ResourceMetaResourcesSpanMapperGroup,
|
||||
ResourceMetaResourceSpanMapper,
|
||||
ResourceMetaResourcesSpanMapper,
|
||||
ResourceMetaResourceZeusProfile,
|
||||
ResourceMetaResourcesZeusProfile,
|
||||
ResourceMetaResourceZeusHost,
|
||||
ResourceMetaResourcesZeusHost,
|
||||
ResourceMetaResourceMetricField,
|
||||
ResourceMetaResourcesMetricField,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -140,4 +152,16 @@ var (
|
||||
ResourceMetaResourcesLogsField = NewResourceMetaResources(KindLogsField)
|
||||
ResourceMetaResourceTracesField = NewResourceMetaResource(KindTracesField)
|
||||
ResourceMetaResourcesTracesField = NewResourceMetaResources(KindTracesField)
|
||||
ResourceMetaResourceLLMPricingRule = NewResourceMetaResource(KindLLMPricingRule)
|
||||
ResourceMetaResourcesLLMPricingRule = NewResourceMetaResources(KindLLMPricingRule)
|
||||
ResourceMetaResourceSpanMapperGroup = NewResourceMetaResource(KindSpanMapperGroup)
|
||||
ResourceMetaResourcesSpanMapperGroup = NewResourceMetaResources(KindSpanMapperGroup)
|
||||
ResourceMetaResourceSpanMapper = NewResourceMetaResource(KindSpanMapper)
|
||||
ResourceMetaResourcesSpanMapper = NewResourceMetaResources(KindSpanMapper)
|
||||
ResourceMetaResourceZeusProfile = NewResourceMetaResource(KindZeusProfile)
|
||||
ResourceMetaResourcesZeusProfile = NewResourceMetaResources(KindZeusProfile)
|
||||
ResourceMetaResourceZeusHost = NewResourceMetaResource(KindZeusHost)
|
||||
ResourceMetaResourcesZeusHost = NewResourceMetaResources(KindZeusHost)
|
||||
ResourceMetaResourceMetricField = NewResourceMetaResource(KindMetricField)
|
||||
ResourceMetaResourcesMetricField = NewResourceMetaResources(KindMetricField)
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ pytest_plugins = [
|
||||
"fixtures.zookeeper",
|
||||
"fixtures.signoz",
|
||||
"fixtures.audit",
|
||||
"fixtures.auditor",
|
||||
"fixtures.logs",
|
||||
"fixtures.traces",
|
||||
"fixtures.metrics",
|
||||
|
||||
183
tests/fixtures/auditor.py
vendored
Normal file
183
tests/fixtures/auditor.py
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import reuse, types
|
||||
from fixtures.auth import find_user_by_email
|
||||
|
||||
# Filename used for the audit log inside the host-mounted tmp dir. The same
|
||||
# absolute path is mounted into the SigNoz container at the same location, so
|
||||
# the file is visible to both the backend (writer) and the test runner (reader).
|
||||
AUDIT_FILE_NAME = "audit.log"
|
||||
|
||||
|
||||
@dataclass
|
||||
class _AuditDir:
|
||||
"""Cacheable wrapper around the audit directory path so the reuse.wrap
|
||||
machinery can persist it across pytest runs alongside the SigNoz container."""
|
||||
|
||||
path: str
|
||||
|
||||
def __cache__(self) -> dict:
|
||||
return {"path": self.path}
|
||||
|
||||
def __log__(self) -> str:
|
||||
return f"AuditDir(path={self.path})"
|
||||
|
||||
|
||||
@pytest.fixture(name="audit_dir", scope="package")
|
||||
def audit_dir(
|
||||
tmpfs: Callable[[str], types.LegacyPath],
|
||||
request: pytest.FixtureRequest,
|
||||
pytestconfig: pytest.Config,
|
||||
) -> str:
|
||||
"""Host tmp directory mounted into the SigNoz container as the auditor file path.
|
||||
|
||||
Mirrors the sqlite/clickhouse pattern: a tmpfs directory is created on the
|
||||
host and bind-mounted into the container at the same absolute path, so the
|
||||
audit log file is reachable from both sides without docker exec. The path
|
||||
is cached via reuse.wrap so re-runs under --reuse keep using the same dir
|
||||
that the long-lived SigNoz container has bind-mounted.
|
||||
"""
|
||||
|
||||
def create() -> _AuditDir:
|
||||
return _AuditDir(path=str(tmpfs("auditor")))
|
||||
|
||||
def delete(_: _AuditDir) -> None:
|
||||
pass
|
||||
|
||||
def restore(cache: dict) -> _AuditDir:
|
||||
return _AuditDir(path=cache["path"])
|
||||
|
||||
return reuse.wrap(
|
||||
request,
|
||||
pytestconfig,
|
||||
"auditor_dir",
|
||||
lambda: _AuditDir(path=""),
|
||||
create,
|
||||
delete,
|
||||
restore,
|
||||
).path
|
||||
|
||||
|
||||
@pytest.fixture(name="audit_file_path", scope="package")
|
||||
def audit_file_path(audit_dir: str) -> str: # pylint: disable=redefined-outer-name
|
||||
return os.path.join(audit_dir, AUDIT_FILE_NAME)
|
||||
|
||||
|
||||
def ensure_user_active(
|
||||
signoz: types.SigNoz,
|
||||
admin_token: str,
|
||||
email: str,
|
||||
role: str,
|
||||
password: str,
|
||||
name: str = "",
|
||||
) -> str:
|
||||
"""Invite + activate a user, or return the existing user's id if already present.
|
||||
|
||||
Idempotent counterpart to fixtures.auth.create_active_user — needed because the
|
||||
auditor suite reuses a long-lived SigNoz container across pytest runs and would
|
||||
otherwise hit a 409 on the second invite.
|
||||
"""
|
||||
invite = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/invite"),
|
||||
json={"email": email, "role": role, "name": name},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
if invite.status_code == HTTPStatus.CONFLICT:
|
||||
return find_user_by_email(signoz, admin_token, email)["id"]
|
||||
assert invite.status_code == HTTPStatus.CREATED, invite.text
|
||||
|
||||
invited_user = invite.json()["data"]
|
||||
activate = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/resetPassword"),
|
||||
json={"password": password, "token": invited_user["token"]},
|
||||
timeout=5,
|
||||
)
|
||||
assert activate.status_code == HTTPStatus.NO_CONTENT, activate.text
|
||||
return invited_user["id"]
|
||||
|
||||
|
||||
def read_audit_records(audit_file_path: str) -> list[dict[str, Any]]: # pylint: disable=redefined-outer-name
|
||||
"""Read every audit log record from the host-side audit file.
|
||||
|
||||
Each line of the file is one OTLP-Logs JSON object containing all events
|
||||
flushed in a single export batch. Returns the flattened list of LogRecord
|
||||
dicts across every line, with the parent resource attributes merged into
|
||||
each record's attributes so signoz.audit.resource.kind and
|
||||
signoz.audit.resource.id are reachable via attr_value.
|
||||
"""
|
||||
if not os.path.exists(audit_file_path):
|
||||
return []
|
||||
|
||||
records: list[dict[str, Any]] = []
|
||||
with open(audit_file_path, encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
payload = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
# Partial line caught mid-write between flush syscalls; the
|
||||
# next poll will re-read the full file once the write
|
||||
# completes. Skip without failing the test.
|
||||
continue
|
||||
for resource_log in payload.get("resourceLogs", []):
|
||||
resource_attrs = resource_log.get("resource", {}).get("attributes", [])
|
||||
for scope_log in resource_log.get("scopeLogs", []):
|
||||
for record in scope_log.get("logRecords", []):
|
||||
merged = dict(record)
|
||||
merged["attributes"] = list(record.get("attributes", [])) + list(resource_attrs)
|
||||
records.append(merged)
|
||||
return records
|
||||
|
||||
|
||||
def attr_value(record: dict[str, Any], key: str) -> Any:
|
||||
"""Return the value of an OTLP-JSON attribute by key, or None if absent."""
|
||||
for kv in record.get("attributes", []):
|
||||
if kv.get("key") != key:
|
||||
continue
|
||||
value = kv.get("value", {})
|
||||
for kind in ("stringValue", "intValue", "boolValue", "doubleValue"):
|
||||
if kind in value:
|
||||
return value[kind]
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def find_event(records: list[dict[str, Any]], event_name: str, **filters: Any) -> dict[str, Any] | None:
|
||||
"""Find the first record whose eventName matches and whose audit attributes match every filter."""
|
||||
for record in records:
|
||||
if record.get("eventName") != event_name:
|
||||
continue
|
||||
if all(attr_value(record, k) == v for k, v in filters.items()):
|
||||
return record
|
||||
return None
|
||||
|
||||
|
||||
def wait_for_event(
|
||||
audit_file_path: str, # pylint: disable=redefined-outer-name
|
||||
event_name: str,
|
||||
timeout: float = 2.0,
|
||||
interval: float = 0.1,
|
||||
**filters: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Poll the audit file until an event matching event_name + filters appears."""
|
||||
deadline = time.monotonic() + timeout
|
||||
last_records: list[dict[str, Any]] = []
|
||||
while time.monotonic() < deadline:
|
||||
last_records = read_audit_records(audit_file_path)
|
||||
event = find_event(last_records, event_name, **filters)
|
||||
if event is not None:
|
||||
return event
|
||||
time.sleep(interval)
|
||||
raise AssertionError(f"audit event {event_name!r} matching {filters} not found within {timeout}s (saw {len(last_records)} records: {[r.get('eventName') for r in last_records]})")
|
||||
13
tests/fixtures/signoz.py
vendored
13
tests/fixtures/signoz.py
vendored
@@ -16,7 +16,7 @@ from fixtures.logger import setup_logger
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def create_signoz(
|
||||
def create_signoz( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
network: Network,
|
||||
zeus: types.TestContainerDocker,
|
||||
gateway: types.TestContainerDocker,
|
||||
@@ -26,10 +26,15 @@ def create_signoz(
|
||||
pytestconfig: pytest.Config,
|
||||
cache_key: str = "signoz",
|
||||
env_overrides: dict | None = None,
|
||||
volume_mappings: list[tuple[str, str]] | None = None,
|
||||
) -> types.SigNoz:
|
||||
"""
|
||||
Factory function for creating a SigNoz container.
|
||||
Accepts optional env_overrides to customize the container environment.
|
||||
|
||||
Accepts optional env_overrides to customize the container environment, and
|
||||
optional volume_mappings (host_path, container_path) tuples to mount host
|
||||
directories into the container — mirrors how sqlite/clickhouse fixtures
|
||||
expose tmp paths back to the test runner.
|
||||
"""
|
||||
|
||||
def create() -> types.SigNoz:
|
||||
@@ -104,6 +109,10 @@ def create_signoz(
|
||||
"rw",
|
||||
)
|
||||
|
||||
if volume_mappings:
|
||||
for host_path, container_path in volume_mappings:
|
||||
container.with_volume_mapping(host_path, container_path, "rw")
|
||||
|
||||
container.start()
|
||||
|
||||
def ready(container: DockerContainer) -> None:
|
||||
|
||||
91
tests/integration/tests/auditor/01_session.py
Normal file
91
tests/integration/tests/auditor/01_session.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auditor import attr_value, ensure_user_active, wait_for_event
|
||||
from fixtures.auth import (
|
||||
USER_ADMIN_EMAIL,
|
||||
USER_ADMIN_PASSWORD,
|
||||
USER_VIEWER_EMAIL,
|
||||
USER_VIEWER_NAME,
|
||||
USER_VIEWER_PASSWORD,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_session_deleted_event_appears_in_file(
|
||||
signoz: types.SigNoz,
|
||||
apply_license: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
audit_file_path: str,
|
||||
) -> None:
|
||||
"""An admin logout posts to DELETE /api/v2/sessions; the audit middleware
|
||||
captures the post-auth claims and the file provider writes session.deleted."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/sessions"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
record = wait_for_event(
|
||||
audit_file_path,
|
||||
"session.deleted",
|
||||
**{
|
||||
"signoz.audit.outcome": "success",
|
||||
"signoz.audit.verb": "delete",
|
||||
"signoz.audit.principal.email": USER_ADMIN_EMAIL,
|
||||
},
|
||||
)
|
||||
assert attr_value(record, "signoz.audit.resource.kind") == "session"
|
||||
assert attr_value(record, "signoz.audit.action_category") == "access_control"
|
||||
assert record["severityText"] == "INFO"
|
||||
|
||||
|
||||
def test_audit_records_failure_outcome(
|
||||
signoz: types.SigNoz,
|
||||
apply_license: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
audit_file_path: str,
|
||||
) -> None:
|
||||
"""A viewer hitting an admin-only mutation must produce an audit record with
|
||||
outcome=failure and the captured error.type. Proves the middleware writes
|
||||
on the 4xx path, not just the happy path."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
ensure_user_active(
|
||||
signoz,
|
||||
admin_token,
|
||||
USER_VIEWER_EMAIL,
|
||||
"VIEWER",
|
||||
USER_VIEWER_PASSWORD,
|
||||
USER_VIEWER_NAME,
|
||||
)
|
||||
|
||||
admin_user = find_user_by_email(signoz, admin_token, USER_ADMIN_EMAIL)
|
||||
viewer_token = get_token(USER_VIEWER_EMAIL, USER_VIEWER_PASSWORD)
|
||||
|
||||
forbidden = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{admin_user['id']}"),
|
||||
json={"displayName": "should not work"},
|
||||
headers={"Authorization": f"Bearer {viewer_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert forbidden.status_code == HTTPStatus.FORBIDDEN, forbidden.text
|
||||
|
||||
record = wait_for_event(
|
||||
audit_file_path,
|
||||
"user.updated",
|
||||
**{
|
||||
"signoz.audit.outcome": "failure",
|
||||
"signoz.audit.principal.email": USER_VIEWER_EMAIL,
|
||||
"signoz.audit.resource.id": admin_user["id"],
|
||||
},
|
||||
)
|
||||
assert record["severityText"] == "ERROR"
|
||||
assert attr_value(record, "signoz.audit.error.type") is not None
|
||||
122
tests/integration/tests/auditor/02_user_role.py
Normal file
122
tests/integration/tests/auditor/02_user_role.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auditor import attr_value, ensure_user_active, wait_for_event
|
||||
from fixtures.auth import (
|
||||
USER_ADMIN_EMAIL,
|
||||
USER_ADMIN_PASSWORD,
|
||||
USER_EDITOR_EMAIL,
|
||||
USER_EDITOR_NAME,
|
||||
USER_EDITOR_PASSWORD,
|
||||
change_user_role,
|
||||
find_user_by_email,
|
||||
)
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_user_updated_event_appears_in_file(
|
||||
signoz: types.SigNoz,
|
||||
apply_license: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
audit_file_path: str,
|
||||
) -> None:
|
||||
"""Admin renames an editor user via PUT /api/v2/users/{id}; the file provider
|
||||
writes user.updated with the editor id as the resource."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
editor_id = ensure_user_active(
|
||||
signoz,
|
||||
admin_token,
|
||||
USER_EDITOR_EMAIL,
|
||||
"EDITOR",
|
||||
USER_EDITOR_PASSWORD,
|
||||
USER_EDITOR_NAME,
|
||||
)
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{editor_id}"),
|
||||
json={"displayName": "Renamed Editor"},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
record = wait_for_event(
|
||||
audit_file_path,
|
||||
"user.updated",
|
||||
**{
|
||||
"signoz.audit.outcome": "success",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.resource.id": editor_id,
|
||||
"signoz.audit.principal.email": USER_ADMIN_EMAIL,
|
||||
},
|
||||
)
|
||||
assert attr_value(record, "signoz.audit.action_category") == "configuration_change"
|
||||
assert attr_value(record, "signoz.audit.resource.kind") == "user"
|
||||
|
||||
|
||||
def test_user_role_change_emits_created_and_deleted_events(
|
||||
signoz: types.SigNoz,
|
||||
apply_license: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
audit_file_path: str,
|
||||
) -> None:
|
||||
"""Toggling the editor user's managed role between signoz-editor and signoz-viewer
|
||||
fires both DELETE and POST against /api/v2/users/{id}/roles; the file provider
|
||||
writes one user-role.deleted and one user-role.created tied to the editor id.
|
||||
|
||||
The toggle direction is computed from the current role so the test is idempotent
|
||||
across re-runs of the long-lived auditor SigNoz container.
|
||||
"""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
editor = find_user_by_email(signoz, admin_token, USER_EDITOR_EMAIL)
|
||||
editor_id = editor["id"]
|
||||
|
||||
roles_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v2/users/{editor_id}/roles"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert roles_response.status_code == HTTPStatus.OK, roles_response.text
|
||||
managed_role = next(
|
||||
(r for r in roles_response.json()["data"] if r["name"] in ("signoz-editor", "signoz-viewer")),
|
||||
None,
|
||||
)
|
||||
assert managed_role is not None, "editor user is missing both managed roles"
|
||||
|
||||
if managed_role["name"] == "signoz-editor":
|
||||
old_role, new_role = "signoz-editor", "signoz-viewer"
|
||||
else:
|
||||
old_role, new_role = "signoz-viewer", "signoz-editor"
|
||||
|
||||
change_user_role(signoz, admin_token, editor_id, old_role, new_role)
|
||||
|
||||
deleted = wait_for_event(
|
||||
audit_file_path,
|
||||
"user-role.deleted",
|
||||
**{
|
||||
"signoz.audit.outcome": "success",
|
||||
"signoz.audit.verb": "delete",
|
||||
"signoz.audit.resource.id": editor_id,
|
||||
"signoz.audit.principal.email": USER_ADMIN_EMAIL,
|
||||
},
|
||||
)
|
||||
assert attr_value(deleted, "signoz.audit.action_category") == "access_control"
|
||||
assert attr_value(deleted, "signoz.audit.resource.kind") == "user-role"
|
||||
|
||||
created = wait_for_event(
|
||||
audit_file_path,
|
||||
"user-role.created",
|
||||
**{
|
||||
"signoz.audit.outcome": "success",
|
||||
"signoz.audit.verb": "create",
|
||||
"signoz.audit.resource.id": editor_id,
|
||||
"signoz.audit.principal.email": USER_ADMIN_EMAIL,
|
||||
},
|
||||
)
|
||||
assert attr_value(created, "signoz.audit.action_category") == "access_control"
|
||||
assert attr_value(created, "signoz.audit.resource.kind") == "user-role"
|
||||
0
tests/integration/tests/auditor/__init__.py
Normal file
0
tests/integration/tests/auditor/__init__.py
Normal file
44
tests/integration/tests/auditor/conftest.py
Normal file
44
tests/integration/tests/auditor/conftest.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
from testcontainers.core.container import Network
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.signoz import create_signoz
|
||||
|
||||
|
||||
@pytest.fixture(name="signoz", scope="package")
|
||||
def signoz( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
network: Network,
|
||||
zeus: types.TestContainerDocker,
|
||||
gateway: types.TestContainerDocker,
|
||||
sqlstore: types.TestContainerSQL,
|
||||
clickhouse: types.TestContainerClickhouse,
|
||||
request: pytest.FixtureRequest,
|
||||
pytestconfig: pytest.Config,
|
||||
audit_dir: str,
|
||||
audit_file_path: str,
|
||||
) -> types.SigNoz:
|
||||
"""Package-scoped SigNoz container configured with the file auditor.
|
||||
|
||||
BatchSize is set to 1 so every audited request flushes to disk on the moreC
|
||||
path without waiting on the periodic ticker. FlushInterval stays short so
|
||||
the periodic flush has bounded lag if BatchSize is ever raised. The audit
|
||||
directory is bind-mounted from the host (see fixtures.auditor.audit_dir)
|
||||
so tests can read the file with a plain open() call.
|
||||
"""
|
||||
return create_signoz(
|
||||
network=network,
|
||||
zeus=zeus,
|
||||
gateway=gateway,
|
||||
sqlstore=sqlstore,
|
||||
clickhouse=clickhouse,
|
||||
request=request,
|
||||
pytestconfig=pytestconfig,
|
||||
cache_key="signoz_auditor",
|
||||
env_overrides={
|
||||
"SIGNOZ_AUDITOR_PROVIDER": "file",
|
||||
"SIGNOZ_AUDITOR_FILE_PATH": audit_file_path,
|
||||
"SIGNOZ_AUDITOR_BATCH__SIZE": "1",
|
||||
"SIGNOZ_AUDITOR_FLUSH__INTERVAL": "100ms",
|
||||
},
|
||||
volume_mappings=[(audit_dir, audit_dir)],
|
||||
)
|
||||
@@ -37,7 +37,7 @@ def test_audit_list_all(
|
||||
"signoz.audit.principal.id": "user-010",
|
||||
"signoz.audit.principal.email": "ops@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "create",
|
||||
"signoz.audit.verb": "create",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
body="ops@acme.com (user-010) created alert-rule (alert-001)",
|
||||
@@ -55,7 +55,7 @@ def test_audit_list_all(
|
||||
"signoz.audit.principal.id": "user-010",
|
||||
"signoz.audit.principal.email": "ops@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "update",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
body="ops@acme.com (user-010) updated saved-view (view-001)",
|
||||
@@ -73,7 +73,7 @@ def test_audit_list_all(
|
||||
"signoz.audit.principal.id": "user-010",
|
||||
"signoz.audit.principal.email": "ops@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "update",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.action_category": "access_control",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
@@ -146,7 +146,7 @@ def test_audit_list_all(
|
||||
id="filter_by_principal_type",
|
||||
),
|
||||
pytest.param(
|
||||
"signoz.audit.resource.kind = 'dashboard' AND signoz.audit.action = 'delete'",
|
||||
"signoz.audit.resource.kind = 'dashboard' AND signoz.audit.verb = 'delete'",
|
||||
1,
|
||||
{"dashboard.deleted"},
|
||||
id="filter_by_resource_kind_and_action",
|
||||
@@ -177,7 +177,7 @@ def test_audit_filter(
|
||||
"signoz.audit.principal.id": "user-001",
|
||||
"signoz.audit.principal.email": "alice@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "create",
|
||||
"signoz.audit.verb": "create",
|
||||
"signoz.audit.action_category": "configuration_change",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
@@ -195,7 +195,7 @@ def test_audit_filter(
|
||||
"signoz.audit.principal.id": "user-001",
|
||||
"signoz.audit.principal.email": "alice@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "update",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.action_category": "configuration_change",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
@@ -213,7 +213,7 @@ def test_audit_filter(
|
||||
"signoz.audit.principal.id": "user-002",
|
||||
"signoz.audit.principal.email": "viewer@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "delete",
|
||||
"signoz.audit.verb": "delete",
|
||||
"signoz.audit.action_category": "configuration_change",
|
||||
"signoz.audit.outcome": "failure",
|
||||
"signoz.audit.error.type": "forbidden",
|
||||
@@ -234,7 +234,7 @@ def test_audit_filter(
|
||||
"signoz.audit.principal.id": "sa-001",
|
||||
"signoz.audit.principal.email": "",
|
||||
"signoz.audit.principal.type": "service_account",
|
||||
"signoz.audit.action": "create",
|
||||
"signoz.audit.verb": "create",
|
||||
"signoz.audit.action_category": "access_control",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
@@ -252,7 +252,7 @@ def test_audit_filter(
|
||||
"signoz.audit.principal.id": "user-001",
|
||||
"signoz.audit.principal.email": "alice@acme.com",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "login",
|
||||
"signoz.audit.verb": "login",
|
||||
"signoz.audit.action_category": "access_control",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
@@ -310,7 +310,7 @@ def test_audit_scalar_count_failures(
|
||||
attributes={
|
||||
"signoz.audit.principal.id": "user-050",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "delete",
|
||||
"signoz.audit.verb": "delete",
|
||||
"signoz.audit.outcome": "failure",
|
||||
},
|
||||
body="user-050 failed to delete dashboard",
|
||||
@@ -327,7 +327,7 @@ def test_audit_scalar_count_failures(
|
||||
attributes={
|
||||
"signoz.audit.principal.id": "user-060",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "update",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.outcome": "failure",
|
||||
},
|
||||
body="user-060 failed to update alert-rule",
|
||||
@@ -344,7 +344,7 @@ def test_audit_scalar_count_failures(
|
||||
attributes={
|
||||
"signoz.audit.principal.id": "user-050",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "update",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
body="user-050 updated dashboard",
|
||||
@@ -400,7 +400,7 @@ def test_audit_does_not_leak_into_logs(
|
||||
attributes={
|
||||
"signoz.audit.principal.id": "user-admin",
|
||||
"signoz.audit.principal.type": "user",
|
||||
"signoz.audit.action": "update",
|
||||
"signoz.audit.verb": "update",
|
||||
"signoz.audit.outcome": "success",
|
||||
},
|
||||
body="user-admin updated organization (org-999)",
|
||||
@@ -430,5 +430,5 @@ def test_audit_does_not_leak_into_logs(
|
||||
|
||||
rows = response.json()["data"]["data"]["results"][0].get("rows") or []
|
||||
|
||||
audit_bodies = [row["data"]["body"] for row in rows if "signoz.audit" in row["data"].get("attributes_string", {}).get("signoz.audit.action", "")]
|
||||
audit_bodies = [row["data"]["body"] for row in rows if "signoz.audit" in row["data"].get("attributes_string", {}).get("signoz.audit.verb", "")]
|
||||
assert len(audit_bodies) == 0
|
||||
|
||||
Reference in New Issue
Block a user