Skip to content

Commit ac2e9f8

Browse files
Handle PRIVILEGE_LEVEL_CHANGE action (#10231)
1 parent 89b0f76 commit ac2e9f8

28 files changed

+802
-261
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# REQUIRED
2+
# Kind can be one of:
3+
# - breaking-change: a change to previously-documented behavior
4+
# - deprecation: functionality that is being removed in a later release
5+
# - bug-fix: fixes a problem in a previous version
6+
# - enhancement: extends functionality but does not break or fix existing behavior
7+
# - feature: new functionality
8+
# - known-issue: problems that we are aware of in a given version
9+
# - security: impacts on the security of a product or a user’s deployment.
10+
# - upgrade: important information for someone upgrading from a prior version
11+
# - other: does not fit into any of the other categories
12+
kind: feature
13+
14+
# REQUIRED for all kinds
15+
# Change summary; a 80ish characters long description of the change.
16+
summary: Add support for downgrading a running Agent's privileges from Fleet
17+
18+
# REQUIRED for breaking-change, deprecation, known-issue
19+
# Long description; in case the summary is not enough to describe the change
20+
# this field accommodate a description without length limits.
21+
# description:
22+
23+
# REQUIRED for breaking-change, deprecation, known-issue
24+
# impact:
25+
26+
# REQUIRED for breaking-change, deprecation, known-issue
27+
# action:
28+
29+
# REQUIRED for all kinds
30+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
31+
component: elastic-agent
32+
33+
# AUTOMATED
34+
# OPTIONAL to manually add other PR URLs
35+
# PR URL: A link the PR that added the changeset.
36+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
37+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
38+
# Please provide it if you are adding a fragment for a different PR.
39+
# pr: https://github.com/owner/repo/1234
40+
41+
# AUTOMATED
42+
# OPTIONAL to manually add other issue URLs
43+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
44+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
45+
# issue: https://github.com/owner/repo/1234
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 2.0;
3+
// you may not use this file except in compliance with the Elastic License 2.0.
4+
5+
package handlers
6+
7+
import (
8+
"context"
9+
"fmt"
10+
11+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator"
12+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
13+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/reexec"
14+
"github.com/elastic/elastic-agent/internal/pkg/agent/install"
15+
"github.com/elastic/elastic-agent/internal/pkg/agent/install/componentvalidation"
16+
"github.com/elastic/elastic-agent/internal/pkg/fleetapi"
17+
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker"
18+
"github.com/elastic/elastic-agent/pkg/core/logger"
19+
)
20+
21+
type reexecCoordinator interface {
22+
ReExec(callback reexec.ShutdownCallbackFn, argOverrides ...string)
23+
}
24+
25+
type PrivilegeLevelChange struct {
26+
log *logger.Logger
27+
coord reexecCoordinator
28+
ch chan coordinator.ConfigChange
29+
}
30+
31+
func NewPrivilegeLevelChange(
32+
log *logger.Logger,
33+
coord reexecCoordinator,
34+
ch chan coordinator.ConfigChange,
35+
) *PrivilegeLevelChange {
36+
return &PrivilegeLevelChange{
37+
log: log,
38+
coord: coord,
39+
ch: ch,
40+
}
41+
}
42+
43+
// Handle handles PRIVILEGE_LEVEL_CHANGE action.
44+
func (h *PrivilegeLevelChange) Handle(ctx context.Context, a fleetapi.Action, acker acker.Acker) error {
45+
return h.handle(ctx, a, acker)
46+
}
47+
48+
func (h *PrivilegeLevelChange) handle(ctx context.Context, a fleetapi.Action, acker acker.Acker) (rerr error) {
49+
action, ok := a.(*fleetapi.ActionPrivilegeLevelChange)
50+
if !ok {
51+
return fmt.Errorf("invalid type, expected ActionPrivilegeLevelChange and received %T", a)
52+
}
53+
54+
defer func() {
55+
if rerr != nil {
56+
h.ackFailure(ctx, rerr, action, acker)
57+
}
58+
}()
59+
60+
if !action.Data.Unprivileged {
61+
// only unprivileged supported at this point
62+
return fmt.Errorf("unsupported action, ActionPrivilegeLevelChange supports only downgrading permissions")
63+
}
64+
65+
// ensure no component issues
66+
err := componentvalidation.EnsureNoServiceComponentIssues()
67+
if err != nil {
68+
h.log.Debugf("handlerPrivilegeLevelChange: found issues with components: %v", err)
69+
return err
70+
}
71+
72+
var username, groupname, password string
73+
if action.Data.UserInfo != nil {
74+
username = action.Data.UserInfo.Username
75+
groupname = action.Data.UserInfo.Groupname
76+
password = action.Data.UserInfo.Password
77+
}
78+
username, password = install.UnprivilegedUser(username, password)
79+
groupname = install.UnprivilegedGroup(groupname)
80+
81+
// apply empty config to stop processing
82+
stopComponents(ctx, h.ch, a, acker, nil)
83+
84+
// fix permissions
85+
topPath := paths.Top()
86+
_, err = install.SwitchServiceUser(topPath, &debugDescriber{h.log}, username, groupname, password)
87+
if err != nil {
88+
// error already adds context
89+
return err
90+
}
91+
92+
// ack
93+
if err := acker.Ack(ctx, a); err != nil {
94+
h.log.Errorw("failed to ACK an action",
95+
"error.message", err,
96+
"action", a)
97+
}
98+
if err := acker.Commit(ctx); err != nil {
99+
h.log.Errorw("failed to commit ACK of an action",
100+
"error.message", err,
101+
"action", a)
102+
}
103+
104+
// check everything is properly set up
105+
userName, groupName, err := install.GetDesiredUser()
106+
if err != nil {
107+
return fmt.Errorf("failed to determine target user: %w", err)
108+
}
109+
110+
if userName != "" || groupName != "" {
111+
_, err = install.EnsureUserAndGroup(userName, groupName, &debugDescriber{h.log}, true)
112+
if err != nil {
113+
return fmt.Errorf("failed to setup user: %w", err)
114+
}
115+
}
116+
117+
// restart
118+
h.coord.ReExec(nil)
119+
return nil
120+
}
121+
122+
func (h *PrivilegeLevelChange) ackFailure(ctx context.Context, err error, action *fleetapi.ActionPrivilegeLevelChange, acker acker.Acker) {
123+
action.Err = err
124+
125+
if err := acker.Ack(ctx, action); err != nil {
126+
h.log.Errorw("failed to ack privilege level change action",
127+
"error.message", err,
128+
"action", action)
129+
}
130+
131+
if err := acker.Commit(ctx); err != nil {
132+
h.log.Errorw("failed to commit privilege level change action",
133+
"error.message", err,
134+
"action", action)
135+
}
136+
}
137+
138+
type debugDescriber struct {
139+
l *logger.Logger
140+
}
141+
142+
func (d *debugDescriber) Describe(a string) {
143+
d.l.Debug(a)
144+
}

internal/pkg/agent/application/actions/handlers/handler_action_unenroll.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"time"
1111

1212
"github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator"
13-
"github.com/elastic/elastic-agent/internal/pkg/config"
1413
"github.com/elastic/elastic-agent/internal/pkg/fleetapi"
1514
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker"
1615
"github.com/elastic/elastic-agent/pkg/core/logger"
@@ -92,20 +91,16 @@ func (h *Unenroll) handle(ctx context.Context, a fleetapi.Action, acker acker.Ac
9291
a = nil
9392
}
9493

95-
// Generate empty policy change, this removing all the running components
96-
unenrollPolicy := newPolicyChange(ctx, config.New(), a, acker, true, false)
97-
h.ch <- unenrollPolicy
98-
9994
// backup action for future start to avoid starting fleet gateway loop
100-
h.stateStore.SetAction(a)
101-
if err := h.stateStore.Save(); err != nil {
102-
h.log.Warnf("Failed to update state store: %v", err)
95+
backupCallback := func() {
96+
h.stateStore.SetAction(a)
97+
if err := h.stateStore.Save(); err != nil {
98+
h.log.Warnf("Failed to update state store: %v", err)
99+
}
103100
}
104101

105-
unenrollCtx, cancel := context.WithTimeout(ctx, timeout)
106-
defer cancel()
107-
108-
unenrollPolicy.WaitAck(unenrollCtx)
102+
// Generate empty policy change, this removing all the running components
103+
stopComponents(ctx, h.ch, a, acker, backupCallback)
109104

110105
// close fleet gateway loop
111106
for _, c := range h.closers {

internal/pkg/agent/application/actions/handlers/handler_helpers.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import (
1515

1616
"github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator"
1717
"github.com/elastic/elastic-agent/internal/pkg/agent/errors"
18+
"github.com/elastic/elastic-agent/internal/pkg/config"
1819
"github.com/elastic/elastic-agent/internal/pkg/core/backoff"
1920
"github.com/elastic/elastic-agent/internal/pkg/fleetapi"
21+
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker"
2022
"github.com/elastic/elastic-agent/pkg/component"
2123
"github.com/elastic/elastic-agent/pkg/component/runtime"
2224
)
@@ -43,6 +45,18 @@ type unitWithComponent struct {
4345
component component.Component
4446
}
4547

48+
func stopComponents(ctx context.Context, ch chan coordinator.ConfigChange, a fleetapi.Action, acker acker.Acker, backupCallback func()) {
49+
unenrollPolicy := newPolicyChange(ctx, config.New(), a, acker, true, false)
50+
ch <- unenrollPolicy
51+
52+
backupCallback()
53+
54+
unenrollCtx, cancel := context.WithTimeout(ctx, unenrollTimeout)
55+
defer cancel()
56+
57+
unenrollPolicy.WaitAck(unenrollCtx)
58+
}
59+
4660
func findMatchingUnitsByActionType(state coordinator.State, typ string) []unitWithComponent {
4761
ucs := make([]unitWithComponent, 0)
4862
for _, comp := range state.Components {

internal/pkg/agent/application/application.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker/lazy"
4040
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker/retrier"
4141
fleetclient "github.com/elastic/elastic-agent/internal/pkg/fleetapi/client"
42+
otelconfig "github.com/elastic/elastic-agent/internal/pkg/otel/config"
4243
otelmanager "github.com/elastic/elastic-agent/internal/pkg/otel/manager"
4344
"github.com/elastic/elastic-agent/internal/pkg/queue"
4445
"github.com/elastic/elastic-agent/internal/pkg/release"
@@ -269,7 +270,7 @@ func New(
269270
otelManager, err := otelmanager.NewOTelManager(
270271
log.Named("otel_manager"),
271272
logLevel, baseLogger,
272-
otelmanager.SubprocessExecutionMode,
273+
otelconfig.SubprocessExecutionMode,
273274
agentInfo,
274275
cfg.Settings.Collector,
275276
monitor.ComponentMonitoringConfig,

internal/pkg/agent/application/managed_mode.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,11 @@ func (m *managedConfigManager) initDispatcher(canceller context.CancelFunc) *han
402402
handlers.NewMigrate(m.log, m.agentInfo, m.coord),
403403
)
404404

405+
m.dispatcher.MustRegister(
406+
&fleetapi.ActionPrivilegeLevelChange{},
407+
handlers.NewPrivilegeLevelChange(m.log, m.coord, m.ch),
408+
)
409+
405410
m.dispatcher.MustRegister(
406411
&fleetapi.ActionUnknown{},
407412
handlers.NewUnknown(m.log),

0 commit comments

Comments
 (0)