Ctrl K
ComunidadGuías…

internal/auditlog/auditlog.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Package auditlog is the audit vocabulary: the event shape, the action
// catalog and the emission seam community code records through. It is a leaf
// package (no imports beyond the standard library) on purpose: services,
// handlers and middlewares reference it without depending on the enterprise
// audit machinery in ee/audit, which consumes these types from the other side.
// Nothing here records anything: the only Recorder implementation that
// persists events lives in ee/audit, behind the enterprise license gate.
package auditlog

import (
	"context"
	"time"
)

// ActorType distinguishes who acted: a signed-in dashboard account, an
// app-scoped API credential (CLI publishes), or the server itself (SSO
// provisioning, scheduled jobs).
type ActorType string

const (
	ActorUser   ActorType = "user"
	ActorAPIKey ActorType = "api_key"
	ActorSystem ActorType = "system"
)

// Outcome is the result of the recorded action. Denied comes from the RBAC
// middleware refusing a permission; failure is reserved for actions that ran
// and failed in a security-relevant way (bad login).
type Outcome string

const (
	OutcomeSuccess Outcome = "success"
	OutcomeDenied  Outcome = "denied"
	OutcomeFailure Outcome = "failure"
)

// Action names follow target.action, past tense (update.rollback is the one
// deliberate exception: Expo vocabulary, a noun). This catalog is the
// documented contract of the audit log (mirrored in the dashboard's filter
// dropdown and in the public docs): call sites must use these constants, and
// adding one here means documenting it.
type Action string

const (
	// Auth and account.
	ActionUserLogin           Action = "user.login"
	ActionUserSSOLogin        Action = "user.sso_login"
	ActionUserPasswordChanged Action = "user.password_changed"

	// Users and privileges.
	ActionUserCreated        Action = "user.created"
	ActionUserUpdated        Action = "user.updated"
	ActionUserDeleted        Action = "user.deleted"
	ActionUserSSOProvisioned Action = "user.sso_provisioned"
	ActionUserSSOLinked      Action = "user.sso_linked"
	ActionUserApproved       Action = "user.approved"
	ActionUserAdminGranted   Action = "user.admin_granted"
	ActionUserAdminRevoked   Action = "user.admin_revoked"
	ActionRoleCreated        Action = "role.created"
	ActionRoleUpdated        Action = "role.updated"
	ActionRoleDeleted        Action = "role.deleted"
	ActionUserGrantsUpdated  Action = "user.grants_updated"

	// Enterprise administration.
	ActionLicenseActivated Action = "license.activated"
	ActionLicenseRemoved   Action = "license.removed"
	ActionLicenseSuspended Action = "license.suspended"
	ActionSSOConfigSaved   Action = "sso_config.saved"
	ActionSSOConfigDeleted Action = "sso_config.deleted"

	// App management.
	ActionAppCreated           Action = "app.created"
	ActionAppRenamed           Action = "app.renamed"
	ActionAppGitURLUpdated     Action = "app.git_url_updated"
	ActionAppDeleted           Action = "app.deleted"
	ActionChannelCreated       Action = "channel.created"
	ActionChannelDeleted       Action = "channel.deleted"
	ActionChannelBranchMapped  Action = "channel_branch.mapped"
	ActionBranchSurfingUpdated Action = "channel_branch_surfing.updated"
	ActionBranchCreated        Action = "branch.created"
	ActionBranchDeleted        Action = "branch.deleted"

	// Delivery.
	ActionUpdatePublished         Action = "update.published"
	ActionUpdateRollback          Action = "update.rollback"
	ActionUpdateRepublished       Action = "update.republished"
	ActionChannelRolloutStarted   Action = "channel_rollout.started"
	ActionChannelRolloutUpdated   Action = "channel_rollout.updated"
	ActionChannelRolloutEnded     Action = "channel_rollout.ended"
	ActionUpdateRolloutSet        Action = "update_rollout.set"
	ActionUpdateRolloutReverted   Action = "update_rollout.reverted"
	ActionUpdatePatchesRecomputed Action = "update_patches.recomputed"

	// Credentials and key material. The api_key prefix matches ActorAPIKey's
	// value so grouping by resource and by actor type line up.
	ActionAPIKeyCreated             Action = "api_key.created"
	ActionAPIKeyRevoked             Action = "api_key.revoked"
	ActionAPIKeyRestrictionsUpdated Action = "api_key.restrictions_updated"
	ActionBranchProtectionUpdated   Action = "branch_protection.updated"
	ActionCertificateDownloaded     Action = "certificate.downloaded"

	// Access control. permission.denied is the single event for authorization
	// refusals (the RBAC middleware emits it, with OutcomeDenied); domain
	// actions are only ever recorded once they actually executed, so one
	// refusal is never representable two ways.
	ActionPermissionDenied Action = "permission.denied"
)

// Event is one audit log entry. Actor and target carry denormalized display
// names taken at write time: entries must keep rendering identically after
// the user, app or key they mention is deleted. AppID empty means the event
// is account-level (users, roles, license, SSO). Metadata never carries
// secrets or full request bodies.
type Event struct {
	ID            int64
	OccurredAt    time.Time
	ActorType     ActorType
	ActorID       string
	ActorDisplay  string
	Action        Action
	TargetType    string
	TargetID      string
	TargetDisplay string
	AppID         string
	Outcome       Outcome
	IP            string
	UserAgent     string
	Metadata      map[string]any
}

// RecordFunc is the emission seam: community services and handlers hold one
// (nil means nobody is listening) and the enterprise wiring plugs
// ee/audit's Record method value in. Implementations must be best-effort:
// recording never fails the caller's request.
type RecordFunc func(ctx context.Context, event Event)

// RequestMeta is the network context of the request an event was emitted
// from. It rides the request context so services can emit events without
// taking *http.Request.
type RequestMeta struct {
	IP        string
	UserAgent string
}

type requestMetaKey struct{}

// WithRequestMeta stamps the network context; the HTTP middleware doing it
// for every request lives in internal/middleware.
func WithRequestMeta(ctx context.Context, meta RequestMeta) context.Context {
	return context.WithValue(ctx, requestMetaKey{}, meta)
}

// MetaFromContext returns the zero RequestMeta outside a request (jobs,
// tests): events then simply carry no network context.
func MetaFromContext(ctx context.Context) RequestMeta {
	meta, _ := ctx.Value(requestMetaKey{}).(RequestMeta)
	return meta
}