// Copyright (c) 2015-2021 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package cmd import ( "context" "errors" "fmt" "io/ioutil" "log" "net/url" "os" "os/signal" "strings" "syscall" "github.com/gorilla/mux" "github.com/minio/cli" "github.com/minio/madmin-go" "github.com/minio/minio/internal/color" "github.com/minio/minio/internal/config" xhttp "github.com/minio/minio/internal/http" "github.com/minio/minio/internal/logger" "github.com/minio/pkg/certs" "github.com/minio/pkg/env" ) var gatewayCmd = cli.Command{ Name: "gateway", Usage: "start object storage gateway", Flags: append(ServerFlags, GlobalFlags...), HideHelpCommand: true, } // GatewayLocker implements custom NewNSLock implementation type GatewayLocker struct { ObjectLayer nsMutex *nsLockMap } // NewNSLock - implements gateway level locker func (l *GatewayLocker) NewNSLock(bucket string, objects ...string) RWLocker { return l.nsMutex.NewNSLock(nil, bucket, objects...) } // Walk - implements common gateway level Walker, to walk on all objects recursively at a prefix func (l *GatewayLocker) Walk(ctx context.Context, bucket, prefix string, results chan<- ObjectInfo, opts ObjectOptions) error { walk := func(ctx context.Context, bucket, prefix string, results chan<- ObjectInfo) error { go func() { // Make sure the results channel is ready to be read when we're done. defer close(results) var marker string for { // set maxKeys to '0' to list maximum possible objects in single call. loi, err := l.ObjectLayer.ListObjects(ctx, bucket, prefix, marker, "", 0) if err != nil { logger.LogIf(ctx, err) return } marker = loi.NextMarker for _, obj := range loi.Objects { select { case results <- obj: case <-ctx.Done(): return } } if !loi.IsTruncated { break } } }() return nil } if err := l.ObjectLayer.Walk(ctx, bucket, prefix, results, opts); err != nil { if _, ok := err.(NotImplemented); ok { return walk(ctx, bucket, prefix, results) } return err } return nil } // NewGatewayLayerWithLocker - initialize gateway with locker. func NewGatewayLayerWithLocker(gwLayer ObjectLayer) ObjectLayer { return &GatewayLocker{ObjectLayer: gwLayer, nsMutex: newNSLock(false)} } // RegisterGatewayCommand registers a new command for gateway. func RegisterGatewayCommand(cmd cli.Command) error { cmd.Flags = append(append(cmd.Flags, ServerFlags...), GlobalFlags...) gatewayCmd.Subcommands = append(gatewayCmd.Subcommands, cmd) return nil } // ParseGatewayEndpoint - Return endpoint. func ParseGatewayEndpoint(arg string) (endPoint string, secure bool, err error) { schemeSpecified := len(strings.Split(arg, "://")) > 1 if !schemeSpecified { // Default connection will be "secure". arg = "https://" + arg } u, err := url.Parse(arg) if err != nil { return "", false, err } switch u.Scheme { case "http": return u.Host, false, nil case "https": return u.Host, true, nil default: return "", false, fmt.Errorf("Unrecognized scheme %s", u.Scheme) } } // ValidateGatewayArguments - Validate gateway arguments. func ValidateGatewayArguments(serverAddr, endpointAddr string) error { if err := CheckLocalServerAddr(serverAddr); err != nil { return err } if endpointAddr != "" { // Reject the endpoint if it points to the gateway handler itself. sameTarget, err := sameLocalAddrs(endpointAddr, serverAddr) if err != nil { return err } if sameTarget { return fmt.Errorf("endpoint points to the local gateway") } } return nil } // StartGateway - handler for 'minio gateway '. func StartGateway(ctx *cli.Context, gw Gateway) { signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go handleSignals() signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) // This is only to uniquely identify each gateway deployments. globalDeploymentID = env.Get("MINIO_GATEWAY_DEPLOYMENT_ID", mustGetUUID()) xhttp.SetDeploymentID(globalDeploymentID) if gw == nil { logger.FatalIf(errUnexpected, "Gateway implementation not initialized") } // Validate if we have access, secret set through environment. globalGatewayName = gw.Name() gatewayName := gw.Name() if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, gatewayName, 1) } // Initialize globalConsoleSys system globalConsoleSys = NewConsoleLogger(GlobalContext) logger.AddSystemTarget(globalConsoleSys) // Handle common command args. handleCommonCmdArgs(ctx) // Check and load TLS certificates. var err error globalPublicCerts, globalTLSCerts, globalIsTLS, err = getTLSConfig() logger.FatalIf(err, "Invalid TLS certificate file") // Check and load Root CAs. globalRootCAs, err = certs.GetRootCAs(globalCertsCADir.Get()) logger.FatalIf(err, "Failed to read root CAs (%v)", err) // Add the global public crts as part of global root CAs for _, publicCrt := range globalPublicCerts { globalRootCAs.AddCert(publicCrt) } // Register root CAs for remote ENVs env.RegisterGlobalCAs(globalRootCAs) // Initialize all help initHelp() // On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back // to IPv6 address ie minio will start listening on IPv6 address whereas another // (non-)minio process is listening on IPv4 of given port. // To avoid this error situation we check for port availability. logger.FatalIf(checkPortAvailability(globalMinioHost, globalMinioPort), "Unable to start the gateway") // Handle gateway specific env gatewayHandleEnvVars() // Initialize KMS configuration handleKMSConfig() // Set system resources to maximum. setMaxResources() // Set when gateway is enabled globalIsGateway = true // TODO: We need to move this code with globalConfigSys.Init() // for now keep it here such that "s3" gateway layer initializes // itself properly when KMS is set. // Initialize server config. srvCfg := newServerConfig() // Override any values from ENVs. lookupConfigs(srvCfg, nil) // hold the mutex lock before a new config is assigned. globalServerConfigMu.Lock() globalServerConfig = srvCfg globalServerConfigMu.Unlock() // Initialize router. `SkipClean(true)` stops gorilla/mux from // normalizing URL path minio/minio#3256 // avoid URL path encoding minio/minio#8950 router := mux.NewRouter().SkipClean(true).UseEncodedPath() // Enable STS router if etcd is enabled. registerSTSRouter(router) // Enable IAM admin APIs if etcd is enabled, if not just enable basic // operations such as profiling, server info etc. registerAdminRouter(router, false) // Add healthcheck router registerHealthCheckRouter(router) // Add server metrics router registerMetricsRouter(router) // Add API router. registerAPIRouter(router) // Enable bucket forwarding handler only if bucket federation is enabled. if globalDNSConfig != nil && globalBucketFederation { globalHandlers = append(globalHandlers, setBucketForwardingHandler) } // Use all the middlewares router.Use(globalHandlers...) var getCert certs.GetCertificateFunc if globalTLSCerts != nil { getCert = globalTLSCerts.GetCertificate } listeners := ctx.Int("listeners") if listeners == 0 { listeners = 1 } addrs := make([]string, 0, listeners) for i := 0; i < listeners; i++ { addrs = append(addrs, globalMinioAddr) } httpServer := xhttp.NewServer(addrs). UseHandler(setCriticalErrorHandler(corsHandler(router))). UseTLSConfig(newTLSConfig(getCert)). UseShutdownTimeout(ctx.Duration("shutdown-timeout")). UseBaseContext(GlobalContext). UseCustomLogger(log.New(ioutil.Discard, "", 0)) // Turn-off random logging by Go stdlib go func() { globalHTTPServerErrorCh <- httpServer.Start(GlobalContext) }() setHTTPServer(httpServer) newObject, err := gw.NewGatewayLayer(madmin.Credentials{ AccessKey: globalActiveCred.AccessKey, SecretKey: globalActiveCred.SecretKey, }) if err != nil { if errors.Is(err, errFreshDisk) { err = config.ErrInvalidFSValue(err) } logger.FatalIf(err, "Unable to initialize gateway backend") } newObject = NewGatewayLayerWithLocker(newObject) // Calls all New() for all sub-systems. initAllSubsystems() // Once endpoints are finalized, initialize the new object api in safe mode. globalObjLayerMutex.Lock() globalObjectAPI = newObject globalObjLayerMutex.Unlock() go globalIAMSys.Init(GlobalContext, newObject, globalEtcdClient, globalRefreshIAMInterval) if gatewayName == NASBackendGateway { buckets, err := newObject.ListBuckets(GlobalContext) if err != nil { logger.Fatal(err, "Unable to list buckets") } logger.FatalIf(globalBucketMetadataSys.Init(GlobalContext, buckets, newObject), "Unable to initialize bucket metadata") logger.FatalIf(globalNotificationSys.InitBucketTargets(GlobalContext, newObject), "Unable to initialize bucket targets for notification system") } if globalCacheConfig.Enabled { // initialize the new disk cache objects. var cacheAPI CacheObjectLayer cacheAPI, err = newServerCacheObjects(GlobalContext, globalCacheConfig) logger.FatalIf(err, "Unable to initialize disk caching") globalObjLayerMutex.Lock() globalCacheObjectAPI = cacheAPI globalObjLayerMutex.Unlock() } // Populate existing buckets to the etcd backend if globalDNSConfig != nil { buckets, err := newObject.ListBuckets(GlobalContext) if err != nil { logger.Fatal(err, "Unable to list buckets") } initFederatorBackend(buckets, newObject) } // Verify if object layer supports // - encryption // - compression verifyObjectLayerFeatures("gateway "+gatewayName, newObject) // Check for updates in non-blocking manner. go func() { if !globalCLIContext.Quiet && !globalInplaceUpdateDisabled { // Check for new updates from dl.min.io. checkUpdate(getMinioMode()) } }() if !globalCLIContext.Quiet { // Print gateway startup message. printGatewayStartupMessage(getAPIEndpoints(), gatewayName) } if globalBrowserEnabled { srv, err := initConsoleServer() if err != nil { logger.FatalIf(err, "Unable to initialize console service") } setConsoleSrv(srv) go func() { logger.FatalIf(newConsoleServerFn().Serve(), "Unable to initialize console server") }() } if serverDebugLog { logger.Info("== DEBUG Mode enabled ==") logger.Info("Currently set environment settings:") for _, v := range os.Environ() { logger.Info(v) } logger.Info("======") } // TODO: remove the following line by June 1st. logger.Info( color.RedBold(` =================================================================================== **** WARNING: MinIO Gateway will be removed by June 1st from MinIO repository ***** Please read https://github.com/minio/minio/issues/14331 =================================================================================== `)) <-globalOSSignalCh }