mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-19 20:23:35 +00:00
158 lines
7.9 KiB
Go
158 lines
7.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// AgentGrantSummary is the lightweight per-grant item returned in the List response.
|
|
// It exposes env_set (bool: has override) but NEVER the encrypted bytes.
|
|
type AgentGrantSummary struct {
|
|
GrantID uuid.UUID `json:"grant_id"`
|
|
AgentID uuid.UUID `json:"agent_id"`
|
|
AgentKey string `json:"agent_key"`
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
EnvSet bool `json:"env_set"` // true when encrypted_env IS NOT NULL — projection only, never the blob
|
|
}
|
|
|
|
// SecureCLIBinary represents a CLI binary with auto-injected credentials.
|
|
// Credentials are encrypted at rest and injected into child processes via Direct Exec Mode.
|
|
type SecureCLIBinary struct {
|
|
BaseModel
|
|
BinaryName string `json:"binary_name" db:"binary_name"`
|
|
BinaryPath *string `json:"binary_path,omitempty" db:"binary_path"`
|
|
Description string `json:"description" db:"description"`
|
|
EncryptedEnv []byte `json:"-" db:"encrypted_env"` // AES-256-GCM encrypted JSON — never serialized to API
|
|
DenyArgs json.RawMessage `json:"deny_args" db:"deny_args"` // regex patterns for blocked subcommands
|
|
DenyVerbose json.RawMessage `json:"deny_verbose" db:"deny_verbose"` // blocked verbose/debug flags
|
|
TimeoutSeconds int `json:"timeout_seconds" db:"timeout_seconds"`
|
|
Tips string `json:"tips" db:"tips"` // hint injected into TOOLS.md context
|
|
IsGlobal bool `json:"is_global" db:"is_global"`
|
|
Enabled bool `json:"enabled" db:"enabled"`
|
|
CreatedBy string `json:"created_by" db:"created_by"`
|
|
UserEnv []byte `json:"-" db:"-"` // per-user encrypted env (populated by LookupByBinary LEFT JOIN)
|
|
// EnvKeys is set by HTTP handlers only (names from decrypted env, no values); not a DB column.
|
|
EnvKeys []string `json:"env_keys,omitempty" db:"-"`
|
|
// Env is set by HTTP handlers only. Sensitive values are masked; value entries are visible.
|
|
Env map[string]SecureCLIEnvResponseEntry `json:"env,omitempty" db:"-"`
|
|
// AgentGrantsSummary is populated by List only — lightweight per-grant summary (no env bytes).
|
|
AgentGrantsSummary []AgentGrantSummary `json:"agent_grants_summary" db:"-"`
|
|
}
|
|
|
|
// MergeGrantOverrides applies agent grant overrides onto a binary config.
|
|
// Non-nil grant fields replace binary defaults; nil fields keep binary values.
|
|
func (b *SecureCLIBinary) MergeGrantOverrides(g *SecureCLIAgentGrant) {
|
|
if g == nil {
|
|
return
|
|
}
|
|
if g.DenyArgs != nil {
|
|
b.DenyArgs = *g.DenyArgs
|
|
}
|
|
if g.DenyVerbose != nil {
|
|
b.DenyVerbose = *g.DenyVerbose
|
|
}
|
|
if g.TimeoutSeconds != nil {
|
|
b.TimeoutSeconds = *g.TimeoutSeconds
|
|
}
|
|
if g.Tips != nil {
|
|
b.Tips = *g.Tips
|
|
}
|
|
// Grant env fully replaces binary default env when non-empty.
|
|
if len(g.EncryptedEnv) > 0 {
|
|
b.EncryptedEnv = g.EncryptedEnv
|
|
}
|
|
}
|
|
|
|
// SecureCLIUserCredential holds per-user encrypted env overrides for a binary.
|
|
type SecureCLIUserCredential struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
BinaryID uuid.UUID `json:"binary_id" db:"binary_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Metadata json.RawMessage `json:"metadata,omitempty" db:"metadata"`
|
|
CreatedAt string `json:"created_at" db:"created_at"`
|
|
UpdatedAt string `json:"updated_at" db:"updated_at"`
|
|
// EncryptedEnv is decrypted JSON — never serialized to API.
|
|
EncryptedEnv []byte `json:"-" db:"encrypted_env"`
|
|
}
|
|
|
|
// SecureCLIAgentGrant represents a per-agent grant with optional setting overrides.
|
|
type SecureCLIAgentGrant struct {
|
|
BaseModel
|
|
BinaryID uuid.UUID `json:"binary_id" db:"binary_id"`
|
|
AgentID uuid.UUID `json:"agent_id" db:"agent_id"`
|
|
DenyArgs *json.RawMessage `json:"deny_args,omitempty" db:"deny_args"`
|
|
DenyVerbose *json.RawMessage `json:"deny_verbose,omitempty" db:"deny_verbose"`
|
|
TimeoutSeconds *int `json:"timeout_seconds,omitempty" db:"timeout_seconds"`
|
|
Tips *string `json:"tips,omitempty" db:"tips"`
|
|
Enabled bool `json:"enabled" db:"enabled"`
|
|
// EncryptedEnv holds per-grant AES-256-GCM encrypted env vars. NULL means no override.
|
|
// Never serialized to API — HTTP layer exposes env_keys + env_set only.
|
|
EncryptedEnv []byte `json:"-" db:"encrypted_env"`
|
|
// EnvKeys is populated by HTTP handlers only (sorted key names, no values). Not a DB column.
|
|
EnvKeys []string `json:"env_keys,omitempty" db:"-"`
|
|
// Env is populated by HTTP handlers only for sanitized responses.
|
|
Env map[string]SecureCLIEnvResponseEntry `json:"env,omitempty" db:"-"`
|
|
// EnvSet indicates whether this grant has an env override. Not a DB column.
|
|
EnvSet bool `json:"env_set" db:"-"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// SecureCLIStore manages secure CLI binary credential configurations.
|
|
type SecureCLIStore interface {
|
|
Create(ctx context.Context, b *SecureCLIBinary) error
|
|
Get(ctx context.Context, id uuid.UUID) (*SecureCLIBinary, error)
|
|
Update(ctx context.Context, id uuid.UUID, updates map[string]any) error
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
|
List(ctx context.Context) ([]SecureCLIBinary, error)
|
|
|
|
// LookupByBinary finds the credential config for a binary name.
|
|
// If agentID is provided, checks grant authorization and merges overrides.
|
|
// If userID is non-empty, also fetches per-user env overrides via LEFT JOIN.
|
|
LookupByBinary(ctx context.Context, binaryName string, agentID *uuid.UUID, userID string) (*SecureCLIBinary, error)
|
|
|
|
// ListEnabled returns all enabled configs (for TOOLS.md context generation).
|
|
ListEnabled(ctx context.Context) ([]SecureCLIBinary, error)
|
|
|
|
// ListForAgent returns all CLIs accessible by an agent (global + granted),
|
|
// with grant overrides merged into the returned configs.
|
|
ListForAgent(ctx context.Context, agentID uuid.UUID) ([]SecureCLIBinary, error)
|
|
|
|
// IsRegisteredBinary reports whether a binary with the given name is
|
|
// registered and enabled for the tenant in ctx AND requires a grant
|
|
// (is_global = false). Used by the shell exec gate to hard-deny
|
|
// execution of credentialed binaries when the calling agent has no
|
|
// grant. is_global = true binaries are open to all agents and MUST
|
|
// NOT be reported as gate-needing. Tenant-scoped unless IsCrossTenant(ctx).
|
|
// Returns (false, nil) when name is empty or tenant context is missing.
|
|
IsRegisteredBinary(ctx context.Context, binaryName string) (bool, error)
|
|
|
|
// --- Per-user credential management ---
|
|
|
|
GetUserCredentials(ctx context.Context, binaryID uuid.UUID, userID string) (*SecureCLIUserCredential, error)
|
|
SetUserCredentials(ctx context.Context, binaryID uuid.UUID, userID string, encryptedEnv []byte) error
|
|
DeleteUserCredentials(ctx context.Context, binaryID uuid.UUID, userID string) error
|
|
ListUserCredentials(ctx context.Context, binaryID uuid.UUID) ([]SecureCLIUserCredential, error)
|
|
}
|
|
|
|
// SecureCLIAgentGrantStore manages per-agent grants for secure CLI binaries.
|
|
type SecureCLIAgentGrantStore interface {
|
|
BinaryExists(ctx context.Context, binaryID uuid.UUID) (bool, error)
|
|
AgentExists(ctx context.Context, agentID uuid.UUID) (bool, error)
|
|
Create(ctx context.Context, g *SecureCLIAgentGrant) error
|
|
Get(ctx context.Context, id uuid.UUID) (*SecureCLIAgentGrant, error)
|
|
Update(ctx context.Context, id uuid.UUID, updates map[string]any) error
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
|
ListByBinary(ctx context.Context, binaryID uuid.UUID) ([]SecureCLIAgentGrant, error)
|
|
ListByAgent(ctx context.Context, agentID uuid.UUID) ([]SecureCLIAgentGrant, error)
|
|
|
|
// UpdateGrantEnv sets the encrypted env override for a grant.
|
|
// encryptedEnv must be the plaintext JSON bytes — the store layer encrypts with AES-256-GCM.
|
|
// Pass nil to clear the env override. Fails closed if encryption key is missing.
|
|
UpdateGrantEnv(ctx context.Context, grantID uuid.UUID, plaintextEnv []byte) error
|
|
}
|