From 6438e61670e17a44d1e5ddb9d2ae0a280d90e68c Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 21 Jun 2024 17:21:57 +0200 Subject: [PATCH 01/14] WIP: it upgrades but there is a hash mismatch --- testing/integration/upgrade_fleet_test.go | 115 ++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 3c6d8fa759f..b9fb72f7774 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + "io" "net" "net/http" "net/http/httptest" @@ -17,6 +18,7 @@ import ( "os/exec" "path/filepath" "runtime" + "sort" "strings" "testing" "time" @@ -32,6 +34,7 @@ import ( "github.com/elastic/elastic-agent/pkg/testing/tools/fleettools" "github.com/elastic/elastic-agent/pkg/testing/tools/testcontext" "github.com/elastic/elastic-agent/pkg/version" + "github.com/elastic/elastic-agent/testing/pgptest" "github.com/elastic/elastic-agent/testing/upgradetest" ) @@ -127,6 +130,118 @@ func TestFleetAirGappedUpgradePrivileged(t *testing.T) { testFleetAirGappedUpgrade(t, stack, false) } +func TestFleetPRBuildSelfSigned(t *testing.T) { + stack := define.Require(t, define.Requirements{ + Group: Fleet, + Stack: &define.Stack{}, + OS: []define.OS{{Type: define.Linux}}, // The test uses /etc/hosts. + Sudo: true, // The test uses /etc/hosts. + Local: false, // The test requires Agent installation + }) + + ctx := context.Background() + rootDir := t.TempDir() + downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") + err := os.MkdirAll(downloadDir, 0644) + require.NoError(t, err, `could not create download directory`) + + // start file server + fs := http.FileServer(http.Dir(rootDir)) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fs.ServeHTTP(w, r) + })) + + // PR build + toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err, "failed to get fixture with PR build") + + path, err := toFixture.SrcPackage(ctx) + require.NoError(t, err, "could not get path to PR build artifact") + + // copy PR build to file server + _, filename := filepath.Split(path) + copyFile(t, path, filepath.Join(downloadDir, filename)) + + agentPkg, err := os.Open(path) + require.NoError(t, err, "could not open PR build artifact") + + // sign the build + pubKey, ascData := pgptest.Sing(t, agentPkg) + + // add the PGP key to file server + err = os.WriteFile( + filepath.Join(rootDir, "GPG-KEY-elastic-agent"), pubKey, 0o600) + require.NoError(t, err, "could not write GPG-KEY-elastic-agent to disk") + + // add package signature to file server + err = os.WriteFile( + filepath.Join(downloadDir, filename+".asc"), ascData, 0o600) + require.NoError(t, err, "could not write agent .asc file to disk") + + // impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent + ercHostEntry := fmt.Sprintf("\n%server\t%server", server.URL, "artifacts.elastic.co") + appendToFile(t, "/etc/hosts", []byte(ercHostEntry)) + + downloadSource := kibana.DownloadSource{ + Name: "self-signed-" + uuid.Must(uuid.NewV4()).String(), + Host: server.URL + "/downloads/", + IsDefault: false, // other tests reuse the stack, let's not mess things up + } + + t.Logf("creating download source %q, using %q.", + downloadSource.Name, downloadSource.Host) + src, err := stack.KibanaClient.CreateDownloadSource(ctx, downloadSource) + require.NoError(t, err, "could not create download source") + policy := defaultPolicy() + policy.DownloadSourceID = src.Item.ID + + // prepare last release + versions, err := upgradetest.GetUpgradableVersions() + require.NoError(t, err, "could not get upgradable versions") + + sortedVers := version.SortableParsedVersions(versions) + sort.Sort(sort.Reverse(sortedVers)) + + t.Logf("upgradable versions: %v", versions) + var latestRelease version.ParsedSemVer + for _, v := range versions { + if !v.IsSnapshot() { + latestRelease = *v + break + } + } + fromFixture, err := atesting.NewFixture(t, latestRelease.String()) + require.NoError(t, err, "could not create fixture for latest release") + + testUpgradeFleetManagedElasticAgent( + ctx, t, stack, fromFixture, toFixture, defaultPolicy(), true) +} + +func copyFile(t *testing.T, srcPath, dstPath string) { + src, err := os.Open(srcPath) + require.NoError(t, err, "Failed to open source file") + defer src.Close() + + dst, err := os.Create(dstPath) + require.NoError(t, err, "Failed to create destination file") + defer dst.Close() + + _, err = io.Copy(dst, src) + require.NoError(t, err, "Failed to copy file") + + err = dst.Sync() + require.NoError(t, err, "Failed to sync dst file") +} + +func appendToFile(t *testing.T, name string, data []byte) { + f, err := os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0o644) + require.NoError(t, err, "could not open file for append") + + _, err = f.Write(data) + require.NoError(t, err, "could not write data to file") + require.NoError(t, f.Close(), "could not close file") +} + func testFleetAirGappedUpgrade(t *testing.T, stack *define.Info, unprivileged bool) { ctx, _ := testcontext.WithDeadline( t, context.Background(), time.Now().Add(10*time.Minute)) From cca72737b9b6b183d4043c1e3262898fb3349480 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Thu, 29 Aug 2024 15:10:54 +0200 Subject: [PATCH 02/14] WIP --- dev-tools/mage/build.go | 1 + .../upgrade/artifact/download/verifier.go | 2 + pkg/testing/fixture.go | 23 ++++++++ pkg/testing/fixture_install.go | 4 +- testing/integration/upgrade_fleet_test.go | 56 ++++++++++++++++--- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/dev-tools/mage/build.go b/dev-tools/mage/build.go index e8cb5358702..3f6ac8c6406 100644 --- a/dev-tools/mage/build.go +++ b/dev-tools/mage/build.go @@ -39,6 +39,7 @@ func DefaultBuildArgs() BuildArgs { args := BuildArgs{ Name: BeatName, CGO: build.Default.CgoEnabled, + Env: map[string]string{"GODEBUG": "tls=1"}, Vars: map[string]string{ elasticAgentModulePath + "/version.buildTime": "{{ date }}", elasticAgentModulePath + "/version.commit": "{{ commit }}", diff --git a/internal/pkg/agent/application/upgrade/artifact/download/verifier.go b/internal/pkg/agent/application/upgrade/artifact/download/verifier.go index 07b212ca505..d5b9260eca5 100644 --- a/internal/pkg/agent/application/upgrade/artifact/download/verifier.go +++ b/internal/pkg/agent/application/upgrade/artifact/download/verifier.go @@ -288,6 +288,7 @@ func PgpBytesFromSource(log warnLogger, source string, client HTTPClient) ([]byt } if strings.HasPrefix(source, PgpSourceURIPrefix) { + log.Warnf("downloading pgp key: %s", source) pgpBytes, err := fetchPgpFromURI(strings.TrimPrefix(source, PgpSourceURIPrefix), client) if errors.Is(err, ErrRemotePGPDownloadFailed) || errors.Is(err, ErrInvalidLocation) { log.Warnf("Skipped remote PGP located at %q because it's unavailable: %v", strings.TrimPrefix(source, PgpSourceURIPrefix), err) @@ -295,6 +296,7 @@ func PgpBytesFromSource(log warnLogger, source string, client HTTPClient) ([]byt log.Warnf("Failed to fetch remote PGP key from %q: %v", strings.TrimPrefix(source, PgpSourceURIPrefix), err) } + log.Warnf("downloaded pgp key: %s", source) return pgpBytes, nil } diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index 512181494c9..1ffd5af7ada 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -1044,6 +1044,29 @@ func (f *Fixture) DumpProcesses(suffix string) { } } +func (f *Fixture) KeepFileByMoving(file string) { + filename := filepath.Base(file) + + dir, err := findProjectRoot(f.caller) + if err != nil { + f.t.Logf("failed to keep file; failed to find project root: %s", err) + return + } + + destFile := filepath.Join(dir, "build", "diagnostics", filename) + fileDir := path.Dir(destFile) + if err := os.MkdirAll(fileDir, 0777); err != nil { + f.t.Logf("failed to keep file; failed to create directory %s: %s", fileDir, err) + return + } + + f.t.Logf("moving %q to %q", file, destFile) + err = os.Rename(file, destFile) + if err != nil { + f.t.Logf("failed to move %q to %q: %v", file, destFile, err) + } +} + // validateComponents ensures that the provided UsableComponent's are valid. func validateComponents(components ...UsableComponent) error { for idx, comp := range components { diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go index 5430252150f..c080d34516e 100644 --- a/pkg/testing/fixture_install.go +++ b/pkg/testing/fixture_install.go @@ -165,7 +165,7 @@ func (f *Fixture) Install(ctx context.Context, installOpts *InstallOpts, opts .. // check for running agents before installing, but only if not installed into a namespace whose point is allowing two agents at once. if installOpts != nil && !installOpts.Develop && installOpts.Namespace == "" { - assert.Empty(f.t, getElasticAgentProcesses(f.t), "there should be no running agent at beginning of Install()") + // assert.Empty(f.t, getElasticAgentProcesses(f.t), "there should be no running agent at beginning of Install()") } switch f.packageFormat { @@ -258,7 +258,7 @@ func (f *Fixture) installNoPkgManager(ctx context.Context, installOpts *InstallO return } - assert.Empty(f.t, processes, "there should be no running agent at the end of the test") + // assert.Empty(f.t, processes, "there should be no running agent at the end of the test") }) f.t.Cleanup(func() { diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index b9fb72f7774..739975ccc00 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -140,14 +140,32 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { }) ctx := context.Background() - rootDir := t.TempDir() + + // rootDir := t.TempDir() + rootDir := "/tmp/fileserver" + matches, err := filepath.Glob(rootDir + "/*") + require.NoError(t, err, "failed to glob rootDir") + for _, f := range matches { + os.Remove(f) + } + // downloads/beats/elastic-agent/ downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") err := os.MkdirAll(downloadDir, 0644) - require.NoError(t, err, `could not create download directory`) + require.NoError(t, err, "could not create download directory") + // it's useful for debugging + dl, err := os.ReadDir(rootDir) + require.NoError(t, err) + var files []string + for _, d := range dl { + files = append(files, d.Name()) + } + fmt.Printf("ArtifactsServer root dir %q, served files %q\n", + rootDir, files) // start file server fs := http.FileServer(http.Dir(rootDir)) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) fs.ServeHTTP(w, r) })) @@ -155,22 +173,27 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) require.NoError(t, err, "failed to get fixture with PR build") - path, err := toFixture.SrcPackage(ctx) + prBuildPkgPath, err := toFixture.SrcPackage(ctx) require.NoError(t, err, "could not get path to PR build artifact") + t.Logf("prBuildPkgPath: %q", prBuildPkgPath) + // copy PR build to file server - _, filename := filepath.Split(path) - copyFile(t, path, filepath.Join(downloadDir, filename)) + _, filename := filepath.Split(prBuildPkgPath) + pkgDownloadPath := filepath.Join(downloadDir, filename) + copyFile(t, prBuildPkgPath, pkgDownloadPath) + copyFile(t, prBuildPkgPath+".sha512", pkgDownloadPath+".sha512") - agentPkg, err := os.Open(path) + agentPkg, err := os.Open(prBuildPkgPath) require.NoError(t, err, "could not open PR build artifact") // sign the build pubKey, ascData := pgptest.Sing(t, agentPkg) // add the PGP key to file server + gpgKeyElasticAgent := filepath.Join(rootDir, "GPG-KEY-elastic-agent") err = os.WriteFile( - filepath.Join(rootDir, "GPG-KEY-elastic-agent"), pubKey, 0o600) + gpgKeyElasticAgent, pubKey, 0o600) require.NoError(t, err, "could not write GPG-KEY-elastic-agent to disk") // add package signature to file server @@ -179,8 +202,11 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { require.NoError(t, err, "could not write agent .asc file to disk") // impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent - ercHostEntry := fmt.Sprintf("\n%server\t%server", server.URL, "artifacts.elastic.co") + host, _ := url.Parse(server.URL) + ercHostEntry := fmt.Sprintf("\n%s\t%s\n", host.Hostname(), "artifacts.elastic.co") appendToFile(t, "/etc/hosts", []byte(ercHostEntry)) + // sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080 + // sudo iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8080 -j MASQUERADE downloadSource := kibana.DownloadSource{ Name: "self-signed-" + uuid.Must(uuid.NewV4()).String(), @@ -194,6 +220,8 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { require.NoError(t, err, "could not create download source") policy := defaultPolicy() policy.DownloadSourceID = src.Item.ID + t.Logf("policy %s using DownloadSourceID: %s", + policy.ID, policy.DownloadSourceID) // prepare last release versions, err := upgradetest.GetUpgradableVersions() @@ -213,11 +241,21 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { fromFixture, err := atesting.NewFixture(t, latestRelease.String()) require.NoError(t, err, "could not create fixture for latest release") + defer func() { + if !t.Failed() { + return + } + + fromFixture.KeepFileByMoving(pkgDownloadPath) + fromFixture.KeepFileByMoving(gpgKeyElasticAgent) + }() + testUpgradeFleetManagedElasticAgent( - ctx, t, stack, fromFixture, toFixture, defaultPolicy(), true) + ctx, t, stack, fromFixture, toFixture, policy, true) } func copyFile(t *testing.T, srcPath, dstPath string) { + t.Logf("copyFile: src %q, dst %q", srcPath, dstPath) src, err := os.Open(srcPath) require.NoError(t, err, "Failed to open source file") defer src.Close() From 1ed685886f69558f769c714b3c14df298ff581d6 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 12:10:13 +0200 Subject: [PATCH 03/14] WIP --- pkg/testing/fixture.go | 15 +++ testing/integration/upgrade_fleet_test.go | 112 +++++++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index 1ffd5af7ada..1bc06b32685 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -50,6 +50,7 @@ type Fixture struct { binaryName string runLength time.Duration additionalArgs []string + env []string srcPackage string workDir string @@ -139,6 +140,15 @@ func WithAdditionalArgs(args []string) FixtureOpt { } } +// WithEnv sets the environment when running the Elastic Agent. +// Each entry is of the form "key=value". os.Environ() is added by default. +// See exec.Cmd.Env for details. +func WithEnv(env []string) FixtureOpt { + return func(f *Fixture) { + f.env = append(os.Environ(), env...) + } +} + // 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 @@ -674,6 +684,11 @@ func (f *Fixture) PrepareAgentCommand(ctx context.Context, args []string, opts . return nil, fmt.Errorf("error adding opts to Exec: %w", err) } } + + if len(f.env) > 0 { + + cmd.Env = f.env + } return cmd, nil } diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 739975ccc00..9eb1d3e2b7f 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -8,12 +8,16 @@ package integration import ( "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" + "net/url" "os" "os/exec" "path/filepath" @@ -28,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/elastic/elastic-agent-libs/kibana" + "github.com/elastic/elastic-agent-libs/testing/certutil" atesting "github.com/elastic/elastic-agent/pkg/testing" "github.com/elastic/elastic-agent/pkg/testing/define" "github.com/elastic/elastic-agent/pkg/testing/tools/check" @@ -130,6 +135,48 @@ func TestFleetAirGappedUpgradePrivileged(t *testing.T) { testFleetAirGappedUpgrade(t, stack, false) } +func TestMine(t *testing.T) { + rootPair, childPair, err := certutil.NewRootAndChildCerts() + require.NoError(t, err, "could not generate TLS certificates") + // cert, err := tls.X509KeyPair(child.Cert, child.Key) + // require.NoError(t, err, "could not create tls.Certificates from child certificate") + // ======================================================================== + // ======================================================================== + rootBlock, _ := pem.Decode(rootPair.Cert) + if rootBlock == nil { + panic("Failed to parse certificate PEM") + + } + root, err := x509.ParseCertificate(rootBlock.Bytes) + if err != nil { + panic("Failed to parse certificate: " + err.Error()) + } + + childBlock, _ := pem.Decode(childPair.Cert) + if rootBlock == nil { + panic("Failed to parse certificate PEM") + + } + child, err := x509.ParseCertificate(childBlock.Bytes) + if err != nil { + panic("Failed to parse certificate: " + err.Error()) + } + + // ==== + caCertPool := x509.NewCertPool() + caCertPool.AddCert(root) + + // Verify the certificate using the CA pool + opts := x509.VerifyOptions{ + Roots: caCertPool, // Use the CA pool for verification + } + if _, err := child.Verify(opts); err != nil { + fmt.Printf("Failed to verify certificate: %v\n", err) + } else { + fmt.Println("Certificate verification successful!") + } +} + func TestFleetPRBuildSelfSigned(t *testing.T) { stack := define.Require(t, define.Requirements{ Group: Fleet, @@ -148,9 +195,53 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { for _, f := range matches { os.Remove(f) } + + rootPair, childPair, err := certutil.NewRootAndChildCerts() + require.NoError(t, err, "could not generate TLS certificates") + cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) + require.NoError(t, err, "could not create tls.Certificates from child certificate") + // ======================================================================== + // ======================================================================== + rootBlock, _ := pem.Decode(rootPair.Cert) + if rootBlock == nil { + panic("Failed to parse certificate PEM") + + } + root, err := x509.ParseCertificate(rootBlock.Bytes) + if err != nil { + panic("Failed to parse certificate: " + err.Error()) + } + + childBlock, _ := pem.Decode(childPair.Cert) + if rootBlock == nil { + panic("Failed to parse certificate PEM") + + } + child, err := x509.ParseCertificate(childBlock.Bytes) + if err != nil { + panic("Failed to parse certificate: " + err.Error()) + } + + // ==== + caCertPool := x509.NewCertPool() + caCertPool.AddCert(root) + + // Verify the certificate using the CA pool + opts := x509.VerifyOptions{ + Roots: caCertPool, // Use the CA pool for verification + } + if _, err := child.Verify(opts); err != nil { + fmt.Printf("Failed to verify certificate: %v\n", err) + } else { + fmt.Println("Certificate verification successful!") + } + + // ======================================================================== + // ======================================================================== + // downloads/beats/elastic-agent/ downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") - err := os.MkdirAll(downloadDir, 0644) + err = os.MkdirAll(downloadDir, 0644) require.NoError(t, err, "could not create download directory") // it's useful for debugging @@ -164,11 +255,24 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { rootDir, files) // start file server fs := http.FileServer(http.Dir(rootDir)) - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // server:= httptest.NewTLSServer() + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) fs.ServeHTTP(w, r) })) + server.Listener, err = net.Listen("tcp", ":443") + require.NoError(t, err, "could not create net listener for port 443") + + server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} + server.StartTLS() + defer server.Close() + t.Logf("file server running on %s", server.URL) + rootCAPath := filepath.Join(rootDir, "rootCA.pem") + err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) + require.NoError(t, err, "could not write root CA") + env := []string{fmt.Sprintf("SSL_CERT_FILE=%s", rootCAPath)} + // PR build toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) require.NoError(t, err, "failed to get fixture with PR build") @@ -238,7 +342,8 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { break } } - fromFixture, err := atesting.NewFixture(t, latestRelease.String()) + fromFixture, err := atesting.NewFixture(t, + latestRelease.String(), atesting.WithEnv(env)) require.NoError(t, err, "could not create fixture for latest release") defer func() { @@ -400,6 +505,7 @@ func testUpgradeFleetManagedElasticAgent( nonInteractiveFlag = true } installOpts := atesting.InstallOpts{ + Insecure: true, NonInteractive: nonInteractiveFlag, Force: true, EnrollOpts: atesting.EnrollOpts{ From 1bc85156c027630a8cd49d8ed4f5c1f8c569c54a Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 14:49:11 +0200 Subject: [PATCH 04/14] wip - mockserver works, fleet: context deadline exceeded --- testing/integration/upgrade_fleet_test.go | 190 ++++++++++++++++++---- 1 file changed, 157 insertions(+), 33 deletions(-) diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 9eb1d3e2b7f..8f738a391b0 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -135,46 +135,168 @@ func TestFleetAirGappedUpgradePrivileged(t *testing.T) { testFleetAirGappedUpgrade(t, stack, false) } -func TestMine(t *testing.T) { - rootPair, childPair, err := certutil.NewRootAndChildCerts() - require.NoError(t, err, "could not generate TLS certificates") - // cert, err := tls.X509KeyPair(child.Cert, child.Key) - // require.NoError(t, err, "could not create tls.Certificates from child certificate") - // ======================================================================== - // ======================================================================== - rootBlock, _ := pem.Decode(rootPair.Cert) - if rootBlock == nil { - panic("Failed to parse certificate PEM") +// +// func TestMine(t *testing.T) { +// rootPair, childPair, err := certutil.NewRootAndChildCerts() +// require.NoError(t, err, "could not generate TLS certificates") +// cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) +// require.NoError(t, err, "could not create tls.Certificates from child certificate") +// // ======================================================================== +// // ======================================================================== +// rootBlock, _ := pem.Decode(rootPair.Cert) +// if rootBlock == nil { +// panic("Failed to parse certificate PEM") +// +// } +// root, err := x509.ParseCertificate(rootBlock.Bytes) +// if err != nil { +// panic("Failed to parse certificate: " + err.Error()) +// } +// +// childBlock, _ := pem.Decode(childPair.Cert) +// if rootBlock == nil { +// panic("Failed to parse certificate PEM") +// +// } +// child, err := x509.ParseCertificate(childBlock.Bytes) +// if err != nil { +// panic("Failed to parse certificate: " + err.Error()) +// } +// +// // ==== +// caCertPool := x509.NewCertPool() +// caCertPool.AddCert(root) +// +// // Verify the certificate using the CA pool +// opts := x509.VerifyOptions{ +// Roots: caCertPool, // Use the CA pool for verification +// } +// if _, err := child.Verify(opts); err != nil { +// fmt.Printf("Failed to verify certificate: %v\n", err) +// } else { +// fmt.Println("Certificate verification successful!") +// } +// +// // ========================== file server ================================== +// // downloads/beats/elastic-agent/ +// downloadDir := "/tmp" +// rootDir := downloadDir +// err = os.MkdirAll(downloadDir, 0644) +// require.NoError(t, err, "could not create download directory") +// +// // // it's useful for debugging +// // dl, err := os.ReadDir(rootDir) +// // require.NoError(t, err) +// // var files []string +// // for _, d := range dl { +// // files = append(files, d.Name()) +// // } +// // fmt.Printf("ArtifactsServer root dir %q, served files %q\n", +// // rootDir, files) +// // start file server +// fs := http.FileServer(http.Dir(rootDir)) +// // server:= httptest.NewTLSServer() +// server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) +// fs.ServeHTTP(w, r) +// })) +// +// server.Listener, err = net.Listen("tcp", "127.0.0.1:443") +// require.NoError(t, err, "could not create net listener for port 443") +// +// server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} +// server.StartTLS() +// defer server.Close() +// t.Logf("file server running on %s", server.URL) +// rootCAPath := filepath.Join(rootDir, "rootCA.pem") +// err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) +// require.NoError(t, err, "could not write root CA") +// +// // test the server +// tlsConfig := &tls.Config{ +// RootCAs: caCertPool, +// } +// +// // Create a transport using the custom TLS configuration +// transport := &http.Transport{TLSClientConfig: tlsConfig} +// +// // Build the http.Client with the custom transport +// client := &http.Client{Transport: transport} +// resp, err := client.Get(server.URL) +// require.NoError(t, err, "could not GET URL: %s", server.URL) +// t.Log(resp.Status) +// } + +func TestSaveCerts(t *testing.T) { + rootKey, rootCACert, rootPair, err := certutil.NewRootCA() + require.NoError(t, err, "could not create root CA") + + _, childPair, err := certutil.GenerateChildCert( + "localhost", + []net.IP{net.ParseIP("127.0.0.1")}, + rootKey, + rootCACert) + require.NoError(t, err, "could not create child cert") + + // rootCAPath := filepath.Join(rootDir, "rootCA.pem") + // err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) + // require.NoError(t, err, "could not write root CA") - } - root, err := x509.ParseCertificate(rootBlock.Bytes) - if err != nil { - panic("Failed to parse certificate: " + err.Error()) - } + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("It works!")) + if err != nil { + fmt.Printf("could not write response: %v", err) + } + })) - childBlock, _ := pem.Decode(childPair.Cert) + childCert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) + require.NoError(t, err, "could not load cert") + + server.TLS = &tls.Config{Certificates: []tls.Certificate{childCert}} + server.StartTLS() + defer server.Close() + + t.Logf("file server running on %s", server.URL) + + // test the server + rootBlock, _ := pem.Decode(rootPair.Cert) if rootBlock == nil { panic("Failed to parse certificate PEM") } - child, err := x509.ParseCertificate(childBlock.Bytes) + root, err := x509.ParseCertificate(rootBlock.Bytes) if err != nil { panic("Failed to parse certificate: " + err.Error()) } - - // ==== caCertPool := x509.NewCertPool() caCertPool.AddCert(root) - // Verify the certificate using the CA pool - opts := x509.VerifyOptions{ - Roots: caCertPool, // Use the CA pool for verification - } - if _, err := child.Verify(opts); err != nil { - fmt.Printf("Failed to verify certificate: %v\n", err) - } else { - fmt.Println("Certificate verification successful!") + tlsConfig := &tls.Config{ + RootCAs: caCertPool, } + + // Create a transport using the custom TLS configuration + transport := &http.Transport{TLSClientConfig: tlsConfig} + + // Build the http.Client with the custom transport + client := &http.Client{Transport: transport} + resp, err := client.Get(server.URL) + require.NoError(t, err, "could not GET URL: %s", server.URL) + t.Log(resp.Status) +} + +func NewRootAndChildCerts(t *testing.T, host string, ips []net.IP) (certutil.Pair, certutil.Pair) { + rootKey, rootCACert, rootPair, err := certutil.NewRootCA() + require.NoError(t, err, "could not create root CA") + + _, childPair, err := certutil.GenerateChildCert( + host, + ips, + rootKey, + rootCACert) + require.NoError(t, err, "could not create child cert") + + return rootPair, childPair } func TestFleetPRBuildSelfSigned(t *testing.T) { @@ -196,7 +318,8 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { os.Remove(f) } - rootPair, childPair, err := certutil.NewRootAndChildCerts() + rootPair, childPair := NewRootAndChildCerts( + t, "artifacts.elastic.co", []net.IP{net.ParseIP("127.0.0.1")}) require.NoError(t, err, "could not generate TLS certificates") cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) require.NoError(t, err, "could not create tls.Certificates from child certificate") @@ -239,6 +362,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { // ======================================================================== // ======================================================================== + // ========================== file server ================================== // downloads/beats/elastic-agent/ downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") err = os.MkdirAll(downloadDir, 0644) @@ -273,7 +397,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { require.NoError(t, err, "could not write root CA") env := []string{fmt.Sprintf("SSL_CERT_FILE=%s", rootCAPath)} - // PR build + // ============================== PR build ============================== toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) require.NoError(t, err, "failed to get fixture with PR build") @@ -282,7 +406,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { t.Logf("prBuildPkgPath: %q", prBuildPkgPath) - // copy PR build to file server + // ============================== copy PR build to file server ============================== _, filename := filepath.Split(prBuildPkgPath) pkgDownloadPath := filepath.Join(downloadDir, filename) copyFile(t, prBuildPkgPath, pkgDownloadPath) @@ -300,12 +424,12 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { gpgKeyElasticAgent, pubKey, 0o600) require.NoError(t, err, "could not write GPG-KEY-elastic-agent to disk") - // add package signature to file server + // ============================== add package signature to file server ============================== err = os.WriteFile( filepath.Join(downloadDir, filename+".asc"), ascData, 0o600) require.NoError(t, err, "could not write agent .asc file to disk") - // impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent + // ============================== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ============================== host, _ := url.Parse(server.URL) ercHostEntry := fmt.Sprintf("\n%s\t%s\n", host.Hostname(), "artifacts.elastic.co") appendToFile(t, "/etc/hosts", []byte(ercHostEntry)) @@ -327,7 +451,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { t.Logf("policy %s using DownloadSourceID: %s", policy.ID, policy.DownloadSourceID) - // prepare last release + // ============================== prepare last release ============================== versions, err := upgradetest.GetUpgradableVersions() require.NoError(t, err, "could not get upgradable versions") From 56f1b701fa4bef2a4e033f07dd74a3cbb7d01284 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 15:49:24 +0200 Subject: [PATCH 05/14] it works! --- dev-tools/mage/build.go | 1 - pkg/testing/fixture.go | 9 - testing/integration/upgrade_fleet_test.go | 199 ++++++++++++---------- 3 files changed, 112 insertions(+), 97 deletions(-) diff --git a/dev-tools/mage/build.go b/dev-tools/mage/build.go index 3f6ac8c6406..e8cb5358702 100644 --- a/dev-tools/mage/build.go +++ b/dev-tools/mage/build.go @@ -39,7 +39,6 @@ func DefaultBuildArgs() BuildArgs { args := BuildArgs{ Name: BeatName, CGO: build.Default.CgoEnabled, - Env: map[string]string{"GODEBUG": "tls=1"}, Vars: map[string]string{ elasticAgentModulePath + "/version.buildTime": "{{ date }}", elasticAgentModulePath + "/version.commit": "{{ commit }}", diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index 1bc06b32685..8133225b0c4 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -140,15 +140,6 @@ func WithAdditionalArgs(args []string) FixtureOpt { } } -// WithEnv sets the environment when running the Elastic Agent. -// Each entry is of the form "key=value". os.Environ() is added by default. -// See exec.Cmd.Env for details. -func WithEnv(env []string) FixtureOpt { - return func(f *Fixture) { - f.env = append(os.Environ(), env...) - } -} - // 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 diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 8f738a391b0..062e7cecd40 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -17,7 +17,6 @@ import ( "net" "net/http" "net/http/httptest" - "net/url" "os" "os/exec" "path/filepath" @@ -108,7 +107,7 @@ func testFleetManagedUpgrade(t *testing.T, info *define.Info, unprivileged bool) t.Logf("Testing Elastic Agent upgrade from %s to %s with Fleet...", define.Version(), endVersionInfo.Binary.String()) - testUpgradeFleetManagedElasticAgent(ctx, t, info, startFixture, endFixture, defaultPolicy(), unprivileged) + testUpgradeFleetManagedElasticAgent(ctx, t, info, startFixture, endFixture, defaultPolicy(), unprivileged, func(iopts atesting.InstallOpts) atesting.InstallOpts { return iopts }) } func TestFleetAirGappedUpgradeUnprivileged(t *testing.T) { @@ -318,84 +317,25 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { os.Remove(f) } - rootPair, childPair := NewRootAndChildCerts( - t, "artifacts.elastic.co", []net.IP{net.ParseIP("127.0.0.1")}) - require.NoError(t, err, "could not generate TLS certificates") - cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) - require.NoError(t, err, "could not create tls.Certificates from child certificate") - // ======================================================================== - // ======================================================================== - rootBlock, _ := pem.Decode(rootPair.Cert) - if rootBlock == nil { - panic("Failed to parse certificate PEM") - - } - root, err := x509.ParseCertificate(rootBlock.Bytes) - if err != nil { - panic("Failed to parse certificate: " + err.Error()) - } - - childBlock, _ := pem.Decode(childPair.Cert) - if rootBlock == nil { - panic("Failed to parse certificate PEM") - - } - child, err := x509.ParseCertificate(childBlock.Bytes) - if err != nil { - panic("Failed to parse certificate: " + err.Error()) - } - - // ==== - caCertPool := x509.NewCertPool() - caCertPool.AddCert(root) - - // Verify the certificate using the CA pool - opts := x509.VerifyOptions{ - Roots: caCertPool, // Use the CA pool for verification - } - if _, err := child.Verify(opts); err != nil { - fmt.Printf("Failed to verify certificate: %v\n", err) - } else { - fmt.Println("Certificate verification successful!") - } + rootPair, cert := prepareTLSCerts(t) // ======================================================================== // ======================================================================== // ========================== file server ================================== // downloads/beats/elastic-agent/ - downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") - err = os.MkdirAll(downloadDir, 0644) - require.NoError(t, err, "could not create download directory") - - // it's useful for debugging - dl, err := os.ReadDir(rootDir) - require.NoError(t, err) - var files []string - for _, d := range dl { - files = append(files, d.Name()) - } - fmt.Printf("ArtifactsServer root dir %q, served files %q\n", - rootDir, files) - // start file server - fs := http.FileServer(http.Dir(rootDir)) - // server:= httptest.NewTLSServer() - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) - fs.ServeHTTP(w, r) - })) - - server.Listener, err = net.Listen("tcp", ":443") - require.NoError(t, err, "could not create net listener for port 443") - - server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} - server.StartTLS() + server, downloadDir := prepareFileServer(t, rootDir, cert) defer server.Close() - t.Logf("file server running on %s", server.URL) + rootCAPath := filepath.Join(rootDir, "rootCA.pem") err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) require.NoError(t, err, "could not write root CA") - env := []string{fmt.Sprintf("SSL_CERT_FILE=%s", rootCAPath)} + + // /etc/ssl/certs + err = os.WriteFile( + filepath.Join("/etc/ssl/certs", "TestFleetPRBuildSelfSigned.pem"), + rootPair.Cert, 0440) + require.NoError(t, err, "could not write root CA to /etc/ssl/certs") // ============================== PR build ============================== toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) @@ -421,7 +361,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { // add the PGP key to file server gpgKeyElasticAgent := filepath.Join(rootDir, "GPG-KEY-elastic-agent") err = os.WriteFile( - gpgKeyElasticAgent, pubKey, 0o600) + gpgKeyElasticAgent, pubKey, 0o644) require.NoError(t, err, "could not write GPG-KEY-elastic-agent to disk") // ============================== add package signature to file server ============================== @@ -430,11 +370,8 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { require.NoError(t, err, "could not write agent .asc file to disk") // ============================== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ============================== - host, _ := url.Parse(server.URL) - ercHostEntry := fmt.Sprintf("\n%s\t%s\n", host.Hostname(), "artifacts.elastic.co") - appendToFile(t, "/etc/hosts", []byte(ercHostEntry)) - // sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080 - // sudo iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8080 -j MASQUERADE + undoEtcHosts := impersonateHost(t, "artifacts.elastic.co", "127.0.0.1") + t.Cleanup(undoEtcHosts) downloadSource := kibana.DownloadSource{ Name: "self-signed-" + uuid.Must(uuid.NewV4()).String(), @@ -467,7 +404,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { } } fromFixture, err := atesting.NewFixture(t, - latestRelease.String(), atesting.WithEnv(env)) + latestRelease.String()) require.NoError(t, err, "could not create fixture for latest release") defer func() { @@ -480,7 +417,101 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { }() testUpgradeFleetManagedElasticAgent( - ctx, t, stack, fromFixture, toFixture, policy, true) + ctx, t, stack, fromFixture, toFixture, policy, false, func(iopts atesting.InstallOpts) atesting.InstallOpts { + iopts.EnrollOpts.CertificateAuthorities = []string{rootCAPath} + return iopts + }) +} + +func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) (*httptest.Server, string) { + downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") + err := os.MkdirAll(downloadDir, 0644) + require.NoError(t, err, "could not create download directory") + + // it's useful for debugging + dl, err := os.ReadDir(rootDir) + require.NoError(t, err) + var files []string + for _, d := range dl { + files = append(files, d.Name()) + } + fmt.Printf("ArtifactsServer root dir %q, served files %q\n", + rootDir, files) + // start file server + fs := http.FileServer(http.Dir(rootDir)) + // server:= httptest.NewTLSServer() + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) + fs.ServeHTTP(w, r) + })) + + server.Listener, err = net.Listen("tcp", "127.0.0.1:443") + require.NoError(t, err, "could not create net listener for port 443") + + server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} + server.StartTLS() + t.Logf("file server running on %s", server.URL) + return server, downloadDir +} + +func prepareTLSCerts(t *testing.T) (certutil.Pair, tls.Certificate) { + rootPair, childPair := NewRootAndChildCerts( + t, "artifacts.elastic.co", []net.IP{net.ParseIP("127.0.0.1")}) + + cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) + require.NoError(t, err, "could not create tls.Certificates from child certificate") + // ======================================================================== + // ======================================================================== + rootBlock, _ := pem.Decode(rootPair.Cert) + if rootBlock == nil { + panic("Failed to parse certificate PEM") + + } + root, err := x509.ParseCertificate(rootBlock.Bytes) + if err != nil { + panic("Failed to parse certificate: " + err.Error()) + } + + childBlock, _ := pem.Decode(childPair.Cert) + if rootBlock == nil { + panic("Failed to parse certificate PEM") + + } + child, err := x509.ParseCertificate(childBlock.Bytes) + if err != nil { + panic("Failed to parse certificate: " + err.Error()) + } + + // ==== + caCertPool := x509.NewCertPool() + caCertPool.AddCert(root) + + // Verify the certificate using the CA pool + opts := x509.VerifyOptions{ + Roots: caCertPool, // Use the CA pool for verification + } + if _, err := child.Verify(opts); err != nil { + fmt.Printf("Failed to verify certificate: %v\n", err) + } else { + fmt.Println("Certificate verification successful!") + } + + return rootPair, cert +} + +// impersonateHost impersonates 'host' by adding an entry to /etc/hosts mapping +// 'ip' to 'host'. +// It returns a callback to restore /etc/hosts to its original state. +func impersonateHost(t *testing.T, host string, ip string) func() { + copyFile(t, "/etc/hosts", "/etc/hosts.old") + + ercHostEntry := fmt.Sprintf("\n%s\t%s\n", ip, host) + appendToFile(t, "/etc/hosts", []byte(ercHostEntry)) + + return func() { + err := os.Rename("/etc/hosts.old", "/etc/hosts") + require.NoError(t, err, "could not restore /etc/hosts") + } } func copyFile(t *testing.T, srcPath, dstPath string) { @@ -574,17 +605,10 @@ func testFleetAirGappedUpgrade(t *testing.T, stack *define.Info, unprivileged bo policy := defaultPolicy() policy.DownloadSourceID = src.Item.ID - testUpgradeFleetManagedElasticAgent(ctx, t, stack, fixture, upgradeTo, policy, unprivileged) + testUpgradeFleetManagedElasticAgent(ctx, t, stack, fixture, upgradeTo, policy, unprivileged, func(iopts atesting.InstallOpts) atesting.InstallOpts { return iopts }) } -func testUpgradeFleetManagedElasticAgent( - ctx context.Context, - t *testing.T, - info *define.Info, - startFixture *atesting.Fixture, - endFixture *atesting.Fixture, - policy kibana.AgentPolicy, - unprivileged bool) { +func testUpgradeFleetManagedElasticAgent(ctx context.Context, t *testing.T, info *define.Info, startFixture *atesting.Fixture, endFixture *atesting.Fixture, policy kibana.AgentPolicy, unprivileged bool, iopts func(iopts atesting.InstallOpts) atesting.InstallOpts) { kibClient := info.KibanaClient startVersionInfo, err := startFixture.ExecVersion(ctx) @@ -638,6 +662,7 @@ func testUpgradeFleetManagedElasticAgent( }, Privileged: !unprivileged, } + installOpts = iopts(installOpts) output, err := startFixture.Install(ctx, &installOpts) require.NoError(t, err, "failed to install start agent [output: %s]", string(output)) From c44afa058873158dffc0648b269dc444b25dad44 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 16:04:11 +0200 Subject: [PATCH 06/14] clean up --- testing/integration/upgrade_fleet_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 062e7cecd40..acab56c419b 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -337,16 +337,15 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { rootPair.Cert, 0440) require.NoError(t, err, "could not write root CA to /etc/ssl/certs") - // ============================== PR build ============================== + // ========================= Fixture from PR build ========================= toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) require.NoError(t, err, "failed to get fixture with PR build") prBuildPkgPath, err := toFixture.SrcPackage(ctx) require.NoError(t, err, "could not get path to PR build artifact") - t.Logf("prBuildPkgPath: %q", prBuildPkgPath) - - // ============================== copy PR build to file server ============================== + // ====================== copy files to file server ====================== + // PR build package _, filename := filepath.Split(prBuildPkgPath) pkgDownloadPath := filepath.Join(downloadDir, filename) copyFile(t, prBuildPkgPath, pkgDownloadPath) @@ -364,15 +363,16 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { gpgKeyElasticAgent, pubKey, 0o644) require.NoError(t, err, "could not write GPG-KEY-elastic-agent to disk") - // ============================== add package signature to file server ============================== + // add package signature to file server err = os.WriteFile( filepath.Join(downloadDir, filename+".asc"), ascData, 0o600) require.NoError(t, err, "could not write agent .asc file to disk") - // ============================== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ============================== + // ==== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ==== undoEtcHosts := impersonateHost(t, "artifacts.elastic.co", "127.0.0.1") t.Cleanup(undoEtcHosts) + // prepare agent's download source downloadSource := kibana.DownloadSource{ Name: "self-signed-" + uuid.Must(uuid.NewV4()).String(), Host: server.URL + "/downloads/", @@ -388,7 +388,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { t.Logf("policy %s using DownloadSourceID: %s", policy.ID, policy.DownloadSourceID) - // ============================== prepare last release ============================== + // ========================= prepare from fixture ========================= versions, err := upgradetest.GetUpgradableVersions() require.NoError(t, err, "could not get upgradable versions") From 16205d06bc76513fc964c25b54d2f056e570599e Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 16:26:42 +0200 Subject: [PATCH 07/14] clean up: so far so good --- testing/integration/upgrade_fleet_test.go | 166 +++++++++++----------- 1 file changed, 81 insertions(+), 85 deletions(-) diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index acab56c419b..80802b5cb93 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -107,7 +107,7 @@ func testFleetManagedUpgrade(t *testing.T, info *define.Info, unprivileged bool) t.Logf("Testing Elastic Agent upgrade from %s to %s with Fleet...", define.Version(), endVersionInfo.Binary.String()) - testUpgradeFleetManagedElasticAgent(ctx, t, info, startFixture, endFixture, defaultPolicy(), unprivileged, func(iopts atesting.InstallOpts) atesting.InstallOpts { return iopts }) + testUpgradeFleetManagedElasticAgent(ctx, t, info, startFixture, endFixture, defaultPolicy(), unprivileged) } func TestFleetAirGappedUpgradeUnprivileged(t *testing.T) { @@ -309,70 +309,73 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { ctx := context.Background() - // rootDir := t.TempDir() - rootDir := "/tmp/fileserver" - matches, err := filepath.Glob(rootDir + "/*") - require.NoError(t, err, "failed to glob rootDir") - for _, f := range matches { - os.Remove(f) - } - + rootDir := t.TempDir() rootPair, cert := prepareTLSCerts(t) - // ======================================================================== - // ======================================================================== - // ========================== file server ================================== // downloads/beats/elastic-agent/ server, downloadDir := prepareFileServer(t, rootDir, cert) defer server.Close() - rootCAPath := filepath.Join(rootDir, "rootCA.pem") - err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) - require.NoError(t, err, "could not write root CA") - - // /etc/ssl/certs - err = os.WriteFile( - filepath.Join("/etc/ssl/certs", "TestFleetPRBuildSelfSigned.pem"), + // add root CA to /etc/ssl/certs. It was the only option that worked + rootCAPath := filepath.Join("/etc/ssl/certs", "TestFleetPRBuildSelfSigned.pem") + err := os.WriteFile( + rootCAPath, rootPair.Cert, 0440) require.NoError(t, err, "could not write root CA to /etc/ssl/certs") + t.Cleanup(func() { + assert.NoError(t, os.Remove(rootCAPath), "cleanup: could not remove root CA") + }) - // ========================= Fixture from PR build ========================= + // ==================== prepare to fixture from PR build =================== toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) require.NoError(t, err, "failed to get fixture with PR build") prBuildPkgPath, err := toFixture.SrcPackage(ctx) require.NoError(t, err, "could not get path to PR build artifact") - // ====================== copy files to file server ====================== - // PR build package - _, filename := filepath.Split(prBuildPkgPath) - pkgDownloadPath := filepath.Join(downloadDir, filename) - copyFile(t, prBuildPkgPath, pkgDownloadPath) - copyFile(t, prBuildPkgPath+".sha512", pkgDownloadPath+".sha512") - agentPkg, err := os.Open(prBuildPkgPath) require.NoError(t, err, "could not open PR build artifact") // sign the build pubKey, ascData := pgptest.Sing(t, agentPkg) - // add the PGP key to file server + // ====================== copy files to file server ====================== + // copy the agent package + _, filename := filepath.Split(prBuildPkgPath) + pkgDownloadPath := filepath.Join(downloadDir, filename) + copyFile(t, prBuildPkgPath, pkgDownloadPath) + copyFile(t, prBuildPkgPath+".sha512", pkgDownloadPath+".sha512") + + // copy the PGP key gpgKeyElasticAgent := filepath.Join(rootDir, "GPG-KEY-elastic-agent") err = os.WriteFile( gpgKeyElasticAgent, pubKey, 0o644) require.NoError(t, err, "could not write GPG-KEY-elastic-agent to disk") - // add package signature to file server + // copy the package signature + ascFile := filepath.Join(downloadDir, filename+".asc") err = os.WriteFile( - filepath.Join(downloadDir, filename+".asc"), ascData, 0o600) + ascFile, ascData, 0o600) require.NoError(t, err, "could not write agent .asc file to disk") + defer func() { + if !t.Failed() { + return + } + + toFixture.KeepFileByMoving(rootCAPath) + toFixture.KeepFileByMoving(pkgDownloadPath) + toFixture.KeepFileByMoving(pkgDownloadPath + ".sha512") + toFixture.KeepFileByMoving(gpgKeyElasticAgent) + toFixture.KeepFileByMoving(ascFile) + }() + // ==== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ==== undoEtcHosts := impersonateHost(t, "artifacts.elastic.co", "127.0.0.1") t.Cleanup(undoEtcHosts) - // prepare agent's download source + // ==================== prepare agent's download source ==================== downloadSource := kibana.DownloadSource{ Name: "self-signed-" + uuid.Must(uuid.NewV4()).String(), Host: server.URL + "/downloads/", @@ -407,20 +410,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { latestRelease.String()) require.NoError(t, err, "could not create fixture for latest release") - defer func() { - if !t.Failed() { - return - } - - fromFixture.KeepFileByMoving(pkgDownloadPath) - fromFixture.KeepFileByMoving(gpgKeyElasticAgent) - }() - - testUpgradeFleetManagedElasticAgent( - ctx, t, stack, fromFixture, toFixture, policy, false, func(iopts atesting.InstallOpts) atesting.InstallOpts { - iopts.EnrollOpts.CertificateAuthorities = []string{rootCAPath} - return iopts - }) + testUpgradeFleetManagedElasticAgent(ctx, t, stack, fromFixture, toFixture, policy, false) } func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) (*httptest.Server, string) { @@ -437,9 +427,8 @@ func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) (*htt } fmt.Printf("ArtifactsServer root dir %q, served files %q\n", rootDir, files) - // start file server + fs := http.FileServer(http.Dir(rootDir)) - // server:= httptest.NewTLSServer() server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) fs.ServeHTTP(w, r) @@ -460,41 +449,41 @@ func prepareTLSCerts(t *testing.T) (certutil.Pair, tls.Certificate) { cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) require.NoError(t, err, "could not create tls.Certificates from child certificate") - // ======================================================================== - // ======================================================================== - rootBlock, _ := pem.Decode(rootPair.Cert) - if rootBlock == nil { - panic("Failed to parse certificate PEM") - - } - root, err := x509.ParseCertificate(rootBlock.Bytes) - if err != nil { - panic("Failed to parse certificate: " + err.Error()) - } - - childBlock, _ := pem.Decode(childPair.Cert) - if rootBlock == nil { - panic("Failed to parse certificate PEM") - - } - child, err := x509.ParseCertificate(childBlock.Bytes) - if err != nil { - panic("Failed to parse certificate: " + err.Error()) - } - - // ==== - caCertPool := x509.NewCertPool() - caCertPool.AddCert(root) - - // Verify the certificate using the CA pool - opts := x509.VerifyOptions{ - Roots: caCertPool, // Use the CA pool for verification - } - if _, err := child.Verify(opts); err != nil { - fmt.Printf("Failed to verify certificate: %v\n", err) - } else { - fmt.Println("Certificate verification successful!") - } + // // ======================================================================== + // // ======================================================================== + // rootBlock, _ := pem.Decode(rootPair.Cert) + // if rootBlock == nil { + // panic("Failed to parse certificate PEM") + // + // } + // root, err := x509.ParseCertificate(rootBlock.Bytes) + // if err != nil { + // panic("Failed to parse certificate: " + err.Error()) + // } + // + // childBlock, _ := pem.Decode(childPair.Cert) + // if rootBlock == nil { + // panic("Failed to parse certificate PEM") + // + // } + // child, err := x509.ParseCertificate(childBlock.Bytes) + // if err != nil { + // panic("Failed to parse certificate: " + err.Error()) + // } + // + // // ==== + // caCertPool := x509.NewCertPool() + // caCertPool.AddCert(root) + // + // // Verify the certificate using the CA pool + // opts := x509.VerifyOptions{ + // Roots: caCertPool, // Use the CA pool for verification + // } + // if _, err := child.Verify(opts); err != nil { + // fmt.Printf("Failed to verify certificate: %v\n", err) + // } else { + // fmt.Println("Certificate verification successful!") + // } return rootPair, cert } @@ -605,10 +594,18 @@ func testFleetAirGappedUpgrade(t *testing.T, stack *define.Info, unprivileged bo policy := defaultPolicy() policy.DownloadSourceID = src.Item.ID - testUpgradeFleetManagedElasticAgent(ctx, t, stack, fixture, upgradeTo, policy, unprivileged, func(iopts atesting.InstallOpts) atesting.InstallOpts { return iopts }) + testUpgradeFleetManagedElasticAgent(ctx, t, stack, fixture, upgradeTo, policy, unprivileged) } -func testUpgradeFleetManagedElasticAgent(ctx context.Context, t *testing.T, info *define.Info, startFixture *atesting.Fixture, endFixture *atesting.Fixture, policy kibana.AgentPolicy, unprivileged bool, iopts func(iopts atesting.InstallOpts) atesting.InstallOpts) { +func testUpgradeFleetManagedElasticAgent( + ctx context.Context, + t *testing.T, + info *define.Info, + startFixture *atesting.Fixture, + endFixture *atesting.Fixture, + policy kibana.AgentPolicy, + unprivileged bool) { + kibClient := info.KibanaClient startVersionInfo, err := startFixture.ExecVersion(ctx) @@ -662,7 +659,6 @@ func testUpgradeFleetManagedElasticAgent(ctx context.Context, t *testing.T, info }, Privileged: !unprivileged, } - installOpts = iopts(installOpts) output, err := startFixture.Install(ctx, &installOpts) require.NoError(t, err, "failed to install start agent [output: %s]", string(output)) From be8e121a3e386dec37a2b9bf4b1e47e89501eff2 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 16:41:27 +0200 Subject: [PATCH 08/14] more clean up --- testing/integration/upgrade_fleet_test.go | 60 ++++++++--------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 80802b5cb93..87e7d181088 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -310,7 +310,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { ctx := context.Background() rootDir := t.TempDir() - rootPair, cert := prepareTLSCerts(t) + rootPair, childPair, cert := prepareTLSCerts(t) // ========================== file server ================================== // downloads/beats/elastic-agent/ @@ -324,7 +324,9 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { rootPair.Cert, 0440) require.NoError(t, err, "could not write root CA to /etc/ssl/certs") t.Cleanup(func() { - assert.NoError(t, os.Remove(rootCAPath), "cleanup: could not remove root CA") + if err = os.Remove(rootCAPath); err != nil { + t.Log("cleanup: could not remove root CA") + } }) // ==================== prepare to fixture from PR build =================== @@ -359,11 +361,24 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { ascFile, ascData, 0o600) require.NoError(t, err, "could not write agent .asc file to disk") + // TODO: decide if it's worth saving this files, if so, give them a name + // associating them with the test run defer func() { if !t.Failed() { return } + if err = os.WriteFile(filepath.Join(rootDir, "server.pem"), childPair.Cert, 0o777); err != nil { + t.Log("cleanup: could not save server cert for investigation") + } + if err = os.WriteFile(filepath.Join(rootDir, "server_key.pem"), childPair.Key, 0o777); err != nil { + t.Log("cleanup: could not save server cert key for investigation") + } + + if err = os.WriteFile(filepath.Join(rootDir, "server_key.pem"), rootPair.Key, 0o777); err != nil { + t.Log("cleanup: could not save rootCA key for investigation") + } + toFixture.KeepFileByMoving(rootCAPath) toFixture.KeepFileByMoving(pkgDownloadPath) toFixture.KeepFileByMoving(pkgDownloadPath + ".sha512") @@ -443,49 +458,14 @@ func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) (*htt return server, downloadDir } -func prepareTLSCerts(t *testing.T) (certutil.Pair, tls.Certificate) { +func prepareTLSCerts(t *testing.T) (certutil.Pair, certutil.Pair, tls.Certificate) { rootPair, childPair := NewRootAndChildCerts( t, "artifacts.elastic.co", []net.IP{net.ParseIP("127.0.0.1")}) cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) require.NoError(t, err, "could not create tls.Certificates from child certificate") - // // ======================================================================== - // // ======================================================================== - // rootBlock, _ := pem.Decode(rootPair.Cert) - // if rootBlock == nil { - // panic("Failed to parse certificate PEM") - // - // } - // root, err := x509.ParseCertificate(rootBlock.Bytes) - // if err != nil { - // panic("Failed to parse certificate: " + err.Error()) - // } - // - // childBlock, _ := pem.Decode(childPair.Cert) - // if rootBlock == nil { - // panic("Failed to parse certificate PEM") - // - // } - // child, err := x509.ParseCertificate(childBlock.Bytes) - // if err != nil { - // panic("Failed to parse certificate: " + err.Error()) - // } - // - // // ==== - // caCertPool := x509.NewCertPool() - // caCertPool.AddCert(root) - // - // // Verify the certificate using the CA pool - // opts := x509.VerifyOptions{ - // Roots: caCertPool, // Use the CA pool for verification - // } - // if _, err := child.Verify(opts); err != nil { - // fmt.Printf("Failed to verify certificate: %v\n", err) - // } else { - // fmt.Println("Certificate verification successful!") - // } - - return rootPair, cert + + return rootPair, childPair, cert } // impersonateHost impersonates 'host' by adding an entry to /etc/hosts mapping From 9b33c851a75664226c0cbeaab99fd97969462935 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Fri, 6 Sep 2024 16:58:58 +0200 Subject: [PATCH 09/14] download from fixture before impersonating artifacts API --- testing/integration/upgrade_fleet_test.go | 43 ++++++++++++----------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 87e7d181088..d00097d34e6 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -309,6 +309,28 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { ctx := context.Background() + // ========================= prepare from fixture ========================= + versions, err := upgradetest.GetUpgradableVersions() + require.NoError(t, err, "could not get upgradable versions") + + sortedVers := version.SortableParsedVersions(versions) + sort.Sort(sort.Reverse(sortedVers)) + + t.Logf("upgradable versions: %v", versions) + var latestRelease version.ParsedSemVer + for _, v := range versions { + if !v.IsSnapshot() { + latestRelease = *v + break + } + } + fromFixture, err := atesting.NewFixture(t, + latestRelease.String()) + require.NoError(t, err, "could not create fixture for latest release") + // make sure to download it before the test impersonates artifacts API + err = fromFixture.Prepare(ctx) + require.NoError(t, err, "could not prepare fromFixture") + rootDir := t.TempDir() rootPair, childPair, cert := prepareTLSCerts(t) @@ -319,7 +341,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { // add root CA to /etc/ssl/certs. It was the only option that worked rootCAPath := filepath.Join("/etc/ssl/certs", "TestFleetPRBuildSelfSigned.pem") - err := os.WriteFile( + err = os.WriteFile( rootCAPath, rootPair.Cert, 0440) require.NoError(t, err, "could not write root CA to /etc/ssl/certs") @@ -406,25 +428,6 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { t.Logf("policy %s using DownloadSourceID: %s", policy.ID, policy.DownloadSourceID) - // ========================= prepare from fixture ========================= - versions, err := upgradetest.GetUpgradableVersions() - require.NoError(t, err, "could not get upgradable versions") - - sortedVers := version.SortableParsedVersions(versions) - sort.Sort(sort.Reverse(sortedVers)) - - t.Logf("upgradable versions: %v", versions) - var latestRelease version.ParsedSemVer - for _, v := range versions { - if !v.IsSnapshot() { - latestRelease = *v - break - } - } - fromFixture, err := atesting.NewFixture(t, - latestRelease.String()) - require.NoError(t, err, "could not create fixture for latest release") - testUpgradeFleetManagedElasticAgent(ctx, t, stack, fromFixture, toFixture, policy, false) } From 67f1c3122f2d37ee188810c76b73b51c77b74105 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Mon, 9 Sep 2024 10:25:42 +0200 Subject: [PATCH 10/14] adjustments and clean up --- testing/integration/groups_test.go | 4 + testing/integration/upgrade_fleet_test.go | 274 +++++----------------- 2 files changed, 64 insertions(+), 214 deletions(-) diff --git a/testing/integration/groups_test.go b/testing/integration/groups_test.go index b4bb4500dad..61c2978741a 100644 --- a/testing/integration/groups_test.go +++ b/testing/integration/groups_test.go @@ -25,6 +25,10 @@ const ( // privileged and airgapped. FleetAirgappedPrivileged = "fleet-airgapped-privileged" + // FleetUpgradeToPRBuild group of tests. Used for testing Elastic Agent + // upgrading to a build built from the PR being tested. + FleetUpgradeToPRBuild = "fleet-upgrade-to-pr-build" + // FQDN group of tests. Used for testing Elastic Agent with FQDN enabled. FQDN = "fqdn" diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index d00097d34e6..b883b00eac4 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -9,8 +9,6 @@ package integration import ( "context" "crypto/tls" - "crypto/x509" - "encoding/pem" "errors" "fmt" "io" @@ -134,182 +132,22 @@ func TestFleetAirGappedUpgradePrivileged(t *testing.T) { testFleetAirGappedUpgrade(t, stack, false) } -// -// func TestMine(t *testing.T) { -// rootPair, childPair, err := certutil.NewRootAndChildCerts() -// require.NoError(t, err, "could not generate TLS certificates") -// cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) -// require.NoError(t, err, "could not create tls.Certificates from child certificate") -// // ======================================================================== -// // ======================================================================== -// rootBlock, _ := pem.Decode(rootPair.Cert) -// if rootBlock == nil { -// panic("Failed to parse certificate PEM") -// -// } -// root, err := x509.ParseCertificate(rootBlock.Bytes) -// if err != nil { -// panic("Failed to parse certificate: " + err.Error()) -// } -// -// childBlock, _ := pem.Decode(childPair.Cert) -// if rootBlock == nil { -// panic("Failed to parse certificate PEM") -// -// } -// child, err := x509.ParseCertificate(childBlock.Bytes) -// if err != nil { -// panic("Failed to parse certificate: " + err.Error()) -// } -// -// // ==== -// caCertPool := x509.NewCertPool() -// caCertPool.AddCert(root) -// -// // Verify the certificate using the CA pool -// opts := x509.VerifyOptions{ -// Roots: caCertPool, // Use the CA pool for verification -// } -// if _, err := child.Verify(opts); err != nil { -// fmt.Printf("Failed to verify certificate: %v\n", err) -// } else { -// fmt.Println("Certificate verification successful!") -// } -// -// // ========================== file server ================================== -// // downloads/beats/elastic-agent/ -// downloadDir := "/tmp" -// rootDir := downloadDir -// err = os.MkdirAll(downloadDir, 0644) -// require.NoError(t, err, "could not create download directory") -// -// // // it's useful for debugging -// // dl, err := os.ReadDir(rootDir) -// // require.NoError(t, err) -// // var files []string -// // for _, d := range dl { -// // files = append(files, d.Name()) -// // } -// // fmt.Printf("ArtifactsServer root dir %q, served files %q\n", -// // rootDir, files) -// // start file server -// fs := http.FileServer(http.Dir(rootDir)) -// // server:= httptest.NewTLSServer() -// server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) -// fs.ServeHTTP(w, r) -// })) -// -// server.Listener, err = net.Listen("tcp", "127.0.0.1:443") -// require.NoError(t, err, "could not create net listener for port 443") -// -// server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} -// server.StartTLS() -// defer server.Close() -// t.Logf("file server running on %s", server.URL) -// rootCAPath := filepath.Join(rootDir, "rootCA.pem") -// err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) -// require.NoError(t, err, "could not write root CA") -// -// // test the server -// tlsConfig := &tls.Config{ -// RootCAs: caCertPool, -// } -// -// // Create a transport using the custom TLS configuration -// transport := &http.Transport{TLSClientConfig: tlsConfig} -// -// // Build the http.Client with the custom transport -// client := &http.Client{Transport: transport} -// resp, err := client.Get(server.URL) -// require.NoError(t, err, "could not GET URL: %s", server.URL) -// t.Log(resp.Status) -// } - -func TestSaveCerts(t *testing.T) { - rootKey, rootCACert, rootPair, err := certutil.NewRootCA() - require.NoError(t, err, "could not create root CA") - - _, childPair, err := certutil.GenerateChildCert( - "localhost", - []net.IP{net.ParseIP("127.0.0.1")}, - rootKey, - rootCACert) - require.NoError(t, err, "could not create child cert") - - // rootCAPath := filepath.Join(rootDir, "rootCA.pem") - // err = os.WriteFile(rootCAPath, rootPair.Cert, 0440) - // require.NoError(t, err, "could not write root CA") - - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte("It works!")) - if err != nil { - fmt.Printf("could not write response: %v", err) - } - })) - - childCert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) - require.NoError(t, err, "could not load cert") - - server.TLS = &tls.Config{Certificates: []tls.Certificate{childCert}} - server.StartTLS() - defer server.Close() - - t.Logf("file server running on %s", server.URL) - - // test the server - rootBlock, _ := pem.Decode(rootPair.Cert) - if rootBlock == nil { - panic("Failed to parse certificate PEM") - - } - root, err := x509.ParseCertificate(rootBlock.Bytes) - if err != nil { - panic("Failed to parse certificate: " + err.Error()) - } - caCertPool := x509.NewCertPool() - caCertPool.AddCert(root) - - tlsConfig := &tls.Config{ - RootCAs: caCertPool, - } - - // Create a transport using the custom TLS configuration - transport := &http.Transport{TLSClientConfig: tlsConfig} - - // Build the http.Client with the custom transport - client := &http.Client{Transport: transport} - resp, err := client.Get(server.URL) - require.NoError(t, err, "could not GET URL: %s", server.URL) - t.Log(resp.Status) -} - -func NewRootAndChildCerts(t *testing.T, host string, ips []net.IP) (certutil.Pair, certutil.Pair) { - rootKey, rootCACert, rootPair, err := certutil.NewRootCA() - require.NoError(t, err, "could not create root CA") - - _, childPair, err := certutil.GenerateChildCert( - host, - ips, - rootKey, - rootCACert) - require.NoError(t, err, "could not create child cert") - - return rootPair, childPair -} - -func TestFleetPRBuildSelfSigned(t *testing.T) { +func TestFleetUpgradeToPRBuild(t *testing.T) { stack := define.Require(t, define.Requirements{ - Group: Fleet, + Group: FleetUpgradeToPRBuild, Stack: &define.Stack{}, OS: []define.OS{{Type: define.Linux}}, // The test uses /etc/hosts. Sudo: true, // The test uses /etc/hosts. - Local: false, // The test requires Agent installation + // The test requires: + // - bind to port 443 (HTTPS) + // - edit /etc/hosts + // - agent installation + Local: false, }) ctx := context.Background() - // ========================= prepare from fixture ========================= + // ========================= prepare from fixture ========================== versions, err := upgradetest.GetUpgradableVersions() require.NoError(t, err, "could not get upgradable versions") @@ -332,15 +170,32 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { require.NoError(t, err, "could not prepare fromFixture") rootDir := t.TempDir() - rootPair, childPair, cert := prepareTLSCerts(t) + rootPair, childPair, cert := prepareTLSCerts( + t, "artifacts.elastic.co", []net.IP{net.ParseIP("127.0.0.1")}) + + // ==================== prepare to fixture from PR build =================== + toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err, "failed to get fixture with PR build") + + prBuildPkgPath, err := toFixture.SrcPackage(ctx) + require.NoError(t, err, "could not get path to PR build artifact") + + agentPkg, err := os.Open(prBuildPkgPath) + require.NoError(t, err, "could not open PR build artifact") + + // sign the build + pubKey, ascData := pgptest.Sing(t, agentPkg) // ========================== file server ================================== - // downloads/beats/elastic-agent/ - server, downloadDir := prepareFileServer(t, rootDir, cert) + downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") + err = os.MkdirAll(downloadDir, 0644) + require.NoError(t, err, "could not create download directory") + + server := prepareFileServer(t, rootDir, cert) defer server.Close() // add root CA to /etc/ssl/certs. It was the only option that worked - rootCAPath := filepath.Join("/etc/ssl/certs", "TestFleetPRBuildSelfSigned.pem") + rootCAPath := filepath.Join("/etc/ssl/certs", "TestFleetUpgradeToPRBuild.pem") err = os.WriteFile( rootCAPath, rootPair.Cert, 0440) @@ -351,19 +206,6 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { } }) - // ==================== prepare to fixture from PR build =================== - toFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) - require.NoError(t, err, "failed to get fixture with PR build") - - prBuildPkgPath, err := toFixture.SrcPackage(ctx) - require.NoError(t, err, "could not get path to PR build artifact") - - agentPkg, err := os.Open(prBuildPkgPath) - require.NoError(t, err, "could not open PR build artifact") - - // sign the build - pubKey, ascData := pgptest.Sing(t, agentPkg) - // ====================== copy files to file server ====================== // copy the agent package _, filename := filepath.Split(prBuildPkgPath) @@ -384,7 +226,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { require.NoError(t, err, "could not write agent .asc file to disk") // TODO: decide if it's worth saving this files, if so, give them a name - // associating them with the test run + // associating them with the test run defer func() { if !t.Failed() { return @@ -409,8 +251,7 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { }() // ==== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ==== - undoEtcHosts := impersonateHost(t, "artifacts.elastic.co", "127.0.0.1") - t.Cleanup(undoEtcHosts) + impersonateHost(t, "artifacts.elastic.co", "127.0.0.1") // ==================== prepare agent's download source ==================== downloadSource := kibana.DownloadSource{ @@ -431,11 +272,9 @@ func TestFleetPRBuildSelfSigned(t *testing.T) { testUpgradeFleetManagedElasticAgent(ctx, t, stack, fromFixture, toFixture, policy, false) } -func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) (*httptest.Server, string) { - downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") - err := os.MkdirAll(downloadDir, 0644) - require.NoError(t, err, "could not create download directory") - +// prepareFileServer prepares and returns a started HTTPS file server serving +// files from rootDir and using cert as its TLS certificate. +func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) *httptest.Server { // it's useful for debugging dl, err := os.ReadDir(rootDir) require.NoError(t, err) @@ -458,12 +297,22 @@ func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) (*htt server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} server.StartTLS() t.Logf("file server running on %s", server.URL) - return server, downloadDir + + return server } -func prepareTLSCerts(t *testing.T) (certutil.Pair, certutil.Pair, tls.Certificate) { - rootPair, childPair := NewRootAndChildCerts( - t, "artifacts.elastic.co", []net.IP{net.ParseIP("127.0.0.1")}) +// prepareTLSCerts generates a CA and a child certificate for the given host and +// IPs. +func prepareTLSCerts(t *testing.T, host string, ips []net.IP) (certutil.Pair, certutil.Pair, tls.Certificate) { + rootKey, rootCACert, rootPair, err := certutil.NewRootCA() + require.NoError(t, err, "could not create root CA") + + _, childPair, err := certutil.GenerateChildCert( + host, + ips, + rootKey, + rootCACert) + require.NoError(t, err, "could not create child cert") cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) require.NoError(t, err, "could not create tls.Certificates from child certificate") @@ -473,17 +322,23 @@ func prepareTLSCerts(t *testing.T) (certutil.Pair, certutil.Pair, tls.Certificat // impersonateHost impersonates 'host' by adding an entry to /etc/hosts mapping // 'ip' to 'host'. -// It returns a callback to restore /etc/hosts to its original state. -func impersonateHost(t *testing.T, host string, ip string) func() { +// It registers a function with t.Cleanup to restore /etc/hosts to its original +// state. +func impersonateHost(t *testing.T, host string, ip string) { copyFile(t, "/etc/hosts", "/etc/hosts.old") - ercHostEntry := fmt.Sprintf("\n%s\t%s\n", ip, host) - appendToFile(t, "/etc/hosts", []byte(ercHostEntry)) + entry := fmt.Sprintf("\n%s\t%s\n", ip, host) + f, err := os.OpenFile("/etc/hosts", os.O_WRONLY|os.O_APPEND, 0o644) + require.NoError(t, err, "could not open file for append") + + _, err = f.Write([]byte(entry)) + require.NoError(t, err, "could not write data to file") + require.NoError(t, f.Close(), "could not close file") - return func() { + t.Cleanup(func() { err := os.Rename("/etc/hosts.old", "/etc/hosts") require.NoError(t, err, "could not restore /etc/hosts") - } + }) } func copyFile(t *testing.T, srcPath, dstPath string) { @@ -503,15 +358,6 @@ func copyFile(t *testing.T, srcPath, dstPath string) { require.NoError(t, err, "Failed to sync dst file") } -func appendToFile(t *testing.T, name string, data []byte) { - f, err := os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0o644) - require.NoError(t, err, "could not open file for append") - - _, err = f.Write(data) - require.NoError(t, err, "could not write data to file") - require.NoError(t, f.Close(), "could not close file") -} - func testFleetAirGappedUpgrade(t *testing.T, stack *define.Info, unprivileged bool) { ctx, _ := testcontext.WithDeadline( t, context.Background(), time.Now().Add(10*time.Minute)) From 58208ad8bdb5b2b61477fabf6af0ed90c8cbbab9 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Mon, 9 Sep 2024 10:50:14 +0200 Subject: [PATCH 11/14] add Fixture.FileNamePrefix for a pre test unique prefix for files --- pkg/testing/fixture.go | 15 +- pkg/testing/fixture_install.go | 35 ++-- testing/integration/upgrade_fleet_test.go | 192 +++++++++++----------- 3 files changed, 129 insertions(+), 113 deletions(-) diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index 8133225b0c4..bb187f4c160 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -63,6 +63,7 @@ type Fixture struct { // Uninstall token value that is needed for the agent uninstall if it's tamper protected uninstallToken string + fileNamePrefix string } // FixtureOpt is an option for the fixture. @@ -1022,10 +1023,8 @@ func (f *Fixture) DumpProcesses(suffix string) { return } - // Sub-test names are separated by "/" characters which are not valid filenames on Linux. - sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-") - - filePath := filepath.Join(dir, "build", "diagnostics", fmt.Sprintf("TEST-%s-%s-%s-ProcessDump%s.json", sanitizedTestName, f.operatingSystem, f.architecture, suffix)) + prefix := f.FileNamePrefix() + filePath := filepath.Join(dir, "build", "diagnostics", fmt.Sprintf("%s-ProcessDump%s.json", prefix, suffix)) fileDir := path.Dir(filePath) if err := os.MkdirAll(fileDir, 0777); err != nil { f.t.Logf("failed to dump process; failed to create directory %s: %s", fileDir, err) @@ -1050,7 +1049,11 @@ func (f *Fixture) DumpProcesses(suffix string) { } } -func (f *Fixture) KeepFileByMoving(file string) { +// KeepFileByMoving moves file to 'build/diagnostics' which contents are +// available on CI if the test fails or on the agent's 'build/diagnostics' +// if the test is run locally. +// 'prefix is added to the file name when moving +func (f *Fixture) KeepFileByMoving(file, prefix string) { filename := filepath.Base(file) dir, err := findProjectRoot(f.caller) @@ -1059,7 +1062,7 @@ func (f *Fixture) KeepFileByMoving(file string) { return } - destFile := filepath.Join(dir, "build", "diagnostics", filename) + destFile := filepath.Join(dir, "build", "diagnostics", prefix+filename) fileDir := path.Dir(destFile) if err := os.MkdirAll(fileDir, 0777); err != nil { f.t.Logf("failed to keep file; failed to create directory %s: %s", fileDir, err) diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go index c080d34516e..35cf7387a8c 100644 --- a/pkg/testing/fixture_install.go +++ b/pkg/testing/fixture_install.go @@ -661,15 +661,8 @@ func (f *Fixture) collectDiagnostics() { return } - stamp := time.Now().Format(time.RFC3339) - if runtime.GOOS == "windows" { - // on Windows a filename cannot contain a ':' as this collides with disk labels (aka. C:\) - stamp = strings.ReplaceAll(stamp, ":", "-") - } - - // Sub-test names are separated by "/" characters which are not valid filenames on Linux. - sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-") - outputPath := filepath.Join(diagPath, fmt.Sprintf("%s-diagnostics-%s.zip", sanitizedTestName, stamp)) + prefix := f.FileNamePrefix() + outputPath := filepath.Join(diagPath, prefix+"-diagnostics.zip") output, err := f.Exec(ctx, []string{"diagnostics", "-f", outputPath}) if err != nil { @@ -689,8 +682,7 @@ func (f *Fixture) collectDiagnostics() { if err != nil { // If collecting diagnostics fails, zip up the entire installation directory with the hope that it will contain logs. f.t.Logf("creating zip archive of the installation directory: %s", f.workDir) - timestamp := strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "-") - zipPath := filepath.Join(diagPath, fmt.Sprintf("%s-install-directory-%s.zip", sanitizedTestName, timestamp)) + zipPath := filepath.Join(diagPath, fmt.Sprintf("%s-install-directory.zip", prefix)) err = f.archiveInstallDirectory(f.workDir, zipPath) if err != nil { f.t.Logf("failed to zip install directory to %s: %s", zipPath, err) @@ -699,6 +691,27 @@ func (f *Fixture) collectDiagnostics() { } } +// FileNamePrefix returns a sanitized and unique name to be used as prefix for +// files to be kept as resources for investigation when the test fails. +func (f *Fixture) FileNamePrefix() string { + if f.fileNamePrefix != "" { + return f.fileNamePrefix + } + + stamp := time.Now().Format(time.RFC3339) + // on Windows a filename cannot contain a ':' as this collides with disk + // labels (aka. C:\) + stamp = strings.ReplaceAll(stamp, ":", "-") + + // Subtest names are separated by "/" characters which are not valid + // filenames on Linux. + sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-") + prefix := fmt.Sprintf("%s-%s", sanitizedTestName, stamp) + + f.fileNamePrefix = prefix + return f.fileNamePrefix +} + // DiagDir returned {projectRoot}/build/diagnostics path. Files on this path // are saved if any test fails. Use it to save files for further investigation. func (f *Fixture) DiagDir() (string, error) { diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index b883b00eac4..4a3459719dc 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -225,29 +225,29 @@ func TestFleetUpgradeToPRBuild(t *testing.T) { ascFile, ascData, 0o600) require.NoError(t, err, "could not write agent .asc file to disk") - // TODO: decide if it's worth saving this files, if so, give them a name - // associating them with the test run defer func() { if !t.Failed() { return } - if err = os.WriteFile(filepath.Join(rootDir, "server.pem"), childPair.Cert, 0o777); err != nil { + prefix := fromFixture.FileNamePrefix() + "-" + + if err = os.WriteFile(filepath.Join(rootDir, prefix+"server.pem"), childPair.Cert, 0o777); err != nil { t.Log("cleanup: could not save server cert for investigation") } - if err = os.WriteFile(filepath.Join(rootDir, "server_key.pem"), childPair.Key, 0o777); err != nil { + if err = os.WriteFile(filepath.Join(rootDir, prefix+"server_key.pem"), childPair.Key, 0o777); err != nil { t.Log("cleanup: could not save server cert key for investigation") } - if err = os.WriteFile(filepath.Join(rootDir, "server_key.pem"), rootPair.Key, 0o777); err != nil { + if err = os.WriteFile(filepath.Join(rootDir, prefix+"server_key.pem"), rootPair.Key, 0o777); err != nil { t.Log("cleanup: could not save rootCA key for investigation") } - toFixture.KeepFileByMoving(rootCAPath) - toFixture.KeepFileByMoving(pkgDownloadPath) - toFixture.KeepFileByMoving(pkgDownloadPath + ".sha512") - toFixture.KeepFileByMoving(gpgKeyElasticAgent) - toFixture.KeepFileByMoving(ascFile) + toFixture.KeepFileByMoving(rootCAPath, prefix) + toFixture.KeepFileByMoving(pkgDownloadPath, prefix) + toFixture.KeepFileByMoving(pkgDownloadPath+".sha512", prefix) + toFixture.KeepFileByMoving(gpgKeyElasticAgent, prefix) + toFixture.KeepFileByMoving(ascFile, prefix) }() // ==== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ==== @@ -272,92 +272,6 @@ func TestFleetUpgradeToPRBuild(t *testing.T) { testUpgradeFleetManagedElasticAgent(ctx, t, stack, fromFixture, toFixture, policy, false) } -// prepareFileServer prepares and returns a started HTTPS file server serving -// files from rootDir and using cert as its TLS certificate. -func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) *httptest.Server { - // it's useful for debugging - dl, err := os.ReadDir(rootDir) - require.NoError(t, err) - var files []string - for _, d := range dl { - files = append(files, d.Name()) - } - fmt.Printf("ArtifactsServer root dir %q, served files %q\n", - rootDir, files) - - fs := http.FileServer(http.Dir(rootDir)) - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) - fs.ServeHTTP(w, r) - })) - - server.Listener, err = net.Listen("tcp", "127.0.0.1:443") - require.NoError(t, err, "could not create net listener for port 443") - - server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} - server.StartTLS() - t.Logf("file server running on %s", server.URL) - - return server -} - -// prepareTLSCerts generates a CA and a child certificate for the given host and -// IPs. -func prepareTLSCerts(t *testing.T, host string, ips []net.IP) (certutil.Pair, certutil.Pair, tls.Certificate) { - rootKey, rootCACert, rootPair, err := certutil.NewRootCA() - require.NoError(t, err, "could not create root CA") - - _, childPair, err := certutil.GenerateChildCert( - host, - ips, - rootKey, - rootCACert) - require.NoError(t, err, "could not create child cert") - - cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) - require.NoError(t, err, "could not create tls.Certificates from child certificate") - - return rootPair, childPair, cert -} - -// impersonateHost impersonates 'host' by adding an entry to /etc/hosts mapping -// 'ip' to 'host'. -// It registers a function with t.Cleanup to restore /etc/hosts to its original -// state. -func impersonateHost(t *testing.T, host string, ip string) { - copyFile(t, "/etc/hosts", "/etc/hosts.old") - - entry := fmt.Sprintf("\n%s\t%s\n", ip, host) - f, err := os.OpenFile("/etc/hosts", os.O_WRONLY|os.O_APPEND, 0o644) - require.NoError(t, err, "could not open file for append") - - _, err = f.Write([]byte(entry)) - require.NoError(t, err, "could not write data to file") - require.NoError(t, f.Close(), "could not close file") - - t.Cleanup(func() { - err := os.Rename("/etc/hosts.old", "/etc/hosts") - require.NoError(t, err, "could not restore /etc/hosts") - }) -} - -func copyFile(t *testing.T, srcPath, dstPath string) { - t.Logf("copyFile: src %q, dst %q", srcPath, dstPath) - src, err := os.Open(srcPath) - require.NoError(t, err, "Failed to open source file") - defer src.Close() - - dst, err := os.Create(dstPath) - require.NoError(t, err, "Failed to create destination file") - defer dst.Close() - - _, err = io.Copy(dst, src) - require.NoError(t, err, "Failed to copy file") - - err = dst.Sync() - require.NoError(t, err, "Failed to sync dst file") -} - func testFleetAirGappedUpgrade(t *testing.T, stack *define.Info, unprivileged bool) { ctx, _ := testcontext.WithDeadline( t, context.Background(), time.Now().Add(10*time.Minute)) @@ -659,3 +573,89 @@ func agentUpgradeDetailsString(a *kibana.AgentExisting) string { return fmt.Sprintf("%#v", *a.UpgradeDetails) } + +// prepareFileServer prepares and returns a started HTTPS file server serving +// files from rootDir and using cert as its TLS certificate. +func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) *httptest.Server { + // it's useful for debugging + dl, err := os.ReadDir(rootDir) + require.NoError(t, err) + var files []string + for _, d := range dl { + files = append(files, d.Name()) + } + fmt.Printf("ArtifactsServer root dir %q, served files %q\n", + rootDir, files) + + fs := http.FileServer(http.Dir(rootDir)) + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Logf("[fileserver] %s - %s", r.Method, r.URL.Path) + fs.ServeHTTP(w, r) + })) + + server.Listener, err = net.Listen("tcp", "127.0.0.1:443") + require.NoError(t, err, "could not create net listener for port 443") + + server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} + server.StartTLS() + t.Logf("file server running on %s", server.URL) + + return server +} + +// prepareTLSCerts generates a CA and a child certificate for the given host and +// IPs. +func prepareTLSCerts(t *testing.T, host string, ips []net.IP) (certutil.Pair, certutil.Pair, tls.Certificate) { + rootKey, rootCACert, rootPair, err := certutil.NewRootCA() + require.NoError(t, err, "could not create root CA") + + _, childPair, err := certutil.GenerateChildCert( + host, + ips, + rootKey, + rootCACert) + require.NoError(t, err, "could not create child cert") + + cert, err := tls.X509KeyPair(childPair.Cert, childPair.Key) + require.NoError(t, err, "could not create tls.Certificates from child certificate") + + return rootPair, childPair, cert +} + +// impersonateHost impersonates 'host' by adding an entry to /etc/hosts mapping +// 'ip' to 'host'. +// It registers a function with t.Cleanup to restore /etc/hosts to its original +// state. +func impersonateHost(t *testing.T, host string, ip string) { + copyFile(t, "/etc/hosts", "/etc/hosts.old") + + entry := fmt.Sprintf("\n%s\t%s\n", ip, host) + f, err := os.OpenFile("/etc/hosts", os.O_WRONLY|os.O_APPEND, 0o644) + require.NoError(t, err, "could not open file for append") + + _, err = f.Write([]byte(entry)) + require.NoError(t, err, "could not write data to file") + require.NoError(t, f.Close(), "could not close file") + + t.Cleanup(func() { + err := os.Rename("/etc/hosts.old", "/etc/hosts") + require.NoError(t, err, "could not restore /etc/hosts") + }) +} + +func copyFile(t *testing.T, srcPath, dstPath string) { + t.Logf("copyFile: src %q, dst %q", srcPath, dstPath) + src, err := os.Open(srcPath) + require.NoError(t, err, "Failed to open source file") + defer src.Close() + + dst, err := os.Create(dstPath) + require.NoError(t, err, "Failed to create destination file") + defer dst.Close() + + _, err = io.Copy(dst, src) + require.NoError(t, err, "Failed to copy file") + + err = dst.Sync() + require.NoError(t, err, "Failed to sync dst file") +} From b5ceea815d38bb48d4fe1e0c2987bbaf43026b30 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Mon, 9 Sep 2024 12:01:31 +0200 Subject: [PATCH 12/14] small fixes --- .../agent/application/upgrade/artifact/download/verifier.go | 2 -- pkg/testing/fixture.go | 5 ----- pkg/testing/fixture_install.go | 4 ++-- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/internal/pkg/agent/application/upgrade/artifact/download/verifier.go b/internal/pkg/agent/application/upgrade/artifact/download/verifier.go index d5b9260eca5..07b212ca505 100644 --- a/internal/pkg/agent/application/upgrade/artifact/download/verifier.go +++ b/internal/pkg/agent/application/upgrade/artifact/download/verifier.go @@ -288,7 +288,6 @@ func PgpBytesFromSource(log warnLogger, source string, client HTTPClient) ([]byt } if strings.HasPrefix(source, PgpSourceURIPrefix) { - log.Warnf("downloading pgp key: %s", source) pgpBytes, err := fetchPgpFromURI(strings.TrimPrefix(source, PgpSourceURIPrefix), client) if errors.Is(err, ErrRemotePGPDownloadFailed) || errors.Is(err, ErrInvalidLocation) { log.Warnf("Skipped remote PGP located at %q because it's unavailable: %v", strings.TrimPrefix(source, PgpSourceURIPrefix), err) @@ -296,7 +295,6 @@ func PgpBytesFromSource(log warnLogger, source string, client HTTPClient) ([]byt log.Warnf("Failed to fetch remote PGP key from %q: %v", strings.TrimPrefix(source, PgpSourceURIPrefix), err) } - log.Warnf("downloaded pgp key: %s", source) return pgpBytes, nil } diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index bb187f4c160..f087b90b7ad 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -50,7 +50,6 @@ type Fixture struct { binaryName string runLength time.Duration additionalArgs []string - env []string srcPackage string workDir string @@ -677,10 +676,6 @@ func (f *Fixture) PrepareAgentCommand(ctx context.Context, args []string, opts . } } - if len(f.env) > 0 { - - cmd.Env = f.env - } return cmd, nil } diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go index 35cf7387a8c..51c45a0ddf7 100644 --- a/pkg/testing/fixture_install.go +++ b/pkg/testing/fixture_install.go @@ -165,7 +165,7 @@ func (f *Fixture) Install(ctx context.Context, installOpts *InstallOpts, opts .. // check for running agents before installing, but only if not installed into a namespace whose point is allowing two agents at once. if installOpts != nil && !installOpts.Develop && installOpts.Namespace == "" { - // assert.Empty(f.t, getElasticAgentProcesses(f.t), "there should be no running agent at beginning of Install()") + assert.Empty(f.t, getElasticAgentProcesses(f.t), "there should be no running agent at beginning of Install()") } switch f.packageFormat { @@ -258,7 +258,7 @@ func (f *Fixture) installNoPkgManager(ctx context.Context, installOpts *InstallO return } - // assert.Empty(f.t, processes, "there should be no running agent at the end of the test") + assert.Empty(f.t, processes, "there should be no running agent at the end of the test") }) f.t.Cleanup(func() { From 7d78006ebf7260246a09e517d49f2b357abaa8f5 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Mon, 9 Sep 2024 12:20:05 +0200 Subject: [PATCH 13/14] minimize changes --- pkg/testing/fixture.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index f087b90b7ad..ca8130f1bc2 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -675,7 +675,6 @@ func (f *Fixture) PrepareAgentCommand(ctx context.Context, args []string, opts . return nil, fmt.Errorf("error adding opts to Exec: %w", err) } } - return cmd, nil } From 2e67d7fbfe5c39cb1d45cf73270aae8a80f61bc3 Mon Sep 17 00:00:00 2001 From: Anderson Queiroz Date: Tue, 10 Sep 2024 13:57:32 +0200 Subject: [PATCH 14/14] pr changes --- .../artifact/download/fs/verifier_test.go | 2 +- .../artifact/download/http/common_test.go | 2 +- pkg/testing/fixture.go | 71 ++++++++++++++----- pkg/testing/fixture_install.go | 35 +-------- testing/integration/package_version_test.go | 2 +- testing/integration/upgrade_fleet_test.go | 22 +++--- testing/pgptest/pgp.go | 4 +- 7 files changed, 72 insertions(+), 66 deletions(-) diff --git a/internal/pkg/agent/application/upgrade/artifact/download/fs/verifier_test.go b/internal/pkg/agent/application/upgrade/artifact/download/fs/verifier_test.go index 7496c0cb9b2..f1c25394b4c 100644 --- a/internal/pkg/agent/application/upgrade/artifact/download/fs/verifier_test.go +++ b/internal/pkg/agent/application/upgrade/artifact/download/fs/verifier_test.go @@ -266,7 +266,7 @@ func prepareTestCase(t *testing.T, a artifact.Artifact, version *agtversion.Pars err = os.WriteFile(filePathSHA, []byte(hashContent), 0644) require.NoErrorf(t, err, "could not write %q file", filePathSHA) - pub, sig := pgptest.Sing(t, bytes.NewReader(content)) + pub, sig := pgptest.Sign(t, bytes.NewReader(content)) err = os.WriteFile(filePathASC, sig, 0644) require.NoErrorf(t, err, "could not write %q file", filePathASC) diff --git a/internal/pkg/agent/application/upgrade/artifact/download/http/common_test.go b/internal/pkg/agent/application/upgrade/artifact/download/http/common_test.go index 3ec8603f047..b7cbfe64620 100644 --- a/internal/pkg/agent/application/upgrade/artifact/download/http/common_test.go +++ b/internal/pkg/agent/application/upgrade/artifact/download/http/common_test.go @@ -71,7 +71,7 @@ func getElasticCoServer(t *testing.T) (*httptest.Server, []byte) { var resp []byte content := []byte("anything will do") hash := sha512.Sum512(content) - pub, sig := pgptest.Sing(t, bytes.NewReader(content)) + pub, sig := pgptest.Sign(t, bytes.NewReader(content)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { packageName := r.URL.Path[len(sourcePattern):] diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index ca8130f1bc2..5e0f646e323 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -62,6 +62,10 @@ type Fixture struct { // Uninstall token value that is needed for the agent uninstall if it's tamper protected uninstallToken string + + // fileNamePrefix is a prefix to be used when saving files from this test. + // it's set by FileNamePrefix and once it's set, FileNamePrefix will return + // its value. fileNamePrefix string } @@ -1011,14 +1015,13 @@ func (f *Fixture) setClient(c client.Client) { func (f *Fixture) DumpProcesses(suffix string) { procs := getProcesses(f.t, `.*`) - dir, err := findProjectRoot(f.caller) + dir, err := f.DiagnosticsDir() if err != nil { - f.t.Logf("failed to dump process; failed to find project root: %s", err) + f.t.Logf("failed to dump process: %s", err) return } - prefix := f.FileNamePrefix() - filePath := filepath.Join(dir, "build", "diagnostics", fmt.Sprintf("%s-ProcessDump%s.json", prefix, suffix)) + filePath := filepath.Join(dir, fmt.Sprintf("%s-ProcessDump%s.json", f.FileNamePrefix(), suffix)) fileDir := path.Dir(filePath) if err := os.MkdirAll(fileDir, 0777); err != nil { f.t.Logf("failed to dump process; failed to create directory %s: %s", fileDir, err) @@ -1043,25 +1046,23 @@ func (f *Fixture) DumpProcesses(suffix string) { } } -// KeepFileByMoving moves file to 'build/diagnostics' which contents are +// MoveToDiagnosticsDir moves file to 'build/diagnostics' which contents are // available on CI if the test fails or on the agent's 'build/diagnostics' // if the test is run locally. -// 'prefix is added to the file name when moving -func (f *Fixture) KeepFileByMoving(file, prefix string) { - filename := filepath.Base(file) - - dir, err := findProjectRoot(f.caller) +// If the file name does nos start with Fixture.FileNamePrefix(), it'll be added +// to the filename when moving. +func (f *Fixture) MoveToDiagnosticsDir(file string) { + dir, err := f.DiagnosticsDir() if err != nil { - f.t.Logf("failed to keep file; failed to find project root: %s", err) + f.t.Logf("failed to move file to diagnostcs directory: %s", err) return } - destFile := filepath.Join(dir, "build", "diagnostics", prefix+filename) - fileDir := path.Dir(destFile) - if err := os.MkdirAll(fileDir, 0777); err != nil { - f.t.Logf("failed to keep file; failed to create directory %s: %s", fileDir, err) - return + filename := filepath.Base(file) + if !strings.HasPrefix(filename, f.FileNamePrefix()) { + filename = fmt.Sprintf("%s-%s", f.FileNamePrefix(), filename) } + destFile := filepath.Join(dir, filename) f.t.Logf("moving %q to %q", file, destFile) err = os.Rename(file, destFile) @@ -1070,6 +1071,44 @@ func (f *Fixture) KeepFileByMoving(file, prefix string) { } } +// FileNamePrefix returns a sanitized and unique name to be used as prefix for +// files to be kept as resources for investigation when the test fails. +func (f *Fixture) FileNamePrefix() string { + if f.fileNamePrefix != "" { + return f.fileNamePrefix + } + + stamp := time.Now().Format(time.RFC3339) + // on Windows a filename cannot contain a ':' as this collides with disk + // labels (aka. C:\) + stamp = strings.ReplaceAll(stamp, ":", "-") + + // Subtest names are separated by "/" characters which are not valid + // filenames on Linux. + sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-") + prefix := fmt.Sprintf("%s-%s", sanitizedTestName, stamp) + + f.fileNamePrefix = prefix + return f.fileNamePrefix +} + +// DiagnosticsDir returned {projectRoot}/build/diagnostics path. Files on this path +// are saved if any test fails. Use it to save files for further investigation. +func (f *Fixture) DiagnosticsDir() (string, error) { + dir, err := findProjectRoot(f.caller) + if err != nil { + return "", fmt.Errorf("failed to find project root: %w", err) + } + + diagPath := filepath.Join(dir, "build", "diagnostics") + + if err := os.MkdirAll(diagPath, 0777); err != nil { + return "", fmt.Errorf("failed to create directory %s: %w", diagPath, err) + } + + return diagPath, nil +} + // validateComponents ensures that the provided UsableComponent's are valid. func validateComponents(components ...UsableComponent) error { for idx, comp := range components { diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go index 802411b048b..037ee3abb85 100644 --- a/pkg/testing/fixture_install.go +++ b/pkg/testing/fixture_install.go @@ -649,7 +649,7 @@ func (f *Fixture) collectDiagnostics() { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() - diagPath, err := f.DiagDir() + diagPath, err := f.DiagnosticsDir() if err != nil { f.t.Logf("failed to collect diagnostics: %v", err) return @@ -691,39 +691,6 @@ func (f *Fixture) collectDiagnostics() { } } -// FileNamePrefix returns a sanitized and unique name to be used as prefix for -// files to be kept as resources for investigation when the test fails. -func (f *Fixture) FileNamePrefix() string { - if f.fileNamePrefix != "" { - return f.fileNamePrefix - } - - stamp := time.Now().Format(time.RFC3339) - // on Windows a filename cannot contain a ':' as this collides with disk - // labels (aka. C:\) - stamp = strings.ReplaceAll(stamp, ":", "-") - - // Subtest names are separated by "/" characters which are not valid - // filenames on Linux. - sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-") - prefix := fmt.Sprintf("%s-%s", sanitizedTestName, stamp) - - f.fileNamePrefix = prefix - return f.fileNamePrefix -} - -// DiagDir returned {projectRoot}/build/diagnostics path. Files on this path -// are saved if any test fails. Use it to save files for further investigation. -func (f *Fixture) DiagDir() (string, error) { - dir, err := findProjectRoot(f.caller) - if err != nil { - return "", fmt.Errorf("failed to find project root: %w", err) - } - - diagPath := filepath.Join(dir, "build", "diagnostics") - return diagPath, nil -} - func (f *Fixture) archiveInstallDirectory(installPath string, outputPath string) error { file, err := os.Create(outputPath) if err != nil { diff --git a/testing/integration/package_version_test.go b/testing/integration/package_version_test.go index 531cff4a873..f27fed0f1ac 100644 --- a/testing/integration/package_version_test.go +++ b/testing/integration/package_version_test.go @@ -207,7 +207,7 @@ func TestComponentBuildHashInDiagnostics(t *testing.T) { } t.Logf("the test failed: trying to save the diagnostics used on the test") - diagDir, err := f.DiagDir() + diagDir, err := f.DiagnosticsDir() if err != nil { t.Logf("could not get diagnostics directory to save the diagnostics used on the test") return diff --git a/testing/integration/upgrade_fleet_test.go b/testing/integration/upgrade_fleet_test.go index 4a3459719dc..0c4026460a6 100644 --- a/testing/integration/upgrade_fleet_test.go +++ b/testing/integration/upgrade_fleet_test.go @@ -140,7 +140,8 @@ func TestFleetUpgradeToPRBuild(t *testing.T) { Sudo: true, // The test uses /etc/hosts. // The test requires: // - bind to port 443 (HTTPS) - // - edit /etc/hosts + // - changes to /etc/hosts + // - changes to /etc/ssl/certs // - agent installation Local: false, }) @@ -184,14 +185,14 @@ func TestFleetUpgradeToPRBuild(t *testing.T) { require.NoError(t, err, "could not open PR build artifact") // sign the build - pubKey, ascData := pgptest.Sing(t, agentPkg) + pubKey, ascData := pgptest.Sign(t, agentPkg) // ========================== file server ================================== downloadDir := filepath.Join(rootDir, "downloads", "beats", "elastic-agent") err = os.MkdirAll(downloadDir, 0644) require.NoError(t, err, "could not create download directory") - server := prepareFileServer(t, rootDir, cert) + server := startHTTPSFileServer(t, rootDir, cert) defer server.Close() // add root CA to /etc/ssl/certs. It was the only option that worked @@ -243,11 +244,11 @@ func TestFleetUpgradeToPRBuild(t *testing.T) { t.Log("cleanup: could not save rootCA key for investigation") } - toFixture.KeepFileByMoving(rootCAPath, prefix) - toFixture.KeepFileByMoving(pkgDownloadPath, prefix) - toFixture.KeepFileByMoving(pkgDownloadPath+".sha512", prefix) - toFixture.KeepFileByMoving(gpgKeyElasticAgent, prefix) - toFixture.KeepFileByMoving(ascFile, prefix) + toFixture.MoveToDiagnosticsDir(rootCAPath) + toFixture.MoveToDiagnosticsDir(pkgDownloadPath) + toFixture.MoveToDiagnosticsDir(pkgDownloadPath + ".sha512") + toFixture.MoveToDiagnosticsDir(gpgKeyElasticAgent) + toFixture.MoveToDiagnosticsDir(ascFile) }() // ==== impersonate https://artifacts.elastic.co/GPG-KEY-elastic-agent ==== @@ -393,7 +394,6 @@ func testUpgradeFleetManagedElasticAgent( nonInteractiveFlag = true } installOpts := atesting.InstallOpts{ - Insecure: true, NonInteractive: nonInteractiveFlag, Force: true, EnrollOpts: atesting.EnrollOpts{ @@ -574,9 +574,9 @@ func agentUpgradeDetailsString(a *kibana.AgentExisting) string { return fmt.Sprintf("%#v", *a.UpgradeDetails) } -// prepareFileServer prepares and returns a started HTTPS file server serving +// startHTTPSFileServer prepares and returns a started HTTPS file server serving // files from rootDir and using cert as its TLS certificate. -func prepareFileServer(t *testing.T, rootDir string, cert tls.Certificate) *httptest.Server { +func startHTTPSFileServer(t *testing.T, rootDir string, cert tls.Certificate) *httptest.Server { // it's useful for debugging dl, err := os.ReadDir(rootDir) require.NoError(t, err) diff --git a/testing/pgptest/pgp.go b/testing/pgptest/pgp.go index c6c441536bf..a2a1aea6aee 100644 --- a/testing/pgptest/pgp.go +++ b/testing/pgptest/pgp.go @@ -14,9 +14,9 @@ import ( "golang.org/x/crypto/openpgp/armor" //nolint:staticcheck // It still receives security fixes and it's just test code ) -// Sing signs data using RSA. It creates the key, sings data and returns the +// Sign signs data using RSA. It creates the key, sings data and returns the // ASCII armored public key and detached signature. -func Sing(t *testing.T, data io.Reader) ([]byte, []byte) { +func Sign(t *testing.T, data io.Reader) ([]byte, []byte) { pub := &bytes.Buffer{} asc := &bytes.Buffer{}