mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-27 14:10:30 +01:00
Compare commits
9 Commits
main
...
platform-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa599a3f28 | ||
|
|
2c57b63164 | ||
|
|
d2ece5ce01 | ||
|
|
cbbe3a2dd6 | ||
|
|
bcc6511341 | ||
|
|
9d3b8ba18d | ||
|
|
9c01d0aa0e | ||
|
|
9774a2e476 | ||
|
|
722b7c22cf |
@@ -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 {
|
||||
|
||||
38
ee/auditor/fileauditor/export.go
Normal file
38
ee/auditor/fileauditor/export.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package fileauditor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/auditor"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
)
|
||||
|
||||
// export converts a batch of audit events to OTLP-JSON log records and appends
|
||||
// the encoded payload to the configured file as one NDJSON line per batch.
|
||||
// Mirrors the wire format that otlphttpauditor sends, so downstream tools can
|
||||
// consume both transports interchangeably.
|
||||
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 errors.Wrapf(err, errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "failed to marshal audit logs")
|
||||
}
|
||||
|
||||
// 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 errors.Wrapf(err, errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "failed to write audit logs")
|
||||
}
|
||||
|
||||
return provider.file.Sync()
|
||||
}
|
||||
99
ee/auditor/fileauditor/provider.go
Normal file
99
ee/auditor/fileauditor/provider.go
Normal file
@@ -0,0 +1,99 @@
|
||||
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
|
||||
}
|
||||
@@ -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/ruletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -37,64 +38,99 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "rule",
|
||||
Action: audittypes.ActionCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "rule",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "rule",
|
||||
Action: audittypes.ActionDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "rule",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -143,45 +179,71 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "planned-maintenance",
|
||||
Action: audittypes.ActionCreate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "planned-maintenance",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "planned-maintenance",
|
||||
Action: audittypes.ActionDelete,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodDelete).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/serviceaccounttypes"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -181,20 +182,29 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.CreateFactorAPIKey), 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: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys", handler.New(
|
||||
provider.authZ.AdminAccess(provider.serviceAccountHandler.CreateFactorAPIKey),
|
||||
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: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.AuditDef{
|
||||
ResourceKind: "factor-api-key",
|
||||
Action: audittypes.ActionCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -232,20 +242,29 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(provider.authZ.AdminAccess(provider.serviceAccountHandler.RevokeFactorAPIKey), 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: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/service_accounts/{id}/keys/{fid}", handler.New(
|
||||
provider.authZ.AdminAccess(provider.serviceAccountHandler.RevokeFactorAPIKey),
|
||||
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: newSecuritySchemes(types.RoleAdmin),
|
||||
},
|
||||
handler.WithAuditDef(handler.AuditDef{
|
||||
ResourceKind: "factor-api-key",
|
||||
Action: audittypes.ActionDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,25 +4,34 @@ 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/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/sessions/email_password", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "session",
|
||||
Action: audittypes.ActionCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,37 +52,53 @@ func (provider *provider) addSessionRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions/rotate", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "session",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/sessions", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "session",
|
||||
Action: audittypes.ActionDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).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/gorilla/mux"
|
||||
)
|
||||
@@ -111,20 +112,28 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "user",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -179,20 +188,29 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "user",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryConfigurationChange,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -247,20 +265,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.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "reset-password-token",
|
||||
Action: audittypes.ActionCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -281,37 +308,53 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me/factor_password", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "factor-password",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "factor-password",
|
||||
Action: audittypes.ActionUpdate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -332,37 +375,55 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "user-role",
|
||||
Action: audittypes.ActionCreate,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/{id}/roles/{roleId}", handler.New(provider.authZ.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.authZ.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.AuditDef{
|
||||
ResourceKind: "user-role",
|
||||
Action: audittypes.ActionDelete,
|
||||
Category: audittypes.ActionCategoryAccessControl,
|
||||
ResourceIDParam: "id",
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -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,14 @@ type OTLPHTTPConfig struct {
|
||||
Retry RetryConfig `mapstructure:"retry"`
|
||||
}
|
||||
|
||||
// FileConfig holds configuration for the file exporter provider.
|
||||
// Audit events are encoded as OTLP-JSON log records and appended to the file.
|
||||
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 +121,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
|
||||
}
|
||||
|
||||
@@ -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.action": "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.action": "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.action": "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.action": "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)],
|
||||
)
|
||||
Reference in New Issue
Block a user