Skip to content

Commit 16e054b

Browse files
committed
Add Fleet-managed Agent FIPS upgrade test
1 parent b2d1421 commit 16e054b

File tree

8 files changed

+157
-61
lines changed

8 files changed

+157
-61
lines changed

pkg/testing/define/define.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,40 @@ func NewFixtureFromLocalBuild(t *testing.T, version string, opts ...atesting.Fix
9292
buildsDir = filepath.Join(projectDir, "build", "distributions")
9393
}
9494

95-
return NewFixtureWithBinary(t, version, "elastic-agent", buildsDir, opts...)
95+
return NewFixtureWithBinary(t, version, "elastic-agent", buildsDir, false, opts...)
96+
}
9697

98+
// NewFixtureFromLocalFIPSBuild returns a new FIPS-capable Elastic Agent testing fixture with a LocalFetcher
99+
// and the agent logging to the test logger.
100+
func NewFixtureFromLocalFIPSBuild(t *testing.T, version string, opts ...atesting.FixtureOpt) (*atesting.Fixture, error) {
101+
buildsDir := os.Getenv("AGENT_BUILD_DIR")
102+
if buildsDir == "" {
103+
projectDir, err := findProjectRoot()
104+
if err != nil {
105+
return nil, err
106+
}
107+
buildsDir = filepath.Join(projectDir, "build", "distributions")
108+
}
109+
110+
return NewFixtureWithBinary(t, version, "elastic-agent", buildsDir, true, opts...)
97111
}
98112

