Skip to content

Commit ee26696

Browse files
committed
Work on privileged/unprivileged command.
1 parent adef7f7 commit ee26696

15 files changed

+629
-211
lines changed

internal/pkg/agent/cmd/common.go

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command {
7777
cmd.AddCommand(newUpgradeCommandWithArgs(args, streams))
7878
cmd.AddCommand(newEnrollCommandWithArgs(args, streams))
7979
cmd.AddCommand(newInspectCommandWithArgs(args, streams))
80+
cmd.AddCommand(newPrivilegedCommandWithArgs(args, streams))
81+
cmd.AddCommand(newUnprivilegedCommandWithArgs(args, streams))
8082
cmd.AddCommand(newWatchCommandWithArgs(args, streams))
8183
cmd.AddCommand(newContainerCommand(args, streams))
8284
cmd.AddCommand(newStatusCommand(args, streams))

internal/pkg/agent/cmd/enroll_cmd.go

+2-49
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/elastic/elastic-agent/internal/pkg/release"
3939
"github.com/elastic/elastic-agent/internal/pkg/remote"
4040
"github.com/elastic/elastic-agent/pkg/control/v2/client"
41+
"github.com/elastic/elastic-agent/pkg/control/v2/client/wait"
4142
"github.com/elastic/elastic-agent/pkg/core/logger"
4243
"github.com/elastic/elastic-agent/pkg/core/process"
4344
"github.com/elastic/elastic-agent/pkg/utils"
@@ -324,7 +325,7 @@ func (c *enrollCmd) fleetServerBootstrap(ctx context.Context, persistentConfig m
324325
if err != nil {
325326
if !c.options.FleetServer.SpawnAgent {
326327
// wait longer to try and communicate with the Elastic Agent
327-
err = waitForAgent(ctx, c.options.DaemonTimeout)
328+
err = wait.ForAgent(ctx, c.options.DaemonTimeout)
328329
if err != nil {
329330
return "", errors.New("failed to communicate with elastic-agent daemon; is elastic-agent running?")
330331
}
@@ -711,54 +712,6 @@ type waitResult struct {
711712
err error
712713
}
713714

714-
func waitForAgent(ctx context.Context, timeout time.Duration) error {
715-
if timeout == 0 {
716-
timeout = 1 * time.Minute
717-
}
718-
if timeout > 0 {
719-
var cancel context.CancelFunc
720-
ctx, cancel = context.WithTimeout(ctx, timeout)
721-
defer cancel()
722-
}
723-
maxBackoff := timeout
724-
if maxBackoff <= 0 {
725-
// indefinite timeout
726-
maxBackoff = 10 * time.Minute
727-
}
728-
729-
resChan := make(chan waitResult)
730-
innerCtx, innerCancel := context.WithCancel(context.Background())
731-
defer innerCancel()
732-
go func() {
733-
backOff := expBackoffWithContext(innerCtx, 1*time.Second, maxBackoff)
734-
for {
735-
backOff.Wait()
736-
_, err := getDaemonState(innerCtx)
737-
if errors.Is(err, context.Canceled) {
738-
resChan <- waitResult{err: err}
739-
return
740-
}
741-
if err == nil {
742-
resChan <- waitResult{}
743-
break
744-
}
745-
}
746-
}()
747-
748-
var res waitResult
749-
select {
750-
case <-ctx.Done():
751-
innerCancel()
752-
res = <-resChan
753-
case res = <-resChan:
754-
}
755-
756-
if res.err != nil {
757-
return res.err
758-
}
759-
return nil
760-
}
761-
762715
func waitForFleetServer(ctx context.Context, agentSubproc <-chan *os.ProcessState, log *logger.Logger, timeout time.Duration) (string, error) {
763716
if timeout == 0 {
764717
timeout = 2 * time.Minute

internal/pkg/agent/cmd/inspect.go

+43-33
Original file line numberDiff line numberDiff line change
@@ -252,43 +252,12 @@ func inspectComponents(ctx context.Context, cfgPath string, opts inspectComponen
252252
return err
253253
}
254254

255-
// Load the requirements before trying to load the configuration. These should always load
256-
// even if the configuration is wrong.
257-
platform, err := component.LoadPlatformDetail()
258-
if err != nil {
259-
return fmt.Errorf("failed to gather system information: %w", err)
260-
}
261-
specs, err := component.LoadRuntimeSpecs(paths.Components(), platform)
262-
if err != nil {
263-
return fmt.Errorf("failed to detect inputs and outputs: %w", err)
264-
}
265-
266-
isAdmin, err := utils.HasRoot()
267-
if err != nil {
268-
return fmt.Errorf("error checking for root/Administrator privileges: %w", err)
269-
}
270-
271-
m, lvl, err := getConfigWithVariables(ctx, l, cfgPath, opts.variablesWait, !isAdmin)
255+
comps, err := getComponentsFromPolicy(ctx, l, cfgPath, opts.variablesWait)
272256
if err != nil {
257+
// error already includes the context
273258
return err
274259
}
275260

276-
monitorFn, err := getMonitoringFn(ctx, m)
277-
if err != nil {
278-
return fmt.Errorf("failed to get monitoring: %w", err)
279-
}
280-
281-
agentInfo, err := info.NewAgentInfoWithLog(ctx, "error", false)
282-
if err != nil {
283-
return fmt.Errorf("could not load agent info: %w", err)
284-
}
285-
286-
// Compute the components from the computed configuration.
287-
comps, err := specs.ToComponents(m, monitorFn, lvl, agentInfo)
288-
if err != nil {
289-
return fmt.Errorf("failed to render components: %w", err)
290-
}
291-
292261
// Hide configuration unless toggled on.
293262
if !opts.showConfig {
294263
for i, comp := range comps {
@@ -349,6 +318,47 @@ func inspectComponents(ctx context.Context, cfgPath string, opts inspectComponen
349318
return printComponents(allowed, blocked, streams)
350319
}
351320

321+
func getComponentsFromPolicy(ctx context.Context, l *logger.Logger, cfgPath string, variablesWait time.Duration) ([]component.Component, error) {
322+
// Load the requirements before trying to load the configuration. These should always load
323+
// even if the configuration is wrong.
324+
platform, err := component.LoadPlatformDetail()
325+
if err != nil {
326+
return nil, fmt.Errorf("failed to gather system information: %w", err)
327+
}
328+
specs, err := component.LoadRuntimeSpecs(paths.Components(), platform)
329+
if err != nil {
330+
return nil, fmt.Errorf("failed to detect inputs and outputs: %w", err)
331+
}
332+
333+
isAdmin, err := utils.HasRoot()
334+
if err != nil {
335+
return nil, fmt.Errorf("error checking for root/Administrator privileges: %w", err)
336+
}
337+
338+
m, lvl, err := getConfigWithVariables(ctx, l, cfgPath, variablesWait, !isAdmin)
339+
if err != nil {
340+
return nil, err
341+
}
342+
343+
monitorFn, err := getMonitoringFn(ctx, m)
344+
if err != nil {
345+
return nil, fmt.Errorf("failed to get monitoring: %w", err)
346+
}
347+
348+
agentInfo, err := info.NewAgentInfoWithLog(ctx, "error", false)
349+
if err != nil {
350+
return nil, fmt.Errorf("could not load agent info: %w", err)
351+
}
352+
353+
// Compute the components from the computed configuration.
354+
comps, err := specs.ToComponents(m, monitorFn, lvl, agentInfo)
355+
if err != nil {
356+
return nil, fmt.Errorf("failed to render components: %w", err)
357+
}
358+
359+
return comps, nil
360+
}
361+
352362
func getMonitoringFn(ctx context.Context, cfg map[string]interface{}) (component.GenerateMonitoringCfgFn, error) {
353363
config, err := config.NewConfigFrom(cfg)
354364
if err != nil {

internal/pkg/agent/cmd/install.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13+
"time"
1314

1415
"github.com/spf13/cobra"
1516

@@ -230,7 +231,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command) error {
230231
defer func() {
231232
if err != nil {
232233
progBar.Describe("Stopping Service")
233-
innerErr := install.StopService(topPath)
234+
innerErr := install.StopService(topPath, 30*time.Second, 250*time.Millisecond)
234235
if innerErr != nil {
235236
progBar.Describe("Failed to Stop Service")
236237
} else {

internal/pkg/agent/cmd/privileged.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package cmd
6+
7+
import (
8+
"context"
9+
"errors"
10+
"fmt"
11+
"os"
12+
"runtime"
13+
14+
"github.com/spf13/cobra"
15+
16+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
17+
"github.com/elastic/elastic-agent/internal/pkg/agent/install"
18+
"github.com/elastic/elastic-agent/internal/pkg/cli"
19+
"github.com/elastic/elastic-agent/pkg/control/v2/client/wait"
20+
"github.com/elastic/elastic-agent/pkg/utils"
21+
)
22+
23+
func newPrivilegedCommandWithArgs(s []string, streams *cli.IOStreams) *cobra.Command {
24+
cmd := &cobra.Command{
25+
Use: "privileged",
26+
Short: "Switch installed Elastic Agent to run as privileged",
27+
Long: `This command converts the installed Elastic Agent from running unprivileged to running as privileged.
28+
29+
By default this command will ask or a confirmation before making this change. You can bypass the confirmation request
30+
using the -f flag. This will always stop the running Elastic Agent (if running) before performing the switch it is
31+
possible that loss of metrics could occur during this small window of time. The command also always starts the
32+
Elastic Agent at the end (even if it was off to start). In the case that the Elastic Agent is already running
33+
privileged it will still perform all the same work, including stopping and starting the Elastic Agent.
34+
`,
35+
Args: cobra.ExactArgs(0),
36+
Run: func(c *cobra.Command, args []string) {
37+
if err := privilegedCmd(streams, c); err != nil {
38+
fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
39+
os.Exit(1)
40+
}
41+
},
42+
}
43+
44+
cmd.Flags().BoolP("force", "f", false, "Do not prompt for confirmation")
45+
cmd.Flags().DurationP("daemon-timeout", "", 0, "Timeout waiting for Elastic Agent daemon")
46+
47+
return cmd
48+
}
49+
50+
func privilegedCmd(streams *cli.IOStreams, cmd *cobra.Command) (err error) {
51+
isAdmin, err := utils.HasRoot()
52+
if err != nil {
53+
return fmt.Errorf("unable to perform privileged command while checking for root/Administrator rights: %w", err)
54+
}
55+
if !isAdmin {
56+
return fmt.Errorf("unable to perform privileged command, not executed with %s permissions", utils.PermissionUser)
57+
}
58+
59+
// TODO(blakerouse): More work to get this working on macOS.
60+
// Need to switch the vault from keystore based to file based vault.
61+
if runtime.GOOS == "darwin" {
62+
return errors.New("unable to perform unprivileged on macOS (not supported)")
63+
}
64+
65+
topPath := paths.Top()
66+
daemonTimeout, _ := cmd.Flags().GetDuration("daemon-timeout")
67+
force, _ := cmd.Flags().GetBool("force")
68+
if !force {
69+
confirm, err := cli.Confirm("This will restart the running Elastic Agent and convert it to run in privileged mode. Do you want to continue?", true)
70+
if err != nil {
71+
return fmt.Errorf("problem reading prompt response")
72+
}
73+
if !confirm {
74+
return fmt.Errorf("unprivileged switch was cancelled by the user")
75+
}
76+
}
77+
78+
pt := install.CreateAndStartNewSpinner(streams.Out, "Converting Elastic Agent to privileged...")
79+
err = install.SwitchExecutingMode(topPath, pt, "", "")
80+
if err != nil {
81+
// error already adds context
82+
return err
83+
}
84+
85+
// wait for the service
86+
if daemonTimeout >= 0 {
87+
pt.Describe("Waiting for running service")
88+
ctx := handleSignal(context.Background()) // allowed to be cancelled
89+
err = wait.ForAgent(ctx, daemonTimeout)
90+
if err != nil && !errors.Is(err, context.Canceled) {
91+
pt.Describe("Failed waiting for running service")
92+
return err
93+
}
94+
pt.Describe("Service is up and running")
95+
}
96+
97+
return nil
98+
}

0 commit comments

Comments
 (0)