Documentation
¶
Index ¶
- Variables
- func InitOIDC(ctx context.Context, cfg config.YAMLConfigurationOIDC) error
- func InitSAML(ctx context.Context, cfg config.YAMLConfigurationSAML, entityID, acsURL string) error
- type APIQueryData
- type ActivityBucket
- type AuthMethod
- type AuthMethodsResponse
- type ContextKey
- type ContextValue
- type EnvStats
- type HandlersApi
- func (h *HandlersApi) ActiveNodesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) AllNodesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) AllQueriesShowHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) AllTagsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) AuditLogsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) AuthMethodsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarveArchiveHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarveListHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarveQueriesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarveSamplesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarveShowHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarvesActionHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CarvesRunHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CheckHandlerAuth(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) CheckHandlerNoAuth(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) DeleteNodeHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) DeleteUserTokenHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvActionsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvActivityHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvCertUploadHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvConfigurationHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvEnrollActionsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvEnrollHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvRemoveActionsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvRemoveHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentConfigHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentConfigPatchHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentCreateHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentDeleteHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentExpirationPatchHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentIntervalsPatchHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentMapHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentUpdateHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) EnvironmentsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) ErrorHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) ForbiddenHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) GetUserPermissionsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) HealthHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) InactiveNodesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) LoginEnvironmentsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) LoginHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) LogoutHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) LookupNodeHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) MeHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) MePasswordHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) MePatchHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) NodeActivityBatchHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) NodeActivityHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) NodeHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) NodeLogsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) NodesPagedHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) OIDCCallbackHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) OIDCLoginHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) OsqueryTablesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) OsqueryVersionsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) PlatformsEnvHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) PlatformsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QueriesActionHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QueriesRunHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QueryListHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QueryResultsCSVHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QueryResultsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QuerySamplesHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) QueryShowHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) RefreshUserTokenHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) RootHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SAMLACSHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SAMLLoginHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SAMLMetadataHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SavedQueriesListHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SavedQueryCreateHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SavedQueryDeleteHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SavedQueryUpdateHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SetUserPermissionsAllHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SetUserPermissionsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SettingPatchHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SettingsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SettingsServiceEnvHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SettingsServiceEnvJSONHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SettingsServiceHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) SettingsServiceJSONHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) StatsHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) TagEnvHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) TagNodeHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) TagsActionHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) TagsEnvHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) UserActionHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) UserHandler(w http.ResponseWriter, r *http.Request)
- func (h *HandlersApi) UsersHandler(w http.ResponseWriter, r *http.Request)
- type HandlersOption
- func WithAuditLog(auditLog *auditlog.AuditLogManager) HandlersOption
- func WithCache(rds *cache.RedisManager) HandlersOption
- func WithCarves(carves *carves.Carves) HandlersOption
- func WithDB(db *gorm.DB) HandlersOption
- func WithDebugHTTP(cfg *config.YAMLConfigurationDebug) HandlersOption
- func WithEnvs(envs *environments.EnvManager) HandlersOption
- func WithJWTSecret(secret []byte) HandlersOption
- func WithName(name string) HandlersOption
- func WithNodes(nodes *nodes.NodeManager) HandlersOption
- func WithOIDC(enabled bool) HandlersOption
- func WithOsqueryTables(tables []types.OsqueryTable) HandlersOption
- func WithOsqueryValues(values config.YAMLConfigurationOsquery) HandlersOption
- func WithQueries(queries *queries.Queries) HandlersOption
- func WithSAML(enabled bool) HandlersOption
- func WithSettings(settings *settings.Settings) HandlersOption
- func WithTags(tags *tags.TagManager) HandlersOption
- func WithUsers(users *users.UserManager) HandlersOption
- func WithVersion(version string) HandlersOption
- type LogoutResponse
- type NodeActivityBucket
- type NodeLogsResponse
- type StatsResponse
Constants ¶
This section is empty.
Variables ¶
Define targets to be used to retrieve an environment map
var ErrAuthUserRejected = errors.New("auth: identity cannot be resolved to an AdminUser")
ErrAuthUserRejected is returned by resolveFederatedUser when the identity cannot be turned into a usable AdminUser. Callers should not surface this to clients verbatim — return a generic "authentication failed" error to avoid leaking which path rejected (timing-oracle and information-disclosure defense).
var QueryTargets = map[string]bool{ queries.TargetAll: true, queries.TargetAllFull: true, queries.TargetActive: true, queries.TargetCompleted: true, queries.TargetExpired: true, queries.TargetSaved: true, queries.TargetHiddenCompleted: true, queries.TargetDeleted: true, queries.TargetHidden: true, }
QueryTargets enumerates the target filters accepted by QueryListHandler. TargetHiddenActive is intentionally excluded: no UI tab references it and GetByEnvTargetPaged has no branch for it (mirrors Gets() which returns nothing).
Functions ¶
func InitOIDC ¶ added in v0.5.3
func InitOIDC(ctx context.Context, cfg config.YAMLConfigurationOIDC) error
InitOIDC constructs the global OIDC provider for osctrl-api from the YAML/CLI config. Returns a non-nil error on:
- Config.Validate failure (missing issuer, client id, etc.)
- OIDC discovery failure (IdP unreachable, malformed metadata)
The caller (cmd/api/main.go) should treat any error as fatal during startup — we want loud failures so misconfigured deployments don't run silently with federated login broken.
Username sanitization defaults to STRICT for cmd/api (no LegacyPermissiveUsername shim). osctrl-api is greenfield; we don't have pre-existing email-format usernames to preserve.
func InitSAML ¶ added in v0.5.3
InitSAML constructs the global SAML provider for osctrl-api from the YAML/CLI config. Returns a non-nil error on:
- Config.Validate failure (missing metadata, required fields, etc.)
- SAML metadata fetch / parse failure
The caller (cmd/api/main.go) treats any error as fatal during startup — loud failures over silent broken-SSO.
Username sanitization defaults to STRICT for cmd/api (no LegacyPermissiveUsername shim). osctrl-api is greenfield; no pre-existing email-format usernames to preserve.
Types ¶
type ActivityBucket ¶ added in v0.5.2
type ActivityBucket struct {
BucketStart time.Time `json:"bucket_start"`
Config int `json:"config"`
Query int `json:"query"`
Carve int `json:"carve"`
Enroll int `json:"enroll"`
}
ActivityBucket is one cell of the 24-hour activity heatmap. BucketStart is the start of the 15-minute window (UTC, RFC3339); the four counters are the audit-log entry counts that fell into that window for each category.
Categories (audit log_type → category):
- config ← Setting (8) + Environment (7)
- query ← Query (4)
- carve ← Carve (5)
- enroll ← Node (3) — covers enroll, archive, deletion
type AuthMethod ¶ added in v0.5.3
type AuthMethod struct {
Type string `json:"type"`
// LoginURL is the relative URL the SPA should redirect the
// browser to when this method is chosen. For "password" this
// is "/api/v1/login/{env}" (env is interpolated client-side
// from the env switcher). For "oidc" this is the global
// "/api/v1/auth/oidc/login" — env is irrelevant for federated
// login because the federated user resolves to a single
// AdminUser row regardless of which env tab they were viewing.
LoginURL string `json:"loginUrl"`
}
AuthMethod describes one auth surface advertised to the SPA. `type` is the discriminator; clients render the appropriate UI based on it. We avoid leaking the issuer URL, client id, or any other IdP-specific detail at this layer — those are the IdP's responsibility to reveal once the user is redirected.
type AuthMethodsResponse ¶ added in v0.5.3
type AuthMethodsResponse struct {
Methods []AuthMethod `json:"methods"`
}
AuthMethodsResponse is the JSON shape returned by GET /api/v1/auth/methods. Always returns at least the "password" method; OIDC is added only when --oidc-enabled is true AND the provider validated at startup. The SPA renders one button per method in stable order; we don't promise stable order beyond "password always first."
type ContextKey ¶
type ContextKey string
ContextKey to help with the context key, to pass session data
type ContextValue ¶
ContextValue to hold session data in the context
type EnvStats ¶ added in v0.5.2
type EnvStats struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Active int64 `json:"active"`
Inactive int64 `json:"inactive"`
Total int64 `json:"total"`
ActiveQueries int `json:"active_queries"`
ActiveCarves int `json:"active_carves"`
// PlatformCounts buckets the env's nodes by OS family (linux / darwin /
// windows / other). Drives the Nodes-table QuickFilters chip row. Counts
// are total (active + inactive), since the filter chip lists all nodes
// of that platform regardless of staleness — the Active/Inactive toggle
// is independent.
PlatformCounts nodes.PlatformCounts `json:"platform_counts"`
}
EnvStats is one row in the per-env breakdown returned by /api/v1/stats.
type HandlersApi ¶
type HandlersApi struct {
DB *gorm.DB
Users *users.UserManager
Tags *tags.TagManager
Envs *environments.EnvManager
Nodes *nodes.NodeManager
Queries *queries.Queries
Carves *carves.Carves
Settings *settings.Settings
RedisCache *cache.RedisManager
ServiceVersion string
ServiceName string
AuditLog *auditlog.AuditLogManager
ApiConfig *config.APIConfiguration
DebugHTTP *zerolog.Logger
DebugHTTPConfig *config.YAMLConfigurationDebug
OsqueryTables []types.OsqueryTable
OsqueryValues config.YAMLConfigurationOsquery
// JWTSecret is the HMAC key used by pkg/auth state-cookie
// helpers. Populated via WithJWTSecret at handler init. Same
// bytes the Users manager signs user JWTs with; the auth
// state-cookie code uses audience claims to segregate uses.
JWTSecret []byte
// OIDCEnabled is the global toggle for federated login on
// osctrl-api. When true, /api/v1/auth/methods advertises OIDC
// and the OIDC login/callback routes initiate the federated
// flow. When false, only password auth is offered. OIDC is
// global because osctrl-api is a single-tenant API surface;
// per-env identity provider config (if ever needed) would
// belong on the operator layer, not here.
OIDCEnabled bool
// SAMLEnabled is the SAML analogue of OIDCEnabled. Same
// semantics: global, single-tenant, advertised through
// /api/v1/auth/methods. OIDC and SAML can both be on
// simultaneously — the SPA renders one button per advertised
// method.
SAMLEnabled bool
}
func CreateHandlersApi ¶
func CreateHandlersApi(opts ...HandlersOption) *HandlersApi
CreateHandlersApi to initialize the Admin handlers struct
func (*HandlersApi) ActiveNodesHandler ¶
func (h *HandlersApi) ActiveNodesHandler(w http.ResponseWriter, r *http.Request)
ActiveNodesHandler - GET Handler for active JSON nodes @Summary List active nodes @Description Returns active enrolled nodes for an environment. @Tags nodes @Produce json @Param env path string true "Environment name or UUID" @Success 200 {array} nodes.OsqueryNode @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env}/active [get]
func (*HandlersApi) AllNodesHandler ¶
func (h *HandlersApi) AllNodesHandler(w http.ResponseWriter, r *http.Request)
AllNodesHandler - GET Handler for all JSON nodes @Summary List all nodes @Description Returns all enrolled nodes for an environment. @Tags nodes @Produce json @Param env path string true "Environment name or UUID" @Success 200 {array} nodes.OsqueryNode @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env}/all [get]
func (*HandlersApi) AllQueriesShowHandler ¶
func (h *HandlersApi) AllQueriesShowHandler(w http.ResponseWriter, r *http.Request)
AllQueriesShowHandler - GET Handler to return all queries in JSON @Summary List queries @Description Returns on-demand queries for an environment. @Tags queries @Produce json @Param env path string true "Environment name or UUID" @Success 200 {array} queries.DistributedQuery @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env} [get] @Router /api/v1/all-queries/{env} [get]
func (*HandlersApi) AllTagsHandler ¶
func (h *HandlersApi) AllTagsHandler(w http.ResponseWriter, r *http.Request)
AllTagsHandler - GET Handler for all JSON tags @Summary List tags @Description Returns tags across environments. @Tags tags @Produce json @Success 200 {array} tags.AdminTag @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/tags [get]
func (*HandlersApi) AuditLogsHandler ¶ added in v0.4.8
func (h *HandlersApi) AuditLogsHandler(w http.ResponseWriter, r *http.Request)
AuditLogsHandler - GET /api/v1/audit-logs
Query params:
?service=... exact match on service name ?username=... case-insensitive partial match on username ?type=... log type integer (1..10), see pkg/auditlog.LogType* ?env_uuid=... filter to one environment (resolved to internal ID) ?since=RFC3339 created_at >= since ?until=RFC3339 created_at <= until ?page=N 1-indexed page; default 1 ?page_size=N default 50, max 500
Returns the SPA-canonical paginated envelope. The handler audit-logs the visit on success. @Summary List audit logs @Description Returns paginated API audit log entries. @Tags audit @Produce json @Param page query int false "Page number" @Param page_size query int false "Page size" @Param q query string false "Search query" @Param service query string false "Service filter" @Param username query string false "Username filter" @Param env query string false "Environment filter" @Success 200 {object} types.AuditLogsPagedResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/audit-logs [get]
func (*HandlersApi) AuthMethodsHandler ¶ added in v0.5.3
func (h *HandlersApi) AuthMethodsHandler(w http.ResponseWriter, r *http.Request)
AuthMethodsHandler — GET /api/v1/auth/methods (no env path).
Unauthenticated by design: the SPA calls this BEFORE the user has logged in to decide which login UI to render. The response leaks only the *list* of auth shapes; no per-user, per-env, or per-IdP detail. The endpoint exists so the SPA never has to ship a "is OIDC compiled in?" build-time flag — operators toggle it server-side without re-deploying the SPA.
Rate-limited at the route layer (same preAuthRateLimit as the env/sample endpoints) to keep this from being a free metadata scrape vector. @Summary List authentication methods @Description Returns the authentication methods enabled for the API login UI. @Tags auth @Produce json @Success 200 {object} AuthMethodsResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/auth/methods [get]
func (*HandlersApi) CarveArchiveHandler ¶ added in v0.5.2
func (h *HandlersApi) CarveArchiveHandler(w http.ResponseWriter, r *http.Request)
CarveArchiveHandler - GET /api/v1/carves/{env}/archive/{name}
(The literal `archive` lives in segment 2 — not as a `/{name}/archive` suffix — because Go's ServeMux refuses to register patterns that ambiguously overlap with `/{env}/queries/{target}` registered on the same prefix.)
Streams (or redirects to) the reassembled carve archive blob.
Resolution rules:
- The carve query identified by {name} must exist and be type=carve.
- If exactly one CarvedFile exists for the query, it is served.
- If multiple exist, an explicit ?session=<session-id> must select one. A missing/ambiguous session selector returns 409 Conflict.
- If the underlying file is not yet archived, it is archived on demand (local or DB carver: written to a temp dir, then served; S3: a presigned download URL is returned via 302 redirect).
Content-Disposition is set to attachment with the carve archive filename. @Summary Download carve archive @Description Downloads the archive for a completed file carve. @Tags carves @Produce application/octet-stream @Param env path string true "Environment name or UUID" @Param name path string true "Carve query name" @Success 200 {file} file @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/{env}/archive/{name} [get]
func (*HandlersApi) CarveListHandler ¶
func (h *HandlersApi) CarveListHandler(w http.ResponseWriter, r *http.Request)
CarveListHandler - GET /api/v1/carves/{env}
Paginated, sorted, searchable list of carve queries (DistributedQuery rows with type=carve). Query params: page, page_size, q, sort, dir, target. Empty result → HTTP 200 with items: []. @Summary List file carves @Description Returns paginated file carves for an environment. @Tags carves @Produce json @Param env path string true "Environment name or UUID" @Param page query int false "Page number" @Param page_size query int false "Page size" @Param q query string false "Search query" @Success 200 {object} types.CarvesPagedResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/{env} [get] @Router /api/v1/carves/{env}/list [get]
func (*HandlersApi) CarveQueriesHandler ¶
func (h *HandlersApi) CarveQueriesHandler(w http.ResponseWriter, r *http.Request)
CarveQueriesHandler - GET /api/v1/carves/{env}/queries/{target}
Returns carve queries by target. Retained from the legacy contract; the canonical list endpoint is now CarveListHandler at /api/v1/carves/{env}. @Summary List carve queries @Description Returns file carve queries by target and environment. @Tags carves @Produce json @Param env path string true "Environment name or UUID" @Param target path string true "Carve target filter" @Success 200 {array} queries.DistributedQuery @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/{env}/queries/{target} [get]
func (*HandlersApi) CarveSamplesHandler ¶ added in v0.5.2
func (h *HandlersApi) CarveSamplesHandler(w http.ResponseWriter, r *http.Request)
CarveSamplesHandler - GET /api/v1/carves/samples
Returns the static starter library of common carve-target file paths (e.g., /etc/passwd, C:\Windows\System32\config\SAM). Same auth posture as QuerySamplesHandler. The path list is the set of high-value exfiltration locations osctrl is provisioned to carve; surfacing it to anonymous callers was a free recon gift to attackers. @Summary List carve samples @Description Returns sample carve path templates. @Tags carves @Produce json @Success 200 {array} carves.CarveSample @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/samples [get]
func (*HandlersApi) CarveShowHandler ¶
func (h *HandlersApi) CarveShowHandler(w http.ResponseWriter, r *http.Request)
CarveShowHandler - GET /api/v1/carves/{env}/{name}
Returns the carve query metadata plus the array of per-node CarvedFile rows produced by the carve. Returns 404 when the carve query name does not exist in the environment. @Summary Get file carve @Description Returns a file carve and the files produced by it. @Tags carves @Produce json @Param env path string true "Environment name or UUID" @Param name path string true "Carve query name" @Success 200 {object} types.CarveDetailResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/{env}/{name} [get]
func (*HandlersApi) CarvesActionHandler ¶
func (h *HandlersApi) CarvesActionHandler(w http.ResponseWriter, r *http.Request)
CarvesActionHandler - POST /api/v1/carves/{env}/{action}/{name} @Summary Execute carve action @Description Deletes, expires, or otherwise acts on a file carve. @Tags carves @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param action path string true "Carve action" @Param name path string true "Carve query name" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/{env}/{action}/{name} [post]
func (*HandlersApi) CarvesRunHandler ¶
func (h *HandlersApi) CarvesRunHandler(w http.ResponseWriter, r *http.Request)
CarvesRunHandler - POST /api/v1/carves/{env} @Summary Run file carve @Description Starts a new file carve. @Tags carves @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.ApiDistributedQueryRequest true "Request body" @Success 200 {object} types.ApiQueriesResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/carves/{env} [post]
func (*HandlersApi) CheckHandlerAuth ¶ added in v0.4.6
func (h *HandlersApi) CheckHandlerAuth(w http.ResponseWriter, r *http.Request)
CheckHandlerAuth - Handle authenticated check requests @Summary Authenticated API check @Description Returns API availability for an authenticated user. @Tags checks @Produce json @Success 200 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/checks-auth [get]
func (*HandlersApi) CheckHandlerNoAuth ¶ added in v0.4.6
func (h *HandlersApi) CheckHandlerNoAuth(w http.ResponseWriter, r *http.Request)
CheckHandlerNoAuth - Handle unauthenticated check requests @Summary Unauthenticated API check @Description Returns API availability without requiring authentication. @Tags checks @Produce json @Success 200 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/checks-no-auth [get]
func (*HandlersApi) DeleteNodeHandler ¶
func (h *HandlersApi) DeleteNodeHandler(w http.ResponseWriter, r *http.Request)
DeleteNodeHandler - POST Handler to delete single node @Summary Delete node @Description Deletes a node from an environment. @Tags nodes @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.ApiNodeGenericRequest true "Request body" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env}/delete [post]
func (*HandlersApi) DeleteUserTokenHandler ¶ added in v0.5.2
func (h *HandlersApi) DeleteUserTokenHandler(w http.ResponseWriter, r *http.Request)
DeleteUserTokenHandler - DELETE /api/v1/users/{username}/token
Clears the user's APIToken so any existing JWT for them stops working. Requires super-admin OR the user themselves. @Summary Delete user token @Description Deletes an API token for a user. @Tags users @Produce json @Param username path string true "Username" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username}/token [delete]
func (*HandlersApi) EnvActionsHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvActionsHandler(w http.ResponseWriter, r *http.Request)
EnvActionsHandler - POST Handler to perform actions (create, delete, edit) on environments @Summary Execute environment action @Description Creates or modifies an environment using the legacy action endpoint. @Tags environments @Accept json @Produce json @Param request body types.ApiEnvRequest true "Request body" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/actions [post]
func (*HandlersApi) EnvActivityHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvActivityHandler(w http.ResponseWriter, r *http.Request)
EnvActivityHandler — GET /api/v1/stats/activity/{env}?interval=KEY
Returns audit-log activity for one env over the requested interval, bucketed at a fixed size per interval (see activityIntervalPresets). `interval` accepts 3h / 6h / 12h / 1d / 2d / 3d / 7d (default 1d, falls back to 1d on any unknown value rather than 400ing — the SPA picker is the only allowed source).
Buckets are emitted contiguously — empty windows return zero rows for that bucket — so the SPA can render the grid without densifying client-side. @Summary Get environment activity @Description Returns activity buckets for an environment. @Tags stats @Produce json @Param env path string true "Environment name or UUID" @Param hours query int false "Number of hours to include" @Success 200 {object} ActivityBucket @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/stats/activity/{env} [get]
func (*HandlersApi) EnvCertUploadHandler ¶ added in v0.5.3
func (h *HandlersApi) EnvCertUploadHandler(w http.ResponseWriter, r *http.Request)
EnvCertUploadHandler - POST handler to upload the enrollment certificate for an environment. Body: { "certificate_b64": "<base64 PEM>" }. The PEM must parse as one or more CERTIFICATE blocks and the leaf must be a real x509 cert — we don't accept "looks like base64 of something." Legacy admin's equivalent path skipped this validation; the SPA target gets it so a typo'd paste fails fast instead of breaking enrollment downloads.
func (*HandlersApi) EnvConfigurationHandler ¶ added in v0.5.3
func (h *HandlersApi) EnvConfigurationHandler(w http.ResponseWriter, r *http.Request)
EnvConfigurationHandler - GET handler returning the assembled osquery configuration JSON for an environment. Returns the stored composed blob (options + schedule + packs + decorators + ATC). The composition is kept up to date by RefreshConfiguration, which fires from every parts mutation (UpdateOptions / UpdateSchedule / UpdatePacks / etc. in pkg/environments/osqueryconf.go), so reading the cached value is safe — the agents see the exact same blob.
SECURITY: deliberately a pure read. The first cut of this handler called RefreshConfiguration on every GET, which turned the endpoint into a CSRF-via-GET shape and a hot-loop DB-write hazard when React-Query's stale refetch path hit it. The mutation path on the parts is the canonical place for the recompose.
func (*HandlersApi) EnvEnrollActionsHandler ¶
func (h *HandlersApi) EnvEnrollActionsHandler(w http.ResponseWriter, r *http.Request)
EnvEnrollActionsHandler - POST Handler to perform actions (extend, expire) in enroll values @Summary Execute enrollment action @Description Extends, expires, rotates, or updates enrollment values for an environment. @Tags environments @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param action path string true "Enrollment action" @Param request body types.ApiActionsRequest true "Request body" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env}/enroll/{action} [post]
func (*HandlersApi) EnvEnrollHandler ¶
func (h *HandlersApi) EnvEnrollHandler(w http.ResponseWriter, r *http.Request)
EnvEnrollHandler - GET Handler to return node enrollment values (secret, certificate, one-liner) for an environment as JSON @Summary Get enrollment values @Description Returns enrollment helper values for an environment. @Tags environments @Produce json @Param env path string true "Environment name or UUID" @Param target path string true "Enrollment target" @Success 200 {object} types.ApiDataResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env}/enroll/{target} [get]
func (*HandlersApi) EnvRemoveActionsHandler ¶
func (h *HandlersApi) EnvRemoveActionsHandler(w http.ResponseWriter, r *http.Request)
EnvRemoveActionsHandler - POST Handler to perform actions (extend, expire) in remove values @Summary Execute removal action @Description Extends, expires, rotates, or updates removal values for an environment. @Tags environments @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param action path string true "Removal action" @Param request body types.ApiActionsRequest true "Request body" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env}/remove/{action} [post]
func (*HandlersApi) EnvRemoveHandler ¶
func (h *HandlersApi) EnvRemoveHandler(w http.ResponseWriter, r *http.Request)
EnvRemoveHandler - GET Handler to return node removal values for an environment as JSON @Summary Get removal values @Description Returns removal helper values for an environment. @Tags environments @Produce json @Param env path string true "Environment name or UUID" @Param target path string true "Removal target" @Success 200 {object} types.ApiDataResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env}/remove/{target} [get]
func (*HandlersApi) EnvironmentConfigHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentConfigHandler(w http.ResponseWriter, r *http.Request)
EnvironmentConfigHandler - GET /api/v1/environments/config/{env}
Returns the env's JSON-shaped config sections (options/schedule/packs/ decorators/atc/flags) so the SPA's Monaco editor can render each section. @Summary Get environment config @Description Returns raw osquery config sections for an environment. @Tags environments @Produce json @Param env path string true "Environment name or UUID" @Success 200 {object} types.EnvConfigResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/config/{env} [get]
func (*HandlersApi) EnvironmentConfigPatchHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentConfigPatchHandler(w http.ResponseWriter, r *http.Request)
EnvironmentConfigPatchHandler - PATCH /api/v1/environments/config/{env}
Body: optional options/schedule/packs/decorators/atc/flags string fields. Each non-nil field is validated as JSON before persisting; an invalid payload is rejected with 400 (no partial writes). @Summary Update environment config @Description Updates raw osquery config sections for an environment. @Tags environments @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.EnvConfigPatchRequest true "Request body" @Success 200 {object} types.EnvConfigResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/config/{env} [patch]
func (*HandlersApi) EnvironmentCreateHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentCreateHandler(w http.ResponseWriter, r *http.Request)
EnvironmentCreateHandler - POST /api/v1/environments
Body: { name, hostname, type? }. Generates a UUID, defaults config / schedule / packs / decorators / ATC to "{}", and persists the env. Returns 201 with the created TLSEnvironment. Super-admin only. @Summary Create environment @Description Creates an environment. @Tags environments @Accept json @Produce json @Param request body types.EnvCreateRequest true "Request body" @Success 200 {object} environments.TLSEnvironment @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments [post]
func (*HandlersApi) EnvironmentDeleteHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentDeleteHandler(w http.ResponseWriter, r *http.Request)
EnvironmentDeleteHandler - DELETE /api/v1/environments/{env}
Removes the environment. Super-admin only. Returns 200 with a message. @Summary Delete environment @Description Deletes an environment. @Tags environments @Produce json @Param env path string true "Environment name or UUID" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env} [delete]
func (*HandlersApi) EnvironmentExpirationPatchHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentExpirationPatchHandler(w http.ResponseWriter, r *http.Request)
EnvironmentExpirationPatchHandler - PATCH /api/v1/environments/expiration/{env}
Convenience wrapper around the existing enrollment lifecycle actions (extend / expire / rotate / not-expire), accepting one of those actions via JSON body instead of as a path segment. Mirrors the legacy EnvEnrollActionsHandler semantics for both enroll and remove paths. @Summary Update environment expiration @Description Updates enrollment expiration state for an environment. @Tags environments @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.EnvExpirationPatchRequest true "Request body" @Success 200 {object} environments.TLSEnvironment @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/expiration/{env} [patch]
func (*HandlersApi) EnvironmentHandler ¶
func (h *HandlersApi) EnvironmentHandler(w http.ResponseWriter, r *http.Request)
EnvironmentHandler - GET Handler to return one environment by UUID as JSON @Summary Get environment @Description Returns one environment by name or UUID. @Tags environments @Produce json @Param env path string true "Environment name or UUID" @Success 200 {object} types.TLSEnvironmentView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env} [get]
func (*HandlersApi) EnvironmentIntervalsPatchHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentIntervalsPatchHandler(w http.ResponseWriter, r *http.Request)
EnvironmentIntervalsPatchHandler - PATCH /api/v1/environments/intervals/{env}
Body: { config_interval?, log_interval?, query_interval? }. Updates the three node-pull intervals atomically. Unsupplied fields are kept. @Summary Update environment intervals @Description Updates osquery interval settings for an environment. @Tags environments @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.EnvIntervalsPatchRequest true "Request body" @Success 200 {object} environments.TLSEnvironment @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/intervals/{env} [patch]
func (*HandlersApi) EnvironmentMapHandler ¶
func (h *HandlersApi) EnvironmentMapHandler(w http.ResponseWriter, r *http.Request)
EnvironmentMapHandler - GET Handler to return one environment as JSON @Summary Map environments @Description Returns an environment lookup map by target key. @Tags environments @Produce json @Param target path string true "Map target: id, name, or uuid" @Success 200 {object} map[string]environments.NameUUID @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/map/{target} [get]
func (*HandlersApi) EnvironmentUpdateHandler ¶ added in v0.5.2
func (h *HandlersApi) EnvironmentUpdateHandler(w http.ResponseWriter, r *http.Request)
EnvironmentUpdateHandler - PATCH /api/v1/environments/{env}
Updates name / hostname / type / icon / debug_http / accept_enrolls. Other env fields go through the per-section endpoints. Super-admin only. @Summary Update environment @Description Updates an environment. @Tags environments @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.EnvUpdateRequest true "Request body" @Success 200 {object} environments.TLSEnvironment @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments/{env} [patch]
func (*HandlersApi) EnvironmentsHandler ¶
func (h *HandlersApi) EnvironmentsHandler(w http.ResponseWriter, r *http.Request)
EnvironmentsHandler - GET Handler to return all environments as JSON.
Super-admins see every env; non-super-admins get the subset where their EnvAccess.User OR EnvAccess.Admin is true (the read-surface gate elsewhere in the API). The previous super-admin-only gate meant a non-super-admin user with valid env permissions couldn't even populate the SPA's env switcher — their nav read "No environments configured" even though they had access to envs. @Summary List environments @Description Returns environments visible to the authenticated user. @Tags environments @Produce json @Success 200 {array} types.TLSEnvironmentView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/environments [get]
func (*HandlersApi) ErrorHandler ¶
func (h *HandlersApi) ErrorHandler(w http.ResponseWriter, r *http.Request)
ErrorHandler - Handle error requests @Summary API error response @Description Returns a generic API error response. @Tags system @Produce json @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {string} string "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /error [get]
func (*HandlersApi) ForbiddenHandler ¶
func (h *HandlersApi) ForbiddenHandler(w http.ResponseWriter, r *http.Request)
ForbiddenHandler - Handle forbidden error requests @Summary API forbidden response @Description Returns a generic forbidden response. @Tags system @Produce json @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {string} string "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /forbidden [get]
func (*HandlersApi) GetUserPermissionsHandler ¶ added in v0.5.3
func (h *HandlersApi) GetUserPermissionsHandler(w http.ResponseWriter, r *http.Request)
GetUserPermissionsHandler - GET /api/v1/users/{username}/permissions
Returns the target user's current permission map: env UUID → {user, query, carve, admin}. Envs with no permission rows are omitted (treated as "no access" by the SPA). Requires super-admin (AdminLevel, NoEnvironment).
Used by the Permissions modal to prefill checkboxes with the user's existing access for the selected env, so the operator sees current state before making changes — no more accidentally overwriting (user:true, query:true) by re-saving the modal's default of (user:true, query:false). @Summary Get user permissions @Description Returns per-environment permissions for a user. @Tags users @Produce json @Param username path string true "Username" @Success 200 {object} types.GetPermissionsResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username}/permissions [get]
func (*HandlersApi) HealthHandler ¶
func (h *HandlersApi) HealthHandler(w http.ResponseWriter, r *http.Request)
HealthHandler - Handle health requests @Summary API health check @Description Returns the API health response. @Tags system @Produce json @Success 200 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /health [get]
func (*HandlersApi) InactiveNodesHandler ¶
func (h *HandlersApi) InactiveNodesHandler(w http.ResponseWriter, r *http.Request)
InactiveNodesHandler - GET Handler for inactive JSON nodes @Summary List inactive nodes @Description Returns inactive enrolled nodes for an environment. @Tags nodes @Produce json @Param env path string true "Environment name or UUID" @Success 200 {array} nodes.OsqueryNode @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env}/inactive [get]
func (*HandlersApi) LoginEnvironmentsHandler ¶ added in v0.5.2
func (h *HandlersApi) LoginEnvironmentsHandler(w http.ResponseWriter, r *http.Request)
LoginEnvironmentsHandler - GET /api/v1/login/environments
Pre-auth endpoint that returns the list of environments the user may attempt to log into. Surface is intentionally minimal: only the env UUID and name. No enroll secrets, no certificates, no settings, no hostnames — those all stay behind auth on /api/v1/environments and its CRUD siblings.
Rationale: forcing the user to type the env name on the login screen is bad UX (you don't know it until you've logged in once, and single-env installs only ever have one option). The legacy admin shows env names pre-auth in its login form, so we're not changing the security posture — just exposing the same identifiers that the URL space already commits to using post-auth.
Like POST /login/{env}, this lives behind the per-IP rate limit registered in main.go so the endpoint can't be turned into an env-enumeration oracle for brute-force prep beyond the limit. @Summary List login environments @Description Returns pre-auth environment choices for the login UI. @Tags auth @Produce json @Success 200 {array} types.LoginEnvironment @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/login/environments [get]
func (*HandlersApi) LoginHandler ¶
func (h *HandlersApi) LoginHandler(w http.ResponseWriter, r *http.Request)
LoginHandler - POST Handler for API login request
Registered on both POST /api/v1/login and POST /api/v1/login/{env}. When {env} is present, the handler additionally verifies that the user has AdminLevel access to that specific environment before issuing a token. When {env} is absent (the SPA's default), the handler authenticates the user and issues a token without an env-scoped permission check — per-request authorization on every subsequent endpoint enforces env access anyway. @Summary Log in @Description Authenticates an API user and returns a JWT token. @Tags auth @Accept json @Produce json @Param env path string false "Environment name or UUID" @Param request body types.ApiLoginRequest true "Request body" @Success 200 {object} types.ApiLoginResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/login [post] @Router /api/v1/login/{env} [post]
func (*HandlersApi) LogoutHandler ¶ added in v0.5.3
func (h *HandlersApi) LogoutHandler(w http.ResponseWriter, r *http.Request)
LogoutHandler — POST /api/v1/logout.
Three-tier teardown:
Clear osctrl_token + osctrl_csrf cookies on the response (Max-Age=0 expires them immediately client-side).
Clear the user's APIToken in the DB so any cached copy of the JWT — including the same JWT in another browser tab — fails the handlerAuthCheck APIToken-match guard on its next request. This is the server-side revocation half: cookie expiry alone is client-honor-system; APIToken=="" turns the token into a no-op even if the bytes are reused.
Return the IdP's end_session_endpoint URL so the SPA can navigate to it. Keycloak's logout page in turn redirects the browser to post_logout_redirect_uri (here, the SPA's /login route). Without this step, the next "Continue with SSO" would silently re-auth against the still-valid IdP session cookie and the user would never see a credential prompt.
Idempotent: a request with no auth context (already-logged-out user) still emits cookie-clearing headers and an end-session URL, returns 200. We do not gate the endpoint behind handlerAuthCheck for this reason — logging out from an expired session should not require a fresh login first.
Username derivation: pulled from the in-memory CSRF cookie pair rather than re-parsing the JWT. The osctrl_token cookie + the stored APIToken match each other in handlerAuthCheck; here we trust the token cookie at face value because the worst case is "someone forged a logout request" — the only consequence is invalidating the legitimate user's token, which is exactly what logout is supposed to do. No privilege gain. @Summary Log out @Description Clears API session cookies and revokes the active token when present. @Tags auth @Accept json @Produce json @Success 200 {object} LogoutResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/logout [post]
func (*HandlersApi) LookupNodeHandler ¶ added in v0.4.5
func (h *HandlersApi) LookupNodeHandler(w http.ResponseWriter, r *http.Request)
LookupNodeHandler - POST Handler to lookup a node by identifier @Summary Lookup node @Description Looks up a node by UUID, hostname, or local name. @Tags nodes @Accept json @Produce json @Param request body types.ApiLookupRequest true "Request body" @Success 200 {object} nodes.OsqueryNode @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/lookup [post]
func (*HandlersApi) MeHandler ¶ added in v0.5.2
func (h *HandlersApi) MeHandler(w http.ResponseWriter, r *http.Request)
MeHandler - GET /api/v1/users/me
Returns the currently authenticated user's profile (sans password hash and API token). Useful for the SPA's Profile page. @Summary Get current user @Description Returns the currently authenticated user profile. @Tags users @Produce json @Success 200 {object} types.UserMeResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/me [get]
func (*HandlersApi) MePasswordHandler ¶ added in v0.5.2
func (h *HandlersApi) MePasswordHandler(w http.ResponseWriter, r *http.Request)
MePasswordHandler - POST /api/v1/users/me/password
Changes the currently authenticated user's password. Verifies the current password (bcrypt) before persisting the new hash. @Summary Change current user password @Description Changes the current user's password. @Tags users @Accept json @Produce json @Param request body types.PasswordChangeRequest true "Request body" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/me/password [post]
func (*HandlersApi) MePatchHandler ¶ added in v0.5.2
func (h *HandlersApi) MePatchHandler(w http.ResponseWriter, r *http.Request)
MePatchHandler - PATCH /api/v1/users/me
Updates email and/or fullname for the currently authenticated user. Sends each empty field through unchanged. Returns the updated profile. @Summary Update current user @Description Updates the current user's profile fields. @Tags users @Accept json @Produce json @Param request body types.UserMePatchRequest true "Request body" @Success 200 {object} types.UserMeResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/me [patch]
func (*HandlersApi) NodeActivityBatchHandler ¶ added in v0.5.2
func (h *HandlersApi) NodeActivityBatchHandler(w http.ResponseWriter, r *http.Request)
NodeActivityBatchHandler — GET /api/v1/stats/activity/node-batch/{env}?uuids=A,B,C&interval=KEY
Returns activity buckets for up to 100 nodes in one call. The response is a map keyed by node UUID so the SPA can render a sparkline per row in the Nodes table without firing N parallel requests.
Cap is 100 to bound the per-request DB load — each node still requires 4 timestamp queries. The SPA's pagination is already <=500 page size; for pages above 100 nodes the SPA fans out 2-3 batch requests instead.
Unknown / unauthorized UUIDs are silently omitted from the response (they're treated as "no data"), not 404'd — that lets a single bad UUID in the list not break the whole page render. @Summary Get node activity batch @Description Returns activity buckets for multiple nodes in an environment. @Tags stats @Produce json @Param env path string true "Environment name or UUID" @Param uuids query string false "Comma-separated node UUIDs" @Param hours query int false "Number of hours to include" @Success 200 {object} map[string][]NodeActivityBucket @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/stats/activity/node-batch/{env} [get]
func (*HandlersApi) NodeActivityHandler ¶ added in v0.5.2
func (h *HandlersApi) NodeActivityHandler(w http.ResponseWriter, r *http.Request)
NodeActivityHandler — GET /api/v1/stats/activity/node/{env}/{uuid}?interval=KEY
Per-node version of EnvActivityHandler. Same bucketing rules (see activityIntervalPresets). The four categories partition different DB tables (see NodeActivityBucket) keyed by the node's UUID — except node_queries which keys by numeric NodeID, looked up once from the resolved node. @Summary Get node activity @Description Returns activity buckets for a node. @Tags stats @Produce json @Param env path string true "Environment name or UUID" @Param uuid path string true "Node UUID" @Param hours query int false "Number of hours to include" @Success 200 {object} NodeActivityBucket @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/stats/activity/node/{env}/{uuid} [get]
func (*HandlersApi) NodeHandler ¶
func (h *HandlersApi) NodeHandler(w http.ResponseWriter, r *http.Request)
NodeHandler - GET Handler for single JSON nodes @Summary Get node @Description Returns a single enrolled node in an environment. @Tags nodes @Produce json @Param env path string true "Environment name or UUID" @Param node path string true "Node UUID, hostname, or local name" @Success 200 {object} types.NodeView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env}/node/{node} [get]
func (*HandlersApi) NodeLogsHandler ¶ added in v0.5.2
func (h *HandlersApi) NodeLogsHandler(w http.ResponseWriter, r *http.Request)
NodeLogsHandler returns recent log entries for a node.
Path: /api/v1/logs/{type}/{env}/{uuid}
type: "status" | "result" env: UUID or name uuid: node UUID
Query params:
since: RFC3339 timestamp; entries strictly after this point only limit: 1..1000 (default 100)
@Summary Get node logs @Description Returns recent status or result logs for a node. @Tags logs @Produce json @Param env path string true "Environment name or UUID" @Param type path string true "Log type: status or result" @Param uuid path string true "Node UUID" @Param limit query int false "Maximum number of log rows" @Param since query string false "RFC3339 lower bound" @Success 200 {object} NodeLogsResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/logs/{type}/{env}/{uuid} [get]
func (*HandlersApi) NodesPagedHandler ¶ added in v0.5.2
func (h *HandlersApi) NodesPagedHandler(w http.ResponseWriter, r *http.Request)
NodesPagedHandler returns paginated, sorted, searchable nodes for an env. This is the canonical endpoint consumed by the React admin SPA.
Query params:
status: "all" | "active" | "inactive" (default "all")
q: free-text search (case-insensitive partial match on uuid,
hostname, localname, ip, username, osquery_user, platform, version)
sort: one of nodes.SortableColumns keys (default "lastseen")
dir: "asc" | "desc" (default "desc" for lastseen, "asc" otherwise)
page: 1-indexed page number (default 1)
page_size: 1..500 (default 50)
@Summary List paginated nodes @Description Returns paginated, filtered, and sorted nodes for an environment. @Tags nodes @Produce json @Param env path string true "Environment name or UUID" @Param page query int false "Page number" @Param page_size query int false "Page size" @Param q query string false "Search query" @Param status query string false "Node status filter" @Param platform query string false "Platform filter" @Param sort query string false "Sort field" @Param order query string false "Sort order" @Success 200 {object} types.NodesPagedResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env} [get]
func (*HandlersApi) OIDCCallbackHandler ¶ added in v0.5.3
func (h *HandlersApi) OIDCCallbackHandler(w http.ResponseWriter, r *http.Request)
OIDCCallbackHandler — GET /api/v1/auth/oidc/callback.
Consumes the IdP's callback URL:
- Parse + verify the state JWT cookie (audience, expiry, signature).
- Clear the state cookie immediately — single-use semantics.
- Run Provider.HandleCallback (the full 10-step verification chain including signature, iss, aud, exp, nonce, group gate, username sanitization).
- Resolve the identity to an AdminUser (existing row or JIT provision per oidcJITProvision).
- Mint user JWT + CSRF cookies via userJWTSessionTokens.
- 302-redirect to "/" — the SPA root takes over from there.
All failure paths redirect to "/" too (no error param leak). The server-side log records WHY; the client gets a generic outcome. This is the timing-oracle defense (threat T31): every failure mode produces an indistinguishable client-visible response. @Summary Complete OIDC login @Description Handles the OIDC authorization callback and creates an API session. @Tags auth @Produce json @Param code query string false "OIDC authorization code" @Param state query string false "OIDC state" @Success 200 {string} string @Failure 302 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/auth/oidc/callback [get]
func (*HandlersApi) OIDCLoginHandler ¶ added in v0.5.3
func (h *HandlersApi) OIDCLoginHandler(w http.ResponseWriter, r *http.Request)
OIDCLoginHandler — GET /api/v1/auth/oidc/login.
Starts the Authorization Code flow:
- Mint a fresh 256-bit nonce (and, if PKCE is enabled, a verifier).
- Issue the state JWT cookie (Path=/api/v1/auth/, HttpOnly, Secure, SameSite=Lax, 10-minute TTL).
- Build the IdP authorize URL with state=<nonce>, nonce=<nonce>, and (when PKCE on) code_challenge=S256(verifier).
- 302-redirect the browser to the IdP.
Unauthenticated: the user is logging IN, so they cannot have a session yet. The state cookie is the only thing tying the eventual callback back to this request.
EnvUUID on the State is a fixed sentinel ("api") because osctrl-api has no per-env IdP concept — see auth-providers spec § "OIDC is global." The legacy admin uses "admin" for the same reason. @Summary Start OIDC login @Description Redirects the browser to the configured OIDC identity provider. @Tags auth @Produce json @Success 200 {string} string @Failure 302 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/auth/oidc/login [get]
func (*HandlersApi) OsqueryTablesHandler ¶ added in v0.5.2
func (h *HandlersApi) OsqueryTablesHandler(w http.ResponseWriter, r *http.Request)
OsqueryTablesHandler - GET Handler to return the osquery schema tables
Path: /api/v1/osquery/tables The schema is global (not env-scoped). Requires any authenticated user. Responses are cache-able for one hour since the schema rarely changes. @Summary List osquery tables @Description Returns the osquery schema table metadata known to the API. @Tags osquery @Produce json @Success 200 {array} types.OsqueryTable @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/osquery/tables [get]
func (*HandlersApi) OsqueryVersionsHandler ¶ added in v0.5.2
func (h *HandlersApi) OsqueryVersionsHandler(w http.ResponseWriter, r *http.Request)
OsqueryVersionsHandler — GET /api/v1/stats/osquery-versions.
Returns fleet-wide osquery agent version breakdown for the dashboard's "fleet hygiene" panel. Operators use this to spot stale agents that need upgrading. Cross-env (no env filter); the dashboard already surfaces the per-env breakdown in its env tiles.
Counts include both active and inactive nodes — a node sitting at an old osquery version is still "stale" even if it's offline today, because once it comes back online it'll come back stale. @Summary Get osquery version stats @Description Returns fleet-wide osquery version counts. @Tags stats @Produce json @Success 200 {object} nodes.OsqueryVersionCount @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/stats/osquery-versions [get]
func (*HandlersApi) PlatformsEnvHandler ¶
func (h *HandlersApi) PlatformsEnvHandler(w http.ResponseWriter, r *http.Request)
PlatformsEnvHandler - GET Handler to return platforms for one environment as JSON @Summary List platforms @Description Returns platform counts for an environment. @Tags platforms @Produce json @Param env path string true "Environment name or UUID" @Success 200 {array} nodes.PlatformCounts @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/platforms/{env} [get]
func (*HandlersApi) PlatformsHandler ¶
func (h *HandlersApi) PlatformsHandler(w http.ResponseWriter, r *http.Request)
PlatformsHandler - GET Handler for multiple JSON platforms
func (*HandlersApi) QueriesActionHandler ¶
func (h *HandlersApi) QueriesActionHandler(w http.ResponseWriter, r *http.Request)
QueriesActionHandler - POST Handler to delete/expire a query @Summary Execute query action @Description Deletes, expires, or otherwise acts on an on-demand query. @Tags queries @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param action path string true "Query action" @Param name path string true "Query name" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env}/{action}/{name} [post]
func (*HandlersApi) QueriesRunHandler ¶
func (h *HandlersApi) QueriesRunHandler(w http.ResponseWriter, r *http.Request)
QueriesRunHandler - POST Handler to run a query @Summary Run query @Description Starts a new distributed query. @Tags queries @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.ApiDistributedQueryRequest true "Request body" @Success 200 {object} types.ApiQueriesResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env} [post]
func (*HandlersApi) QueryListHandler ¶
func (h *HandlersApi) QueryListHandler(w http.ResponseWriter, r *http.Request)
QueryListHandler - GET Handler to return queries in JSON by target and environment (paginated)
Query params: page, page_size, q (free-text search), sort (column key), dir (asc|desc) @Summary List paginated queries @Description Returns paginated on-demand queries by target and environment. @Tags queries @Produce json @Param env path string true "Environment name or UUID" @Param target path string true "Query target filter" @Param page query int false "Page number" @Param page_size query int false "Page size" @Param q query string false "Search query" @Param sort query string false "Sort field" @Param order query string false "Sort order" @Success 200 {object} types.QueriesPagedResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env}/list/{target} [get]
func (*HandlersApi) QueryResultsCSVHandler ¶ added in v0.5.2
func (h *HandlersApi) QueryResultsCSVHandler(w http.ResponseWriter, r *http.Request)
QueryResultsCSVHandler - GET Handler to stream query results as CSV
Path: /api/v1/queries/{env}/results/csv/{name}
(The `.csv` lives as a literal path segment before `{name}` because Go's ServeMux grammar requires wildcards to end at `/` or end-of-pattern, so `{name}.csv` is a parse error at registration time.) @Summary Export query results CSV @Description Streams query results as CSV. @Tags queries @Produce text/csv @Param env path string true "Environment name or UUID" @Param name path string true "Query name" @Success 200 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env}/results/csv/{name} [get]
func (*HandlersApi) QueryResultsHandler ¶
func (h *HandlersApi) QueryResultsHandler(w http.ResponseWriter, r *http.Request)
QueryResultsHandler - GET Handler to return paginated query results in JSON
Path: /api/v1/queries/{env}/results/{name} Params: page, page_size, since (RFC3339 timestamp; unparseable → ignored)
Empty results are a valid state and return HTTP 200 with items: []. @Summary Get query results @Description Returns paginated results for an on-demand query. @Tags queries @Produce json @Param env path string true "Environment name or UUID" @Param name path string true "Query name" @Param page query int false "Page number" @Param page_size query int false "Page size" @Param since query string false "RFC3339 lower bound" @Success 200 {object} types.QueryResultsResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env}/results/{name} [get]
func (*HandlersApi) QuerySamplesHandler ¶ added in v0.5.2
func (h *HandlersApi) QuerySamplesHandler(w http.ResponseWriter, r *http.Request)
QuerySamplesHandler - GET /api/v1/queries/samples
Returns the static starter library of osquery SQL templates so the SPA's queries/new form can populate its QuickTemplates row. Authenticated.
History: an earlier revision exposed this pre-auth on the rationale that the data is static and ships with the binary. Even read-only data fingerprints the deployment as osctrl and reveals the SQL-template starter pack to anonymous callers — neither of which the only consumer (the post-login queries/new form) requires at pre-auth time. Moved behind handlerAuthCheck in cmd/api/main.go. @Summary List query samples @Description Returns sample query templates. @Tags queries @Produce json @Success 200 {array} queries.QuerySample @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/samples [get]
func (*HandlersApi) QueryShowHandler ¶
func (h *HandlersApi) QueryShowHandler(w http.ResponseWriter, r *http.Request)
QueryShowHandler - GET Handler to return a single query in JSON @Summary Get query @Description Returns a single on-demand query. @Tags queries @Produce json @Param env path string true "Environment name or UUID" @Param name path string true "Query name" @Success 200 {object} queries.DistributedQuery @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/queries/{env}/{name} [get]
func (*HandlersApi) RefreshUserTokenHandler ¶ added in v0.5.2
func (h *HandlersApi) RefreshUserTokenHandler(w http.ResponseWriter, r *http.Request)
RefreshUserTokenHandler - POST /api/v1/users/{username}/token/refresh
Generates a new JWT for the target user, persists it as the user's APIToken (invalidating the previous token), and returns the new token + expiry. Requires super-admin OR the request author asking for their own token. Audit-logged on success. @Summary Refresh user token @Description Refreshes an API token for a user. @Tags users @Accept json @Produce json @Param username path string true "Username" @Success 200 {object} types.TokenResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username}/token/refresh [post]
func (*HandlersApi) RootHandler ¶
func (h *HandlersApi) RootHandler(w http.ResponseWriter, r *http.Request)
RootHandler - Handle root requests.
`GET /` is registered as the api's liveness check (returns "✅" with 200). Go's ServeMux uses `/` as a wildcard match for any GET request the mux doesn't have a more-specific pattern for, which means typos like `GET /api/v1/totally-fake-endpoint` would land here and return 200 — confusing for clients debugging an integration. We tighten the contract: respond 200 ONLY when the request actually targets `/`. Other GETs that fall through fall out as 404.
Returning 404 here doesn't leak endpoint structure beyond what's already in the public OpenAPI; it just stops the api from silently claiming success on misrouted requests. @Summary API root liveness check @Description Returns the API root liveness response. @Tags system @Produce json @Success 200 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router / [get]
func (*HandlersApi) SAMLACSHandler ¶ added in v0.5.3
func (h *HandlersApi) SAMLACSHandler(w http.ResponseWriter, r *http.Request)
SAMLACSHandler — POST /api/v1/auth/saml/acs.
Consumes the IdP's signed SAMLResponse POST:
- Parse + verify the state JWT cookie (audience, expiry, signature).
- Clear the state cookie immediately — single-use semantics.
- Run Provider.HandleCallback (RelayState match, signature, audience, NotBefore/NotOnOrAfter, recipient, InResponseTo, replay cache, groups gate, username sanitization).
- Resolve the identity to an AdminUser (existing row or JIT provision per samlJITProvision).
- Mint user JWT + CSRF cookies via userJWTSessionTokens.
- 302-redirect to "/" — the SPA root takes over.
Failure paths redirect to "/" too (no error param leak). Server-side log records WHY; client sees a generic outcome. Timing-oracle defense matches the OIDC handler. @Summary Complete SAML login @Description Handles the SAML assertion consumer service callback and creates an API session. @Tags auth @Accept json @Produce json @Param SAMLResponse formData string false "SAML response assertion" @Param RelayState formData string false "SAML relay state" @Success 200 {string} string @Failure 302 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/auth/saml/acs [post]
func (*HandlersApi) SAMLLoginHandler ¶ added in v0.5.3
func (h *HandlersApi) SAMLLoginHandler(w http.ResponseWriter, r *http.Request)
SAMLLoginHandler — GET /api/v1/auth/saml/login.
Starts the SAML SP-initiated SSO flow:
- Mint a fresh 256-bit nonce + OAuthState (defense-in-depth split, same shape as the OIDC handler).
- Issue the state JWT cookie (Path=/api/v1/auth/, HttpOnly, Secure, SameSite=Lax, 10-minute TTL).
- Build the IdP SSO URL with RelayState=<OAuthState>. The IdP echoes RelayState verbatim on the ACS POST; SAMLACSHandler validates the echo against the state cookie.
- 302-redirect the browser to the IdP.
Unauthenticated by design: the user is logging IN. The state cookie is the only thing tying the eventual ACS POST back to this request.
EnvUUID on the State is a fixed sentinel ("api") because osctrl-api is single-tenant for federated login. Matches the OIDC handler's posture. @Summary Start SAML login @Description Redirects the browser to the configured SAML identity provider. @Tags auth @Produce json @Success 200 {string} string @Failure 302 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/auth/saml/login [get]
func (*HandlersApi) SAMLMetadataHandler ¶ added in v0.5.3
func (h *HandlersApi) SAMLMetadataHandler(w http.ResponseWriter, r *http.Request)
SAMLMetadataHandler — GET /api/v1/auth/saml/metadata.
Serves the SP metadata XML for IdP-side registration. The IdP administrator points their config at this URL (or downloads the XML and uploads it) so the IdP knows our EntityID + ACS URL + supported NameID formats.
Public by design — SP metadata is meant to be machine-readable by anyone who wants to federate with us. The information it carries is the same information the IdP would learn via the AuthnRequest URL during the first login attempt.
Rate-limited at the route layer like the other unauth endpoints. @Summary Get SAML metadata @Description Returns service provider metadata for SAML identity provider registration. @Tags auth @Produce application/xml @Success 200 {string} string @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Router /api/v1/auth/saml/metadata [get]
func (*HandlersApi) SavedQueriesListHandler ¶ added in v0.5.2
func (h *HandlersApi) SavedQueriesListHandler(w http.ResponseWriter, r *http.Request)
SavedQueriesListHandler - GET /api/v1/saved-queries/{env}
Paginated, sorted, searchable list of saved queries for an environment. Query params: page, page_size, q (free-text), sort (column key), dir (asc|desc). @Summary List saved queries @Description Returns paginated saved queries for an environment. @Tags saved-queries @Produce json @Param env path string true "Environment name or UUID" @Param page query int false "Page number" @Param page_size query int false "Page size" @Param q query string false "Search query" @Success 200 {object} types.SavedQueriesPagedResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/saved-queries/{env} [get]
func (*HandlersApi) SavedQueryCreateHandler ¶ added in v0.5.2
func (h *HandlersApi) SavedQueryCreateHandler(w http.ResponseWriter, r *http.Request)
SavedQueryCreateHandler - POST /api/v1/saved-queries/{env}
Body: { "name": string, "query": string }. Returns 201 with the created view, 409 if a saved query with that name already exists in the environment. @Summary Create saved query @Description Creates a saved query in an environment. @Tags saved-queries @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.SavedQueryCreateRequest true "Request body" @Success 200 {object} types.SavedQueryView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/saved-queries/{env} [post]
func (*HandlersApi) SavedQueryDeleteHandler ¶ added in v0.5.2
func (h *HandlersApi) SavedQueryDeleteHandler(w http.ResponseWriter, r *http.Request)
SavedQueryDeleteHandler - DELETE /api/v1/saved-queries/{env}/{name} @Summary Delete saved query @Description Deletes a saved query in an environment. @Tags saved-queries @Produce json @Param env path string true "Environment name or UUID" @Param name path string true "Saved query name" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/saved-queries/{env}/{name} [delete]
func (*HandlersApi) SavedQueryUpdateHandler ¶ added in v0.5.2
func (h *HandlersApi) SavedQueryUpdateHandler(w http.ResponseWriter, r *http.Request)
SavedQueryUpdateHandler - PATCH /api/v1/saved-queries/{env}/{name}
Body: { "query": string }. Updates the SQL body only; the original creator is preserved. Returns the updated view. @Summary Update saved query @Description Updates a saved query in an environment. @Tags saved-queries @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param name path string true "Saved query name" @Param request body types.SavedQueryUpdateRequest true "Request body" @Success 200 {object} types.SavedQueryView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/saved-queries/{env}/{name} [patch]
func (*HandlersApi) SetUserPermissionsAllHandler ¶ added in v0.5.3
func (h *HandlersApi) SetUserPermissionsAllHandler(w http.ResponseWriter, r *http.Request)
SetUserPermissionsAllHandler - POST /api/v1/users/{username}/permissions/all
Bulk variant of SetUserPermissionsHandler: applies the same EnvAccess shape to every environment that exists at request time. Body: { access: { user, query, carve, admin } }. Returns { updated, total, access } on success.
Same authn/authz posture as the per-env handler: requires super-admin (AdminLevel, NoEnvironment). Same lockout guards:
- Super-admins cannot self-demote via this endpoint.
- The last super-admin cannot be demoted under any path.
"All current envs" semantics: enumeration happens server-side at request time. Envs created LATER do not inherit; the operator re-applies as needed. See SetPermissionsAllRequest godoc. @Summary Set all user permissions @Description Sets permissions across all environments for a user. @Tags users @Accept json @Produce json @Param username path string true "Username" @Param request body types.SetPermissionsAllRequest true "Request body" @Success 200 {object} types.SetPermissionsAllResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username}/permissions/all [post]
func (*HandlersApi) SetUserPermissionsHandler ¶ added in v0.5.2
func (h *HandlersApi) SetUserPermissionsHandler(w http.ResponseWriter, r *http.Request)
SetUserPermissionsHandler - POST /api/v1/users/{username}/permissions
Body: { env_uuid, access: { user, query, carve, admin } }. Replaces the target user's per-env access rows. Returns 200 with the new EnvAccess. Requires super-admin (AdminLevel, NoEnvironment) — env-scoped admins can not grant permissions for their environment from this endpoint. @Summary Set user permissions @Description Sets per-environment permissions for a user. @Tags users @Accept json @Produce json @Param username path string true "Username" @Param request body types.SetPermissionsRequest true "Request body" @Success 200 {object} types.EnvAccessView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username}/permissions [post]
func (*HandlersApi) SettingPatchHandler ¶ added in v0.5.2
func (h *HandlersApi) SettingPatchHandler(w http.ResponseWriter, r *http.Request)
SettingPatchHandler — PATCH /api/v1/settings/{service}/{name}
Body shape (one of String, Boolean, Integer):
{ "string": "value" }
{ "boolean": true }
{ "integer": 42 }
The handler reads the existing setting first to determine its type, then applies the matching typed setter. Mismatched payloads return 400. The setting must already exist (creation is the legacy admin's job); a missing setting → 404. Audit-log on success only. @Summary Update setting @Description Updates a mutable setting value. @Tags settings @Accept json @Produce json @Param service path string true "Service name" @Param name path string true "Setting name" @Param request body types.SettingPatchRequest true "Request body" @Success 200 {object} settings.SettingValue @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/settings/{service}/{name} [patch]
func (*HandlersApi) SettingsHandler ¶
func (h *HandlersApi) SettingsHandler(w http.ResponseWriter, r *http.Request)
SettingsHandler - GET Handler for all settings including JSON @Summary List settings @Description Returns settings for all services. @Tags settings @Produce json @Success 200 {array} settings.SettingValue @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/settings [get]
func (*HandlersApi) SettingsServiceEnvHandler ¶
func (h *HandlersApi) SettingsServiceEnvHandler(w http.ResponseWriter, r *http.Request)
SettingsServiceEnvHandler - GET Handler for service and environment specific settings excluding JSON @Summary List service environment settings @Description Returns settings for a service and environment. @Tags settings @Produce json @Param service path string true "Service name" @Param env path string true "Environment name or UUID" @Success 200 {array} settings.SettingValue @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/settings/{service}/{env} [get]
func (*HandlersApi) SettingsServiceEnvJSONHandler ¶
func (h *HandlersApi) SettingsServiceEnvJSONHandler(w http.ResponseWriter, r *http.Request)
GET Handler for service and environment specific settings including JSON @Summary List service environment JSON settings @Description Returns JSON settings for a service and environment. @Tags settings @Produce json @Param service path string true "Service name" @Param env path string true "Environment name or UUID" @Success 200 {array} settings.SettingValue @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/settings/{service}/json/{env} [get]
func (*HandlersApi) SettingsServiceHandler ¶
func (h *HandlersApi) SettingsServiceHandler(w http.ResponseWriter, r *http.Request)
SettingsServiceHandler - GET Handler for service specific settings excluding JSON @Summary List service settings @Description Returns settings for a service. @Tags settings @Produce json @Param service path string true "Service name" @Success 200 {array} settings.SettingValue @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/settings/{service} [get]
func (*HandlersApi) SettingsServiceJSONHandler ¶
func (h *HandlersApi) SettingsServiceJSONHandler(w http.ResponseWriter, r *http.Request)
SettingsServiceJSONHandler - GET Handler for service specific settings including JSON @Summary List service JSON settings @Description Returns JSON settings for a service. @Tags settings @Produce json @Param service path string true "Service name" @Success 200 {array} settings.SettingValue @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/settings/{service}/json [get]
func (*HandlersApi) StatsHandler ¶ added in v0.5.2
func (h *HandlersApi) StatsHandler(w http.ResponseWriter, r *http.Request)
StatsHandler returns cross-env totals + per-env counts, filtered to the envs the calling user has UserLevel access to. Used by the SPA dashboard.
No query params. The response is small (one entry per accessible env) and cacheable for 30s on the client (Cache-Control: private, max-age=30).
NOTE on query/carve counting:
- GetActive(envID) returns ALL active rows regardless of type (union).
- To avoid double-counting we call GetQueries("active", envID) for standard queries and GetCarves("active", envID) for carves separately.
- Unit test for this handler is deferred: the underlying pkg/queries functions are exercised by existing tests in pkg/queries; a full integration test would require DB fixture setup that is out of scope for Track 2.
@Summary Get dashboard stats @Description Returns cross-environment dashboard statistics. @Tags stats @Produce json @Success 200 {object} StatsResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/stats [get]
func (*HandlersApi) TagEnvHandler ¶
func (h *HandlersApi) TagEnvHandler(w http.ResponseWriter, r *http.Request)
TagEnvHandler - GET Handler to return one tag for one environment as JSON. Permission is scoped to env.UUID admin so non-super operators with admin rights on this specific environment can view its tags. @Summary Get environment tag @Description Returns one tag by name for an environment. @Tags tags @Produce json @Param env path string true "Environment name or UUID" @Param name path string true "Tag name" @Success 200 {object} tags.AdminTag @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/tags/{env}/{name} [get]
func (*HandlersApi) TagNodeHandler ¶ added in v0.4.6
func (h *HandlersApi) TagNodeHandler(w http.ResponseWriter, r *http.Request)
TagNodeHandler - POST Handler to tag a node @Summary Tag node @Description Adds or updates a tag on a node. @Tags nodes @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param request body types.ApiNodeTagRequest true "Request body" @Success 200 {object} types.ApiGenericResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/nodes/{env}/tag [post]
func (*HandlersApi) TagsActionHandler ¶
func (h *HandlersApi) TagsActionHandler(w http.ResponseWriter, r *http.Request)
TagsActionHandler - POST Handler to create / update / delete tags. The action arrives as a URL path segment (legacy contract retained because Track 6 doesn't introduce new tag routes); body validation surfaces 400 on parse error and 409 on duplicate-name conflicts. @Summary Execute tag action @Description Creates, updates, deletes, or applies tags in an environment. @Tags tags @Accept json @Produce json @Param env path string true "Environment name or UUID" @Param action path string true "Tag action" @Param request body types.ApiTagsRequest true "Request body" @Success 200 {object} types.ApiDataResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/tags/{env}/{action} [post]
func (*HandlersApi) TagsEnvHandler ¶
func (h *HandlersApi) TagsEnvHandler(w http.ResponseWriter, r *http.Request)
TagsEnvHandler - GET Handler to return tags for one environment as JSON. Permission is scoped to env.UUID admin (see TagEnvHandler note). @Summary List environment tags @Description Returns tags for an environment. @Tags tags @Produce json @Param env path string true "Environment name or UUID" @Success 200 {array} tags.AdminTag @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/tags/{env} [get]
func (*HandlersApi) UserActionHandler ¶ added in v0.4.6
func (h *HandlersApi) UserActionHandler(w http.ResponseWriter, r *http.Request)
UserActionHandler - POST Handler to take actions on a user by username and environment @Summary Execute user action @Description Creates, updates, deletes, or changes flags on a user. @Tags users @Accept json @Produce json @Param username path string true "Username" @Param action path string true "User action" @Param request body types.ApiUserRequest true "Request body" @Success 200 {object} types.ApiDataResponse @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username}/{action} [post]
func (*HandlersApi) UserHandler ¶
func (h *HandlersApi) UserHandler(w http.ResponseWriter, r *http.Request)
UserHandler - GET Handler for environment users @Summary Get user @Description Returns an API user by username. @Tags users @Produce json @Param username path string true "Username" @Success 200 {object} types.AdminUserView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users/{username} [get]
func (*HandlersApi) UsersHandler ¶
func (h *HandlersApi) UsersHandler(w http.ResponseWriter, r *http.Request)
UsersHandler - GET Handler for multiple JSON nodes @Summary List users @Description Returns API users. @Tags users @Produce json @Success 200 {array} types.AdminUserView @Failure 400 {object} types.ApiErrorResponse "Bad request" @Failure 401 {object} types.ApiErrorResponse "Unauthorized" @Failure 403 {object} types.ApiErrorResponse "Forbidden" @Failure 404 {object} types.ApiErrorResponse "Not found" @Failure 409 {object} types.ApiErrorResponse "Conflict" @Failure 429 {object} types.ApiErrorResponse "Too many requests" @Failure 500 {object} types.ApiErrorResponse "Internal server error" @Failure 503 {object} types.ApiErrorResponse "Service unavailable" @Security ApiKeyAuth @Router /api/v1/users [get]
type HandlersOption ¶
type HandlersOption func(*HandlersApi)
func WithAuditLog ¶ added in v0.4.8
func WithAuditLog(auditLog *auditlog.AuditLogManager) HandlersOption
func WithCache ¶
func WithCache(rds *cache.RedisManager) HandlersOption
func WithCarves ¶
func WithCarves(carves *carves.Carves) HandlersOption
func WithDB ¶
func WithDB(db *gorm.DB) HandlersOption
func WithDebugHTTP ¶ added in v0.4.5
func WithDebugHTTP(cfg *config.YAMLConfigurationDebug) HandlersOption
func WithEnvs ¶
func WithEnvs(envs *environments.EnvManager) HandlersOption
func WithJWTSecret ¶ added in v0.5.3
func WithJWTSecret(secret []byte) HandlersOption
WithJWTSecret attaches the HMAC key for auth state-cookie signing. MUST be the same secret pkg/users uses for user JWTs; the audience claim ("osctrl-auth-state" vs "osctrl-api") segregates the two purposes so sharing the underlying secret is safe.
func WithName ¶
func WithName(name string) HandlersOption
func WithNodes ¶
func WithNodes(nodes *nodes.NodeManager) HandlersOption
func WithOIDC ¶ added in v0.5.3
func WithOIDC(enabled bool) HandlersOption
WithOIDC toggles the global OIDC routes and the /api/v1/auth/methods response. Pass true at startup when the operator has configured an OIDC provider. When false, /api/v1/auth/methods returns password-only so the SPA renders the password form alone.
func WithOsqueryTables ¶ added in v0.5.2
func WithOsqueryTables(tables []types.OsqueryTable) HandlersOption
func WithOsqueryValues ¶ added in v0.5.2
func WithOsqueryValues(values config.YAMLConfigurationOsquery) HandlersOption
func WithQueries ¶
func WithQueries(queries *queries.Queries) HandlersOption
func WithSAML ¶ added in v0.5.3
func WithSAML(enabled bool) HandlersOption
WithSAML toggles the global SAML routes and the /api/v1/auth/methods response. SAML analogue of WithOIDC; the two can be enabled simultaneously.
func WithSettings ¶
func WithSettings(settings *settings.Settings) HandlersOption
func WithTags ¶
func WithTags(tags *tags.TagManager) HandlersOption
func WithUsers ¶
func WithUsers(users *users.UserManager) HandlersOption
func WithVersion ¶
func WithVersion(version string) HandlersOption
type LogoutResponse ¶ added in v0.5.3
type LogoutResponse struct {
AuthSource string `json:"auth_source,omitempty"`
IdPLogoutURL string `json:"idp_logout_url,omitempty"`
IdPClientID string `json:"idp_client_id,omitempty"`
IdPIDTokenHint string `json:"idp_id_token_hint,omitempty"`
}
LogoutResponse is the JSON payload returned by POST /api/v1/logout.
AuthSource carries which provider issued the active session ("oidc" / "saml" / ""). The SPA uses it to decide which IdP-logout flow to run — sending a SAML user to the OIDC end-session URL with a stale id_token_hint just produces a confusing Keycloak error page.
IdPLogoutURL is non-empty when AuthSource=="oidc" AND the OIDC provider advertised an end_session_endpoint in its discovery document — the SPA navigates the browser there to terminate the IdP session. Empty for SAML users (SLO deferred to v2 per spec) and for password users.
IdPClientID is the OIDC client_id registered with the IdP. Some IdPs (Keycloak) accept it as an alternative to id_token_hint when the operator chains post_logout_redirect_uri. The SPA appends it as ?client_id=...
IdPIDTokenHint is the raw id_token from the user's most recent federated login. Okta REQUIRES this on /v1/logout when chaining a post-logout redirect; without it Okta returns "Missing parameter: id_token_hint". Non-empty when the osctrl_id_token cookie was present and valid; empty for password-only operators or after a fresh login from a cookie-stripped browser. The SPA appends it as ?id_token_hint=... — the IdP verifies the token's signature before terminating the session, so this is safe to expose to the browser (we set it back into the URL the browser navigates to).
type NodeActivityBucket ¶ added in v0.5.2
type NodeActivityBucket struct {
BucketStart time.Time `json:"bucket_start"`
Status int `json:"status"`
Result int `json:"result"`
Query int `json:"query"`
Carve int `json:"carve"`
}
NodeActivityBucket is one cell of the per-node 24h activity heatmap. Categories pivot from the env-scoped variant — node-scoped activity is about what THIS device has been doing, not what operators have done to the env. So:
- status ← osquery_status_data row count (status logs received from this node)
- result ← osquery_result_data row count (query results returned by this node)
- query ← node_queries row count (distributed queries scheduled against this node)
- carve ← carved_files row count (carves this node has produced)
All four are joinable by node uuid (or numeric node id for node_queries).
type NodeLogsResponse ¶ added in v0.5.2
type NodeLogsResponse struct {
Items []map[string]any `json:"items"`
Type string `json:"type"`
UUID string `json:"uuid"`
Env string `json:"env"`
Since string `json:"since,omitempty"`
Limit int `json:"limit"`
}
NodeLogsResponse is the SPA-canonical response for GET /api/v1/logs/{type}/{env}/{uuid}.
type StatsResponse ¶ added in v0.5.2
type StatsResponse struct {
// Cross-env totals (the user's allowed envs only).
TotalNodes int64 `json:"total_nodes"`
ActiveNodes int64 `json:"active_nodes"`
InactiveNodes int64 `json:"inactive_nodes"`
// TotalActiveQueries counts standard query-type active queries (excludes carves).
TotalActiveQueries int `json:"total_active_queries"`
// TotalActiveCarves counts active carve-type queries.
TotalActiveCarves int `json:"total_active_carves"`
// Cross-env platform breakdown — sum of every accessible env's PlatformCounts.
PlatformCounts nodes.PlatformCounts `json:"platform_counts"`
// Per-env breakdown, in stable alphabetical order by name.
Environments []EnvStats `json:"environments"`
}
StatsResponse is the canonical /api/v1/stats shape consumed by the dashboard.
Source Files
¶
- audit.go
- auth_jwt.go
- auth_logout.go
- auth_methods.go
- auth_oidc.go
- auth_resolve.go
- auth_saml.go
- carves.go
- environments.go
- environments_crud.go
- get.go
- handlers.go
- login.go
- login_envs.go
- logs.go
- nodes.go
- platforms.go
- queries.go
- samples.go
- saved_queries.go
- settings.go
- settings_patch.go
- stats.go
- tags.go
- users.go
- users_profile.go
- utils.go