Files
teleport/integrations/event-handler/cli.go
T
2026-02-23 18:25:52 +00:00

310 lines
12 KiB
Go

/*
Copyright 2015-2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"log/slog"
"runtime"
"strings"
"time"
"github.com/alecthomas/kong"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/lib/utils/set"
)
// FluentdConfig represents fluentd instance configuration
type FluentdConfig struct {
// FluentdURL fluentd url for audit log events
FluentdURL string `help:"fluentd url" required:"true" env:"FDFWD_FLUENTD_URL"`
// FluentdSessionURL is the base URL for session recording events.
// The event handler appends .<session-id>.log to this URL for each session.
FluentdSessionURL string `help:"fluentd session url (.<session-id>.log is appended per session)" required:"true" env:"FDFWD_FLUENTD_SESSION_URL"`
// FluentdCert is a path to fluentd cert
FluentdCert string `help:"fluentd TLS certificate file" type:"existingfile" env:"FDFWD_FLUENTD_CERT,FDWRD_FLUENTD_CERT"`
// FluentdKey is a path to fluentd key
FluentdKey string `help:"fluentd TLS key file" type:"existingfile" env:"FDFWD_FLUENTD_KEY,FDWRD_FLUENTD_KEY"`
// FluentdCA is a path to fluentd CA
FluentdCA string `help:"fluentd TLS CA file" type:"existingfile" env:"FDFWD_FLUENTD_CA,FDWRD_FLUENTD_CA"`
// FluentdMaxConnections caps the number of connections to fluentd. Defaults to a dynamic value
// calculated relative to app-level concurrency.
FluentdMaxConnections int `help:"Maximum number of connections to fluentd" env:"FDFWD_MAX_CONNECTIONS,FDWRD_MAX_CONNECTIONS"`
}
// TeleportConfig is Teleport instance configuration
type TeleportConfig struct {
// TeleportAddr is a Teleport addr
TeleportAddr string `help:"Teleport addr" env:"FDFWD_TELEPORT_ADDR" default:"localhost:3025"`
// TeleportIdentityFile is a path to Teleport identity file
TeleportIdentityFile string `help:"Teleport identity file" type:"existingfile" name:"teleport-identity" env:"FDFWD_TELEPORT_IDENTITY"`
// TeleportRefreshEnabled will reload the identity file from disk on the
// configured interval.
TeleportRefreshEnabled bool `help:"Configures the identity file to be reloaded from disk at a configured interval." env:"FDFWD_TELEPORT_REFRESH_ENABLED"`
// TeleportRefreshInterval is how often the identity file should
// be reloaded from disk.
TeleportRefreshInterval time.Duration `help:"Configures how often the identity file should be reloaded from disk." env:"FDFWD_TELEPORT_REFRESH_INTERVAL" default:"1m"`
// TeleportCA is a path to Teleport CA file
TeleportCA string `help:"Teleport TLS CA file" type:"existingfile" env:"FDFWD_TELEPORT_CA"`
// TeleportCert is a path to Teleport cert file
TeleportCert string `help:"Teleport TLS certificate file" type:"existingfile" env:"FDFWD_TELEPORT_CERT,FDWRD_TELEPORT_CERT"`
// TeleportKey is a path to Teleport key file
TeleportKey string `help:"Teleport TLS key file" type:"existingfile" env:"FDFWD_TELEPORT_KEY"`
}
// Check verifies that a valid configuration is set
func (cfg *TeleportConfig) Check() error {
provided := set.NewWithCapacity[string](3)
missing := set.NewWithCapacity[string](3)
if cfg.TeleportCert != "" {
provided.Add("`teleport.cert`")
} else {
missing.Add("`teleport.cert`")
}
if cfg.TeleportKey != "" {
provided.Add("`teleport.key`")
} else {
missing.Add("`teleport.key`")
}
if cfg.TeleportCA != "" {
provided.Add("`teleport.ca`")
} else {
missing.Add("`teleport.ca`")
}
if len(provided) > 0 && len(provided) < 3 {
return trace.BadParameter(
"configuration setting(s) %s are provided but setting(s) %s are missing",
strings.Join(provided.Elements(), ", "),
strings.Join(missing.Elements(), ", "),
)
}
if cfg.TeleportIdentityFile != "" && len(provided) != 0 {
return trace.BadParameter("configuration setting `identity` is mutually exclusive with the `cert`, `key` and `ca` settings")
}
if len(provided) == 0 && cfg.TeleportIdentityFile == "" {
return trace.BadParameter("neither `identity` file nor `cert`, `key` and `ca` files configured")
}
return nil
}
// IngestConfig ingestion configuration
type IngestConfig struct {
// StorageDir is a path to dv storage dir
StorageDir string `help:"Storage directory" required:"true" env:"FDFWD_STORAGE" name:"storage"`
// BatchSize is a fetch batch size
BatchSize int `help:"Fetch batch size" default:"20" env:"FDFWD_BATCH" name:"batch"`
// TypesRaw are event types to log
TypesRaw []string `name:"types" help:"Comma-separated list of event types to forward" env:"FDFWD_TYPES"`
// Types is a map generated from TypesRaw
Types map[string]struct{} `kong:"-"`
// SkipEventTypesRaw are event types to skip
SkipEventTypesRaw []string `name:"skip-event-types" help:"Comma-separated list of audit log event types to skip" env:"FDFWD_SKIP_EVENT_TYPES"`
// SkipEventTypes is a map generated from SkipEventTypesRaw
SkipEventTypes map[string]struct{} `kong:"-"`
// SkipSessionTypes are session event types to skip
SkipSessionTypesRaw []string `name:"skip-session-types" help:"Comma-separated list of session recording event types to skip" default:"print,desktop.recording" env:"FDFWD_SKIP_SESSION_TYPES"`
// SkipSessionTypes is a map generated from SkipSessionTypes
SkipSessionTypes map[string]struct{} `kong:"-"`
// StartTime is a time to start ingestion from
StartTime *time.Time `help:"Minimum event time in RFC3339 format" env:"FDFWD_START_TIME"`
// Timeout is the time poller will wait before the new request if there are no events in the queue
Timeout time.Duration `help:"Polling timeout" default:"10s" env:"FDFWD_TIMEOUT"`
// DryRun is the flag which simulates execution without sending events to fluentd
DryRun bool `help:"Events are read from Teleport, but are not sent to fluentd. Separate storage is used. Debug flag." env:"FDFWD_DRY_RUN"`
// ExitOnLastEvent exit when last event is processed
ExitOnLastEvent bool `help:"Exit when last event is processed" env:"FDFWD_EXIT_ON_LAST_EVENT"`
// Concurrency sets the number of concurrent sessions to ingest
Concurrency int `help:"Number of concurrent sessions" default:"5" env:"FDFWD_CONCURRENCY"`
// WindowSize is the size of the window to process events
WindowSize time.Duration `help:"Window size to process events" default:"24h" env:"FDFWD_WINDOW_SIZE"`
}
// LockConfig represents locking configuration
type LockConfig struct {
// LockEnabled represents locking enabled flag
LockEnabled bool `help:"Enable user auto-locking" name:"lock-enabled" default:"false" env:"FDFWD_LOCKING_ENABLED"`
// LockFailedAttemptsCount number of failed attempts which triggers locking
LockFailedAttemptsCount int `help:"Number of failed attempts in lock-period which triggers locking" name:"lock-failed-attempts-count" default:"3" env:"FDFWD_LOCKING_FAILED_ATTEMPTS"`
// LockPeriod represents rate limiting period
LockPeriod time.Duration `help:"Time period where lock-failed-attempts-count failed attempts will trigger locking" name:"lock-period" default:"1m" env:"FDFWD_LOCKING_PERIOD"`
// LockFor represents the duration of the new lock
LockFor time.Duration `help:"Time period for which user gets lock" name:"lock-for" env:"FDFWD_LOCKING_FOR"`
}
// StartCmdConfig is start command description
type StartCmdConfig struct {
FluentdConfig
TeleportConfig
IngestConfig
LockConfig
}
// ConfigureCmdConfig holds CLI options for teleport-event-handler configure
type ConfigureCmdConfig struct {
// Out path and file prefix to put certificates into
Out string `arg:"true" help:"Output directory" type:"existingdir" required:"true"`
// Output is a mock arg for now, it specifies export target
Output string `help:"Export target service" type:"string" required:"true" default:"fluentd"`
// Addr is Teleport auth proxy instance address
Addr string `arg:"true" help:"Teleport auth proxy instance address" type:"string" required:"true" default:"localhost:3025"`
// CAName CA certificate and key name
CAName string `arg:"true" help:"CA certificate and key name" required:"true" default:"ca"`
// ServerName server certificate and key name
ServerName string `arg:"true" help:"Server certificate and key name" required:"true" default:"server"`
// ClientName client certificate and key name
ClientName string `arg:"true" help:"Client certificate and key name" required:"true" default:"client"`
// Certificate TTL
TTL time.Duration `help:"Certificate TTL" required:"true" default:"87600h"`
// DNSNames is a DNS subjectAltNames for server cert
DNSNames []string `help:"Certificate SAN hosts" default:"localhost"`
// HostNames is an IP subjectAltNames for server cert
IP []string `help:"Certificate SAN IPs"`
// Length is RSA key length
Length int `help:"Key length" enum:"1024,2048,4096" default:"4096"`
}
// CLI represents command structure
type CLI struct {
// Config is the path to configuration file
Config kong.ConfigFlag `help:"Path to TOML configuration file" optional:"true" short:"c" type:"existingfile" env:"FDFWD_CONFIG"`
// Debug is a debug logging mode flag
Debug bool `help:"Debug logging" short:"d" env:"FDFWD_DEBUG"`
// Version is the version print command
Version struct{} `cmd:"true" help:"Print plugin version"`
// Configure is the generate certificates command configuration
Configure ConfigureCmdConfig `cmd:"true" help:"Generate mTLS certificates for Fluentd"`
// Start is the start command configuration
Start StartCmdConfig `cmd:"true" help:"Start log ingestion"`
}
// Validate validates start command arguments and prints them to log
func (c *StartCmdConfig) Validate() error {
if c.StartTime != nil {
t := c.StartTime.Truncate(time.Second)
c.StartTime = &t
}
if err := c.TeleportConfig.Check(); err != nil {
return trace.Wrap(err)
}
c.Types = set.New(c.TypesRaw...)
c.SkipSessionTypes = set.New(c.SkipSessionTypesRaw...)
c.SkipEventTypes = set.New(c.SkipEventTypesRaw...)
if c.FluentdMaxConnections < 1 {
// 2x concurrency is effectively uncapped.
c.FluentdMaxConnections = c.Concurrency * 2
}
return nil
}
// Dump dumps configuration values to the log
func (c *StartCmdConfig) Dump(ctx context.Context, log *slog.Logger) {
// Log configuration variables
log.DebugContext(ctx, "Initializing plugin",
"name", pluginName,
"version", slog.GroupValue(
slog.String("teleport", Version),
slog.String("teleport_git", Gitref),
slog.String("go", runtime.Version()),
),
)
log.InfoContext(ctx, "Using storage", "storage", c.StorageDir)
log.InfoContext(ctx, "Using batch size", "batch", c.BatchSize)
log.InfoContext(ctx, "Using concurrency", "concurrency", c.Concurrency)
log.InfoContext(ctx, "Using type filter", "types", c.Types)
log.InfoContext(ctx, "Skipping audit events of type", "types", c.SkipEventTypes)
log.InfoContext(ctx, "Skipping session recording events of type", "types", c.SkipSessionTypes)
log.InfoContext(ctx, "Using start time", "value", c.StartTime)
log.InfoContext(ctx, "Using timeout", "timeout", c.Timeout)
log.InfoContext(ctx, "Using Fluentd url", "url", c.FluentdURL)
log.InfoContext(ctx, "Using Fluentd session url", "url", c.FluentdSessionURL)
log.InfoContext(ctx, "Using Fluentd ca", "ca", c.FluentdCA)
log.InfoContext(ctx, "Using Fluentd cert", "cert", c.FluentdCert)
log.InfoContext(ctx, "Using Fluentd key", "key", c.FluentdKey)
log.InfoContext(ctx, "Using Fluentd max connections", "max_connections", c.FluentdMaxConnections)
log.InfoContext(ctx, "Using window size", "window_size", c.WindowSize)
if c.TeleportIdentityFile != "" {
log.InfoContext(ctx, "Using Teleport identity file", "file", c.TeleportIdentityFile)
}
if c.TeleportRefreshEnabled {
log.InfoContext(ctx, "Using Teleport identity file refresh", "interval", c.TeleportRefreshInterval)
}
if c.TeleportKey != "" {
log.InfoContext(ctx, "Using Teleport addr", "addr", c.TeleportAddr)
log.InfoContext(ctx, "Using Teleport CA", "ca", c.TeleportCA)
log.InfoContext(ctx, "Using Teleport cert", "cert", c.TeleportCert)
log.InfoContext(ctx, "Using Teleport key", "key", c.TeleportKey)
}
if c.LockEnabled {
log.InfoContext(ctx, "Auto-locking enabled", "count", c.LockFailedAttemptsCount, "period", c.LockPeriod, "for", c.LockFor)
}
if c.DryRun {
log.WarnContext(ctx, "Dry run! Events are not sent to Fluentd. Separate storage is used.")
}
if c.ExitOnLastEvent {
log.WarnContext(ctx, "Exit on last event setting enabled")
}
}