2022-03-09 21:13:56 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
|
|
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
)
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Get Current Device's Host
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type getDeviceHostRequest struct {
|
|
|
|
Token string `url:"token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *getDeviceHostRequest) deviceAuthToken() string {
|
|
|
|
return r.Token
|
|
|
|
}
|
|
|
|
|
2022-03-16 14:15:25 +00:00
|
|
|
type getDeviceHostResponse struct {
|
2022-06-10 15:39:02 +00:00
|
|
|
Host *HostDetailResponse `json:"host"`
|
|
|
|
OrgLogoURL string `json:"org_logo_url"`
|
|
|
|
TransparencyURL string `json:"transparency_url"`
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
License fleet.LicenseInfo `json:"license"`
|
2022-03-16 14:15:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r getDeviceHostResponse) error() error { return r.Err }
|
|
|
|
|
2022-03-09 21:13:56 +00:00
|
|
|
func getDeviceHostEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
2022-03-16 14:15:25 +00:00
|
|
|
return getDeviceHostResponse{Err: err}, nil
|
2022-03-09 21:13:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// must still load the full host details, as it returns more information
|
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
|
|
|
opts := fleet.HostDetailOptions{
|
|
|
|
IncludeCVEScores: false,
|
|
|
|
IncludePolicies: false,
|
|
|
|
}
|
|
|
|
hostDetails, err := svc.GetHost(ctx, host.ID, opts)
|
2022-03-09 21:13:56 +00:00
|
|
|
if err != nil {
|
2022-03-16 14:15:25 +00:00
|
|
|
return getDeviceHostResponse{Err: err}, nil
|
2022-03-09 21:13:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := hostDetailResponseForHost(ctx, svc, hostDetails)
|
|
|
|
if err != nil {
|
2022-03-16 14:15:25 +00:00
|
|
|
return getDeviceHostResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// the org logo URL config is required by the frontend to render the page;
|
|
|
|
// we need to be careful with what we return from AppConfig in the response
|
|
|
|
// as this is a weakly authenticated endpoint (with the device auth token).
|
|
|
|
ac, err := svc.AppConfig(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return getDeviceHostResponse{Err: err}, nil
|
2022-03-09 21:13:56 +00:00
|
|
|
}
|
|
|
|
|
2022-05-19 21:28:49 +00:00
|
|
|
license, err := svc.License(ctx)
|
|
|
|
if err != nil {
|
2022-05-19 23:29:33 +00:00
|
|
|
return getDeviceHostResponse{Err: err}, nil
|
2022-05-19 21:28:49 +00:00
|
|
|
}
|
|
|
|
|
2022-06-10 15:39:02 +00:00
|
|
|
transparencyURL := fleet.DefaultTransparencyURL
|
|
|
|
if license.Tier == "premium" && ac.FleetDesktop.TransparencyURL != "" {
|
|
|
|
transparencyURL = ac.FleetDesktop.TransparencyURL
|
|
|
|
}
|
|
|
|
|
2022-03-16 14:15:25 +00:00
|
|
|
return getDeviceHostResponse{
|
2022-06-10 15:39:02 +00:00
|
|
|
Host: resp,
|
|
|
|
OrgLogoURL: ac.OrgInfo.OrgLogoURL,
|
|
|
|
TransparencyURL: transparencyURL,
|
|
|
|
License: *license,
|
2022-03-16 14:15:25 +00:00
|
|
|
}, nil
|
2022-03-09 21:13:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AuthenticateDevice returns the host identified by the device authentication
|
|
|
|
// token, along with a boolean indicating if debug logging is enabled for that
|
|
|
|
// host.
|
|
|
|
func (svc *Service) AuthenticateDevice(ctx context.Context, authToken string) (*fleet.Host, bool, error) {
|
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
if authToken == "" {
|
|
|
|
return nil, false, ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("authentication error: missing device authentication token"))
|
|
|
|
}
|
|
|
|
|
|
|
|
host, err := svc.ds.LoadHostByDeviceAuthToken(ctx, authToken)
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
// OK
|
|
|
|
case fleet.IsNotFound(err):
|
|
|
|
return nil, false, ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("authentication error: invalid device authentication token"))
|
|
|
|
default:
|
|
|
|
return nil, false, ctxerr.Wrap(ctx, err, "authenticate device")
|
|
|
|
}
|
|
|
|
|
|
|
|
return host, svc.debugEnabledForHost(ctx, host.ID), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Refetch Current Device's Host
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type refetchDeviceHostRequest struct {
|
|
|
|
Token string `url:"token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *refetchDeviceHostRequest) deviceAuthToken() string {
|
|
|
|
return r.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
func refetchDeviceHostEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
|
|
|
return getHostResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := svc.RefetchHost(ctx, host.ID)
|
|
|
|
if err != nil {
|
|
|
|
return refetchHostResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return refetchHostResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// List Current Device's Host Device Mappings
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type listDeviceHostDeviceMappingRequest struct {
|
|
|
|
Token string `url:"token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *listDeviceHostDeviceMappingRequest) deviceAuthToken() string {
|
|
|
|
return r.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
func listDeviceHostDeviceMappingEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
|
|
|
return getHostResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dms, err := svc.ListHostDeviceMapping(ctx, host.ID)
|
|
|
|
if err != nil {
|
|
|
|
return listHostDeviceMappingResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return listHostDeviceMappingResponse{HostID: host.ID, DeviceMapping: dms}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Get Current Device's Macadmins
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type getDeviceMacadminsDataRequest struct {
|
|
|
|
Token string `url:"token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *getDeviceMacadminsDataRequest) deviceAuthToken() string {
|
|
|
|
return r.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDeviceMacadminsDataEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
|
|
|
return getHostResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := svc.MacadminsData(ctx, host.ID)
|
|
|
|
if err != nil {
|
|
|
|
return getMacadminsDataResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return getMacadminsDataResponse{Macadmins: data}, nil
|
|
|
|
}
|
add premium, device authed endpoint to retrieve policies (#5967)
This adds a new device authenticated endpoint, `/api/_version_/fleet/device/{token}/policies` to retrieve the device policies.
An example request / response looks like:
```bash
curl https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a/policies
```
```json
{
"policies": [
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Admin",
"author_email": "admin@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,windows,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-05-23T20:53:36Z",
"response": "fail"
}
]
}
```
Related to [#5685](https://github.com/fleetdm/fleet/issues/5685), in another changeset I will be adding "client" endpoints so we can consume this endpoint from Fleet Desktop
2022-05-31 17:54:43 +00:00
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// List Current Device's Policies
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type listDevicePoliciesRequest struct {
|
|
|
|
Token string `url:"token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *listDevicePoliciesRequest) deviceAuthToken() string {
|
|
|
|
return r.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
type listDevicePoliciesResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
Policies []*fleet.HostPolicy `json:"policies"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r listDevicePoliciesResponse) error() error { return r.Err }
|
|
|
|
|
|
|
|
func listDevicePoliciesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
|
|
|
return getHostResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := svc.ListDevicePolicies(ctx, host)
|
|
|
|
if err != nil {
|
|
|
|
return listDevicePoliciesResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return listDevicePoliciesResponse{Policies: data}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
|
|
|
|
// skipauth: No authorization check needed due to implementation returning
|
|
|
|
// only license error.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
return nil, fleet.ErrMissingLicense
|
|
|
|
}
|
2022-06-09 13:17:55 +00:00
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Device API features
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type deviceAPIFeaturesRequest struct {
|
|
|
|
Token string `url:"token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *deviceAPIFeaturesRequest) deviceAuthToken() string {
|
|
|
|
return r.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
type deviceAPIFeaturesResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
Features fleet.DeviceAPIFeatures
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r deviceAPIFeaturesResponse) error() error { return r.Err }
|
|
|
|
|
|
|
|
func deviceAPIFeaturesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
|
|
|
return deviceAPIFeaturesResponse{Features: fleet.DeviceAPIFeatures{}}, nil
|
|
|
|
}
|