Skip to content

Commit ec334d3

Browse files
committed
Lookup node by identifier in osctrl-api
1 parent 1cf69c5 commit ec334d3

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed

cmd/api/handlers/nodes.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,37 @@ func (h *HandlersApi) DeleteNodeHandler(w http.ResponseWriter, r *http.Request)
217217
log.Debug().Msgf("Returned node %s", n.UUID)
218218
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: "node deleted"})
219219
}
220+
221+
// LookupNodeHandler - POST Handler to lookup a node by identifier
222+
func (h *HandlersApi) LookupNodeHandler(w http.ResponseWriter, r *http.Request) {
223+
// Debug HTTP if enabled
224+
if h.DebugHTTPConfig.Enabled {
225+
utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
226+
}
227+
// Get context data and check access
228+
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
229+
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, users.NoEnvironment) {
230+
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
231+
return
232+
}
233+
var l types.ApiLookupRequest
234+
// Parse request JSON body
235+
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
236+
apiErrorResponse(w, "error parsing POST body", http.StatusInternalServerError, err)
237+
return
238+
}
239+
if l.Identifier == "" {
240+
apiErrorResponse(w, "error with identifier", http.StatusBadRequest, nil)
241+
return
242+
}
243+
n, err := h.Nodes.GetByIdentifier(l.Identifier)
244+
if err != nil {
245+
if err.Error() == "record not found" {
246+
apiErrorResponse(w, "node not found", http.StatusNotFound, err)
247+
} else {
248+
apiErrorResponse(w, "error getting node", http.StatusInternalServerError, err)
249+
}
250+
}
251+
// Serialize and serve JSON
252+
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, _nodeToApiLookupResponse(n))
253+
}

cmd/api/handlers/utils.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55

66
"github.com/jmpsec/osctrl/pkg/logging"
7+
"github.com/jmpsec/osctrl/pkg/nodes"
78
"github.com/jmpsec/osctrl/pkg/types"
89
"github.com/jmpsec/osctrl/pkg/utils"
910
"github.com/rs/zerolog/log"
@@ -53,3 +54,20 @@ func checkValidPlatform(platforms []string, platform string) bool {
5354
}
5455
return false
5556
}
57+
58+
// Helper to convert a node into a ApiLookupResponse
59+
func _nodeToApiLookupResponse(node nodes.OsqueryNode) types.ApiLookupResponse {
60+
return types.ApiLookupResponse{
61+
UUID: node.UUID,
62+
Platform: node.Platform,
63+
PlatformVersion: node.PlatformVersion,
64+
OsqueryVersion: node.OsqueryVersion,
65+
Hostname: node.Hostname,
66+
Localname: node.Localname,
67+
IPAddress: node.IPAddress,
68+
Username: node.Username,
69+
Environment: node.Environment,
70+
HardwareSerial: node.HardwareSerial,
71+
LastSeen: node.LastSeen.String(),
72+
}
73+
}

cmd/api/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ func osctrlAPIService() {
239239
muxAPI.Handle(
240240
"POST "+_apiPath(apiNodesPath)+"/{env}/delete",
241241
handlerAuthCheck(http.HandlerFunc(handlersApi.DeleteNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))
242+
muxAPI.Handle(
243+
"POST "+_apiPath(apiNodesPath)+"/lookup",
244+
handlerAuthCheck(http.HandlerFunc(handlersApi.LookupNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))
242245
// API: queries by environment
243246
muxAPI.Handle(
244247
"GET "+_apiPath(apiQueriesPath)+"/{env}",

osctrl-api.yaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,52 @@ paths:
304304
security:
305305
- Authorization:
306306
- admin
307+
/nodes/lookup:
308+
post:
309+
tags:
310+
- nodes
311+
summary: Lookup node by identifier
312+
description: Looks up an enrolled node by identifier (UUID, hostname or localname)
313+
operationId: LookupNodeHandler
314+
requestBody:
315+
content:
316+
application/json:
317+
schema:
318+
$ref: "#/components/schemas/ApiLookupRequest"
319+
responses:
320+
200:
321+
description: successful operation
322+
content:
323+
application/json:
324+
schema:
325+
$ref: "#/components/schemas/ApiLookupResponse"
326+
400:
327+
description: bad request
328+
content:
329+
application/json:
330+
schema:
331+
$ref: "#/components/schemas/ApiErrorResponse"
332+
403:
333+
description: no access
334+
content:
335+
application/json:
336+
schema:
337+
$ref: "#/components/schemas/ApiErrorResponse"
338+
404:
339+
description: no nodes
340+
content:
341+
application/json:
342+
schema:
343+
$ref: "#/components/schemas/ApiErrorResponse"
344+
500:
345+
description: error deleting node
346+
content:
347+
application/json:
348+
schema:
349+
$ref: "#/components/schemas/ApiErrorResponse"
350+
security:
351+
- Authorization:
352+
- admin
307353
/queries/{env}:
308354
get:
309355
tags:
@@ -2412,6 +2458,41 @@ components:
24122458
TagType:
24132459
type: integer
24142460
format: uint32
2461+
ApiLookupRequest:
2462+
type: object
2463+
properties:
2464+
Identifier:
2465+
type: string
2466+
ApiLookupResponse:
2467+
type: object
2468+
properties:
2469+
ID:
2470+
type: integer
2471+
format: int32
2472+
UUID:
2473+
type: string
2474+
Hostname:
2475+
type: string
2476+
Localname:
2477+
type: string
2478+
IPAddress:
2479+
type: string
2480+
Username:
2481+
type: string
2482+
HardwareSerial:
2483+
type: string
2484+
Platform:
2485+
type: string
2486+
PlatformVersion:
2487+
type: string
2488+
OsqueryVersion:
2489+
type: string
2490+
Environment:
2491+
type: string
2492+
EnvironmentUUID:
2493+
type: string
2494+
LastSeen:
2495+
type: string
24152496
MapEnvByID:
24162497
type: object
24172498
properties:

pkg/types/types.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,25 @@ type ApiTagsRequest struct {
109109
EnvUUID string `json:"env_uuid"`
110110
TagType uint `json:"tagtype"`
111111
}
112+
113+
// ApiLookupRequest to receive lookup requests
114+
type ApiLookupRequest struct {
115+
Identifier string `json:"identifier"`
116+
}
117+
118+
// ApiLookupResponse to be returned to API lookup requests
119+
type ApiLookupResponse struct {
120+
ID uint `json:"id"`
121+
UUID string `json:"uuid"`
122+
Hostname string `json:"hostname"`
123+
Localname string `json:"localname"`
124+
IPAddress string `json:"ip_address"`
125+
Username string `json:"username"`
126+
HardwareSerial string `json:"hardware_serial"`
127+
Platform string `json:"platform"`
128+
PlatformVersion string `json:"platform_version"`
129+
OsqueryVersion string `json:"osquery_version"`
130+
Environment string `json:"environment"`
131+
EnvironmentUUID string `json:"environment_uuid"`
132+
LastSeen string `json:"last_seen"`
133+
}

0 commit comments

Comments
 (0)