2022-03-09 21:13:56 +00:00
package service
import (
"context"
2023-11-01 14:13:12 +00:00
"crypto/x509"
2024-09-13 17:53:05 +00:00
"database/sql"
2023-08-24 16:04:27 +00:00
"encoding/json"
2024-09-13 17:53:05 +00:00
"errors"
2025-09-04 15:58:47 +00:00
"fmt"
2023-08-24 16:04:27 +00:00
"io"
2022-06-13 19:07:08 +00:00
"net/http"
2023-08-24 16:04:27 +00:00
"net/url"
2023-01-16 15:22:12 +00:00
"strconv"
2022-10-10 20:15:35 +00:00
"time"
2022-03-09 21:13:56 +00:00
2023-01-16 15:22:12 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/authz"
2022-03-09 21:13:56 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
2023-01-16 15:22:12 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/logging"
2022-03-09 21:13:56 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2023-03-13 13:33:32 +00:00
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
2024-05-30 21:18:42 +00:00
mdmcrypto "github.com/fleetdm/fleet/v4/server/mdm/crypto"
2023-05-23 18:01:04 +00:00
"github.com/fleetdm/fleet/v4/server/ptr"
2024-10-31 19:24:42 +00:00
"github.com/go-kit/log/level"
2022-03-09 21:13:56 +00:00
)
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
/////////////////////////////////////////////////////////////////////////////////
// Ping device endpoint
/////////////////////////////////////////////////////////////////////////////////
type devicePingRequest struct { }
2023-12-13 20:31:48 +00:00
type deviceAuthPingRequest struct {
Token string ` url:"token" `
}
func ( r * deviceAuthPingRequest ) deviceAuthToken ( ) string {
return r . Token
}
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
type devicePingResponse struct { }
2025-02-03 17:23:26 +00:00
func ( r devicePingResponse ) Error ( ) error { return nil }
2022-12-27 14:26:59 +00:00
2025-02-18 17:09:43 +00:00
func ( r devicePingResponse ) HijackRender ( ctx context . Context , w http . ResponseWriter ) {
2023-08-24 16:04:27 +00:00
writeCapabilitiesHeader ( w , fleet . GetServerDeviceCapabilities ( ) )
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
}
// NOTE: we're intentionally not reading the capabilities header in this
// endpoint as is unauthenticated and we don't want to trust whatever comes in
// there.
2025-02-14 22:19:34 +00:00
func devicePingEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
svc . DisableAuthForPing ( ctx )
return devicePingResponse { } , nil
}
func ( svc * Service ) DisableAuthForPing ( ctx context . Context ) {
// skipauth: this endpoint is intentionally public to allow devices to ping
// the server and among other things, get the fleet.Capabilities header to
// determine which capabilities are enabled in the server.
svc . authz . SkipAuthorization ( ctx )
}
2022-09-15 16:12:50 +00:00
/////////////////////////////////////////////////////////////////////////////////
2022-09-12 19:37:38 +00:00
// Fleet Desktop endpoints
2022-09-15 16:12:50 +00:00
/////////////////////////////////////////////////////////////////////////////////
2022-10-12 20:13:43 +00:00
type fleetDesktopResponse struct {
2023-05-15 20:00:52 +00:00
Err error ` json:"error,omitempty" `
fleet . DesktopSummary
2022-09-12 19:37:38 +00:00
}
2025-02-03 17:23:26 +00:00
func ( r fleetDesktopResponse ) Error ( ) error { return r . Err }
2022-10-26 19:17:11 +00:00
2022-09-12 19:37:38 +00:00
type getFleetDesktopRequest struct {
Token string ` url:"token" `
}
func ( r * getFleetDesktopRequest ) deviceAuthToken ( ) string {
return r . Token
}
// getFleetDesktopEndpoint is meant to be the only API endpoint used by Fleet Desktop. This
// endpoint should not include any kind of identifying information about the host.
2025-02-14 22:19:34 +00:00
func getFleetDesktopEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2023-05-15 20:00:52 +00:00
sum , err := svc . GetFleetDesktopSummary ( ctx )
2022-09-12 19:37:38 +00:00
if err != nil {
2022-10-12 20:13:43 +00:00
return fleetDesktopResponse { Err : err } , nil
2022-09-12 19:37:38 +00:00
}
2023-05-15 20:00:52 +00:00
return fleetDesktopResponse { DesktopSummary : sum } , nil
}
func ( svc * Service ) GetFleetDesktopSummary ( ctx context . Context ) ( fleet . DesktopSummary , error ) {
// skipauth: No authorization check needed due to implementation returning
// only license error.
svc . authz . SkipAuthorization ( ctx )
2022-09-12 19:37:38 +00:00
2023-05-15 20:00:52 +00:00
return fleet . DesktopSummary { } , fleet . ErrMissingLicense
2022-09-12 19:37:38 +00:00
}
2022-03-09 21:13:56 +00:00
/////////////////////////////////////////////////////////////////////////////////
// Get Current Device's Host
/////////////////////////////////////////////////////////////////////////////////
type getDeviceHostRequest struct {
2024-06-24 15:43:16 +00:00
Token string ` url:"token" `
ExcludeSoftware bool ` query:"exclude_software,optional" `
2022-03-09 21:13:56 +00:00
}
func ( r * getDeviceHostRequest ) deviceAuthToken ( ) string {
return r . Token
}
2022-03-16 14:15:25 +00:00
type getDeviceHostResponse struct {
2023-07-13 18:35:25 +00:00
Host * HostDetailResponse ` json:"host" `
2024-07-02 16:32:49 +00:00
SelfService bool ` json:"self_service" `
2023-07-13 18:35:25 +00:00
OrgLogoURL string ` json:"org_logo_url" `
OrgLogoURLLightBackground string ` json:"org_logo_url_light_background" `
2024-05-29 12:54:48 +00:00
OrgContactURL string ` json:"org_contact_url" `
2023-07-13 18:35:25 +00:00
Err error ` json:"error,omitempty" `
License fleet . LicenseInfo ` json:"license" `
GlobalConfig fleet . DeviceGlobalConfig ` json:"global_config" `
2022-03-16 14:15:25 +00:00
}
2025-02-03 17:23:26 +00:00
func ( r getDeviceHostResponse ) Error ( ) error { return r . Err }
2022-03-16 14:15:25 +00:00
2025-02-14 22:19:34 +00:00
func getDeviceHostEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2024-06-24 15:43:16 +00:00
req := request . ( * getDeviceHostRequest )
2022-03-09 21:13:56 +00:00
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 ,
2024-06-24 15:43:16 +00:00
ExcludeSoftware : req . ExcludeSoftware ,
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
}
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).
2023-03-28 18:23:15 +00:00
ac , err := svc . AppConfigObfuscated ( ctx )
2022-03-16 14:15:25 +00:00
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
}
2023-05-23 18:01:04 +00:00
resp . DEPAssignedToFleet = ptr . Bool ( false )
if ac . MDM . EnabledAndConfigured && license . IsPremium ( ) {
hdep , err := svc . GetHostDEPAssignment ( ctx , host )
if err != nil && ! fleet . IsNotFound ( err ) {
return getDeviceHostResponse { Err : err } , nil
}
resp . DEPAssignedToFleet = ptr . Bool ( hdep . IsDEPAssignedToFleet ( ) )
}
2024-05-29 12:54:48 +00:00
softwareInventoryEnabled := ac . Features . EnableSoftwareInventory
if resp . TeamID != nil {
// load the team to get the device's team's software inventory config.
tm , err := svc . GetTeam ( ctx , * resp . TeamID )
if err != nil && ! fleet . IsNotFound ( err ) {
return getDeviceHostResponse { Err : err } , nil
}
if tm != nil {
2024-06-24 15:43:16 +00:00
softwareInventoryEnabled = tm . Config . Features . EnableSoftwareInventory // TODO: We should look for opportunities to fix the confusing name of the `global_config` object in the API response. Also, how can we better clarify/document the expected order of precedence for team and global feature flags?
2024-05-29 12:54:48 +00:00
}
}
2024-07-02 16:32:49 +00:00
hasSelfService := false
if softwareInventoryEnabled {
hasSelfService , err = svc . HasSelfServiceSoftwareInstallers ( ctx , host )
if err != nil {
return getDeviceHostResponse { Err : err } , nil
}
}
2024-05-29 12:54:48 +00:00
deviceGlobalConfig := fleet . DeviceGlobalConfig {
MDM : fleet . DeviceGlobalMDMConfig {
2024-05-29 15:01:48 +00:00
// TODO(mna): It currently only returns the Apple enabled and configured,
// regardless of the platform of the device. See
// https://github.com/fleetdm/fleet/pull/19304#discussion_r1618792410.
2024-05-29 12:54:48 +00:00
EnabledAndConfigured : ac . MDM . EnabledAndConfigured ,
} ,
Features : fleet . DeviceFeatures {
EnableSoftwareInventory : softwareInventoryEnabled ,
} ,
}
2022-03-16 14:15:25 +00:00
return getDeviceHostResponse {
2024-05-29 12:54:48 +00:00
Host : resp ,
OrgLogoURL : ac . OrgInfo . OrgLogoURL ,
OrgContactURL : ac . OrgInfo . ContactURL ,
License : * license ,
GlobalConfig : deviceGlobalConfig ,
2024-07-02 16:32:49 +00:00
SelfService : hasSelfService ,
2022-03-16 14:15:25 +00:00
} , nil
2022-03-09 21:13:56 +00:00
}
2023-05-23 18:01:04 +00:00
func ( svc * Service ) GetHostDEPAssignment ( ctx context . Context , host * fleet . Host ) ( * fleet . HostDEPAssignment , error ) {
2024-04-08 14:10:29 +00:00
alreadyAuthd := svc . authz . IsAuthenticatedWith ( ctx , authz . AuthnDeviceToken )
2023-05-23 18:01:04 +00:00
if ! alreadyAuthd {
if err := svc . authz . Authorize ( ctx , host , fleet . ActionRead ) ; err != nil {
return nil , err
}
}
return svc . ds . GetHostDEPAssignment ( ctx , host . ID )
}
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 ) {
2022-10-10 20:15:35 +00:00
const deviceAuthTokenTTL = time . Hour
2022-03-09 21:13:56 +00:00
// 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" ) )
}
2022-10-10 20:15:35 +00:00
host , err := svc . ds . LoadHostByDeviceAuthToken ( ctx , authToken , deviceAuthTokenTTL )
2022-03-09 21:13:56 +00:00
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
}
2025-02-14 22:19:34 +00:00
func refetchDeviceHostEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2022-03-09 21:13:56 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
2022-06-29 12:12:20 +00:00
return refetchHostResponse { Err : err } , nil
2022-03-09 21:13:56 +00:00
}
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
}
2025-02-14 22:19:34 +00:00
func listDeviceHostDeviceMappingEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2022-03-09 21:13:56 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
2022-06-29 12:12:20 +00:00
return listHostDeviceMappingResponse { Err : err } , nil
2022-03-09 21:13:56 +00:00
}
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
}
2025-02-14 22:19:34 +00:00
func getDeviceMacadminsDataEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2022-03-09 21:13:56 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
2022-06-29 12:12:20 +00:00
return getMacadminsDataResponse { Err : err } , nil
2022-03-09 21:13:56 +00:00
}
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" `
}
2025-02-03 17:23:26 +00:00
func ( r listDevicePoliciesResponse ) Error ( ) error { return r . Err }
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
2025-02-14 22:19:34 +00:00
func listDevicePoliciesEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
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
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
2022-06-29 12:12:20 +00:00
return listDevicePoliciesResponse { Err : err } , 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
}
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
2025-09-16 07:25:02 +00:00
////////////////////////////////////////////////////////////////////////////////
// Resend configuration profile
////////////////////////////////////////////////////////////////////////////////
type resendDeviceConfigurationProfileRequest struct {
Token string ` url:"token" `
ProfileUUID string ` url:"profile_uuid" `
}
func ( r * resendDeviceConfigurationProfileRequest ) deviceAuthToken ( ) string {
return r . Token
}
type resendDeviceConfigurationProfileResponse struct {
Err error ` json:"error,omitempty" `
}
func ( r resendDeviceConfigurationProfileResponse ) Error ( ) error { return r . Err }
func ( r resendDeviceConfigurationProfileResponse ) Status ( ) int { return http . StatusAccepted }
func resendDeviceConfigurationProfileEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return resendDeviceConfigurationProfileResponse { Err : err } , nil
}
req := request . ( * resendDeviceConfigurationProfileRequest )
2025-09-18 06:38:52 +00:00
err := svc . ResendDeviceHostMDMProfile ( ctx , host , req . ProfileUUID )
2025-09-16 07:25:02 +00:00
if err != nil {
return resendDeviceConfigurationProfileResponse {
Err : err ,
} , nil
}
return resendDeviceConfigurationProfileResponse { } , nil
}
2025-04-24 17:20:21 +00:00
////////////////////////////////////////////////////////////////////////////////
// Get software MDM command results
////////////////////////////////////////////////////////////////////////////////
type getDeviceMDMCommandResultsRequest struct {
Token string ` url:"token" `
CommandUUID string ` url:"command_uuid" `
}
func ( r * getDeviceMDMCommandResultsRequest ) deviceAuthToken ( ) string {
return r . Token
}
func getDeviceMDMCommandResultsEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
_ , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return getMDMCommandResultsResponse { Err : err } , nil
}
req := request . ( * getDeviceMDMCommandResultsRequest )
results , err := svc . GetMDMCommandResults ( ctx , req . CommandUUID )
if err != nil {
return getMDMCommandResultsResponse {
Err : err ,
} , nil
}
return getMDMCommandResultsResponse {
Results : results ,
} , nil
}
2022-06-13 19:07:08 +00:00
////////////////////////////////////////////////////////////////////////////////
// Transparency URL Redirect
////////////////////////////////////////////////////////////////////////////////
type transparencyURLRequest struct {
Token string ` url:"token" `
}
func ( r * transparencyURLRequest ) deviceAuthToken ( ) string {
return r . Token
}
type transparencyURLResponse struct {
2025-02-18 17:09:43 +00:00
RedirectURL string ` json:"-" ` // used to control the redirect, see HijackRender method
2022-06-13 19:07:08 +00:00
Err error ` json:"error,omitempty" `
}
2025-02-18 17:09:43 +00:00
func ( r transparencyURLResponse ) HijackRender ( ctx context . Context , w http . ResponseWriter ) {
2022-06-13 19:07:08 +00:00
w . Header ( ) . Set ( "Location" , r . RedirectURL )
w . WriteHeader ( http . StatusTemporaryRedirect )
}
2025-02-03 17:23:26 +00:00
func ( r transparencyURLResponse ) Error ( ) error { return r . Err }
2022-06-13 19:07:08 +00:00
2025-02-14 22:19:34 +00:00
func transparencyURL ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2025-03-31 19:43:17 +00:00
transparencyURL , err := svc . GetTransparencyURL ( ctx )
return transparencyURLResponse { RedirectURL : transparencyURL , Err : err } , nil
}
func ( svc * Service ) GetTransparencyURL ( ctx context . Context ) ( string , error ) {
2023-03-28 18:23:15 +00:00
config , err := svc . AppConfigObfuscated ( ctx )
2022-06-13 19:07:08 +00:00
if err != nil {
2025-03-31 19:43:17 +00:00
return "" , err
2022-06-13 19:07:08 +00:00
}
license , err := svc . License ( ctx )
if err != nil {
2025-03-31 19:43:17 +00:00
return "" , err
2022-06-13 19:07:08 +00:00
}
transparencyURL := fleet . DefaultTransparencyURL
2025-03-31 19:43:17 +00:00
// See #27309; overridden if on Fleet Premium and custom transparency URL is set
if svc . config . Partnerships . EnableSecureframe {
transparencyURL = fleet . SecureframeTransparencyURL
}
// Fleet Premium license is required for custom transparency URL
2023-05-15 18:06:09 +00:00
if license . IsPremium ( ) && config . FleetDesktop . TransparencyURL != "" {
2022-06-13 19:07:08 +00:00
transparencyURL = config . FleetDesktop . TransparencyURL
}
2025-03-31 19:43:17 +00:00
return transparencyURL , nil
2022-06-13 19:07:08 +00:00
}
2023-01-16 15:22:12 +00:00
2025-09-05 22:31:03 +00:00
// ///////////////////////////////////////////////////////////////////////////////
// Software title icons
// ///////////////////////////////////////////////////////////////////////////////
type getDeviceSoftwareIconRequest struct {
Token string ` url:"token" `
SoftwareTitleID uint ` url:"software_title_id" `
}
func ( r * getDeviceSoftwareIconRequest ) deviceAuthToken ( ) string {
return r . Token
}
type getDeviceSoftwareIconResponse struct {
Err error ` json:"error,omitempty" `
ImageData [ ] byte ` json:"-" `
ContentType string ` json:"-" `
Filename string ` json:"-" `
Size int64 ` json:"-" `
}
func ( r getDeviceSoftwareIconResponse ) Error ( ) error { return r . Err }
type getDeviceSoftwareIconRedirectResponse struct {
Err error ` json:"error,omitempty" `
RedirectURL string ` json:"-" `
}
func ( r getDeviceSoftwareIconRedirectResponse ) Error ( ) error { return r . Err }
func ( r getDeviceSoftwareIconRedirectResponse ) HijackRender ( ctx context . Context , w http . ResponseWriter ) {
if r . Err != nil {
return
}
w . Header ( ) . Set ( "Location" , r . RedirectURL )
w . WriteHeader ( http . StatusFound )
}
func ( r getDeviceSoftwareIconResponse ) HijackRender ( ctx context . Context , w http . ResponseWriter ) {
w . Header ( ) . Set ( "Content-Type" , r . ContentType )
w . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( ` inline; filename="%s" ` , r . Filename ) )
w . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , r . Size ) )
_ , _ = w . Write ( r . ImageData )
}
func getDeviceSoftwareIconEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return getDeviceSoftwareIconResponse { Err : err } , nil
}
req := request . ( * getDeviceSoftwareIconRequest )
var teamID uint
if host . TeamID != nil {
teamID = * host . TeamID
}
iconData , size , filename , err := svc . GetDeviceSoftwareIconsTitleIcon ( ctx , teamID , req . SoftwareTitleID )
if err != nil {
var vppErr * fleet . VPPIconAvailable
if errors . As ( err , & vppErr ) {
// 302 redirect to vpp app IconURL
return getDeviceSoftwareIconRedirectResponse { RedirectURL : vppErr . IconURL } , nil
}
return getDeviceSoftwareIconResponse { Err : err } , nil
}
return getDeviceSoftwareIconResponse {
ImageData : iconData ,
ContentType : "image/png" , // only type of icon we currently allow
Filename : filename ,
Size : size ,
} , nil
}
func ( svc * Service ) GetDeviceSoftwareIconsTitleIcon ( ctx context . Context , teamID uint , titleID uint ) ( [ ] byte , int64 , string , error ) {
// skipauth: No authorization check needed due to implementation returning
// only license error.
svc . authz . SkipAuthorization ( ctx )
return nil , 0 , "" , fleet . ErrMissingLicense
}
2023-08-24 16:04:27 +00:00
////////////////////////////////////////////////////////////////////////////////
// Receive errors from the client
////////////////////////////////////////////////////////////////////////////////
type fleetdErrorRequest struct {
Token string ` url:"token" `
fleet . FleetdError
}
func ( f * fleetdErrorRequest ) deviceAuthToken ( ) string {
return f . Token
}
// Since we're directly storing what we get in Redis, limit the request size to
// 5MB, this combined with the rate limit of this endpoint should be enough to
// prevent a malicious actor.
const maxFleetdErrorReportSize int64 = 5 * 1024 * 1024
2023-11-01 14:13:12 +00:00
func ( f * fleetdErrorRequest ) DecodeBody ( ctx context . Context , r io . Reader , u url . Values , c [ ] * x509 . Certificate ) error {
2023-08-24 16:04:27 +00:00
limitedReader := io . LimitReader ( r , maxFleetdErrorReportSize + 1 )
decoder := json . NewDecoder ( limitedReader )
for {
if err := decoder . Decode ( & f . FleetdError ) ; err == io . EOF {
break
} else if err == io . ErrUnexpectedEOF {
return & fleet . BadRequestError { Message : "payload exceeds maximum accepted size" }
} else if err != nil {
return & fleet . BadRequestError { Message : "invalid payload" }
}
}
return nil
}
type fleetdErrorResponse struct { }
2025-02-03 17:23:26 +00:00
func ( r fleetdErrorResponse ) Error ( ) error { return nil }
2023-08-24 16:04:27 +00:00
2025-02-14 22:19:34 +00:00
func fleetdError ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2023-08-24 16:04:27 +00:00
req := request . ( * fleetdErrorRequest )
2024-06-21 12:12:06 +00:00
err := svc . LogFleetdError ( ctx , req . FleetdError )
if err != nil {
return nil , err
}
return fleetdErrorResponse { } , nil
2023-08-24 16:04:27 +00:00
}
2024-06-21 12:12:06 +00:00
func ( svc * Service ) LogFleetdError ( ctx context . Context , fleetdError fleet . FleetdError ) error {
2023-08-24 16:04:27 +00:00
if ! svc . authz . IsAuthenticatedWith ( ctx , authz . AuthnDeviceToken ) {
return ctxerr . Wrap ( ctx , fleet . NewPermissionError ( "forbidden: only device-authenticated hosts can access this endpoint" ) )
}
2024-10-31 19:24:42 +00:00
err := ctxerr . WrapWithData ( ctx , fleetdError , "receive fleetd error" , fleetdError . ToMap ( ) )
level . Warn ( svc . logger ) . Log (
2024-06-21 12:12:06 +00:00
"msg" ,
"fleetd error" ,
"error" ,
2024-10-31 19:24:42 +00:00
err ,
2024-06-21 12:12:06 +00:00
)
2024-10-31 19:24:42 +00:00
// Send to Redis/telemetry (if enabled)
ctxerr . Handle ( ctx , err )
2024-06-21 12:12:06 +00:00
return nil
2023-08-24 16:04:27 +00:00
}
2023-01-16 15:22:12 +00:00
////////////////////////////////////////////////////////////////////////////////
// Get Current Device's MDM Apple Enrollment Profile
////////////////////////////////////////////////////////////////////////////////
type getDeviceMDMManualEnrollProfileRequest struct {
Token string ` url:"token" `
}
func ( r * getDeviceMDMManualEnrollProfileRequest ) deviceAuthToken ( ) string {
return r . Token
}
type getDeviceMDMManualEnrollProfileResponse struct {
2025-02-18 17:09:43 +00:00
// Profile field is used in HijackRender for the response.
2023-01-16 15:22:12 +00:00
Profile [ ] byte
Err error ` json:"error,omitempty" `
}
2025-02-18 17:09:43 +00:00
func ( r getDeviceMDMManualEnrollProfileResponse ) HijackRender ( ctx context . Context , w http . ResponseWriter ) {
2023-01-16 15:22:12 +00:00
// make the browser download the content to a file
w . Header ( ) . Add ( "Content-Disposition" , ` attachment; filename="fleet-mdm-enrollment-profile.mobileconfig" ` )
// explicitly set the content length before the write, so the caller can
// detect short writes (if it fails to send the full content properly)
w . Header ( ) . Set ( "Content-Length" , strconv . FormatInt ( int64 ( len ( r . Profile ) ) , 10 ) )
// this content type will make macos open the profile with the proper application
2024-10-24 16:54:24 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/x-apple-aspen-config; charset=utf-8" )
2023-01-16 15:22:12 +00:00
// prevent detection of content, obey the provided content-type
w . Header ( ) . Set ( "X-Content-Type-Options" , "nosniff" )
if n , err := w . Write ( r . Profile ) ; err != nil {
logging . WithExtras ( ctx , "err" , err , "written" , n )
}
}
2025-02-03 17:23:26 +00:00
func ( r getDeviceMDMManualEnrollProfileResponse ) Error ( ) error { return r . Err }
2023-01-16 15:22:12 +00:00
2025-02-14 22:19:34 +00:00
func getDeviceMDMManualEnrollProfileEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2023-01-16 15:22:12 +00:00
// this call ensures that the authentication was done, no need to actually
// use the host
if _ , ok := hostctx . FromContext ( ctx ) ; ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return getDeviceMDMManualEnrollProfileResponse { Err : err } , nil
}
profile , err := svc . GetDeviceMDMAppleEnrollmentProfile ( ctx )
if err != nil {
return getDeviceMDMManualEnrollProfileResponse { Err : err } , nil
}
return getDeviceMDMManualEnrollProfileResponse { Profile : profile } , nil
}
func ( svc * Service ) GetDeviceMDMAppleEnrollmentProfile ( ctx context . Context ) ( [ ] byte , error ) {
// must be device-authenticated, no additional authorization is required
if ! svc . authz . IsAuthenticatedWith ( ctx , authz . AuthnDeviceToken ) {
return nil , ctxerr . Wrap ( ctx , fleet . NewPermissionError ( "forbidden: only device-authenticated hosts can access this endpoint" ) )
}
2024-09-13 17:53:05 +00:00
cfg , err := svc . ds . AppConfig ( ctx )
2023-01-16 15:22:12 +00:00
if err != nil {
2024-09-13 17:53:05 +00:00
return nil , ctxerr . Wrap ( ctx , err , "fetching app config" )
2023-01-16 15:22:12 +00:00
}
2024-09-13 17:53:05 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
return nil , ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
2024-05-30 21:18:42 +00:00
}
2024-09-13 17:53:05 +00:00
tmSecrets , err := svc . ds . GetEnrollSecrets ( ctx , host . TeamID )
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
return nil , ctxerr . Wrap ( ctx , err , "getting host team enroll secrets" )
}
if len ( tmSecrets ) == 0 && host . TeamID != nil {
tmSecrets , err = svc . ds . GetEnrollSecrets ( ctx , nil )
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
return nil , ctxerr . Wrap ( ctx , err , "getting no team enroll secrets" )
}
}
if len ( tmSecrets ) == 0 {
return nil , & fleet . BadRequestError { Message : "unable to find an enroll secret to generate enrollment profile" }
2024-06-03 21:33:52 +00:00
}
2024-09-13 17:53:05 +00:00
enrollSecret := tmSecrets [ 0 ] . Secret
2025-08-18 16:31:53 +00:00
// IB: We skip IDP association here, since it's not part of the spec
profBytes , err := apple_mdm . GenerateOTAEnrollmentProfileMobileconfig ( cfg . OrgInfo . OrgName , cfg . MDMUrl ( ) , enrollSecret , "" )
2023-01-16 15:22:12 +00:00
if err != nil {
2024-09-13 17:53:05 +00:00
return nil , ctxerr . Wrap ( ctx , err , "generating ota mobileconfig file for manual enrollment" )
2023-01-16 15:22:12 +00:00
}
2024-04-18 21:01:37 +00:00
2024-09-13 17:53:05 +00:00
signed , err := mdmcrypto . Sign ( ctx , profBytes , svc . ds )
2024-04-18 21:01:37 +00:00
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "signing profile" )
}
return signed , nil
2023-01-16 15:22:12 +00:00
}
2023-03-20 19:14:07 +00:00
2023-05-17 14:16:26 +00:00
////////////////////////////////////////////////////////////////////////////////
// Signal start of mdm migration on a device
////////////////////////////////////////////////////////////////////////////////
type deviceMigrateMDMRequest struct {
Token string ` url:"token" `
}
func ( r * deviceMigrateMDMRequest ) deviceAuthToken ( ) string {
return r . Token
}
type deviceMigrateMDMResponse struct {
Err error ` json:"error,omitempty" `
}
2025-02-03 17:23:26 +00:00
func ( r deviceMigrateMDMResponse ) Error ( ) error { return r . Err }
2023-05-17 14:16:26 +00:00
func ( r deviceMigrateMDMResponse ) Status ( ) int { return http . StatusNoContent }
2025-02-14 22:19:34 +00:00
func migrateMDMDeviceEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2023-05-17 14:16:26 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return deviceMigrateMDMResponse { Err : err } , nil
}
if err := svc . TriggerMigrateMDMDevice ( ctx , host ) ; err != nil {
return deviceMigrateMDMResponse { Err : err } , nil
}
return deviceMigrateMDMResponse { } , nil
}
func ( svc * Service ) TriggerMigrateMDMDevice ( ctx context . Context , host * fleet . Host ) error {
return fleet . ErrMissingLicense
}
2024-05-01 18:37:52 +00:00
2024-11-18 22:44:25 +00:00
////////////////////////////////////////////////////////////////////////////////
// Trigger linux key escrow
////////////////////////////////////////////////////////////////////////////////
type triggerLinuxDiskEncryptionEscrowRequest struct {
Token string ` url:"token" `
}
func ( r * triggerLinuxDiskEncryptionEscrowRequest ) deviceAuthToken ( ) string {
return r . Token
}
type triggerLinuxDiskEncryptionEscrowResponse struct {
Err error ` json:"error,omitempty" `
}
2025-02-03 17:23:26 +00:00
func ( r triggerLinuxDiskEncryptionEscrowResponse ) Error ( ) error { return r . Err }
2024-11-18 22:44:25 +00:00
func ( r triggerLinuxDiskEncryptionEscrowResponse ) Status ( ) int { return http . StatusNoContent }
2025-02-14 22:19:34 +00:00
func triggerLinuxDiskEncryptionEscrowEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2024-11-18 22:44:25 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return triggerLinuxDiskEncryptionEscrowResponse { Err : err } , nil
}
if err := svc . TriggerLinuxDiskEncryptionEscrow ( ctx , host ) ; err != nil {
return triggerLinuxDiskEncryptionEscrowResponse { Err : err } , nil
}
return triggerLinuxDiskEncryptionEscrowResponse { } , nil
}
func ( svc * Service ) TriggerLinuxDiskEncryptionEscrow ( ctx context . Context , host * fleet . Host ) error {
return fleet . ErrMissingLicense
}
2024-05-01 18:37:52 +00:00
////////////////////////////////////////////////////////////////////////////////
// Get Current Device's Software
////////////////////////////////////////////////////////////////////////////////
type getDeviceSoftwareRequest struct {
2024-05-27 14:53:41 +00:00
Token string ` url:"token" `
fleet . HostSoftwareTitleListOptions
2024-05-01 18:37:52 +00:00
}
func ( r * getDeviceSoftwareRequest ) deviceAuthToken ( ) string {
return r . Token
}
type getDeviceSoftwareResponse struct {
Software [ ] * fleet . HostSoftwareWithInstaller ` json:"software" `
2024-05-15 12:55:27 +00:00
Count int ` json:"count" `
2024-05-01 18:37:52 +00:00
Meta * fleet . PaginationMetadata ` json:"meta,omitempty" `
Err error ` json:"error,omitempty" `
}
2025-02-03 17:23:26 +00:00
func ( r getDeviceSoftwareResponse ) Error ( ) error { return r . Err }
2024-05-01 18:37:52 +00:00
2025-02-14 22:19:34 +00:00
func getDeviceSoftwareEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2024-05-01 18:37:52 +00:00
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return getDeviceSoftwareResponse { Err : err } , nil
}
req := request . ( * getDeviceSoftwareRequest )
2024-05-27 14:53:41 +00:00
res , meta , err := svc . ListHostSoftware ( ctx , host . ID , req . HostSoftwareTitleListOptions )
2025-09-05 22:31:03 +00:00
for _ , s := range res {
// mutate HostSoftwareWithInstaller records for my device page
s . ForMyDevicePage ( req . Token )
}
2024-05-01 18:37:52 +00:00
if err != nil {
return getDeviceSoftwareResponse { Err : err } , nil
}
if res == nil {
res = [ ] * fleet . HostSoftwareWithInstaller { }
}
2024-10-18 17:38:26 +00:00
return getDeviceSoftwareResponse { Software : res , Meta : meta , Count : int ( meta . TotalResults ) } , nil //nolint:gosec // dismiss G115
2024-05-01 18:37:52 +00:00
}
2025-02-24 17:52:39 +00:00
////////////////////////////////////////////////////////////////////////////////
// List Current Device's Certificates
////////////////////////////////////////////////////////////////////////////////
type listDeviceCertificatesRequest struct {
Token string ` url:"token" `
fleet . ListOptions
}
2025-02-25 15:42:17 +00:00
func ( r * listDeviceCertificatesRequest ) ValidateRequest ( ) error {
if r . ListOptions . OrderKey != "" && ! listHostCertificatesSortCols [ r . ListOptions . OrderKey ] {
return badRequest ( "invalid order key" )
}
return nil
}
2025-02-24 17:52:39 +00:00
func ( r * listDeviceCertificatesRequest ) deviceAuthToken ( ) string {
return r . Token
}
type listDeviceCertificatesResponse struct {
Certificates [ ] * fleet . HostCertificatePayload ` json:"certificates" `
Meta * fleet . PaginationMetadata ` json:"meta,omitempty" `
2025-09-17 11:05:32 +00:00
Count uint ` json:"count" `
2025-02-24 17:52:39 +00:00
Err error ` json:"error,omitempty" `
}
func ( r listDeviceCertificatesResponse ) Error ( ) error { return r . Err }
func listDeviceCertificatesEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
host , ok := hostctx . FromContext ( ctx )
if ! ok {
err := ctxerr . Wrap ( ctx , fleet . NewAuthRequiredError ( "internal error: missing host from request context" ) )
return listDevicePoliciesResponse { Err : err } , nil
}
req := request . ( * listDeviceCertificatesRequest )
res , meta , err := svc . ListHostCertificates ( ctx , host . ID , req . ListOptions )
if err != nil {
return listDeviceCertificatesResponse { Err : err } , nil
}
if res == nil {
res = [ ] * fleet . HostCertificatePayload { }
}
2025-09-17 11:05:32 +00:00
return listDeviceCertificatesResponse { Certificates : res , Meta : meta , Count : meta . TotalResults } , nil
2025-02-24 17:52:39 +00:00
}
2025-09-04 15:58:47 +00:00
/////////////////////////////////////////////////////////////////////////////////
// Get "Setup experience" status.
/////////////////////////////////////////////////////////////////////////////////
type getDeviceSetupExperienceStatusRequest struct {
Token string ` url:"token" `
}
func ( r * getDeviceSetupExperienceStatusRequest ) deviceAuthToken ( ) string {
return r . Token
}
type getDeviceSetupExperienceStatusResponse struct {
Results * fleet . DeviceSetupExperienceStatusPayload ` json:"setup_experience_results,omitempty" `
Err error ` json:"error,omitempty" `
}
func ( r getDeviceSetupExperienceStatusResponse ) Error ( ) error { return r . Err }
func getDeviceSetupExperienceStatusEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
if _ , ok := request . ( * getDeviceSetupExperienceStatusRequest ) ; ! ok {
return nil , fmt . Errorf ( "internal error: invalid request type: %T" , request )
}
results , err := svc . GetDeviceSetupExperienceStatus ( ctx )
if err != nil {
return & getDeviceSetupExperienceStatusResponse { Err : err } , nil
}
return & getDeviceSetupExperienceStatusResponse { Results : results } , nil
}
func ( svc * Service ) GetDeviceSetupExperienceStatus ( ctx context . Context ) ( * fleet . DeviceSetupExperienceStatusPayload , error ) {
// skipauth: No authorization check needed due to implementation returning
// only license error.
svc . authz . SkipAuthorization ( ctx )
return nil , fleet . ErrMissingLicense
}