diff --git a/cmd/outpost-migrate-redis/cli.go b/cmd/outpost-migrate-redis/cli.go index 1207d8662..9e98bfaa1 100644 --- a/cmd/outpost-migrate-redis/cli.go +++ b/cmd/outpost-migrate-redis/cli.go @@ -30,6 +30,11 @@ func NewCommand() *cli.Command { Usage: "Redis server port (overrides config)", Sources: cli.EnvVars("REDIS_PORT"), }, + &cli.StringFlag{ + Name: "redis-username", + Usage: "Redis username for ACL authentication (overrides config)", + Sources: cli.EnvVars("REDIS_USERNAME"), + }, &cli.StringFlag{ Name: "redis-password", Usage: "Redis password (overrides config)", @@ -202,6 +207,7 @@ func withMigrator(ctx context.Context, c *cli.Command, fn func(*Migrator) error) rc.Database, rc.ClusterEnabled, rc.TLSEnabled, + rc.Username != "", rc.Password != "", ) diff --git a/cmd/outpost-migrate-redis/config.go b/cmd/outpost-migrate-redis/config.go index cf05b9674..2a1d2ebcc 100644 --- a/cmd/outpost-migrate-redis/config.go +++ b/cmd/outpost-migrate-redis/config.go @@ -56,6 +56,10 @@ func (cl *ConfigLoader) applyRedisOverrides(c *cli.Command, cfg *config.Config) cfg.Redis.Port = port } + if username := c.String("redis-username"); username != "" { + cfg.Redis.Username = username + } + if password := c.String("redis-password"); password != "" { cfg.Redis.Password = password } diff --git a/cmd/outpost-migrate-redis/logger.go b/cmd/outpost-migrate-redis/logger.go index 5e8a7f11a..0e26175de 100644 --- a/cmd/outpost-migrate-redis/logger.go +++ b/cmd/outpost-migrate-redis/logger.go @@ -26,7 +26,7 @@ type MigrationLogger interface { Verbose() bool // Configuration and initialization - LogRedisConfig(host string, port int, database int, clusterEnabled bool, tlsEnabled bool, hasPassword bool) + LogRedisConfig(host string, port int, database int, clusterEnabled bool, tlsEnabled bool, hasUsername bool, hasPassword bool) LogInitialization(isFresh bool, migrationsMarked int) // Migration lifecycle diff --git a/cmd/outpost-migrate-redis/logger_cli.go b/cmd/outpost-migrate-redis/logger_cli.go index d788ec713..e4e5f7f74 100644 --- a/cmd/outpost-migrate-redis/logger_cli.go +++ b/cmd/outpost-migrate-redis/logger_cli.go @@ -28,7 +28,7 @@ func (l *CLILogger) Verbose() bool { } // Configuration and initialization -func (l *CLILogger) LogRedisConfig(host string, port int, database int, clusterEnabled bool, tlsEnabled bool, hasPassword bool) { +func (l *CLILogger) LogRedisConfig(host string, port int, database int, clusterEnabled bool, tlsEnabled bool, hasUsername bool, hasPassword bool) { if !l.verbose { return } @@ -38,6 +38,11 @@ func (l *CLILogger) LogRedisConfig(host string, port int, database int, clusterE fmt.Printf(" Database: %d\n", database) fmt.Printf(" Cluster Enabled: %v\n", clusterEnabled) fmt.Printf(" TLS Enabled: %v\n", tlsEnabled) + if hasUsername { + fmt.Printf(" Username: ****** (set)\n") + } else { + fmt.Printf(" Username: (not set)\n") + } if hasPassword { fmt.Printf(" Password: ****** (set)\n") } else { diff --git a/cmd/outpost-migrate-redis/logger_server.go b/cmd/outpost-migrate-redis/logger_server.go index 970a8e1cb..837843c98 100644 --- a/cmd/outpost-migrate-redis/logger_server.go +++ b/cmd/outpost-migrate-redis/logger_server.go @@ -29,7 +29,7 @@ func (l *ServerLogger) Verbose() bool { } // Configuration and initialization -func (l *ServerLogger) LogRedisConfig(host string, port int, database int, clusterEnabled bool, tlsEnabled bool, hasPassword bool) { +func (l *ServerLogger) LogRedisConfig(host string, port int, database int, clusterEnabled bool, tlsEnabled bool, hasUsername bool, hasPassword bool) { if !l.verbose { return } @@ -39,6 +39,7 @@ func (l *ServerLogger) LogRedisConfig(host string, port int, database int, clust zap.Int("database", database), zap.Bool("cluster_enabled", clusterEnabled), zap.Bool("tls_enabled", tlsEnabled), + zap.Bool("username_set", hasUsername), zap.Bool("password_set", hasPassword), ) } diff --git a/cmd/outpost-migrate-redis/migration_test.go b/cmd/outpost-migrate-redis/migration_test.go index f23860e96..fc89794e3 100644 --- a/cmd/outpost-migrate-redis/migration_test.go +++ b/cmd/outpost-migrate-redis/migration_test.go @@ -106,7 +106,7 @@ func newMockLogger() *mockLogger { } func (l *mockLogger) Verbose() bool { return false } -func (l *mockLogger) LogRedisConfig(string, int, int, bool, bool, bool) {} +func (l *mockLogger) LogRedisConfig(string, int, int, bool, bool, bool, bool) {} func (l *mockLogger) LogInitialization(bool, int) {} func (l *mockLogger) LogMigrationList(map[string]string) {} func (l *mockLogger) LogMigrationStatus(int, int) {} diff --git a/docs/pages/references/configuration.mdx b/docs/pages/references/configuration.mdx index 8d7ddb52c..60fc60fdf 100644 --- a/docs/pages/references/configuration.mdx +++ b/docs/pages/references/configuration.mdx @@ -41,6 +41,10 @@ Global configurations are provided through env variables or a YAML file. ConfigM | `AZURE_SERVICEBUS_RESOURCE_GROUP` | Azure resource group name | `nil` | Yes | | `AZURE_SERVICEBUS_SUBSCRIPTION_ID` | Azure subscription ID | `nil` | Yes | | `AZURE_SERVICEBUS_TENANT_ID` | Azure Active Directory tenant ID | `nil` | Yes | +| `CLICKHOUSE_ADDR` | Address (host:port) of the ClickHouse server. Example: 'localhost:9000'. | `nil` | No | +| `CLICKHOUSE_DATABASE` | Database name in ClickHouse to use. | `outpost` | No | +| `CLICKHOUSE_PASSWORD` | Password for ClickHouse authentication. | `nil` | No | +| `CLICKHOUSE_USERNAME` | Username for ClickHouse authentication. | `nil` | No | | `DELIVERY_IDEMPOTENCY_KEY_TTL` | Time-to-live in seconds for delivery queue idempotency keys. Controls how long processed deliveries are remembered to prevent duplicate delivery attempts. Default: 3600 (1 hour). | `3600` | No | | `DELIVERY_MAX_CONCURRENCY` | Maximum number of delivery attempts to process concurrently. | `1` | No | | `DELIVERY_TIMEOUT_SECONDS` | Timeout in seconds for HTTP requests made during event delivery to webhook destinations. | `5` | No | @@ -96,7 +100,7 @@ Global configurations are provided through env variables or a YAML file. ConfigM | `PORTAL_ORGANIZATION_NAME` | Organization name displayed in the Outpost Portal. | `nil` | No | | `PORTAL_PROXY_URL` | URL to proxy the Outpost Portal through. If set, Outpost serves the portal assets, and this URL is used as the base. Must be a valid URL. | `nil` | No | | `PORTAL_REFERER_URL` | The URL where the user is redirected when the JWT token is expired or when the user clicks 'back'. Required if the Outpost Portal is enabled/used. | `nil` | Conditional | -| `POSTGRES_URL` | Connection URL for PostgreSQL, used for log storage. Example: 'postgres://user:pass@host:port/dbname?sslmode=disable'. | `nil` | Yes | +| `POSTGRES_URL` | Connection URL for PostgreSQL, used for log storage. Example: 'postgres://user:pass@host:port/dbname?sslmode=disable'. | `nil` | No | | `PUBLISH_AWS_SQS_ACCESS_KEY_ID` | AWS Access Key ID for the SQS publish queue. Required if AWS SQS is the chosen publish MQ provider. | `nil` | Conditional | | `PUBLISH_AWS_SQS_ENDPOINT` | Custom AWS SQS endpoint URL for the publish queue. Optional. | `nil` | No | | `PUBLISH_AWS_SQS_QUEUE` | Name of the SQS queue for publishing events. Required if AWS SQS is the chosen publish MQ provider. | `nil` | Conditional | @@ -125,6 +129,7 @@ Global configurations are provided through env variables or a YAML file. ConfigM | `REDIS_PASSWORD` | Password for Redis authentication, if required by the server. | `nil` | Yes | | `REDIS_PORT` | Port number for the Redis server. | `6379` | Yes | | `REDIS_TLS_ENABLED` | Enable TLS encryption for Redis connection. | `false` | No | +| `REDIS_USERNAME` | Username for Redis ACL authentication. | `nil` | No | | `RETRY_INTERVAL_SECONDS` | Interval in seconds for exponential backoff retry strategy (base 2). Ignored if retry_schedule is provided. | `30` | No | | `RETRY_POLL_BACKOFF_MS` | Backoff time in milliseconds when the retry monitor finds no messages to process. When a retry message is found, the monitor immediately polls for the next message without delay. Lower values provide faster retry processing but increase Redis load. For serverless Redis providers (Upstash, ElastiCache Serverless), consider increasing to 5000-10000ms to reduce costs. Default: 100 | `100` | No | | `RETRY_SCHEDULE` | Comma-separated list of retry delays in seconds. If provided, overrides retry_interval_seconds and retry_max_limit. Schedule length defines the max number of retries. Example: '5,60,600,3600,7200' for 5 retries at 5s, 1m, 10m, 1h, 2h. | `[]` | No | @@ -172,6 +177,20 @@ alert: # Enables or disables audit logging for significant events. audit_log: true +clickhouse: + # Address (host:port) of the ClickHouse server. Example: 'localhost:9000'. + addr: "" + + # Database name in ClickHouse to use. + database: "outpost" + + # Password for ClickHouse authentication. + password: "" + + # Username for ClickHouse authentication. + username: "" + + # Time-to-live in seconds for delivery queue idempotency keys. Controls how long processed deliveries are remembered to prevent duplicate delivery attempts. Default: 3600 (1 hour). delivery_idempotency_key_ttl: 3600 @@ -466,7 +485,6 @@ portal: # Connection URL for PostgreSQL, used for log storage. Example: 'postgres://user:pass@host:port/dbname?sslmode=disable'. -# Required: Y postgres: "" # Time-to-live in seconds for publish queue idempotency keys. Controls how long processed events are remembered to prevent duplicate processing. Default: 3600 (1 hour). @@ -573,6 +591,9 @@ redis: # Enable TLS encryption for Redis connection. tls_enabled: false + # Username for Redis ACL authentication. + username: "" + # Interval in seconds for exponential backoff retry strategy (base 2). Ignored if retry_schedule is provided. retry_interval_seconds: 30 diff --git a/examples/docker-compose/.env.example b/examples/docker-compose/.env.example index fc212237c..dd6542dc4 100644 --- a/examples/docker-compose/.env.example +++ b/examples/docker-compose/.env.example @@ -6,5 +6,6 @@ AES_ENCRYPTION_SECRET="encryptionsecret" # Redis REDIS_HOST="redis" REDIS_PORT="6379" +REDIS_USERNAME="" REDIS_PASSWORD="password" REDIS_DATABASE="0" \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index 1b7eac207..7d90c7f39 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -360,6 +360,7 @@ func ParseWithOS(flags Flags, osInterface OSInterface) (*Config, error) { type RedisConfig struct { Host string `yaml:"host" env:"REDIS_HOST" desc:"Hostname or IP address of the Redis server." required:"Y"` Port int `yaml:"port" env:"REDIS_PORT" desc:"Port number for the Redis server." required:"Y"` + Username string `yaml:"username" env:"REDIS_USERNAME" desc:"Username for Redis ACL authentication." required:"N"` Password string `yaml:"password" env:"REDIS_PASSWORD" desc:"Password for Redis authentication, if required by the server." required:"Y"` Database int `yaml:"database" env:"REDIS_DATABASE" desc:"Redis database number to select after connecting (ignored in cluster mode)." required:"Y"` TLSEnabled bool `yaml:"tls_enabled" env:"REDIS_TLS_ENABLED" desc:"Enable TLS encryption for Redis connection." required:"N"` @@ -371,6 +372,7 @@ func (c *RedisConfig) ToConfig() *redis.RedisConfig { return &redis.RedisConfig{ Host: c.Host, Port: c.Port, + Username: c.Username, Password: c.Password, Database: c.Database, TLSEnabled: c.TLSEnabled, diff --git a/internal/redis/config.go b/internal/redis/config.go index 665869e5f..073686a92 100644 --- a/internal/redis/config.go +++ b/internal/redis/config.go @@ -3,6 +3,7 @@ package redis type RedisConfig struct { Host string Port int + Username string Password string Database int TLSEnabled bool diff --git a/internal/redis/redis.go b/internal/redis/redis.go index 7ebb12b56..953ff3a00 100644 --- a/internal/redis/redis.go +++ b/internal/redis/redis.go @@ -65,6 +65,7 @@ func createClusterClient(ctx context.Context, config *RedisConfig) (Client, erro // Start with single node - cluster client will auto-discover other nodes options := &r.ClusterOptions{ Addrs: []string{fmt.Sprintf("%s:%d", config.Host, config.Port)}, + Username: config.Username, Password: config.Password, // Note: Database is ignored in cluster mode } @@ -103,6 +104,7 @@ func createClusterClient(ctx context.Context, config *RedisConfig) (Client, erro func createRegularClient(ctx context.Context, config *RedisConfig) (Client, error) { options := &r.Options{ Addr: fmt.Sprintf("%s:%d", config.Host, config.Port), + Username: config.Username, Password: config.Password, DB: config.Database, }