storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/common-main.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017-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 "crypto/tls" 22 "crypto/x509" 23 "encoding/gob" 24 "encoding/hex" 25 "errors" 26 "fmt" 27 "math/rand" 28 "net" 29 "net/url" 30 "os" 31 "path/filepath" 32 "runtime" 33 "sort" 34 "strings" 35 "time" 36 37 "github.com/fatih/color" 38 dns2 "github.com/miekg/dns" 39 "github.com/minio/cli" 40 "github.com/minio/minio-go/v7/pkg/set" 41 42 "storj.io/minio/cmd/config" 43 "storj.io/minio/cmd/crypto" 44 xhttp "storj.io/minio/cmd/http" 45 "storj.io/minio/cmd/logger" 46 "storj.io/minio/pkg/auth" 47 "storj.io/minio/pkg/certs" 48 "storj.io/minio/pkg/console" 49 "storj.io/minio/pkg/env" 50 "storj.io/minio/pkg/handlers" 51 "storj.io/minio/pkg/kms" 52 ) 53 54 // serverDebugLog will enable debug printing 55 var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn 56 57 func init() { 58 rand.Seed(time.Now().UTC().UnixNano()) 59 60 logger.Init(GOPATH, GOROOT) 61 logger.RegisterError(config.FmtError) 62 63 // Inject into config package. 64 config.Logger.Info = logger.Info 65 config.Logger.LogIf = logger.LogIf 66 67 if IsKubernetes() || IsDocker() || IsBOSH() || IsDCOS() || IsKubernetesReplicaSet() || IsPCFTile() { 68 // 30 seconds matches the orchestrator DNS TTLs, have 69 // a 5 second timeout to lookup from DNS servers. 70 globalDNSCache = xhttp.NewDNSCache(30*time.Second, 5*time.Second, logger.LogOnceIf) 71 } else { 72 // On bare-metals DNS do not change often, so it is 73 // safe to assume a higher timeout upto 10 minutes. 74 globalDNSCache = xhttp.NewDNSCache(10*time.Minute, 5*time.Second, logger.LogOnceIf) 75 } 76 77 initGlobalContext() 78 79 globalForwarder = handlers.NewForwarder(&handlers.Forwarder{ 80 PassHost: true, 81 RoundTripper: newGatewayHTTPTransport(1 * time.Hour), 82 Logger: func(err error) { 83 if err != nil && !errors.Is(err, context.Canceled) { 84 logger.LogIf(GlobalContext, err) 85 } 86 }, 87 }) 88 89 globalTransitionState = newTransitionState() 90 91 console.SetColor("Debug", color.New()) 92 93 gob.Register(StorageErr("")) 94 } 95 96 func verifyObjectLayerFeatures(name string, objAPI ObjectLayer) { 97 if (GlobalKMS != nil) && !objAPI.IsEncryptionSupported() { 98 logger.Fatal(errInvalidArgument, 99 "Encryption support is requested but '%s' does not support encryption", name) 100 } 101 102 if strings.HasPrefix(name, "gateway") { 103 if GlobalGatewaySSE.IsSet() && GlobalKMS == nil { 104 uiErr := config.ErrInvalidGWSSEEnvValue(nil).Msg("MINIO_GATEWAY_SSE set but KMS is not configured") 105 logger.Fatal(uiErr, "Unable to start gateway with SSE") 106 } 107 } 108 109 globalCompressConfigMu.Lock() 110 if globalCompressConfig.Enabled && !objAPI.IsCompressionSupported() { 111 logger.Fatal(errInvalidArgument, 112 "Compression support is requested but '%s' does not support compression", name) 113 } 114 globalCompressConfigMu.Unlock() 115 } 116 117 // Check for updates and print a notification message 118 func checkUpdate(mode string) { 119 updateURL := minioReleaseInfoURL 120 if runtime.GOOS == globalWindowsOSName { 121 updateURL = minioReleaseWindowsInfoURL 122 } 123 124 u, err := url.Parse(updateURL) 125 if err != nil { 126 return 127 } 128 129 // Its OK to ignore any errors during doUpdate() here. 130 crTime, err := GetCurrentReleaseTime() 131 if err != nil { 132 return 133 } 134 135 _, lrTime, err := getLatestReleaseTime(u, 2*time.Second, mode) 136 if err != nil { 137 return 138 } 139 140 var older time.Duration 141 var downloadURL string 142 if lrTime.After(crTime) { 143 older = lrTime.Sub(crTime) 144 downloadURL = getDownloadURL(releaseTimeToReleaseTag(lrTime)) 145 } 146 147 updateMsg := prepareUpdateMessage(downloadURL, older) 148 if updateMsg == "" { 149 return 150 } 151 152 logStartupMessage(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime))) 153 } 154 155 func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) { 156 var dir string 157 var dirSet bool 158 159 switch { 160 case ctx.IsSet(option): 161 dir = ctx.String(option) 162 dirSet = true 163 case ctx.GlobalIsSet(option): 164 dir = ctx.GlobalString(option) 165 dirSet = true 166 // cli package does not expose parent's option option. Below code is workaround. 167 if dir == "" || dir == getDefaultDir() { 168 dirSet = false // Unset to false since GlobalIsSet() true is a false positive. 169 if ctx.Parent().GlobalIsSet(option) { 170 dir = ctx.Parent().GlobalString(option) 171 dirSet = true 172 } 173 } 174 default: 175 // Neither local nor global option is provided. In this case, try to use 176 // default directory. 177 dir = getDefaultDir() 178 if dir == "" { 179 logger.FatalIf(errInvalidArgument, "%s option must be provided", option) 180 } 181 } 182 183 if dir == "" { 184 logger.FatalIf(errors.New("empty directory"), "%s directory cannot be empty", option) 185 } 186 187 // Disallow relative paths, figure out absolute paths. 188 dirAbs, err := filepath.Abs(dir) 189 logger.FatalIf(err, "Unable to fetch absolute path for %s=%s", option, dir) 190 191 logger.FatalIf(mkdirAllIgnorePerm(dirAbs), "Unable to create directory specified %s=%s", option, dir) 192 193 return &ConfigDir{path: dirAbs}, dirSet 194 } 195 196 func handleCommonCmdArgs(ctx *cli.Context) { 197 198 // Get "json" flag from command line argument and 199 // enable json and quite modes if json flag is turned on. 200 GlobalCLIContext.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json") 201 if GlobalCLIContext.JSON { 202 logger.EnableJSON() 203 } 204 205 // Get quiet flag from command line argument. 206 GlobalCLIContext.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet") 207 if GlobalCLIContext.Quiet { 208 logger.EnableQuiet() 209 } 210 211 // Get anonymous flag from command line argument. 212 GlobalCLIContext.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous") 213 if GlobalCLIContext.Anonymous { 214 logger.EnableAnonymous() 215 } 216 217 // Fetch address option 218 GlobalCLIContext.Addr = ctx.GlobalString("address") 219 if GlobalCLIContext.Addr == "" || GlobalCLIContext.Addr == ":"+GlobalMinioDefaultPort { 220 GlobalCLIContext.Addr = ctx.String("address") 221 } 222 223 // Check "no-compat" flag from command line argument. 224 GlobalCLIContext.StrictS3Compat = true 225 if ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat") { 226 GlobalCLIContext.StrictS3Compat = false 227 } 228 229 // Set all config, certs and CAs directories. 230 var configSet, certsSet bool 231 globalConfigDir, configSet = newConfigDirFromCtx(ctx, "config-dir", defaultConfigDir.Get) 232 globalCertsDir, certsSet = newConfigDirFromCtx(ctx, "certs-dir", defaultCertsDir.Get) 233 234 // Remove this code when we deprecate and remove config-dir. 235 // This code is to make sure we inherit from the config-dir 236 // option if certs-dir is not provided. 237 if !certsSet && configSet { 238 globalCertsDir = &ConfigDir{path: filepath.Join(globalConfigDir.Get(), certsDir)} 239 } 240 241 globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)} 242 243 logger.FatalIf(mkdirAllIgnorePerm(globalCertsCADir.Get()), "Unable to create certs CA directory at %s", globalCertsCADir.Get()) 244 } 245 246 func HandleCommonEnvVars() { 247 wormEnabled, err := config.LookupWorm() 248 if err != nil { 249 logger.Fatal(config.ErrInvalidWormValue(err), "Invalid worm configuration") 250 } 251 if wormEnabled { 252 logger.Fatal(errors.New("WORM is deprecated"), "global MINIO_WORM support is removed, please downgrade your server or migrate to https://storj.io/minio/tree/master/docs/retention") 253 } 254 255 globalBrowserEnabled, err = config.ParseBool(env.Get(config.EnvBrowser, config.EnableOn)) 256 if err != nil { 257 logger.Fatal(config.ErrInvalidBrowserValue(err), "Invalid MINIO_BROWSER value in environment variable") 258 } 259 260 globalFSOSync, err = config.ParseBool(env.Get(config.EnvFSOSync, config.EnableOff)) 261 if err != nil { 262 logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable") 263 } 264 265 domains := env.Get(config.EnvDomain, "") 266 if len(domains) != 0 { 267 for _, domainName := range strings.Split(domains, config.ValueSeparator) { 268 if _, ok := dns2.IsDomainName(domainName); !ok { 269 logger.Fatal(config.ErrInvalidDomainValue(nil).Msg("Unknown value `%s`", domainName), 270 "Invalid MINIO_DOMAIN value in environment variable") 271 } 272 globalDomainNames = append(globalDomainNames, domainName) 273 } 274 sort.Strings(globalDomainNames) 275 lcpSuf := lcpSuffix(globalDomainNames) 276 for _, domainName := range globalDomainNames { 277 if domainName == lcpSuf && len(globalDomainNames) > 1 { 278 logger.Fatal(config.ErrOverlappingDomainValue(nil).Msg("Overlapping domains `%s` not allowed", globalDomainNames), 279 "Invalid MINIO_DOMAIN value in environment variable") 280 } 281 } 282 } 283 284 publicIPs := env.Get(config.EnvPublicIPs, "") 285 if len(publicIPs) != 0 { 286 minioEndpoints := strings.Split(publicIPs, config.ValueSeparator) 287 var domainIPs = set.NewStringSet() 288 for _, endpoint := range minioEndpoints { 289 if net.ParseIP(endpoint) == nil { 290 // Checking if the IP is a DNS entry. 291 addrs, err := net.LookupHost(endpoint) 292 if err != nil { 293 logger.FatalIf(err, "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint) 294 } 295 for _, addr := range addrs { 296 domainIPs.Add(addr) 297 } 298 } 299 domainIPs.Add(endpoint) 300 } 301 updateDomainIPs(domainIPs) 302 } else { 303 // Add found interfaces IP address to global domain IPS, 304 // loopback addresses will be naturally dropped. 305 domainIPs := mustGetLocalIP4() 306 for _, host := range globalEndpoints.Hostnames() { 307 domainIPs.Add(host) 308 } 309 updateDomainIPs(domainIPs) 310 } 311 312 // In place update is true by default if the MINIO_UPDATE is not set 313 // or is not set to 'off', if MINIO_UPDATE is set to 'off' then 314 // in-place update is off. 315 globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.EnableOn), config.EnableOff) 316 317 if env.IsSet(config.EnvAccessKey) || env.IsSet(config.EnvSecretKey) { 318 cred, err := auth.CreateCredentials(env.Get(config.EnvAccessKey, ""), env.Get(config.EnvSecretKey, "")) 319 if err != nil { 320 logger.Fatal(config.ErrInvalidCredentials(err), 321 "Unable to validate credentials inherited from the shell environment") 322 } 323 globalActiveCred = cred 324 } 325 326 if env.IsSet(config.EnvRootUser) || env.IsSet(config.EnvRootPassword) { 327 cred, err := auth.CreateCredentials(env.Get(config.EnvRootUser, ""), env.Get(config.EnvRootPassword, "")) 328 if err != nil { 329 logger.Fatal(config.ErrInvalidCredentials(err), 330 "Unable to validate credentials inherited from the shell environment") 331 } 332 globalActiveCred = cred 333 } 334 335 if env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint) { 336 logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint)) 337 } 338 switch { 339 case env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint): 340 logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint)) 341 case env.IsSet(config.EnvKMSMasterKey) && env.IsSet(config.EnvKESEndpoint): 342 logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSMasterKey, config.EnvKESEndpoint)) 343 } 344 if env.IsSet(config.EnvKMSSecretKey) { 345 KMS, err := kms.Parse(env.Get(config.EnvKMSSecretKey, "")) 346 if err != nil { 347 logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") 348 } 349 GlobalKMS = KMS 350 } else if env.IsSet(config.EnvKMSMasterKey) { 351 logger.LogIf(GlobalContext, errors.New("legacy KMS configuration"), fmt.Sprintf("The environment variable %q is deprecated and will be removed in the future", config.EnvKMSMasterKey)) 352 353 v := strings.SplitN(env.Get(config.EnvKMSMasterKey, ""), ":", 2) 354 if len(v) != 2 { 355 logger.Fatal(errors.New("invalid "+config.EnvKMSMasterKey), "Unable to parse the KMS secret key inherited from the shell environment") 356 } 357 secretKey, err := hex.DecodeString(v[1]) 358 if err != nil { 359 logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") 360 } 361 KMS, err := kms.New(v[0], secretKey) 362 if err != nil { 363 logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") 364 } 365 GlobalKMS = KMS 366 } 367 if env.IsSet(config.EnvKESEndpoint) { 368 kesEndpoints, err := crypto.ParseKESEndpoints(env.Get(config.EnvKESEndpoint, "")) 369 if err != nil { 370 logger.Fatal(err, "Unable to parse the KES endpoints inherited from the shell environment") 371 } 372 KMS, err := crypto.NewKes(crypto.KesConfig{ 373 Enabled: true, 374 Endpoint: kesEndpoints, 375 DefaultKeyID: env.Get(config.EnvKESKeyName, ""), 376 CertFile: env.Get(config.EnvKESClientCert, ""), 377 KeyFile: env.Get(config.EnvKESClientKey, ""), 378 CAPath: env.Get(config.EnvKESServerCA, globalCertsCADir.Get()), 379 Transport: newCustomHTTPTransportWithHTTP2(&tls.Config{RootCAs: globalRootCAs}, defaultDialTimeout)(), 380 }) 381 if err != nil { 382 logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment") 383 } 384 GlobalKMS = KMS 385 } 386 } 387 388 func logStartupMessage(msg string) { 389 if globalConsoleSys != nil { 390 globalConsoleSys.Send(msg, string(logger.All)) 391 } 392 logger.StartupMessage(msg) 393 } 394 395 func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secureConn bool, err error) { 396 if !(isFile(getPublicCertFile()) && isFile(getPrivateKeyFile())) { 397 return nil, nil, false, nil 398 } 399 400 if x509Certs, err = config.ParsePublicCertFile(getPublicCertFile()); err != nil { 401 return nil, nil, false, err 402 } 403 404 manager, err = certs.NewManager(GlobalContext, getPublicCertFile(), getPrivateKeyFile(), config.LoadX509KeyPair) 405 if err != nil { 406 return nil, nil, false, err 407 } 408 409 // MinIO has support for multiple certificates. It expects the following structure: 410 // certs/ 411 // │ 412 // ├─ public.crt 413 // ├─ private.key 414 // │ 415 // ├─ example.com/ 416 // │ │ 417 // │ ├─ public.crt 418 // │ └─ private.key 419 // └─ foobar.org/ 420 // │ 421 // ├─ public.crt 422 // └─ private.key 423 // ... 424 // 425 // Therefore, we read all filenames in the cert directory and check 426 // for each directory whether it contains a public.crt and private.key. 427 // If so, we try to add it to certificate manager. 428 root, err := os.Open(globalCertsDir.Get()) 429 if err != nil { 430 return nil, nil, false, err 431 } 432 defer root.Close() 433 434 files, err := root.Readdir(-1) 435 if err != nil { 436 return nil, nil, false, err 437 } 438 for _, file := range files { 439 // Ignore all 440 // - regular files 441 // - "CAs" directory 442 // - any directory which starts with ".." 443 if file.Mode().IsRegular() || file.Name() == "CAs" || strings.HasPrefix(file.Name(), "..") { 444 continue 445 } 446 if file.Mode()&os.ModeSymlink == os.ModeSymlink { 447 file, err = os.Stat(filepath.Join(root.Name(), file.Name())) 448 if err != nil { 449 // not accessible ignore 450 continue 451 } 452 if !file.IsDir() { 453 continue 454 } 455 } 456 457 var ( 458 certFile = filepath.Join(root.Name(), file.Name(), publicCertFile) 459 keyFile = filepath.Join(root.Name(), file.Name(), privateKeyFile) 460 ) 461 if !isFile(certFile) || !isFile(keyFile) { 462 continue 463 } 464 if err = manager.AddCertificate(certFile, keyFile); err != nil { 465 err = fmt.Errorf("Unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err) 466 logger.LogIf(GlobalContext, err, logger.Minio) 467 } 468 } 469 secureConn = true 470 return x509Certs, manager, secureConn, nil 471 } 472 473 // contextCanceled returns whether a context is canceled. 474 func contextCanceled(ctx context.Context) bool { 475 select { 476 case <-ctx.Done(): 477 return true 478 default: 479 return false 480 } 481 }