fleet/server/service/orbit_client.go

371 lines
10 KiB
Go
Raw Normal View History

package service
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"runtime"
"sync"
"time"
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
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/orbit/pkg/logging"
"github.com/fleetdm/fleet/v4/orbit/pkg/platform"
"github.com/fleetdm/fleet/v4/pkg/retry"
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
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/rs/zerolog/log"
)
// OrbitClient exposes the Orbit API to communicate with the Fleet server.
type OrbitClient struct {
*baseClient
nodeKeyFilePath string
enrollSecret string
Orbit to set `--database_path` when invoking osquery to retrieve system info (#10308) #9132 The actual fix for the empty hosts is adding the `--database_path` argument in the initial `osqueryd -S` invocation when retrieving the UUID. Osquery attempts to retrieve the UUID from OS files/APIs, when not possible (which is what happens on some linux distributions), then it resorts to generating a new random UUID and storing it in the `osquery.db`. The issue was Orbit's first invocation of `osqueryd -S` was not using the same `osquery.db` as the main daemon invocation of `osqueryd`. I'm also adding a `hostname` + `platform` to the orbit enroll phase so that if there are any issues in the future we can avoid the "empty" host and have some information to help us troubleshoot. ## How to reproduce On Linux, osquery reads `/sys/class/dmi/id/product_uuid` to load the hardware UUID. Some Linux distributions running on specific hardware or container environments do not have such file available. The way to reproduce on a Linux VM is to do the following: ```sh $ sudo su # chmod -r /sys/class/dmi/id/product_uuid ``` which will turn the file inaccessible to root. ## Checklist - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] 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. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2023-03-13 21:54:18 +00:00
hostInfo fleet.OrbitHostInfo
enrolledMu sync.Mutex
enrolled bool
lastRecordedErrMu sync.Mutex
lastRecordedErr error
configCache configCache
// TestNodeKey is used for testing only.
TestNodeKey string
}
// time-to-live for config cache
const configCacheTTL = 3 * time.Second
type configCache struct {
mu sync.Mutex
lastUpdated time.Time
config *fleet.OrbitConfig
err error
}
func (oc *OrbitClient) request(verb string, path string, params interface{}, resp interface{}) error {
var bodyBytes []byte
var err error
if params != nil {
bodyBytes, err = json.Marshal(params)
if err != nil {
return fmt.Errorf("making request json marshalling : %w", err)
}
}
request, err := http.NewRequest(
verb,
oc.url(path, "").String(),
bytes.NewBuffer(bodyBytes),
)
if err != nil {
return err
}
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
oc.setClientCapabilitiesHeader(request)
response, err := oc.http.Do(request)
if err != nil {
oc.setLastRecordedError(err)
return fmt.Errorf("%s %s: %w", verb, path, err)
}
defer response.Body.Close()
if err := oc.parseResponse(verb, path, response, resp); err != nil {
oc.setLastRecordedError(err)
return err
}
return nil
}
// NewOrbitClient creates a new OrbitClient.
//
Orbit to set `--database_path` when invoking osquery to retrieve system info (#10308) #9132 The actual fix for the empty hosts is adding the `--database_path` argument in the initial `osqueryd -S` invocation when retrieving the UUID. Osquery attempts to retrieve the UUID from OS files/APIs, when not possible (which is what happens on some linux distributions), then it resorts to generating a new random UUID and storing it in the `osquery.db`. The issue was Orbit's first invocation of `osqueryd -S` was not using the same `osquery.db` as the main daemon invocation of `osqueryd`. I'm also adding a `hostname` + `platform` to the orbit enroll phase so that if there are any issues in the future we can avoid the "empty" host and have some information to help us troubleshoot. ## How to reproduce On Linux, osquery reads `/sys/class/dmi/id/product_uuid` to load the hardware UUID. Some Linux distributions running on specific hardware or container environments do not have such file available. The way to reproduce on a Linux VM is to do the following: ```sh $ sudo su # chmod -r /sys/class/dmi/id/product_uuid ``` which will turn the file inaccessible to root. ## Checklist - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] 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. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2023-03-13 21:54:18 +00:00
// - rootDir is the Orbit's root directory, where the Orbit node key is loaded-from/stored.
// - addr is the address of the Fleet server.
// - orbitHostInfo is the host system information used for enrolling to Fleet.
func NewOrbitClient(
rootDir string,
addr string,
rootCA string,
insecureSkipVerify bool,
enrollSecret string,
fleetClientCert *tls.Certificate,
Orbit to set `--database_path` when invoking osquery to retrieve system info (#10308) #9132 The actual fix for the empty hosts is adding the `--database_path` argument in the initial `osqueryd -S` invocation when retrieving the UUID. Osquery attempts to retrieve the UUID from OS files/APIs, when not possible (which is what happens on some linux distributions), then it resorts to generating a new random UUID and storing it in the `osquery.db`. The issue was Orbit's first invocation of `osqueryd -S` was not using the same `osquery.db` as the main daemon invocation of `osqueryd`. I'm also adding a `hostname` + `platform` to the orbit enroll phase so that if there are any issues in the future we can avoid the "empty" host and have some information to help us troubleshoot. ## How to reproduce On Linux, osquery reads `/sys/class/dmi/id/product_uuid` to load the hardware UUID. Some Linux distributions running on specific hardware or container environments do not have such file available. The way to reproduce on a Linux VM is to do the following: ```sh $ sudo su # chmod -r /sys/class/dmi/id/product_uuid ``` which will turn the file inaccessible to root. ## Checklist - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] 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. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2023-03-13 21:54:18 +00:00
orbitHostInfo fleet.OrbitHostInfo,
) (*OrbitClient, error) {
orbitCapabilities := fleet.CapabilityMap{}
bc, err := newBaseClient(addr, insecureSkipVerify, rootCA, "", fleetClientCert, orbitCapabilities)
if err != nil {
return nil, err
}
nodeKeyFilePath := filepath.Join(rootDir, constant.OrbitNodeKeyFileName)
return &OrbitClient{
nodeKeyFilePath: nodeKeyFilePath,
baseClient: bc,
enrollSecret: enrollSecret,
Orbit to set `--database_path` when invoking osquery to retrieve system info (#10308) #9132 The actual fix for the empty hosts is adding the `--database_path` argument in the initial `osqueryd -S` invocation when retrieving the UUID. Osquery attempts to retrieve the UUID from OS files/APIs, when not possible (which is what happens on some linux distributions), then it resorts to generating a new random UUID and storing it in the `osquery.db`. The issue was Orbit's first invocation of `osqueryd -S` was not using the same `osquery.db` as the main daemon invocation of `osqueryd`. I'm also adding a `hostname` + `platform` to the orbit enroll phase so that if there are any issues in the future we can avoid the "empty" host and have some information to help us troubleshoot. ## How to reproduce On Linux, osquery reads `/sys/class/dmi/id/product_uuid` to load the hardware UUID. Some Linux distributions running on specific hardware or container environments do not have such file available. The way to reproduce on a Linux VM is to do the following: ```sh $ sudo su # chmod -r /sys/class/dmi/id/product_uuid ``` which will turn the file inaccessible to root. ## Checklist - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] 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. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2023-03-13 21:54:18 +00:00
hostInfo: orbitHostInfo,
enrolled: false,
}, nil
}
// GetConfig returns the Orbit config fetched from Fleet server for this instance of OrbitClient.
// Since this method is called in multiple places, we use a cache with configCacheTTL time-to-live to reduce traffic to the Fleet server.
func (oc *OrbitClient) GetConfig() (*fleet.OrbitConfig, error) {
oc.configCache.mu.Lock()
defer oc.configCache.mu.Unlock()
// If time-to-live passed, we update the config cache
now := time.Now()
if now.After(oc.configCache.lastUpdated.Add(configCacheTTL)) {
verb, path := "POST", "/api/fleet/orbit/config"
var resp fleet.OrbitConfig
err := oc.authenticatedRequest(verb, path, &orbitGetConfigRequest{}, &resp)
oc.configCache.config = &resp
oc.configCache.err = err
oc.configCache.lastUpdated = now
}
return oc.configCache.config, oc.configCache.err
}
// SetOrUpdateDeviceToken sends a request to the server to set or update the
// device token with the given value.
func (oc *OrbitClient) SetOrUpdateDeviceToken(deviceAuthToken string) error {
verb, path := "POST", "/api/fleet/orbit/device_token"
params := setOrUpdateDeviceTokenRequest{
DeviceAuthToken: deviceAuthToken,
}
var resp setOrUpdateDeviceTokenResponse
if err := oc.authenticatedRequest(verb, path, &params, &resp); err != nil {
return err
}
return nil
}
// GetHostScript returns the script fetched from Fleet server to run on this
// host.
func (oc *OrbitClient) GetHostScript(execID string) (*fleet.HostScriptResult, error) {
verb, path := "POST", "/api/fleet/orbit/scripts/request"
var resp orbitGetScriptResponse
if err := oc.authenticatedRequest(verb, path, &orbitGetScriptRequest{
ExecutionID: execID,
}, &resp); err != nil {
return nil, err
}
return resp.HostScriptResult, nil
}
// SaveHostScriptResult saves the result of running the script on this host.
func (oc *OrbitClient) SaveHostScriptResult(result *fleet.HostScriptResultPayload) error {
verb, path := "POST", "/api/fleet/orbit/scripts/result"
var resp orbitPostScriptResultResponse
if err := oc.authenticatedRequest(verb, path, &orbitPostScriptResultRequest{
HostScriptResultPayload: result,
}, &resp); err != nil {
return err
}
return nil
}
// Ping sends a ping request to the orbit/ping endpoint.
func (oc *OrbitClient) Ping() error {
verb, path := "HEAD", "/api/fleet/orbit/ping"
err := oc.request(verb, path, nil, nil)
if err == nil || errors.Is(err, notFoundErr{}) {
// notFound is ok, it means an old server without the capabilities header
return nil
}
return err
}
func (oc *OrbitClient) enroll() (string, error) {
verb, path := "POST", "/api/fleet/orbit/enroll"
Orbit to set `--database_path` when invoking osquery to retrieve system info (#10308) #9132 The actual fix for the empty hosts is adding the `--database_path` argument in the initial `osqueryd -S` invocation when retrieving the UUID. Osquery attempts to retrieve the UUID from OS files/APIs, when not possible (which is what happens on some linux distributions), then it resorts to generating a new random UUID and storing it in the `osquery.db`. The issue was Orbit's first invocation of `osqueryd -S` was not using the same `osquery.db` as the main daemon invocation of `osqueryd`. I'm also adding a `hostname` + `platform` to the orbit enroll phase so that if there are any issues in the future we can avoid the "empty" host and have some information to help us troubleshoot. ## How to reproduce On Linux, osquery reads `/sys/class/dmi/id/product_uuid` to load the hardware UUID. Some Linux distributions running on specific hardware or container environments do not have such file available. The way to reproduce on a Linux VM is to do the following: ```sh $ sudo su # chmod -r /sys/class/dmi/id/product_uuid ``` which will turn the file inaccessible to root. ## Checklist - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] 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. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2023-03-13 21:54:18 +00:00
params := EnrollOrbitRequest{
EnrollSecret: oc.enrollSecret,
HardwareUUID: oc.hostInfo.HardwareUUID,
HardwareSerial: oc.hostInfo.HardwareSerial,
Hostname: oc.hostInfo.Hostname,
Platform: oc.hostInfo.Platform,
OsqueryIdentifier: oc.hostInfo.OsqueryIdentifier,
Orbit to set `--database_path` when invoking osquery to retrieve system info (#10308) #9132 The actual fix for the empty hosts is adding the `--database_path` argument in the initial `osqueryd -S` invocation when retrieving the UUID. Osquery attempts to retrieve the UUID from OS files/APIs, when not possible (which is what happens on some linux distributions), then it resorts to generating a new random UUID and storing it in the `osquery.db`. The issue was Orbit's first invocation of `osqueryd -S` was not using the same `osquery.db` as the main daemon invocation of `osqueryd`. I'm also adding a `hostname` + `platform` to the orbit enroll phase so that if there are any issues in the future we can avoid the "empty" host and have some information to help us troubleshoot. ## How to reproduce On Linux, osquery reads `/sys/class/dmi/id/product_uuid` to load the hardware UUID. Some Linux distributions running on specific hardware or container environments do not have such file available. The way to reproduce on a Linux VM is to do the following: ```sh $ sudo su # chmod -r /sys/class/dmi/id/product_uuid ``` which will turn the file inaccessible to root. ## Checklist - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] 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. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2023-03-13 21:54:18 +00:00
}
var resp EnrollOrbitResponse
err := oc.request(verb, path, params, &resp)
if err != nil {
return "", err
}
return resp.OrbitNodeKey, nil
}
// enrollLock helps protect the enrolling process in case mutliple OrbitClients
// want to re-enroll at the same time.
var enrollLock sync.Mutex
// getNodeKeyOrEnroll attempts to read the orbit node key if the file exists on disk
// otherwise it enrolls the host with Fleet and saves the node key to disk
func (oc *OrbitClient) getNodeKeyOrEnroll() (string, error) {
if oc.TestNodeKey != "" {
return oc.TestNodeKey, nil
}
enrollLock.Lock()
defer enrollLock.Unlock()
orbitNodeKey, err := os.ReadFile(oc.nodeKeyFilePath)
switch {
case err == nil:
return string(orbitNodeKey), nil
case errors.Is(err, fs.ErrNotExist):
// OK, if there's no orbit node key, proceed to enroll.
default:
return "", fmt.Errorf("read orbit node key file: %w", err)
}
var (
orbitNodeKey_ string
endpointDoesNotExist bool
)
if err := retry.Do(
func() error {
var err error
orbitNodeKey_, err = oc.enrollAndWriteNodeKeyFile()
switch {
case err == nil:
return nil
case errors.Is(err, notFoundErr{}):
// Do not retry if the endpoint does not exist.
endpointDoesNotExist = true
return nil
default:
logging.LogErrIfEnvNotSet(constant.SilenceEnrollLogErrorEnvVar, err, "enroll failed, retrying")
return err
}
},
retry.WithInterval(OrbitRetryInterval()),
retry.WithMaxAttempts(constant.OrbitEnrollMaxRetries),
); err != nil {
return "", fmt.Errorf("orbit node key enroll failed, attempts=%d", constant.OrbitEnrollMaxRetries)
}
if endpointDoesNotExist {
return "", errors.New("enroll endpoint does not exist")
}
return orbitNodeKey_, nil
}
// GetNodeKey gets the orbit node key from file.
func (oc *OrbitClient) GetNodeKey() (string, error) {
orbitNodeKey, err := os.ReadFile(oc.nodeKeyFilePath)
if err != nil {
return "", err
}
return string(orbitNodeKey), nil
}
func (oc *OrbitClient) enrollAndWriteNodeKeyFile() (string, error) {
orbitNodeKey, err := oc.enroll()
if err != nil {
return "", fmt.Errorf("enroll request: %w", err)
}
if runtime.GOOS == "windows" {
// creating the secret file with empty content
if err := os.WriteFile(oc.nodeKeyFilePath, nil, constant.DefaultFileMode); err != nil {
return "", fmt.Errorf("create orbit node key file: %w", err)
}
// restricting file access
if err := platform.ChmodRestrictFile(oc.nodeKeyFilePath); err != nil {
return "", fmt.Errorf("apply ACLs: %w", err)
}
}
// writing raw key material to the acl-ready secret file
if err := os.WriteFile(oc.nodeKeyFilePath, []byte(orbitNodeKey), constant.DefaultFileMode); err != nil {
return "", fmt.Errorf("write orbit node key file: %w", err)
}
return orbitNodeKey, nil
}
func (oc *OrbitClient) authenticatedRequest(verb string, path string, params interface{}, resp interface{}) error {
nodeKey, err := oc.getNodeKeyOrEnroll()
if err != nil {
return err
}
s := params.(setOrbitNodeKeyer)
s.setOrbitNodeKey(nodeKey)
err = oc.request(verb, path, params, resp)
switch {
case err == nil:
oc.setEnrolled(true)
return nil
case errors.Is(err, ErrUnauthenticated):
if err := os.Remove(oc.nodeKeyFilePath); err != nil {
log.Info().Err(err).Msg("remove orbit node key")
}
oc.setEnrolled(false)
return err
default:
return err
}
}
func (oc *OrbitClient) Enrolled() bool {
oc.enrolledMu.Lock()
defer oc.enrolledMu.Unlock()
return oc.enrolled
}
func (oc *OrbitClient) setEnrolled(v bool) {
oc.enrolledMu.Lock()
defer oc.enrolledMu.Unlock()
oc.enrolled = v
}
func (oc *OrbitClient) LastRecordedError() error {
oc.lastRecordedErrMu.Lock()
defer oc.lastRecordedErrMu.Unlock()
return oc.lastRecordedErr
}
func (oc *OrbitClient) setLastRecordedError(err error) {
oc.lastRecordedErrMu.Lock()
defer oc.lastRecordedErrMu.Unlock()
oc.lastRecordedErr = fmt.Errorf("%s: %w", time.Now().UTC().Format("2006-01-02T15:04:05Z"), err)
}
func OrbitRetryInterval() time.Duration {
interval := os.Getenv("FLEETD_ENROLL_RETRY_INTERVAL")
if interval != "" {
d, err := time.ParseDuration(interval)
if err == nil {
return d
}
}
return constant.OrbitEnrollRetrySleep
}
// SetOrUpdateDiskEncryptionKey sends a request to the server to set or update the disk
// encryption keys and result of the encryption process
func (oc *OrbitClient) SetOrUpdateDiskEncryptionKey(diskEncryptionStatus fleet.OrbitHostDiskEncryptionKeyPayload) error {
verb, path := "POST", "/api/fleet/orbit/disk_encryption_key"
var resp orbitPostDiskEncryptionKeyResponse
if err := oc.authenticatedRequest(verb, path, &orbitPostDiskEncryptionKeyRequest{
EncryptionKey: diskEncryptionStatus.EncryptionKey,
ClientError: diskEncryptionStatus.ClientError,
}, &resp); err != nil {
return err
}
return nil
}