Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions cmd/api/handlers/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,37 @@ func (h *HandlersApi) DeleteNodeHandler(w http.ResponseWriter, r *http.Request)
log.Debug().Msgf("Returned node %s", n.UUID)
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: "node deleted"})
}

// LookupNodeHandler - POST Handler to lookup a node by identifier
func (h *HandlersApi) LookupNodeHandler(w http.ResponseWriter, r *http.Request) {
// Debug HTTP if enabled
if h.DebugHTTPConfig.Enabled {
utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
}
// Get context data and check access
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, users.NoEnvironment) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
return
}
var l types.ApiLookupRequest
// Parse request JSON body
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
apiErrorResponse(w, "error parsing POST body", http.StatusInternalServerError, err)
return
}
if l.Identifier == "" {
apiErrorResponse(w, "error with identifier", http.StatusBadRequest, nil)
return
}
n, err := h.Nodes.GetByIdentifier(l.Identifier)
if err != nil {
if err.Error() == "record not found" {
apiErrorResponse(w, "node not found", http.StatusNotFound, err)
} else {
apiErrorResponse(w, "error getting node", http.StatusInternalServerError, err)
}
}
// Serialize and serve JSON
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, _nodeToApiLookupResponse(n))
}
18 changes: 18 additions & 0 deletions cmd/api/handlers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"github.com/jmpsec/osctrl/pkg/logging"
"github.com/jmpsec/osctrl/pkg/nodes"
"github.com/jmpsec/osctrl/pkg/types"
"github.com/jmpsec/osctrl/pkg/utils"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -53,3 +54,20 @@ func checkValidPlatform(platforms []string, platform string) bool {
}
return false
}

// Helper to convert a node into a ApiLookupResponse
func _nodeToApiLookupResponse(node nodes.OsqueryNode) types.ApiLookupResponse {
return types.ApiLookupResponse{
UUID: node.UUID,
Platform: node.Platform,
PlatformVersion: node.PlatformVersion,
OsqueryVersion: node.OsqueryVersion,
Hostname: node.Hostname,
Localname: node.Localname,
IPAddress: node.IPAddress,
Username: node.Username,
Environment: node.Environment,
HardwareSerial: node.HardwareSerial,
LastSeen: node.LastSeen.String(),
}
}
3 changes: 3 additions & 0 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ func osctrlAPIService() {
muxAPI.Handle(
"POST "+_apiPath(apiNodesPath)+"/{env}/delete",
handlerAuthCheck(http.HandlerFunc(handlersApi.DeleteNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))
muxAPI.Handle(
"POST "+_apiPath(apiNodesPath)+"/lookup",
handlerAuthCheck(http.HandlerFunc(handlersApi.LookupNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))
// API: queries by environment
muxAPI.Handle(
"GET "+_apiPath(apiQueriesPath)+"/{env}",
Expand Down
81 changes: 81 additions & 0 deletions osctrl-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,52 @@ paths:
security:
- Authorization:
- admin
/nodes/lookup:
post:
tags:
- nodes
summary: Lookup node by identifier
description: Looks up an enrolled node by identifier (UUID, hostname or localname)
operationId: LookupNodeHandler
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ApiLookupRequest"
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: "#/components/schemas/ApiLookupResponse"
400:
description: bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
403:
description: no access
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
404:
description: no nodes
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
500:
description: error deleting node
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
security:
- Authorization:
- admin
/queries/{env}:
get:
tags:
Expand Down Expand Up @@ -2412,6 +2458,41 @@ components:
TagType:
type: integer
format: uint32
ApiLookupRequest:
type: object
properties:
Identifier:
type: string
ApiLookupResponse:
type: object
properties:
ID:
type: integer
format: int32
UUID:
type: string
Hostname:
type: string
Localname:
type: string
IPAddress:
type: string
Username:
type: string
HardwareSerial:
type: string
Platform:
type: string
PlatformVersion:
type: string
OsqueryVersion:
type: string
Environment:
type: string
EnvironmentUUID:
type: string
LastSeen:
type: string
MapEnvByID:
type: object
properties:
Expand Down
22 changes: 22 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,25 @@ type ApiTagsRequest struct {
EnvUUID string `json:"env_uuid"`
TagType uint `json:"tagtype"`
}

// ApiLookupRequest to receive lookup requests
type ApiLookupRequest struct {
Identifier string `json:"identifier"`
}

// ApiLookupResponse to be returned to API lookup requests
type ApiLookupResponse struct {
ID uint `json:"id"`
UUID string `json:"uuid"`
Hostname string `json:"hostname"`
Localname string `json:"localname"`
IPAddress string `json:"ip_address"`
Username string `json:"username"`
HardwareSerial string `json:"hardware_serial"`
Platform string `json:"platform"`
PlatformVersion string `json:"platform_version"`
OsqueryVersion string `json:"osquery_version"`
Environment string `json:"environment"`
EnvironmentUUID string `json:"environment_uuid"`
LastSeen string `json:"last_seen"`
}
Loading