diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go index cf33122a93b..d983e6fb62f 100644 --- a/pkg/testing/fixture_install.go +++ b/pkg/testing/fixture_install.go @@ -175,6 +175,14 @@ func (i *InstallOpts) ToCmdArgs() []string { // - the combined output of Install command stdout and stderr // - an error if any. func (f *Fixture) Install(ctx context.Context, installOpts *InstallOpts, opts ...process.CmdOption) ([]byte, error) { + return f.installFunc(ctx, installOpts, true, opts...) +} + +func (f *Fixture) InstallWithoutEnroll(ctx context.Context, installOpts *InstallOpts, opts ...process.CmdOption) ([]byte, error) { + return f.installFunc(ctx, installOpts, false, opts...) +} + +func (f *Fixture) installFunc(ctx context.Context, installOpts *InstallOpts, shouldEnroll bool, opts ...process.CmdOption) ([]byte, error) { f.t.Logf("[test %s] Inside fixture install function", f.t.Name()) // check for running agents before installing, but only if not installed into a namespace whose point is allowing two agents at once. @@ -184,11 +192,11 @@ func (f *Fixture) Install(ctx context.Context, installOpts *InstallOpts, opts .. switch f.packageFormat { case "targz", "zip": - return f.installNoPkgManager(ctx, installOpts, opts) + return f.installNoPkgManager(ctx, installOpts, shouldEnroll, opts) case "deb": - return f.installDeb(ctx, installOpts, opts) + return f.installDeb(ctx, installOpts, shouldEnroll, opts) case "rpm": - return f.installRpm(ctx, installOpts, opts) + return f.installRpm(ctx, installOpts, shouldEnroll, opts) default: return nil, fmt.Errorf("package format %s isn't supported yet", f.packageFormat) } @@ -202,14 +210,25 @@ func (f *Fixture) Install(ctx context.Context, installOpts *InstallOpts, opts .. // It returns: // - the combined output of Install command stdout and stderr // - an error if any. -func (f *Fixture) installNoPkgManager(ctx context.Context, installOpts *InstallOpts, opts []process.CmdOption) ([]byte, error) { +func (f *Fixture) installNoPkgManager(ctx context.Context, installOpts *InstallOpts, shouldEnroll bool, opts []process.CmdOption) ([]byte, error) { f.t.Logf("[test %s] Inside fixture installNoPkgManager function", f.t.Name()) if installOpts == nil { // default options when not provided installOpts = &InstallOpts{} } + // Removes install params to prevent enrollment + removeEnrollParams := func(installOpts *InstallOpts) { + installOpts.URL = "" + installOpts.EnrollmentToken = "" + installOpts.ESHost = "" + } + installArgs := []string{"install"} + if !shouldEnroll { + removeEnrollParams(installOpts) + } + installArgs = append(installArgs, installOpts.ToCmdArgs()...) out, err := f.Exec(ctx, installArgs, opts...) if err != nil { @@ -410,7 +429,7 @@ func getProcesses(t *gotesting.T, regex string) []runningProcess { // It returns: // - the combined output of Install command stdout and stderr // - an error if any. -func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, opts []process.CmdOption) ([]byte, error) { +func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, shouldEnroll bool, opts []process.CmdOption) ([]byte, error) { f.t.Logf("[test %s] Inside fixture installDeb function", f.t.Name()) // Prepare so that the f.srcPackage string is populated err := f.EnsurePrepared(ctx) @@ -456,6 +475,10 @@ func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, opts return out, fmt.Errorf("systemctl start elastic-agent failed: %w", err) } + if !shouldEnroll { + return nil, nil + } + // apt install doesn't enroll, so need to do that enrollArgs := []string{"elastic-agent", "enroll"} if installOpts.Force { @@ -491,7 +514,7 @@ func (f *Fixture) installDeb(ctx context.Context, installOpts *InstallOpts, opts // It returns: // - the combined output of Install command stdout and stderr // - an error if any. -func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, opts []process.CmdOption) ([]byte, error) { +func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, shouldEnroll bool, opts []process.CmdOption) ([]byte, error) { f.t.Logf("[test %s] Inside fixture installRpm function", f.t.Name()) // Prepare so that the f.srcPackage string is populated err := f.EnsurePrepared(ctx) @@ -507,6 +530,7 @@ func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, opts f.t.Cleanup(func() { f.t.Logf("[test %s] Inside fixture installRpm cleanup function", f.t.Name()) + uninstallCtx, uninstallCancel := context.WithTimeout(context.Background(), 5*time.Minute) defer uninstallCancel() // stop elastic-agent, non fatal if error, might have been stopped before this. @@ -522,6 +546,14 @@ func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, opts f.t.Logf("failed to 'sudo rpm -e elastic-agent': %s, output: %s", err, string(out)) f.t.FailNow() } + + f.t.Logf("removing installed agent files") + out, err = exec.CommandContext(uninstallCtx, "sudo", "rm", "-rf", "/var/lib/elastic-agent", "/var/log/elastic-agent", "/etc/elastic-agent").CombinedOutput() + if err != nil { + f.t.Log(string(out)) + f.t.Logf("failed to 'sudo rm -rf /var/lib/elastic-agent /var/log/elastic-agent/ /etc/elastic-agent'") + f.t.FailNow() + } }) // start elastic-agent @@ -530,6 +562,10 @@ func (f *Fixture) installRpm(ctx context.Context, installOpts *InstallOpts, opts return out, fmt.Errorf("systemctl start elastic-agent failed: %w", err) } + if !shouldEnroll { + return nil, nil + } + // rpm install doesn't enroll, so need to do that enrollArgs := []string{"elastic-agent", "enroll"} if installOpts.Force { diff --git a/testing/integration/kubernetes_agent_standalone_test.go b/testing/integration/kubernetes_agent_standalone_test.go index f3158b29644..24b91ab0d38 100644 --- a/testing/integration/kubernetes_agent_standalone_test.go +++ b/testing/integration/kubernetes_agent_standalone_test.go @@ -48,6 +48,7 @@ import ( "helm.sh/helm/v3/pkg/cli" helmKube "helm.sh/helm/v3/pkg/kube" + "github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator" aclient "github.com/elastic/elastic-agent/pkg/control/v2/client" atesting "github.com/elastic/elastic-agent/pkg/testing" "github.com/elastic/elastic-agent/pkg/testing/define" @@ -288,6 +289,7 @@ func TestKubernetesAgentHelm(t *testing.T) { k8sStepCheckAgentStatus("name=agent-pernode-helm-agent", schedulableNodeCount, "agent", nil), k8sStepCheckAgentStatus("name=agent-clusterwide-helm-agent", 1, "agent", nil), k8sStepCheckAgentStatus("name=agent-ksmsharded-helm-agent", 1, "agent", nil), + k8sStepCheckRestrictUpgrade("name=agent-pernode-helm-agent", schedulableNodeCount, "agent"), k8sStepRunInnerTests("name=agent-pernode-helm-agent", schedulableNodeCount, "agent"), k8sStepRunInnerTests("name=agent-clusterwide-helm-agent", 1, "agent"), k8sStepRunInnerTests("name=agent-ksmsharded-helm-agent", 1, "agent"), @@ -320,6 +322,7 @@ func TestKubernetesAgentHelm(t *testing.T) { k8sStepCheckAgentStatus("name=agent-pernode-helm-agent", schedulableNodeCount, "agent", nil), k8sStepCheckAgentStatus("name=agent-clusterwide-helm-agent", 1, "agent", nil), k8sStepCheckAgentStatus("name=agent-ksmsharded-helm-agent", 1, "agent", nil), + k8sStepCheckRestrictUpgrade("name=agent-pernode-helm-agent", schedulableNodeCount, "agent"), k8sStepRunInnerTests("name=agent-pernode-helm-agent", schedulableNodeCount, "agent"), k8sStepRunInnerTests("name=agent-clusterwide-helm-agent", 1, "agent"), k8sStepRunInnerTests("name=agent-ksmsharded-helm-agent", 1, "agent"), @@ -1335,3 +1338,23 @@ func k8sStepHintsRedisDelete() k8sTestStep { require.NoError(t, err, "failed to delete redis k8s objects") } } + +func k8sStepCheckRestrictUpgrade(agentPodLabelSelector string, expectedPodNumber int, containerName string) k8sTestStep { + return func(t *testing.T, ctx context.Context, kCtx k8sContext, namespace string) { + perNodePodList := &corev1.PodList{} + err := kCtx.client.Resources(namespace).List(ctx, perNodePodList, func(opt *metav1.ListOptions) { + opt.LabelSelector = agentPodLabelSelector + }) + require.NoError(t, err, "failed to list pods with selector ", perNodePodList) + require.NotEmpty(t, perNodePodList.Items, "no pods found with selector ", perNodePodList) + require.Equal(t, expectedPodNumber, len(perNodePodList.Items), "unexpected number of pods found with selector ", perNodePodList) + for _, pod := range perNodePodList.Items { + var stdout, stderr bytes.Buffer + + command := []string{"elastic-agent", "upgrade", "1.0.0"} + err := kCtx.client.Resources().ExecInPod(ctx, namespace, pod.Name, containerName, command, &stdout, &stderr) + require.Error(t, err) + require.Contains(t, stderr.String(), coordinator.ErrNotUpgradable.Error()) + } + } +} diff --git a/testing/integration/restrict_upgrade_deb_test.go b/testing/integration/restrict_upgrade_deb_test.go new file mode 100644 index 00000000000..80ee581eef3 --- /dev/null +++ b/testing/integration/restrict_upgrade_deb_test.go @@ -0,0 +1,59 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +//go:build integration + +package integration + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator" + atesting "github.com/elastic/elastic-agent/pkg/testing" + "github.com/elastic/elastic-agent/pkg/testing/define" +) + +func TestRestrictUpgradeDeb(t *testing.T) { + define.Require(t, define.Requirements{ + Group: Deb, + Stack: &define.Stack{}, + Sudo: true, + OS: []define.OS{ + { + Type: define.Linux, + Distro: "ubuntu", + }, + }, + }) + t.Run("when agent is deployed via deb, a user should not be able to upgrade the agent using the cli", func(t *testing.T) { + ctx := context.Background() + + fixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("deb")) + require.NoError(t, err) + installOpts := atesting.InstallOpts{ + NonInteractive: true, + Privileged: true, + Force: true, + } + + _, err = fixture.InstallWithoutEnroll(ctx, &installOpts) + require.NoError(t, err) + + require.Eventuallyf(t, func() bool { + err = fixture.IsHealthy(ctx) + return err == nil + }, 5*time.Minute, time.Second, + "Elastic-Agent did not report healthy. Agent status error: \"%v\"", + err, + ) + + out, err := fixture.Exec(ctx, []string{"upgrade", "1.0.0"}) + require.Error(t, err) + require.Contains(t, string(out), coordinator.ErrNotUpgradable.Error()) + }) +} diff --git a/testing/integration/restrict_upgrade_rpm_test.go b/testing/integration/restrict_upgrade_rpm_test.go new file mode 100644 index 00000000000..5b1d8e31607 --- /dev/null +++ b/testing/integration/restrict_upgrade_rpm_test.go @@ -0,0 +1,59 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +//go:build integration + +package integration + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator" + atesting "github.com/elastic/elastic-agent/pkg/testing" + "github.com/elastic/elastic-agent/pkg/testing/define" +) + +func TestRestrictUpgradeRPM(t *testing.T) { + define.Require(t, define.Requirements{ + Group: RPM, + Stack: &define.Stack{}, + Sudo: true, + OS: []define.OS{ + { + Type: define.Linux, + Distro: "rhel", + }, + }, + }) + t.Run("when agent is deployed via rpm, a user should not be able to upgrade the agent using the cli", func(t *testing.T) { + ctx := context.Background() + + fixture, err := define.NewFixtureFromLocalBuild(t, define.Version(), atesting.WithPackageFormat("rpm")) + require.NoError(t, err) + installOpts := atesting.InstallOpts{ + NonInteractive: true, + Privileged: true, + Force: true, + } + + _, err = fixture.InstallWithoutEnroll(ctx, &installOpts) + require.NoError(t, err) + + require.Eventuallyf(t, func() bool { + err = fixture.IsHealthy(ctx) + return err == nil + }, 5*time.Minute, time.Second, + "Elastic-Agent did not report healthy. Agent status error: \"%v\"", + err, + ) + + out, err := fixture.Exec(ctx, []string{"upgrade", "1.0.0"}) + require.Error(t, err) + require.Contains(t, string(out), coordinator.ErrNotUpgradable.Error()) + }) +}