Skip to content

Commit

Permalink
x-pack/filebeat/input/entityanalytics/{okta,azuread/fetcher/graph}: a…
Browse files Browse the repository at this point in the history
…dd ability to remove request trace logs

This is essentially a replay of elastic#39969, but for the entity analytics
providers.

The previous configuration system did not allow users to remove trace
logs from agents after they are no longer needed. This is potential
security risk as it leaves potentially sensitive information on the file
system beyond its required lifetime. The mechanism for communicating to
the input whether to write logs is not currently powerful enough to
indicate that existing logs should be removed without deleting logs from
other instances. So add an enabled configuration option to allow the
target name to be sent independently of whether the files should be
written or removed.

The new option is optional, defaulting to the previous behaviour so
that it can be opted into via progressive repair in the client
integrations.
  • Loading branch information
efd6 committed Jun 28, 2024
1 parent 8bbcfcc commit da71774
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
- Add ability to remove request trace logs from HTTPJSON input. {pull}40003[40003]
- Update CEL mito extensions to v1.13.0 {pull}40035[40035]
- Add Jamf entity analytics provider. {pull}39996[39996]
- Add ability to remove request trace logs from entityanalytics input. {pull}40004[40004]

*Auditbeat*

Expand Down
32 changes: 20 additions & 12 deletions x-pack/filebeat/docs/inputs/input-entity-analytics.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -511,17 +511,21 @@ This is a list of optional query parameters. The default is `["accountEnabled",
"alternativeSecurityIds"]`.

[float]
==== `tracer.filename`
==== `tracer.enabled`

It is possible to log HTTP requests and responses to the EntraID API to a local file-system for debugging configurations.
This option is enabled by setting the `tracer.filename` value. Additional options are available to
tune log rotation behavior.

To differentiate the trace files generated from different input instances, a placeholder `*` can be added to the filename and will be replaced with the input instance id.
For Example, `http-request-trace-*.ndjson`.
This option is enabled by setting `tracer.enabled` to true and setting the `tracer.filename` value.
Additional options are available to tune log rotation behavior. To delete existing logs, set `tracer.enabled`
to false without unsetting the filename option.

Enabling this option compromises security and should only be used for debugging.

[float]
==== `tracer.filename`

To differentiate the trace files generated from different input instances, a placeholder `*` can be added to the
filename and will be replaced with the input instance id. For Example, `http-request-trace-*.ndjson`.

[id="provider-jamf"]
==== Jamf Computer Management (`jamf`)

Expand Down Expand Up @@ -984,17 +988,21 @@ shorter than the full synchronization interval (`sync_interval`). Expressed as a
duration string (e.g., 1m, 3h, 24h). Defaults to `15m` (15 minutes).

[float]
==== `tracer.filename`
==== `tracer.enabled`

It is possible to log HTTP requests and responses to the Okta API to a local file-system for debugging configurations.
This option is enabled by setting the `tracer.filename` value. Additional options are available to
tune log rotation behavior.

To differentiate the trace files generated from different input instances, a placeholder `*` can be added to the filename and will be replaced with the input instance id.
For Example, `http-request-trace-*.ndjson`.
This option is enabled by setting `tracer.enabled` to true and setting the `tracer.filename` value.
Additional options are available to tune log rotation behavior. To delete existing logs, set `tracer.enabled`
to false without unsetting the filename option.

Enabling this option compromises security and should only be used for debugging.

[float]
==== `tracer.filename`

To differentiate the trace files generated from different input instances, a placeholder `*` can be added to the
filename and will be replaced with the input instance id. For Example, `http-request-trace-*.ndjson`.

[float]
==== Metrics

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"

Expand Down Expand Up @@ -112,7 +114,16 @@ type graphConf struct {
Transport httpcommon.HTTPTransportSettings `config:",inline"`

// Tracer allows configuration of request trace logging.
Tracer *lumberjack.Logger `config:"tracer"`
Tracer *tracerConfig `config:"tracer"`
}

type tracerConfig struct {
Enabled *bool `config:"enabled"`
lumberjack.Logger `config:",inline"`
}

func (t *tracerConfig) enabled() bool {
return t != nil && (t.Enabled == nil || *t.Enabled)
}

type selection struct {
Expand Down Expand Up @@ -398,12 +409,39 @@ func New(ctx context.Context, id string, cfg *config.C, logger *logp.Logger, aut
return &f, nil
}

// lumberjackTimestamp is a glob expression matching the time format string used
// by lumberjack when rolling over logs, "2006-01-02T15-04-05.000".
// https://github.com/natefinch/lumberjack/blob/4cb27fcfbb0f35cb48c542c5ea80b7c1d18933d0/lumberjack.go#L39
const lumberjackTimestamp = "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]-[0-9][0-9]-[0-9][0-9].[0-9][0-9][0-9]"

// requestTrace decorates cli with an httplog.LoggingRoundTripper if cfg.Tracer
// is non-nil.
func requestTrace(ctx context.Context, cli *http.Client, cfg graphConf, log *logp.Logger) *http.Client {
if cfg.Tracer == nil {
return cli
}
if !cfg.Tracer.enabled() {
// We have a trace log name, but we are not enabled,
// so remove all trace logs we own.
err := os.Remove(cfg.Tracer.Filename)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Errorw("failed to remove request trace log", "path", cfg.Tracer.Filename, "error", err)
}
ext := filepath.Ext(cfg.Tracer.Filename)
base := strings.TrimSuffix(cfg.Tracer.Filename, ext)
paths, err := filepath.Glob(base + "-" + lumberjackTimestamp + ext)
if err != nil {
log.Errorw("failed to collect request trace log path names", "error", err)
}
for _, p := range paths {
err = os.Remove(p)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Errorw("failed to remove request trace log", "path", p, "error", err)
}
}
return cli
}

