Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 4 additions & 1 deletion internal/pkg/agent/application/upgrade/step_unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,10 @@ func readCommitHash(reader io.Reader) (string, error) {
}

func getFileNamePrefix(archivePath string) string {
return strings.TrimSuffix(filepath.Base(archivePath), ".tar.gz") + "/" // omitting `elastic-agent-{version}-{os}-{arch}/` in filename
prefix := strings.TrimSuffix(filepath.Base(archivePath), ".tar.gz") + "/" // omitting `elastic-agent-{version}-{os}-{arch}/` in filename
prefix = strings.Replace(prefix, fipsPrefix, "", 1)

return prefix
}

func validFileName(p string) bool {
Expand Down
24 changes: 24 additions & 0 deletions internal/pkg/agent/application/upgrade/step_unpack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,27 @@ func addEntryToZipArchive(af files, writer *zip.Writer) error {

return nil
}

func TestGetFileNamePrefix(t *testing.T) {
tests := map[string]struct {
archivePath string
expectedPrefix string
}{
"fips": {
archivePath: "/foo/bar/elastic-agent-fips-9.1.0-SNAPSHOT-linux-arm64.tar.gz",
expectedPrefix: "elastic-agent-9.1.0-SNAPSHOT-linux-arm64/",
},
"no_fips": {
archivePath: "/foo/bar/elastic-agent-9.1.0-SNAPSHOT-linux-arm64.tar.gz",
expectedPrefix: "elastic-agent-9.1.0-SNAPSHOT-linux-arm64/",
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
prefix := getFileNamePrefix(test.archivePath)
require.Equal(t, test.expectedPrefix, prefix)
})
}

}
9 changes: 9 additions & 0 deletions internal/pkg/agent/application/upgrade/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
runDirMod = 0770
snapshotSuffix = "-SNAPSHOT"
watcherMaxWaitTime = 30 * time.Second
fipsPrefix = "-fips"
)

