Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/remoteconfig: client error handling (backport of release-v1.45.x fixes) #1627

Merged
merged 5 commits into from
Dec 28, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions internal/appsec/remoteconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ func (a *appsec) asmFeaturesCallback(u remoteconfig.ProductUpdate) map[string]rc
a.stop()
}
if err != nil {
status.State = rc.ApplyStateError
status.Error = err.Error()
status = genApplyStatus(false, err)
}
statuses[path] = status
}
Expand All @@ -97,6 +96,15 @@ func (h *wafHandleWrapper) asmDataCallback(u remoteconfig.ProductUpdate) map[str

for path, raw := range u {
log.Debug("appsec: Remote config: processing %s", path)

// A nil config means ASM_DATA was disabled, and we stopped receiving the config file
// Don't ack the config in this case
if raw == nil {
log.Debug("appsec: remote config: %s disabled", path)
statuses[path] = genApplyStatus(false, nil)
continue
}

var rulesData rc.ASMDataRulesData
if err := json.Unmarshal(raw, &rulesData); err != nil {
log.Debug("appsec: Remote config: error while unmarshalling payload for %s: %v. Configuration won't be applied.", path, err)
Expand Down
89 changes: 56 additions & 33 deletions internal/remoteconfig/remoteconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
rc "github.com/DataDog/datadog-agent/pkg/remoteconfig/state"

"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
)

Expand Down Expand Up @@ -148,19 +149,19 @@ func (c *Client) Stop() {
func (c *Client) updateState() {
data, err := c.newUpdateRequest()
if err != nil {
c.lastError = err
log.Error("remoteconfig: unexpected error while creating a new update request payload: %v", err)
return
}

req, err := http.NewRequest(http.MethodGet, c.endpoint, &data)
if err != nil {
c.lastError = err
log.Error("remoteconfig: unexpected error while creating a new http request: %v", err)
return
}

resp, err := c.HTTP.Do(req)
if err != nil {
c.lastError = err
log.Debug("remoteconfig: http request error: %v", err)
return
}
// Flush and close the response body when returning (cf. https://pkg.go.dev/net/http#Client.Do)
Expand All @@ -169,15 +170,28 @@ func (c *Client) updateState() {
resp.Body.Close()
}()

var update clientGetConfigsResponse
err = json.NewDecoder(resp.Body).Decode(&update)
if sc := resp.StatusCode; sc != http.StatusOK {
log.Debug("remoteconfig: http request error: response status code is not 200 (OK) but %s", http.StatusText(sc))
return
}

respBody, err := io.ReadAll(resp.Body)
if err != nil {
c.lastError = err
log.Error("remoteconfig: http request error: could not read the response body: %v", err)
return
}

err = c.applyUpdate(&update)
c.lastError = err
if body := string(respBody); body == `{}` || body == `null` {
return
}
Hellzy marked this conversation as resolved.
Show resolved Hide resolved

var update clientGetConfigsResponse
if err := json.Unmarshal(respBody, &update); err != nil {
log.Error("remoteconfig: http request error: could not parse the json response body: %v", err)
return
}

c.lastError = c.applyUpdate(&update)
}

// RegisterCallback allows registering a callback that will be invoked when the client
Expand All @@ -199,13 +213,6 @@ func (c *Client) applyUpdate(pbUpdate *clientGetConfigsResponse) error {
}
}

update := rc.Update{
TUFRoots: pbUpdate.Roots,
TUFTargets: pbUpdate.Targets,
TargetFiles: fileMap,
ClientConfigs: pbUpdate.ClientConfigs,
}

mapify := func(s *rc.RepositoryState) map[string]string {
m := make(map[string]string)
for i := range s.Configs {
Expand All @@ -219,30 +226,43 @@ func (c *Client) applyUpdate(pbUpdate *clientGetConfigsResponse) error {
// Check the repository state before and after the update to detect which configs are not being sent anymore.
// This is needed because some products can stop sending configurations, and we want to make sure that the subscribers
// are provided with this information in this case
stateBefore, _ := c.repository.CurrentState()
products, err := c.repository.Update(update)
stateAfter, _ := c.repository.CurrentState()
stateBefore, err := c.repository.CurrentState()
if err != nil {
return fmt.Errorf("repository current state error: %v", err)
}
products, err := c.repository.Update(rc.Update{
TUFRoots: pbUpdate.Roots,
TUFTargets: pbUpdate.Targets,
TargetFiles: fileMap,
ClientConfigs: pbUpdate.ClientConfigs,
})
if err != nil {
return fmt.Errorf("repository update error: %v", err)
}
stateAfter, err := c.repository.CurrentState()
if err != nil {
return fmt.Errorf("repository current state error after update: %v", err)
}

// Create a config files diff between before/after the update to see which config files are missing
mBefore := mapify(&stateBefore)
mAfter := mapify(&stateAfter)
for k := range mAfter {
for k := range mapify(&stateAfter) {
delete(mBefore, k)
}

// Set the payload data to nil for missing config files. The callbacks then can handle the nil config case to detect
// that this config will not be updated anymore.
updatedProducts := make(map[string]bool)
updatedProducts := make(map[string]struct{})
for path, product := range mBefore {
if productUpdates[product] == nil {
productUpdates[product] = make(ProductUpdate)
}
productUpdates[product][path] = nil
updatedProducts[product] = true
updatedProducts[product] = struct{}{}
}
// Aggregate updated products and missing products so that callbacks get called for both
for _, p := range products {
updatedProducts[p] = true
updatedProducts[p] = struct{}{}
}

// Performs the callbacks registered for all updated products and update the application status in the repository
Expand All @@ -255,7 +275,7 @@ func (c *Client) applyUpdate(pbUpdate *clientGetConfigsResponse) error {
}
}

return err
return nil
}

func (c *Client) newUpdateRequest() (bytes.Buffer, error) {
Expand Down Expand Up @@ -290,15 +310,18 @@ func (c *Client) newUpdateRequest() (bytes.Buffer, error) {
errMsg = c.lastError.Error()
}

pbConfigState := make([]*configState, 0, len(state.Configs))
for _, f := range state.Configs {
pbConfigState = append(pbConfigState, &configState{
ID: f.ID,
Version: f.Version,
Product: f.Product,
ApplyState: f.ApplyStatus.State,
ApplyError: f.ApplyStatus.Error,
})
var pbConfigState []*configState
if !hasError {
pbConfigState = make([]*configState, 0, len(state.Configs))
for _, f := range state.Configs {
pbConfigState = append(pbConfigState, &configState{
ID: f.ID,
Version: f.Version,
Product: f.Product,
ApplyState: f.ApplyStatus.State,
ApplyError: f.ApplyStatus.Error,
})
}
}

cap := big.NewInt(0)
Expand Down