From 0afc917fa8716dfbbf9109b8d456092dee7ead92 Mon Sep 17 00:00:00 2001 From: Tim Alexander Date: Tue, 10 Sep 2024 13:39:28 -0400 Subject: [PATCH] Change Updater to use newly implemented RC CDN client --- go.mod | 2 +- pkg/config/remote/service/cdn.go | 287 +++++++++++++++++++ pkg/config/remote/service/cdn_test.go | 209 ++++++++++++++ pkg/config/remote/service/service.go | 2 +- pkg/config/remote/service/service_test.go | 43 ++- pkg/config/remote/service/tmp/test-config.db | Bin 0 -> 131072 bytes pkg/config/remote/uptane/cdn_store.go | 122 ++++++++ pkg/config/remote/uptane/cdn_store_test.go | 274 ++++++++++++++++++ pkg/config/remote/uptane/client.go | 162 ++++++++--- pkg/config/remote/uptane/client_test.go | 6 +- pkg/config/remote/uptane/remote_store.go | 2 +- pkg/fleet/daemon/daemon.go | 12 +- pkg/fleet/daemon/daemon_test.go | 4 +- pkg/fleet/installer/installer.go | 6 +- pkg/fleet/internal/cdn/cdn.go | 148 +++++----- 15 files changed, 1146 insertions(+), 133 deletions(-) create mode 100644 pkg/config/remote/service/cdn.go create mode 100644 pkg/config/remote/service/cdn_test.go create mode 100644 pkg/config/remote/service/tmp/test-config.db create mode 100644 pkg/config/remote/uptane/cdn_store.go create mode 100644 pkg/config/remote/uptane/cdn_store_test.go diff --git a/go.mod b/go.mod index 974179601f42e..a1c8061155f18 100644 --- a/go.mod +++ b/go.mod @@ -354,7 +354,7 @@ require ( github.com/BurntSushi/toml v1.3.2 // indirect github.com/DataDog/aptly v1.5.3 // indirect github.com/DataDog/extendeddaemonset v0.10.0-rc.4 // indirect - github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect + github.com/DataDog/go-tuf v1.1.0-0.5.2 github.com/DataDog/gostackparse v0.7.0 // indirect github.com/DataDog/mmh3 v0.0.0-20210722141835-012dc69a9e49 // indirect github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7 // indirect diff --git a/pkg/config/remote/service/cdn.go b/pkg/config/remote/service/cdn.go new file mode 100644 index 0000000000000..88877d594965e --- /dev/null +++ b/pkg/config/remote/service/cdn.go @@ -0,0 +1,287 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024-present Datadog, Inc. + +package service + +import ( + "context" + "encoding/hex" + "fmt" + "github.com/DataDog/datadog-agent/pkg/config/model" + "github.com/DataDog/datadog-agent/pkg/config/remote/api" + "net/url" + "path" + "sync" + "time" + + rdata "github.com/DataDog/datadog-agent/pkg/config/remote/data" + "github.com/DataDog/datadog-agent/pkg/config/remote/uptane" + pbgo "github.com/DataDog/datadog-agent/pkg/proto/pbgo/core" + "github.com/DataDog/datadog-agent/pkg/remoteconfig/state" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/go-tuf/data" + tufutil "github.com/DataDog/go-tuf/util" +) + +const maxUpdateFrequency = 50 * time.Second + +// httpUptaneClient is used to mock the uptane component for testing +type httpUptaneClient interface { + Update() error + State() (uptane.State, error) + DirectorRoot(version uint64) ([]byte, error) + StoredOrgUUID() (string, error) + Targets() (data.TargetFiles, error) + TargetFile(path string) ([]byte, error) + TargetsMeta() ([]byte, error) + TargetsCustom() ([]byte, error) + TUFVersionState() (uptane.TUFVersions, error) +} + +// HTTPClient defines a client that can be used to fetch Remote Configurations from an HTTP(s)-based backend +type HTTPClient struct { + sync.Mutex + + lastUpdate time.Time + + // rcType is used to differentiate multiple RC services running in a single agent. + // Today, it is simply logged as a prefix in all log messages to help when triaging + // via logs. + rcType string + + api api.API + uptane httpUptaneClient +} + +// NewHTTPClient creates a new HTTPClient that can be used to fetch Remote Configurations from an HTTP(s)-based backend +func NewHTTPClient(cfg model.Reader, baseRawURL, host, site, apiKey, rcKey, agentVersion string) (*HTTPClient, error) { + + dbPath := path.Join(cfg.GetString("run_path"), "remote-config-cdn.db") + db, err := openCacheDB(dbPath, agentVersion, apiKey) + if err != nil { + return nil, err + } + uptaneClientOptions := []uptane.ClientOption{ + uptane.WithConfigRootOverride(site, ""), + uptane.WithDirectorRootOverride(site, ""), + } + baseURL, err := url.Parse(baseRawURL) + if err != nil { + return nil, err + } + authKeys, err := getRemoteConfigAuthKeys(apiKey, rcKey) + if err != nil { + return nil, err + } + http, err := api.NewHTTPClient(authKeys.apiAuth(), cfg, baseURL) + if err != nil { + return nil, err + } + uptaneHTTPClient, err := uptane.NewHTTPClient( + db, + host, + site, + apiKey, + newRCBackendOrgUUIDProvider(http), + uptaneClientOptions..., + ) + if err != nil { + return nil, err + } + + return &HTTPClient{ + rcType: "CDN", + uptane: uptaneHTTPClient, + api: http, + }, nil +} + +func (s *HTTPClient) update() error { + s.Lock() + defer s.Unlock() + + err := s.uptane.Update() + if err != nil { + return err + } + + return nil +} + +func (s *HTTPClient) shouldUpdate() bool { + s.Lock() + defer s.Unlock() + if time.Since(s.lastUpdate) > maxUpdateFrequency { + s.lastUpdate = time.Now() + return true + } + return false +} + +// GetCDNConfigUpdate returns any updated configs. If multiple requests have been made +// in a short amount of time, a cached response is returned. If RC has been disabled, +// an error is returned. If there is no update (the targets version is up-to-date) nil +// is returned for both the update and error. +func (s *HTTPClient) GetCDNConfigUpdate( + products []string, + currentTargetsVersion, currentRootVersion uint64, + cachedTargetFiles []*pbgo.TargetFileMeta, +) (*state.Update, error) { + + if !s.shouldUpdate() { + return s.getUpdate(products, currentTargetsVersion, currentRootVersion, cachedTargetFiles) + } + + // check org status in the backend. If RC is disabled, return current state. + response, err := s.api.FetchOrgStatus(context.Background()) + if err != nil || !response.Enabled || !response.Authorized { + return s.getUpdate(products, currentTargetsVersion, currentRootVersion, cachedTargetFiles) + } + + err = s.update() + if err != nil { + _ = log.Warn(fmt.Sprintf("Error updating CDN config repo: %v", err)) + } + + return s.getUpdate(products, currentTargetsVersion, currentRootVersion, cachedTargetFiles) +} + +func (s *HTTPClient) getUpdate( + products []string, + currentTargetsVersion, currentRootVersion uint64, + cachedTargetFiles []*pbgo.TargetFileMeta, +) (*state.Update, error) { + s.Lock() + defer s.Unlock() + + tufVersions, err := s.uptane.TUFVersionState() + if err != nil { + return nil, err + } + if tufVersions.DirectorTargets == currentTargetsVersion { + return nil, nil + } + roots, err := s.getNewDirectorRoots(currentRootVersion, tufVersions.DirectorRoot) + if err != nil { + return nil, err + } + targetsRaw, err := s.uptane.TargetsMeta() + if err != nil { + return nil, err + } + targetFiles, err := s.getTargetFiles(rdata.StringListToProduct(products), cachedTargetFiles) + if err != nil { + return nil, err + } + + canonicalTargets, err := enforceCanonicalJSON(targetsRaw) + if err != nil { + return nil, err + } + + directorTargets, err := s.uptane.Targets() + if err != nil { + return nil, err + } + + productsMap := make(map[string]struct{}) + for _, product := range products { + productsMap[product] = struct{}{} + } + configs := make([]string, 0) + for path, meta := range directorTargets { + pathMeta, err := rdata.ParseConfigPath(path) + if err != nil { + return nil, err + } + if _, productRequested := productsMap[pathMeta.Product]; !productRequested { + continue + } + configMetadata, err := parseFileMetaCustom(meta.Custom) + if err != nil { + return nil, err + } + if configExpired(configMetadata.Expires) { + continue + } + + configs = append(configs, path) + } + + fileMap := make(map[string][]byte, len(targetFiles)) + for _, f := range targetFiles { + fileMap[f.Path] = f.Raw + } + + return &state.Update{ + TUFRoots: roots, + TUFTargets: canonicalTargets, + TargetFiles: fileMap, + ClientConfigs: configs, + }, nil +} + +func (s *HTTPClient) getNewDirectorRoots(currentVersion uint64, newVersion uint64) ([][]byte, error) { + var roots [][]byte + for i := currentVersion + 1; i <= newVersion; i++ { + root, err := s.uptane.DirectorRoot(i) + if err != nil { + return nil, err + } + canonicalRoot, err := enforceCanonicalJSON(root) + if err != nil { + return nil, err + } + roots = append(roots, canonicalRoot) + } + return roots, nil +} + +func (s *HTTPClient) getTargetFiles(products []rdata.Product, cachedTargetFiles []*pbgo.TargetFileMeta) ([]*pbgo.File, error) { + productSet := make(map[rdata.Product]struct{}) + for _, product := range products { + productSet[product] = struct{}{} + } + targets, err := s.uptane.Targets() + if err != nil { + return nil, err + } + cachedTargets := make(map[string]data.FileMeta) + for _, cachedTarget := range cachedTargetFiles { + hashes := make(data.Hashes) + for _, hash := range cachedTarget.Hashes { + h, err := hex.DecodeString(hash.Hash) + if err != nil { + return nil, err + } + hashes[hash.Algorithm] = h + } + cachedTargets[cachedTarget.Path] = data.FileMeta{ + Hashes: hashes, + Length: cachedTarget.Length, + } + } + var configFiles []*pbgo.File + for targetPath, targetMeta := range targets { + configPathMeta, err := rdata.ParseConfigPath(targetPath) + if err != nil { + return nil, err + } + if _, inClientProducts := productSet[rdata.Product(configPathMeta.Product)]; inClientProducts { + if notEqualErr := tufutil.FileMetaEqual(cachedTargets[targetPath], targetMeta.FileMeta); notEqualErr == nil { + continue + } + fileContents, err := s.uptane.TargetFile(targetPath) + if err != nil { + return nil, err + } + configFiles = append(configFiles, &pbgo.File{ + Path: targetPath, + Raw: fileContents, + }) + } + } + return configFiles, nil +} diff --git a/pkg/config/remote/service/cdn_test.go b/pkg/config/remote/service/cdn_test.go new file mode 100644 index 0000000000000..60191796cbd39 --- /dev/null +++ b/pkg/config/remote/service/cdn_test.go @@ -0,0 +1,209 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024-present Datadog, Inc. + +package service + +import ( + "encoding/base32" + "fmt" + "strings" + "testing" + "time" + + "github.com/DataDog/datadog-agent/pkg/config/model" + "github.com/DataDog/datadog-agent/pkg/config/remote/uptane" + pbgo "github.com/DataDog/datadog-agent/pkg/proto/pbgo/core" + "github.com/DataDog/go-tuf/data" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func setupCDNClient(t *testing.T, uptaneClient *mockCDNUptane, api *mockAPI) *HTTPClient { + cfg := model.NewConfig("datadog", "DD", strings.NewReplacer(".", "_")) + + cfg.SetWithoutSource("hostname", "test-hostname") + defer cfg.SetWithoutSource("hostname", "") + + dir := t.TempDir() + cfg.SetWithoutSource("run_path", dir) + serializedKey, _ := testRCKey.MarshalMsg(nil) + cfg.SetWithoutSource("remote_configuration.key", base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(serializedKey)) + baseRawURL := "https://localhost" + client, err := NewHTTPClient(cfg, baseRawURL, host, site, k, "", "9.9.9") + require.NoError(t, err) + if api != nil { + client.api = api + } + if uptaneClient != nil { + client.uptane = uptaneClient + } + return client +} + +var ( + host = "test-host" + site = "test-site" + k = "test-api-key" +) + +// TestHTTPClientRecentUpdate tests that with a recent (<50s ago) last-update-time, +// the client will not fetch a new update and will return the cached state +func TestHTTPClientRecentUpdate(t *testing.T) { + api := &mockAPI{} + + uptaneClient := &mockCDNUptane{} + uptaneClient.On("TUFVersionState").Return(uptane.TUFVersions{ + DirectorRoot: 1, + DirectorTargets: 1, + ConfigRoot: 1, + ConfigSnapshot: 1, + }, nil) + uptaneClient.On("DirectorRoot", uint64(1)).Return([]byte(`{"signatures": "testroot1", "signed": "one"}`), nil) + uptaneClient.On("TargetsMeta").Return([]byte(`{"signatures": "testtargets", "signed": "stuff"}`), nil) + uptaneClient.On("Targets").Return( + data.TargetFiles{ + "datadog/2/TESTING1/id/1": {}, + "datadog/2/TESTING2/id/2": {}, + }, + nil, + ) + uptaneClient.On("TargetFile", "datadog/2/TESTING1/id/1").Return([]byte(`testing_1`), nil) + + client := setupCDNClient(t, uptaneClient, api) + client.lastUpdate = time.Now() + + u, err := client.GetCDNConfigUpdate([]string{"TESTING1"}, 0, 0, []*pbgo.TargetFileMeta{}) + require.NoError(t, err) + uptaneClient.AssertExpectations(t) + require.NotNil(t, u) + require.Len(t, u.TargetFiles, 1) + require.Equal(t, []byte(`testing_1`), u.TargetFiles["datadog/2/TESTING1/id/1"]) + require.Len(t, u.ClientConfigs, 1) + require.Equal(t, "datadog/2/TESTING1/id/1", u.ClientConfigs[0]) + require.Len(t, u.TUFRoots, 1) + require.Equal(t, []byte(`{"signatures":"testroot1","signed":"one"}`), u.TUFRoots[0]) + + response := &pbgo.OrgStatusResponse{ + Enabled: true, + Authorized: true, + } + api.On("FetchOrgStatus", mock.Anything).Return(response, nil) +} + +// TestHTTPClientNegativeOrgStatus tests that with a recent (<50s ago) last-update-time, +// the client will not fetch a new update and will return the cached state +func TestHTTPClientNegativeOrgStatus(t *testing.T) { + var tests = []struct { + enabled, authorized bool + err error + }{ + {false, true, nil}, + {true, false, nil}, + {false, false, nil}, + {true, true, fmt.Errorf("error")}, + {false, false, fmt.Errorf("error")}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("enabled=%t, authorized=%t, err=%v", tt.enabled, tt.authorized, tt.err), func(t *testing.T) { + api := &mockAPI{} + response := &pbgo.OrgStatusResponse{ + Enabled: tt.enabled, + Authorized: tt.authorized, + } + api.On("FetchOrgStatus", mock.Anything).Return(response, tt.err) + uptaneClient := &mockCDNUptane{} + uptaneClient.On("TUFVersionState").Return(uptane.TUFVersions{ + DirectorRoot: 1, + DirectorTargets: 1, + ConfigRoot: 1, + ConfigSnapshot: 1, + }, nil) + uptaneClient.On("DirectorRoot", uint64(1)).Return([]byte(`{"signatures": "testroot1", "signed": "one"}`), nil) + uptaneClient.On("TargetsMeta").Return([]byte(`{"signatures": "testtargets", "signed": "stuff"}`), nil) + uptaneClient.On("Targets").Return( + data.TargetFiles{ + "datadog/2/TESTING1/id/1": {}, + "datadog/2/TESTING2/id/2": {}, + }, + nil, + ) + uptaneClient.On("TargetFile", "datadog/2/TESTING1/id/1").Return([]byte(`testing_1`), nil) + + client := setupCDNClient(t, uptaneClient, api) + client.lastUpdate = time.Now().Add(time.Second * -60) + + u, err := client.GetCDNConfigUpdate([]string{"TESTING1"}, 0, 0, []*pbgo.TargetFileMeta{}) + require.NoError(t, err) + uptaneClient.AssertExpectations(t) + require.NotNil(t, u) + require.Len(t, u.TargetFiles, 1) + require.Equal(t, []byte(`testing_1`), u.TargetFiles["datadog/2/TESTING1/id/1"]) + require.Len(t, u.ClientConfigs, 1) + require.Equal(t, "datadog/2/TESTING1/id/1", u.ClientConfigs[0]) + require.Len(t, u.TUFRoots, 1) + require.Equal(t, []byte(`{"signatures":"testroot1","signed":"one"}`), u.TUFRoots[0]) + }) + } +} + +// TestHTTPClientUpdateSuccess tests that a stale state will trigger an update of the cached state +// before returning the cached state. In the event that the Update fails, the stale state will be returned. +func TestHTTPClientUpdateSuccess(t *testing.T) { + var tests = []struct { + updateSucceeds bool + }{ + {true}, + {false}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("updateSucceeds=%t", tt.updateSucceeds), func(t *testing.T) { + api := &mockAPI{} + response := &pbgo.OrgStatusResponse{ + Enabled: true, + Authorized: true, + } + api.On("FetchOrgStatus", mock.Anything).Return(response, nil) + uptaneClient := &mockCDNUptane{} + uptaneClient.On("TUFVersionState").Return(uptane.TUFVersions{ + DirectorRoot: 1, + DirectorTargets: 1, + ConfigRoot: 1, + ConfigSnapshot: 1, + }, nil) + uptaneClient.On("DirectorRoot", uint64(1)).Return([]byte(`{"signatures": "testroot1", "signed": "one"}`), nil) + uptaneClient.On("TargetsMeta").Return([]byte(`{"signatures": "testtargets", "signed": "stuff"}`), nil) + uptaneClient.On("Targets").Return( + data.TargetFiles{ + "datadog/2/TESTING1/id/1": {}, + "datadog/2/TESTING2/id/2": {}, + }, + nil, + ) + uptaneClient.On("TargetFile", "datadog/2/TESTING1/id/1").Return([]byte(`testing_1`), nil) + + updateErr := fmt.Errorf("uh oh") + if tt.updateSucceeds { + updateErr = nil + } + uptaneClient.On("Update").Return(updateErr) + + client := setupCDNClient(t, uptaneClient, api) + client.lastUpdate = time.Now().Add(time.Second * -60) + + u, err := client.GetCDNConfigUpdate([]string{"TESTING1"}, 0, 0, []*pbgo.TargetFileMeta{}) + require.NoError(t, err) + uptaneClient.AssertExpectations(t) + require.NotNil(t, u) + require.Len(t, u.TargetFiles, 1) + require.Equal(t, []byte(`testing_1`), u.TargetFiles["datadog/2/TESTING1/id/1"]) + require.Len(t, u.ClientConfigs, 1) + require.Equal(t, "datadog/2/TESTING1/id/1", u.ClientConfigs[0]) + require.Len(t, u.TUFRoots, 1) + require.Equal(t, []byte(`{"signatures":"testroot1","signed":"one"}`), u.TUFRoots[0]) + }) + } +} diff --git a/pkg/config/remote/service/service.go b/pkg/config/remote/service/service.go index 61cc0e5d21e72..4f9ec4a1079bf 100644 --- a/pkg/config/remote/service/service.go +++ b/pkg/config/remote/service/service.go @@ -337,7 +337,7 @@ func NewService(cfg model.Reader, rcType, baseRawURL, hostname string, tagsGette if authKeys.rcKeySet { opt = append(opt, uptane.WithOrgIDCheck(authKeys.rcKey.OrgID)) } - uptaneClient, err := uptane.NewClient( + uptaneClient, err := uptane.NewCoreAgentClient( db, newRCBackendOrgUUIDProvider(http), opt..., diff --git a/pkg/config/remote/service/service_test.go b/pkg/config/remote/service/service_test.go index 683713d49a747..56c1f3c7bf51d 100644 --- a/pkg/config/remote/service/service_test.go +++ b/pkg/config/remote/service/service_test.go @@ -69,11 +69,24 @@ type mockUptane struct { mock.Mock } -func (m *mockUptane) Update(response *pbgo.LatestConfigsResponse) error { +type mockCoreAgentUptane struct { + mockUptane +} + +type mockCDNUptane struct { + mockUptane +} + +func (m *mockCoreAgentUptane) Update(response *pbgo.LatestConfigsResponse) error { args := m.Called(response) return args.Error(0) } +func (m *mockCDNUptane) Update() error { + args := m.Called() + return args.Error(0) +} + func (m *mockUptane) State() (uptane.State, error) { args := m.Called() return args.Get(0).(uptane.State), args.Error(1) @@ -139,7 +152,7 @@ var testRCKey = msgpgo.RemoteConfigKey{ Datacenter: "dd.com", } -func newTestService(t *testing.T, api *mockAPI, uptane *mockUptane, clock clock.Clock) *Service { +func newTestService(t *testing.T, api *mockAPI, uptane *mockCoreAgentUptane, clock clock.Clock) *Service { cfg := model.NewConfig("datadog", "DD", strings.NewReplacer(".", "_")) cfg.SetWithoutSource("hostname", "test-hostname") @@ -167,7 +180,7 @@ func newTestService(t *testing.T, api *mockAPI, uptane *mockUptane, clock clock. func TestServiceBackoffFailure(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -251,7 +264,7 @@ func TestServiceBackoffFailure(t *testing.T) { func TestServiceBackoffFailureRecovery(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -319,7 +332,7 @@ func customMeta(tracerPredicates []*pbgo.TracerPredicateV1, expiration int64) *j // gRPC's InvalidArgument status code. func TestClientGetConfigsRequestMissingFields(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -384,7 +397,7 @@ func TestClientGetConfigsRequestMissingFields(t *testing.T) { func TestService(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -415,7 +428,7 @@ func TestService(t *testing.T) { api.AssertExpectations(t) uptaneClient.AssertExpectations(t) - *uptaneClient = mockUptane{} + *uptaneClient = mockCoreAgentUptane{} *api = mockAPI{} root3 := []byte(`{"signatures": "testroot3", "signed": "signed"}`) @@ -530,7 +543,7 @@ func TestServiceClientPredicates(t *testing.T) { lastConfigResponse := &pbgo.LatestConfigsResponse{ TargetFiles: []*pbgo.File{{Path: "test"}}, } - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} api := &mockAPI{} service := newTestService(t, api, uptaneClient, clock) @@ -624,7 +637,7 @@ func TestServiceClientPredicates(t *testing.T) { func TestServiceGetRefreshIntervalNone(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -664,7 +677,7 @@ func TestServiceGetRefreshIntervalNone(t *testing.T) { func TestServiceGetRefreshIntervalValid(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -704,7 +717,7 @@ func TestServiceGetRefreshIntervalValid(t *testing.T) { func TestServiceGetRefreshIntervalTooSmall(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -744,7 +757,7 @@ func TestServiceGetRefreshIntervalTooSmall(t *testing.T) { func TestServiceGetRefreshIntervalTooBig(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -784,7 +797,7 @@ func TestServiceGetRefreshIntervalTooBig(t *testing.T) { func TestServiceGetRefreshIntervalNoOverrideAllowed(t *testing.T) { api := &mockAPI{} - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} clock := clock.NewMock() service := newTestService(t, api, uptaneClient, clock) @@ -836,7 +849,7 @@ func TestConfigExpiration(t *testing.T) { lastConfigResponse := &pbgo.LatestConfigsResponse{ TargetFiles: []*pbgo.File{{Path: "test"}}, } - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} api := &mockAPI{} service := newTestService(t, api, uptaneClient, clock) @@ -914,7 +927,7 @@ func TestConfigExpiration(t *testing.T) { func TestOrgStatus(t *testing.T) { api := &mockAPI{} clock := clock.NewMock() - uptaneClient := &mockUptane{} + uptaneClient := &mockCoreAgentUptane{} service := newTestService(t, api, uptaneClient, clock) response := &pbgo.OrgStatusResponse{ diff --git a/pkg/config/remote/service/tmp/test-config.db b/pkg/config/remote/service/tmp/test-config.db new file mode 100644 index 0000000000000000000000000000000000000000..b1cbf6d74560c26abe53bef9d52c015c8471d0d2 GIT binary patch literal 131072 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∈;e0lw?cnq{ z%#Z1A6-w~cYi6BRRZ3gApu;hw@GOKZIFC}rLdSjtR*Q^uJVq^a#AeBK+k{$aj_IO= z1ubOS+5*i|FrlW*)BlHp9dUM~%%S(nX)$GrGL+rR!Q@VVYx#FFxx44(V0I9I00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z h1Rwwb2tWV=5P$##AOHafKmY;|fB*y_0D)Ht{09uW+ c.currentRootsVersion { + c.currentRootsVersion = uint64(r.Version) + } } - service.Start() - defer func() { _ = service.Stop() }() - // Force a cache bypass - cfgs, err := service.ClientGetConfigs(ctx, &pbgo.ClientGetConfigsRequest{ - Client: &pbgo.Client{ - Id: uuid.New().String(), - Products: []string{"AGENT_CONFIG"}, - IsUpdater: true, - ClientUpdater: &pbgo.ClientUpdater{}, - State: &pbgo.ClientState{ - RootVersion: 1, - TargetsVersion: 1, - }, - }, - }) - if err != nil { - return nil, err + + var signedTargets data.Signed + err = json.Unmarshal(agentConfigUpdate.TUFTargets, &signedTargets) + if err == nil { + var targets data.Targets + err = json.Unmarshal(signedTargets.Signed, &targets) + if err == nil && uint64(targets.Version) > c.currentTargetsVersion { + c.currentTargetsVersion = uint64(targets.Version) + } } // Unmarshal RC results configLayers := map[string]*layer{} var configOrder *orderConfig var layersErr error - for _, file := range cfgs.TargetFiles { - matched := datadogConfigIDRegexp.FindStringSubmatch(file.GetPath()) + paths := agentConfigUpdate.ClientConfigs + targetFiles := agentConfigUpdate.TargetFiles + for _, path := range paths { + matched := datadogConfigIDRegexp.FindStringSubmatch(path) if len(matched) != 2 { - layersErr = multierr.Append(layersErr, fmt.Errorf("invalid config path: %s", file.GetPath())) + layersErr = multierr.Append(layersErr, fmt.Errorf("invalid config path: %s", path)) continue } configName := matched[1] + file := targetFiles[path] if configName != configOrderID { configLayer := &layer{} - err = json.Unmarshal(file.GetRaw(), configLayer) + err = json.Unmarshal(file, configLayer) if err != nil { // If a layer is wrong, fail later to parse the rest and check them all layersErr = multierr.Append(layersErr, err) @@ -139,7 +153,7 @@ func (c *CDN) getOrderedLayers(ctx context.Context) ([]*layer, error) { configLayers[configName] = configLayer } else { configOrder = &orderConfig{} - err = json.Unmarshal(file.GetRaw(), configOrder) + err = json.Unmarshal(file, configOrder) if err != nil { // Return first - we can't continue without the order return nil, err @@ -163,17 +177,3 @@ func (c *CDN) getOrderedLayers(ctx context.Context) ([]*layer, error) { return orderedLayers, nil } - -func getHostTags(ctx context.Context, config model.Config) func() []string { - return func() []string { - // Host tags are cached on host, but we add a timeout to avoid blocking the RC request - // if the host tags are not available yet and need to be fetched. They will be fetched - // by the first agent metadata V5 payload. - ctx, cc := context.WithTimeout(ctx, time.Second) - defer cc() - hostTags := hosttags.Get(ctx, true, config) - tags := append(hostTags.System, hostTags.GoogleCloudPlatform...) - tags = append(tags, "installer:true") - return tags - } -}