var agentArtifact = artifact.Artifact{
Expand All @@ -61,6 +62,12 @@ var (
ErrFipsToNonFips = errors.New("cannot switch to non-fips mode when upgrading")
)

func init() {
if release.FIPSDistribution() {
agentArtifact.Cmd += fipsPrefix
}
}

// Upgrader performs an upgrade
type Upgrader struct {
log *logger.Logger
Expand Down Expand Up @@ -174,10 +181,12 @@ func checkUpgrade(log *logger.Logger, currentVersion, newVersion agentVersion, m
}

if currentVersion.fips && !metadata.manifest.Package.Fips {
log.Warnf("Upgrade action skipped because FIPS-capable Agent cannot be upgraded to non-FIPS-capable Agent")
return ErrFipsToNonFips
}

if !currentVersion.fips && metadata.manifest.Package.Fips {
log.Warnf("Upgrade action skipped because non-FIPS-capable Agent cannot be upgraded to FIPS-capable Agent")
return ErrNonFipsToFips
}

Expand Down
14 changes: 11 additions & 3 deletions internal/pkg/agent/cmd/enroll_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1054,10 +1054,18 @@ func createFleetConfigFromEnroll(accessAPIKey string, enrollmentToken string, re
if err != nil {
return nil, errors.New(err, "failed to generate enrollment hash", errors.TypeConfig)
}
cfg.ReplaceTokenHash, err = fleetHashToken(replaceToken)
if err != nil {
return nil, errors.New(err, "failed to generate replace token hash", errors.TypeConfig)

// Hash replaceToken if provided; it is not expected to be provided when an Agent
// is being enrolled for the very first time. Hashing an empty replaceToken with the
// FIPS-capable build of Elastic Agent results in an "invalid key length" error from
// OpenSSL's FIPS provider.
if replaceToken != "" {
cfg.ReplaceTokenHash, err = fleetHashToken(replaceToken)
if err != nil {
return nil, errors.New(err, "failed to generate replace token hash", errors.TypeConfig)
}
}

if err := cfg.Valid(); err != nil {
return nil, errors.New(err, "invalid enrollment options", errors.TypeConfig)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/control/v2/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ func (c *client) Version(ctx context.Context) (Version, error) {
Commit: res.Commit,
BuildTime: bt,
Snapshot: res.Snapshot,
Fips: res.Fips,
}, nil
}

Expand Down
43 changes: 28 additions & 15 deletions pkg/testing/define/define.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"sync"
"testing"

"github.com/stretchr/testify/require"

"github.com/gofrs/uuid/v5"

"github.com/elastic/elastic-agent-libs/kibana"
Expand Down Expand Up @@ -90,33 +92,31 @@ func Version() string {
// NewFixtureFromLocalBuild returns a new Elastic Agent testing fixture with a LocalFetcher and
// the agent logging to the test logger.
func NewFixtureFromLocalBuild(t *testing.T, version string, opts ...atesting.FixtureOpt) (*atesting.Fixture, error) {
buildsDir := os.Getenv("AGENT_BUILD_DIR")
if buildsDir == "" {
projectDir, err := findProjectRoot()
if err != nil {
return nil, err
}
buildsDir = filepath.Join(projectDir, "build", "distributions")
}

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

// NewFixtureFromLocalFIPSBuild returns a new FIPS-capable Elastic Agent testing fixture with a LocalFetcher
// and the agent logging to the test logger.
func NewFixtureFromLocalFIPSBuild(t *testing.T, version string, opts ...atesting.FixtureOpt) (*atesting.Fixture, error) {
return NewFixtureWithBinary(t, version, "elastic-agent", buildsDir(t), true, opts...)
}

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

var binFetcher atesting.Fetcher
localFetcherOpts := []atesting.LocalFetcherOpt{atesting.WithCustomBinaryName(binary)}
if ver.IsSnapshot() {
binFetcher = atesting.LocalFetcher(buildsDir, atesting.WithLocalSnapshotOnly(), atesting.WithCustomBinaryName(binary))
} else {
binFetcher = atesting.LocalFetcher(buildsDir, atesting.WithCustomBinaryName(binary))
localFetcherOpts = append(localFetcherOpts, atesting.WithLocalSnapshotOnly())
}
if fips {
localFetcherOpts = append(localFetcherOpts, atesting.WithLocalFIPSOnly())
}
binFetcher := atesting.LocalFetcher(buildsDir, localFetcherOpts...)

opts = append(opts, atesting.WithFetcher(binFetcher), atesting.WithLogOutput())
if binary != "elastic-agent" {
Expand Down Expand Up @@ -308,3 +308,16 @@ func getKibanaClient() (*kibana.Client, error) {
}
return c, nil
}

func buildsDir(t *testing.T) string {
t.Helper()

buildsDir := os.Getenv("AGENT_BUILD_DIR")
if buildsDir == "" {
projectDir, err := findProjectRoot()
require.NoError(t, err)
buildsDir = filepath.Join(projectDir, "build", "distributions")
}

return buildsDir
}
23 changes: 16 additions & 7 deletions pkg/testing/fetcher_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,35 @@ import (
type localFetcher struct {
dir string
snapshotOnly bool
fipsOnly bool
binaryName string
}

type localFetcherOpt func(f *localFetcher)
type LocalFetcherOpt func(f *localFetcher)

// WithLocalSnapshotOnly sets the LocalFetcher to only pull the snapshot build.
func WithLocalSnapshotOnly() localFetcherOpt {
func WithLocalSnapshotOnly() LocalFetcherOpt {
return func(f *localFetcher) {
f.snapshotOnly = true
}
}

// WithLocalFIPSOnly sets the LocalFetcher to only pull a FIPS-compliant build.
func WithLocalFIPSOnly() LocalFetcherOpt {
return func(f *localFetcher) {
f.fipsOnly = true
}
}

// WithCustomBinaryName sets the binary to a custom name, the default is `elastic-agent`
func WithCustomBinaryName(name string) localFetcherOpt {
func WithCustomBinaryName(name string) LocalFetcherOpt {
return func(f *localFetcher) {
f.binaryName = name
}
}

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

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

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

if ver.IsSnapshot() && !matchesEarlyReleaseVersion {
build := fmt.Sprintf(mainBuildfmt, f.binaryName, ver.VersionWithPrerelease(), suffix)
build := fmt.Sprintf(mainBuildfmt, f.binaryName, prefix, ver.VersionWithPrerelease(), suffix)
buildPath = filepath.Join(ver.BuildMetadata(), build)
} else {
buildPath = fmt.Sprintf(mainBuildfmt, f.binaryName, ver.String(), suffix)
buildPath = fmt.Sprintf(mainBuildfmt, f.binaryName, prefix, ver.String(), suffix)
}

fullPath := filepath.Join(f.dir, buildPath)
Expand Down
8 changes: 4 additions & 4 deletions pkg/testing/fetcher_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestLocalFetcher(t *testing.T) {
tcs := []struct {
name string
version string
opts []localFetcherOpt
opts []LocalFetcherOpt
want []byte
wantHash []byte
}{
Expand All @@ -69,7 +69,7 @@ func TestLocalFetcher(t *testing.T) {
}, {
name: "SnapshotOnly",
version: baseVersion,
opts: []localFetcherOpt{WithLocalSnapshotOnly()},
opts: []LocalFetcherOpt{WithLocalSnapshotOnly()},
want: snapshotContent,
wantHash: snapshotContentHash,
}, {
Expand All @@ -80,13 +80,13 @@ func TestLocalFetcher(t *testing.T) {
}, {
name: "version with snapshot and SnapshotOnly",
version: baseVersion + "-SNAPSHOT",
opts: []localFetcherOpt{WithLocalSnapshotOnly()},
opts: []LocalFetcherOpt{WithLocalSnapshotOnly()},
want: snapshotContent,
wantHash: snapshotContentHash,
}, {
name: "version with snapshot and build ID",
version: baseVersion + "-SNAPSHOT+l5snflwr",
opts: []localFetcherOpt{},
opts: []LocalFetcherOpt{},
want: snapshotContent,
wantHash: snapshotContentHash,
},
Expand Down
7 changes: 7 additions & 0 deletions pkg/testing/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Fixture struct {
binaryName string
runLength time.Duration
additionalArgs []string
fipsArtifact bool

srcPackage string
workDir string
Expand Down Expand Up @@ -145,6 +146,12 @@ func WithAdditionalArgs(args []string) FixtureOpt {
}
}

func WithFIPSArtifact() FixtureOpt {
return func(f *Fixture) {
f.fipsArtifact = true
}
}

// NewFixture creates a new fixture to setup and manage Elastic Agent.
func NewFixture(t *testing.T, version string, opts ...FixtureOpt) (*Fixture, error) {
// we store the caller so the fixture can find the cache directory for the artifacts that
Expand Down
4 changes: 4 additions & 0 deletions pkg/version/version_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ func (psv ParsedSemVer) IndependentBuildID() string {
return ""
}

func (psv ParsedSemVer) Equal(other ParsedSemVer) bool {
return !psv.Less(other) && !other.Less(psv)
}

func (psv ParsedSemVer) Less(other ParsedSemVer) bool {
// compare major version
if psv.major != other.major {
Expand Down
Loading