99113
// NewFixtureWithBinary returns a new Elastic Agent testing fixture with a LocalFetcher and
100114
// the agent logging to the test logger.
101-
func NewFixtureWithBinary(t *testing.T, version string, binary string, buildsDir string, opts ...atesting.FixtureOpt) (*atesting.Fixture, error) {
115+
func NewFixtureWithBinary(t *testing.T, version string, binary string, buildsDir string, fips bool, opts ...atesting.FixtureOpt) (*atesting.Fixture, error) {
102116
ver, err := semver.ParseVersion(version)
103117
if err != nil {
104118
return nil, fmt.Errorf("%q is an invalid agent version: %w", version, err)
105119
}
106120

107-
var binFetcher atesting.Fetcher
121+
localFetcherOpts := []atesting.LocalFetcherOpt{atesting.WithCustomBinaryName(binary)}
108122
if ver.IsSnapshot() {
109-
binFetcher = atesting.LocalFetcher(buildsDir, atesting.WithLocalSnapshotOnly(), atesting.WithCustomBinaryName(binary))
110-
} else {
111-
binFetcher = atesting.LocalFetcher(buildsDir, atesting.WithCustomBinaryName(binary))
123+
localFetcherOpts = append(localFetcherOpts, atesting.WithLocalSnapshotOnly())
124+
}
125+
if fips {
126+
localFetcherOpts = append(localFetcherOpts, atesting.WithLocalFIPSOnly())
112127
}
128+
binFetcher := atesting.LocalFetcher(buildsDir, localFetcherOpts...)
113129

114130
opts = append(opts, atesting.WithFetcher(binFetcher), atesting.WithLogOutput())
115131
if binary != "elastic-agent" {

pkg/testing/fetcher_local.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,35 @@ import (
1818
type localFetcher struct {
1919
dir string
2020
snapshotOnly bool
21+
fipsOnly bool
2122
binaryName string
2223
}
2324

24-
type localFetcherOpt func(f *localFetcher)
25+
type LocalFetcherOpt func(f *localFetcher)
2526

2627
// WithLocalSnapshotOnly sets the LocalFetcher to only pull the snapshot build.
27-
func WithLocalSnapshotOnly() localFetcherOpt {
28+
func WithLocalSnapshotOnly() LocalFetcherOpt {
2829
return func(f *localFetcher) {
2930
f.snapshotOnly = true
3031
}
3132
}
3233

34+
// WithLocalFIPSOnly sets the LocalFetcher to only pull a FIPS-compliant build.
35+
func WithLocalFIPSOnly() LocalFetcherOpt {
36+
return func(f *localFetcher) {
37+
f.fipsOnly = true
38+
}
39+
}
40+
3341
// WithCustomBinaryName sets the binary to a custom name, the default is `elastic-agent`
34-
func WithCustomBinaryName(name string) localFetcherOpt {
42+
func WithCustomBinaryName(name string) LocalFetcherOpt {
3543
return func(f *localFetcher) {
3644
f.binaryName = name
3745
}
3846
}
3947

4048
// LocalFetcher returns a fetcher that pulls the binary of the Elastic Agent from a local location.
41-
func LocalFetcher(dir string, opts ...localFetcherOpt) Fetcher {
49+
func LocalFetcher(dir string, opts ...LocalFetcherOpt) Fetcher {
4250
f := &localFetcher{
4351
dir: dir,
4452
binaryName: "elastic-agent",
@@ -56,6 +64,7 @@ func (f *localFetcher) Name() string {
5664

5765
// Fetch fetches the Elastic Agent and places the resulting binary at the path.
5866
func (f *localFetcher) Fetch(_ context.Context, operatingSystem string, architecture string, version string, packageFormat string) (FetcherResult, error) {
67+
prefix := GetPackagePrefix(f.fipsOnly)
5968
suffix, err := GetPackageSuffix(operatingSystem, architecture, packageFormat)
6069
if err != nil {
6170
return nil, err
@@ -66,7 +75,7 @@ func (f *localFetcher) Fetch(_ context.Context, operatingSystem string, architec
6675
return nil, fmt.Errorf("invalid version: %q: %w", ver, err)
6776
}
6877

69-
mainBuildfmt := "%s-%s-%s"
78+
mainBuildfmt := "%s-%s%s-%s"
7079
if f.snapshotOnly && !ver.IsSnapshot() {
7180
if ver.Prerelease() == "" {
7281
ver = semver.NewParsedSemVer(ver.Major(), ver.Minor(), ver.Patch(), "SNAPSHOT", ver.BuildMetadata())
@@ -85,10 +94,10 @@ func (f *localFetcher) Fetch(_ context.Context, operatingSystem string, architec
8594
}
8695

8796
if ver.IsSnapshot() && !matchesEarlyReleaseVersion {
88-
build := fmt.Sprintf(mainBuildfmt, f.binaryName, ver.VersionWithPrerelease(), suffix)
97+
build := fmt.Sprintf(mainBuildfmt, f.binaryName, prefix, ver.VersionWithPrerelease(), suffix)
8998
buildPath = filepath.Join(ver.BuildMetadata(), build)
9099
} else {
91-
buildPath = fmt.Sprintf(mainBuildfmt, f.binaryName, ver.String(), suffix)
100+
buildPath = fmt.Sprintf(mainBuildfmt, f.binaryName, prefix, ver.String(), suffix)
92101
}
93102

94103
fullPath := filepath.Join(f.dir, buildPath)

pkg/testing/fetcher_local_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestLocalFetcher(t *testing.T) {
5757
tcs := []struct {
5858
name string
5959
version string
60-
opts []localFetcherOpt
60+
opts []LocalFetcherOpt
6161
want []byte
6262
wantHash []byte
6363
}{
@@ -69,7 +69,7 @@ func TestLocalFetcher(t *testing.T) {
6969
}, {
7070
name: "SnapshotOnly",
7171
version: baseVersion,
72-
opts: []localFetcherOpt{WithLocalSnapshotOnly()},
72+
opts: []LocalFetcherOpt{WithLocalSnapshotOnly()},
7373
want: snapshotContent,
7474
wantHash: snapshotContentHash,
7575
}, {
@@ -80,13 +80,13 @@ func TestLocalFetcher(t *testing.T) {
8080
}, {
8181
name: "version with snapshot and SnapshotOnly",
8282
version: baseVersion + "-SNAPSHOT",
83-
opts: []localFetcherOpt{WithLocalSnapshotOnly()},
83+
opts: []LocalFetcherOpt{WithLocalSnapshotOnly()},
8484
want: snapshotContent,
8585
wantHash: snapshotContentHash,
8686
}, {
8787
name: "version with snapshot and build ID",
8888
version: baseVersion + "-SNAPSHOT+l5snflwr",
89-
opts: []localFetcherOpt{},
89+
opts: []LocalFetcherOpt{},
9090
want: snapshotContent,
9191
wantHash: snapshotContentHash,
9292
},

pkg/testing/fixture.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type Fixture struct {
5050
binaryName string
5151
runLength time.Duration
5252
additionalArgs []string
53+
fipsArtifact bool
5354

5455
srcPackage string
5556
workDir string
@@ -145,6 +146,12 @@ func WithAdditionalArgs(args []string) FixtureOpt {
145146
}
146147
}
147148

149+
func WithFIPSArtifact() FixtureOpt {
150+
return func(f *Fixture) {
151+
f.fipsArtifact = true
152+
}
153+
}
154+
148155
// NewFixture creates a new fixture to setup and manage Elastic Agent.
149156
func NewFixture(t *testing.T, version string, opts ...FixtureOpt) (*Fixture, error) {
150157
// we store the caller so the fixture can find the cache directory for the artifacts that

testing/integration/beats_serverless_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func (runner *BeatRunner) SetupSuite() {
7878
}
7979
runner.T().Logf("running serverless tests with %s", runner.testbeatName)
8080

81-
agentFixture, err := define.NewFixtureWithBinary(runner.T(), define.Version(), runner.testbeatName, "/home/ubuntu", atesting.WithRunLength(time.Minute*3), atesting.WithAdditionalArgs([]string{"-E", "output.elasticsearch.allow_older_versions=true"}))
81+
agentFixture, err := define.NewFixtureWithBinary(runner.T(), define.Version(), runner.testbeatName, "/home/ubuntu", false, atesting.WithRunLength(time.Minute*3), atesting.WithAdditionalArgs([]string{"-E", "output.elasticsearch.allow_older_versions=true"}))
8282
runner.agentFixture = agentFixture
8383
require.NoError(runner.T(), err)
8484

testing/integration/upgrade_fleet_test.go

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestFleetManagedUpgradeUnprivileged(t *testing.T) {
5151
Local: false, // requires Agent installation
5252
Sudo: true, // requires Agent installation
5353
})
54-
testFleetManagedUpgrade(t, info, true)
54+
testFleetManagedUpgrade(t, info, true, false)
5555
}
5656

5757
// TestFleetManagedUpgradePrivileged tests that the build under test can retrieve an action from
@@ -65,16 +65,63 @@ func TestFleetManagedUpgradePrivileged(t *testing.T) {
6565
Local: false, // requires Agent installation
6666
Sudo: true, // requires Agent installation
6767
})
68-
testFleetManagedUpgrade(t, info, false)
68+
testFleetManagedUpgrade(t, info, false, false)
6969
}
7070

71-
func testFleetManagedUpgrade(t *testing.T, info *define.Info, unprivileged bool) {
71+
// TestFleetManagedUpgradeUnprivilegedFIPS tests that the build under test can retrieve an action from
72+
// Fleet and perform the upgrade as an unprivileged FIPS-capable Elastic Agent. It does not need to test
73+
// all the combinations of versions as the standalone tests already perform those tests and
74+
// would be redundant.
75+
func TestFleetManagedUpgradeUnprivilegedFIPS(t *testing.T) {
76+
info := define.Require(t, define.Requirements{
77+
Group: Fleet,
78+
Stack: &define.Stack{},
79+
Local: false, // requires Agent installation
80+
Sudo: true, // requires Agent installation
81+
// FIPS: true // TODO: uncomment when https://github.com/elastic/elastic-agent/pull/8083 is merged
82+
})
83+
postWatcherSuccessHook := upgradetest.PostUpgradeAgentIsFIPSCapable
84+
upgradeOpts := []upgradetest.UpgradeOpt{upgradetest.WithPostWatcherSuccessHook(postWatcherSuccessHook)}
85+
testFleetManagedUpgrade(t, info, true, true, upgradeOpts...)
86+
}
87+
88+
// TestFleetManagedUpgradePrivileged tests that the build under test can retrieve an action from
89+
// Fleet and perform the upgrade as a privileged FIPS-capable Elastic Agent. It does not need to test all
90+
// the combinations of versions as the standalone tests already perform those tests and
91+
// would be redundant.
92+
func TestFleetManagedUpgradePrivilegedFIPS(t *testing.T) {
93+
info := define.Require(t, define.Requirements{
94+
Group: FleetPrivileged,
95+
Stack: &define.Stack{},
96+
Local: false, // requires Agent installation
97+
Sudo: true, // requires Agent installation
98+
// FIPS: true // TODO: uncomment when https://github.com/elastic/elastic-agent/pull/8083 is merged
99+
})
100+
101+
// Check that new (post-upgrade) Agent is also FIPS-capable
102+
postWatcherSuccessHook := upgradetest.PostUpgradeAgentIsFIPSCapable
103+
upgradeOpts := []upgradetest.UpgradeOpt{upgradetest.WithPostWatcherSuccessHook(postWatcherSuccessHook)}
104+
testFleetManagedUpgrade(t, info, false, true, upgradeOpts...)
105+
}
106+
107+
func testFleetManagedUpgrade(t *testing.T, info *define.Info, unprivileged bool, fips bool, upgradeOpts ...upgradetest.UpgradeOpts) {
72108
ctx, cancel := context.WithCancel(context.TODO())
73109
defer cancel()
74110

75111
// Start at the build version as we want to test the retry
76112
// logic that is in the build.
77-
startFixture, err := define.NewFixtureFromLocalBuild(t, define.Version())
113+
fixtureOpts := make([]atesting.FixtureOpt, 0)
114+
if fips {
115+
fixtureOpts = append(fixtureOpts, atesting.WithFetcher())
116+
}
117+
118+
var startFixture *testing.Fixture
119+
var err error
120+
if fips {
121+
startFixture, err = define.NewFixtureFromLocalFIPSBuild(t, define.Version(), fixtureOpts...)
122+
} else {
123+
startFixture, err = define.NewFixtureFromLocalBuild(t, define.Version(), fixtureOpts...)
124+
}
78125
require.NoError(t, err)
79126
err = startFixture.Prepare(ctx)
80127
require.NoError(t, err)
@@ -105,7 +152,7 @@ func testFleetManagedUpgrade(t *testing.T, info *define.Info, unprivileged bool)
105152
t.Logf("Testing Elastic Agent upgrade from %s to %s with Fleet...",
106153
define.Version(), endVersionInfo.Binary.String())
107154

108-
testUpgradeFleetManagedElasticAgent(ctx, t, info, startFixture, endFixture, defaultPolicy(), unprivileged)
155+
testUpgradeFleetManagedElasticAgent(ctx, t, info, startFixture, endFixture, defaultPolicy(), unprivileged, upgradeOpts...)
109156
}
110157

111158
func TestFleetAirGappedUpgradeUnprivileged(t *testing.T) {
@@ -348,7 +395,15 @@ func testUpgradeFleetManagedElasticAgent(
348395
startFixture *atesting.Fixture,
349396
endFixture *atesting.Fixture,
350397
policy kibana.AgentPolicy,
351-
unprivileged bool) {
398+
unprivileged bool,
399+
opts ...upgradetest.UpgradeOpt,
400+
) {
401+
402+
// use the passed in options to perform the upgrade
403+
var upgradeOpts upgradetest.UpgradeOpts
404+
for _, o := range opts {
405+
o(&upgradeOpts)
406+
}
352407

353408
kibClient := info.KibanaClient
354409

@@ -470,6 +525,12 @@ func testUpgradeFleetManagedElasticAgent(
470525
// version, otherwise it's possible that it was rolled back to the original version
471526
err = upgradetest.CheckHealthyAndVersion(ctx, startFixture, endVersionInfo.Binary)
472527
assert.NoError(t, err)
528+
529+
if upgradeOpts.postWatcherSuccessHook != nil {
530+
if err := upgradeOpts.postWatcherSuccessHook(ctx, endFixture); err != nil {
531+
return fmt.Errorf("post watcher success hook failed: %w", err)
532+
}
533+
}
473534
}
474535

475536
func defaultPolicy() kibana.AgentPolicy {

testing/integration/upgrade_standalone_fips_test.go

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
package integration
88

99
import (
10-
"context"
11-
"errors"
1210
"fmt"
1311
"testing"
1412

@@ -33,6 +31,7 @@ func TestStandaloneUpgradeFIPStoFIPS(t *testing.T) {
3331
OS: []define.OS{
3432
{Type: define.Linux},
3533
},
34+
// FIPS: true // TODO: uncomment when https://github.com/elastic/elastic-agent/pull/8083 is merged
3635
})
3736

3837
// parse the version we are testing
@@ -59,24 +58,7 @@ func TestStandaloneUpgradeFIPStoFIPS(t *testing.T) {
5958
require.NoError(t, err)
6059

6160
// Check that new (post-upgrade) Agent is also FIPS-capable
62-
postWatcherSuccessHook := func(ctx context.Context, endFixture *atesting.Fixture) error {
63-
client := endFixture.Client()
64-
err := client.Connect(ctx)
65-
if err != nil {
66-
return err
67-
}
68-
69-
ver, err := client.Version(ctx)
70-
if err != nil {
71-
return err
72-
}
73-
74-
if !ver.Fips {
75-
return errors.New("expected upgraded Agent to be FIPS-capable")
76-
}
77-
78-
return nil
79-
}
61+
postWatcherSuccessHook := upgradetest.PostUpgradeAgentIsFIPSCapable
8062

8163
for _, startVersion := range versionList {
8264
// We need to start the upgrade from a FIPS-capable version

0 commit comments

Comments
 (0)