Documentation
¶
Overview ¶
Package approvals provides the data model and handlers for the approvals service.
Index ¶
- Variables
- func BuildApprovalRequestedCloudEvent(n NotificationOutbox, source, summary string) ([]byte, error)
- func ParseSecretRefMap(raw string) map[string]string
- func SafeTransport() *http.Transport
- func SignBodyHMACSHA256(rawBody []byte, secret string) string
- func ValidateWebhookURL(rawURL string) error
- func VerifySlackRequest(rawBody []byte, signatureHeader, timestampHeader, secret string, now time.Time) bool
- type ApprovalGrant
- type ApprovalRequest
- type ApprovalScope
- type ApproverAuthorizer
- func (a *ApproverAuthorizer) AllowEmail(ctx context.Context, tenantID, email string) bool
- func (a *ApproverAuthorizer) AllowSlack(ctx context.Context, tenantID, slackUserID string) bool
- func (a *ApproverAuthorizer) ResolveSlackApprover(ctx context.Context, tenantID, slackUserID string) (email string, ok bool)
- type ApproverLookup
- type ConsoleUserIdentity
- type CreateApprovalInput
- type DenyInput
- type Dispatcher
- type GrantInput
- type Handlers
- func (h *Handlers) ApproveRequest(w http.ResponseWriter, r *http.Request)
- func (h *Handlers) CreateRequest(w http.ResponseWriter, r *http.Request)
- func (h *Handlers) DenyRequest(w http.ResponseWriter, r *http.Request)
- func (h *Handlers) GetRequest(w http.ResponseWriter, r *http.Request)
- func (h *Handlers) ListPending(w http.ResponseWriter, r *http.Request)
- func (h *Handlers) RegisterRoutes(r chi.Router)
- func (h *Handlers) SlackInteractions(w http.ResponseWriter, r *http.Request)
- type LLMSummaryProvider
- type NotificationOutbox
- type Store
- func (s *Store) ClaimDueNotifications(ctx context.Context, limit int) ([]NotificationOutbox, error)
- func (s *Store) CreateRequest(ctx context.Context, in CreateApprovalInput) (*ApprovalRequest, error)
- func (s *Store) DenyRequest(ctx context.Context, requestID string, in DenyInput) error
- func (s *Store) ExpirePendingRequests(ctx context.Context) (int64, error)
- func (s *Store) FindAndConsumeGrant(ctx context.Context, tenantID, agentID, tool, action, resource string) (*ApprovalGrant, error)
- func (s *Store) FindUserByEmail(ctx context.Context, email string) (*ConsoleUserIdentity, error)
- func (s *Store) FindUserBySlackUserID(ctx context.Context, slackUserID string) (*ConsoleUserIdentity, error)
- func (s *Store) GetRequest(ctx context.Context, id string) (*ApprovalRequest, error)
- func (s *Store) GrantRequest(ctx context.Context, requestID string, in GrantInput) (*ApprovalGrant, error)
- func (s *Store) IsApproverUserForTenant(ctx context.Context, tenantID, userID string) (bool, error)
- func (s *Store) ListPending(ctx context.Context, tenantID string, limit, offset int) ([]ApprovalRequest, error)
- func (s *Store) MarkNotificationFailed(ctx context.Context, id string, lastErr string) error
- func (s *Store) MarkNotificationRetry(ctx context.Context, id string, attempts int, nextAttemptAt time.Time, ...) error
- func (s *Store) MarkNotificationSent(ctx context.Context, id string) error
- type Summarizer
- type SummaryInput
- type SummaryOutput
- type SummaryProvider
- type TemplateSummarizer
- type TemplateSummaryProvider
Constants ¶
This section is empty.
Variables ¶
var ErrApprovalRequestNotPendingOrExpired = errors.New("approval request not pending or expired")
Functions ¶
func BuildApprovalRequestedCloudEvent ¶
func BuildApprovalRequestedCloudEvent(n NotificationOutbox, source, summary string) ([]byte, error)
func ParseSecretRefMap ¶
func SafeTransport ¶
SafeTransport returns an http.Transport that blocks connections to private, loopback, and link-local IP addresses (MED-02: DNS rebinding defense).
func SignBodyHMACSHA256 ¶
func ValidateWebhookURL ¶
Types ¶
type ApprovalGrant ¶
type ApprovalGrant struct {
ID string `json:"id"`
RequestID string `json:"request_id"`
TenantID string `json:"tenant_id"`
Approver string `json:"approver"`
Scope ApprovalScope `json:"scope"`
MaxUses int `json:"max_uses"`
UsesLeft int `json:"uses_left"`
ExpiresAt time.Time `json:"expires_at"`
GrantedAt time.Time `json:"granted_at"`
}
type ApprovalRequest ¶
type ApprovalRequest struct {
ID string `json:"id"`
EventID string `json:"event_id"`
TenantID string `json:"tenant_id"`
AgentID string `json:"agent_id"`
UserID string `json:"user_id,omitempty"`
UserName string `json:"user_name,omitempty"`
UserEmail string `json:"user_email,omitempty"`
SessionID string `json:"session_id,omitempty"`
TraceID string `json:"trace_id,omitempty"`
Tool string `json:"tool"`
Action string `json:"action"`
Resource string `json:"resource,omitempty"`
RiskScore int `json:"risk_score"`
Reason string `json:"reason"`
DenyReason string `json:"deny_reason,omitempty"`
Status string `json:"status"` // "pending", "approved", "denied", "expired"
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
}
type ApprovalScope ¶
type ApproverAuthorizer ¶
type ApproverAuthorizer struct {
// contains filtered or unexported fields
}
func NewApproverAuthorizer ¶
func NewApproverAuthorizer(lookup ApproverLookup, emailAllowlist, slackAllowlist string, source string) *ApproverAuthorizer
NewApproverAuthorizer gates authorization by DB (users+user_roles) and optionally by env allowlists. env allowlists are intended as a dev bootstrap fallback.
func (*ApproverAuthorizer) AllowEmail ¶
func (a *ApproverAuthorizer) AllowEmail(ctx context.Context, tenantID, email string) bool
AllowEmail checks whether the given email identity is an approver for the tenant. DB is the primary source of truth; env allowlists are optional depending on allowlist source.
func (*ApproverAuthorizer) AllowSlack ¶
func (a *ApproverAuthorizer) AllowSlack(ctx context.Context, tenantID, slackUserID string) bool
func (*ApproverAuthorizer) ResolveSlackApprover ¶
func (a *ApproverAuthorizer) ResolveSlackApprover(ctx context.Context, tenantID, slackUserID string) (email string, ok bool)
ResolveSlackApprover returns the console user email associated with the given Slack user id, but only if that user exists (slack_user_id) and is an approver for the tenant.
type ApproverLookup ¶
type ConsoleUserIdentity ¶
ConsoleUserIdentity is a minimal identity mapping from console tables used for approver authorization.
type CreateApprovalInput ¶
type CreateApprovalInput struct {
EventID string `json:"event_id"`
TenantID string `json:"tenant_id"`
AgentID string `json:"agent_id"`
Tool string `json:"tool"`
Action string `json:"action"`
Resource string `json:"resource,omitempty"`
RiskScore int `json:"risk_score"`
RiskFactors []string `json:"risk_factors,omitempty"`
Reason string `json:"reason"`
TraceID string `json:"trace_id,omitempty"`
ApproverGroup string `json:"approver_group,omitempty"`
Notify []types.PolicyNotify `json:"notify,omitempty"`
ApprovalBaseURL string `json:"approval_base_url,omitempty"`
}
type Dispatcher ¶
type Dispatcher struct {
SkipWebhookValidation bool // testing only — disables SSRF URL checks
// contains filtered or unexported fields
}
func NewDispatcher ¶
func NewDispatcher(store notificationStore, source string, secrets map[string]string, slackURL, internalToken string) *Dispatcher
func (*Dispatcher) DispatchOnce ¶
func (d *Dispatcher) DispatchOnce(ctx context.Context) error
type GrantInput ¶
type Handlers ¶
type Handlers struct {
// contains filtered or unexported fields
}
Handlers groups the HTTP handlers for the approvals service.
func NewHandlers ¶
func NewHandlers(store handlersStore, authorizer *ApproverAuthorizer, slackSigningSecret string) *Handlers
NewHandlers creates handlers backed by the given store.
func (*Handlers) ApproveRequest ¶
func (h *Handlers) ApproveRequest(w http.ResponseWriter, r *http.Request)
ApproveRequest handles POST /v1/approvals/requests/{id}/approve
func (*Handlers) CreateRequest ¶
func (h *Handlers) CreateRequest(w http.ResponseWriter, r *http.Request)
CreateRequest handles POST /v1/approvals/requests
func (*Handlers) DenyRequest ¶
func (h *Handlers) DenyRequest(w http.ResponseWriter, r *http.Request)
DenyRequest handles POST /v1/approvals/requests/{id}/deny
func (*Handlers) GetRequest ¶
func (h *Handlers) GetRequest(w http.ResponseWriter, r *http.Request)
GetRequest handles GET /v1/approvals/requests/{id}
func (*Handlers) ListPending ¶
func (h *Handlers) ListPending(w http.ResponseWriter, r *http.Request)
ListPending handles GET /v1/approvals/pending?tenant_id=...&limit=...&offset=...
func (*Handlers) RegisterRoutes ¶
RegisterRoutes mounts the approval routes on r. These routes are internal-only (behind internalAuthMiddleware). Tenant isolation is enforced at the gateway layer; the approval service trusts tenant_id values from authenticated internal callers.
func (*Handlers) SlackInteractions ¶
func (h *Handlers) SlackInteractions(w http.ResponseWriter, r *http.Request)
SlackInteractions handles POST /v1/integrations/slack/interactions.
type LLMSummaryProvider ¶
type LLMSummaryProvider struct {
// contains filtered or unexported fields
}
LLMSummaryProvider calls the llm-summarizer service over HTTP and falls back to the template provider on any error.
func NewLLMSummaryProvider ¶
func NewLLMSummaryProvider(llmURL string) *LLMSummaryProvider
func (*LLMSummaryProvider) Summarize ¶
func (p *LLMSummaryProvider) Summarize(ctx context.Context, input SummaryInput) (SummaryOutput, error)
type NotificationOutbox ¶
type NotificationOutbox struct {
ID string
ApprovalRequestID string
TenantID string
EventID string
TraceID string
Tool string
Action string
Resource string
RiskScore int
RiskFactors []string
Reason string
ApprovalURL string
ApproverGroup string
NotifyKind string
NotifyURL string
SecretRef string
SlackChannel string
Attempts int
Status string
NextAttemptAt time.Time
CreatedAt time.Time
}
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store manages approval requests and grants in Postgres.
func (*Store) ClaimDueNotifications ¶
ClaimDueNotifications claims pending due rows for delivery using row-level locking so concurrent workers cannot deliver the same ID twice.
func (*Store) CreateRequest ¶
func (s *Store) CreateRequest(ctx context.Context, in CreateApprovalInput) (*ApprovalRequest, error)
CreateRequest inserts a new pending approval request.
func (*Store) DenyRequest ¶
DenyRequest marks a pending request as denied. The original reason is preserved; deny_reason stores the denier's rationale.
func (*Store) ExpirePendingRequests ¶
ExpirePendingRequests transitions stale pending requests to 'expired' (MED-07).
func (*Store) FindAndConsumeGrant ¶
func (s *Store) FindAndConsumeGrant(ctx context.Context, tenantID, agentID, tool, action, resource string) (*ApprovalGrant, error)
FindAndConsumeGrant finds a valid grant matching the given scope and atomically decrements its usage. Iterates through all candidates (not just LIMIT 1) to ensure resource-pattern mismatches don't hide valid grants.
func (*Store) FindUserByEmail ¶
FindUserByEmail returns a console user identity by email (case-insensitive).
func (*Store) FindUserBySlackUserID ¶
func (s *Store) FindUserBySlackUserID(ctx context.Context, slackUserID string) (*ConsoleUserIdentity, error)
FindUserBySlackUserID returns a console user identity by slack user id.
func (*Store) GetRequest ¶
GetRequest fetches a single approval request.
func (*Store) GrantRequest ¶
func (s *Store) GrantRequest(ctx context.Context, requestID string, in GrantInput) (*ApprovalGrant, error)
GrantRequest approves a pending request, creating a grant. The status check is performed inside the transaction to eliminate TOCTOU races.
func (*Store) IsApproverUserForTenant ¶
IsApproverUserForTenant returns true if the given user has role='approver' for the given tenant.
func (*Store) ListPending ¶
func (s *Store) ListPending(ctx context.Context, tenantID string, limit, offset int) ([]ApprovalRequest, error)
ListPending returns pending requests for a tenant (paginated). When tenantID is empty, all pending requests across tenants are returned (used by platform admins).
func (*Store) MarkNotificationFailed ¶
MarkNotificationFailed marks an outbox row terminally failed.
type Summarizer ¶
type Summarizer interface {
Summarize(NotificationOutbox) string
}
Summarizer builds human-friendly notification summaries from sanitized fields.
type SummaryInput ¶
type SummaryInput struct {
Tool string `json:"tool"`
Action string `json:"action"`
Resource string `json:"resource"`
RiskScore int `json:"risk_score"`
RiskFactors []string `json:"risk_factors"`
Reason string `json:"reason"`
TenantID string `json:"tenant_id"`
AgentID string `json:"agent_id"`
}
func SummaryInputFromOutbox ¶
func SummaryInputFromOutbox(n NotificationOutbox) SummaryInput
SummaryInputFromOutbox converts a NotificationOutbox into sanitized SummaryInput.
type SummaryOutput ¶
type SummaryProvider ¶
type SummaryProvider interface {
Summarize(ctx context.Context, input SummaryInput) (SummaryOutput, error)
}
SummaryProvider produces human-readable summaries for approval notifications. Implementations range from deterministic templates to LLM-backed summarizers.
type TemplateSummarizer ¶
type TemplateSummarizer struct{}
TemplateSummarizer is deterministic and does not use model inference.
func (TemplateSummarizer) Summarize ¶
func (TemplateSummarizer) Summarize(n NotificationOutbox) string
type TemplateSummaryProvider ¶
type TemplateSummaryProvider struct{}
TemplateSummaryProvider is a deterministic, zero-dependency summary provider.
func (TemplateSummaryProvider) Summarize ¶
func (TemplateSummaryProvider) Summarize(_ context.Context, input SummaryInput) (SummaryOutput, error)