From 09fabb53902312ee8b4500789f20879f58c3d626 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 17 Sep 2020 16:55:05 -0400 Subject: [PATCH 01/33] Add install command. --- NOTICE.txt | 109 ++++++++++++ dev-tools/notice/rules.json | 3 +- go.mod | 2 + go.sum | 11 ++ x-pack/elastic-agent/pkg/agent/cmd/common.go | 3 +- x-pack/elastic-agent/pkg/agent/cmd/enroll.go | 10 +- x-pack/elastic-agent/pkg/agent/cmd/install.go | 156 ++++++++++++++++++ .../pkg/agent/install/install.go | 135 +++++++++++++++ .../pkg/agent/install/installed.go | 32 ++++ .../elastic-agent/pkg/agent/install/paths.go | 31 ++++ .../pkg/agent/install/paths_darwin.go | 30 ++++ .../pkg/agent/install/paths_windows.go | 29 ++++ .../pkg/agent/install/root_unix.go | 19 +++ .../pkg/agent/install/root_windows.go | 39 +++++ 14 files changed, 604 insertions(+), 5 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/cmd/install.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/install.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/installed.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/paths.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/paths_darwin.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/paths_windows.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/root_unix.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/root_windows.go diff --git a/NOTICE.txt b/NOTICE.txt index 748fe0f5e981..3e324a4815d8 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -10907,6 +10907,36 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- +Dependency : github.com/kardianos/service +Version: v1.1.0 +Licence type (autodetected): Zlib +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/kardianos/service@v1.1.0/LICENSE: + +Copyright (c) 2015 Daniel Theophanes + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + -------------------------------------------------------------------------------- Dependency : github.com/lib/pq Version: v1.1.2-0.20190507191818-2ff3cb3adc01 @@ -11843,6 +11873,37 @@ Contents of probable licence file $GOMODCACHE/github.com/oklog/ulid@v1.3.1/LICEN limitations under the License. +-------------------------------------------------------------------------------- +Dependency : github.com/otiai10/copy +Version: v1.2.0 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/otiai10/copy@v1.2.0/LICENSE: + +The MIT License (MIT) + +Copyright (c) 2018 otiai10 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + -------------------------------------------------------------------------------- Dependency : github.com/pierrre/gotestcover Version: v0.0.0-20160517101806-924dca7d15f0 @@ -34009,6 +34070,54 @@ Contents of probable licence file $GOMODCACHE/github.com/opencontainers/runtime- limitations under the License. +-------------------------------------------------------------------------------- +Dependency : github.com/otiai10/curr +Version: v1.0.0 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/otiai10/curr@v1.0.0/LICENSE: + +The MIT License (MIT) + +Copyright (c) 2020 Hiromu Ochiai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +-------------------------------------------------------------------------------- +Dependency : github.com/otiai10/mint +Version: v1.3.1 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/otiai10/mint@v1.3.1/LICENSE: + +Copyright 2017 otiai10 (Hiromu OCHIAI) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------------- Dependency : github.com/oxtoacart/bpool Version: v0.0.0-20150712133111-4e1c5567d7c2 diff --git a/dev-tools/notice/rules.json b/dev-tools/notice/rules.json index 73ce763cdaef..c9638a9c6cf8 100644 --- a/dev-tools/notice/rules.json +++ b/dev-tools/notice/rules.json @@ -9,7 +9,8 @@ "ISC", "MIT", "MPL-2.0", - "Public Domain" + "Public Domain", + "Zlib" ], "maybelist": [ "EPL-1.0", diff --git a/go.mod b/go.mod index 2fafe7508792..289caae0a974 100644 --- a/go.mod +++ b/go.mod @@ -107,6 +107,7 @@ require ( github.com/josephspurrier/goversioninfo v0.0.0-20190209210621-63e6d1acd3dd github.com/jpillora/backoff v1.0.0 // indirect github.com/jstemmer/go-junit-report v0.9.1 + github.com/kardianos/service v1.1.0 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.1.2-0.20190507191818-2ff3cb3adc01 github.com/magefile/mage v1.10.0 @@ -123,6 +124,7 @@ require ( github.com/oklog/ulid v1.3.1 github.com/opencontainers/go-digest v1.0.0-rc1.0.20190228220655-ac19fd6e7483 // indirect github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 // indirect + github.com/otiai10/copy v1.2.0 github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 diff --git a/go.sum b/go.sum index 44f6eeb2ba02..f4a70ac2be7c 100644 --- a/go.sum +++ b/go.sum @@ -464,6 +464,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/justinas/nosurf v1.1.0/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= +github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0= +github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc= github.com/karrick/godirwalk v1.15.6 h1:Yf2mmR8TJy+8Fa0SuQVto5SYap6IF7lNVX4Jdl8G1qA= github.com/karrick/godirwalk v1.15.6/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -563,6 +565,14 @@ github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rm github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -817,6 +827,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/x-pack/elastic-agent/pkg/agent/cmd/common.go b/x-pack/elastic-agent/pkg/agent/cmd/common.go index d5c195566bdb..5b756cf2dbe1 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/common.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/common.go @@ -72,6 +72,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { run := newRunCommandWithArgs(flags, args, streams) cmd.AddCommand(basecmd.NewDefaultCommandsWithArgs(args, streams)...) cmd.AddCommand(run) + cmd.AddCommand(newInstallCommandWithArgs(flags, args, streams)) cmd.AddCommand(newEnrollCommandWithArgs(flags, args, streams)) cmd.AddCommand(newInspectCommandWithArgs(flags, args, streams)) @@ -80,7 +81,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { if reexec != nil { cmd.AddCommand(reexec) } - cmd.PersistentPreRunE = preRunCheck(flags) + run.PersistentPreRunE = preRunCheck(flags) cmd.Run = run.Run return cmd diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go index 6749b57b2506..678cef48239e 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go @@ -42,14 +42,18 @@ func newEnrollCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStr }, } + addEnrollFlags(cmd) + + return cmd +} + +func addEnrollFlags(cmd *cobra.Command) { cmd.Flags().StringP("certificate-authorities", "a", "", "Comma separated list of root certificate for server verifications") cmd.Flags().StringP("ca-sha256", "p", "", "Comma separated list of certificate authorities hash pins used for certificate verifications") cmd.Flags().BoolP("force", "f", false, "Force overwrite the current and do not prompt for confirmation") cmd.Flags().BoolP("insecure", "i", false, "Allow insecure connection to Kibana") cmd.Flags().StringP("staging", "", "", "Configures agent to download artifacts from a staging build") cmd.Flags().Bool("no-restart", false, "Skip restarting the currently running daemon") - - return cmd } func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { @@ -85,7 +89,7 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args return errors.New(err, "problem reading prompt response") } if !confirm { - fmt.Fprintln(streams.Out, "Enrollment was canceled by the user") + fmt.Fprintln(streams.Out, "Enrollment was cancelled by the user") return nil } } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go new file mode 100644 index 000000000000..4079ef7bd341 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -0,0 +1,156 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" + + c "github.com/elastic/beats/v7/libbeat/common/cli" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/warn" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" +) + +func newInstallCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "install", + Short: "Install Agent permanently on this system", + Long: ` +This will install Agent permanently on this system and will become managed by the systems service manager. + +Unless all the require command-line parameters are provided or -f is used this command will ask questions on how you +would like the Agent to operate. +`, + Run: func(c *cobra.Command, args []string) { + if err := installCmd(streams, c, flags, args); err != nil { + fmt.Fprintf(streams.Err, "%v\n", err) + os.Exit(1) + } + }, + } + + cmd.Flags().StringP("kibana-url", "", "", "URL of Kibana to enroll Agent into Fleet") + cmd.Flags().StringP("enrollment-token", "", "", "Enrollment token to use to enroll Agent into Fleet") + addEnrollFlags(cmd) + + return cmd +} + +func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { + if !install.HasRoot() { + return fmt.Errorf("Error: unable to start install command, not executed with %s permissions.", install.PermissionUser) + } + installPath := install.Installed() + if installPath != "" { + return fmt.Errorf("Error: Elastic Agent is already installed at: %s", installPath) + } + + warn.PrintNotGA(streams.Out) + force, _ := cmd.Flags().GetBool("force") + if !force { + confirm, err := c.Confirm("Elastic Agent will be installed onto your system and will run as a service. Do you want to continue?", true) + if err != nil { + return fmt.Errorf("Error: problem reading prompt response") + } + if !confirm { + return fmt.Errorf("Warn: Installation was cancelled by the user") + } + } + + err := install.Install() + if err != nil { + return fmt.Errorf("Error: %s", err) + } + err = install.StartService() + if err != nil { + fmt.Fprintf(streams.Out, "Installation of required system files was successful, but starting of the service failed.") + return fmt.Errorf("Error: %s", err) + } + + /* + insecure, _ := cmd.Flags().GetBool("insecure") + + logger, err := logger.NewFromConfig("", cfg.Settings.LoggingConfig) + if err != nil { + return err + } + + url := args[0] + enrollmentToken := args[1] + + caStr, _ := cmd.Flags().GetString("certificate-authorities") + CAs := cli.StringToSlice(caStr) + + caSHA256str, _ := cmd.Flags().GetString("ca-sha256") + caSHA256 := cli.StringToSlice(caSHA256str) + + delay(defaultDelay) + + options := application.EnrollCmdOption{ + ID: "", // TODO(ph), This should not be an empty string, will clarify in a new PR. + EnrollAPIKey: enrollmentToken, + URL: url, + CAs: CAs, + CASha256: caSHA256, + Insecure: insecure, + UserProvidedMetadata: make(map[string]interface{}), + Staging: staging, + } + + c, err := application.NewEnrollCmd( + logger, + &options, + pathConfigFile, + ) + + if err != nil { + return err + } + + err = c.Execute() + signal := make(chan struct{}) + + backExp := backoff.NewExpBackoff(signal, 60*time.Second, 10*time.Minute) + + for errors.Is(err, fleetapi.ErrTooManyRequests) { + fmt.Fprintln(streams.Out, "Too many requests on the remote server, will retry in a moment.") + backExp.Wait() + fmt.Fprintln(streams.Out, "Retrying to enroll...") + err = c.Execute() + } + + close(signal) + + if err != nil { + return errors.New(err, "fail to enroll") + } + + fmt.Fprintln(streams.Out, "Successfully enrolled the Elastic Agent.") + + // skip restarting + noRestart, _ := cmd.Flags().GetBool("no-restart") + if noRestart { + return nil + } + + daemon := client.New() + err = daemon.Connect(context.Background()) + if err == nil { + defer daemon.Disconnect() + err = daemon.Restart(context.Background()) + if err == nil { + fmt.Fprintln(streams.Out, "Successfully triggered restart on running Elastic Agent.") + return nil + } + } + fmt.Fprintln(streams.Out, "Elastic Agent might not be running; unable to trigger restart") + return nil + */ + return nil +} diff --git a/x-pack/elastic-agent/pkg/agent/install/install.go b/x-pack/elastic-agent/pkg/agent/install/install.go new file mode 100644 index 000000000000..6443b1ca64e4 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/install.go @@ -0,0 +1,135 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package install + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/kardianos/service" + "github.com/otiai10/copy" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" +) + +const ( + // ServiceDisplayName is the service display name for the service. + ServiceDisplayName = "Elastic Agent" + + ServiceDescription = ` +Elastic Agent is a unified agent to observe, monitor and protect your system. +` +) + +// Install installs Elastic Agent persistently on the system including creating and starting its service. +func Install() error { + dir, err := findDirectory() + if err != nil { + return errors.New(err, "failed to discover the source directory for installation", errors.TypeFilesystem) + } + err = os.Mkdir(filepath.Dir(InstallPath), 0755) + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to create installation parent directory (%s)", filepath.Dir(InstallPath)), + errors.M("directory", filepath.Dir(InstallPath))) + } + err = copy.Copy(dir, InstallPath, copy.Options{ + OnSymlink: func(_ string) copy.SymlinkAction { + return copy.Shallow + }, + Sync: true, + }) + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to copy source directory (%s) to destination (%s)", dir, InstallPath), + errors.M("source", dir), errors.M("destination", InstallPath)) + } + if ShellWrapperPath != "" { + err = ioutil.WriteFile(ShellWrapperPath, []byte(ShellWrapper), 0755) + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to write shell wrapper (%s)", ShellWrapperPath), + errors.M("destination", ShellWrapperPath)) + } + } + svc, err := newService() + if err != nil { + return err + } + err = svc.Install() + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to install service (%s)", ServiceName), + errors.M("service", ServiceName)) + } + return nil +} + +// StartService starts the installed service. +// +// This should only be called after Install is successful. +func StartService() error { + svc, err := newService() + if err != nil { + return err + } + err = svc.Start() + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to start service (%s)", ServiceName), + errors.M("service", ServiceName)) + } + return nil +} + +// findDirectory returns the directory to copy into the installation location. +// +// This also verifies that the discovered directory is a valid directory for installation. +func findDirectory() (string, error) { + execPath, err := os.Executable() + if err != nil { + return "", err + } + execPath, err = filepath.Abs(execPath) + if err != nil { + return "", err + } + sourceDir := filepath.Dir(execPath) + err = verifyDirectory(sourceDir) + if err != nil { + return "", err + } + return sourceDir, nil +} + +// verifyDirectory ensures that the directory includes the executable. +func verifyDirectory(dir string) error { + _, err := os.Stat(filepath.Join(dir, BinaryName)) + if os.IsNotExist(err) { + return fmt.Errorf("missing %s", BinaryName) + } + return nil +} + +func newService() (service.Service, error) { + exec := filepath.Join(InstallPath, BinaryName) + if ShellWrapperPath != "" { + exec = ShellWrapperPath + } + return service.New(nil, &service.Config{ + Name: ServiceName, + DisplayName: ServiceDisplayName, + Description: ServiceDescription, + Executable: exec, + WorkingDirectory: InstallPath, + }) +} diff --git a/x-pack/elastic-agent/pkg/agent/install/installed.go b/x-pack/elastic-agent/pkg/agent/install/installed.go new file mode 100644 index 000000000000..a7c42ba7651b --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/installed.go @@ -0,0 +1,32 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package install + +import ( + "os" + "path/filepath" +) + +// RunningInstalled returns true when executing Agent is the installed Agent. +// +// This verifies the running executable path based on hard-coded paths +// for each platform type. +func RunningInstalled() bool { + expected := filepath.Join(InstallPath, BinaryName) + execPath, _ := os.Executable() + return expected == execPath +} + +// Installed returns installed path of Agent when it is installed on the system. +// +// This returns path even if the executing Agent is not the system installed Agent. +func Installed() string { + expected := filepath.Join(InstallPath, BinaryName) + _, err := os.Stat(expected) + if !os.IsNotExist(err) { + return InstallPath + } + return "" +} diff --git a/x-pack/elastic-agent/pkg/agent/install/paths.go b/x-pack/elastic-agent/pkg/agent/install/paths.go new file mode 100644 index 000000000000..53fef093cff7 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/paths.go @@ -0,0 +1,31 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build !darwin +// +build !windows + +package install + +const ( + // BinaryName is the name of the installed binary. + BinaryName = "elastic-agent" + + // InstallPath is the installation path using for install command. + InstallPath = "/opt/Elastic/Agent" + + // SocketPath is the socket path used when installed. + SocketPath = "unix:///run/elastic-agent.sock" + + // ServiceName is the service name when installed. + ServiceName = "elastic-agent" + + // ShellWrapperPath is the path to the installed shell wrapper. + ShellWrapperPath = "/usr/bin/elastic-agent" + + // ShellWrapper is the wrapper that is installed. + ShellWrapper = ` +#!/bin/sh +exec /opt/Elastic/Agent/elastic-agent $@ +` +) diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go new file mode 100644 index 000000000000..dfab2df29541 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go @@ -0,0 +1,30 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build darwin + +package install + +const ( + // BinaryName is the name of the installed binary. + BinaryName = "elastic_agent" + + // InstallPath is the installation path using for install command. + InstallPath = "/Library/Elastic/Agent" + + // SocketPath is the socket path used when installed. + SocketPath = "unix:///run/elastic-agent.sock" + + // ServiceName is the service name when installed. + ServiceName = "com.elastic.elastic-agent" + + // ShellWrapperPath is the path to the installed shell wrapper. + ShellWrapperPath = "/usr/bin/elastic-agent" + + // ShellWrapper is the wrapper that is installed. + ShellWrapper = ` +#!/bin/sh +exec /Library/Elastic/Agent/elastic-agent $@ +` +) diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_windows.go b/x-pack/elastic-agent/pkg/agent/install/paths_windows.go new file mode 100644 index 000000000000..12032f043747 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/paths_windows.go @@ -0,0 +1,29 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build windows + +package install + +const ( + // BinaryName is the name of the installed binary. + BinaryName = "elastic-agent.exe" + + // InstallPath is the installation path using for install command. + InstallPath = `C:\Program Files\Elastic\Agent` + + // SocketPath is the socket path used when installed. + // + // `\\.\pipe\elastic-agent-%x`, sha256.Sum256([]byte(`C:\Program Files\Elastic\Agent\elastic-agent.exe`)) + SocketPath = `\\.\pipe\elastic-agent-56c56575c1f574fe48db8f56ac6db1cbcd78996a355bd2b44c71ebedd9c9a15b` + + // ServiceName is the service name when installed. + ServiceName = "Elastic Agent" + + // ShellWrapperPath is the path to the installed shell wrapper. + ShellWrapperPath = "" // no wrapper on Windows + + // ShellWrapper is the wrapper that is installed. + ShellWrapper = "" // no wrapper on Windows +) diff --git a/x-pack/elastic-agent/pkg/agent/install/root_unix.go b/x-pack/elastic-agent/pkg/agent/install/root_unix.go new file mode 100644 index 000000000000..b79f104d98d9 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/root_unix.go @@ -0,0 +1,19 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build !windows + +package install + +import "os" + +const ( + // PermissionUser is the permission level the user needs to be. + PermissionUser = "root" +) + +// HasRoot returns true if the user has root permissions. +func HasRoot() bool { + return os.Getegid() == 0 +} diff --git a/x-pack/elastic-agent/pkg/agent/install/root_windows.go b/x-pack/elastic-agent/pkg/agent/install/root_windows.go new file mode 100644 index 000000000000..84fde178a92a --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/root_windows.go @@ -0,0 +1,39 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build windows + +package install + +import ( + "golang.org/x/sys/windows" +) + +const ( + // PermissionUser is the permission level the user needs to be. + PermissionUser = "Administrator" +) + +// HasRoot returns true if the user has Administrator/SYSTEM permissions. +func HasRoot() bool { + var sid *windows.SID + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + return false + } + defer windows.FreeSid(sid) + + token := windows.Token(0) + _, err = token.IsMember(sid) + if err != nil { + return false + } + return token.IsElevated() +} From f99d7f9d5bfd85e60d233fcbcf0276f42a9b90dc Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 17 Sep 2020 17:00:00 -0400 Subject: [PATCH 02/33] Fix binary name on darwin. --- x-pack/elastic-agent/pkg/agent/install/paths_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go index dfab2df29541..d1195f65c112 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go @@ -8,7 +8,7 @@ package install const ( // BinaryName is the name of the installed binary. - BinaryName = "elastic_agent" + BinaryName = "elastic-agent" // InstallPath is the installation path using for install command. InstallPath = "/Library/Elastic/Agent" From 4fabf0a3a692028b595a981324c1fb5af0792432 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 17 Sep 2020 17:13:02 -0400 Subject: [PATCH 03/33] Fix install of mkdir. --- x-pack/elastic-agent/pkg/agent/install/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/install.go b/x-pack/elastic-agent/pkg/agent/install/install.go index 6443b1ca64e4..adfd06a0352a 100644 --- a/x-pack/elastic-agent/pkg/agent/install/install.go +++ b/x-pack/elastic-agent/pkg/agent/install/install.go @@ -31,7 +31,7 @@ func Install() error { if err != nil { return errors.New(err, "failed to discover the source directory for installation", errors.TypeFilesystem) } - err = os.Mkdir(filepath.Dir(InstallPath), 0755) + err = os.MkdirAll(filepath.Dir(InstallPath), 0755) if err != nil { return errors.New( err, From a6f0549fa319f1a12fbdf07fe5292998488ff0cd Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 17 Sep 2020 19:16:29 -0400 Subject: [PATCH 04/33] Update shell wrapper path for darwin. --- x-pack/elastic-agent/pkg/agent/install/paths_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go index d1195f65c112..848bd05e5f2a 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go @@ -20,7 +20,7 @@ const ( ServiceName = "com.elastic.elastic-agent" // ShellWrapperPath is the path to the installed shell wrapper. - ShellWrapperPath = "/usr/bin/elastic-agent" + ShellWrapperPath = "/usr/local/bin/elastic-agent" // ShellWrapper is the wrapper that is installed. ShellWrapper = ` From 92d64daeba13857d6ca5ca6b73ce16e6a9b07102 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 17 Sep 2020 22:42:49 -0400 Subject: [PATCH 05/33] Add ability to fix broken installation. --- x-pack/elastic-agent/pkg/agent/cmd/install.go | 31 ++++++--- .../pkg/agent/install/install.go | 36 ++++------- .../pkg/agent/install/installed.go | 45 +++++++++---- x-pack/elastic-agent/pkg/agent/install/svc.go | 30 +++++++++ .../pkg/agent/install/uninstall.go | 64 +++++++++++++++++++ 5 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/install/svc.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/uninstall.go diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 4079ef7bd341..71d6d3c53a79 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -46,20 +46,33 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, if !install.HasRoot() { return fmt.Errorf("Error: unable to start install command, not executed with %s permissions.", install.PermissionUser) } - installPath := install.Installed() - if installPath != "" { - return fmt.Errorf("Error: Elastic Agent is already installed at: %s", installPath) + status, reason := install.Status() + if status == install.Installed { + return fmt.Errorf("Error: Elastic Agent is already installed at: %s", install.InstallPath) } warn.PrintNotGA(streams.Out) force, _ := cmd.Flags().GetBool("force") - if !force { - confirm, err := c.Confirm("Elastic Agent will be installed onto your system and will run as a service. Do you want to continue?", true) - if err != nil { - return fmt.Errorf("Error: problem reading prompt response") + if status == install.Broken { + if !force { + fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s", reason) + confirm, err := c.Confirm("Continuing will re-install Elastic Agent over the current installation. Do you want to continue?", true) + if err != nil { + return fmt.Errorf("Error: problem reading prompt response") + } + if !confirm { + return fmt.Errorf("Warn: Installation was cancelled by the user") + } } - if !confirm { - return fmt.Errorf("Warn: Installation was cancelled by the user") + } else { + if !force { + confirm, err := c.Confirm("Elastic Agent will be installed onto your system and will run as a service. Do you want to continue?", true) + if err != nil { + return fmt.Errorf("Error: problem reading prompt response") + } + if !confirm { + return fmt.Errorf("Warn: Installation was cancelled by the user") + } } } diff --git a/x-pack/elastic-agent/pkg/agent/install/install.go b/x-pack/elastic-agent/pkg/agent/install/install.go index adfd06a0352a..3471eda38867 100644 --- a/x-pack/elastic-agent/pkg/agent/install/install.go +++ b/x-pack/elastic-agent/pkg/agent/install/install.go @@ -10,27 +10,25 @@ import ( "os" "path/filepath" - "github.com/kardianos/service" "github.com/otiai10/copy" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" ) -const ( - // ServiceDisplayName is the service display name for the service. - ServiceDisplayName = "Elastic Agent" - - ServiceDescription = ` -Elastic Agent is a unified agent to observe, monitor and protect your system. -` -) - // Install installs Elastic Agent persistently on the system including creating and starting its service. func Install() error { dir, err := findDirectory() if err != nil { return errors.New(err, "failed to discover the source directory for installation", errors.TypeFilesystem) } + + // uninstall current installation + err = Uninstall() + if err != nil { + return err + } + + // ensure parent directory exists, copy source into install path err = os.MkdirAll(filepath.Dir(InstallPath), 0755) if err != nil { return errors.New( @@ -50,6 +48,8 @@ func Install() error { fmt.Sprintf("failed to copy source directory (%s) to destination (%s)", dir, InstallPath), errors.M("source", dir), errors.M("destination", InstallPath)) } + + // place shell wrapper, if present on platform if ShellWrapperPath != "" { err = ioutil.WriteFile(ShellWrapperPath, []byte(ShellWrapper), 0755) if err != nil { @@ -59,6 +59,8 @@ func Install() error { errors.M("destination", ShellWrapperPath)) } } + + // install service svc, err := newService() if err != nil { return err @@ -119,17 +121,3 @@ func verifyDirectory(dir string) error { } return nil } - -func newService() (service.Service, error) { - exec := filepath.Join(InstallPath, BinaryName) - if ShellWrapperPath != "" { - exec = ShellWrapperPath - } - return service.New(nil, &service.Config{ - Name: ServiceName, - DisplayName: ServiceDisplayName, - Description: ServiceDescription, - Executable: exec, - WorkingDirectory: InstallPath, - }) -} diff --git a/x-pack/elastic-agent/pkg/agent/install/installed.go b/x-pack/elastic-agent/pkg/agent/install/installed.go index a7c42ba7651b..98250eaf952f 100644 --- a/x-pack/elastic-agent/pkg/agent/install/installed.go +++ b/x-pack/elastic-agent/pkg/agent/install/installed.go @@ -5,10 +5,43 @@ package install import ( + "github.com/kardianos/service" "os" "path/filepath" ) +type InstallStatus int + +const ( + // NotInstalled returned when Elastic Agent is not installed. + NotInstalled InstallStatus = iota + // Installed returned when Elastic Agent is installed currectly. + Installed + // Broken returned when Elastic Agent is installed but broken. + Broken +) + +// Status returns the installation status of Agent. +func Status() (InstallStatus, string) { + expected := filepath.Join(InstallPath, BinaryName) + _, err := os.Stat(expected) + if os.IsNotExist(err) { + return NotInstalled, "binary not located at install path" + } + svc, err := newService() + if err != nil { + return Broken, "unable to check service status" + } + status, err := svc.Status() + if err != nil { + return Broken, "unable to check service status" + } + if status == service.StatusUnknown { + return Broken, "service is not installed" + } + return Installed, "" +} + // RunningInstalled returns true when executing Agent is the installed Agent. // // This verifies the running executable path based on hard-coded paths @@ -18,15 +51,3 @@ func RunningInstalled() bool { execPath, _ := os.Executable() return expected == execPath } - -// Installed returns installed path of Agent when it is installed on the system. -// -// This returns path even if the executing Agent is not the system installed Agent. -func Installed() string { - expected := filepath.Join(InstallPath, BinaryName) - _, err := os.Stat(expected) - if !os.IsNotExist(err) { - return InstallPath - } - return "" -} diff --git a/x-pack/elastic-agent/pkg/agent/install/svc.go b/x-pack/elastic-agent/pkg/agent/install/svc.go new file mode 100644 index 000000000000..3694eba4b3d5 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/svc.go @@ -0,0 +1,30 @@ +package install + +import ( + "path/filepath" + + "github.com/kardianos/service" +) + +const ( + // ServiceDisplayName is the service display name for the service. + ServiceDisplayName = "Elastic Agent" + + ServiceDescription = ` +Elastic Agent is a unified agent to observe, monitor and protect your system. +` +) + +func newService() (service.Service, error) { + exec := filepath.Join(InstallPath, BinaryName) + if ShellWrapperPath != "" { + exec = ShellWrapperPath + } + return service.New(nil, &service.Config{ + Name: ServiceName, + DisplayName: ServiceDisplayName, + Description: ServiceDescription, + Executable: exec, + WorkingDirectory: InstallPath, + }) +} diff --git a/x-pack/elastic-agent/pkg/agent/install/uninstall.go b/x-pack/elastic-agent/pkg/agent/install/uninstall.go new file mode 100644 index 000000000000..6ae012fd5bb2 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/uninstall.go @@ -0,0 +1,64 @@ +package install + +import ( + "fmt" + "os" + + "github.com/kardianos/service" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" +) + +// Uninstall uninstalls persistently Elastic Agent on the system. +func Uninstall() error { + // uninstall the current service + svc, err := newService() + if err != nil { + return err + } + status, err := svc.Status() + if err != nil { + return err + } + if status == service.StatusRunning { + err := svc.Stop() + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to stop service (%s)", ServiceName), + errors.M("service", ServiceName)) + } + status = service.StatusStopped + } + if status == service.StatusStopped { + err := svc.Uninstall() + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to uninstall service (%s)", ServiceName), + errors.M("service", ServiceName)) + } + } + + // remove existing directory + err = os.RemoveAll(InstallPath) + if err != nil { + return errors.New( + err, + fmt.Sprintf("failed to remove installation directory (%s)", InstallPath), + errors.M("directory", InstallPath)) + } + + // remove, if present on platform + if ShellWrapperPath != "" { + err = os.Remove(ShellWrapperPath) + if !os.IsNotExist(err) { + return errors.New( + err, + fmt.Sprintf("failed to remove shell wrapper (%s)", ShellWrapperPath), + errors.M("destination", ShellWrapperPath)) + } + } + + return nil +} From de393a363a77bdd7bd5ae9e8f72da70eda582c2e Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Fri, 18 Sep 2020 00:32:40 -0400 Subject: [PATCH 06/33] Fix issues with install/uninstall. --- x-pack/elastic-agent/pkg/agent/cmd/install.go | 2 +- .../pkg/agent/install/installed.go | 34 +++++++++++++------ x-pack/elastic-agent/pkg/agent/install/svc.go | 1 + .../pkg/agent/install/uninstall.go | 7 ++-- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 71d6d3c53a79..925cd03755b5 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -55,7 +55,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, force, _ := cmd.Flags().GetBool("force") if status == install.Broken { if !force { - fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s", reason) + fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s\n", reason) confirm, err := c.Confirm("Continuing will re-install Elastic Agent over the current installation. Do you want to continue?", true) if err != nil { return fmt.Errorf("Error: problem reading prompt response") diff --git a/x-pack/elastic-agent/pkg/agent/install/installed.go b/x-pack/elastic-agent/pkg/agent/install/installed.go index 98250eaf952f..510b68ffa518 100644 --- a/x-pack/elastic-agent/pkg/agent/install/installed.go +++ b/x-pack/elastic-agent/pkg/agent/install/installed.go @@ -10,6 +10,7 @@ import ( "path/filepath" ) +// InstallStatus is the return status types. type InstallStatus int const ( @@ -24,20 +25,18 @@ const ( // Status returns the installation status of Agent. func Status() (InstallStatus, string) { expected := filepath.Join(InstallPath, BinaryName) + status, reason := checkService() _, err := os.Stat(expected) if os.IsNotExist(err) { - return NotInstalled, "binary not located at install path" + if status == Installed { + // service installed, but no install path + return Broken, "service exists but installation path is missing" + } + return NotInstalled, "no install path or service" } - svc, err := newService() - if err != nil { - return Broken, "unable to check service status" - } - status, err := svc.Status() - if err != nil { - return Broken, "unable to check service status" - } - if status == service.StatusUnknown { - return Broken, "service is not installed" + if status == NotInstalled { + // install path present, but not service + return Broken, reason } return Installed, "" } @@ -51,3 +50,16 @@ func RunningInstalled() bool { execPath, _ := os.Executable() return expected == execPath } + +// checkService only checks the status of the service. +func checkService() (InstallStatus, string) { + svc, err := newService() + if err != nil { + return NotInstalled, "unable to check service status" + } + status, _ := svc.Status() + if status == service.StatusUnknown { + return NotInstalled, "service is not installed" + } + return Installed, "" +} diff --git a/x-pack/elastic-agent/pkg/agent/install/svc.go b/x-pack/elastic-agent/pkg/agent/install/svc.go index 3694eba4b3d5..6065e2a3233e 100644 --- a/x-pack/elastic-agent/pkg/agent/install/svc.go +++ b/x-pack/elastic-agent/pkg/agent/install/svc.go @@ -10,6 +10,7 @@ const ( // ServiceDisplayName is the service display name for the service. ServiceDisplayName = "Elastic Agent" + // ServiceDescription is the description for the service. ServiceDescription = ` Elastic Agent is a unified agent to observe, monitor and protect your system. ` diff --git a/x-pack/elastic-agent/pkg/agent/install/uninstall.go b/x-pack/elastic-agent/pkg/agent/install/uninstall.go index 6ae012fd5bb2..52948f0697f9 100644 --- a/x-pack/elastic-agent/pkg/agent/install/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/install/uninstall.go @@ -16,10 +16,7 @@ func Uninstall() error { if err != nil { return err } - status, err := svc.Status() - if err != nil { - return err - } + status, _ := svc.Status() if status == service.StatusRunning { err := svc.Stop() if err != nil { @@ -52,7 +49,7 @@ func Uninstall() error { // remove, if present on platform if ShellWrapperPath != "" { err = os.Remove(ShellWrapperPath) - if !os.IsNotExist(err) { + if !os.IsNotExist(err) && err != nil { return errors.New( err, fmt.Sprintf("failed to remove shell wrapper (%s)", ShellWrapperPath), From 86983aae7144dcab674bedf649d3e99526e668c7 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Fri, 18 Sep 2020 00:50:32 -0400 Subject: [PATCH 07/33] Add dedicated uninstall command. --- x-pack/elastic-agent/pkg/agent/cmd/common.go | 1 + x-pack/elastic-agent/pkg/agent/cmd/install.go | 13 +-- .../elastic-agent/pkg/agent/cmd/uninstall.go | 83 +++++++++++++++++++ x-pack/elastic-agent/pkg/agent/install/svc.go | 9 +- x-pack/elastic-agent/pkg/agent/warn/warn.go | 1 + 5 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/cmd/uninstall.go diff --git a/x-pack/elastic-agent/pkg/agent/cmd/common.go b/x-pack/elastic-agent/pkg/agent/cmd/common.go index 5b756cf2dbe1..2d58107a7e94 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/common.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/common.go @@ -73,6 +73,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { cmd.AddCommand(basecmd.NewDefaultCommandsWithArgs(args, streams)...) cmd.AddCommand(run) cmd.AddCommand(newInstallCommandWithArgs(flags, args, streams)) + cmd.AddCommand(newUninstallCommandWithArgs(flags, args, streams)) cmd.AddCommand(newEnrollCommandWithArgs(flags, args, streams)) cmd.AddCommand(newInspectCommandWithArgs(flags, args, streams)) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 925cd03755b5..6668975f84c4 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -20,9 +20,9 @@ import ( func newInstallCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "install", - Short: "Install Agent permanently on this system", + Short: "Install Elastic Agent permanently on this system", Long: ` -This will install Agent permanently on this system and will become managed by the systems service manager. +This will install Elastic Agent permanently on this system and will become managed by the systems service manager. Unless all the require command-line parameters are provided or -f is used this command will ask questions on how you would like the Agent to operate. @@ -44,11 +44,11 @@ would like the Agent to operate. func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { if !install.HasRoot() { - return fmt.Errorf("Error: unable to start install command, not executed with %s permissions.", install.PermissionUser) + return fmt.Errorf("Error: unable to perform install command, not executed with %s permissions.", install.PermissionUser) } status, reason := install.Status() if status == install.Installed { - return fmt.Errorf("Error: Elastic Agent is already installed at: %s", install.InstallPath) + return fmt.Errorf("Elastic Agent is already installed at: %s", install.InstallPath) } warn.PrintNotGA(streams.Out) @@ -61,7 +61,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, return fmt.Errorf("Error: problem reading prompt response") } if !confirm { - return fmt.Errorf("Warn: Installation was cancelled by the user") + return fmt.Errorf("Installation was cancelled by the user") } } } else { @@ -71,7 +71,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, return fmt.Errorf("Error: problem reading prompt response") } if !confirm { - return fmt.Errorf("Warn: Installation was cancelled by the user") + return fmt.Errorf("Installation was cancelled by the user") } } } @@ -85,6 +85,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, fmt.Fprintf(streams.Out, "Installation of required system files was successful, but starting of the service failed.") return fmt.Errorf("Error: %s", err) } + fmt.Fprintf(streams.Out, "Installation was successful and Elastic Agent is running.") /* insecure, _ := cmd.Flags().GetBool("insecure") diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go new file mode 100644 index 000000000000..4d276541e95f --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -0,0 +1,83 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" + + c "github.com/elastic/beats/v7/libbeat/common/cli" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" +) + +func newUninstallCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "uninstall", + Short: "Uninstall permanent Elastic Agent from this system", + Long: ` +This will uninstall permanent Elastic Agent from this system and will no longer be managed by this system. + +Unless -f is used this command will ask confirmation before performing removal. +`, + Run: func(c *cobra.Command, args []string) { + if err := uninstallCmd(streams, c, flags, args); err != nil { + fmt.Fprintf(streams.Err, "%v\n", err) + os.Exit(1) + } + }, + } + + cmd.Flags().BoolP("force", "f", false, "Force overwrite the current and do not prompt for confirmation") + + return cmd +} + +func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { + if !install.HasRoot() { + return fmt.Errorf("Error: unable to perform uninstall command, not executed with %s permissions.", install.PermissionUser) + } + status, reason := install.Status() + if status == install.NotInstalled { + return fmt.Errorf("Elastic Agent is not installed") + } + if status == install.Installed && !install.RunningInstalled() { + return fmt.Errorf("Elastic Agent can only be uninstall by executing the installed Elastic Agent at: %s", install.ExecutablePath()) + } + + force, _ := cmd.Flags().GetBool("force") + if status == install.Broken { + if !force { + fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s\n", reason) + confirm, err := c.Confirm("Continuing will uninstall the broken Elastic Agent. Do you want to continue?", true) + if err != nil { + return fmt.Errorf("Error: problem reading prompt response") + } + if !confirm { + return fmt.Errorf("Uninstall was cancelled by the user") + } + } + } else { + if !force { + confirm, err := c.Confirm("Elastic Agent will be uninstalled from your system. Do you want to continue?", true) + if err != nil { + return fmt.Errorf("Error: problem reading prompt response") + } + if !confirm { + return fmt.Errorf("Uninstall was cancelled by the user") + } + } + } + + err := install.Uninstall() + if err != nil { + return fmt.Errorf("Error: %s", err) + } + fmt.Fprintf(streams.Out, "Elastic Agent has been uninstalled.") + return nil +} diff --git a/x-pack/elastic-agent/pkg/agent/install/svc.go b/x-pack/elastic-agent/pkg/agent/install/svc.go index 6065e2a3233e..d28e32ece172 100644 --- a/x-pack/elastic-agent/pkg/agent/install/svc.go +++ b/x-pack/elastic-agent/pkg/agent/install/svc.go @@ -16,16 +16,21 @@ Elastic Agent is a unified agent to observe, monitor and protect your system. ` ) -func newService() (service.Service, error) { +// ExecutablePath returns the path for the installed Agents executable. +func ExecutablePath() string { exec := filepath.Join(InstallPath, BinaryName) if ShellWrapperPath != "" { exec = ShellWrapperPath } + return exec +} + +func newService() (service.Service, error) { return service.New(nil, &service.Config{ Name: ServiceName, DisplayName: ServiceDisplayName, Description: ServiceDescription, - Executable: exec, + Executable: ExecutablePath(), WorkingDirectory: InstallPath, }) } diff --git a/x-pack/elastic-agent/pkg/agent/warn/warn.go b/x-pack/elastic-agent/pkg/agent/warn/warn.go index 03d746992f6e..182d2af50b01 100644 --- a/x-pack/elastic-agent/pkg/agent/warn/warn.go +++ b/x-pack/elastic-agent/pkg/agent/warn/warn.go @@ -21,4 +21,5 @@ func LogNotGA(log *logger.Logger) { // PrintNotGA writes to the received writer that the Agent is not GA. func PrintNotGA(output io.Writer) { fmt.Fprintln(output, message) + fmt.Fprintln(output) } From 29e507bc4c42df090dddf03e14183c927b5cf73d Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Fri, 18 Sep 2020 10:16:46 -0400 Subject: [PATCH 08/33] Add enrollment at the end of install command. --- libbeat/common/cli/input.go | 43 ++++++ libbeat/common/cli/input_test.go | 63 +++++++++ x-pack/elastic-agent/pkg/agent/cmd/enroll.go | 29 +++- x-pack/elastic-agent/pkg/agent/cmd/install.go | 128 ++++++++---------- .../elastic-agent/pkg/agent/cmd/uninstall.go | 2 +- .../pkg/agent/install/installed.go | 3 +- x-pack/elastic-agent/pkg/agent/install/svc.go | 4 + .../pkg/agent/install/uninstall.go | 4 + 8 files changed, 201 insertions(+), 75 deletions(-) create mode 100644 libbeat/common/cli/input.go create mode 100644 libbeat/common/cli/input_test.go diff --git a/libbeat/common/cli/input.go b/libbeat/common/cli/input.go new file mode 100644 index 000000000000..a6a516fd3d49 --- /dev/null +++ b/libbeat/common/cli/input.go @@ -0,0 +1,43 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cli + +import ( + "bufio" + "fmt" + "io" + "os" + + "github.com/pkg/errors" +) + +// ReadInput shows the text and ask the user to provide input. +func ReadInput(prompt string) (string, error) { + reader := bufio.NewReader(os.Stdin) + return input(reader, prompt) +} + +func input(r io.Reader, prompt string) (string, error) { + reader := bufio.NewScanner(r) + fmt.Print(prompt + " ") + + if !reader.Scan() { + return "", errors.New("error reading user input") + } + return reader.Text(), nil +} diff --git a/libbeat/common/cli/input_test.go b/libbeat/common/cli/input_test.go new file mode 100644 index 000000000000..d0e5dc320bd2 --- /dev/null +++ b/libbeat/common/cli/input_test.go @@ -0,0 +1,63 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cli + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadInput(t *testing.T) { + tests := []struct { + name string + input string + res string + }{ + { + name: "Question 1?", + input: "\n", + res: "", + }, + { + name: "Question 2?", + input: "full string input\n", + res: "full string input", + }, + { + name: "Question 3?", + input: "123456789\n", + res: "123456789", + }, + { + name: "Question 4?", + input: "false\n", + res: "false", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r := strings.NewReader(test.input) + result, err := input(r, test.name) + assert.NoError(t, err) + assert.Equal(t, test.res, result) + }) + } +} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go index 678cef48239e..a9fb15f03184 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go @@ -43,6 +43,8 @@ func newEnrollCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStr } addEnrollFlags(cmd) + cmd.Flags().BoolP("force", "f", false, "Force overwrite the current and do not prompt for confirmation") + cmd.Flags().Bool("no-restart", false, "Skip restarting the currently running daemon") return cmd } @@ -50,10 +52,33 @@ func newEnrollCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStr func addEnrollFlags(cmd *cobra.Command) { cmd.Flags().StringP("certificate-authorities", "a", "", "Comma separated list of root certificate for server verifications") cmd.Flags().StringP("ca-sha256", "p", "", "Comma separated list of certificate authorities hash pins used for certificate verifications") - cmd.Flags().BoolP("force", "f", false, "Force overwrite the current and do not prompt for confirmation") cmd.Flags().BoolP("insecure", "i", false, "Allow insecure connection to Kibana") cmd.Flags().StringP("staging", "", "", "Configures agent to download artifacts from a staging build") - cmd.Flags().Bool("no-restart", false, "Skip restarting the currently running daemon") +} + +func buildEnrollmentFlags(cmd *cobra.Command) []string { + ca, _ := cmd.Flags().GetString("certificate-authorities") + sha256, _ := cmd.Flags().GetString("ca-sha256") + insecure, _ := cmd.Flags().GetBool("insecure") + staging, _ := cmd.Flags().GetString("staging") + + args := []string{} + if ca != "" { + args = append(args, "--certificate-authorities") + args = append(args, ca) + } + if sha256 != "" { + args = append(args, "--ca-sha256") + args = append(args, sha256) + } + if insecure { + args = append(args, "--insecure") + } + if staging != "" { + args = append(args, "--staging") + args = append(args, staging) + } + return args } func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 6668975f84c4..467ff4240c88 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" "os" + "os/exec" "github.com/spf13/cobra" @@ -35,8 +36,9 @@ would like the Agent to operate. }, } - cmd.Flags().StringP("kibana-url", "", "", "URL of Kibana to enroll Agent into Fleet") - cmd.Flags().StringP("enrollment-token", "", "", "Enrollment token to use to enroll Agent into Fleet") + cmd.Flags().StringP("kibana-url", "k", "", "URL of Kibana to enroll Agent into Fleet") + cmd.Flags().StringP("enrollment-token", "t", "", "Enrollment token to use to enroll Agent into Fleet") + cmd.Flags().BoolP("force", "f", false, "Force overwrite the current and do not prompt for confirmation") addEnrollFlags(cmd) return cmd @@ -82,89 +84,73 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } err = install.StartService() if err != nil { - fmt.Fprintf(streams.Out, "Installation of required system files was successful, but starting of the service failed.") + fmt.Fprintf(streams.Out, "Installation of required system files was successful, but starting of the service failed.\n") return fmt.Errorf("Error: %s", err) } - fmt.Fprintf(streams.Out, "Installation was successful and Elastic Agent is running.") + fmt.Fprintf(streams.Out, "Installation was successful and Elastic Agent is running.\n") - /* - insecure, _ := cmd.Flags().GetBool("insecure") - - logger, err := logger.NewFromConfig("", cfg.Settings.LoggingConfig) + askEnroll := true + kibana, _ := cmd.Flags().GetString("kibana-url") + token, _ := cmd.Flags().GetString("enrollment-token") + if kibana != "" && token != "" { + askEnroll = false + } + if force { + askEnroll = false + } + if askEnroll { + confirm, err := c.Confirm("Do you want to enroll this Agent into Fleet?", true) if err != nil { - return err + return fmt.Errorf("Error: problem reading prompt response") } - - url := args[0] - enrollmentToken := args[1] - - caStr, _ := cmd.Flags().GetString("certificate-authorities") - CAs := cli.StringToSlice(caStr) - - caSHA256str, _ := cmd.Flags().GetString("ca-sha256") - caSHA256 := cli.StringToSlice(caSHA256str) - - delay(defaultDelay) - - options := application.EnrollCmdOption{ - ID: "", // TODO(ph), This should not be an empty string, will clarify in a new PR. - EnrollAPIKey: enrollmentToken, - URL: url, - CAs: CAs, - CASha256: caSHA256, - Insecure: insecure, - UserProvidedMetadata: make(map[string]interface{}), - Staging: staging, + if !confirm { + // not enrolling, all done (standalone mode) + return nil } + } + if !askEnroll && (kibana == "" || token == "") { + // force was performed without required enrollment arguments, all done (standalone mode) + return nil + } - c, err := application.NewEnrollCmd( - logger, - &options, - pathConfigFile, - ) - + if kibana == "" { + kibana, err := c.ReadInput("Kibana URL you want to enroll this Agent into:") if err != nil { - return err + return fmt.Errorf("Error: problem reading prompt response") } - - err = c.Execute() - signal := make(chan struct{}) - - backExp := backoff.NewExpBackoff(signal, 60*time.Second, 10*time.Minute) - - for errors.Is(err, fleetapi.ErrTooManyRequests) { - fmt.Fprintln(streams.Out, "Too many requests on the remote server, will retry in a moment.") - backExp.Wait() - fmt.Fprintln(streams.Out, "Retrying to enroll...") - err = c.Execute() + if kibana == "" { + fmt.Fprintf(streams.Out, "Enrollment cancelled because no URL was provided.\n") + return nil } - - close(signal) - + } + if token == "" { + token, err := c.ReadInput("Fleet enrollment token:") if err != nil { - return errors.New(err, "fail to enroll") + return fmt.Errorf("Error: problem reading prompt response") } - - fmt.Fprintln(streams.Out, "Successfully enrolled the Elastic Agent.") - - // skip restarting - noRestart, _ := cmd.Flags().GetBool("no-restart") - if noRestart { + if token == "" { + fmt.Fprintf(streams.Out, "Enrollment cancelled because no enrollment token was provided.\n") return nil } + } - daemon := client.New() - err = daemon.Connect(context.Background()) - if err == nil { - defer daemon.Disconnect() - err = daemon.Restart(context.Background()) - if err == nil { - fmt.Fprintln(streams.Out, "Successfully triggered restart on running Elastic Agent.") - return nil - } - } - fmt.Fprintln(streams.Out, "Elastic Agent might not be running; unable to trigger restart") + enrollArgs := []string{"enroll", kibana, token, "--force"} + enrollArgs = append(enrollArgs, buildEnrollmentFlags(cmd)...) + enrollCmd := exec.Command(install.ExecutablePath(), enrollArgs...) + enrollCmd.Stdin = os.Stdin + enrollCmd.Stdout = os.Stdout + enrollCmd.Stderr = os.Stderr + err = enrollCmd.Start() + if err != nil { + return fmt.Errorf("Error: failed to execute enroll command: %s", err) + } + err = enrollCmd.Wait() + if err == nil { return nil - */ - return nil + } + exitErr, ok := err.(*exec.ExitError) + if ok { + return fmt.Errorf("Error: enroll command failed with exit code %d", exitErr.ExitCode()) + } + return fmt.Errorf("Error: enroll command failed for unknown reason: %s", err) } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index 4d276541e95f..a15a88dc1a22 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -78,6 +78,6 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags if err != nil { return fmt.Errorf("Error: %s", err) } - fmt.Fprintf(streams.Out, "Elastic Agent has been uninstalled.") + fmt.Fprintf(streams.Out, "Elastic Agent has been uninstalled.\n") return nil } diff --git a/x-pack/elastic-agent/pkg/agent/install/installed.go b/x-pack/elastic-agent/pkg/agent/install/installed.go index 510b68ffa518..2f99bdcd6e37 100644 --- a/x-pack/elastic-agent/pkg/agent/install/installed.go +++ b/x-pack/elastic-agent/pkg/agent/install/installed.go @@ -5,9 +5,10 @@ package install import ( - "github.com/kardianos/service" "os" "path/filepath" + + "github.com/kardianos/service" ) // InstallStatus is the return status types. diff --git a/x-pack/elastic-agent/pkg/agent/install/svc.go b/x-pack/elastic-agent/pkg/agent/install/svc.go index d28e32ece172..44494eda9a9f 100644 --- a/x-pack/elastic-agent/pkg/agent/install/svc.go +++ b/x-pack/elastic-agent/pkg/agent/install/svc.go @@ -1,3 +1,7 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + package install import ( diff --git a/x-pack/elastic-agent/pkg/agent/install/uninstall.go b/x-pack/elastic-agent/pkg/agent/install/uninstall.go index 52948f0697f9..762a985bd41b 100644 --- a/x-pack/elastic-agent/pkg/agent/install/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/install/uninstall.go @@ -1,3 +1,7 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + package install import ( From 7a7716ad96cc64e4f15623b7fc11161a705a6801 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 10:17:22 -0400 Subject: [PATCH 09/33] Fix installation of shell wrapper. --- x-pack/elastic-agent/pkg/agent/cmd/install.go | 8 +++----- x-pack/elastic-agent/pkg/agent/cmd/uninstall.go | 6 ++---- x-pack/elastic-agent/pkg/agent/install/paths.go | 3 +-- x-pack/elastic-agent/pkg/agent/install/paths_darwin.go | 3 +-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 467ff4240c88..c8dfbab207da 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -11,9 +11,8 @@ import ( "github.com/spf13/cobra" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" - c "github.com/elastic/beats/v7/libbeat/common/cli" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/warn" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" ) @@ -22,8 +21,7 @@ func newInstallCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOSt cmd := &cobra.Command{ Use: "install", Short: "Install Elastic Agent permanently on this system", - Long: ` -This will install Elastic Agent permanently on this system and will become managed by the systems service manager. + Long: `This will install Elastic Agent permanently on this system and will become managed by the systems service manager. Unless all the require command-line parameters are provided or -f is used this command will ask questions on how you would like the Agent to operate. @@ -150,7 +148,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } exitErr, ok := err.(*exec.ExitError) if ok { - return fmt.Errorf("Error: enroll command failed with exit code %d", exitErr.ExitCode()) + return fmt.Errorf("Error: enroll command failed with exit code: %d", exitErr.ExitCode()) } return fmt.Errorf("Error: enroll command failed for unknown reason: %s", err) } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index a15a88dc1a22..87d56f220bb1 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -10,9 +10,8 @@ import ( "github.com/spf13/cobra" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" - c "github.com/elastic/beats/v7/libbeat/common/cli" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" ) @@ -20,8 +19,7 @@ func newUninstallCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IO cmd := &cobra.Command{ Use: "uninstall", Short: "Uninstall permanent Elastic Agent from this system", - Long: ` -This will uninstall permanent Elastic Agent from this system and will no longer be managed by this system. + Long: `This will uninstall permanent Elastic Agent from this system and will no longer be managed by this system. Unless -f is used this command will ask confirmation before performing removal. `, diff --git a/x-pack/elastic-agent/pkg/agent/install/paths.go b/x-pack/elastic-agent/pkg/agent/install/paths.go index 53fef093cff7..5936c31926ab 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths.go @@ -24,8 +24,7 @@ const ( ShellWrapperPath = "/usr/bin/elastic-agent" // ShellWrapper is the wrapper that is installed. - ShellWrapper = ` -#!/bin/sh + ShellWrapper = `#!/bin/sh exec /opt/Elastic/Agent/elastic-agent $@ ` ) diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go index 848bd05e5f2a..588689442e91 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go @@ -23,8 +23,7 @@ const ( ShellWrapperPath = "/usr/local/bin/elastic-agent" // ShellWrapper is the wrapper that is installed. - ShellWrapper = ` -#!/bin/sh + ShellWrapper = `#!/bin/sh exec /Library/Elastic/Agent/elastic-agent $@ ` ) From d54768e5f1cdb92b67d4615d6f4d960813f2e717 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 16:48:52 -0400 Subject: [PATCH 10/33] Fix root_windows.go --- .../pkg/agent/install/root_windows.go | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/root_windows.go b/x-pack/elastic-agent/pkg/agent/install/root_windows.go index 84fde178a92a..c72432084ace 100644 --- a/x-pack/elastic-agent/pkg/agent/install/root_windows.go +++ b/x-pack/elastic-agent/pkg/agent/install/root_windows.go @@ -7,7 +7,7 @@ package install import ( - "golang.org/x/sys/windows" + "os" ) const ( @@ -17,23 +17,11 @@ const ( // HasRoot returns true if the user has Administrator/SYSTEM permissions. func HasRoot() bool { - var sid *windows.SID - err := windows.AllocateAndInitializeSid( - &windows.SECURITY_NT_AUTHORITY, - 2, - windows.SECURITY_BUILTIN_DOMAIN_RID, - windows.DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &sid) + // only valid rights can open the physical drive + f, err := os.Open("\\\\.\\PHYSICALDRIVE0") if err != nil { return false } - defer windows.FreeSid(sid) - - token := windows.Token(0) - _, err = token.IsMember(sid) - if err != nil { - return false - } - return token.IsElevated() + defer f.Close() + return true } From d260e250fb13f1cb22f80c30fb0e702293d86767 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 17:32:59 -0400 Subject: [PATCH 11/33] Fix uninstall on Windows. --- .../elastic-agent/pkg/agent/cmd/uninstall.go | 11 +++++++++ .../pkg/agent/install/uninstall.go | 24 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index 87d56f220bb1..d714712728dc 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -7,6 +7,9 @@ package cmd import ( "fmt" "os" + "os/exec" + "path/filepath" + "runtime" "github.com/spf13/cobra" @@ -77,5 +80,13 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags return fmt.Errorf("Error: %s", err) } fmt.Fprintf(streams.Out, "Elastic Agent has been uninstalled.\n") + + if runtime.GOOS == "windows" { + // The installation path will still exists because we are executing from that + // directory. So cmd.exe is spawned to remove the directory after we exit. + cmd := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", "rmdir", "/s", "/q", install.InstallPath) + _ = cmd.Start() + } + return nil } diff --git a/x-pack/elastic-agent/pkg/agent/install/uninstall.go b/x-pack/elastic-agent/pkg/agent/install/uninstall.go index 762a985bd41b..381427eb8c72 100644 --- a/x-pack/elastic-agent/pkg/agent/install/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/install/uninstall.go @@ -7,6 +7,7 @@ package install import ( "fmt" "os" + "runtime" "github.com/kardianos/service" @@ -41,15 +42,6 @@ func Uninstall() error { } } - // remove existing directory - err = os.RemoveAll(InstallPath) - if err != nil { - return errors.New( - err, - fmt.Sprintf("failed to remove installation directory (%s)", InstallPath), - errors.M("directory", InstallPath)) - } - // remove, if present on platform if ShellWrapperPath != "" { err = os.Remove(ShellWrapperPath) @@ -61,5 +53,19 @@ func Uninstall() error { } } + // remove existing directory + err = os.RemoveAll(InstallPath) + if err != nil { + if runtime.GOOS == "windows" { + // possible to fail on Windows, because elastic-agent.exe is running from + // this directory. + return nil + } + return errors.New( + err, + fmt.Sprintf("failed to remove installation directory (%s)", InstallPath), + errors.M("directory", InstallPath)) + } + return nil } From 3099b66b2924e4398cefbec9632c65863a3e5b00 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 17:45:22 -0400 Subject: [PATCH 12/33] Add sleep to removal on Windows. --- x-pack/elastic-agent/pkg/agent/cmd/uninstall.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index d714712728dc..f3e1fbb4f43d 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -84,7 +84,8 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags if runtime.GOOS == "windows" { // The installation path will still exists because we are executing from that // directory. So cmd.exe is spawned to remove the directory after we exit. - cmd := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", "rmdir", "/s", "/q", install.InstallPath) + command := fmt.Sprintf("sleep 3 && rmdir /s /q %s", install.InstallPath) + cmd := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", command) _ = cmd.Start() } From c7eeb778f4c105e927b9a3630619679739f8b532 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 18:03:02 -0400 Subject: [PATCH 13/33] Fix sleep to timeout in uninstall subcommand. --- x-pack/elastic-agent/pkg/agent/cmd/uninstall.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index f3e1fbb4f43d..fd08f0f989f0 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -84,9 +84,9 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags if runtime.GOOS == "windows" { // The installation path will still exists because we are executing from that // directory. So cmd.exe is spawned to remove the directory after we exit. - command := fmt.Sprintf("sleep 3 && rmdir /s /q %s", install.InstallPath) - cmd := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", command) - _ = cmd.Start() + command := fmt.Sprintf("timeout 3 && rmdir /s /q %s", install.InstallPath) + rmdir := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", command) + _ = rmdir.Start() } return nil From 2c6f0bbfda0db7f8dcfea1721a02f18c17bd52be Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 18:14:31 -0400 Subject: [PATCH 14/33] Fix uninstall for nested timeout with cmd.exe. --- x-pack/elastic-agent/pkg/agent/cmd/uninstall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index fd08f0f989f0..d9e80331f199 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -84,7 +84,7 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags if runtime.GOOS == "windows" { // The installation path will still exists because we are executing from that // directory. So cmd.exe is spawned to remove the directory after we exit. - command := fmt.Sprintf("timeout 3 && rmdir /s /q %s", install.InstallPath) + command := fmt.Sprintf(`timeout 3 && rmdir /s /q "%s"`, install.InstallPath) rmdir := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", command) _ = rmdir.Start() } From 189b1e08fff76df440209b5d490ae45e6b1da2e2 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 21:35:18 -0400 Subject: [PATCH 15/33] Fix uninstall on windows to actually sleep correctly. --- x-pack/elastic-agent/pkg/agent/cmd/uninstall.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index d9e80331f199..6dad36be2090 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -83,9 +83,11 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags if runtime.GOOS == "windows" { // The installation path will still exists because we are executing from that - // directory. So cmd.exe is spawned to remove the directory after we exit. - command := fmt.Sprintf(`timeout 3 && rmdir /s /q "%s"`, install.InstallPath) - rmdir := exec.Command(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), "/C", command) + // directory. So cmd.exe is spawned that sleeps for 2 seconds (using ping, recommend way from + // from Windows) then rmdir is performed. + rmdir := exec.Command( + filepath.Join(os.Getenv("windir"), "system32", "cmd.exe"), + "/C", "ping", "-n", "2", "127.0.0.1", "&&", "rmdir", "/s", "/q", install.InstallPath) _ = rmdir.Start() } From 1eb59ae5fd4f9ac549718d7c71c30c1081a1d1e2 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 21:45:52 -0400 Subject: [PATCH 16/33] Fix formatting. --- libbeat/common/cli/input_test.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/libbeat/common/cli/input_test.go b/libbeat/common/cli/input_test.go index d0e5dc320bd2..de87b5efe2ae 100644 --- a/libbeat/common/cli/input_test.go +++ b/libbeat/common/cli/input_test.go @@ -26,29 +26,29 @@ import ( func TestReadInput(t *testing.T) { tests := []struct { - name string - input string - res string + name string + input string + res string }{ { - name: "Question 1?", - input: "\n", - res: "", + name: "Question 1?", + input: "\n", + res: "", }, { - name: "Question 2?", - input: "full string input\n", - res: "full string input", + name: "Question 2?", + input: "full string input\n", + res: "full string input", }, { - name: "Question 3?", - input: "123456789\n", - res: "123456789", + name: "Question 3?", + input: "123456789\n", + res: "123456789", }, { - name: "Question 4?", - input: "false\n", - res: "false", + name: "Question 4?", + input: "false\n", + res: "false", }, } From 387e4c1fcf0624225b7d6eb3d43444827a97fc6f Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 21:51:36 -0400 Subject: [PATCH 17/33] Add changelog. --- x-pack/elastic-agent/CHANGELOG.next.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/elastic-agent/CHANGELOG.next.asciidoc b/x-pack/elastic-agent/CHANGELOG.next.asciidoc index d9475d35be30..df141f3c9839 100644 --- a/x-pack/elastic-agent/CHANGELOG.next.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.next.asciidoc @@ -21,3 +21,4 @@ - Add support for dynamic inputs with providers and `{{variable|"default"}}` substitution. {pull}20839[20839] - Add support for EQL based condition on inputs {pull}20994[20994] - Send `fleet.host.id` to Endpoint Security {pull}21042[21042] +- Add `install` and `uninstall` subcommands {pull}21206[21206] From 2d9e0bb192284f4c11df0a43e8e4600f8e612c71 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 21 Sep 2020 22:42:04 -0400 Subject: [PATCH 18/33] Fixes for mage check. --- x-pack/elastic-agent/pkg/agent/cmd/install.go | 22 +++++++++---------- .../elastic-agent/pkg/agent/cmd/uninstall.go | 16 +++++++------- .../pkg/agent/install/installed.go | 10 ++++----- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index c8dfbab207da..43a834b15192 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -44,11 +44,11 @@ would like the Agent to operate. func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { if !install.HasRoot() { - return fmt.Errorf("Error: unable to perform install command, not executed with %s permissions.", install.PermissionUser) + return fmt.Errorf("unable to perform install command, not executed with %s permissions", install.PermissionUser) } status, reason := install.Status() if status == install.Installed { - return fmt.Errorf("Elastic Agent is already installed at: %s", install.InstallPath) + return fmt.Errorf("already installed at: %s", install.InstallPath) } warn.PrintNotGA(streams.Out) @@ -61,7 +61,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, return fmt.Errorf("Error: problem reading prompt response") } if !confirm { - return fmt.Errorf("Installation was cancelled by the user") + return fmt.Errorf("installation was cancelled by the user") } } } else { @@ -71,7 +71,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, return fmt.Errorf("Error: problem reading prompt response") } if !confirm { - return fmt.Errorf("Installation was cancelled by the user") + return fmt.Errorf("installation was cancelled by the user") } } } @@ -83,7 +83,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, err = install.StartService() if err != nil { fmt.Fprintf(streams.Out, "Installation of required system files was successful, but starting of the service failed.\n") - return fmt.Errorf("Error: %s", err) + return err } fmt.Fprintf(streams.Out, "Installation was successful and Elastic Agent is running.\n") @@ -99,7 +99,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, if askEnroll { confirm, err := c.Confirm("Do you want to enroll this Agent into Fleet?", true) if err != nil { - return fmt.Errorf("Error: problem reading prompt response") + return fmt.Errorf("problem reading prompt response") } if !confirm { // not enrolling, all done (standalone mode) @@ -114,7 +114,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, if kibana == "" { kibana, err := c.ReadInput("Kibana URL you want to enroll this Agent into:") if err != nil { - return fmt.Errorf("Error: problem reading prompt response") + return fmt.Errorf("problem reading prompt response") } if kibana == "" { fmt.Fprintf(streams.Out, "Enrollment cancelled because no URL was provided.\n") @@ -124,7 +124,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, if token == "" { token, err := c.ReadInput("Fleet enrollment token:") if err != nil { - return fmt.Errorf("Error: problem reading prompt response") + return fmt.Errorf("problem reading prompt response") } if token == "" { fmt.Fprintf(streams.Out, "Enrollment cancelled because no enrollment token was provided.\n") @@ -140,7 +140,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, enrollCmd.Stderr = os.Stderr err = enrollCmd.Start() if err != nil { - return fmt.Errorf("Error: failed to execute enroll command: %s", err) + return fmt.Errorf("failed to execute enroll command: %s", err) } err = enrollCmd.Wait() if err == nil { @@ -148,7 +148,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } exitErr, ok := err.(*exec.ExitError) if ok { - return fmt.Errorf("Error: enroll command failed with exit code: %d", exitErr.ExitCode()) + return fmt.Errorf("enroll command failed with exit code: %d", exitErr.ExitCode()) } - return fmt.Errorf("Error: enroll command failed for unknown reason: %s", err) + return fmt.Errorf("enroll command failed for unknown reason: %s", err) } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index 6dad36be2090..453dab6c355b 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -41,14 +41,14 @@ Unless -f is used this command will ask confirmation before performing removal. func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { if !install.HasRoot() { - return fmt.Errorf("Error: unable to perform uninstall command, not executed with %s permissions.", install.PermissionUser) + return fmt.Errorf("unable to perform uninstall command, not executed with %s permissions", install.PermissionUser) } status, reason := install.Status() if status == install.NotInstalled { - return fmt.Errorf("Elastic Agent is not installed") + return fmt.Errorf("not installed") } if status == install.Installed && !install.RunningInstalled() { - return fmt.Errorf("Elastic Agent can only be uninstall by executing the installed Elastic Agent at: %s", install.ExecutablePath()) + return fmt.Errorf("can only be uninstall by executing the installed Elastic Agent at: %s", install.ExecutablePath()) } force, _ := cmd.Flags().GetBool("force") @@ -57,27 +57,27 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s\n", reason) confirm, err := c.Confirm("Continuing will uninstall the broken Elastic Agent. Do you want to continue?", true) if err != nil { - return fmt.Errorf("Error: problem reading prompt response") + return fmt.Errorf("problem reading prompt response") } if !confirm { - return fmt.Errorf("Uninstall was cancelled by the user") + return fmt.Errorf("uninstall was cancelled by the user") } } } else { if !force { confirm, err := c.Confirm("Elastic Agent will be uninstalled from your system. Do you want to continue?", true) if err != nil { - return fmt.Errorf("Error: problem reading prompt response") + return fmt.Errorf("problem reading prompt response") } if !confirm { - return fmt.Errorf("Uninstall was cancelled by the user") + return fmt.Errorf("uninstall was cancelled by the user") } } } err := install.Uninstall() if err != nil { - return fmt.Errorf("Error: %s", err) + return err } fmt.Fprintf(streams.Out, "Elastic Agent has been uninstalled.\n") diff --git a/x-pack/elastic-agent/pkg/agent/install/installed.go b/x-pack/elastic-agent/pkg/agent/install/installed.go index 2f99bdcd6e37..685b4a177de4 100644 --- a/x-pack/elastic-agent/pkg/agent/install/installed.go +++ b/x-pack/elastic-agent/pkg/agent/install/installed.go @@ -11,12 +11,12 @@ import ( "github.com/kardianos/service" ) -// InstallStatus is the return status types. -type InstallStatus int +// StatusType is the return status types. +type StatusType int const ( // NotInstalled returned when Elastic Agent is not installed. - NotInstalled InstallStatus = iota + NotInstalled StatusType = iota // Installed returned when Elastic Agent is installed currectly. Installed // Broken returned when Elastic Agent is installed but broken. @@ -24,7 +24,7 @@ const ( ) // Status returns the installation status of Agent. -func Status() (InstallStatus, string) { +func Status() (StatusType, string) { expected := filepath.Join(InstallPath, BinaryName) status, reason := checkService() _, err := os.Stat(expected) @@ -53,7 +53,7 @@ func RunningInstalled() bool { } // checkService only checks the status of the service. -func checkService() (InstallStatus, string) { +func checkService() (StatusType, string) { svc, err := newService() if err != nil { return NotInstalled, "unable to check service status" From bb3b2165e67aa3e0e904089830547c1b9cb51e90 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 12:47:41 -0400 Subject: [PATCH 19/33] Create the symlink on Windows on install, fix issue with enroll questions during install. --- x-pack/elastic-agent/pkg/agent/cmd/checks.go | 2 +- .../pkg/agent/cmd/checks_windows.go | 2 +- x-pack/elastic-agent/pkg/agent/cmd/install.go | 4 +-- .../elastic-agent/pkg/agent/control/addr.go | 7 +++++ .../pkg/agent/control/addr_windows.go | 7 +++++ .../pkg/agent/install/install.go | 6 ++++ .../pkg/agent/install/install_unix.go | 13 +++++++++ .../pkg/agent/install/install_windows.go | 29 +++++++++++++++++++ x-pack/elastic-agent/pkg/release/version.go | 13 +++++++++ 9 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/install/install_unix.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/install_windows.go diff --git a/x-pack/elastic-agent/pkg/agent/cmd/checks.go b/x-pack/elastic-agent/pkg/agent/cmd/checks.go index 4fee74970099..58831147112b 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/checks.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/checks.go @@ -34,7 +34,7 @@ func preRunCheck(flags *globalFlags) func(cmd *cobra.Command, args []string) err } // get versioned path - smallHash := fmt.Sprintf("elastic-agent-%s", smallHash(release.Commit())) + smallHash := fmt.Sprintf("elastic-agent-%s", release.ShortCommit()) commitFilepath := filepath.Join(paths.Config(), commitFile) // use other file in the future if content, err := ioutil.ReadFile(commitFilepath); err == nil { smallHash = hashedDirName(content) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go b/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go index 36108c8e08bd..d5ba239c3b56 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go @@ -39,7 +39,7 @@ func preRunCheck(flags *globalFlags) func(cmd *cobra.Command, args []string) err return nil } - smallHash := fmt.Sprintf("elastic-agent-%s", smallHash(release.Commit())) + smallHash := fmt.Sprintf("elastic-agent-%s", release.ShortCommit()) commitFilepath := filepath.Join(paths.Config(), commitFile) if content, err := ioutil.ReadFile(commitFilepath); err == nil { smallHash = hashedDirName(content) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 43a834b15192..bf702e6d53da 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -112,7 +112,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } if kibana == "" { - kibana, err := c.ReadInput("Kibana URL you want to enroll this Agent into:") + kibana, err = c.ReadInput("Kibana URL you want to enroll this Agent into:") if err != nil { return fmt.Errorf("problem reading prompt response") } @@ -122,7 +122,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } } if token == "" { - token, err := c.ReadInput("Fleet enrollment token:") + token, err = c.ReadInput("Fleet enrollment token:") if err != nil { return fmt.Errorf("problem reading prompt response") } diff --git a/x-pack/elastic-agent/pkg/agent/control/addr.go b/x-pack/elastic-agent/pkg/agent/control/addr.go index 20bc1e6a0056..31005e8e34d8 100644 --- a/x-pack/elastic-agent/pkg/agent/control/addr.go +++ b/x-pack/elastic-agent/pkg/agent/control/addr.go @@ -11,10 +11,17 @@ import ( "fmt" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" ) // Address returns the address to connect to Elastic Agent daemon. func Address() string { + // when installed the control address is fixed + if install.RunningInstalled() { + return install.SocketPath + } + + // not install, adjust the path based on data path data := paths.Data() // entire string cannot be longer than 107 characters, this forces the // length to always be 88 characters (but unique per data path) diff --git a/x-pack/elastic-agent/pkg/agent/control/addr_windows.go b/x-pack/elastic-agent/pkg/agent/control/addr_windows.go index bf2e164fbaec..cbfcdf2c99ea 100644 --- a/x-pack/elastic-agent/pkg/agent/control/addr_windows.go +++ b/x-pack/elastic-agent/pkg/agent/control/addr_windows.go @@ -11,10 +11,17 @@ import ( "fmt" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" ) // Address returns the address to connect to Elastic Agent daemon. func Address() string { + // when installed the control address is fixed + if install.RunningInstalled() { + return install.SocketPath + } + + // not install, adjust the path based on data path data := paths.Data() // entire string cannot be longer than 256 characters, this forces the // length to always be 87 characters (but unique per data path) diff --git a/x-pack/elastic-agent/pkg/agent/install/install.go b/x-pack/elastic-agent/pkg/agent/install/install.go index 3471eda38867..3b74e4653ec4 100644 --- a/x-pack/elastic-agent/pkg/agent/install/install.go +++ b/x-pack/elastic-agent/pkg/agent/install/install.go @@ -60,6 +60,12 @@ func Install() error { } } + // post install (per platform) + err = postInstall() + if err != nil { + return err + } + // install service svc, err := newService() if err != nil { diff --git a/x-pack/elastic-agent/pkg/agent/install/install_unix.go b/x-pack/elastic-agent/pkg/agent/install/install_unix.go new file mode 100644 index 000000000000..07d696d580a1 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/install_unix.go @@ -0,0 +1,13 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build !windows + +package install + +// postInstall performs post installation for unix-based systems. +func postInstall() error { + // do nothing + return nil +} diff --git a/x-pack/elastic-agent/pkg/agent/install/install_windows.go b/x-pack/elastic-agent/pkg/agent/install/install_windows.go new file mode 100644 index 000000000000..08c7278be2d8 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/install_windows.go @@ -0,0 +1,29 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build windows + +package install + +import "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" + +// postInstall performs post installation for Windows systems. +func postInstall() error { + // delete the top-level elastic-agent.exe + binary := filepath.Join(InstallPath, BinaryName) + err := os.Remove(binary) + if err != nil { + // do not handle does not exist, it should have existed + return err + } + + // create top-level symlink to nested binary + realBinary := filepath.Join(InstallPath, "data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit()), BinaryName) + err = os.Symlink(binary, realBinary) + if err != nil { + return err + } + + return nil +} diff --git a/x-pack/elastic-agent/pkg/release/version.go b/x-pack/elastic-agent/pkg/release/version.go index 37579ac86de5..67a423a64c57 100644 --- a/x-pack/elastic-agent/pkg/release/version.go +++ b/x-pack/elastic-agent/pkg/release/version.go @@ -12,6 +12,10 @@ import ( libbeatVersion "github.com/elastic/beats/v7/libbeat/version" ) +const ( + hashLen = 6 +) + // snapshot is a flag marking build as a snapshot. var snapshot = "" @@ -24,6 +28,15 @@ func Commit() string { return libbeatVersion.Commit() } +// ShortCommit returns commit up to 6 characters. +func ShortCommit() string { + hash := Commit() + if len(hash) > hashLen { + hash = hash[:hashLen] + } + return hash +} + // BuildTime returns the build time of the binaries. func BuildTime() time.Time { return libbeatVersion.BuildTime() From a508c2bfe4f6349a25d7f995fddfb27ad14e6b85 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 13:55:54 -0400 Subject: [PATCH 20/33] Refactor to remove the paths.yml and symlink dance on Windows, because new install command handles that. --- .../templates/linux/elastic-agent.sh.tmpl | 1 - .../pkg/agent/application/paths/paths.go | 108 ++++++----------- .../agent/application/upgrade/step_mark.go | 54 +-------- .../agent/application/upgrade/step_relink.go | 10 +- .../pkg/agent/application/upgrade/upgrade.go | 5 - x-pack/elastic-agent/pkg/agent/cmd/checks.go | 57 --------- .../pkg/agent/cmd/checks_windows.go | 114 ------------------ x-pack/elastic-agent/pkg/agent/cmd/common.go | 57 --------- .../pkg/agent/install/install_windows.go | 8 +- 9 files changed, 50 insertions(+), 364 deletions(-) delete mode 100644 x-pack/elastic-agent/pkg/agent/cmd/checks.go delete mode 100644 x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go diff --git a/dev-tools/packaging/templates/linux/elastic-agent.sh.tmpl b/dev-tools/packaging/templates/linux/elastic-agent.sh.tmpl index 744abc05702d..835b59553243 100644 --- a/dev-tools/packaging/templates/linux/elastic-agent.sh.tmpl +++ b/dev-tools/packaging/templates/linux/elastic-agent.sh.tmpl @@ -6,6 +6,5 @@ exec /usr/share/{{.BeatName}}/bin/{{.BeatName}} \ --path.home /var/lib/{{.BeatName}} \ --path.config /etc/{{.BeatName}} \ - --path.data /var/lib/{{.BeatName}}/data \ --path.logs /var/log/{{.BeatName}} \ "$@" diff --git a/x-pack/elastic-agent/pkg/agent/application/paths/paths.go b/x-pack/elastic-agent/pkg/agent/application/paths/paths.go index 48544ec7593d..b646f3796ba8 100644 --- a/x-pack/elastic-agent/pkg/agent/application/paths/paths.go +++ b/x-pack/elastic-agent/pkg/agent/application/paths/paths.go @@ -6,121 +6,87 @@ package paths import ( "flag" + "fmt" "os" "path/filepath" - "runtime" - "sync" + "strings" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" ) var ( - homePath string - configPath string - dataPath string - logsPath string - serviceName string - - overridesLoader sync.Once + topPath string + configPath string + logsPath string ) func init() { - initialHome := initialHome() + topPath = initialTop() + configPath = topPath + logsPath = topPath fs := flag.CommandLine - fs.StringVar(&homePath, "path.home", initialHome, "Agent root path") - fs.StringVar(&configPath, "path.config", initialHome, "Config path is the directory Agent looks for its config file") - fs.StringVar(&dataPath, "path.data", filepath.Join(initialHome, "data"), "Data path contains Agent managed binaries") - fs.StringVar(&logsPath, "path.logs", initialHome, "Logs path contains Agent log output") + fs.StringVar(&topPath, "path.home", topPath, "Agent root path") + fs.StringVar(&configPath, "path.config", configPath, "Config path is the directory Agent looks for its config file") + fs.StringVar(&logsPath, "path.logs", logsPath, "Logs path contains Agent log output") } -// UpdatePaths update paths based on changes in paths file. -func UpdatePaths() { - getOverrides() -} - -func getOverrides() { - type paths struct { - HomePath string `config:"path.home" yaml:"path.home"` - ConfigPath string `config:"path.config" yaml:"path.config"` - DataPath string `config:"path.data" yaml:"path.data"` - LogsPath string `config:"path.logs" yaml:"path.logs"` - ServiceName string `config:"path.service_name" yaml:"path.service_name"` - } - - defaults := &paths{ - HomePath: homePath, - ConfigPath: configPath, - DataPath: dataPath, - LogsPath: logsPath, - } - - pathsFile := filepath.Join(dataPath, "paths.yml") - rawConfig, err := config.LoadYAML(pathsFile) - if err != nil { - return - } - - rawConfig.Unpack(defaults) - homePath = defaults.HomePath - configPath = defaults.ConfigPath - dataPath = defaults.DataPath - logsPath = defaults.LogsPath - serviceName = defaults.ServiceName -} - -// ServiceName return predefined service name if defined by initial call. -func ServiceName() string { - // needs to do this at this place because otherwise it will - // get overwritten by flags behavior. - overridesLoader.Do(getOverrides) - return serviceName +// Top returns the top directory for Elastic Agent, all the versioned +// home directories live under this top-level/data/elastic-agent-${hash} +func Top() string { + return topPath } // Home returns a directory where binary lives -// Executable is not supported on nacl. func Home() string { - overridesLoader.Do(getOverrides) - return homePath + return versionedHome(topPath) } // Config returns a directory where configuration file lives func Config() string { - overridesLoader.Do(getOverrides) return configPath } // Data returns the data directory for Agent func Data() string { - overridesLoader.Do(getOverrides) - return dataPath + return filepath.Join(Top(), "data") } // Logs returns a the log directory for Agent func Logs() string { - overridesLoader.Do(getOverrides) return logsPath } +// initialTop returns the initial top-level path for the binary +// +// When nested in top-level/data/elastic-agent-${hash}/ the result is top-level/. +func initialTop() string { + exePath := retrieveExecutablePath() + if insideData(exePath) { + return filepath.Dir(filepath.Dir(exePath)) + } + return exePath +} + +// retrieveExecutablePath returns the executing binary, even if the started binary was a symlink func retrieveExecutablePath() string { execPath, err := os.Executable() if err != nil { panic(err) } - evalPath, err := filepath.EvalSymlinks(execPath) if err != nil { panic(err) } - return filepath.Dir(evalPath) } -func initialHome() string { - exePath := retrieveExecutablePath() - if runtime.GOOS == "windows" { - return exePath - } +// insideData returns true when the exePath is inside of the current Agents data path. +func insideData(exePath string) bool { + expectedPath := filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit())) + return strings.HasSuffix(exePath, expectedPath) +} - return filepath.Dir(filepath.Dir(exePath)) // is two level up the executable (symlink evaluated) +func versionedHome(base string) string { + return filepath.Join(base, "data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit())) } diff --git a/x-pack/elastic-agent/pkg/agent/application/upgrade/step_mark.go b/x-pack/elastic-agent/pkg/agent/application/upgrade/step_mark.go index 0d8253bb9ca8..53920e6ecffa 100644 --- a/x-pack/elastic-agent/pkg/agent/application/upgrade/step_mark.go +++ b/x-pack/elastic-agent/pkg/agent/application/upgrade/step_mark.go @@ -6,11 +6,8 @@ package upgrade import ( "context" - "fmt" "io/ioutil" - "os" "path/filepath" - "runtime" "time" "gopkg.in/yaml.v2" @@ -41,10 +38,6 @@ type updateMarker struct { // markUpgrade marks update happened so we can handle grace period func (h *Upgrader) markUpgrade(ctx context.Context, hash string, action *fleetapi.ActionUpgrade) error { - if err := updateHomePath(hash); err != nil { - return err - } - prevVersion := release.Version() prevHash := release.Commit() if len(prevHash) > hashLen { @@ -69,55 +62,10 @@ func (h *Upgrader) markUpgrade(ctx context.Context, hash string, action *fleetap return errors.New(err, errors.TypeFilesystem, "failed to create update marker file", errors.M(errors.MetaKeyPath, markerPath)) } - activeCommitPath := filepath.Join(paths.Config(), agentCommitFile) + activeCommitPath := filepath.Join(paths.Top(), agentCommitFile) if err := ioutil.WriteFile(activeCommitPath, []byte(hash), 0644); err != nil { return errors.New(err, errors.TypeFilesystem, "failed to update active commit", errors.M(errors.MetaKeyPath, activeCommitPath)) } return nil } - -func updateHomePath(hash string) error { - if err := createPathsSymlink(hash); err != nil { - return errors.New(err, errors.TypeFilesystem, "failed to create paths symlink") - } - - pathsMap := make(map[string]string) - pathsFilepath := filepath.Join(paths.Data(), "paths.yml") - - pathsBytes, err := ioutil.ReadFile(pathsFilepath) - if err != nil { - return errors.New(err, errors.TypeConfig, "failed to read paths file") - } - - if err := yaml.Unmarshal(pathsBytes, &pathsMap); err != nil { - return errors.New(err, errors.TypeConfig, "failed to parse paths file") - } - - pathsMap["path.home"] = filepath.Join(filepath.Dir(paths.Home()), fmt.Sprintf("%s-%s", agentName, hash)) - - pathsBytes, err = yaml.Marshal(pathsMap) - if err != nil { - return errors.New(err, errors.TypeConfig, "failed to marshal paths file") - } - - return ioutil.WriteFile(pathsFilepath, pathsBytes, 0740) -} - -func createPathsSymlink(hash string) error { - // only on windows, as windows resolves PWD using symlinks in a different way. - // we create symlink for each versioned agent inside `data/` directory - // on other systems path is shared - if runtime.GOOS != "windows" { - return nil - } - - dir := filepath.Join(paths.Data(), fmt.Sprintf("%s-%s", agentName, hash)) - versionedPath := filepath.Join(dir, "data", "paths.yml") - if err := os.MkdirAll(filepath.Dir(versionedPath), 0700); err != nil { - return err - } - - pathsCfgPath := filepath.Join(paths.Data(), "paths.yml") - return os.Symlink(pathsCfgPath, versionedPath) -} diff --git a/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go b/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go index 48d22de36cfe..e0cf407afbf8 100644 --- a/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go +++ b/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go @@ -21,18 +21,18 @@ func (u *Upgrader) changeSymlink(ctx context.Context, newHash string) error { // create symlink to elastic-agent-{hash} hashedDir := fmt.Sprintf("%s-%s", agentName, newHash) - agentBakName := agentName + ".bak" - symlinkPath := filepath.Join(paths.Config(), agentName) - newPath := filepath.Join(paths.Data(), hashedDir, agentName) + agentBakName := agentName + ".prev" + symlinkPath := filepath.Join(paths.Top(), agentName) + newPath := filepath.Join(paths.Top(), "data", hashedDir, agentName) // handle windows suffixes if runtime.GOOS == "windows" { - agentBakName = agentName + ".exe.back" //.bak is already used + agentBakName = agentName + ".exe.prev" //.bak is already used symlinkPath += ".exe" newPath += ".exe" } - bakNewPath := filepath.Join(paths.Config(), agentBakName) + bakNewPath := filepath.Join(paths.Top(), agentBakName) if err := os.Symlink(newPath, bakNewPath); err != nil { return errors.New(err, errors.TypeFilesystem, "failed to update agent symlink") } diff --git a/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go b/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go index cc27846051f0..56bb41246028 100644 --- a/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go +++ b/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go @@ -129,11 +129,6 @@ func (u *Upgrader) Ack(ctx context.Context) error { return ioutil.WriteFile(markerFile, markerBytes, 0600) } -func isSubdir(base, target string) (bool, error) { - relPath, err := filepath.Rel(base, target) - return strings.HasPrefix(relPath, ".."), err -} - func rollbackInstall(hash string) { os.RemoveAll(filepath.Join(paths.Data(), fmt.Sprintf("%s-%s", agentName, hash))) } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/checks.go b/x-pack/elastic-agent/pkg/agent/cmd/checks.go deleted file mode 100644 index 58831147112b..000000000000 --- a/x-pack/elastic-agent/pkg/agent/cmd/checks.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -// +build !windows - -package cmd - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - // import logp flags - _ "github.com/elastic/beats/v7/libbeat/logp/configure" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" -) - -// preRunCheck is noop because -// - darwin.tar - symlink created during packaging -// - linux.tar - symlink created during packaging -// - linux.rpm - symlink created using install script -// - linux.deb - symlink created using install script -// - linux.docker - symlink created using Dockerfile -func preRunCheck(flags *globalFlags) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - if sn := paths.ServiceName(); sn != "" { - // paths were created we're running as child. - return nil - } - - // get versioned path - smallHash := fmt.Sprintf("elastic-agent-%s", release.ShortCommit()) - commitFilepath := filepath.Join(paths.Config(), commitFile) // use other file in the future - if content, err := ioutil.ReadFile(commitFilepath); err == nil { - smallHash = hashedDirName(content) - } - - origExecPath, err := os.Executable() - if err != nil { - return err - } - reexecPath := filepath.Join(paths.Data(), smallHash, filepath.Base(origExecPath)) - - // generate paths - if err := generatePaths(filepath.Dir(reexecPath), origExecPath); err != nil { - return err - } - - paths.UpdatePaths() - return nil - } -} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go b/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go deleted file mode 100644 index d5ba239c3b56..000000000000 --- a/x-pack/elastic-agent/pkg/agent/cmd/checks_windows.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -// +build windows - -package cmd - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - // import logp flags - _ "github.com/elastic/beats/v7/libbeat/logp/configure" - - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/reexec" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/configuration" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" -) - -func preRunCheck(flags *globalFlags) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - if sn := paths.ServiceName(); sn != "" { - // replacing with correct service name so we - // can talk to service manager. - if !filepath.IsAbs(os.Args[0]) { - os.Args[0] = sn - } - - // paths were created we're running as child. - return nil - } - - smallHash := fmt.Sprintf("elastic-agent-%s", release.ShortCommit()) - commitFilepath := filepath.Join(paths.Config(), commitFile) - if content, err := ioutil.ReadFile(commitFilepath); err == nil { - smallHash = hashedDirName(content) - } - - // rename itself - origExecPath, err := os.Executable() - if err != nil { - return err - } - - if err := os.Rename(origExecPath, origExecPath+".bak"); err != nil { - return err - } - - // create symlink to elastic-agent-{hash} - reexecPath := filepath.Join(paths.Data(), smallHash, filepath.Base(origExecPath)) - if err := os.Symlink(reexecPath, origExecPath); err != nil { - return err - } - - // generate paths - if err := generatePaths(filepath.Dir(reexecPath), origExecPath); err != nil { - return err - } - - paths.UpdatePaths() - - // reexec if running run - if cmd.Use == "run" { - pathConfigFile := flags.Config() - rawConfig, err := config.LoadYAML(pathConfigFile) - if err != nil { - return errors.New(err, - fmt.Sprintf("could not read configuration file %s", pathConfigFile), - errors.TypeFilesystem, - errors.M(errors.MetaKeyPath, pathConfigFile)) - } - - cfg, err := configuration.NewFromConfig(rawConfig) - if err != nil { - return errors.New(err, - fmt.Sprintf("could not parse configuration file %s", pathConfigFile), - errors.TypeFilesystem, - errors.M(errors.MetaKeyPath, pathConfigFile)) - } - - logger, err := logger.NewFromConfig("", cfg.Settings.LoggingConfig) - if err != nil { - return err - } - - rexLogger := logger.Named("reexec") - rm := reexec.NewManager(rexLogger, reexecPath) - - argsOverrides := []string{ - "--path.data", paths.Data(), - "--path.home", filepath.Dir(reexecPath), - "--path.config", paths.Config(), - } - rm.ReExec(argsOverrides...) - - // trigger reexec - rm.ShutdownComplete() - - // return without running Run method - os.Exit(0) - } - - return nil - } -} diff --git a/x-pack/elastic-agent/pkg/agent/cmd/common.go b/x-pack/elastic-agent/pkg/agent/cmd/common.go index 2d58107a7e94..8ca5700f3c6b 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/common.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/common.go @@ -6,15 +6,10 @@ package cmd import ( "flag" - "fmt" - "io/ioutil" "os" "path/filepath" - "runtime" - "strings" "github.com/spf13/cobra" - "gopkg.in/yaml.v2" // import logp flags _ "github.com/elastic/beats/v7/libbeat/logp/configure" @@ -58,7 +53,6 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { // path flags cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.home")) cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.config")) - cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.data")) cmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.logs")) cmd.PersistentFlags().StringVarP(&flags.PathConfigFile, "c", "c", defaultConfig, `Configuration file, relative to path.config`) @@ -82,58 +76,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { if reexec != nil { cmd.AddCommand(reexec) } - run.PersistentPreRunE = preRunCheck(flags) cmd.Run = run.Run return cmd } - -func hashedDirName(filecontent []byte) string { - s := strings.TrimSpace(string(filecontent)) - if len(s) == 0 { - return "elastic-agent" - } - - s = smallHash(s) - - return fmt.Sprintf("elastic-agent-%s", s) -} - -func smallHash(hash string) string { - if len(hash) > hashLen { - hash = hash[:hashLen] - } - - return hash -} - -func generatePaths(dir, origExec string) error { - pathsCfg := map[string]interface{}{ - "path.data": paths.Data(), - "path.home": dir, - "path.config": paths.Config(), - "path.service_name": origExec, - } - - pathsCfgPath := filepath.Join(paths.Data(), "paths.yml") - pathsContent, err := yaml.Marshal(pathsCfg) - if err != nil { - return err - } - - if err := ioutil.WriteFile(pathsCfgPath, pathsContent, 0740); err != nil { - return err - } - - if runtime.GOOS == "windows" { - // due to two binaries we need to do a path dance - // as versioned binary will look for path inside it's own directory - versionedPath := filepath.Join(dir, "data", "paths.yml") - if err := os.MkdirAll(filepath.Dir(versionedPath), 0700); err != nil { - return err - } - return os.Symlink(pathsCfgPath, versionedPath) - } - - return nil -} diff --git a/x-pack/elastic-agent/pkg/agent/install/install_windows.go b/x-pack/elastic-agent/pkg/agent/install/install_windows.go index 08c7278be2d8..af572fe4994e 100644 --- a/x-pack/elastic-agent/pkg/agent/install/install_windows.go +++ b/x-pack/elastic-agent/pkg/agent/install/install_windows.go @@ -6,7 +6,13 @@ package install -import "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" +import ( + "fmt" + "os" + "path/filepath" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" +) // postInstall performs post installation for Windows systems. func postInstall() error { From 29c66defc0390bedaaf88a60958206b46a2e1978 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 14:37:17 -0400 Subject: [PATCH 21/33] Cleanup repeat GA warning. --- x-pack/elastic-agent/pkg/agent/cmd/enroll.go | 13 ++++++++++++- x-pack/elastic-agent/pkg/agent/cmd/install.go | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go index a9fb15f03184..6a604554136a 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/enroll.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/enroll.go @@ -46,6 +46,10 @@ func newEnrollCommandWithArgs(flags *globalFlags, _ []string, streams *cli.IOStr cmd.Flags().BoolP("force", "f", false, "Force overwrite the current and do not prompt for confirmation") cmd.Flags().Bool("no-restart", false, "Skip restarting the currently running daemon") + // used by install command + cmd.Flags().BoolP("from-install", "", false, "Set by install command to signal this was executed from install") + cmd.Flags().MarkHidden("from-install") + return cmd } @@ -82,7 +86,11 @@ func buildEnrollmentFlags(cmd *cobra.Command) []string { } func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args []string) error { - warn.PrintNotGA(streams.Out) + fromInstall, _ := cmd.Flags().GetBool("from-install") + if !fromInstall { + warn.PrintNotGA(streams.Out) + } + pathConfigFile := flags.Config() rawConfig, err := application.LoadConfigFromFile(pathConfigFile) if err != nil { @@ -108,6 +116,9 @@ func enroll(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, args } force, _ := cmd.Flags().GetBool("force") + if fromInstall { + force = true + } if !force { confirm, err := c.Confirm("This will replace your current settings. Do you want to continue?", true) if err != nil { diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index bf702e6d53da..981f3b51c50d 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -132,7 +132,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } } - enrollArgs := []string{"enroll", kibana, token, "--force"} + enrollArgs := []string{"enroll", kibana, token, "--from-install"} enrollArgs = append(enrollArgs, buildEnrollmentFlags(cmd)...) enrollCmd := exec.Command(install.ExecutablePath(), enrollArgs...) enrollCmd.Stdin = os.Stdin From 2d9fe52c1c974702a43d6c9760768031931be959 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 14:45:02 -0400 Subject: [PATCH 22/33] Fix symlink on Windows. --- x-pack/elastic-agent/pkg/agent/install/install_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/install_windows.go b/x-pack/elastic-agent/pkg/agent/install/install_windows.go index af572fe4994e..ec10467ce79a 100644 --- a/x-pack/elastic-agent/pkg/agent/install/install_windows.go +++ b/x-pack/elastic-agent/pkg/agent/install/install_windows.go @@ -26,7 +26,7 @@ func postInstall() error { // create top-level symlink to nested binary realBinary := filepath.Join(InstallPath, "data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit()), BinaryName) - err = os.Symlink(binary, realBinary) + err = os.Symlink(realBinary, binary) if err != nil { return err } From 729b4b3f133d97484027c4d2104bfb09200312de Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 16:16:34 -0400 Subject: [PATCH 23/33] Fix control socket on windows. --- .../pkg/agent/control/server/listener.go | 1 - .../agent/control/server/listener_windows.go | 31 ++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/control/server/listener.go b/x-pack/elastic-agent/pkg/agent/control/server/listener.go index 7edfc7b8ee97..3090f3d140d8 100644 --- a/x-pack/elastic-agent/pkg/agent/control/server/listener.go +++ b/x-pack/elastic-agent/pkg/agent/control/server/listener.go @@ -8,7 +8,6 @@ package server import ( "fmt" - "net" "os" "path/filepath" diff --git a/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go b/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go index f98c32bcee3d..f425a4f390ed 100644 --- a/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go +++ b/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go @@ -10,6 +10,8 @@ import ( "net" "os/user" + "github.com/pkg/errors" + "github.com/elastic/beats/v7/libbeat/api/npipe" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/control" @@ -18,11 +20,7 @@ import ( // createListener creates a named pipe listener on Windows func createListener(_ *logger.Logger) (net.Listener, error) { - u, err := user.Current() - if err != nil { - return nil, err - } - sd, err := npipe.DefaultSD(u.Username) + sd, err := securityDescriptor() if err != nil { return nil, err } @@ -32,3 +30,26 @@ func createListener(_ *logger.Logger) (net.Listener, error) { func cleanupListener(_ *logger.Logger) { // nothing to do on windows } + +func securityDescriptor() (string, error) { + u, err := user.Current() + if err != nil { + return "", errors.Wrap(err, "failed to get current user") + } + // Named pipe security and access rights. + // We create the pipe and the specific users should only be able to write to it. + // See docs: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights + // String definition: https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings + // Give generic read/write access to the specified user. + descriptor := "D:P(A;;GA;;;" + u.Uid + ")" + if u.Username == "SYSTEM" { + // running as SYSTEM, include Administrators group so Administrators can talk over + // the named pipe + admin, err := user.LookupGroup("Administrators") + if err != nil { + return "", errors.Wrap(err, "failed to lookup Administrators group") + } + descriptor += "(A;;GA;;;" + admin.Gid + ")" + } + return descriptor, nil +} From f537b2cf6f15ed410a5c7803c086a7eb5850437d Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 17:01:15 -0400 Subject: [PATCH 24/33] Fix socket path and authority on Windows. --- .../pkg/agent/control/server/listener_windows.go | 4 ++-- x-pack/elastic-agent/pkg/agent/install/paths_windows.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go b/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go index f425a4f390ed..b8e61099dde9 100644 --- a/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go +++ b/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go @@ -42,9 +42,9 @@ func securityDescriptor() (string, error) { // String definition: https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings // Give generic read/write access to the specified user. descriptor := "D:P(A;;GA;;;" + u.Uid + ")" - if u.Username == "SYSTEM" { + if u.Username == `NT AUTHORITY\\SYSTEM` { // running as SYSTEM, include Administrators group so Administrators can talk over - // the named pipe + // the named pipe to the running Elastic Agent system process admin, err := user.LookupGroup("Administrators") if err != nil { return "", errors.Wrap(err, "failed to lookup Administrators group") diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_windows.go b/x-pack/elastic-agent/pkg/agent/install/paths_windows.go index 12032f043747..3af4c79d9415 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths_windows.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths_windows.go @@ -14,9 +14,7 @@ const ( InstallPath = `C:\Program Files\Elastic\Agent` // SocketPath is the socket path used when installed. - // - // `\\.\pipe\elastic-agent-%x`, sha256.Sum256([]byte(`C:\Program Files\Elastic\Agent\elastic-agent.exe`)) - SocketPath = `\\.\pipe\elastic-agent-56c56575c1f574fe48db8f56ac6db1cbcd78996a355bd2b44c71ebedd9c9a15b` + SocketPath = `\\.\pipe\elastic-agent-system` // ServiceName is the service name when installed. ServiceName = "Elastic Agent" From 5c8b0a46675775d47eefe0cbf2fe96a4bb23ac4d Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 19:07:08 -0400 Subject: [PATCH 25/33] Fix username matching for SYSTEM. --- .../elastic-agent/pkg/agent/control/server/listener_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go b/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go index b8e61099dde9..eaedc9f88f21 100644 --- a/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go +++ b/x-pack/elastic-agent/pkg/agent/control/server/listener_windows.go @@ -42,7 +42,7 @@ func securityDescriptor() (string, error) { // String definition: https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings // Give generic read/write access to the specified user. descriptor := "D:P(A;;GA;;;" + u.Uid + ")" - if u.Username == `NT AUTHORITY\\SYSTEM` { + if u.Username == "NT AUTHORITY\\SYSTEM" { // running as SYSTEM, include Administrators group so Administrators can talk over // the named pipe to the running Elastic Agent system process admin, err := user.LookupGroup("Administrators") From a6c18574085f0a406fef35ad3adc3aacbe8a308b Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Wed, 23 Sep 2020 22:23:01 -0400 Subject: [PATCH 26/33] Fix socket path on darwin. --- x-pack/elastic-agent/pkg/agent/install/paths_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go index 588689442e91..10499a3d0794 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go @@ -14,7 +14,7 @@ const ( InstallPath = "/Library/Elastic/Agent" // SocketPath is the socket path used when installed. - SocketPath = "unix:///run/elastic-agent.sock" + SocketPath = "unix:///var/run/elastic-agent.sock" // ServiceName is the service name when installed. ServiceName = "com.elastic.elastic-agent" From ff898a264f45bf065b54eec6d7b9bd2868b518a1 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 24 Sep 2020 12:14:47 -0400 Subject: [PATCH 27/33] Fix darwin service name. Fix uninstall on systemd. --- NOTICE.txt | 6 +++--- go.mod | 1 + go.sum | 4 ++-- x-pack/elastic-agent/pkg/agent/install/paths_darwin.go | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 3e324a4815d8..527c13043795 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -10908,12 +10908,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- -Dependency : github.com/kardianos/service -Version: v1.1.0 +Dependency : github.com/blakerouse/service +Version: v1.1.1-0.20200924160513-057808572ffa Licence type (autodetected): Zlib -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/kardianos/service@v1.1.0/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/blakerouse/service@v1.1.1-0.20200924160513-057808572ffa/LICENSE: Copyright (c) 2015 Daniel Theophanes diff --git a/go.mod b/go.mod index 289caae0a974..09cd086cbee1 100644 --- a/go.mod +++ b/go.mod @@ -197,6 +197,7 @@ replace ( github.com/fsnotify/fsnotify => github.com/adriansr/fsnotify v0.0.0-20180417234312-c9bbe1f46f1d github.com/google/gopacket => github.com/adriansr/gopacket v1.1.18-0.20200327165309-dd62abfa8a41 github.com/insomniacslk/dhcp => github.com/elastic/dhcp v0.0.0-20200227161230-57ec251c7eb3 // indirect + github.com/kardianos/service => github.com/blakerouse/service v1.1.1-0.20200924160513-057808572ffa github.com/tonistiigi/fifo => github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c golang.org/x/tools => golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0 // release 1.14 ) diff --git a/go.sum b/go.sum index f4a70ac2be7c..031f1faa0955 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blakerouse/service v1.1.1-0.20200924160513-057808572ffa h1:aXHPZwx8Y5z8r+1WPylnu095usTf6QSshaHs6nVMBc0= +github.com/blakerouse/service v1.1.1-0.20200924160513-057808572ffa/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -464,8 +466,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/justinas/nosurf v1.1.0/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= -github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0= -github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc= github.com/karrick/godirwalk v1.15.6 h1:Yf2mmR8TJy+8Fa0SuQVto5SYap6IF7lNVX4Jdl8G1qA= github.com/karrick/godirwalk v1.15.6/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= diff --git a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go index 10499a3d0794..f6ebcdfdbae9 100644 --- a/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go +++ b/x-pack/elastic-agent/pkg/agent/install/paths_darwin.go @@ -17,7 +17,7 @@ const ( SocketPath = "unix:///var/run/elastic-agent.sock" // ServiceName is the service name when installed. - ServiceName = "com.elastic.elastic-agent" + ServiceName = "co.elastic.elastic-agent" // ShellWrapperPath is the path to the installed shell wrapper. ShellWrapperPath = "/usr/local/bin/elastic-agent" From b574a2968454783bcca072ec28bc230dd7642724 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 24 Sep 2020 12:38:31 -0400 Subject: [PATCH 28/33] Fix install on linux. --- x-pack/elastic-agent/pkg/agent/install/install.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/x-pack/elastic-agent/pkg/agent/install/install.go b/x-pack/elastic-agent/pkg/agent/install/install.go index 3b74e4653ec4..2705ea0bfd93 100644 --- a/x-pack/elastic-agent/pkg/agent/install/install.go +++ b/x-pack/elastic-agent/pkg/agent/install/install.go @@ -9,10 +9,12 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "github.com/otiai10/copy" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" ) // Install installs Elastic Agent persistently on the system including creating and starting its service. @@ -112,6 +114,11 @@ func findDirectory() (string, error) { return "", err } sourceDir := filepath.Dir(execPath) + if insideData(sourceDir) { + // executable path is being reported as being down inside of data path + // move up to directories to perform the copy + sourceDir = filepath.Dir(filepath.Dir(sourceDir)) + } err = verifyDirectory(sourceDir) if err != nil { return "", err @@ -127,3 +134,9 @@ func verifyDirectory(dir string) error { } return nil } + +// insideData returns true when the exePath is inside of the current Agents data path. +func insideData(exePath string) bool { + expectedPath := filepath.Join("data", fmt.Sprintf("elastic-agent-%s", release.ShortCommit())) + return strings.HasSuffix(exePath, expectedPath) +} From 489da3f8efd0f28a0f45e5a0028b7e1f97c61a05 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 24 Sep 2020 12:48:32 -0400 Subject: [PATCH 29/33] Fix RunningInstalled on Linux. --- x-pack/elastic-agent/pkg/agent/install/installed.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/elastic-agent/pkg/agent/install/installed.go b/x-pack/elastic-agent/pkg/agent/install/installed.go index 685b4a177de4..9f294078242d 100644 --- a/x-pack/elastic-agent/pkg/agent/install/installed.go +++ b/x-pack/elastic-agent/pkg/agent/install/installed.go @@ -49,6 +49,15 @@ func Status() (StatusType, string) { func RunningInstalled() bool { expected := filepath.Join(InstallPath, BinaryName) execPath, _ := os.Executable() + execPath, _ = filepath.Abs(execPath) + execName := filepath.Base(execPath) + execDir := filepath.Dir(execPath) + if insideData(execDir) { + // executable path is being reported as being down inside of data path + // move up to directories to perform the comparison + execDir = filepath.Dir(filepath.Dir(execDir)) + execPath = filepath.Join(execDir, execName) + } return expected == execPath } From 3e21b30c4d3b1a08dac33fde0afcc22cc45b307d Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Tue, 22 Sep 2020 11:10:44 -0400 Subject: [PATCH 30/33] Prevent upgrade unless conditions are correct. --- .../pkg/agent/application/upgrade/upgrade.go | 41 ++++++++++++---- .../pkg/agent/install/svc_unix.go | 15 ++++++ .../pkg/agent/install/svc_windows.go | 49 +++++++++++++++++++ 3 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/agent/install/svc_unix.go create mode 100644 x-pack/elastic-agent/pkg/agent/install/svc_windows.go diff --git a/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go b/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go index 56bb41246028..68cd37b7fcdd 100644 --- a/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go +++ b/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go @@ -12,6 +12,8 @@ import ( "path/filepath" "strings" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" + "gopkg.in/yaml.v2" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" @@ -31,11 +33,12 @@ const ( // Upgrader performs an upgrade type Upgrader struct { - settings *artifact.Config - log *logger.Logger - closers []context.CancelFunc - reexec reexecManager - acker acker + settings *artifact.Config + log *logger.Logger + closers []context.CancelFunc + reexec reexecManager + acker acker + upgradable bool } type reexecManager interface { @@ -50,16 +53,28 @@ type acker interface { // NewUpgrader creates an upgrader which is capable of performing upgrade operation func NewUpgrader(settings *artifact.Config, log *logger.Logger, closers []context.CancelFunc, reexec reexecManager, a acker) *Upgrader { return &Upgrader{ - settings: settings, - log: log, - closers: closers, - reexec: reexec, - acker: a, + settings: settings, + log: log, + closers: closers, + reexec: reexec, + acker: a, + upgradable: getUpgradable(), } } +// Upgradable returns true if the Elastic Agent can be upgraded. +func (u *Upgrader) Upgradable() bool { + return u.upgradable +} + // Upgrade upgrades running agent func (u *Upgrader) Upgrade(ctx context.Context, a *fleetapi.ActionUpgrade) error { + if !u.upgradable { + return fmt.Errorf( + "cannot be upgraded; must be installed with install sub-command and " + + "running under control of the systems supervisor") + } + archivePath, err := u.downloadArtifact(ctx, a.Version, a.SourceURI) if err != nil { return err @@ -132,3 +147,9 @@ func (u *Upgrader) Ack(ctx context.Context) error { func rollbackInstall(hash string) { os.RemoveAll(filepath.Join(paths.Data(), fmt.Sprintf("%s-%s", agentName, hash))) } + +func getUpgradable() bool { + // only upgradable if running from Agent installer and running under the + // control of the system supervisor. + return install.RunningInstalled() && install.RunningUnderSupervisor() +} diff --git a/x-pack/elastic-agent/pkg/agent/install/svc_unix.go b/x-pack/elastic-agent/pkg/agent/install/svc_unix.go new file mode 100644 index 000000000000..c7acb998489f --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/svc_unix.go @@ -0,0 +1,15 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build !windows + +package install + +import "os" + +// RunningUnderSupervisor returns true when executing Agent is running under +// the supervisor processes of the OS. +func RunningUnderSupervisor() bool { + return os.Getppid() == 1 +} diff --git a/x-pack/elastic-agent/pkg/agent/install/svc_windows.go b/x-pack/elastic-agent/pkg/agent/install/svc_windows.go new file mode 100644 index 000000000000..9084f3b5ea70 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/install/svc_windows.go @@ -0,0 +1,49 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build windows + +package install + +import ( + "golang.org/x/sys/windows" +) + +// RunningUnderSupervisor returns true when executing Agent is running under +// the supervisor processes of the OS. +func RunningUnderSupervisor() bool { + serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID) + if err != nil { + return false + } + defer windows.FreeSid(serviceSid) + + t, err := windows.OpenCurrentProcessToken() + if err != nil { + return false + } + defer t.Close() + + gs, err := t.GetTokenGroups() + if err != nil { + return false + } + + for _, g := range gs.AllGroups() { + if windows.EqualSid(g.Sid, serviceSid) { + return true + } + } + return false +} + +func allocSid(subAuth0 uint32) (*windows.SID, error) { + var sid *windows.SID + err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, + 1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid) + if err != nil { + return nil, err + } + return sid, nil +} From be282cb63e83f88ae58b9fe1ce7e9de905a77b50 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Tue, 22 Sep 2020 11:17:25 -0400 Subject: [PATCH 31/33] Add ability to force upgradable with DEV=true when built. --- x-pack/elastic-agent/magefile.go | 1 + .../pkg/agent/application/upgrade/upgrade.go | 4 ++-- x-pack/elastic-agent/pkg/release/upgrade.go | 10 ++++++++++ x-pack/elastic-agent/pkg/release/version.go | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/release/upgrade.go diff --git a/x-pack/elastic-agent/magefile.go b/x-pack/elastic-agent/magefile.go index ec6e76a0995a..e232fc6eefbe 100644 --- a/x-pack/elastic-agent/magefile.go +++ b/x-pack/elastic-agent/magefile.go @@ -635,6 +635,7 @@ func buildVars() map[string]string { if isDevFlag, devFound := os.LookupEnv(devEnv); devFound { if isDev, err := strconv.ParseBool(isDevFlag); err == nil && isDev { vars["github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release.allowEmptyPgp"] = "true" + vars["github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release.allowUpgrade"] = "true" } } diff --git a/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go b/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go index 68cd37b7fcdd..89e6f09d0afa 100644 --- a/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go +++ b/x-pack/elastic-agent/pkg/agent/application/upgrade/upgrade.go @@ -150,6 +150,6 @@ func rollbackInstall(hash string) { func getUpgradable() bool { // only upgradable if running from Agent installer and running under the - // control of the system supervisor. - return install.RunningInstalled() && install.RunningUnderSupervisor() + // control of the system supervisor (or built specifically with upgrading enabled) + return release.Upgradable() || (install.RunningInstalled() && install.RunningUnderSupervisor()) } diff --git a/x-pack/elastic-agent/pkg/release/upgrade.go b/x-pack/elastic-agent/pkg/release/upgrade.go new file mode 100644 index 000000000000..ac1e8552dd25 --- /dev/null +++ b/x-pack/elastic-agent/pkg/release/upgrade.go @@ -0,0 +1,10 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package release + +// Upgradable return true when release is built specifically for upgrading. +func Upgradable() bool { + return allowUpgrade == "true" +} diff --git a/x-pack/elastic-agent/pkg/release/version.go b/x-pack/elastic-agent/pkg/release/version.go index 67a423a64c57..dae609a67334 100644 --- a/x-pack/elastic-agent/pkg/release/version.go +++ b/x-pack/elastic-agent/pkg/release/version.go @@ -23,6 +23,10 @@ var snapshot = "" // without valid pgp var allowEmptyPgp string +// allowUpgrade is used as a debug flag and allows working +// with upgrade without requiring Agent to be installed correctly +var allowUpgrade string + // Commit returns the current build hash or unknown if it was not injected in the build process. func Commit() string { return libbeatVersion.Commit() From df6e995d4dcb90d99a3f4d437dd938cb4f719b5b Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Mon, 28 Sep 2020 08:26:52 -0400 Subject: [PATCH 32/33] Fixes from code review. --- .../pkg/agent/application/upgrade/step_relink.go | 10 +++++----- x-pack/elastic-agent/pkg/agent/cmd/install.go | 4 ++-- x-pack/elastic-agent/pkg/agent/cmd/uninstall.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go b/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go index e0cf407afbf8..7cbd78d48499 100644 --- a/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go +++ b/x-pack/elastic-agent/pkg/agent/application/upgrade/step_relink.go @@ -21,22 +21,22 @@ func (u *Upgrader) changeSymlink(ctx context.Context, newHash string) error { // create symlink to elastic-agent-{hash} hashedDir := fmt.Sprintf("%s-%s", agentName, newHash) - agentBakName := agentName + ".prev" + agentPrevName := agentName + ".prev" symlinkPath := filepath.Join(paths.Top(), agentName) newPath := filepath.Join(paths.Top(), "data", hashedDir, agentName) // handle windows suffixes if runtime.GOOS == "windows" { - agentBakName = agentName + ".exe.prev" //.bak is already used + agentPrevName = agentName + ".exe.prev" symlinkPath += ".exe" newPath += ".exe" } - bakNewPath := filepath.Join(paths.Top(), agentBakName) - if err := os.Symlink(newPath, bakNewPath); err != nil { + prevNewPath := filepath.Join(paths.Top(), agentPrevName) + if err := os.Symlink(newPath, prevNewPath); err != nil { return errors.New(err, errors.TypeFilesystem, "failed to update agent symlink") } // safely rotate - return file.SafeFileRotate(symlinkPath, bakNewPath) + return file.SafeFileRotate(symlinkPath, prevNewPath) } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/install.go b/x-pack/elastic-agent/pkg/agent/cmd/install.go index 981f3b51c50d..177f79912c98 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/install.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/install.go @@ -56,7 +56,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, if status == install.Broken { if !force { fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s\n", reason) - confirm, err := c.Confirm("Continuing will re-install Elastic Agent over the current installation. Do you want to continue?", true) + confirm, err := c.Confirm(fmt.Sprintf("Continuing will re-install Elastic Agent over the current installation at %s. Do you want to continue?", install.InstallPath), true) if err != nil { return fmt.Errorf("Error: problem reading prompt response") } @@ -66,7 +66,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags, } } else { if !force { - confirm, err := c.Confirm("Elastic Agent will be installed onto your system and will run as a service. Do you want to continue?", true) + confirm, err := c.Confirm(fmt.Sprintf("Elastic Agent will be installed at %s and will run as a service. Do you want to continue?", install.InstallPath), true) if err != nil { return fmt.Errorf("Error: problem reading prompt response") } diff --git a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go index 453dab6c355b..d215a15e3371 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/uninstall.go @@ -55,7 +55,7 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags if status == install.Broken { if !force { fmt.Fprintf(streams.Out, "Elastic Agent is installed but currently broken: %s\n", reason) - confirm, err := c.Confirm("Continuing will uninstall the broken Elastic Agent. Do you want to continue?", true) + confirm, err := c.Confirm(fmt.Sprintf("Continuing will uninstall the broken Elastic Agent at %s. Do you want to continue?", install.InstallPath), true) if err != nil { return fmt.Errorf("problem reading prompt response") } @@ -65,7 +65,7 @@ func uninstallCmd(streams *cli.IOStreams, cmd *cobra.Command, flags *globalFlags } } else { if !force { - confirm, err := c.Confirm("Elastic Agent will be uninstalled from your system. Do you want to continue?", true) + confirm, err := c.Confirm(fmt.Sprintf("Elastic Agent will be uninstalled from your system at %s. Do you want to continue?", install.InstallPath), true) if err != nil { return fmt.Errorf("problem reading prompt response") } From ee370ce407f8abf8d3b3e51262f8b4ed79eb8ef3 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Tue, 29 Sep 2020 08:37:27 -0400 Subject: [PATCH 33/33] Fix issue with service description. --- x-pack/elastic-agent/pkg/agent/install/svc.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/elastic-agent/pkg/agent/install/svc.go b/x-pack/elastic-agent/pkg/agent/install/svc.go index 44494eda9a9f..18cbc6d840f5 100644 --- a/x-pack/elastic-agent/pkg/agent/install/svc.go +++ b/x-pack/elastic-agent/pkg/agent/install/svc.go @@ -15,9 +15,7 @@ const ( ServiceDisplayName = "Elastic Agent" // ServiceDescription is the description for the service. - ServiceDescription = ` -Elastic Agent is a unified agent to observe, monitor and protect your system. -` + ServiceDescription = "Elastic Agent is a unified agent to observe, monitor and protect your system." ) // ExecutablePath returns the path for the installed Agents executable.