mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
1603ee0ea8
#10880 I was not able to reproduce other 500s in `/api/_version_/fleet/hosts` other than the one fixed in the PR. - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - ~For Orbit and Fleet Desktop changes:~ - ~[ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux.~ - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
454 lines
13 KiB
Go
454 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
)
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Create Label
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type createLabelRequest struct {
|
|
fleet.LabelPayload
|
|
}
|
|
|
|
type createLabelResponse struct {
|
|
Label labelResponse `json:"label"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r createLabelResponse) error() error { return r.Err }
|
|
|
|
func createLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*createLabelRequest)
|
|
|
|
label, err := svc.NewLabel(ctx, req.LabelPayload)
|
|
if err != nil {
|
|
return createLabelResponse{Err: err}, nil
|
|
}
|
|
|
|
labelResp, err := labelResponseForLabel(ctx, svc, label)
|
|
if err != nil {
|
|
return createLabelResponse{Err: err}, nil
|
|
}
|
|
|
|
return createLabelResponse{Label: *labelResp}, nil
|
|
}
|
|
|
|
func (svc *Service) NewLabel(ctx context.Context, p fleet.LabelPayload) (*fleet.Label, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionWrite); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
label := &fleet.Label{}
|
|
|
|
if p.Name == nil {
|
|
return nil, fleet.NewInvalidArgumentError("name", "missing required argument")
|
|
}
|
|
label.Name = *p.Name
|
|
|
|
if p.Query == nil {
|
|
return nil, fleet.NewInvalidArgumentError("query", "missing required argument")
|
|
}
|
|
label.Query = *p.Query
|
|
|
|
if p.Platform != nil {
|
|
label.Platform = *p.Platform
|
|
}
|
|
|
|
if p.Description != nil {
|
|
label.Description = *p.Description
|
|
}
|
|
|
|
label, err := svc.ds.NewLabel(ctx, label)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return label, nil
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Modify Label
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type modifyLabelRequest struct {
|
|
ID uint `json:"-" url:"id"`
|
|
fleet.ModifyLabelPayload
|
|
}
|
|
|
|
type modifyLabelResponse struct {
|
|
Label labelResponse `json:"label"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r modifyLabelResponse) error() error { return r.Err }
|
|
|
|
func modifyLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*modifyLabelRequest)
|
|
label, err := svc.ModifyLabel(ctx, req.ID, req.ModifyLabelPayload)
|
|
if err != nil {
|
|
return modifyLabelResponse{Err: err}, nil
|
|
}
|
|
|
|
labelResp, err := labelResponseForLabel(ctx, svc, label)
|
|
if err != nil {
|
|
return modifyLabelResponse{Err: err}, nil
|
|
}
|
|
|
|
return modifyLabelResponse{Label: *labelResp}, err
|
|
}
|
|
|
|
func (svc *Service) ModifyLabel(ctx context.Context, id uint, payload fleet.ModifyLabelPayload) (*fleet.Label, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionWrite); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
label, err := svc.ds.Label(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if payload.Name != nil {
|
|
label.Name = *payload.Name
|
|
}
|
|
if payload.Description != nil {
|
|
label.Description = *payload.Description
|
|
}
|
|
return svc.ds.SaveLabel(ctx, label)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Get Label
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type getLabelRequest struct {
|
|
ID uint `url:"id"`
|
|
}
|
|
|
|
type labelResponse struct {
|
|
fleet.Label
|
|
DisplayText string `json:"display_text"`
|
|
Count int `json:"count"`
|
|
HostIDs []uint `json:"host_ids"`
|
|
}
|
|
|
|
type getLabelResponse struct {
|
|
Label labelResponse `json:"label"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r getLabelResponse) error() error { return r.Err }
|
|
|
|
func getLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*getLabelRequest)
|
|
label, err := svc.GetLabel(ctx, req.ID)
|
|
if err != nil {
|
|
return getLabelResponse{Err: err}, nil
|
|
}
|
|
resp, err := labelResponseForLabel(ctx, svc, label)
|
|
if err != nil {
|
|
return getLabelResponse{Err: err}, nil
|
|
}
|
|
return getLabelResponse{Label: *resp}, nil
|
|
}
|
|
|
|
func (svc *Service) GetLabel(ctx context.Context, id uint) (*fleet.Label, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return svc.ds.Label(ctx, id)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// List Labels
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type listLabelsRequest struct {
|
|
ListOptions fleet.ListOptions `url:"list_options"`
|
|
}
|
|
|
|
type listLabelsResponse struct {
|
|
Labels []labelResponse `json:"labels"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r listLabelsResponse) error() error { return r.Err }
|
|
|
|
func listLabelsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*listLabelsRequest)
|
|
|
|
labels, err := svc.ListLabels(ctx, req.ListOptions)
|
|
if err != nil {
|
|
return listLabelsResponse{Err: err}, nil
|
|
}
|
|
|
|
resp := listLabelsResponse{}
|
|
for _, label := range labels {
|
|
labelResp, err := labelResponseForLabel(ctx, svc, label)
|
|
if err != nil {
|
|
return listLabelsResponse{Err: err}, nil
|
|
}
|
|
resp.Labels = append(resp.Labels, *labelResp)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (svc *Service) ListLabels(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Label, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
vc, ok := viewer.FromContext(ctx)
|
|
if !ok {
|
|
return nil, fleet.ErrNoContext
|
|
}
|
|
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: true}
|
|
|
|
return svc.ds.ListLabels(ctx, filter, opt)
|
|
}
|
|
|
|
func labelResponseForLabel(ctx context.Context, svc fleet.Service, label *fleet.Label) (*labelResponse, error) {
|
|
return &labelResponse{
|
|
Label: *label,
|
|
DisplayText: label.Name,
|
|
Count: label.HostCount,
|
|
}, nil
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Labels Summary
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type getLabelsSummaryResponse struct {
|
|
Labels []*fleet.LabelSummary `json:"labels"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r getLabelsSummaryResponse) error() error { return r.Err }
|
|
|
|
func getLabelsSummaryEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
labels, err := svc.LabelsSummary(ctx)
|
|
if err != nil {
|
|
return getLabelsSummaryResponse{Err: err}, nil
|
|
}
|
|
return getLabelsSummaryResponse{Labels: labels}, nil
|
|
}
|
|
|
|
func (svc *Service) LabelsSummary(ctx context.Context) ([]*fleet.LabelSummary, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return svc.ds.LabelsSummary(ctx)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// List Hosts in Label
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type listHostsInLabelRequest struct {
|
|
ID uint `url:"id"`
|
|
ListOptions fleet.HostListOptions `url:"host_options"`
|
|
}
|
|
|
|
func listHostsInLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*listHostsInLabelRequest)
|
|
hosts, err := svc.ListHostsInLabel(ctx, req.ID, req.ListOptions)
|
|
if err != nil {
|
|
return listLabelsResponse{Err: err}, nil
|
|
}
|
|
|
|
var mdmSolution *fleet.MDMSolution
|
|
if req.ListOptions.MDMIDFilter != nil {
|
|
var err error
|
|
mdmSolution, err = svc.GetMDMSolution(ctx, *req.ListOptions.MDMIDFilter)
|
|
if err != nil && !fleet.IsNotFound(err) { // ignore not found, just return nil for the MDM solution in that case
|
|
return listHostsResponse{Err: err}, nil
|
|
}
|
|
}
|
|
|
|
hostResponses := make([]fleet.HostResponse, len(hosts))
|
|
for i, host := range hosts {
|
|
h := fleet.HostResponseForHost(ctx, svc, host)
|
|
hostResponses[i] = *h
|
|
}
|
|
return listHostsResponse{Hosts: hostResponses, MDMSolution: mdmSolution}, nil
|
|
}
|
|
|
|
func (svc *Service) ListHostsInLabel(ctx context.Context, lid uint, opt fleet.HostListOptions) ([]*fleet.Host, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
vc, ok := viewer.FromContext(ctx)
|
|
if !ok {
|
|
return nil, fleet.ErrNoContext
|
|
}
|
|
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: true}
|
|
|
|
return svc.ds.ListHostsInLabel(ctx, filter, lid, opt)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Delete Label
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type deleteLabelRequest struct {
|
|
Name string `url:"name"`
|
|
}
|
|
|
|
type deleteLabelResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r deleteLabelResponse) error() error { return r.Err }
|
|
|
|
func deleteLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*deleteLabelRequest)
|
|
err := svc.DeleteLabel(ctx, req.Name)
|
|
if err != nil {
|
|
return deleteLabelResponse{Err: err}, nil
|
|
}
|
|
return deleteLabelResponse{}, nil
|
|
}
|
|
|
|
func (svc *Service) DeleteLabel(ctx context.Context, name string) error {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionWrite); err != nil {
|
|
return err
|
|
}
|
|
|
|
return svc.ds.DeleteLabel(ctx, name)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Delete Label By ID
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type deleteLabelByIDRequest struct {
|
|
ID uint `url:"id"`
|
|
}
|
|
|
|
type deleteLabelByIDResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r deleteLabelByIDResponse) error() error { return r.Err }
|
|
|
|
func deleteLabelByIDEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*deleteLabelByIDRequest)
|
|
err := svc.DeleteLabelByID(ctx, req.ID)
|
|
if err != nil {
|
|
return deleteLabelByIDResponse{Err: err}, nil
|
|
}
|
|
return deleteLabelByIDResponse{}, nil
|
|
}
|
|
|
|
func (svc *Service) DeleteLabelByID(ctx context.Context, id uint) error {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionWrite); err != nil {
|
|
return err
|
|
}
|
|
|
|
label, err := svc.ds.Label(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return svc.ds.DeleteLabel(ctx, label.Name)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Apply Label Specs
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type applyLabelSpecsRequest struct {
|
|
Specs []*fleet.LabelSpec `json:"specs"`
|
|
}
|
|
|
|
type applyLabelSpecsResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r applyLabelSpecsResponse) error() error { return r.Err }
|
|
|
|
func applyLabelSpecsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*applyLabelSpecsRequest)
|
|
err := svc.ApplyLabelSpecs(ctx, req.Specs)
|
|
if err != nil {
|
|
return applyLabelSpecsResponse{Err: err}, nil
|
|
}
|
|
return applyLabelSpecsResponse{}, nil
|
|
}
|
|
|
|
func (svc *Service) ApplyLabelSpecs(ctx context.Context, specs []*fleet.LabelSpec) error {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionWrite); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, spec := range specs {
|
|
if spec.LabelMembershipType == fleet.LabelMembershipTypeDynamic && len(spec.Hosts) > 0 {
|
|
return ctxerr.Errorf(ctx, "label %s is declared as dynamic but contains `hosts` key", spec.Name)
|
|
}
|
|
if spec.LabelMembershipType == fleet.LabelMembershipTypeManual && spec.Hosts == nil {
|
|
// Hosts list doesn't need to contain anything, but it should at least not be nil.
|
|
return ctxerr.Errorf(ctx, "label %s is declared as manual but contains no `hosts key`", spec.Name)
|
|
}
|
|
}
|
|
return svc.ds.ApplyLabelSpecs(ctx, specs)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Get Label Specs
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type getLabelSpecsResponse struct {
|
|
Specs []*fleet.LabelSpec `json:"specs"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r getLabelSpecsResponse) error() error { return r.Err }
|
|
|
|
func getLabelSpecsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
specs, err := svc.GetLabelSpecs(ctx)
|
|
if err != nil {
|
|
return getLabelSpecsResponse{Err: err}, nil
|
|
}
|
|
return getLabelSpecsResponse{Specs: specs}, nil
|
|
}
|
|
|
|
func (svc *Service) GetLabelSpecs(ctx context.Context) ([]*fleet.LabelSpec, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return svc.ds.GetLabelSpecs(ctx)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Get Label Spec
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type getLabelSpecResponse struct {
|
|
Spec *fleet.LabelSpec `json:"specs,omitempty"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r getLabelSpecResponse) error() error { return r.Err }
|
|
|
|
func getLabelSpecEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
req := request.(*getGenericSpecRequest)
|
|
spec, err := svc.GetLabelSpec(ctx, req.Name)
|
|
if err != nil {
|
|
return getLabelSpecResponse{Err: err}, nil
|
|
}
|
|
return getLabelSpecResponse{Spec: spec}, nil
|
|
}
|
|
|
|
func (svc *Service) GetLabelSpec(ctx context.Context, name string) (*fleet.LabelSpec, error) {
|
|
if err := svc.authz.Authorize(ctx, &fleet.Label{}, fleet.ActionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return svc.ds.GetLabelSpec(ctx, name)
|
|
}
|