2022-09-23 19:00:23 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2023-12-28 20:20:36 +00:00
|
|
|
"errors"
|
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
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
|
2024-01-10 19:53:12 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/pkg/scripts"
|
2022-09-23 19:00:23 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
|
|
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
|
2023-09-22 14:09:09 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
2022-09-23 19:00:23 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2023-12-15 18:26:32 +00:00
|
|
|
microsoft_mdm "github.com/fleetdm/fleet/v4/server/mdm/microsoft"
|
2023-10-06 22:04:33 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
2023-03-13 21:54:18 +00:00
|
|
|
"github.com/go-kit/kit/log/level"
|
2022-09-23 19:00:23 +00:00
|
|
|
)
|
|
|
|
|
2022-10-03 20:28:19 +00:00
|
|
|
type setOrbitNodeKeyer interface {
|
|
|
|
setOrbitNodeKey(nodeKey string)
|
|
|
|
}
|
|
|
|
|
2023-03-13 21:54:18 +00:00
|
|
|
// EnrollOrbitRequest is the request Orbit instances use to enroll to Fleet.
|
2022-10-28 17:27:21 +00:00
|
|
|
type EnrollOrbitRequest struct {
|
2023-03-13 21:54:18 +00:00
|
|
|
// EnrollSecret is the secret to authenticate the enroll request.
|
|
|
|
EnrollSecret string `json:"enroll_secret"`
|
|
|
|
// HardwareUUID is the device's hardware UUID.
|
|
|
|
HardwareUUID string `json:"hardware_uuid"`
|
|
|
|
// HardwareSerial is the device's serial number.
|
2023-02-28 17:55:04 +00:00
|
|
|
HardwareSerial string `json:"hardware_serial"`
|
2023-03-13 21:54:18 +00:00
|
|
|
// Hostname is the device's hostname.
|
|
|
|
Hostname string `json:"hostname"`
|
|
|
|
// Platform is the device's platform as defined by osquery.
|
|
|
|
Platform string `json:"platform"`
|
2023-12-15 18:26:32 +00:00
|
|
|
// OsqueryIdentifier holds the identifier used by osquery.
|
|
|
|
// If not set, then the hardware UUID is used to match orbit and osquery.
|
|
|
|
OsqueryIdentifier string `json:"osquery_identifier"`
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 17:27:21 +00:00
|
|
|
type EnrollOrbitResponse struct {
|
2022-09-23 19:00:23 +00:00
|
|
|
OrbitNodeKey string `json:"orbit_node_key,omitempty"`
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type orbitGetConfigRequest struct {
|
|
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
|
|
}
|
|
|
|
|
2022-10-03 20:28:19 +00:00
|
|
|
func (r *orbitGetConfigRequest) setOrbitNodeKey(nodeKey string) {
|
|
|
|
r.OrbitNodeKey = nodeKey
|
|
|
|
}
|
|
|
|
|
2022-09-23 19:00:23 +00:00
|
|
|
func (r *orbitGetConfigRequest) orbitHostNodeKey() string {
|
|
|
|
return r.OrbitNodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type orbitGetConfigResponse struct {
|
2023-01-25 20:03:40 +00:00
|
|
|
fleet.OrbitConfig
|
|
|
|
Err error `json:"error,omitempty"`
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
2022-12-27 14:26:59 +00:00
|
|
|
func (r orbitGetConfigResponse) error() error { return r.Err }
|
|
|
|
|
2022-10-28 17:27:21 +00:00
|
|
|
func (r EnrollOrbitResponse) error() error { return r.Err }
|
2022-09-23 19:00:23 +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
|
|
|
// hijackRender so we can add a header with the server capabilities in the
|
|
|
|
// response, allowing Orbit to know what features are available without the
|
|
|
|
// need to enroll.
|
2022-10-28 17:27:21 +00:00
|
|
|
func (r EnrollOrbitResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
|
2023-08-24 16:04:27 +00:00
|
|
|
writeCapabilitiesHeader(w, fleet.GetServerOrbitCapabilities())
|
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
|
|
|
enc := json.NewEncoder(w)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
|
|
|
|
if err := enc.Encode(r); err != nil {
|
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [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.
- [X] 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:
- [X] 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)).~
2023-03-13 16:44:06 +00:00
|
|
|
encodeError(ctx, newOsqueryError(fmt.Sprintf("orbit enroll failed: %s", err)), w)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-27 14:26:59 +00:00
|
|
|
func enrollOrbitEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
2022-10-28 17:27:21 +00:00
|
|
|
req := request.(*EnrollOrbitRequest)
|
2023-03-13 21:54:18 +00:00
|
|
|
nodeKey, err := svc.EnrollOrbit(ctx, fleet.OrbitHostInfo{
|
2023-12-15 18:26:32 +00:00
|
|
|
HardwareUUID: req.HardwareUUID,
|
|
|
|
HardwareSerial: req.HardwareSerial,
|
|
|
|
Hostname: req.Hostname,
|
|
|
|
Platform: req.Platform,
|
|
|
|
OsqueryIdentifier: req.OsqueryIdentifier,
|
2023-03-13 21:54:18 +00:00
|
|
|
}, req.EnrollSecret)
|
2022-09-23 19:00:23 +00:00
|
|
|
if err != nil {
|
2022-10-28 17:27:21 +00:00
|
|
|
return EnrollOrbitResponse{Err: err}, nil
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
2022-10-28 17:27:21 +00:00
|
|
|
return EnrollOrbitResponse{OrbitNodeKey: nodeKey}, nil
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) AuthenticateOrbitHost(ctx context.Context, orbitNodeKey string) (*fleet.Host, bool, error) {
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
if orbitNodeKey == "" {
|
|
|
|
return nil, false, ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("authentication error: missing orbit node key"))
|
|
|
|
}
|
|
|
|
|
|
|
|
host, err := svc.ds.LoadHostByOrbitNodeKey(ctx, orbitNodeKey)
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
// OK
|
|
|
|
case fleet.IsNotFound(err):
|
|
|
|
return nil, false, ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("authentication error: invalid orbit node key"))
|
|
|
|
default:
|
|
|
|
return nil, false, ctxerr.Wrap(ctx, err, "authentication error orbit")
|
|
|
|
}
|
|
|
|
|
|
|
|
return host, svc.debugEnabledForHost(ctx, host.ID), nil
|
|
|
|
}
|
|
|
|
|
2023-03-13 21:54:18 +00:00
|
|
|
// EnrollOrbit enrolls an Orbit instance to Fleet and returns the orbit node key.
|
|
|
|
func (svc *Service) EnrollOrbit(ctx context.Context, hostInfo fleet.OrbitHostInfo, enrollSecret string) (string, error) {
|
2022-09-23 19:00:23 +00:00
|
|
|
// this is not a user-authenticated endpoint
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
2023-03-13 21:54:18 +00:00
|
|
|
|
|
|
|
logging.WithLevel(
|
|
|
|
logging.WithExtras(ctx,
|
|
|
|
"hardware_uuid", hostInfo.HardwareUUID,
|
|
|
|
"hardware_serial", hostInfo.HardwareSerial,
|
|
|
|
"hostname", hostInfo.Hostname,
|
|
|
|
"platform", hostInfo.Platform,
|
2023-12-15 18:26:32 +00:00
|
|
|
"osquery_identifier", hostInfo.OsqueryIdentifier,
|
2023-03-13 21:54:18 +00:00
|
|
|
),
|
|
|
|
level.Info,
|
|
|
|
)
|
2022-09-23 19:00:23 +00:00
|
|
|
|
|
|
|
secret, err := svc.ds.VerifyEnrollSecret(ctx, enrollSecret)
|
|
|
|
if err != nil {
|
2023-04-13 20:16:40 +00:00
|
|
|
if fleet.IsNotFound(err) {
|
|
|
|
// OK - This can happen if the following sequence of events take place:
|
|
|
|
// 1. User deletes global/team enroll secret.
|
|
|
|
// 2. User deletes the host in Fleet.
|
|
|
|
// 3. Orbit tries to re-enroll using old secret.
|
|
|
|
return "", fleet.NewAuthFailedError("invalid secret")
|
|
|
|
}
|
2023-08-23 20:47:47 +00:00
|
|
|
return "", fleet.OrbitError{Message: err.Error()}
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
orbitNodeKey, err := server.GenerateRandomText(svc.config.Osquery.NodeKeySize)
|
|
|
|
if err != nil {
|
2023-08-23 20:47:47 +00:00
|
|
|
return "", fleet.OrbitError{Message: "failed to generate orbit node key: " + err.Error()}
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 17:55:04 +00:00
|
|
|
appConfig, err := svc.ds.AppConfig(ctx)
|
|
|
|
if err != nil {
|
2023-08-23 20:47:47 +00:00
|
|
|
return "", fleet.OrbitError{Message: "app config load failed: " + err.Error()}
|
2023-02-28 17:55:04 +00:00
|
|
|
}
|
|
|
|
|
2023-03-13 21:54:18 +00:00
|
|
|
_, err = svc.ds.EnrollOrbit(ctx, appConfig.MDM.EnabledAndConfigured, hostInfo, orbitNodeKey, secret.TeamID)
|
2022-09-23 19:00:23 +00:00
|
|
|
if err != nil {
|
2023-08-23 20:47:47 +00:00
|
|
|
return "", fleet.OrbitError{Message: "failed to enroll " + err.Error()}
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return orbitNodeKey, nil
|
|
|
|
}
|
|
|
|
|
2022-12-27 14:26:59 +00:00
|
|
|
func getOrbitConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
2023-01-25 20:03:40 +00:00
|
|
|
cfg, err := svc.GetOrbitConfig(ctx)
|
2022-09-23 19:00:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return orbitGetConfigResponse{Err: err}, nil
|
|
|
|
}
|
2023-01-25 20:03:40 +00:00
|
|
|
return orbitGetConfigResponse{OrbitConfig: cfg}, nil
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-25 20:03:40 +00:00
|
|
|
func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, error) {
|
2022-09-23 19:00:23 +00:00
|
|
|
// this is not a user-authenticated endpoint
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, fleet.OrbitError{Message: "internal error: missing host from request context"}
|
2023-01-17 18:19:48 +00:00
|
|
|
}
|
|
|
|
|
2023-06-28 13:13:37 +00:00
|
|
|
appConfig, err := svc.ds.AppConfig(ctx)
|
2023-05-31 20:25:22 +00:00
|
|
|
if err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2023-05-31 20:25:22 +00:00
|
|
|
}
|
|
|
|
|
2023-06-20 18:06:45 +00:00
|
|
|
// set the host's orbit notifications for macOS MDM
|
2023-09-22 14:09:09 +00:00
|
|
|
var notifs fleet.OrbitConfigNotifications
|
2023-06-28 13:13:37 +00:00
|
|
|
if appConfig.MDM.EnabledAndConfigured && host.IsOsqueryEnrolled() {
|
|
|
|
// TODO(mna): all those notifications implied a macos hosts, but none of
|
|
|
|
// the checks enforce that (only indirectly in some cases, like
|
|
|
|
// IsDEPAssignedToFleet), should we add such a platform check?
|
|
|
|
|
2023-05-23 18:01:04 +00:00
|
|
|
if host.NeedsDEPEnrollment() {
|
2023-03-20 19:14:07 +00:00
|
|
|
notifs.RenewEnrollmentProfile = true
|
|
|
|
}
|
|
|
|
|
2023-06-28 13:13:37 +00:00
|
|
|
if appConfig.MDM.MacOSMigration.Enable &&
|
|
|
|
host.IsEligibleForDEPMigration() {
|
2023-05-31 20:25:22 +00:00
|
|
|
notifs.NeedsMDMMigration = true
|
|
|
|
}
|
|
|
|
|
2023-03-20 19:14:07 +00:00
|
|
|
if host.DiskEncryptionResetRequested != nil && *host.DiskEncryptionResetRequested {
|
|
|
|
notifs.RotateDiskEncryptionKey = true
|
|
|
|
|
|
|
|
// Since this is an user initiated action, we disable
|
|
|
|
// the flag when we deliver the notification to Orbit
|
|
|
|
if err := svc.ds.SetDiskEncryptionResetStatus(ctx, host.ID, false); err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2023-03-20 19:14:07 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
2023-06-27 15:59:33 +00:00
|
|
|
// set the host's orbit notifications for Windows MDM
|
2023-06-28 13:13:37 +00:00
|
|
|
if appConfig.MDM.WindowsEnabledAndConfigured {
|
|
|
|
if host.IsEligibleForWindowsMDMEnrollment() {
|
|
|
|
discoURL, err := microsoft_mdm.ResolveWindowsMDMDiscovery(appConfig.ServerSettings.ServerURL)
|
2023-06-20 18:06:45 +00:00
|
|
|
if err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2023-06-20 18:06:45 +00:00
|
|
|
}
|
2023-06-27 15:59:33 +00:00
|
|
|
notifs.WindowsMDMDiscoveryEndpoint = discoURL
|
|
|
|
notifs.NeedsProgrammaticWindowsMDMEnrollment = true
|
2023-06-20 18:06:45 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-07 17:24:32 +00:00
|
|
|
if !appConfig.MDM.WindowsEnabledAndConfigured {
|
2023-06-28 13:13:37 +00:00
|
|
|
if host.IsEligibleForWindowsMDMUnenrollment() {
|
|
|
|
notifs.NeedsProgrammaticWindowsMDMUnenrollment = true
|
|
|
|
}
|
|
|
|
}
|
2023-06-20 18:06:45 +00:00
|
|
|
|
2023-08-23 20:47:47 +00:00
|
|
|
// load the pending script executions for that host
|
2024-01-03 15:42:08 +00:00
|
|
|
if !appConfig.ServerSettings.ScriptsDisabled {
|
2024-01-10 19:53:12 +00:00
|
|
|
// it is important that the "ignoreOlder" parameter in this call is the
|
|
|
|
// same everywhere (which is here and in RunScript to check if there is
|
|
|
|
// already a pending script).
|
|
|
|
pending, err := svc.ds.ListPendingHostScriptExecutions(ctx, host.ID, scripts.MaxServerWaitTime)
|
2024-01-03 15:42:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return fleet.OrbitConfig{}, err
|
|
|
|
}
|
|
|
|
if len(pending) > 0 {
|
|
|
|
execIDs := make([]string, 0, len(pending))
|
|
|
|
for _, p := range pending {
|
|
|
|
execIDs = append(execIDs, p.ExecutionID)
|
|
|
|
}
|
|
|
|
notifs.PendingScriptExecutionIDs = execIDs
|
2023-08-23 20:47:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 19:00:23 +00:00
|
|
|
// team ID is not nil, get team specific flags and options
|
|
|
|
if host.TeamID != nil {
|
|
|
|
teamAgentOptions, err := svc.ds.TeamAgentOptions(ctx, *host.TeamID)
|
|
|
|
if err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-25 20:03:40 +00:00
|
|
|
var opts fleet.AgentOptions
|
2022-09-23 19:00:23 +00:00
|
|
|
if teamAgentOptions != nil && len(*teamAgentOptions) > 0 {
|
|
|
|
if err := json.Unmarshal(*teamAgentOptions, &opts); err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-25 20:03:40 +00:00
|
|
|
|
2023-09-22 14:09:09 +00:00
|
|
|
extensionsFiltered, err := svc.filterExtensionsForHost(ctx, opts.Extensions, host)
|
2023-09-22 08:17:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return fleet.OrbitConfig{}, err
|
|
|
|
}
|
|
|
|
|
2023-01-25 20:03:40 +00:00
|
|
|
mdmConfig, err := svc.ds.TeamMDMConfig(ctx, *host.TeamID)
|
|
|
|
if err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2023-01-25 20:03:40 +00:00
|
|
|
}
|
|
|
|
|
2023-01-26 22:52:03 +00:00
|
|
|
var nudgeConfig *fleet.NudgeConfig
|
2023-06-30 15:29:27 +00:00
|
|
|
if appConfig.MDM.EnabledAndConfigured &&
|
|
|
|
mdmConfig != nil &&
|
|
|
|
mdmConfig.MacOSUpdates.EnabledForHost(host) {
|
2023-01-26 22:52:03 +00:00
|
|
|
nudgeConfig, err = fleet.NewNudgeConfig(mdmConfig.MacOSUpdates)
|
|
|
|
if err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2023-01-25 20:03:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-07 17:24:32 +00:00
|
|
|
if mdmConfig.EnableDiskEncryption &&
|
2023-10-06 22:04:33 +00:00
|
|
|
host.IsEligibleForBitLockerEncryption() {
|
|
|
|
notifs.EnforceBitLockerEncryption = true
|
|
|
|
}
|
|
|
|
|
2024-01-02 20:59:40 +00:00
|
|
|
var updateChannels *fleet.OrbitUpdateChannels
|
|
|
|
if len(opts.UpdateChannels) > 0 {
|
|
|
|
var uc fleet.OrbitUpdateChannels
|
|
|
|
if err := json.Unmarshal(opts.UpdateChannels, &uc); err != nil {
|
|
|
|
return fleet.OrbitConfig{}, err
|
|
|
|
}
|
|
|
|
updateChannels = &uc
|
|
|
|
}
|
|
|
|
|
2023-01-25 20:03:40 +00:00
|
|
|
return fleet.OrbitConfig{
|
2024-01-02 20:59:40 +00:00
|
|
|
Flags: opts.CommandLineStartUpFlags,
|
|
|
|
Extensions: extensionsFiltered,
|
|
|
|
Notifications: notifs,
|
|
|
|
NudgeConfig: nudgeConfig,
|
|
|
|
UpdateChannels: updateChannels,
|
2023-01-25 20:03:40 +00:00
|
|
|
}, nil
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// team ID is nil, get global flags and options
|
|
|
|
var opts fleet.AgentOptions
|
2023-06-28 13:13:37 +00:00
|
|
|
if appConfig.AgentOptions != nil {
|
|
|
|
if err := json.Unmarshal(*appConfig.AgentOptions, &opts); err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2022-09-23 19:00:23 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-25 20:03:40 +00:00
|
|
|
|
2023-09-22 14:09:09 +00:00
|
|
|
extensionsFiltered, err := svc.filterExtensionsForHost(ctx, opts.Extensions, host)
|
2023-09-22 08:17:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return fleet.OrbitConfig{}, err
|
|
|
|
}
|
|
|
|
|
2023-01-26 22:52:03 +00:00
|
|
|
var nudgeConfig *fleet.NudgeConfig
|
2023-06-30 15:29:27 +00:00
|
|
|
if appConfig.MDM.EnabledAndConfigured &&
|
|
|
|
appConfig.MDM.MacOSUpdates.EnabledForHost(host) {
|
2023-06-28 13:13:37 +00:00
|
|
|
nudgeConfig, err = fleet.NewNudgeConfig(appConfig.MDM.MacOSUpdates)
|
2023-01-26 22:52:03 +00:00
|
|
|
if err != nil {
|
2023-09-22 08:17:27 +00:00
|
|
|
return fleet.OrbitConfig{}, err
|
2023-01-25 20:03:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 22:04:33 +00:00
|
|
|
if appConfig.MDM.WindowsEnabledAndConfigured &&
|
|
|
|
appConfig.MDM.EnableDiskEncryption.Value &&
|
|
|
|
host.IsEligibleForBitLockerEncryption() {
|
|
|
|
notifs.EnforceBitLockerEncryption = true
|
|
|
|
}
|
|
|
|
|
2024-01-02 20:59:40 +00:00
|
|
|
var updateChannels *fleet.OrbitUpdateChannels
|
|
|
|
if len(opts.UpdateChannels) > 0 {
|
|
|
|
var uc fleet.OrbitUpdateChannels
|
|
|
|
if err := json.Unmarshal(opts.UpdateChannels, &uc); err != nil {
|
|
|
|
return fleet.OrbitConfig{}, err
|
|
|
|
}
|
|
|
|
updateChannels = &uc
|
|
|
|
}
|
|
|
|
|
2023-01-25 20:03:40 +00:00
|
|
|
return fleet.OrbitConfig{
|
2024-01-02 20:59:40 +00:00
|
|
|
Flags: opts.CommandLineStartUpFlags,
|
|
|
|
Extensions: extensionsFiltered,
|
|
|
|
Notifications: notifs,
|
|
|
|
NudgeConfig: nudgeConfig,
|
|
|
|
UpdateChannels: updateChannels,
|
2023-01-25 20:03:40 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-09-22 14:09:09 +00:00
|
|
|
// filterExtensionsForHost filters a extensions configuration depending on the host platform and label membership.
|
|
|
|
//
|
|
|
|
// If all extensions are filtered, then it returns (nil, nil) (Orbit expects empty extensions if there
|
|
|
|
// are no extensions for the host.)
|
|
|
|
func (svc *Service) filterExtensionsForHost(ctx context.Context, extensions json.RawMessage, host *fleet.Host) (json.RawMessage, error) {
|
2023-09-22 08:17:27 +00:00
|
|
|
if len(extensions) == 0 {
|
2023-09-22 14:09:09 +00:00
|
|
|
return nil, nil
|
2023-09-22 08:17:27 +00:00
|
|
|
}
|
|
|
|
var extensionsInfo fleet.Extensions
|
|
|
|
if err := json.Unmarshal(extensions, &extensionsInfo); err != nil {
|
2023-09-22 14:09:09 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "unmarshal extensions config")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter the extensions by platform.
|
|
|
|
extensionsInfo.FilterByHostPlatform(host.Platform)
|
|
|
|
|
|
|
|
// Filter the extensions by labels (premium only feature).
|
|
|
|
if license, _ := license.FromContext(ctx); license != nil && license.IsPremium() {
|
|
|
|
for extensionName, extensionInfo := range extensionsInfo {
|
|
|
|
hostIsMemberOfAllLabels, err := svc.ds.HostMemberOfAllLabels(ctx, host.ID, extensionInfo.Labels)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ctxerr.Wrap(ctx, err, "check host labels")
|
|
|
|
}
|
|
|
|
if hostIsMemberOfAllLabels {
|
|
|
|
// Do not filter out, but there's no need to send the label names to the devices.
|
|
|
|
extensionInfo.Labels = nil
|
|
|
|
extensionsInfo[extensionName] = extensionInfo
|
|
|
|
} else {
|
|
|
|
delete(extensionsInfo, extensionName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Orbit expects empty message if no extensions apply.
|
|
|
|
if len(extensionsInfo) == 0 {
|
|
|
|
return nil, nil
|
2023-09-22 08:17:27 +00:00
|
|
|
}
|
|
|
|
extensionsFiltered, err := json.Marshal(extensionsInfo)
|
|
|
|
if err != nil {
|
2023-09-22 14:09:09 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "marshal extensions config")
|
2023-09-22 08:17:27 +00:00
|
|
|
}
|
|
|
|
return extensionsFiltered, nil
|
|
|
|
}
|
|
|
|
|
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 orbit endpoint
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type orbitPingRequest struct{}
|
|
|
|
|
|
|
|
type orbitPingResponse struct{}
|
|
|
|
|
|
|
|
func (r orbitPingResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
|
2023-08-24 16:04:27 +00:00
|
|
|
writeCapabilitiesHeader(w, fleet.GetServerOrbitCapabilities())
|
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
|
|
|
}
|
|
|
|
|
2022-12-27 14:26:59 +00:00
|
|
|
func (r orbitPingResponse) error() error { return nil }
|
|
|
|
|
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.
|
2022-12-27 14:26:59 +00:00
|
|
|
func orbitPingEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (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 orbitPingResponse{}, nil
|
|
|
|
}
|
2022-10-10 20:15:35 +00:00
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// SetOrUpdateDeviceToken endpoint
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type setOrUpdateDeviceTokenRequest struct {
|
|
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
|
|
DeviceAuthToken string `json:"device_auth_token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *setOrUpdateDeviceTokenRequest) setOrbitNodeKey(nodeKey string) {
|
|
|
|
r.OrbitNodeKey = nodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *setOrUpdateDeviceTokenRequest) orbitHostNodeKey() string {
|
|
|
|
return r.OrbitNodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type setOrUpdateDeviceTokenResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r setOrUpdateDeviceTokenResponse) error() error { return r.Err }
|
|
|
|
|
2022-12-27 14:26:59 +00:00
|
|
|
func setOrUpdateDeviceTokenEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
2022-10-10 20:15:35 +00:00
|
|
|
req := request.(*setOrUpdateDeviceTokenRequest)
|
|
|
|
if err := svc.SetOrUpdateDeviceAuthToken(ctx, req.DeviceAuthToken); err != nil {
|
|
|
|
return setOrUpdateDeviceTokenResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return setOrUpdateDeviceTokenResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) SetOrUpdateDeviceAuthToken(ctx context.Context, deviceAuthToken string) error {
|
|
|
|
// this is not a user-authenticated endpoint
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2023-12-28 20:20:36 +00:00
|
|
|
if len(deviceAuthToken) == 0 {
|
|
|
|
return badRequest("device auth token cannot be empty")
|
|
|
|
}
|
|
|
|
|
2022-10-10 20:15:35 +00:00
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [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.
- [X] 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:
- [X] 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)).~
2023-03-13 16:44:06 +00:00
|
|
|
return newOsqueryError("internal error: missing host from request context")
|
2022-10-10 20:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := svc.ds.SetOrUpdateDeviceAuthToken(ctx, host.ID, deviceAuthToken); err != nil {
|
2023-12-28 20:20:36 +00:00
|
|
|
if errors.As(err, &fleet.ConflictError{}) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return newOsqueryError(fmt.Sprintf("internal error: failed to set or update device auth token: %s", err))
|
2022-10-10 20:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-08-23 20:47:47 +00:00
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Get Orbit pending script execution request
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type orbitGetScriptRequest struct {
|
|
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
|
|
ExecutionID string `json:"execution_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by the OrbitClient
|
|
|
|
func (r *orbitGetScriptRequest) setOrbitNodeKey(nodeKey string) {
|
|
|
|
r.OrbitNodeKey = nodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by orbit authentication
|
|
|
|
func (r *orbitGetScriptRequest) orbitHostNodeKey() string {
|
|
|
|
return r.OrbitNodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type orbitGetScriptResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
*fleet.HostScriptResult
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r orbitGetScriptResponse) error() error { return r.Err }
|
|
|
|
|
|
|
|
func getOrbitScriptEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
|
|
req := request.(*orbitGetScriptRequest)
|
|
|
|
script, err := svc.GetHostScript(ctx, req.ExecutionID)
|
|
|
|
if err != nil {
|
|
|
|
return orbitGetScriptResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return orbitGetScriptResponse{HostScriptResult: script}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) GetHostScript(ctx context.Context, execID string) (*fleet.HostScriptResult, error) {
|
|
|
|
// skipauth: No authorization check needed due to implementation returning
|
|
|
|
// only license error.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
return nil, fleet.ErrMissingLicense
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Post Orbit script execution result
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type orbitPostScriptResultRequest struct {
|
|
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
|
|
*fleet.HostScriptResultPayload
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by the OrbitClient
|
|
|
|
func (r *orbitPostScriptResultRequest) setOrbitNodeKey(nodeKey string) {
|
|
|
|
r.OrbitNodeKey = nodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by orbit authentication
|
|
|
|
func (r *orbitPostScriptResultRequest) orbitHostNodeKey() string {
|
|
|
|
return r.OrbitNodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type orbitPostScriptResultResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r orbitPostScriptResultResponse) error() error { return r.Err }
|
|
|
|
|
|
|
|
func postOrbitScriptResultEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
|
|
req := request.(*orbitPostScriptResultRequest)
|
|
|
|
if err := svc.SaveHostScriptResult(ctx, req.HostScriptResultPayload); err != nil {
|
|
|
|
return orbitPostScriptResultResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return orbitPostScriptResultResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) SaveHostScriptResult(ctx context.Context, result *fleet.HostScriptResultPayload) error {
|
|
|
|
// skipauth: No authorization check needed due to implementation returning
|
|
|
|
// only license error.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
return fleet.ErrMissingLicense
|
|
|
|
}
|
2023-10-06 22:04:33 +00:00
|
|
|
|
2023-12-21 17:21:39 +00:00
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Post Orbit device mapping (custom email)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type orbitPutDeviceMappingRequest struct {
|
|
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by the OrbitClient
|
|
|
|
func (r *orbitPutDeviceMappingRequest) setOrbitNodeKey(nodeKey string) {
|
|
|
|
r.OrbitNodeKey = nodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by orbit authentication
|
|
|
|
func (r *orbitPutDeviceMappingRequest) orbitHostNodeKey() string {
|
|
|
|
return r.OrbitNodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type orbitPutDeviceMappingResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r orbitPutDeviceMappingResponse) error() error { return r.Err }
|
|
|
|
|
|
|
|
func putOrbitDeviceMappingEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
|
|
req := request.(*orbitPutDeviceMappingRequest)
|
|
|
|
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
err := newOsqueryError("internal error: missing host from request context")
|
|
|
|
return orbitPutDeviceMappingResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := svc.SetCustomHostDeviceMapping(ctx, host.ID, req.Email)
|
|
|
|
return orbitPutDeviceMappingResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
|
2023-10-06 22:04:33 +00:00
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Post Orbit disk encryption key
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
type orbitPostDiskEncryptionKeyRequest struct {
|
|
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
|
|
EncryptionKey []byte `json:"encryption_key"`
|
|
|
|
ClientError string `json:"client_error"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by the OrbitClient
|
|
|
|
func (r *orbitPostDiskEncryptionKeyRequest) setOrbitNodeKey(nodeKey string) {
|
|
|
|
r.OrbitNodeKey = nodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// interface implementation required by orbit authentication
|
|
|
|
func (r *orbitPostDiskEncryptionKeyRequest) orbitHostNodeKey() string {
|
|
|
|
return r.OrbitNodeKey
|
|
|
|
}
|
|
|
|
|
|
|
|
type orbitPostDiskEncryptionKeyResponse struct {
|
|
|
|
Err error `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r orbitPostDiskEncryptionKeyResponse) error() error { return r.Err }
|
|
|
|
func (r orbitPostDiskEncryptionKeyResponse) Status() int { return http.StatusNoContent }
|
|
|
|
|
|
|
|
func postOrbitDiskEncryptionKeyEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
|
|
|
req := request.(*orbitPostDiskEncryptionKeyRequest)
|
|
|
|
if err := svc.SetOrUpdateDiskEncryptionKey(ctx, string(req.EncryptionKey), req.ClientError); err != nil {
|
|
|
|
return orbitPostDiskEncryptionKeyResponse{Err: err}, nil
|
|
|
|
}
|
|
|
|
return orbitPostDiskEncryptionKeyResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) SetOrUpdateDiskEncryptionKey(ctx context.Context, encryptionKey, clientError string) error {
|
|
|
|
// this is not a user-authenticated endpoint
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
return newOsqueryError("internal error: missing host from request context")
|
|
|
|
}
|
|
|
|
if !host.MDMInfo.IsFleetEnrolled() {
|
|
|
|
return badRequest("host is not enrolled with fleet")
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
encryptedEncryptionKey string
|
|
|
|
decryptable *bool
|
|
|
|
)
|
|
|
|
|
|
|
|
// only set the encryption key if there was no client error
|
|
|
|
if clientError == "" && encryptionKey != "" {
|
|
|
|
wstepCert, _, _, err := svc.config.MDM.MicrosoftWSTEP()
|
|
|
|
if err != nil {
|
|
|
|
// should never return an error because the WSTEP is first parsed and
|
|
|
|
// cached at the start of the fleet serve process.
|
|
|
|
return ctxerr.Wrap(ctx, err, "get WSTEP certificate")
|
|
|
|
}
|
|
|
|
enc, err := microsoft_mdm.Encrypt(encryptionKey, wstepCert.Leaf)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "encrypt the key with WSTEP certificate")
|
|
|
|
}
|
|
|
|
encryptedEncryptionKey = enc
|
|
|
|
decryptable = ptr.Bool(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := svc.ds.SetOrUpdateHostDiskEncryptionKey(ctx, host.ID, encryptedEncryptionKey, clientError, decryptable); err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "set or update disk encryption key")
|
|
|
|
}
|
2023-10-13 21:05:03 +00:00
|
|
|
|
2023-10-06 22:04:33 +00:00
|
|
|
return nil
|
|
|
|
}
|