2022-06-01 23:05:05 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/tls"
|
|
|
|
|
"crypto/x509"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2022-12-23 16:21:49 +00:00
|
|
|
"io"
|
2024-05-14 20:25:35 +00:00
|
|
|
"mime"
|
2022-06-01 23:05:05 +00:00
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
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
|
|
|
"os"
|
2024-05-14 20:25:35 +00:00
|
|
|
"path/filepath"
|
2022-06-01 23:05:05 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
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"
|
2024-05-14 20:25:35 +00:00
|
|
|
"github.com/google/uuid"
|
2022-06-01 23:05:05 +00:00
|
|
|
)
|
|
|
|
|
|
2022-10-28 17:27:21 +00:00
|
|
|
var errInvalidScheme = errors.New("address must start with https:// for remote connections")
|
|
|
|
|
|
2022-06-01 23:05:05 +00:00
|
|
|
// httpClient interface allows the HTTP methods to be mocked.
|
|
|
|
|
type httpClient interface {
|
|
|
|
|
Do(req *http.Request) (*http.Response, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type baseClient struct {
|
|
|
|
|
baseURL *url.URL
|
|
|
|
|
http httpClient
|
|
|
|
|
urlPrefix string
|
|
|
|
|
insecureSkipVerify bool
|
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
|
|
|
// serverCapabilities is a map of capabilities that the server supports.
|
|
|
|
|
// This map is updated on each response we receive from the server.
|
|
|
|
|
serverCapabilities fleet.CapabilityMap
|
|
|
|
|
// clientCapabilities is a map of capabilities that the client supports.
|
|
|
|
|
// This list is given when the client is instantiated and shouldn't be
|
|
|
|
|
// modified afterwards.
|
|
|
|
|
clientCapabilities fleet.CapabilityMap
|
2022-06-01 23:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-25 00:17:20 +00:00
|
|
|
// parseResponse processes the status code and parses the response body.
|
|
|
|
|
// It does not close the response body (should be closed by the caller).
|
2022-06-01 23:05:05 +00:00
|
|
|
func (bc *baseClient) parseResponse(verb, path string, response *http.Response, responseDest interface{}) error {
|
|
|
|
|
switch response.StatusCode {
|
|
|
|
|
case http.StatusNotFound:
|
2023-07-25 00:17:20 +00:00
|
|
|
return notFoundErr{
|
|
|
|
|
msg: extractServerErrorText(response.Body),
|
|
|
|
|
}
|
2022-06-01 23:05:05 +00:00
|
|
|
case http.StatusUnauthorized:
|
2025-03-17 16:44:59 +00:00
|
|
|
errText := extractServerErrorText(response.Body)
|
|
|
|
|
if strings.Contains(errText, "password reset required") {
|
|
|
|
|
return ErrPasswordResetRequired
|
|
|
|
|
}
|
End-user authentication for Window/Linux setup experience: agent (#34847)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #34528
# Details
This PR implements the agent changes for allowing Fleet admins to
require that users authenticate with an IdP prior to having their
devices set up. I'll comment on changes inline but the high-level is:
1. Orbit calls the enroll endpoint as usual. This is triggered lazily by
any one of a number of subsystems like device token rotation or
requesting Fleet config
2. If the enroll endpoint returns the new `ErrEndUserAuthRequired`
response, then it opens a window to the `/mdm/sso` Fleet page and
retries the enroll endpoint every 30 seconds indefinitely.
3. Any other non-200 response to the enroll request is treated as before
(limited # of retries, with backoff)
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-
changes.md#changes-files) for more information.
Will add changelog when story is one.
## Testing
- [X] Added/updated automated tests
Added test for new retry logic
- [X] QA'd all new/changed functionality manually
This is kinda hard to test without the associated backend PR:
https://github.com/fleetdm/fleet/pull/34835
## fleetd/orbit/Fleet Desktop
- [X] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
This is compatible with all Fleet versions, since older ones won't send
the new error.
- [X] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
This is compatible with all platforms, although it currently should only
ever run on Windows and Linux since macOS devices will have end-user
auth taken care of before they even download Orbit.
- [ ] Verified that fleetd runs on macOS, Linux and Windows
Testing this now.
- [ ] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added SSO (Single Sign-On) enrollment support for end-user
authentication
* Enhanced error messaging for authentication-required scenarios
* **Bug Fixes**
* Improved error handling and retry logic for enrollment failures
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-03 22:41:57 +00:00
|
|
|
if strings.Contains(errText, "END_USER_AUTH_REQUIRED") {
|
|
|
|
|
return ErrEndUserAuthRequired
|
|
|
|
|
}
|
2022-06-01 23:05:05 +00:00
|
|
|
return ErrUnauthenticated
|
|
|
|
|
case http.StatusPaymentRequired:
|
|
|
|
|
return ErrMissingLicense
|
|
|
|
|
default:
|
2023-02-15 18:01:44 +00:00
|
|
|
if response.StatusCode >= 200 && response.StatusCode < 300 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-03 18:25:49 +00:00
|
|
|
e := &statusCodeErr{
|
|
|
|
|
code: response.StatusCode,
|
|
|
|
|
body: extractServerErrorText(response.Body),
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("%s %s received status %w", verb, path, e)
|
2022-06-01 23:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
2022-09-26 14:44:09 +00:00
|
|
|
bc.setServerCapabilities(response)
|
|
|
|
|
|
2024-05-14 20:25:35 +00:00
|
|
|
if responseDest != nil {
|
|
|
|
|
if e, ok := responseDest.(bodyHandler); ok {
|
|
|
|
|
if err := e.Handle(response); err != nil {
|
|
|
|
|
return fmt.Errorf("%s %s error with custom body handler contents: %w", verb, path, err)
|
|
|
|
|
}
|
|
|
|
|
} else if response.StatusCode != http.StatusNoContent {
|
|
|
|
|
b, err := io.ReadAll(response.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("reading response body: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(b, &responseDest); err != nil {
|
2025-09-19 22:00:19 +00:00
|
|
|
const maxBodyLen = 200
|
|
|
|
|
truncatedBytes, isHTML := truncateAndDetectHTML(b, maxBodyLen)
|
|
|
|
|
|
|
|
|
|
if isHTML {
|
|
|
|
|
return fmt.Errorf("decode %s %s response: %w, (server returned HTML instead of JSON), body: %s", verb, path, err, truncatedBytes)
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("decode %s %s response: %w, body: %s", verb, path, err, truncatedBytes)
|
2024-05-14 20:25:35 +00:00
|
|
|
}
|
2025-02-14 22:19:34 +00:00
|
|
|
if e, ok := responseDest.(fleet.Errorer); ok {
|
2025-02-03 17:23:26 +00:00
|
|
|
if e.Error() != nil {
|
|
|
|
|
return fmt.Errorf("%s %s error: %w", verb, path, e.Error())
|
2024-05-14 20:25:35 +00:00
|
|
|
}
|
2022-09-26 14:44:09 +00:00
|
|
|
}
|
2022-06-01 23:05:05 +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
|
|
|
bc.setServerCapabilities(response)
|
|
|
|
|
|
2022-06-01 23:05:05 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bc *baseClient) url(path, rawQuery string) *url.URL {
|
|
|
|
|
u := *bc.baseURL
|
|
|
|
|
u.Path = bc.urlPrefix + path
|
|
|
|
|
u.RawQuery = rawQuery
|
|
|
|
|
return &u
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// setServerCapabilities updates the server capabilities based on the response
|
|
|
|
|
// from the server.
|
|
|
|
|
func (bc *baseClient) setServerCapabilities(response *http.Response) {
|
|
|
|
|
capabilities := response.Header.Get(fleet.CapabilitiesHeader)
|
|
|
|
|
bc.serverCapabilities.PopulateFromString(capabilities)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-26 14:44:09 +00:00
|
|
|
func (bc *baseClient) GetServerCapabilities() fleet.CapabilityMap {
|
|
|
|
|
return bc.serverCapabilities
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setClientCapabilities header is used to set a header with the client
|
|
|
|
|
// capabilities in the given request.
|
|
|
|
|
//
|
|
|
|
|
// This method is defined in baseClient because other clients generally have
|
|
|
|
|
// custom implementations of a method to perform the requests to the server.
|
|
|
|
|
func (bc *baseClient) setClientCapabilitiesHeader(req *http.Request) {
|
|
|
|
|
if len(bc.clientCapabilities) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.Header == nil {
|
|
|
|
|
req.Header = http.Header{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req.Header.Set(fleet.CapabilitiesHeader, bc.clientCapabilities.String())
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 11:44:39 +00:00
|
|
|
func newBaseClient(
|
|
|
|
|
addr string,
|
|
|
|
|
insecureSkipVerify bool,
|
|
|
|
|
rootCA, urlPrefix string,
|
|
|
|
|
fleetClientCert *tls.Certificate,
|
|
|
|
|
capabilities fleet.CapabilityMap,
|
2025-07-18 13:19:05 +00:00
|
|
|
signerWrapper func(*http.Client) *http.Client,
|
2023-04-27 11:44:39 +00:00
|
|
|
) (*baseClient, error) {
|
2022-06-01 23:05:05 +00:00
|
|
|
baseURL, err := url.Parse(addr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("parsing URL: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-28 17:27:21 +00:00
|
|
|
allowHTTP := insecureSkipVerify || strings.Contains(baseURL.Host, "localhost") || strings.Contains(baseURL.Host, "127.0.0.1")
|
|
|
|
|
if baseURL.Scheme != "https" && !allowHTTP {
|
|
|
|
|
return nil, errInvalidScheme
|
2022-06-01 23:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rootCAPool := x509.NewCertPool()
|
2022-06-21 19:25:36 +00:00
|
|
|
|
|
|
|
|
tlsConfig := &tls.Config{
|
|
|
|
|
// Osquery itself requires >= TLS 1.2.
|
|
|
|
|
// https://github.com/osquery/osquery/blob/9713ad9e28f1cfe6c16a823fb88bd531e39e192d/osquery/remote/transports/tls.cpp#L97-L98
|
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 11:44:39 +00:00
|
|
|
if fleetClientCert != nil {
|
|
|
|
|
tlsConfig.Certificates = []tls.Certificate{*fleetClientCert}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-21 19:25:36 +00:00
|
|
|
switch {
|
|
|
|
|
case rootCA != "":
|
2022-06-01 23:05:05 +00:00
|
|
|
// read in the root cert file specified in the context
|
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
|
|
|
certs, err := os.ReadFile(rootCA)
|
2022-06-01 23:05:05 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("reading root CA: %w", err)
|
|
|
|
|
}
|
|
|
|
|
// add certs to pool
|
|
|
|
|
if ok := rootCAPool.AppendCertsFromPEM(certs); !ok {
|
|
|
|
|
return nil, errors.New("failed to add certificates to root CA pool")
|
|
|
|
|
}
|
2022-06-21 19:25:36 +00:00
|
|
|
tlsConfig.RootCAs = rootCAPool
|
|
|
|
|
case insecureSkipVerify:
|
|
|
|
|
// Ignoring "G402: TLS InsecureSkipVerify set true", needed for development/testing.
|
|
|
|
|
tlsConfig.InsecureSkipVerify = true //nolint:gosec
|
|
|
|
|
default:
|
2022-06-01 23:05:05 +00:00
|
|
|
rootCAPool, err = x509.SystemCertPool()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("loading system cert pool: %w", err)
|
|
|
|
|
}
|
2022-06-21 19:25:36 +00:00
|
|
|
tlsConfig.RootCAs = rootCAPool
|
2022-06-01 23:05:05 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-21 19:25:36 +00:00
|
|
|
httpClient := fleethttp.NewClient(fleethttp.WithTLSClientConfig(tlsConfig))
|
2025-07-18 13:19:05 +00:00
|
|
|
if signerWrapper != nil {
|
|
|
|
|
httpClient = signerWrapper(httpClient)
|
|
|
|
|
}
|
2022-06-01 23:05:05 +00:00
|
|
|
client := &baseClient{
|
|
|
|
|
baseURL: baseURL,
|
|
|
|
|
http: httpClient,
|
|
|
|
|
insecureSkipVerify: insecureSkipVerify,
|
|
|
|
|
urlPrefix: urlPrefix,
|
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
|
|
|
clientCapabilities: capabilities,
|
2022-09-26 14:44:09 +00:00
|
|
|
serverCapabilities: fleet.CapabilityMap{},
|
2022-06-01 23:05:05 +00:00
|
|
|
}
|
|
|
|
|
return client, nil
|
|
|
|
|
}
|
2024-05-14 20:25:35 +00:00
|
|
|
|
|
|
|
|
type bodyHandler interface {
|
|
|
|
|
Handle(*http.Response) error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FileResponse struct {
|
2025-01-29 16:24:44 +00:00
|
|
|
DestPath string
|
|
|
|
|
DestFile string
|
|
|
|
|
destFilePath string
|
|
|
|
|
SkipMediaType bool
|
2025-03-20 14:09:57 +00:00
|
|
|
ProgressFunc func(n int)
|
2024-05-14 20:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *FileResponse) Handle(resp *http.Response) error {
|
2025-01-29 16:24:44 +00:00
|
|
|
var filename string
|
|
|
|
|
if !f.SkipMediaType {
|
|
|
|
|
_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("parsing media type from response header: %w", err)
|
|
|
|
|
}
|
|
|
|
|
filename = params["filename"]
|
2024-05-14 20:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-29 16:24:44 +00:00
|
|
|
if filename == "" {
|
|
|
|
|
filename = f.DestFile
|
|
|
|
|
}
|
2024-05-14 20:25:35 +00:00
|
|
|
if filename == "" {
|
|
|
|
|
filename = uuid.NewString()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f.destFilePath = filepath.Join(f.DestPath, filename)
|
|
|
|
|
destFile, err := os.Create(f.destFilePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("creating file: %w", err)
|
|
|
|
|
}
|
|
|
|
|
defer destFile.Close()
|
|
|
|
|
|
2025-03-20 14:09:57 +00:00
|
|
|
var respBodyReader io.Reader = resp.Body
|
|
|
|
|
if f.ProgressFunc != nil {
|
|
|
|
|
respBodyReader = &progressReader{
|
|
|
|
|
Reader: respBodyReader,
|
|
|
|
|
progressFunc: f.ProgressFunc,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = io.Copy(destFile, respBodyReader)
|
2024-05-14 20:25:35 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("copying from http stream to file: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := destFile.Close(); err != nil {
|
|
|
|
|
return fmt.Errorf("closing file after copy: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *FileResponse) GetFilePath() string {
|
|
|
|
|
return f.destFilePath
|
|
|
|
|
}
|
2025-03-20 14:09:57 +00:00
|
|
|
|
|
|
|
|
type progressReader struct {
|
|
|
|
|
io.Reader
|
|
|
|
|
progressFunc func(n int)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (pr *progressReader) Read(p []byte) (int, error) {
|
|
|
|
|
n, err := pr.Reader.Read(p)
|
|
|
|
|
pr.progressFunc(n)
|
|
|
|
|
return n, err
|
|
|
|
|
}
|