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

Tamper protected Endpoint integration removal #2747

Closed
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
# - bug-fix: fixes a problem in a previous version
# - enhancement: extends functionality but does not break or fix existing behavior
# - feature: new functionality
# - known-issue: problems that we are aware of in a given version
# - security: impacts on the security of a product or a user’s deployment.
# - upgrade: important information for someone upgrading from a prior version
# - other: does not fit into any of the other categories
kind: feature

# Change summary; a 80ish characters long description of the change.
summary: Tamper protected Endpoint integration removal

# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
#description:

# Affected component; a word indicating the component this changeset affects.
component:

# PR URL; optional; the PR number that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
pr: https://github.com/elastic/elastic-agent/pull/2747

# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
issue: https://github.com/elastic/ingest-dev/issues/1882
22 changes: 19 additions & 3 deletions internal/pkg/agent/application/coordinator/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type RuntimeManager interface {
Runner

// Update updates the current components model.
Update([]component.Component) error
Update(model component.Model) error

// State returns the current components model state.
State() []runtime.ComponentComponentState
Expand Down Expand Up @@ -813,14 +813,30 @@ func (c *Coordinator) process(ctx context.Context) (err error) {
span.End()
}()

_, comps, err := c.compute()
cfg, comps, err := c.compute()
if err != nil {
return err
}

signed, err := component.SignedFromPolicy(cfg)
if err != nil {
if !errors.Is(err, component.ErrNotFound) {
c.logger.Errorf("Failed to parse \"signed\" properties: %v", err)
return err
}

// Some "signed" properties are not found, continue.
c.logger.Debugf("Continue with missing \"signed\" properties: %v", err)
}

model := component.Model{
Components: comps,
Signed: signed,
}

c.logger.Info("Updating running component model")
c.logger.With("components", comps).Debug("Updating running component model")
err = c.runtimeMgr.Update(comps)
err = c.runtimeMgr.Update(model)
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func TestCoordinatorDiagnosticHooks(t *testing.T) {
componentsUpdateChannel := make(chan runtime.ComponentComponentState)
subscriptionAll := runtime.NewSubscriptionAllWithChannel(ctx, componentsUpdateChannel)
helper.runtimeManager.EXPECT().SubscribeAll(mock.Anything).Return(subscriptionAll)
helper.runtimeManager.EXPECT().Update(mock.AnythingOfType("[]component.Component")).Return(nil)
helper.runtimeManager.EXPECT().Update(mock.AnythingOfType("component.Model")).Return(nil)

sut := helper.coordinator
coordinatorWg := new(sync.WaitGroup)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions pkg/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package component

import (
"errors"
"fmt"
"sort"
"strings"
Expand Down Expand Up @@ -76,6 +77,63 @@ type Unit struct {
Err error `yaml:"error,omitempty"`
}

// Signed Strongly typed configuration for the signed data
type Signed struct {
Data string `yaml:"data"`
Signature string `yaml:"signature"`
}

// IsSigned Checks if the signature exists, safe to call on nil
func (s *Signed) IsSigned() bool {
return (s != nil && (len(s.Signature) > 0))
}

// ErrNotFound is returned if the expected "signed" property itself or it's expected properties are missing or not a valid data type
var ErrNotFound = errors.New("not found")

// SignedFromPolicy Returns Signed instance from the nested map representation of the agent configuration
func SignedFromPolicy(policy map[string]interface{}) (*Signed, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add comments to each exposed struct and function. In proper format of // {name} {description}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added comments

v, ok := policy["signed"]
if !ok {
return nil, fmt.Errorf("policy is not signed: %w", ErrNotFound)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this maybe be a defined error? ErrNotSigned?

Copy link
Contributor Author

@aleksmaus aleksmaus Jun 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from general use case point of view, it's the same meaning, we care if we have data or not, same for the tests.
The fmt.Errorf adds additional context to ErrNotFound, which can be logged or propagated up.
It might be more convenient if we could wrap multierror probably like it's possible with Go 1.20
https://tip.golang.org/doc/go1.20#errors

}

signed, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("policy \"signed\" is not map: %w", ErrNotFound)
}

data, err := getStringValue(signed, "data")
if err != nil {
return nil, err
}

signature, err := getStringValue(signed, "signature")
if err != nil {
return nil, err
}

res := &Signed{
Data: data,
Signature: signature,
}
return res, nil
}

func getStringValue(m map[string]interface{}, key string) (string, error) {
v, ok := m[key]
if !ok {
return "", fmt.Errorf("missing signed \"%s\": %w", key, ErrNotFound)
}

s, ok := v.(string)
if !ok {
return "", fmt.Errorf("signed \"%s\" is not string: %w", key, ErrNotFound)
}

return s, nil
}

// Component is a set of units that needs to run.
type Component struct {
// ID is the unique ID of the component.
Expand Down Expand Up @@ -111,6 +169,12 @@ func (c *Component) Type() string {
return ""
}

// Model components model
type Model struct {
Components []Component `yaml:"components,omitempty"`
Signed *Signed `yaml:"signed,omitempty"`
}

// ToComponents returns the components that should be running based on the policy and
// the current runtime specification.
func (r *RuntimeSpecs) ToComponents(
Expand Down
154 changes: 151 additions & 3 deletions pkg/component/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ import (
"sort"
"testing"

"github.com/elastic/elastic-agent-client/v7/pkg/client"
"github.com/elastic/elastic-agent-client/v7/pkg/proto"
"github.com/elastic/elastic-agent-libs/logp"

"github.com/elastic/elastic-agent/internal/pkg/agent/transpiler"
"github.com/elastic/elastic-agent/internal/pkg/eql"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
"gopkg.in/yaml.v2"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/elastic-agent-client/v7/pkg/client"
"github.com/elastic/elastic-agent-client/v7/pkg/proto"
)

func TestToComponents(t *testing.T) {
Expand Down Expand Up @@ -2031,3 +2033,149 @@ type testHeadersProvider struct {
func (h *testHeadersProvider) Headers() map[string]string {
return h.headers
}

// TestSignedMarshalUnmarshal will catch if the yaml library will get updated to v3 for example
func TestSignedMarshalUnmarshal(t *testing.T) {
const data = "eyJAdGltZXN0YW1wIjoiMjAyMy0wNS0yMlQxNzoxOToyOC40NjNaIiwiZXhwaXJhdGlvbiI6IjIwMjMtMDYtMjFUMTc6MTk6MjguNDYzWiIsImFnZW50cyI6WyI3ZjY0YWI2NC1hNmM0LTQ2ZTMtODIyYS0zODUxZGVkYTJmY2UiXSwiYWN0aW9uX2lkIjoiNGYwODQ2MGYtMDE0Yy00ZDllLWJmOGEtY2FhNjQyNzRhZGU0IiwidHlwZSI6IlVORU5ST0xMIiwidHJhY2VwYXJlbnQiOiIwMC1iOTBkYTlmOGNjNzdhODk0OTc0ZWIxZTIzMGNmNjc2Yy1lOTNlNzk4YTU4ODg2MDVhLTAxIn0="
const signature = "MEUCIAxxsi9ff1zyV0+4fsJLqbP8Qb83tedU5iIFldtxEzEfAiEA0KUsrL7q+Fv7z6Boux3dY2P4emGi71jsMGanIZ552bM="

signed := Signed{
Data: data,
Signature: signature,
}

b, err := yaml.Marshal(signed)
if err != nil {
t.Fatal(err)
}

var newSigned Signed
err = yaml.Unmarshal(b, &newSigned)
if err != nil {
t.Fatal(err)
}

diff := cmp.Diff(signed, newSigned)
if diff != "" {
t.Fatal(diff)
}

diff = cmp.Diff(true, signed.IsSigned())
if diff != "" {
t.Fatal(diff)
}

var nilSigned *Signed
diff = cmp.Diff(false, nilSigned.IsSigned())
if diff != "" {
t.Fatal(diff)
}

unsigned := Signed{}
diff = cmp.Diff(false, unsigned.IsSigned())
if diff != "" {
t.Fatal(diff)
}
}

func TestSignedFromPolicy(t *testing.T) {
const data = "eyJAdGltZXN0YW1wIjoiMjAyMy0wNS0yMlQxNzoxOToyOC40NjNaIiwiZXhwaXJhdGlvbiI6IjIwMjMtMDYtMjFUMTc6MTk6MjguNDYzWiIsImFnZW50cyI6WyI3ZjY0YWI2NC1hNmM0LTQ2ZTMtODIyYS0zODUxZGVkYTJmY2UiXSwiYWN0aW9uX2lkIjoiNGYwODQ2MGYtMDE0Yy00ZDllLWJmOGEtY2FhNjQyNzRhZGU0IiwidHlwZSI6IlVORU5ST0xMIiwidHJhY2VwYXJlbnQiOiIwMC1iOTBkYTlmOGNjNzdhODk0OTc0ZWIxZTIzMGNmNjc2Yy1lOTNlNzk4YTU4ODg2MDVhLTAxIn0="
const signature = "MEUCIAxxsi9ff1zyV0+4fsJLqbP8Qb83tedU5iIFldtxEzEfAiEA0KUsrL7q+Fv7z6Boux3dY2P4emGi71jsMGanIZ552bM="

tests := []struct {
name string
policy map[string]interface{}
wantSigned *Signed
wantErr error
}{
{
name: "not signed",
wantErr: ErrNotFound,
},
{
name: "signed nil",
policy: map[string]interface{}{
"signed": nil,
},
wantErr: ErrNotFound,
},
{
name: "signed not map",
policy: map[string]interface{}{
"signed": "",
},
wantErr: ErrNotFound,
},
{
name: "signed empty",
policy: map[string]interface{}{
"signed": map[string]interface{}{},
},
wantErr: ErrNotFound,
},
{
name: "signed missing signature",
policy: map[string]interface{}{
"signed": map[string]interface{}{
"data": data,
},
},
wantErr: ErrNotFound,
},
{
name: "signed missing data",
policy: map[string]interface{}{
"signed": map[string]interface{}{
"signaure": signature,
},
},
wantErr: ErrNotFound,
},
{
name: "signed data invalid data type",
policy: map[string]interface{}{
"signed": map[string]interface{}{
"data": 1,
},
},
wantErr: ErrNotFound,
},
{
name: "signed signature invalid data type",
policy: map[string]interface{}{
"signed": map[string]interface{}{
"signature": 1,
},
},
wantErr: ErrNotFound,
},
{
name: "signed correct",
policy: map[string]interface{}{
"signed": map[string]interface{}{
"data": data,
"signature": signature,
},
},
wantSigned: &Signed{
Data: data,
Signature: signature,
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
signed, err := SignedFromPolicy(tc.policy)
diff := cmp.Diff(tc.wantSigned, signed)
if diff != "" {
t.Fatal(diff)
}

diff = cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors())
if diff != "" {
t.Fatal(diff)
}
})
}
}
2 changes: 1 addition & 1 deletion pkg/component/runtime/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func (c *CommandRuntime) Stop() error {
// Teardown tears down the component.
//
// Non-blocking and never returns an error.
func (c *CommandRuntime) Teardown() error {
func (c *CommandRuntime) Teardown(_ *component.Signed) error {
// clear channel so it's the latest action
select {
case <-c.actionCh:
Expand Down
Loading