Skip to content

Commit 1b6ee75

Browse files
Check agent options for correct key path
1 parent efe3315 commit 1b6ee75

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

ee/server/service/teams.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,19 @@ func (svc *Service) ModifyTeamAgentOptions(ctx context.Context, teamID uint, tea
414414

415415
if teamOptions != nil {
416416
if err := fleet.ValidateJSONAgentOptions(ctx, svc.ds, teamOptions, true); err != nil {
417+
if field := fleet.GetJSONUnknownField(err); field != nil {
418+
correctKeyPath, keyErr := fleet.FindAgentOptionsKeyPath(*field)
419+
if keyErr != nil {
420+
level.Error(svc.logger).Log("err", err, "msg", "failed to find missing agent option")
421+
}
422+
var keyPathJoined string
423+
if len(correctKeyPath) > 1 {
424+
keyPathJoined = fmt.Sprintf("%q", strings.Join(correctKeyPath[:len(correctKeyPath)-1], "."))
425+
} else {
426+
keyPathJoined = "top level"
427+
}
428+
err = fmt.Errorf("%q should be part of the %s object", *field, keyPathJoined)
429+
}
417430
err = fleet.NewUserMessageError(err, http.StatusBadRequest)
418431
if applyOptions.Force && !applyOptions.DryRun {
419432
level.Info(svc.logger).Log("err", err, "msg", "force-apply team agent options with validation errors")

server/fleet/agent_options.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,85 @@ func validateJSONAgentOptionsSet(rawJSON json.RawMessage) error {
325325
}
326326
return nil
327327
}
328+
329+
func FindAgentOptionsKeyPath(key string) ([]string, error) {
330+
if key == "script_execution_timeout" {
331+
return []string{"script_execution_timeout"}, nil
332+
}
333+
334+
configPath, err := locateStructJSONKeyPath(key, "config", osqueryAgentOptions{})
335+
if err != nil {
336+
return nil, fmt.Errorf("locating key path in agent options: %w", err)
337+
}
338+
if configPath != nil {
339+
return configPath, nil
340+
}
341+
342+
if key == "overrides" {
343+
return []string{"overrides"}, nil
344+
}
345+
if key == "platforms" {
346+
return []string{"overrides", "platforms"}, nil
347+
}
348+
349+
commandLinePath, err := locateStructJSONKeyPath(key, "command_line_flags", osqueryCommandLineFlags{})
350+
if err != nil {
351+
return nil, fmt.Errorf("locating key path in agent command line options: %w", err)
352+
}
353+
if commandLinePath != nil {
354+
return commandLinePath, nil
355+
}
356+
357+
extensionsPath, err := locateStructJSONKeyPath(key, "extensions", ExtensionInfo{})
358+
if err != nil {
359+
return nil, fmt.Errorf("locating key path in agent extensions options: %w", err)
360+
}
361+
if extensionsPath != nil {
362+
return extensionsPath, nil
363+
}
364+
365+
channelsPath, err := locateStructJSONKeyPath(key, "update_channels", OrbitUpdateChannels{})
366+
if err != nil {
367+
return nil, fmt.Errorf("locating key path in agent update channels: %w", err)
368+
}
369+
if channelsPath != nil {
370+
return channelsPath, nil
371+
}
372+
373+
return nil, nil
374+
}
375+
376+
// Only searches two layers deep
377+
func locateStructJSONKeyPath(key, startKey string, target any) ([]string, error) {
378+
optionsBytes, err := json.Marshal(target)
379+
if err != nil {
380+
return nil, fmt.Errorf("unable to marshall target: %w", err)
381+
}
382+
383+
var opts map[string]any
384+
385+
if err := json.Unmarshal(optionsBytes, &opts); err != nil {
386+
return nil, fmt.Errorf("unable to unmarshall target: %w", err)
387+
}
388+
389+
var path [3]string
390+
path[0] = startKey
391+
for k, v := range opts {
392+
path[1] = k
393+
if k == key {
394+
return path[:2], nil
395+
}
396+
397+
switch v.(type) {
398+
case map[string]any:
399+
for k2 := range v.(map[string]any) {
400+
path[2] = k2
401+
if key == k2 {
402+
return path[:3], nil
403+
}
404+
}
405+
}
406+
}
407+
408+
return nil, nil
409+
}

server/fleet/errors.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,15 @@ func IsJSONUnknownFieldError(err error) bool {
468468
return rxJSONUnknownField.MatchString(err.Error())
469469
}
470470

471+
func GetJSONUnknownField(err error) *string {
472+
errCause := Cause(err)
473+
if IsJSONUnknownFieldError(errCause) {
474+
substr := rxJSONUnknownField.FindStringSubmatch(errCause.Error())
475+
return &substr[1]
476+
}
477+
return nil
478+
}
479+
471480
// UserMessage implements the user-friendly translation of the error if its
472481
// root cause is one of the supported types, otherwise it returns the error
473482
// message.

0 commit comments

Comments
 (0)