-
Notifications
You must be signed in to change notification settings - Fork 143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read and apply mTLS config from policy #4398
Changes from 17 commits
e56c7b8
c90d31d
ac843e1
b4e7023
10078f3
201fdcd
df1419a
b886e79
2077e3b
e41f4d6
878a230
424cbdf
7411b93
6736f89
713b7b2
74c21c2
9db4742
b2b3a81
b66b67e
cf264db
c7bdf50
2296c98
4964255
89fd117
6c9b6a8
e3fcadc
e4a6a81
cbc9209
e279691
c1c9289
d878942
5d688b3
cd3222b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -282,3 +282,5 @@ | |
|
||
// Exclude this version because the version has an invalid checksum. | ||
exclude github.com/docker/distribution v2.8.0+incompatible | ||
|
||
replace github.com/elastic/elastic-agent-libs => github.com/AndersonQ/elastic-agent-libs v0.0.0-20240307144511-b45f19396c67 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Waiting elastic/elastic-agent-libs#189 to be merged |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -10,12 +10,14 @@ import ( | |||||
"fmt" | ||||||
"io" | ||||||
"net/http" | ||||||
"slices" | ||||||
"sort" | ||||||
"time" | ||||||
|
||||||
"gopkg.in/yaml.v2" | ||||||
|
||||||
"github.com/elastic/elastic-agent-libs/transport/httpcommon" | ||||||
"github.com/elastic/elastic-agent-libs/transport/tlscommon" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/agent/application/actions" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/agent/application/info" | ||||||
|
@@ -25,7 +27,7 @@ import ( | |||||
"github.com/elastic/elastic-agent/internal/pkg/config" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/fleetapi" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/fleetapi/client" | ||||||
fleetclient "github.com/elastic/elastic-agent/internal/pkg/fleetapi/client" | ||||||
"github.com/elastic/elastic-agent/internal/pkg/remote" | ||||||
"github.com/elastic/elastic-agent/pkg/core/logger" | ||||||
) | ||||||
|
@@ -104,7 +106,7 @@ func (h *PolicyChangeHandler) Handle(ctx context.Context, a fleetapi.Action, ack | |||||
} | ||||||
|
||||||
h.log.Debugf("handlerPolicyChange: emit configuration for action %+v", a) | ||||||
err = h.handleFleetServerHosts(ctx, c) | ||||||
err = h.handleFleetServerConfig(ctx, c) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
|
@@ -118,11 +120,11 @@ func (h *PolicyChangeHandler) Watch() <-chan coordinator.ConfigChange { | |||||
return h.ch | ||||||
} | ||||||
|
||||||
func (h *PolicyChangeHandler) handleFleetServerHosts(ctx context.Context, c *config.Config) (err error) { | ||||||
// do not update fleet-server host from policy; no setters provided with local Fleet Server | ||||||
func (h *PolicyChangeHandler) handleFleetServerConfig(ctx context.Context, c *config.Config) (err error) { | ||||||
if len(h.setters) == 0 { | ||||||
return nil | ||||||
} | ||||||
|
||||||
data, err := c.ToMapStr() | ||||||
if err != nil { | ||||||
return errors.New(err, "could not convert the configuration from the policy", errors.TypeConfig) | ||||||
|
@@ -142,29 +144,22 @@ func (h *PolicyChangeHandler) handleFleetServerHosts(ctx context.Context, c *con | |||||
return nil | ||||||
} | ||||||
|
||||||
// only set protocol/hosts as that is all Fleet currently sends | ||||||
prevProtocol := h.config.Fleet.Client.Protocol | ||||||
prevPath := h.config.Fleet.Client.Path | ||||||
prevHost := h.config.Fleet.Client.Host | ||||||
prevHosts := h.config.Fleet.Client.Hosts | ||||||
prevProxy := h.config.Fleet.Client.Transport.Proxy | ||||||
h.config.Fleet.Client.Protocol = cfg.Fleet.Client.Protocol | ||||||
h.config.Fleet.Client.Path = cfg.Fleet.Client.Path | ||||||
h.config.Fleet.Client.Host = cfg.Fleet.Client.Host | ||||||
h.config.Fleet.Client.Hosts = cfg.Fleet.Client.Hosts | ||||||
|
||||||
// Empty proxies from fleet are ignored. That way a proxy set by --proxy-url | ||||||
// it won't be overridden by an absent or empty proxy from fleet-server. | ||||||
// However, if there is a proxy sent by fleet-server, it'll take precedence. | ||||||
// Therefore, it's not possible to remove a proxy once it's set. | ||||||
if cfg.Fleet.Client.Transport.Proxy.URL == nil || | ||||||
cfg.Fleet.Client.Transport.Proxy.URL.String() == "" { | ||||||
h.log.Debug("proxy from fleet is empty or null, the proxy will not be changed") | ||||||
|
||||||
var prevTLSNil bool | ||||||
var prevCAs []string | ||||||
var prevCertificateCfg tlscommon.CertificateConfig | ||||||
if h.config.Fleet.Client.Transport.TLS != nil { | ||||||
prevTLSNil = false | ||||||
prevCAs = h.config.Fleet.Client.Transport.TLS.CAs | ||||||
prevCertificateCfg = h.config.Fleet.Client.Transport.TLS.Certificate | ||||||
} else { | ||||||
h.config.Fleet.Client.Transport.Proxy = cfg.Fleet.Client.Transport.Proxy | ||||||
h.log.Debug("received proxy from fleet, applying it") | ||||||
prevTLSNil = true | ||||||
} | ||||||
|
||||||
// rollback on failure | ||||||
defer func() { | ||||||
if err != nil { | ||||||
|
@@ -173,10 +168,19 @@ func (h *PolicyChangeHandler) handleFleetServerHosts(ctx context.Context, c *con | |||||
h.config.Fleet.Client.Host = prevHost | ||||||
h.config.Fleet.Client.Hosts = prevHosts | ||||||
h.config.Fleet.Client.Transport.Proxy = prevProxy | ||||||
if prevTLSNil { | ||||||
h.config.Fleet.Client.Transport.TLS = nil | ||||||
} else { | ||||||
h.config.Fleet.Client.Transport.TLS.CAs = prevCAs | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just save the entire TLS configuration instead of a boolean and individual fields from it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tl;dr: pointers. The rollback wasn't working properly. I don't quite remember which specific property was the problem. |
||||||
h.config.Fleet.Client.Transport.TLS.Certificate = prevCertificateCfg | ||||||
} | ||||||
h.log.Debugf("an error happened, reverting fleet-server config") | ||||||
} | ||||||
}() | ||||||
|
||||||
client, err := client.NewAuthWithConfig( | ||||||
h.applyConfigWithPrecedence(cfg.Fleet.Client) | ||||||
|
||||||
client, err := fleetclient.NewAuthWithConfig( | ||||||
h.log, h.config.Fleet.AccessAPIKey, h.config.Fleet.Client) | ||||||
if err != nil { | ||||||
return errors.New( | ||||||
|
@@ -220,25 +224,80 @@ func (h *PolicyChangeHandler) handleFleetServerHosts(ctx context.Context, c *con | |||||
return nil | ||||||
} | ||||||
|
||||||
func clientEqual(k1 remote.Config, k2 remote.Config) bool { | ||||||
if k1.Protocol != k2.Protocol { | ||||||
// applyConfigWithPrecedence applies Proxy and TLS configs, but ignores empty or | ||||||
// absent ones. | ||||||
// That way a proxy or TLS config set during install/enroll using cli flags | ||||||
// won't be overridden by an absent or empty proxy from fleet-server. | ||||||
// However, if there is a proxy or TLS config sent by fleet-server, it'll take | ||||||
// precedence. Therefore, it's not possible to remove a proxy or TLS config once | ||||||
// it's set. | ||||||
Comment on lines
+232
to
+233
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You mean it's not possible to remove the CLI parameters? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, we do not have enough information here to know what came from the cli flag and what came from fleet-server neither, IIRC, fleet-server differentiates between empty and absent. Therefore, once a proxy or TLS is set, it's set for good. We do have a different handler for policy reassign, perhaps we could use it to be able to remove proxy and TLS configs. However I'd argue what we actually need is for to be able to differentiate "remove proxy/TLS" from no-change |
||||||
func (h *PolicyChangeHandler) applyConfigWithPrecedence(cfg remote.Config) { | ||||||
defaultcfg := configuration.DefaultFleetAgentConfig() | ||||||
|
||||||
if cfg.Protocol != defaultcfg.Client.Protocol || | ||||||
cfg.Host != defaultcfg.Client.Host || | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if you're changing from localhost:5602 to localhost:5601 |
||||||
!slices.Equal(cfg.Hosts, defaultcfg.Client.Hosts) || | ||||||
cfg.Path != defaultcfg.Client.Path { | ||||||
h.config.Fleet.Client.Protocol = cfg.Protocol | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we overwrite very parameter if only one doesn't match the default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's set as one thing on fleetUI, thus I thought it'd the more accurate way of handling it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a built in assumption that Fleet will never change this unintentionally. Do not assume Fleet will always perfectly conform to your expectations. Bugs can exist on both sides of the connection. If this happened and caused settings not apply, how would we tell and debug it? |
||||||
h.config.Fleet.Client.Path = cfg.Path | ||||||
h.config.Fleet.Client.Host = cfg.Host | ||||||
h.config.Fleet.Client.Hosts = cfg.Hosts | ||||||
} | ||||||
|
||||||
if cfg.Transport.Proxy.URL == nil || | ||||||
cfg.Transport.Proxy.URL.String() == "" { | ||||||
h.log.Debug("proxy from fleet is empty or null, the proxy will not be changed") | ||||||
} else { | ||||||
h.config.Fleet.Client.Transport.Proxy = cfg.Transport.Proxy | ||||||
h.log.Debug("received proxy from fleet, applying it") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't you log what the new proxy URL is? |
||||||
} | ||||||
|
||||||
emptyCertificate := tlscommon.CertificateConfig{} | ||||||
if cfg.Transport.TLS != nil { | ||||||
if h.config.Fleet.Client.Transport.TLS == nil { | ||||||
h.config.Fleet.Client.Transport.TLS = &tlscommon.Config{} | ||||||
} | ||||||
|
||||||
// client certificate | ||||||
if cfg.Transport.TLS.Certificate == emptyCertificate { | ||||||
h.log.Debug("TLS certificate/key from fleet are empty or null, the TLS config will not be changed") | ||||||
} else { | ||||||
h.config.Fleet.Client.Transport.TLS.Certificate = cfg.Transport.TLS.Certificate | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the Certificate configuration from Fleet always complete? Does it or could it ever allow updating only individual fields? If it does this unintentionally clears the ones that weren't set, for example if someone sets only the I suppose if this happened unintentionally and broke things it would be reverted, but it would be hard for a user to debug. We either need to make debugging that failure easier or just prevent it from happening entirely. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Besides again we're in the problem of "remove it" or "no change". Changing it all atomically seems to me the best and less error prone approach. |
||||||
h.log.Debug("received TSL certificate/key from fleet, applying it") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
// CA | ||||||
if len(cfg.Transport.TLS.CAs) == 0 { | ||||||
h.log.Debug("TLS CAs from fleet are empty or null, the TLS config will not be changed") | ||||||
} else { | ||||||
h.config.Fleet.Client.Transport.TLS.CAs = cfg.Transport.TLS.CAs | ||||||
h.log.Debug("received SSL from fleet, applying it") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
func clientEqual(current remote.Config, new remote.Config) bool { | ||||||
if new.Protocol != "" && | ||||||
current.Protocol != new.Protocol { | ||||||
return false | ||||||
} | ||||||
if k1.Path != k2.Path { | ||||||
if new.Path != "" && | ||||||
current.Path != new.Path { | ||||||
return false | ||||||
} | ||||||
|
||||||
sort.Strings(k1.Hosts) | ||||||
sort.Strings(k2.Hosts) | ||||||
if len(k1.Hosts) != len(k2.Hosts) { | ||||||
sort.Strings(current.Hosts) | ||||||
sort.Strings(new.Hosts) | ||||||
if len(current.Hosts) != len(new.Hosts) { | ||||||
return false | ||||||
} | ||||||
for i, v := range k1.Hosts { | ||||||
if v != k2.Hosts[i] { | ||||||
for i, v := range current.Hosts { | ||||||
if v != new.Hosts[i] { | ||||||
return false | ||||||
} | ||||||
} | ||||||
|
||||||
// should it ignore empty headers? | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty headers from Fleet? This should have the same precedence rules as everything else. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, empty proxy headers. Thinking better bout it, if there is a new or non-nil/non-empty proxy config, if the new has no headers, we need to override them. So I think it's correct as it's right now |
||||||
headersEqual := func(h1, h2 httpcommon.ProxyHeaders) bool { | ||||||
if len(h1) != len(h2) { | ||||||
return false | ||||||
|
@@ -255,9 +314,24 @@ func clientEqual(k1 remote.Config, k2 remote.Config) bool { | |||||
} | ||||||
|
||||||
// different proxy | ||||||
if k1.Transport.Proxy.URL != k2.Transport.Proxy.URL || | ||||||
k1.Transport.Proxy.Disable != k2.Transport.Proxy.Disable || | ||||||
!headersEqual(k1.Transport.Proxy.Headers, k2.Transport.Proxy.Headers) { | ||||||
if new.Transport.Proxy.URL != nil && | ||||||
current.Transport.Proxy.URL != new.Transport.Proxy.URL || | ||||||
current.Transport.Proxy.Disable != new.Transport.Proxy.Disable || | ||||||
!headersEqual(current.Transport.Proxy.Headers, new.Transport.Proxy.Headers) { | ||||||
return false | ||||||
} | ||||||
|
||||||
// different TLS config | ||||||
if new.Transport.TLS != nil && | ||||||
len(new.Transport.TLS.CAs) > 0 && | ||||||
!slices.Equal(current.Transport.TLS.CAs, new.Transport.TLS.CAs) { | ||||||
return false | ||||||
} | ||||||
|
||||||
emptyCert := tlscommon.CertificateConfig{} | ||||||
if new.Transport.TLS != nil && | ||||||
new.Transport.TLS.Certificate != emptyCert && | ||||||
current.Transport.TLS.Certificate != new.Transport.TLS.Certificate { | ||||||
return false | ||||||
} | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Waiting elastic/elastic-agent-libs#189 to be merged