storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/config-current.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016-2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "strings" 24 "sync" 25 26 "storj.io/minio/cmd/config" 27 "storj.io/minio/cmd/config/api" 28 "storj.io/minio/cmd/config/cache" 29 "storj.io/minio/cmd/config/compress" 30 "storj.io/minio/cmd/config/heal" 31 xldap "storj.io/minio/cmd/config/identity/ldap" 32 "storj.io/minio/cmd/config/identity/openid" 33 "storj.io/minio/cmd/config/notify" 34 "storj.io/minio/cmd/config/policy/opa" 35 "storj.io/minio/cmd/config/scanner" 36 "storj.io/minio/cmd/config/storageclass" 37 "storj.io/minio/cmd/crypto" 38 xhttp "storj.io/minio/cmd/http" 39 "storj.io/minio/cmd/logger" 40 "storj.io/minio/cmd/logger/target/http" 41 "storj.io/minio/pkg/env" 42 "storj.io/minio/pkg/kms" 43 "storj.io/minio/pkg/madmin" 44 ) 45 46 func initHelp() { 47 var kvs = map[string]config.KVS{ 48 config.CacheSubSys: cache.DefaultKVS, 49 config.CompressionSubSys: compress.DefaultKVS, 50 config.IdentityLDAPSubSys: xldap.DefaultKVS, 51 config.IdentityOpenIDSubSys: openid.DefaultKVS, 52 config.PolicyOPASubSys: opa.DefaultKVS, 53 config.RegionSubSys: config.DefaultRegionKVS, 54 config.APISubSys: api.DefaultKVS, 55 config.CredentialsSubSys: config.DefaultCredentialKVS, 56 config.LoggerWebhookSubSys: logger.DefaultKVS, 57 config.AuditWebhookSubSys: logger.DefaultAuditKVS, 58 config.HealSubSys: heal.DefaultKVS, 59 config.ScannerSubSys: scanner.DefaultKVS, 60 } 61 for k, v := range notify.DefaultNotificationKVS { 62 kvs[k] = v 63 } 64 if globalIsErasure { 65 kvs[config.StorageClassSubSys] = storageclass.DefaultKVS 66 } 67 config.RegisterDefaultKVS(kvs) 68 69 // Captures help for each sub-system 70 var helpSubSys = config.HelpKVS{ 71 config.HelpKV{ 72 Key: config.RegionSubSys, 73 Description: "label the location of the server", 74 }, 75 config.HelpKV{ 76 Key: config.CacheSubSys, 77 Description: "add caching storage tier", 78 }, 79 config.HelpKV{ 80 Key: config.CompressionSubSys, 81 Description: "enable server side compression of objects", 82 }, 83 config.HelpKV{ 84 Key: config.IdentityOpenIDSubSys, 85 Description: "enable OpenID SSO support", 86 }, 87 config.HelpKV{ 88 Key: config.IdentityLDAPSubSys, 89 Description: "enable LDAP SSO support", 90 }, 91 config.HelpKV{ 92 Key: config.PolicyOPASubSys, 93 Description: "[DEPRECATED] enable external OPA for policy enforcement", 94 }, 95 config.HelpKV{ 96 Key: config.KmsVaultSubSys, 97 Description: "enable external HashiCorp Vault key management service", 98 }, 99 config.HelpKV{ 100 Key: config.KmsKesSubSys, 101 Description: "enable external MinIO key encryption service", 102 }, 103 config.HelpKV{ 104 Key: config.APISubSys, 105 Description: "manage global HTTP API call specific features, such as throttling, authentication types, etc.", 106 }, 107 config.HelpKV{ 108 Key: config.HealSubSys, 109 Description: "manage object healing frequency and bitrot verification checks", 110 }, 111 config.HelpKV{ 112 Key: config.ScannerSubSys, 113 Description: "manage namespace scanning for usage calculation, lifecycle, healing and more", 114 }, 115 config.HelpKV{ 116 Key: config.LoggerWebhookSubSys, 117 Description: "send server logs to webhook endpoints", 118 MultipleTargets: true, 119 }, 120 config.HelpKV{ 121 Key: config.AuditWebhookSubSys, 122 Description: "send audit logs to webhook endpoints", 123 MultipleTargets: true, 124 }, 125 config.HelpKV{ 126 Key: config.NotifyWebhookSubSys, 127 Description: "publish bucket notifications to webhook endpoints", 128 MultipleTargets: true, 129 }, 130 config.HelpKV{ 131 Key: config.NotifyAMQPSubSys, 132 Description: "publish bucket notifications to AMQP endpoints", 133 MultipleTargets: true, 134 }, 135 config.HelpKV{ 136 Key: config.NotifyKafkaSubSys, 137 Description: "publish bucket notifications to Kafka endpoints", 138 MultipleTargets: true, 139 }, 140 config.HelpKV{ 141 Key: config.NotifyMQTTSubSys, 142 Description: "publish bucket notifications to MQTT endpoints", 143 MultipleTargets: true, 144 }, 145 config.HelpKV{ 146 Key: config.NotifyNATSSubSys, 147 Description: "publish bucket notifications to NATS endpoints", 148 MultipleTargets: true, 149 }, 150 config.HelpKV{ 151 Key: config.NotifyNSQSubSys, 152 Description: "publish bucket notifications to NSQ endpoints", 153 MultipleTargets: true, 154 }, 155 config.HelpKV{ 156 Key: config.NotifyMySQLSubSys, 157 Description: "publish bucket notifications to MySQL databases", 158 MultipleTargets: true, 159 }, 160 config.HelpKV{ 161 Key: config.NotifyPostgresSubSys, 162 Description: "publish bucket notifications to Postgres databases", 163 MultipleTargets: true, 164 }, 165 config.HelpKV{ 166 Key: config.NotifyESSubSys, 167 Description: "publish bucket notifications to Elasticsearch endpoints", 168 MultipleTargets: true, 169 }, 170 config.HelpKV{ 171 Key: config.NotifyRedisSubSys, 172 Description: "publish bucket notifications to Redis datastores", 173 MultipleTargets: true, 174 }, 175 } 176 177 if globalIsErasure { 178 helpSubSys = append(helpSubSys, config.HelpKV{}) 179 copy(helpSubSys[2:], helpSubSys[1:]) 180 helpSubSys[1] = config.HelpKV{ 181 Key: config.StorageClassSubSys, 182 Description: "define object level redundancy", 183 } 184 } 185 186 var helpMap = map[string]config.HelpKVS{ 187 "": helpSubSys, // Help for all sub-systems. 188 config.RegionSubSys: config.RegionHelp, 189 config.APISubSys: api.Help, 190 config.StorageClassSubSys: storageclass.Help, 191 config.CacheSubSys: cache.Help, 192 config.CompressionSubSys: compress.Help, 193 config.HealSubSys: heal.Help, 194 config.ScannerSubSys: scanner.Help, 195 config.IdentityOpenIDSubSys: openid.Help, 196 config.IdentityLDAPSubSys: xldap.Help, 197 config.PolicyOPASubSys: opa.Help, 198 config.LoggerWebhookSubSys: logger.Help, 199 config.AuditWebhookSubSys: logger.HelpAudit, 200 config.NotifyAMQPSubSys: notify.HelpAMQP, 201 config.NotifyKafkaSubSys: notify.HelpKafka, 202 config.NotifyMQTTSubSys: notify.HelpMQTT, 203 config.NotifyNATSSubSys: notify.HelpNATS, 204 config.NotifyNSQSubSys: notify.HelpNSQ, 205 config.NotifyMySQLSubSys: notify.HelpMySQL, 206 config.NotifyPostgresSubSys: notify.HelpPostgres, 207 config.NotifyRedisSubSys: notify.HelpRedis, 208 config.NotifyWebhookSubSys: notify.HelpWebhook, 209 config.NotifyESSubSys: notify.HelpES, 210 } 211 212 config.RegisterHelpSubSys(helpMap) 213 } 214 215 var ( 216 // globalServerConfig server config. 217 globalServerConfig config.Config 218 globalServerConfigMu sync.RWMutex 219 ) 220 221 func validateConfig(s config.Config, setDriveCounts []int) error { 222 // We must have a global lock for this so nobody else modifies env while we do. 223 defer env.LockSetEnv()() 224 225 // Disable merging env values with config for validation. 226 env.SetEnvOff() 227 228 // Enable env values to validate KMS. 229 defer env.SetEnvOn() 230 231 if _, err := config.LookupCreds(s[config.CredentialsSubSys][config.Default]); err != nil { 232 return err 233 } 234 235 if _, err := config.LookupRegion(s[config.RegionSubSys][config.Default]); err != nil { 236 return err 237 } 238 239 if _, err := api.LookupConfig(s[config.APISubSys][config.Default]); err != nil { 240 return err 241 } 242 243 if globalIsErasure { 244 for _, setDriveCount := range setDriveCounts { 245 if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount); err != nil { 246 return err 247 } 248 } 249 } 250 251 if _, err := cache.LookupConfig(s[config.CacheSubSys][config.Default]); err != nil { 252 return err 253 } 254 255 compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]) 256 if err != nil { 257 return err 258 } 259 objAPI := newObjectLayerFn() 260 if objAPI != nil { 261 if compCfg.Enabled && !objAPI.IsCompressionSupported() { 262 return fmt.Errorf("Backend does not support compression") 263 } 264 } 265 266 if _, err = heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil { 267 return err 268 } 269 270 if _, err = scanner.LookupConfig(s[config.ScannerSubSys][config.Default]); err != nil { 271 return err 272 } 273 274 if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], 275 NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil { 276 return err 277 } 278 279 { 280 cfg, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default], 281 globalRootCAs) 282 if err != nil { 283 return err 284 } 285 if cfg.Enabled { 286 conn, cerr := cfg.Connect() 287 if cerr != nil { 288 return cerr 289 } 290 conn.Close() 291 } 292 } 293 294 if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default], 295 NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil { 296 return err 297 } 298 299 if _, err := logger.LookupConfig(s); err != nil { 300 return err 301 } 302 303 return notify.TestNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), GlobalNotificationSys.ConfiguredTargetIDs()) 304 } 305 306 func lookupConfigs(s config.Config, setDriveCounts []int) { 307 ctx := GlobalContext 308 309 var err error 310 if !globalActiveCred.IsValid() { 311 // Env doesn't seem to be set, we fallback to lookup creds from the config. 312 globalActiveCred, err = config.LookupCreds(s[config.CredentialsSubSys][config.Default]) 313 if err != nil { 314 logger.LogIf(ctx, fmt.Errorf("Invalid credentials configuration: %w", err)) 315 } 316 } 317 318 globalServerRegion, err = config.LookupRegion(s[config.RegionSubSys][config.Default]) 319 if err != nil { 320 logger.LogIf(ctx, fmt.Errorf("Invalid region configuration: %w", err)) 321 } 322 323 apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default]) 324 if err != nil { 325 logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err)) 326 } 327 328 globalAPIConfig.init(apiConfig, setDriveCounts) 329 330 // Initialize remote instance transport once. 331 getRemoteInstanceTransportOnce.Do(func() { 332 getRemoteInstanceTransport = newGatewayHTTPTransport(apiConfig.RemoteTransportDeadline) 333 }) 334 335 if globalIsErasure { 336 for i, setDriveCount := range setDriveCounts { 337 sc, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], setDriveCount) 338 if err != nil { 339 logger.LogIf(ctx, fmt.Errorf("Unable to initialize storage class config: %w", err)) 340 break 341 } 342 // if we validated all setDriveCounts and it was successful 343 // proceed to store the correct storage class globally. 344 if i == len(setDriveCounts)-1 { 345 globalStorageClass.Update(sc) 346 } 347 } 348 } 349 350 globalCacheConfig, err = cache.LookupConfig(s[config.CacheSubSys][config.Default]) 351 if err != nil { 352 if GlobalIsGateway { 353 logger.FatalIf(err, "Unable to setup cache") 354 } else { 355 logger.LogIf(ctx, fmt.Errorf("Unable to setup cache: %w", err)) 356 } 357 } 358 359 if globalCacheConfig.Enabled { 360 if cacheEncKey := env.Get(cache.EnvCacheEncryptionKey, ""); cacheEncKey != "" { 361 globalCacheKMS, err = kms.Parse(cacheEncKey) 362 if err != nil { 363 logger.LogIf(ctx, fmt.Errorf("Unable to setup encryption cache: %w", err)) 364 } 365 } 366 } 367 368 globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled 369 if globalAutoEncryption && GlobalKMS == nil { 370 logger.Fatal(errors.New("no KMS configured"), "MINIO_KMS_AUTO_ENCRYPTION requires a valid KMS configuration") 371 } 372 373 globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], 374 NewGatewayHTTPTransport(), xhttp.DrainBody) 375 if err != nil { 376 logger.LogIf(ctx, fmt.Errorf("Unable to initialize OpenID: %w", err)) 377 } 378 379 opaCfg, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default], 380 NewGatewayHTTPTransport(), xhttp.DrainBody) 381 if err != nil { 382 logger.LogIf(ctx, fmt.Errorf("Unable to initialize OPA: %w", err)) 383 } 384 385 globalOpenIDValidators = getOpenIDValidators(globalOpenIDConfig) 386 GlobalPolicyOPA = opa.New(opaCfg) 387 388 globalLDAPConfig, err = xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default], 389 globalRootCAs) 390 if err != nil { 391 logger.LogIf(ctx, fmt.Errorf("Unable to parse LDAP configuration: %w", err)) 392 } 393 394 // Load logger targets based on user's configuration 395 loggerUserAgent := getUserAgent(getMinioMode()) 396 397 loggerCfg, err := logger.LookupConfig(s) 398 if err != nil { 399 logger.LogIf(ctx, fmt.Errorf("Unable to initialize logger: %w", err)) 400 } 401 402 for k, l := range loggerCfg.HTTP { 403 if l.Enabled { 404 // Enable http logging 405 if err = logger.AddTarget( 406 http.New( 407 http.WithTargetName(k), 408 http.WithEndpoint(l.Endpoint), 409 http.WithAuthToken(l.AuthToken), 410 http.WithUserAgent(loggerUserAgent), 411 http.WithLogKind(string(logger.All)), 412 http.WithTransport(NewGatewayHTTPTransport()), 413 ), 414 ); err != nil { 415 logger.LogIf(ctx, fmt.Errorf("Unable to initialize console HTTP target: %w", err)) 416 } 417 } 418 } 419 420 for k, l := range loggerCfg.Audit { 421 if l.Enabled { 422 // Enable http audit logging 423 if err = logger.AddAuditTarget( 424 http.New( 425 http.WithTargetName(k), 426 http.WithEndpoint(l.Endpoint), 427 http.WithAuthToken(l.AuthToken), 428 http.WithUserAgent(loggerUserAgent), 429 http.WithLogKind(string(logger.All)), 430 http.WithTransport(NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)), 431 ), 432 ); err != nil { 433 logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit HTTP target: %w", err)) 434 } 435 } 436 } 437 438 globalConfigTargetList, err = notify.GetNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), false) 439 if err != nil { 440 logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err)) 441 } 442 443 globalEnvTargetList, err = notify.GetNotificationTargets(GlobalContext, newServerConfig(), NewGatewayHTTPTransport(), true) 444 if err != nil { 445 logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err)) 446 } 447 448 // Apply dynamic config values 449 logger.LogIf(ctx, applyDynamicConfig(ctx, newObjectLayerFn(), s)) 450 } 451 452 // applyDynamicConfig will apply dynamic config values. 453 // Dynamic systems should be in config.SubSystemsDynamic as well. 454 func applyDynamicConfig(ctx context.Context, objAPI ObjectLayer, s config.Config) error { 455 if objAPI == nil { 456 return nil 457 } 458 459 // Read all dynamic configs. 460 // API 461 apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default]) 462 if err != nil { 463 logger.LogIf(ctx, fmt.Errorf("Invalid api configuration: %w", err)) 464 } 465 466 // Compression 467 cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]) 468 if err != nil { 469 return fmt.Errorf("Unable to setup Compression: %w", err) 470 } 471 472 // Validate if the object layer supports compression. 473 if cmpCfg.Enabled && !objAPI.IsCompressionSupported() { 474 return fmt.Errorf("Backend does not support compression") 475 } 476 477 // Heal 478 healCfg, err := heal.LookupConfig(s[config.HealSubSys][config.Default]) 479 if err != nil { 480 return fmt.Errorf("Unable to apply heal config: %w", err) 481 } 482 483 // Scanner 484 scannerCfg, err := scanner.LookupConfig(s[config.ScannerSubSys][config.Default]) 485 if err != nil { 486 return fmt.Errorf("Unable to apply scanner config: %w", err) 487 } 488 489 // Apply configurations. 490 // We should not fail after this. 491 globalAPIConfig.init(apiConfig, objAPI.SetDriveCounts()) 492 493 globalCompressConfigMu.Lock() 494 globalCompressConfig = cmpCfg 495 globalCompressConfigMu.Unlock() 496 497 globalHealConfigMu.Lock() 498 globalHealConfig = healCfg 499 globalHealConfigMu.Unlock() 500 501 // update dynamic scanner values. 502 scannerCycle.Update(scannerCfg.Cycle) 503 logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait)) 504 505 // Update all dynamic config values in memory. 506 globalServerConfigMu.Lock() 507 defer globalServerConfigMu.Unlock() 508 if globalServerConfig != nil { 509 for k := range config.SubSystemsDynamic { 510 globalServerConfig[k] = s[k] 511 } 512 } 513 return nil 514 } 515 516 // Help - return sub-system level help 517 type Help struct { 518 SubSys string `json:"subSys"` 519 Description string `json:"description"` 520 MultipleTargets bool `json:"multipleTargets"` 521 KeysHelp config.HelpKVS `json:"keysHelp"` 522 } 523 524 // GetHelp - returns help for sub-sys, a key for a sub-system or all the help. 525 func GetHelp(subSys, key string, envOnly bool) (Help, error) { 526 if len(subSys) == 0 { 527 return Help{KeysHelp: config.HelpSubSysMap[subSys]}, nil 528 } 529 subSystemValue := strings.SplitN(subSys, config.SubSystemSeparator, 2) 530 if len(subSystemValue) == 0 { 531 return Help{}, config.Errorf("invalid number of arguments %s", subSys) 532 } 533 534 subSys = subSystemValue[0] 535 536 subSysHelp, ok := config.HelpSubSysMap[""].Lookup(subSys) 537 if !ok { 538 return Help{}, config.Errorf("unknown sub-system %s", subSys) 539 } 540 541 h, ok := config.HelpSubSysMap[subSys] 542 if !ok { 543 return Help{}, config.Errorf("unknown sub-system %s", subSys) 544 } 545 if key != "" { 546 value, ok := h.Lookup(key) 547 if !ok { 548 return Help{}, config.Errorf("unknown key %s for sub-system %s", 549 key, subSys) 550 } 551 h = config.HelpKVS{value} 552 } 553 554 envHelp := config.HelpKVS{} 555 if envOnly { 556 // Only for multiple targets, make sure 557 // to list the ENV, for regular k/v EnableKey is 558 // implicit, for ENVs we cannot make it implicit. 559 if subSysHelp.MultipleTargets { 560 envK := config.EnvPrefix + strings.Join([]string{ 561 strings.ToTitle(subSys), strings.ToTitle(madmin.EnableKey), 562 }, config.EnvWordDelimiter) 563 envHelp = append(envHelp, config.HelpKV{ 564 Key: envK, 565 Description: fmt.Sprintf("enable %s target, default is 'off'", subSys), 566 Optional: false, 567 Type: "on|off", 568 }) 569 } 570 for _, hkv := range h { 571 envK := config.EnvPrefix + strings.Join([]string{ 572 strings.ToTitle(subSys), strings.ToTitle(hkv.Key), 573 }, config.EnvWordDelimiter) 574 envHelp = append(envHelp, config.HelpKV{ 575 Key: envK, 576 Description: hkv.Description, 577 Optional: hkv.Optional, 578 Type: hkv.Type, 579 }) 580 } 581 h = envHelp 582 } 583 584 return Help{ 585 SubSys: subSys, 586 Description: subSysHelp.Description, 587 MultipleTargets: subSysHelp.MultipleTargets, 588 KeysHelp: h, 589 }, nil 590 } 591 592 func newServerConfig() config.Config { 593 return config.New() 594 } 595 596 // newSrvConfig - initialize a new server config, saves env parameters if 597 // found, otherwise use default parameters 598 func newSrvConfig(objAPI ObjectLayer) error { 599 // Initialize server config. 600 srvCfg := newServerConfig() 601 602 // hold the mutex lock before a new config is assigned. 603 globalServerConfigMu.Lock() 604 globalServerConfig = srvCfg 605 globalServerConfigMu.Unlock() 606 607 // Save config into file. 608 return saveServerConfig(GlobalContext, objAPI, globalServerConfig) 609 } 610 611 func getValidConfig(objAPI ObjectLayer) (config.Config, error) { 612 return readServerConfig(GlobalContext, objAPI) 613 } 614 615 // loadConfig - loads a new config from disk, overrides params 616 // from env if found and valid 617 func loadConfig(objAPI ObjectLayer) error { 618 srvCfg, err := getValidConfig(objAPI) 619 if err != nil { 620 return err 621 } 622 623 // Override any values from ENVs. 624 lookupConfigs(srvCfg, objAPI.SetDriveCounts()) 625 626 // hold the mutex lock before a new config is assigned. 627 globalServerConfigMu.Lock() 628 globalServerConfig = srvCfg 629 globalServerConfigMu.Unlock() 630 631 return nil 632 } 633 634 // getOpenIDValidators - returns ValidatorList which contains 635 // enabled providers in server config. 636 // A new authentication provider is added like below 637 // * Add a new provider in pkg/iam/openid package. 638 func getOpenIDValidators(cfg openid.Config) *openid.Validators { 639 validators := openid.NewValidators() 640 641 if cfg.JWKS.URL != nil { 642 validators.Add(openid.NewJWT(cfg)) 643 } 644 645 return validators 646 }