Skip to content
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

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e56c7b8
wip - so far so good. Still missing several tests
AndersonQ Mar 7, 2024
c90d31d
add elastic-agent-libs override
AndersonQ Mar 7, 2024
ac843e1
.
AndersonQ Mar 7, 2024
b4e7023
added CA tests
AndersonQ Mar 7, 2024
10078f3
go mod tidy
AndersonQ Mar 7, 2024
201fdcd
certificate/key code and test
AndersonQ Mar 11, 2024
df1419a
unify SSL tests and add a proxy subtest
AndersonQ Mar 11, 2024
b886e79
better log when test fails
AndersonQ Mar 11, 2024
2077e3b
fixes and adjustments
AndersonQ Mar 11, 2024
e41f4d6
go mod tidy
AndersonQ Mar 11, 2024
878a230
revert magefile change
AndersonQ Mar 11, 2024
424cbdf
fix override
AndersonQ Mar 11, 2024
7411b93
fix overrides
AndersonQ Mar 11, 2024
6736f89
fix panic
AndersonQ Mar 12, 2024
713b7b2
fix CA ext keyusage
AndersonQ Mar 14, 2024
74c21c2
add test for encrypted certificate key
AndersonQ Mar 19, 2024
9db4742
fix go.sum after rebase
AndersonQ Mar 19, 2024
b2b3a81
mage notice
AndersonQ Mar 20, 2024
b66b67e
Merge branch 'main' into 2247-mtls
AndersonQ Mar 21, 2024
cf264db
Merge branch 'main' of github.com:elastic/elastic-agent into 2247-mtls
AndersonQ Mar 21, 2024
c7bdf50
Merge branch 'main' into 2247-mtls
AndersonQ Mar 21, 2024
2296c98
use a released version of elastic-agent-libs
AndersonQ Mar 21, 2024
4964255
Merge branch '2247-mtls' of github.com:AndersonQ/elastic-agent into 2…
AndersonQ Mar 21, 2024
89fd117
PR changes
AndersonQ Mar 21, 2024
6c9b6a8
Merge branch 'main' into 2247-mtls
AndersonQ Mar 21, 2024
e3fcadc
Merge branch 'main' into 2247-mtls
AndersonQ Mar 22, 2024
e4a6a81
fix panic in debug log
AndersonQ Mar 22, 2024
cbc9209
Merge branch '2247-mtls' of github.com:AndersonQ/elastic-agent into 2…
AndersonQ Mar 22, 2024
e279691
Merge branch 'main' of github.com:elastic/elastic-agent into 2247-mtls
AndersonQ Mar 26, 2024
c1c9289
add cchangelog
AndersonQ Mar 26, 2024
d878942
Merge branch 'main' into 2247-mtls
AndersonQ Apr 2, 2024
5d688b3
making linter happy
AndersonQ Apr 3, 2024
cd3222b
Merge branch '2247-mtls' of github.com:AndersonQ/elastic-agent into 2…
AndersonQ Apr 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
# - bug-fix: fixes a problem in a previous version
# - enhancement: extends functionality but does not break or fix existing behavior
# - feature: new functionality
# - known-issue: problems that we are aware of in a given version
# - security: impacts on the security of a product or a user’s deployment.
# - upgrade: important information for someone upgrading from a prior version
# - other: does not fit into any of the other categories
kind: feature

# Change summary; a 80ish characters long description of the change.
summary: accept mTLS configuration from the policy

# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
description: |
The Elastic Agent accepts mTLS configuration from the policy.
The mTLS configuration from cli flags during install and enroll take precedence over the configuration defined in the policy.
As a consequence and empty mTLS configuration from the policy will not remove any configuration applied at install or enroll time.


# Affected component; a word indicating the component this changeset affects.
component:

# PR URL; optional; the PR number that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
pr: https://github.com/elastic/elastic-agent/pull/4398

# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
#issue: https://github.com/owner/repo/1234
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
)
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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(
Expand Down Expand Up @@ -220,21 +224,83 @@ 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Therefore, it's not possible to remove a proxy or TLS config once it's set.

You mean it's not possible to remove the CLI parameters?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 ||
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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.Debugw("proxy from fleet is empty or null, the proxy will not be changed")
} else {
if h.config.Fleet.Client.Transport.Proxy.URL != nil {
h.log.Debugw("received proxy from fleet, applying it",
"old_proxy_url", h.config.Fleet.Client.Transport.Proxy.URL.String(),
"new_proxy_url", cfg.Transport.Proxy.URL.String())
} else {
h.log.Debugw("received proxy from fleet, applying it",
"old_proxy_url", "nil",
Copy link
Contributor

@michalpristas michalpristas Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified with string variable oldProxy = h.config.Fleet.Client.Transport.Proxy.URL != nil ? h.config.Fleet.Client.Transport.Proxy.URL.String() : "nil"
c# syntax but you get the idea

"new_proxy_url", cfg.Transport.Proxy.URL.String())
}
h.config.Fleet.Client.Transport.Proxy = cfg.Transport.Proxy
}

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
Copy link
Member

Choose a reason for hiding this comment

The 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 Certificate but not the Key and Key were set previously.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the Certificate configuration from Fleet always complete?
Well, I don't see how one would change a certificate and not the key and the key's passphrase.

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 TLS certificate/key from fleet, applying it")
}

// 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 TLS CAs from fleet, applying it")
}
}
}

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
}
}
Expand All @@ -255,9 +321,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
}

Expand Down
Loading
Loading