github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/logger/config.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package logger 19 20 import ( 21 "context" 22 "crypto/tls" 23 "errors" 24 "strconv" 25 "strings" 26 27 "github.com/minio/pkg/v2/env" 28 xnet "github.com/minio/pkg/v2/net" 29 30 "github.com/minio/minio/internal/config" 31 "github.com/minio/minio/internal/logger/target/http" 32 "github.com/minio/minio/internal/logger/target/kafka" 33 ) 34 35 // Console logger target 36 type Console struct { 37 Enabled bool `json:"enabled"` 38 } 39 40 // Audit/Logger constants 41 const ( 42 Endpoint = "endpoint" 43 AuthToken = "auth_token" 44 ClientCert = "client_cert" 45 ClientKey = "client_key" 46 BatchSize = "batch_size" 47 QueueSize = "queue_size" 48 QueueDir = "queue_dir" 49 Proxy = "proxy" 50 51 KafkaBrokers = "brokers" 52 KafkaTopic = "topic" 53 KafkaTLS = "tls" 54 KafkaTLSSkipVerify = "tls_skip_verify" 55 KafkaTLSClientAuth = "tls_client_auth" 56 KafkaSASL = "sasl" 57 KafkaSASLUsername = "sasl_username" 58 KafkaSASLPassword = "sasl_password" 59 KafkaSASLMechanism = "sasl_mechanism" 60 KafkaClientTLSCert = "client_tls_cert" 61 KafkaClientTLSKey = "client_tls_key" 62 KafkaVersion = "version" 63 KafkaQueueDir = "queue_dir" 64 KafkaQueueSize = "queue_size" 65 66 EnvLoggerWebhookEnable = "MINIO_LOGGER_WEBHOOK_ENABLE" 67 EnvLoggerWebhookEndpoint = "MINIO_LOGGER_WEBHOOK_ENDPOINT" 68 EnvLoggerWebhookAuthToken = "MINIO_LOGGER_WEBHOOK_AUTH_TOKEN" 69 EnvLoggerWebhookClientCert = "MINIO_LOGGER_WEBHOOK_CLIENT_CERT" 70 EnvLoggerWebhookClientKey = "MINIO_LOGGER_WEBHOOK_CLIENT_KEY" 71 EnvLoggerWebhookProxy = "MINIO_LOGGER_WEBHOOK_PROXY" 72 EnvLoggerWebhookBatchSize = "MINIO_LOGGER_WEBHOOK_BATCH_SIZE" 73 EnvLoggerWebhookQueueSize = "MINIO_LOGGER_WEBHOOK_QUEUE_SIZE" 74 EnvLoggerWebhookQueueDir = "MINIO_LOGGER_WEBHOOK_QUEUE_DIR" 75 76 EnvAuditWebhookEnable = "MINIO_AUDIT_WEBHOOK_ENABLE" 77 EnvAuditWebhookEndpoint = "MINIO_AUDIT_WEBHOOK_ENDPOINT" 78 EnvAuditWebhookAuthToken = "MINIO_AUDIT_WEBHOOK_AUTH_TOKEN" 79 EnvAuditWebhookClientCert = "MINIO_AUDIT_WEBHOOK_CLIENT_CERT" 80 EnvAuditWebhookClientKey = "MINIO_AUDIT_WEBHOOK_CLIENT_KEY" 81 EnvAuditWebhookBatchSize = "MINIO_AUDIT_WEBHOOK_BATCH_SIZE" 82 EnvAuditWebhookQueueSize = "MINIO_AUDIT_WEBHOOK_QUEUE_SIZE" 83 EnvAuditWebhookQueueDir = "MINIO_AUDIT_WEBHOOK_QUEUE_DIR" 84 85 EnvKafkaEnable = "MINIO_AUDIT_KAFKA_ENABLE" 86 EnvKafkaBrokers = "MINIO_AUDIT_KAFKA_BROKERS" 87 EnvKafkaTopic = "MINIO_AUDIT_KAFKA_TOPIC" 88 EnvKafkaTLS = "MINIO_AUDIT_KAFKA_TLS" 89 EnvKafkaTLSSkipVerify = "MINIO_AUDIT_KAFKA_TLS_SKIP_VERIFY" 90 EnvKafkaTLSClientAuth = "MINIO_AUDIT_KAFKA_TLS_CLIENT_AUTH" 91 EnvKafkaSASLEnable = "MINIO_AUDIT_KAFKA_SASL" 92 EnvKafkaSASLUsername = "MINIO_AUDIT_KAFKA_SASL_USERNAME" 93 EnvKafkaSASLPassword = "MINIO_AUDIT_KAFKA_SASL_PASSWORD" 94 EnvKafkaSASLMechanism = "MINIO_AUDIT_KAFKA_SASL_MECHANISM" 95 EnvKafkaClientTLSCert = "MINIO_AUDIT_KAFKA_CLIENT_TLS_CERT" 96 EnvKafkaClientTLSKey = "MINIO_AUDIT_KAFKA_CLIENT_TLS_KEY" 97 EnvKafkaVersion = "MINIO_AUDIT_KAFKA_VERSION" 98 EnvKafkaQueueDir = "MINIO_AUDIT_KAFKA_QUEUE_DIR" 99 EnvKafkaQueueSize = "MINIO_AUDIT_KAFKA_QUEUE_SIZE" 100 101 loggerTargetNamePrefix = "logger-" 102 auditTargetNamePrefix = "audit-" 103 ) 104 105 var ( 106 errInvalidQueueSize = errors.New("invalid queue_size value") 107 errInvalidBatchSize = errors.New("invalid batch_size value") 108 ) 109 110 // Default KVS for loggerHTTP and loggerAuditHTTP 111 var ( 112 DefaultLoggerWebhookKVS = config.KVS{ 113 config.KV{ 114 Key: config.Enable, 115 Value: config.EnableOff, 116 }, 117 config.KV{ 118 Key: Endpoint, 119 Value: "", 120 }, 121 config.KV{ 122 Key: AuthToken, 123 Value: "", 124 }, 125 config.KV{ 126 Key: ClientCert, 127 Value: "", 128 }, 129 config.KV{ 130 Key: ClientKey, 131 Value: "", 132 }, 133 config.KV{ 134 Key: Proxy, 135 Value: "", 136 }, 137 config.KV{ 138 Key: BatchSize, 139 Value: "1", 140 }, 141 config.KV{ 142 Key: QueueSize, 143 Value: "100000", 144 }, 145 config.KV{ 146 Key: QueueDir, 147 Value: "", 148 }, 149 } 150 151 DefaultAuditWebhookKVS = config.KVS{ 152 config.KV{ 153 Key: config.Enable, 154 Value: config.EnableOff, 155 }, 156 config.KV{ 157 Key: Endpoint, 158 Value: "", 159 }, 160 config.KV{ 161 Key: AuthToken, 162 Value: "", 163 }, 164 config.KV{ 165 Key: ClientCert, 166 Value: "", 167 }, 168 config.KV{ 169 Key: ClientKey, 170 Value: "", 171 }, 172 config.KV{ 173 Key: BatchSize, 174 Value: "1", 175 }, 176 config.KV{ 177 Key: QueueSize, 178 Value: "100000", 179 }, 180 config.KV{ 181 Key: QueueDir, 182 Value: "", 183 }, 184 } 185 186 DefaultAuditKafkaKVS = config.KVS{ 187 config.KV{ 188 Key: config.Enable, 189 Value: config.EnableOff, 190 }, 191 config.KV{ 192 Key: KafkaTopic, 193 Value: "", 194 }, 195 config.KV{ 196 Key: KafkaBrokers, 197 Value: "", 198 }, 199 config.KV{ 200 Key: KafkaSASLUsername, 201 Value: "", 202 }, 203 config.KV{ 204 Key: KafkaSASLPassword, 205 Value: "", 206 }, 207 config.KV{ 208 Key: KafkaSASLMechanism, 209 Value: "plain", 210 }, 211 config.KV{ 212 Key: KafkaClientTLSCert, 213 Value: "", 214 }, 215 config.KV{ 216 Key: KafkaClientTLSKey, 217 Value: "", 218 }, 219 config.KV{ 220 Key: KafkaTLSClientAuth, 221 Value: "0", 222 }, 223 config.KV{ 224 Key: KafkaSASL, 225 Value: config.EnableOff, 226 }, 227 config.KV{ 228 Key: KafkaTLS, 229 Value: config.EnableOff, 230 }, 231 config.KV{ 232 Key: KafkaTLSSkipVerify, 233 Value: config.EnableOff, 234 }, 235 config.KV{ 236 Key: KafkaVersion, 237 Value: "", 238 }, 239 config.KV{ 240 Key: QueueSize, 241 Value: "100000", 242 }, 243 config.KV{ 244 Key: QueueDir, 245 Value: "", 246 }, 247 } 248 ) 249 250 // Config console and http logger targets 251 type Config struct { 252 Console Console `json:"console"` 253 HTTP map[string]http.Config `json:"http"` 254 AuditWebhook map[string]http.Config `json:"audit"` 255 AuditKafka map[string]kafka.Config `json:"audit_kafka"` 256 } 257 258 // NewConfig - initialize new logger config. 259 func NewConfig() Config { 260 cfg := Config{ 261 // Console logging is on by default 262 Console: Console{ 263 Enabled: true, 264 }, 265 HTTP: make(map[string]http.Config), 266 AuditWebhook: make(map[string]http.Config), 267 AuditKafka: make(map[string]kafka.Config), 268 } 269 270 return cfg 271 } 272 273 func getCfgVal(envName, key, defaultValue string) string { 274 if key != config.Default { 275 envName = envName + config.Default + key 276 } 277 return env.Get(envName, defaultValue) 278 } 279 280 func lookupLegacyConfigForSubSys(ctx context.Context, subSys string) Config { 281 cfg := NewConfig() 282 switch subSys { 283 case config.LoggerWebhookSubSys: 284 var loggerTargets []string 285 envs := env.List(legacyEnvLoggerHTTPEndpoint) 286 for _, k := range envs { 287 target := strings.TrimPrefix(k, legacyEnvLoggerHTTPEndpoint+config.Default) 288 if target == legacyEnvLoggerHTTPEndpoint { 289 target = config.Default 290 } 291 loggerTargets = append(loggerTargets, target) 292 } 293 294 // Load HTTP logger from the environment if found 295 for _, target := range loggerTargets { 296 endpoint := getCfgVal(legacyEnvLoggerHTTPEndpoint, target, "") 297 if endpoint == "" { 298 continue 299 } 300 url, err := xnet.ParseHTTPURL(endpoint) 301 if err != nil { 302 LogOnceIf(ctx, err, "logger-webhook-"+endpoint) 303 continue 304 } 305 cfg.HTTP[target] = http.Config{ 306 Enabled: true, 307 Endpoint: url, 308 } 309 } 310 311 case config.AuditWebhookSubSys: 312 // List legacy audit ENVs if any. 313 var loggerAuditTargets []string 314 envs := env.List(legacyEnvAuditLoggerHTTPEndpoint) 315 for _, k := range envs { 316 target := strings.TrimPrefix(k, legacyEnvAuditLoggerHTTPEndpoint+config.Default) 317 if target == legacyEnvAuditLoggerHTTPEndpoint { 318 target = config.Default 319 } 320 loggerAuditTargets = append(loggerAuditTargets, target) 321 } 322 323 for _, target := range loggerAuditTargets { 324 endpoint := getCfgVal(legacyEnvAuditLoggerHTTPEndpoint, target, "") 325 if endpoint == "" { 326 continue 327 } 328 url, err := xnet.ParseHTTPURL(endpoint) 329 if err != nil { 330 LogOnceIf(ctx, err, "audit-webhook-"+endpoint) 331 continue 332 } 333 cfg.AuditWebhook[target] = http.Config{ 334 Enabled: true, 335 Endpoint: url, 336 } 337 } 338 339 } 340 return cfg 341 } 342 343 func lookupAuditKafkaConfig(scfg config.Config, cfg Config) (Config, error) { 344 for k, kv := range config.Merge(scfg[config.AuditKafkaSubSys], EnvKafkaEnable, DefaultAuditKafkaKVS) { 345 enabledCfgVal := getCfgVal(EnvKafkaEnable, k, kv.Get(config.Enable)) 346 enabled, err := config.ParseBool(enabledCfgVal) 347 if err != nil { 348 return cfg, err 349 } 350 if !enabled { 351 continue 352 } 353 var brokers []xnet.Host 354 kafkaBrokers := getCfgVal(EnvKafkaBrokers, k, kv.Get(KafkaBrokers)) 355 if len(kafkaBrokers) == 0 { 356 return cfg, config.Errorf("kafka 'brokers' cannot be empty") 357 } 358 for _, s := range strings.Split(kafkaBrokers, config.ValueSeparator) { 359 var host *xnet.Host 360 host, err = xnet.ParseHost(s) 361 if err != nil { 362 break 363 } 364 brokers = append(brokers, *host) 365 } 366 if err != nil { 367 return cfg, err 368 } 369 370 clientAuthCfgVal := getCfgVal(EnvKafkaTLSClientAuth, k, kv.Get(KafkaTLSClientAuth)) 371 clientAuth, err := strconv.Atoi(clientAuthCfgVal) 372 if err != nil { 373 return cfg, err 374 } 375 376 kafkaArgs := kafka.Config{ 377 Enabled: enabled, 378 Brokers: brokers, 379 Topic: getCfgVal(EnvKafkaTopic, k, kv.Get(KafkaTopic)), 380 Version: getCfgVal(EnvKafkaVersion, k, kv.Get(KafkaVersion)), 381 } 382 383 kafkaArgs.TLS.Enable = getCfgVal(EnvKafkaTLS, k, kv.Get(KafkaTLS)) == config.EnableOn 384 kafkaArgs.TLS.SkipVerify = getCfgVal(EnvKafkaTLSSkipVerify, k, kv.Get(KafkaTLSSkipVerify)) == config.EnableOn 385 kafkaArgs.TLS.ClientAuth = tls.ClientAuthType(clientAuth) 386 387 kafkaArgs.TLS.ClientTLSCert = getCfgVal(EnvKafkaClientTLSCert, k, kv.Get(KafkaClientTLSCert)) 388 kafkaArgs.TLS.ClientTLSKey = getCfgVal(EnvKafkaClientTLSKey, k, kv.Get(KafkaClientTLSKey)) 389 390 kafkaArgs.SASL.Enable = getCfgVal(EnvKafkaSASLEnable, k, kv.Get(KafkaSASL)) == config.EnableOn 391 kafkaArgs.SASL.User = getCfgVal(EnvKafkaSASLUsername, k, kv.Get(KafkaSASLUsername)) 392 kafkaArgs.SASL.Password = getCfgVal(EnvKafkaSASLPassword, k, kv.Get(KafkaSASLPassword)) 393 kafkaArgs.SASL.Mechanism = getCfgVal(EnvKafkaSASLMechanism, k, kv.Get(KafkaSASLMechanism)) 394 395 kafkaArgs.QueueDir = getCfgVal(EnvKafkaQueueDir, k, kv.Get(KafkaQueueDir)) 396 397 queueSizeCfgVal := getCfgVal(EnvKafkaQueueSize, k, kv.Get(KafkaQueueSize)) 398 queueSize, err := strconv.Atoi(queueSizeCfgVal) 399 if err != nil { 400 return cfg, err 401 } 402 if queueSize <= 0 { 403 return cfg, errInvalidQueueSize 404 } 405 kafkaArgs.QueueSize = queueSize 406 407 cfg.AuditKafka[k] = kafkaArgs 408 } 409 410 return cfg, nil 411 } 412 413 func lookupLoggerWebhookConfig(scfg config.Config, cfg Config) (Config, error) { 414 for k, kv := range config.Merge(scfg[config.LoggerWebhookSubSys], EnvLoggerWebhookEnable, DefaultLoggerWebhookKVS) { 415 if v, ok := cfg.HTTP[k]; ok && v.Enabled { 416 // This target is already enabled using the 417 // legacy environment variables, ignore. 418 continue 419 } 420 subSysTarget := config.LoggerWebhookSubSys 421 if k != config.Default { 422 subSysTarget = config.LoggerWebhookSubSys + config.SubSystemSeparator + k 423 } 424 if err := config.CheckValidKeys(subSysTarget, kv, DefaultLoggerWebhookKVS); err != nil { 425 return cfg, err 426 } 427 enableCfgVal := getCfgVal(EnvLoggerWebhookEnable, k, kv.Get(config.Enable)) 428 enable, err := config.ParseBool(enableCfgVal) 429 if err != nil || !enable { 430 continue 431 } 432 var url *xnet.URL 433 endpoint := getCfgVal(EnvLoggerWebhookEndpoint, k, kv.Get(Endpoint)) 434 url, err = xnet.ParseHTTPURL(endpoint) 435 if err != nil { 436 return cfg, err 437 } 438 clientCert := getCfgVal(EnvLoggerWebhookClientCert, k, kv.Get(ClientCert)) 439 clientKey := getCfgVal(EnvLoggerWebhookClientKey, k, kv.Get(ClientKey)) 440 err = config.EnsureCertAndKey(clientCert, clientKey) 441 if err != nil { 442 return cfg, err 443 } 444 queueSizeCfgVal := getCfgVal(EnvLoggerWebhookQueueSize, k, kv.Get(QueueSize)) 445 queueSize, err := strconv.Atoi(queueSizeCfgVal) 446 if err != nil { 447 return cfg, err 448 } 449 if queueSize <= 0 { 450 return cfg, errInvalidQueueSize 451 } 452 batchSizeCfgVal := getCfgVal(EnvLoggerWebhookBatchSize, k, kv.Get(BatchSize)) 453 batchSize, err := strconv.Atoi(batchSizeCfgVal) 454 if err != nil { 455 return cfg, err 456 } 457 if batchSize <= 0 { 458 return cfg, errInvalidBatchSize 459 } 460 cfg.HTTP[k] = http.Config{ 461 Enabled: true, 462 Endpoint: url, 463 AuthToken: getCfgVal(EnvLoggerWebhookAuthToken, k, kv.Get(AuthToken)), 464 ClientCert: clientCert, 465 ClientKey: clientKey, 466 Proxy: getCfgVal(EnvLoggerWebhookProxy, k, kv.Get(Proxy)), 467 BatchSize: batchSize, 468 QueueSize: queueSize, 469 QueueDir: getCfgVal(EnvLoggerWebhookQueueDir, k, kv.Get(QueueDir)), 470 Name: loggerTargetNamePrefix + k, 471 } 472 } 473 return cfg, nil 474 } 475 476 func lookupAuditWebhookConfig(scfg config.Config, cfg Config) (Config, error) { 477 for k, kv := range config.Merge(scfg[config.AuditWebhookSubSys], EnvAuditWebhookEnable, DefaultAuditWebhookKVS) { 478 if v, ok := cfg.AuditWebhook[k]; ok && v.Enabled { 479 // This target is already enabled using the 480 // legacy environment variables, ignore. 481 continue 482 } 483 subSysTarget := config.AuditWebhookSubSys 484 if k != config.Default { 485 subSysTarget = config.AuditWebhookSubSys + config.SubSystemSeparator + k 486 } 487 if err := config.CheckValidKeys(subSysTarget, kv, DefaultAuditWebhookKVS); err != nil { 488 return cfg, err 489 } 490 enable, err := config.ParseBool(getCfgVal(EnvAuditWebhookEnable, k, kv.Get(config.Enable))) 491 if err != nil || !enable { 492 continue 493 } 494 var url *xnet.URL 495 endpoint := getCfgVal(EnvAuditWebhookEndpoint, k, kv.Get(Endpoint)) 496 url, err = xnet.ParseHTTPURL(endpoint) 497 if err != nil { 498 return cfg, err 499 } 500 clientCert := getCfgVal(EnvAuditWebhookClientCert, k, kv.Get(ClientCert)) 501 clientKey := getCfgVal(EnvAuditWebhookClientKey, k, kv.Get(ClientKey)) 502 err = config.EnsureCertAndKey(clientCert, clientKey) 503 if err != nil { 504 return cfg, err 505 } 506 queueSizeCfgVal := getCfgVal(EnvAuditWebhookQueueSize, k, kv.Get(QueueSize)) 507 queueSize, err := strconv.Atoi(queueSizeCfgVal) 508 if err != nil { 509 return cfg, err 510 } 511 if queueSize <= 0 { 512 return cfg, errInvalidQueueSize 513 } 514 batchSizeCfgVal := getCfgVal(EnvAuditWebhookBatchSize, k, kv.Get(BatchSize)) 515 batchSize, err := strconv.Atoi(batchSizeCfgVal) 516 if err != nil { 517 return cfg, err 518 } 519 if batchSize <= 0 { 520 return cfg, errInvalidBatchSize 521 } 522 cfg.AuditWebhook[k] = http.Config{ 523 Enabled: true, 524 Endpoint: url, 525 AuthToken: getCfgVal(EnvAuditWebhookAuthToken, k, kv.Get(AuthToken)), 526 ClientCert: clientCert, 527 ClientKey: clientKey, 528 BatchSize: batchSize, 529 QueueSize: queueSize, 530 QueueDir: getCfgVal(EnvAuditWebhookQueueDir, k, kv.Get(QueueDir)), 531 Name: auditTargetNamePrefix + k, 532 } 533 } 534 return cfg, nil 535 } 536 537 // LookupConfigForSubSys - lookup logger config, override with ENVs if set, for the given sub-system 538 func LookupConfigForSubSys(ctx context.Context, scfg config.Config, subSys string) (cfg Config, err error) { 539 switch subSys { 540 case config.LoggerWebhookSubSys: 541 cfg = lookupLegacyConfigForSubSys(ctx, config.LoggerWebhookSubSys) 542 if cfg, err = lookupLoggerWebhookConfig(scfg, cfg); err != nil { 543 return cfg, err 544 } 545 case config.AuditWebhookSubSys: 546 cfg = lookupLegacyConfigForSubSys(ctx, config.AuditWebhookSubSys) 547 if cfg, err = lookupAuditWebhookConfig(scfg, cfg); err != nil { 548 return cfg, err 549 } 550 case config.AuditKafkaSubSys: 551 cfg.AuditKafka = make(map[string]kafka.Config) 552 if cfg, err = lookupAuditKafkaConfig(scfg, cfg); err != nil { 553 return cfg, err 554 } 555 } 556 return cfg, nil 557 } 558 559 // ValidateSubSysConfig - validates logger related config of given sub-system 560 func ValidateSubSysConfig(ctx context.Context, scfg config.Config, subSys string) error { 561 // Lookup for legacy environment variables first 562 _, err := LookupConfigForSubSys(ctx, scfg, subSys) 563 return err 564 }