Revert token rotation (#7628)

This reverts all changes related to token rotation.
This commit is contained in:
Roberto Dip
2022-09-08 15:04:02 -03:00
committed by GitHub
parent abc131575b
commit 740aafd817
26 changed files with 196 additions and 900 deletions
+20 -51
View File
@@ -13,18 +13,16 @@ import (
)
const (
appConfigKey = "AppConfig:%s"
defaultAppConfigExpiration = 1 * time.Second
packsHostKey = "Packs:host:%d"
defaultPacksExpiration = 1 * time.Minute
scheduledQueriesKey = "ScheduledQueries:pack:%d"
defaultScheduledQueriesExpiration = 1 * time.Minute
teamAgentOptionsKey = "TeamAgentOptions:team:%d"
defaultTeamAgentOptionsExpiration = 1 * time.Minute
teamFeaturesKey = "TeamFeatures:team:%d"
defaultTeamFeaturesExpiration = 1 * time.Minute
hostDeviceAuthTokenKey = "HostDeviceAuthToken:%d"
defaultHostDeviceAuthTokenExpiration = 10 * time.Minute
appConfigKey = "AppConfig:%s"
defaultAppConfigExpiration = 1 * time.Second
packsHostKey = "Packs:host:%d"
defaultPacksExpiration = 1 * time.Minute
scheduledQueriesKey = "ScheduledQueries:pack:%d"
defaultScheduledQueriesExpiration = 1 * time.Minute
teamAgentOptionsKey = "TeamAgentOptions:team:%d"
defaultTeamAgentOptionsExpiration = 1 * time.Minute
teamFeaturesKey = "TeamFeatures:team:%d"
defaultTeamFeaturesExpiration = 1 * time.Minute
)
// cloner represents any type that can clone itself. Used by types to provide a more efficient clone method.
@@ -97,11 +95,10 @@ type cachedMysql struct {
c *cloneCache
packsExp time.Duration
scheduledQueriesExp time.Duration
teamAgentOptionsExp time.Duration
teamFeaturesExp time.Duration
hostDeviceAuthTokenExp time.Duration
packsExp time.Duration
scheduledQueriesExp time.Duration
teamAgentOptionsExp time.Duration
teamFeaturesExp time.Duration
}
type Option func(*cachedMysql)
@@ -130,21 +127,14 @@ func WithTeamFeaturesExpiration(d time.Duration) Option {
}
}
func WithHostDeviceAuthTokenExpiration(d time.Duration) Option {
return func(o *cachedMysql) {
o.hostDeviceAuthTokenExp = d
}
}
func New(ds fleet.Datastore, opts ...Option) fleet.Datastore {
c := &cachedMysql{
Datastore: ds,
c: &cloneCache{cache.New(5*time.Minute, 10*time.Minute)},
packsExp: defaultPacksExpiration,
scheduledQueriesExp: defaultScheduledQueriesExpiration,
teamAgentOptionsExp: defaultTeamAgentOptionsExpiration,
teamFeaturesExp: defaultTeamFeaturesExpiration,
hostDeviceAuthTokenExp: defaultHostDeviceAuthTokenExpiration,
Datastore: ds,
c: &cloneCache{cache.New(5*time.Minute, 10*time.Minute)},
packsExp: defaultPacksExpiration,
scheduledQueriesExp: defaultScheduledQueriesExpiration,
teamAgentOptionsExp: defaultTeamAgentOptionsExpiration,
teamFeaturesExp: defaultTeamFeaturesExpiration,
}
for _, fn := range opts {
fn(c)
@@ -295,24 +285,3 @@ func (ds *cachedMysql) DeleteTeam(ctx context.Context, teamID uint) error {
return nil
}
func (ds *cachedMysql) SetOrUpdateDeviceAuthToken(ctx context.Context, hostID uint, authToken string) error {
key := fmt.Sprintf(hostDeviceAuthTokenKey, hostID)
if x, found := ds.c.Get(key); found {
if tok, ok := x.(string); ok {
if tok == authToken {
// token did not change, no need to update it
return nil
}
}
}
// token changed or not in cache, update it
if err := ds.Datastore.SetOrUpdateDeviceAuthToken(ctx, hostID, authToken); err != nil {
return err
}
// store it in cache
ds.c.Set(key, authToken, ds.hostDeviceAuthTokenExp)
return nil
}
@@ -405,65 +405,3 @@ func TestCachedTeamFeatures(t *testing.T) {
_, err = ds.TeamFeatures(context.Background(), testTeam.ID)
require.Error(t, err)
}
func TestCachedHostDeviceAuthToken(t *testing.T) {
ctx := context.Background()
mockedDS := new(mock.Store)
ds := New(mockedDS, WithHostDeviceAuthTokenExpiration(100*time.Millisecond))
storedTokens := make(map[uint]string)
mockedDS.SetOrUpdateDeviceAuthTokenFunc = func(ctx context.Context, hostID uint, authToken string) error {
storedTokens[hostID] = authToken
return nil
}
assertInvoked := func(invoked bool) {
require.Equal(t, invoked, mockedDS.SetOrUpdateDeviceAuthTokenFuncInvoked)
mockedDS.SetOrUpdateDeviceAuthTokenFuncInvoked = false
}
err := ds.SetOrUpdateDeviceAuthToken(ctx, 1, "a")
require.NoError(t, err)
assertInvoked(true)
// same host, same value, not expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 1, "a")
require.NoError(t, err)
assertInvoked(false)
time.Sleep(200 * time.Millisecond)
// same host, same value, expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 1, "a")
require.NoError(t, err)
assertInvoked(true)
// same host, new value, not expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 1, "b")
require.NoError(t, err)
assertInvoked(true)
// new host, new value, not expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 2, "c")
require.NoError(t, err)
assertInvoked(true)
time.Sleep(200 * time.Millisecond)
// host 1, same value, expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 1, "b")
require.NoError(t, err)
assertInvoked(true)
// host 2, new value, expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 2, "d")
require.NoError(t, err)
assertInvoked(true)
// host 2, same value, not expired
err = ds.SetOrUpdateDeviceAuthToken(ctx, 2, "d")
require.NoError(t, err)
assertInvoked(false)
require.Equal(t, map[uint]string{1: "b", 2: "d"}, storedTokens)
}
+15 -28
View File
@@ -851,8 +851,8 @@ func (ds *Datastore) LoadHostByNodeKey(ctx context.Context, nodeKey string) (*fl
}
// LoadHostByDeviceAuthToken loads the whole host identified by the device auth token.
// If the token is invalid or expired it returns a NotFoundError.
func (ds *Datastore) LoadHostByDeviceAuthToken(ctx context.Context, authToken string, tokenTTL time.Duration) (*fleet.Host, error) {
// If the token is invalid it returns a NotFoundError.
func (ds *Datastore) LoadHostByDeviceAuthToken(ctx context.Context, authToken string) (*fleet.Host, error) {
const query = `
SELECT
h.*
@@ -862,11 +862,10 @@ func (ds *Datastore) LoadHostByDeviceAuthToken(ctx context.Context, authToken st
hosts h
ON
hda.host_id = h.id
WHERE hda.token = ? AND
hda.updated_at >= DATE_SUB(NOW(), INTERVAL ? SECOND)`
WHERE hda.token = ?`
var host fleet.Host
switch err := sqlx.GetContext(ctx, ds.reader, &host, query, authToken, tokenTTL.Seconds()); {
switch err := sqlx.GetContext(ctx, ds.reader, &host, query, authToken); {
case err == nil:
return &host, nil
case errors.Is(err, sql.ErrNoRows):
@@ -878,24 +877,12 @@ func (ds *Datastore) LoadHostByDeviceAuthToken(ctx context.Context, authToken st
// SetOrUpdateDeviceAuthToken inserts or updates the auth token for a host.
func (ds *Datastore) SetOrUpdateDeviceAuthToken(ctx context.Context, hostID uint, authToken string) error {
// Note that by not specifying "updated_at = VALUES(updated_at)" in the UPDATE part
// of the statement, it inherits the default behaviour which is that the updated_at
// timestamp will NOT be changed if the new token is the same as the old token
// (which is exactly what we want). The updated_at timestamp WILL be updated if the
// new token is different.
const stmt = `
INSERT INTO
host_device_auth ( host_id, token )
VALUES
(?, ?)
ON DUPLICATE KEY UPDATE
token = VALUES(token)
`
_, err := ds.writer.ExecContext(ctx, stmt, hostID, authToken)
if err != nil {
return ctxerr.Wrap(ctx, err, "upsert host's device auth token")
}
return nil
return ds.updateOrInsert(
ctx,
`UPDATE host_device_auth SET token = ? WHERE host_id = ?`,
`INSERT INTO host_device_auth (token, host_id) VALUES (?, ?)`,
authToken, hostID,
)
}
func (ds *Datastore) MarkHostsSeen(ctx context.Context, hostIDs []uint, t time.Time) error {
@@ -931,9 +918,9 @@ func (ds *Datastore) MarkHostsSeen(ctx context.Context, hostIDs []uint, t time.T
}
// SearchHosts performs a search on the hosts table using the following criteria:
// - Use the provided team filter.
// - Search hostname, uuid, hardware_serial, and primary_ip using LIKE (mimics ListHosts behavior)
// - An optional list of IDs to omit from the search.
// - Use the provided team filter.
// - Search hostname, uuid, hardware_serial, and primary_ip using LIKE (mimics ListHosts behavior)
// - An optional list of IDs to omit from the search.
func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, matchQuery string, omit ...uint) ([]*fleet.Host, error) {
query := `SELECT
h.*,
@@ -2542,8 +2529,8 @@ func (ds *Datastore) ListHostBatteries(ctx context.Context, hid uint) ([]*fleet.
// Notes:
// - We use `2 * interval`, because of the artificial jitter added to the intervals in Fleet.
// - Default values for:
// - host.DistributedInterval is usually 10s.
// - svc.config.Osquery.DetailUpdateInterval is usually 1h.
// - host.DistributedInterval is usually 10s.
// - svc.config.Osquery.DetailUpdateInterval is usually 1h.
// - Count only includes hosts seen during the last 7 days.
func countHostsNotRespondingDB(ctx context.Context, db sqlx.QueryerContext, logger log.Logger, config config.FleetConfig) (int, error,
) {
+7 -34
View File
@@ -4405,19 +4405,13 @@ func testHostsLoadHostByDeviceAuthToken(t *testing.T, ds *Datastore) {
err = ds.SetOrUpdateDeviceAuthToken(context.Background(), host.ID, validToken)
require.NoError(t, err)
_, err = ds.LoadHostByDeviceAuthToken(context.Background(), "nosuchtoken", time.Hour)
_, err = ds.LoadHostByDeviceAuthToken(context.Background(), "nosuchtoken")
require.Error(t, err)
assert.ErrorIs(t, err, sql.ErrNoRows)
h, err := ds.LoadHostByDeviceAuthToken(context.Background(), validToken, time.Hour)
h, err := ds.LoadHostByDeviceAuthToken(context.Background(), validToken)
require.NoError(t, err)
require.Equal(t, host.ID, h.ID)
time.Sleep(2 * time.Second) // make sure the token expires
_, err = ds.LoadHostByDeviceAuthToken(context.Background(), validToken, time.Second) // 1s TTL
require.Error(t, err)
assert.ErrorIs(t, err, sql.ErrNoRows)
}
func testHostsSetOrUpdateDeviceAuthToken(t *testing.T, ds *Datastore) {
@@ -4448,14 +4442,6 @@ func testHostsSetOrUpdateDeviceAuthToken(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
loadUpdatedAt := func(hostID uint) time.Time {
var ts time.Time
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(context.Background(), q, &ts, `SELECT updated_at FROM host_device_auth WHERE host_id = ?`, hostID)
})
return ts
}
token1 := "token1"
err = ds.SetOrUpdateDeviceAuthToken(context.Background(), host.ID, token1)
require.NoError(t, err)
@@ -4463,43 +4449,30 @@ func testHostsSetOrUpdateDeviceAuthToken(t *testing.T, ds *Datastore) {
token2 := "token2"
err = ds.SetOrUpdateDeviceAuthToken(context.Background(), host2.ID, token2)
require.NoError(t, err)
h2T1 := loadUpdatedAt(host2.ID)
h, err := ds.LoadHostByDeviceAuthToken(context.Background(), token1, time.Hour)
h, err := ds.LoadHostByDeviceAuthToken(context.Background(), token1)
require.NoError(t, err)
require.Equal(t, host.ID, h.ID)
h, err = ds.LoadHostByDeviceAuthToken(context.Background(), token2, time.Hour)
h, err = ds.LoadHostByDeviceAuthToken(context.Background(), token2)
require.NoError(t, err)
require.Equal(t, host2.ID, h.ID)
time.Sleep(time.Second) // ensure the mysql timestamp is different
token2Updated := "token2_updated"
err = ds.SetOrUpdateDeviceAuthToken(context.Background(), host2.ID, token2Updated)
require.NoError(t, err)
h2T2 := loadUpdatedAt(host2.ID)
require.True(t, h2T2.After(h2T1))
h, err = ds.LoadHostByDeviceAuthToken(context.Background(), token1, time.Hour)
h, err = ds.LoadHostByDeviceAuthToken(context.Background(), token1)
require.NoError(t, err)
require.Equal(t, host.ID, h.ID)
h, err = ds.LoadHostByDeviceAuthToken(context.Background(), token2Updated, time.Hour)
h, err = ds.LoadHostByDeviceAuthToken(context.Background(), token2Updated)
require.NoError(t, err)
require.Equal(t, host2.ID, h.ID)
_, err = ds.LoadHostByDeviceAuthToken(context.Background(), token2, time.Hour)
_, err = ds.LoadHostByDeviceAuthToken(context.Background(), token2)
require.Error(t, err)
assert.ErrorIs(t, err, sql.ErrNoRows)
time.Sleep(time.Second) // ensure the mysql timestamp is different
// update with the same token, should not change the updated_at timestamp
err = ds.SetOrUpdateDeviceAuthToken(context.Background(), host2.ID, token2Updated)
require.NoError(t, err)
h2T3 := loadUpdatedAt(host2.ID)
require.True(t, h2T2.Equal(h2T3))
}
func testOSVersions(t *testing.T, ds *Datastore) {
@@ -1,26 +0,0 @@
package tables
import (
"database/sql"
)
func init() {
MigrationClient.AddMigration(Up_20220901080652, Down_20220901080652)
}
func Up_20220901080652(tx *sql.Tx) error {
logger.Info.Println("Adding timestamps to 'host_device_auth'...")
_, err := tx.Exec(`
ALTER TABLE host_device_auth
ADD COLUMN created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
`)
if err == nil {
logger.Info.Println("Done adding timestamps to 'host_device_auth'...")
}
return err
}
func Down_20220901080652(tx *sql.Tx) error {
return nil
}
@@ -1,50 +0,0 @@
package tables
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestUp_20220901080652(t *testing.T) {
db := applyUpToPrev(t)
_, err := db.Exec(`INSERT INTO host_device_auth (host_id, token) VALUES (1, 'abcd')`)
require.NoError(t, err)
var before time.Time
err = db.QueryRow(`SELECT current_timestamp()`).Scan(&before)
require.NoError(t, err)
applyNext(t, db)
assertRow := func(id int, wantTok string, wantTm time.Time) {
var token string
var afterCreated, afterUpdated time.Time
// check the timestamps for the row that existed before the migation
err = db.QueryRow(`SELECT token, created_at, updated_at FROM host_device_auth WHERE host_id = ?`, id).Scan(&token, &afterCreated, &afterUpdated)
require.NoError(t, err)
require.Equal(t, wantTok, token)
require.WithinDuration(t, wantTm, afterCreated, time.Second)
require.WithinDuration(t, wantTm, afterUpdated, time.Second)
}
assertRow(1, "abcd", before)
// refresh the database timestamp
err = db.QueryRow(`SELECT current_timestamp()`).Scan(&before)
require.NoError(t, err)
// create a new row with the timestamps columns now created
_, err = db.Exec(`INSERT INTO host_device_auth (host_id, token) VALUES (2, 'zzzz')`)
require.NoError(t, err)
assertRow(2, "zzzz", before)
// create a new row with explicit timestamps
tm := time.Now().Add(time.Hour)
_, err = db.Exec(`INSERT INTO host_device_auth (host_id, token, created_at, updated_at) VALUES (3, 'AAA', ?, ?)`, tm, tm)
require.NoError(t, err)
assertRow(3, "AAA", tm)
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -232,8 +232,8 @@ type Datastore interface {
ListHostBatteries(ctx context.Context, id uint) ([]*HostBattery, error)
// LoadHostByDeviceAuthToken loads the host identified by the device auth token.
// If the token is invalid or expired it returns a NotFoundError.
LoadHostByDeviceAuthToken(ctx context.Context, authToken string, tokenTTL time.Duration) (*Host, error)
// If the token is invalid it returns a NotFoundError.
LoadHostByDeviceAuthToken(ctx context.Context, authToken string) (*Host, error)
// SetOrUpdateDeviceAuthToken inserts or updates the auth token for a host.
SetOrUpdateDeviceAuthToken(ctx context.Context, hostID uint, authToken string) error
+3 -3
View File
@@ -183,7 +183,7 @@ type ListHostDeviceMappingFunc func(ctx context.Context, id uint) ([]*fleet.Host
type ListHostBatteriesFunc func(ctx context.Context, id uint) ([]*fleet.HostBattery, error)
type LoadHostByDeviceAuthTokenFunc func(ctx context.Context, authToken string, tokenTTL time.Duration) (*fleet.Host, error)
type LoadHostByDeviceAuthTokenFunc func(ctx context.Context, authToken string) (*fleet.Host, error)
type SetOrUpdateDeviceAuthTokenFunc func(ctx context.Context, hostID uint, authToken string) error
@@ -1525,9 +1525,9 @@ func (s *DataStore) ListHostBatteries(ctx context.Context, id uint) ([]*fleet.Ho
return s.ListHostBatteriesFunc(ctx, id)
}
func (s *DataStore) LoadHostByDeviceAuthToken(ctx context.Context, authToken string, tokenTTL time.Duration) (*fleet.Host, error) {
func (s *DataStore) LoadHostByDeviceAuthToken(ctx context.Context, authToken string) (*fleet.Host, error) {
s.LoadHostByDeviceAuthTokenFuncInvoked = true
return s.LoadHostByDeviceAuthTokenFunc(ctx, authToken, tokenTTL)
return s.LoadHostByDeviceAuthTokenFunc(ctx, authToken)
}
func (s *DataStore) SetOrUpdateDeviceAuthToken(ctx context.Context, hostID uint, authToken string) error {
+13 -24
View File
@@ -12,18 +12,7 @@ import (
// and meant to be used by Fleet Desktop
type DeviceClient struct {
*baseClient
}
// NewDeviceClient instantiates a new client to perform requests against device endpoints
func NewDeviceClient(addr string, insecureSkipVerify bool, rootCA string) (*DeviceClient, error) {
baseClient, err := newBaseClient(addr, insecureSkipVerify, rootCA, "")
if err != nil {
return nil, err
}
return &DeviceClient{
baseClient: baseClient,
}, nil
token string
}
func (dc *DeviceClient) request(verb string, path string, query string, responseDest interface{}) error {
@@ -46,17 +35,22 @@ func (dc *DeviceClient) request(verb string, path string, query string, response
return dc.parseResponse(verb, path, response, responseDest)
}
func (dc *DeviceClient) DeviceURL(token string) string {
return dc.baseClient.url("/device/"+token, "").String()
}
// NewDeviceClient instantiates a new client to perform requests against device endpoints
func NewDeviceClient(addr, token string, insecureSkipVerify bool, rootCA string) (*DeviceClient, error) {
baseClient, err := newBaseClient(addr, insecureSkipVerify, rootCA, "")
if err != nil {
return nil, err
}
func (dc *DeviceClient) TransparencyURL(token string) string {
return dc.baseClient.url("/api/latest/fleet/device/"+token+"/transparency", "").String()
return &DeviceClient{
baseClient: baseClient,
token: token,
}, nil
}
// ListDevicePolicies fetches all policies for the device with the provided token
func (dc *DeviceClient) ListDevicePolicies(token string) ([]*fleet.HostPolicy, error) {
verb, path := "GET", "/api/latest/fleet/device/"+token+"/policies"
func (dc *DeviceClient) ListDevicePolicies() ([]*fleet.HostPolicy, error) {
verb, path := "GET", "/api/latest/fleet/device/"+dc.token+"/policies"
var responseBody listDevicePoliciesResponse
err := dc.request(verb, path, "", &responseBody)
if err != nil {
@@ -64,8 +58,3 @@ func (dc *DeviceClient) ListDevicePolicies(token string) ([]*fleet.HostPolicy, e
}
return responseBody.Policies, nil
}
func (dc *DeviceClient) Check(token string) error {
verb, path := "GET", "/api/latest/fleet/device/"+token+"/policies"
return dc.request(verb, path, "", &listDevicePoliciesResponse{})
}
+4 -5
View File
@@ -29,8 +29,7 @@ func (m *mockHttpClient) Do(req *http.Request) (*http.Response, error) {
}
func TestDeviceClientListPolicies(t *testing.T) {
token := "test-token"
client, err := NewDeviceClient("https://test.com", true, "")
client, err := NewDeviceClient("https://test.com", "test-token", true, "")
require.NoError(t, err)
mockRequestDoer := &mockHttpClient{}
@@ -38,14 +37,14 @@ func TestDeviceClientListPolicies(t *testing.T) {
t.Run("with wrong license", func(t *testing.T) {
mockRequestDoer.statusCode = http.StatusPaymentRequired
_, err = client.ListDevicePolicies(token)
_, err = client.ListDevicePolicies()
require.ErrorIs(t, err, ErrMissingLicense)
})
t.Run("with empty policies", func(t *testing.T) {
mockRequestDoer.statusCode = http.StatusOK
mockRequestDoer.resBody = `{"policies": []}`
policies, err := client.ListDevicePolicies(token)
policies, err := client.ListDevicePolicies()
require.NoError(t, err)
require.Len(t, policies, 0)
})
@@ -53,7 +52,7 @@ func TestDeviceClientListPolicies(t *testing.T) {
t.Run("with policies", func(t *testing.T) {
mockRequestDoer.statusCode = http.StatusOK
mockRequestDoer.resBody = `{"policies": [{"id": 1}]}`
policies, err := client.ListDevicePolicies(token)
policies, err := client.ListDevicePolicies()
require.NoError(t, err)
require.Len(t, policies, 1)
require.Equal(t, uint(1), policies[0].ID)
+1 -4
View File
@@ -3,7 +3,6 @@ package service
import (
"context"
"net/http"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
@@ -77,8 +76,6 @@ func getDeviceHostEndpoint(ctx context.Context, request interface{}, svc fleet.S
// token, along with a boolean indicating if debug logging is enabled for that
// host.
func (svc *Service) AuthenticateDevice(ctx context.Context, authToken string) (*fleet.Host, bool, error) {
const deviceAuthTokenTTL = time.Hour
// skipauth: Authorization is currently for user endpoints only.
svc.authz.SkipAuthorization(ctx)
@@ -86,7 +83,7 @@ func (svc *Service) AuthenticateDevice(ctx context.Context, authToken string) (*
return nil, false, ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("authentication error: missing device authentication token"))
}
host, err := svc.ds.LoadHostByDeviceAuthToken(ctx, authToken, deviceAuthTokenTTL)
host, err := svc.ds.LoadHostByDeviceAuthToken(ctx, authToken)
switch {
case err == nil:
// OK
+16 -6
View File
@@ -502,6 +502,9 @@ func getDistributedQueriesEndpoint(ctx context.Context, request interface{}, svc
}, nil
}
// orbitInfoRefetchAfterEnrollDur value assumes the default distributed_interval value set by Fleet of 10s.
const orbitInfoRefetchAfterEnrollDur = 1 * time.Minute
func (svc *Service) GetDistributedQueries(ctx context.Context) (queries map[string]string, discovery map[string]string, accelerate uint, err error) {
// skipauth: Authorization is currently for user endpoints only.
svc.authz.SkipAuthorization(ctx)
@@ -525,12 +528,19 @@ func (svc *Service) GetDistributedQueries(ctx context.Context) (queries map[stri
discovery[name] = query
}
// We always request the `orbit_info` query results, as orbit is responsible for rotating
// the device auth token. To prevent excessive writes to host_device_auth table, the
// write method (SetOrUpdateDeviceAuthToken) is handled by the cached_mysql package.
// See #6348.
queries[hostDetailQueryPrefix+osquery_utils.OrbitInfoQueryName] = osquery_utils.OrbitInfoDetailQuery.Query
discovery[hostDetailQueryPrefix+osquery_utils.OrbitInfoQueryName] = osquery_utils.OrbitInfoDetailQuery.Discovery
// The following is added to improve Fleet Desktop's UX at install time.
//
// At install (enroll) time, the "orbit_info" extension takes longer to load than the first
// query check-in (distributed/read request).
// To avoid having to wait for the next check-in to ingest the data (after
// svc.config.Osquery.DetailUpdateInterval, 1h by default),
// we make the best effort to retrieve such "device auth token" from the device, but with a
// limit of orbitInfoRefetchAfterEnrollDur to not generate too much write database overhead
// (writes to `host_device_auth` table).
if svc.clock.Now().Sub(host.LastEnrolledAt) < orbitInfoRefetchAfterEnrollDur {
queries[hostDetailQueryPrefix+osquery_utils.OrbitInfoQueryName] = osquery_utils.OrbitInfoDetailQuery.Query
discovery[hostDetailQueryPrefix+osquery_utils.OrbitInfoQueryName] = osquery_utils.OrbitInfoDetailQuery.Discovery
}
labelQueries, err := svc.labelQueriesForHost(ctx, host)
if err != nil {
+13 -21
View File
@@ -754,8 +754,7 @@ func TestLabelQueries(t *testing.T) {
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 1) // orbit_info is always returned
require.NotNil(t, queries[hostDetailQueryPrefix+"orbit_info"])
require.Empty(t, queries)
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
@@ -767,11 +766,10 @@ func TestLabelQueries(t *testing.T) {
}, nil
}
// Now we should get the label queries + orbit_info
// Now we should get the label queries
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 4)
require.NotNil(t, queries[hostDetailQueryPrefix+"orbit_info"])
require.Len(t, queries, 3)
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
@@ -826,8 +824,7 @@ func TestLabelQueries(t *testing.T) {
ctx = hostctx.NewContext(ctx, host)
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 1) // only orbit_info
require.NotNil(t, queries[hostDetailQueryPrefix+"orbit_info"])
require.Empty(t, queries)
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
@@ -867,8 +864,7 @@ func TestLabelQueries(t *testing.T) {
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 1) // only orbit_info
require.NotNil(t, queries[hostDetailQueryPrefix+"orbit_info"])
require.Empty(t, queries)
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
}
@@ -1054,12 +1050,11 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
host.DetailUpdatedAt = mockClock.Now()
mockClock.AddTime(1 * time.Minute)
// Now no detail queries should be required except orbit_info
// Now no detail queries should be required
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 1)
require.NotNil(t, queries[hostDetailQueryPrefix+"orbit_info"])
require.Empty(t, queries)
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
@@ -1375,12 +1370,11 @@ func TestDetailQueries(t *testing.T) {
host.DetailUpdatedAt = mockClock.Now()
mockClock.AddTime(1 * time.Minute)
// Now no detail queries should be required except orbit_info
// Now no detail queries should be required
ctx = hostctx.NewContext(ctx, host)
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 1)
require.NotNil(t, queries[hostDetailQueryPrefix+"orbit_info"])
require.Empty(t, queries)
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
@@ -2821,7 +2815,7 @@ func TestLiveQueriesFailing(t *testing.T) {
}
// TestFleetDesktopOrbitInfo tests that the orbit_info table extension is
// refetched on every distributed/read call.
// refetched for "orbitInfoRefetchAfterEnrollDur" after enroll.
func TestFleetDesktopOrbitInfo(t *testing.T) {
ds := new(mock.Store)
lq := live_query_mock.New(t)
@@ -2857,15 +2851,13 @@ func TestFleetDesktopOrbitInfo(t *testing.T) {
require.Contains(t, queries, "fleet_detail_query_orbit_info")
// Advance mock clock
mockClock.AddTime(time.Minute)
mockClock.AddTime(orbitInfoRefetchAfterEnrollDur)
ctx = hostctx.NewContext(context.Background(), host)
// orbit_info query is still present
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
require.Len(t, queries, 1)
verifyDiscovery(t, queries, discovery)
require.Contains(t, queries, "fleet_detail_query_orbit_info")
require.Len(t, queries, 0)
require.Len(t, discovery, 0)
}
func distQueriesMapKeys(m map[string]string) []string {