Skip to content

Commit

Permalink
Fix endpoint unenroll test (#3086) (#3103)
Browse files Browse the repository at this point in the history
* Add test to cover endpoint uninstall via unenroll.

* Remove duplicated test code.

* Initial fix for endpoint uninstall not working.

* Fix tests, tidy up code.

* Improve comments.

* Ensure uninstall doesn't run forever.

* Document test.

* Fixing typos

* Undo changes except integration test

---------

Co-authored-by: Shaunak Kashyap <[email protected]>
(cherry picked from commit d8a234c)

Co-authored-by: Craig MacKenzie <[email protected]>
  • Loading branch information
mergify[bot] and cmacknz authored Jul 19, 2023
1 parent dc443bc commit 18b2242
Showing 1 changed file with 165 additions and 39 deletions.
204 changes: 165 additions & 39 deletions testing/integration/endpoint_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"context"
_ "embed"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"text/template"
Expand Down Expand Up @@ -49,9 +51,9 @@ type endpointPackageTemplateVars struct {
// installed. The endpoint-security service is uninstalled when the agent is uninstalled.
//
// The agent is automatically uninstalled as part of test cleanup when installed with
// fxiture.Install via tools.InstallAgentWithPolicy. Failure to uninstall the agent will fail the
// fixture.Install via tools.InstallAgentWithPolicy. Failure to uninstall the agent will fail the
// test automatically.
func TestEndpointSecurity(t *testing.T) {
func TestInstallAndCLIUninstallWithEndpointSecurity(t *testing.T) {
info := define.Require(t, define.Requirements{
Stack: &define.Stack{},
Local: false, // requires Agent installation
Expand Down Expand Up @@ -87,54 +89,129 @@ func TestEndpointSecurity(t *testing.T) {
err = agentClient.Connect(ctx)
require.NoError(t, err)

healthyEndpointFunc := func() bool {
state, err := agentClient.State(ctx)
if err != nil {
t.Logf("Error getting agent state: %s", err)
return false
}
require.Eventually(t,
func() bool { return agentAndEndpointAreHealthy(t, ctx, agentClient) },
endpointHealthPollingTimeout,
time.Second,
"Endpoint component or units are not healthy.",
)
t.Log("Verified endpoint component and units are healthy")
}

if state.State != client.Healthy {
t.Logf("Agent is not Healthy\n%+v", state)
return false
}
// Tests that the agent can install and uninstall the endpoint-security service while remaining
// healthy. In this case endpoint-security is uninstalled because the agent was unenrolled, which
// triggers the creation of an empty agent policy removing all inputs (only when not force
// unenrolling). The empty agent policy triggers the uninstall of endpoint because endpoint was
// removed from the policy.
//
// Like the CLI uninstall test, the agent is uninstalled from the command line at the end of the test
// but at this point endpoint is already uninstalled at this point.
func TestInstallAndUnenrollWithEndpointSecurity(t *testing.T) {
info := define.Require(t, define.Requirements{
Stack: &define.Stack{},
Local: false, // requires Agent installation
Isolate: false,
Sudo: true, // requires Agent installation
OS: []define.OS{
define.OS{Type: define.Linux, Arch: define.AMD64},
},
})

// Get path to agent executable.
fixture, err := define.NewFixture(t, define.Version())
require.NoError(t, err)

t.Log("Enrolling the agent in Fleet")
policyUUID := uuid.New().String()
createPolicyReq := kibana.AgentPolicy{
Name: "test-policy-" + policyUUID,
Namespace: "default",
Description: "Test policy " + policyUUID,
MonitoringEnabled: []kibana.MonitoringEnabledOption{
kibana.MonitoringEnabledLogs,
kibana.MonitoringEnabledMetrics,
},
}
policyResp, err := tools.InstallAgentWithPolicy(t, fixture, info.KibanaClient, createPolicyReq)

t.Log("Installing Elastic Defend")
installElasticDefendPackage(t, info, policyResp.ID)

t.Log("Polling for endpoint-security to become Healthy")
ctx, cancel := context.WithTimeout(context.Background(), endpointHealthPollingTimeout)
defer cancel()

agentClient := fixture.Client()
err = agentClient.Connect(ctx)
require.NoError(t, err)

require.Eventually(t,
func() bool { return agentAndEndpointAreHealthy(t, ctx, agentClient) },
endpointHealthPollingTimeout,
time.Second,
"Endpoint component or units are not healthy.",
)
t.Log("Verified endpoint component and units are healthy")

foundEndpointInputUnit := false
foundEndpointOutputUnit := false
for _, comp := range state.Components {
isEndpointComponent := strings.Contains(comp.Name, "endpoint")
if comp.State != client.Healthy {
t.Logf("Component is not Healthy\n%+v", comp)
// Unenroll the agent
t.Log("Unenrolling the agent")

hostname, err := os.Hostname()
require.NoError(t, err)

agentID, err := tools.GetAgentIDByHostname(info.KibanaClient, hostname)
require.NoError(t, err)

_, err = info.KibanaClient.UnEnrollAgent(kibana.UnEnrollAgentRequest{ID: agentID})
require.NoError(t, err)

t.Log("Waiting for inputs to stop")
require.Eventually(t,
func() bool {
state, err := agentClient.State(ctx)
if err != nil {
t.Logf("Error getting agent state: %s", err)
return false
}

for _, unit := range comp.Units {
if isEndpointComponent {
if unit.UnitType == client.UnitTypeInput {
foundEndpointInputUnit = true
}
if unit.UnitType == client.UnitTypeOutput {
foundEndpointOutputUnit = true
}
}
if state.State != client.Healthy {
t.Logf("Agent is not Healthy\n%+v", state)
return false
}

if unit.State != client.Healthy {
t.Logf("Unit is not Healthy\n%+v", unit)
return false
}
if len(state.Components) != 0 {
t.Logf("Components have not been stopped and uninstalled!\n%+v", state)
return false
}
}

// Ensure both the endpoint input and output units were found and healthy.
if !foundEndpointInputUnit || !foundEndpointOutputUnit {
t.Logf("State did not contain endpoint units!\n%+v", state)
return false
if state.FleetState != client.Failed {
t.Logf("Fleet state has not been marked as failed yet!\n%+v", state)
return false
}

return true
},
endpointHealthPollingTimeout,
time.Second,
"All components not removed.",
)
t.Log("Verified endpoint component and units are removed")

// Verify that the Endpoint directory was correctly removed.
// Regression test for https://github.com/elastic/elastic-agent/issues/3077
agentInstallPath := fixture.WorkDir()
files, err := os.ReadDir(filepath.Clean(filepath.Join(agentInstallPath, "..")))
require.NoError(t, err)

t.Logf("Checking directories at install path %s", agentInstallPath)
for _, f := range files {
if !f.IsDir() {
continue
}

return true
t.Log("Found directory", f.Name())
require.False(t, strings.Contains(f.Name(), "Endpoint"), "Endpoint directory was not removed")
}
require.Eventually(t, healthyEndpointFunc, endpointHealthPollingTimeout, time.Second, "Endpoint component or units are not healthy.")
t.Logf("Verified endpoint component and units are healthy")
}

// Installs the Elastic Defend package to cause the agent to install the endpoint-security service.
Expand Down Expand Up @@ -169,3 +246,52 @@ func installElasticDefendPackage(t *testing.T, info *define.Info, policyID strin
require.NoError(t, err)
t.Logf("Endpoint package Policy Response:\n%+v", pkgResp)
}

func agentAndEndpointAreHealthy(t *testing.T, ctx context.Context, agentClient client.Client) bool {
t.Helper()

state, err := agentClient.State(ctx)
if err != nil {
t.Logf("Error getting agent state: %s", err)
return false
}

if state.State != client.Healthy {
t.Logf("Agent is not Healthy\n%+v", state)
return false
}

foundEndpointInputUnit := false
foundEndpointOutputUnit := false
for _, comp := range state.Components {
isEndpointComponent := strings.Contains(comp.Name, "endpoint")
if comp.State != client.Healthy {
t.Logf("Component is not Healthy\n%+v", comp)
return false
}

for _, unit := range comp.Units {
if isEndpointComponent {
if unit.UnitType == client.UnitTypeInput {
foundEndpointInputUnit = true
}
if unit.UnitType == client.UnitTypeOutput {
foundEndpointOutputUnit = true
}
}

if unit.State != client.Healthy {
t.Logf("Unit is not Healthy\n%+v", unit)
return false
}
}
}

// Ensure both the endpoint input and output units were found and healthy.
if !foundEndpointInputUnit || !foundEndpointOutputUnit {
t.Logf("State did not contain endpoint units!\n%+v", state)
return false
}

return true
}

0 comments on commit 18b2242

Please sign in to comment.