w := zapcore.AddSync(cfg.Tracer)
go func() {
// Close the logger when we are done.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,10 @@ func TestGraph_Groups(t *testing.T) {
APIEndpoint: "http://" + testSrv.addr,
}
if *trace {
rawConf.Tracer = &lumberjack.Logger{
// Use legacy behaviour; nil enabled setting.
rawConf.Tracer = &tracerConfig{Logger: lumberjack.Logger{
Filename: "test_trace-*.ndjson",
}
}}
}
c, err := config.NewConfigFrom(&rawConf)
require.NoError(t, err)
Expand Down Expand Up @@ -382,9 +383,9 @@ func TestGraph_Users(t *testing.T) {
APIEndpoint: "http://" + testSrv.addr,
}
if *trace {
rawConf.Tracer = &lumberjack.Logger{
rawConf.Tracer = &tracerConfig{Logger: lumberjack.Logger{
Filename: "test_trace-*.ndjson",
}
}}
}
c, err := config.NewConfigFrom(&rawConf)
require.NoError(t, err)
Expand Down Expand Up @@ -492,9 +493,9 @@ func TestGraph_Devices(t *testing.T) {
Select: test.selection,
}
if *trace {
rawConf.Tracer = &lumberjack.Logger{
rawConf.Tracer = &tracerConfig{Logger: lumberjack.Logger{
Filename: "test_trace-*.ndjson",
}
}}
}
c, err := config.NewConfigFrom(&rawConf)
require.NoError(t, err)
Expand Down
11 changes: 10 additions & 1 deletion x-pack/filebeat/input/entityanalytics/provider/okta/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ type conf struct {
Request *requestConfig `config:"request"`

// Tracer allows configuration of request trace logging.
Tracer *lumberjack.Logger `config:"tracer"`
Tracer *tracerConfig `config:"tracer"`
}

type tracerConfig struct {
Enabled *bool `config:"enabled"`
lumberjack.Logger `config:",inline"`
}

func (t *tracerConfig) enabled() bool {
return t != nil && (t.Enabled == nil || *t.Enabled)
}

type requestConfig struct {
Expand Down
29 changes: 29 additions & 0 deletions x-pack/filebeat/input/entityanalytics/provider/okta/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -184,12 +186,39 @@ func newClient(ctx context.Context, cfg conf, log *logp.Logger) (*http.Client, e
return client.StandardClient(), nil
}

// lumberjackTimestamp is a glob expression matching the time format string used
// by lumberjack when rolling over logs, "2006-01-02T15-04-05.000".
// https://github.com/natefinch/lumberjack/blob/4cb27fcfbb0f35cb48c542c5ea80b7c1d18933d0/lumberjack.go#L39
const lumberjackTimestamp = "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]-[0-9][0-9]-[0-9][0-9].[0-9][0-9][0-9]"

// requestTrace decorates cli with an httplog.LoggingRoundTripper if cfg.Tracer
// is non-nil.
func requestTrace(ctx context.Context, cli *http.Client, cfg conf, log *logp.Logger) *http.Client {
if cfg.Tracer == nil {
return cli
}
if !cfg.Tracer.enabled() {
// We have a trace log name, but we are not enabled,
// so remove all trace logs we own.
err := os.Remove(cfg.Tracer.Filename)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Errorw("failed to remove request trace log", "path", cfg.Tracer.Filename, "error", err)
}
ext := filepath.Ext(cfg.Tracer.Filename)
base := strings.TrimSuffix(cfg.Tracer.Filename, ext)
paths, err := filepath.Glob(base + "-" + lumberjackTimestamp + ext)
if err != nil {
log.Errorw("failed to collect request trace log path names", "error", err)
}
for _, p := range paths {
err = os.Remove(p)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Errorw("failed to remove request trace log", "path", p, "error", err)
}
}
return cli
}

w := zapcore.AddSync(cfg.Tracer)
go func() {
// Close the logger when we are done.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,10 @@ func TestOktaDoFetch(t *testing.T) {
if name == "" {
name = "default"
}
a.cfg.Tracer = &lumberjack.Logger{
// Use legacy behaviour; nil enabled setting.
a.cfg.Tracer = &tracerConfig{Logger: lumberjack.Logger{
Filename: fmt.Sprintf("test_trace_%s.ndjson", name),
}
}}
}
a.client = requestTrace(context.Background(), a.client, a.cfg, a.logger)

Expand Down

0 comments on commit da71774

Please sign in to comment.