internal/pkg/api/handleAudit.go (96 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package api import ( "context" "encoding/json" "fmt" "net/http" "time" "github.com/elastic/fleet-server/v7/internal/pkg/bulk" "github.com/elastic/fleet-server/v7/internal/pkg/cache" "github.com/elastic/fleet-server/v7/internal/pkg/config" "github.com/elastic/fleet-server/v7/internal/pkg/dl" "github.com/elastic/fleet-server/v7/internal/pkg/model" "github.com/miolini/datacounter" "github.com/rs/zerolog" "go.elastic.co/apm/v2" ) var ErrAuditUnenrollReason = fmt.Errorf("agent document contains audit_unenroll_reason: orphaned") type AuditT struct { cfg *config.Server bulk bulk.Bulk cache cache.Cache } func NewAuditT(cfg *config.Server, bulker bulk.Bulk, cache cache.Cache) *AuditT { return &AuditT{ cfg: cfg, bulk: bulker, cache: cache, } } func (audit *AuditT) handleUnenroll(zlog zerolog.Logger, w http.ResponseWriter, r *http.Request, id string) error { agent, err := authAgent(r, &id, audit.bulk, audit.cache) if err != nil { return err } zlog = zlog.With().Str(LogAccessAPIKeyID, agent.AccessAPIKeyID).Logger() ctx := zlog.WithContext(r.Context()) r = r.WithContext(ctx) return audit.unenroll(zlog, w, r, agent) } func (audit *AuditT) unenroll(zlog zerolog.Logger, w http.ResponseWriter, r *http.Request, agent *model.Agent) error { if agent.AuditUnenrolledReason == string(Orphaned) { return ErrAuditUnenrollReason } req, err := audit.validateUnenrollRequest(zlog, w, r) if err != nil { return err } if err := audit.markUnenroll(r.Context(), zlog, req, agent); err != nil { return err } span, _ := apm.StartSpan(r.Context(), "response", "write") defer span.End() w.WriteHeader(http.StatusOK) return nil } func (audit *AuditT) validateUnenrollRequest(zlog zerolog.Logger, w http.ResponseWriter, r *http.Request) (*AuditUnenrollRequest, error) { span, _ := apm.StartSpan(r.Context(), "validateRequest", "validate") defer span.End() body := r.Body if audit.cfg.Limits.AuditUnenrollLimit.MaxBody > 0 { body = http.MaxBytesReader(w, body, audit.cfg.Limits.AuditUnenrollLimit.MaxBody) } readCounter := datacounter.NewReaderCounter(body) var req AuditUnenrollRequest dec := json.NewDecoder(readCounter) if err := dec.Decode(&req); err != nil { return nil, &BadRequestErr{msg: "unable to decode audit/unenroll request", nextErr: err} } switch req.Reason { case Uninstall, Orphaned, KeyRevoked: default: return nil, &BadRequestErr{msg: "audit/unenroll request invalid reason"} } cntAuditUnenroll.bodyIn.Add(readCounter.Count()) zlog.Trace().Msg("Audit unenroll request") return &req, nil } func (audit *AuditT) markUnenroll(ctx context.Context, zlog zerolog.Logger, req *AuditUnenrollRequest, agent *model.Agent) error { span, ctx := apm.StartSpan(ctx, "auditUnenroll", "process") defer span.End() now := time.Now().UTC().Format(time.RFC3339) doc := bulk.UpdateFields{ dl.FieldUpdatedAt: now, dl.FieldAuditUnenrolledTime: req.Timestamp, dl.FieldAuditUnenrolledReason: req.Reason, } body, err := doc.Marshal() if err != nil { return fmt.Errorf("auditUnenroll marshal: %w", err) } if err := audit.bulk.Update(ctx, dl.FleetAgents, agent.Id, body, bulk.WithRefresh(), bulk.WithRetryOnConflict(3)); err != nil { return fmt.Errorf("auditUnenroll update: %w", err) } zlog.Info().Msg("audit unenroll successful") return nil }