Revert token rotation (#7628)
This reverts all changes related to token rotation.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
-26
@@ -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
|
||||
}
|
||||
-50
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user