Skip to content

Commit 6135d60

Browse files
authored
Enable unprivileged on macos (#4362)
* create vault as unprivileged when running as non-root * Fix AgentInfo creation * Allow install --unprivileged on darwin * Add --unprivileged flag to uninstall * Move the check for unprivileged in EncryptedDiskStore defaults Checking if we are running as root when creating the EncryptedDiskStore removes the need for propagating an unprivileged flag from a lot of places in the code. Only exception is the uninstall command, since it needs to run with administrator privileges: in there we check for existence of a FileVault containing the agent secret to detect if we have to load the agent configuration as unprivileged (currently relevant only on Darwin) * Deprecate '--disable-encrypted-store' switch on elastic-agent run * add unit test for uninstall unprivileged check * fixup! add unit test for uninstall unprivileged check * propagate errors when creating encrypted disk store * fixup! propagate errors when creating encrypted disk store * changelog * fixup! fixup! propagate errors when creating encrypted disk store * Fix order of overrides in EncryptedDiskStore * fixup! Fix order of overrides in EncryptedDiskStore * fixup! fixup! Fix order of overrides in EncryptedDiskStore * fixup! fixup! fixup! Fix order of overrides in EncryptedDiskStore * fixup! propagate errors when creating encrypted disk store * fix log and error strings
1 parent 437901d commit 6135d60

23 files changed

+463
-75
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: feature
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Enable --unprivileged on Mac OS, allowing elastic-agent to run as an unprivileged user
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
#description:
20+
21+
# Affected component; a word indicating the component this changeset affects.
22+
component:
23+
24+
# PR URL; optional; the PR number that added the changeset.
25+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
26+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
27+
# Please provide it if you are adding a fragment for a different PR.
28+
#pr: https://github.com/owner/repo/1234
29+
30+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
31+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
32+
#issue: https://github.com/owner/repo/1234

internal/pkg/agent/application/application.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,10 @@ func New(
211211

212212
func mergeFleetConfig(ctx context.Context, rawConfig *config.Config) (storage.Store, *configuration.Configuration, error) {
213213
path := paths.AgentConfigFile()
214-
store := storage.NewEncryptedDiskStore(ctx, path)
214+
store, err := storage.NewEncryptedDiskStore(ctx, path)
215+
if err != nil {
216+
return nil, nil, fmt.Errorf("error instantiating encrypted disk store: %w", err)
217+
}
215218

216219
reader, err := store.Load()
217220
if err != nil {

internal/pkg/agent/application/gateway/fleet/fleet_gateway_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,8 @@ func newStateStore(t *testing.T, log *logger.Logger) *store.StateStore {
502502
require.NoError(t, err)
503503

504504
filename := filepath.Join(dir, "state.enc")
505-
diskStore := storage.NewDiskStore(filename)
505+
diskStore, err := storage.NewDiskStore(filename)
506+
require.NoError(t, err)
506507
stateStore, err := store.NewStateStore(log, diskStore)
507508
require.NoError(t, err)
508509

internal/pkg/agent/application/info/agent_id.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ func updateLogLevel(ctx context.Context, level string) error {
5353
}
5454

5555
agentConfigFile := paths.AgentConfigFile()
56-
diskStore := storage.NewEncryptedDiskStore(ctx, agentConfigFile)
56+
diskStore, err := storage.NewEncryptedDiskStore(ctx, agentConfigFile)
57+
if err != nil {
58+
return fmt.Errorf("error instantiating encrypted disk store: %w", err)
59+
}
5760

5861
ai.LogLevel = level
5962
return updateAgentInfo(diskStore, ai)
@@ -202,7 +205,10 @@ func loadAgentInfo(ctx context.Context, forceUpdate bool, logLevel string, creat
202205
defer idLock.Unlock()
203206

204207
agentConfigFile := paths.AgentConfigFile()
205-
diskStore := storage.NewEncryptedDiskStore(ctx, agentConfigFile)
208+
diskStore, err := storage.NewEncryptedDiskStore(ctx, agentConfigFile)
209+
if err != nil {
210+
return nil, fmt.Errorf("error instantiating encrypted disk store: %w", err)
211+
}
206212

207213
agentInfo, err := getInfoFromStore(diskStore, logLevel)
208214
if err != nil {

internal/pkg/agent/application/secret/secret.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/elastic/elastic-agent/internal/pkg/agent/vault/aesgcm"
1717
)
1818

19-
const agentSecretKey = "secret"
19+
const AgentSecretKey = "secret"
2020

2121
// mutex for secret create calls
2222
var mxCreate sync.Mutex
@@ -29,7 +29,7 @@ type Secret struct {
2929

3030
// CreateAgentSecret creates agent secret key if it doesn't exist
3131
func CreateAgentSecret(ctx context.Context, opts ...vault.OptionFunc) error {
32-
return Create(ctx, agentSecretKey, opts...)
32+
return Create(ctx, AgentSecretKey, opts...)
3333
}
3434

3535
// Create creates secret and stores it in the vault under given key
@@ -69,13 +69,13 @@ func Create(ctx context.Context, key string, opts ...vault.OptionFunc) error {
6969

7070
// GetAgentSecret read the agent secret from the vault
7171
func GetAgentSecret(ctx context.Context, opts ...vault.OptionFunc) (secret Secret, err error) {
72-
return Get(ctx, agentSecretKey, opts...)
72+
return Get(ctx, AgentSecretKey, opts...)
7373
}
7474

7575
// SetAgentSecret saves the agent secret from the vault
7676
// This is needed for migration from 8.3.0-8.3.2 to higher versions
7777
func SetAgentSecret(ctx context.Context, secret Secret, opts ...vault.OptionFunc) error {
78-
return Set(ctx, agentSecretKey, secret, opts...)
78+
return Set(ctx, AgentSecretKey, secret, opts...)
7979
}
8080

8181
// Get reads the secret key from the vault

internal/pkg/agent/cmd/enroll_cmd.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"github.com/elastic/elastic-agent-libs/transport/httpcommon"
2222
"github.com/elastic/elastic-agent-libs/transport/tlscommon"
23+
2324
"github.com/elastic/elastic-agent/internal/pkg/agent/application"
2425
"github.com/elastic/elastic-agent/internal/pkg/agent/application/filelock"
2526
"github.com/elastic/elastic-agent/internal/pkg/agent/application/info"
@@ -29,6 +30,7 @@ import (
2930
"github.com/elastic/elastic-agent/internal/pkg/agent/errors"
3031
"github.com/elastic/elastic-agent/internal/pkg/agent/install"
3132
"github.com/elastic/elastic-agent/internal/pkg/agent/storage"
33+
"github.com/elastic/elastic-agent/internal/pkg/agent/vault"
3234
"github.com/elastic/elastic-agent/internal/pkg/cli"
3335
"github.com/elastic/elastic-agent/internal/pkg/config"
3436
"github.com/elastic/elastic-agent/internal/pkg/core/authority"
@@ -173,10 +175,14 @@ func newEnrollCmd(
173175
configPath string,
174176
) (*enrollCmd, error) {
175177

178+
encryptedDiskStore, err := storage.NewEncryptedDiskStore(ctx, paths.AgentConfigFile())
179+
if err != nil {
180+
return nil, fmt.Errorf("error instantiating encrypted disk store: %w", err)
181+
}
176182
store := storage.NewReplaceOnSuccessStore(
177183
configPath,
178184
application.DefaultAgentFleetConfig,
179-
storage.NewEncryptedDiskStore(ctx, paths.AgentConfigFile()),
185+
encryptedDiskStore,
180186
)
181187

182188
return newEnrollCmdWithStore(
@@ -214,9 +220,14 @@ func (c *enrollCmd) Execute(ctx context.Context, streams *cli.IOStreams) error {
214220
span.End()
215221
}()
216222

223+
hasRoot, err := utils.HasRoot()
224+
if err != nil {
225+
return fmt.Errorf("checking if running with root/Administrator privileges: %w", err)
226+
}
227+
217228
// Create encryption key from the agent before touching configuration
218229
if !c.options.SkipCreateSecret {
219-
err = secret.CreateAgentSecret(ctx)
230+
err = secret.CreateAgentSecret(ctx, vault.WithUnprivileged(!hasRoot))
220231
if err != nil {
221232
return err
222233
}

internal/pkg/agent/cmd/inspect.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/elastic/elastic-agent/internal/pkg/config/operations"
3131
"github.com/elastic/elastic-agent/pkg/component"
3232
"github.com/elastic/elastic-agent/pkg/core/logger"
33+
"github.com/elastic/elastic-agent/pkg/utils"
3334
)
3435

3536
func newInspectCommandWithArgs(s []string, streams *cli.IOStreams) *cobra.Command {
@@ -107,6 +108,7 @@ variables for the configuration.
107108

108109
ctx, cancel := context.WithCancel(context.Background())
109110
service.HandleSignals(func() {}, cancel)
111+
110112
if err := inspectComponents(ctx, paths.ConfigFile(), opts, streams); err != nil {
111113
fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
112114
os.Exit(1)
@@ -133,8 +135,12 @@ func inspectConfig(ctx context.Context, cfgPath string, opts inspectConfigOpts,
133135
return fmt.Errorf("error creating logger: %w", err)
134136
}
135137

138+
isAdmin, err := utils.HasRoot()
139+
if err != nil {
140+
return fmt.Errorf("error checking for root/Administrator privileges: %w", err)
141+
}
136142
if !opts.variables && !opts.includeMonitoring {
137-
fullCfg, err := operations.LoadFullAgentConfig(ctx, l, cfgPath, true)
143+
fullCfg, err := operations.LoadFullAgentConfig(ctx, l, cfgPath, true, !isAdmin)
138144
if err != nil {
139145
return fmt.Errorf("error loading agent config: %w", err)
140146
}
@@ -144,7 +150,7 @@ func inspectConfig(ctx context.Context, cfgPath string, opts inspectConfigOpts,
144150
}
145151
}
146152

147-
cfg, lvl, err := getConfigWithVariables(ctx, l, cfgPath, opts.variablesWait)
153+
cfg, lvl, err := getConfigWithVariables(ctx, l, cfgPath, opts.variablesWait, !isAdmin)
148154
if err != nil {
149155
return fmt.Errorf("error fetching config with variables: %w", err)
150156
}
@@ -257,7 +263,12 @@ func inspectComponents(ctx context.Context, cfgPath string, opts inspectComponen
257263
return fmt.Errorf("failed to detect inputs and outputs: %w", err)
258264
}
259265

260-
m, lvl, err := getConfigWithVariables(ctx, l, cfgPath, opts.variablesWait)
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)
261272
if err != nil {
262273
return err
263274
}
@@ -358,9 +369,9 @@ func getMonitoringFn(ctx context.Context, cfg map[string]interface{}) (component
358369
return monitor.MonitoringConfig, nil
359370
}
360371

361-
func getConfigWithVariables(ctx context.Context, l *logger.Logger, cfgPath string, timeout time.Duration) (map[string]interface{}, logp.Level, error) {
372+
func getConfigWithVariables(ctx context.Context, l *logger.Logger, cfgPath string, timeout time.Duration, unprivileged bool) (map[string]interface{}, logp.Level, error) {
362373

363-
cfg, err := operations.LoadFullAgentConfig(ctx, l, cfgPath, true)
374+
cfg, err := operations.LoadFullAgentConfig(ctx, l, cfgPath, true, unprivileged)
364375
if err != nil {
365376
return nil, logp.InfoLevel, err
366377
}

internal/pkg/agent/cmd/install.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,16 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command) error {
7070

7171
isAdmin, err := utils.HasRoot()
7272
if err != nil {
73-
return fmt.Errorf("unable to perform install command while checking for administrator rights, %w", err)
73+
return fmt.Errorf("unable to perform install command while checking for root/Administrator rights: %w", err)
7474
}
7575
if !isAdmin {
7676
return fmt.Errorf("unable to perform install command, not executed with %s permissions", utils.PermissionUser)
7777
}
7878

79-
// only support Linux at the moment
79+
// only support Linux and MacOS at the moment
8080
unprivileged, _ := cmd.Flags().GetBool(flagInstallUnprivileged)
81-
if unprivileged && runtime.GOOS != "linux" {
82-
return fmt.Errorf("unable to perform install command, unprivileged is currently only supported on Linux")
81+
if unprivileged && (runtime.GOOS != "linux" && runtime.GOOS != "darwin") {
82+
return fmt.Errorf("unable to perform install command, unprivileged is currently only supported on Linux and MacOSß")
8383
}
8484
if unprivileged {
8585
fmt.Fprintln(streams.Out, "Unprivileged installation mode enabled; this is an experimental and currently unsupported feature.")

internal/pkg/agent/cmd/run.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
monitoringLib "github.com/elastic/elastic-agent-libs/monitoring"
2727
"github.com/elastic/elastic-agent-libs/service"
2828
"github.com/elastic/elastic-agent-system-metrics/report"
29+
"github.com/elastic/elastic-agent/internal/pkg/agent/vault"
2930

3031
"github.com/elastic/elastic-agent/internal/pkg/agent/application"
3132
"github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator"
@@ -88,7 +89,11 @@ func newRunCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
8889
// feature of the Elastic Agent. On Mac OS root privileges are required to perform the disk
8990
// store encryption, by setting this flag it disables that feature and allows the Elastic Agent to
9091
// run as non-root.
92+
//
93+
// Deprecated: MacOS can be run/installed without root privileges
9194
cmd.Flags().Bool("disable-encrypted-store", false, "Disable the encrypted disk storage (Only useful on Mac OS)")
95+
_ = cmd.Flags().MarkHidden("disable-encrypted-store")
96+
_ = cmd.Flags().MarkDeprecated("disable-encrypted-store", "agent on Mac OS can be run/installed without root privileges, see elastic-agent install --help")
9297

9398
// --testing-mode is a hidden flag that spawns the Elastic Agent in testing mode
9499
// it is hidden because we really don't want users to execute Elastic Agent to run
@@ -162,6 +167,12 @@ func runElasticAgent(ctx context.Context, cancel context.CancelFunc, override cf
162167

163168
l.Infow("Elastic Agent started", "process.pid", os.Getpid(), "agent.version", version.GetAgentPackageVersion())
164169

170+
// try early to check if running as root
171+
isRoot, err := utils.HasRoot()
172+
if err != nil {
173+
return fmt.Errorf("failed to check for root/Administrator privileges: %w", err)
174+
}
175+
165176
cfg, err = tryDelayEnroll(ctx, l, cfg, override)
166177
if err != nil {
167178
err = errors.New(err, "failed to perform delayed enrollment")
@@ -170,12 +181,6 @@ func runElasticAgent(ctx context.Context, cancel context.CancelFunc, override cf
170181
}
171182
pathConfigFile := paths.AgentConfigFile()
172183

173-
// try early to check if running as root
174-
isRoot, err := utils.HasRoot()
175-
if err != nil {
176-
return fmt.Errorf("failed to check for root permissions: %w", err)
177-
}
178-
179184
// agent ID needs to stay empty in bootstrap mode
180185
createAgentID := !runAsOtel
181186
if cfg.Fleet != nil && cfg.Fleet.Server != nil && cfg.Fleet.Server.Bootstrap {
@@ -186,7 +191,7 @@ func runElasticAgent(ctx context.Context, cancel context.CancelFunc, override cf
186191
// The secret is not created here if it exists already from the previous enrollment.
187192
// This is needed for compatibility with agent running in standalone mode,
188193
// that writes the agentID into fleet.enc (encrypted fleet.yml) before even loading the configuration.
189-
err = secret.CreateAgentSecret(ctx)
194+
err = secret.CreateAgentSecret(ctx, vault.WithUnprivileged(!isRoot))
190195
if err != nil {
191196
return fmt.Errorf("failed to read/write secrets: %w", err)
192197
}
@@ -434,7 +439,10 @@ func getOverwrites(ctx context.Context, rawConfig *config.Config) error {
434439
return nil
435440
}
436441
path := paths.AgentConfigFile()
437-
store := storage.NewEncryptedDiskStore(ctx, path)
442+
store, err := storage.NewEncryptedDiskStore(ctx, path)
443+
if err != nil {
444+
return fmt.Errorf("error instantiating encrypted disk store: %w", err)
445+
}
438446

439447
reader, err := store.Load()
440448
if err != nil && errors.Is(err, os.ErrNotExist) {

internal/pkg/agent/cmd/uninstall.go

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/spf13/cobra"
1212

1313
"github.com/elastic/elastic-agent-libs/logp"
14+
1415
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
1516
"github.com/elastic/elastic-agent/internal/pkg/agent/install"
1617
"github.com/elastic/elastic-agent/internal/pkg/cli"

0 commit comments

Comments
 (0)