github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/common-main.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 cmd
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"context"
    24  	"crypto/tls"
    25  	"crypto/x509"
    26  	"encoding/gob"
    27  	"encoding/pem"
    28  	"errors"
    29  	"fmt"
    30  	"net"
    31  	"net/url"
    32  	"os"
    33  	"path"
    34  	"path/filepath"
    35  	"runtime"
    36  	"sort"
    37  	"strconv"
    38  	"strings"
    39  	"syscall"
    40  	"time"
    41  
    42  	"github.com/dustin/go-humanize"
    43  	fcolor "github.com/fatih/color"
    44  	"github.com/go-openapi/loads"
    45  	"github.com/inconshreveable/mousetrap"
    46  	dns2 "github.com/miekg/dns"
    47  	"github.com/minio/cli"
    48  	consoleapi "github.com/minio/console/api"
    49  	"github.com/minio/console/api/operations"
    50  	consoleoauth2 "github.com/minio/console/pkg/auth/idp/oauth2"
    51  	consoleCerts "github.com/minio/console/pkg/certs"
    52  	"github.com/minio/kms-go/kes"
    53  	"github.com/minio/madmin-go/v3"
    54  	"github.com/minio/minio-go/v7"
    55  	"github.com/minio/minio-go/v7/pkg/set"
    56  	"github.com/minio/minio/internal/auth"
    57  	"github.com/minio/minio/internal/color"
    58  	"github.com/minio/minio/internal/config"
    59  	"github.com/minio/minio/internal/kms"
    60  	"github.com/minio/minio/internal/logger"
    61  	"github.com/minio/pkg/v2/certs"
    62  	"github.com/minio/pkg/v2/console"
    63  	"github.com/minio/pkg/v2/ellipses"
    64  	"github.com/minio/pkg/v2/env"
    65  	xnet "github.com/minio/pkg/v2/net"
    66  )
    67  
    68  // serverDebugLog will enable debug printing
    69  var (
    70  	serverDebugLog     = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
    71  	currentReleaseTime time.Time
    72  	orchestrated       = IsKubernetes() || IsDocker()
    73  )
    74  
    75  func init() {
    76  	if runtime.GOOS == "windows" {
    77  		if mousetrap.StartedByExplorer() {
    78  			fmt.Printf("Don't double-click %s\n", os.Args[0])
    79  			fmt.Println("You need to open cmd.exe/PowerShell and run it from the command line")
    80  			fmt.Println("Refer to the docs here on how to run it as a Windows Service https://github.com/minio/minio-service/tree/master/windows")
    81  			fmt.Println("Press the Enter Key to Exit")
    82  			fmt.Scanln()
    83  			os.Exit(1)
    84  		}
    85  	}
    86  
    87  	logger.Init(GOPATH, GOROOT)
    88  	logger.RegisterError(config.FmtError)
    89  
    90  	globalBatchJobsMetrics = batchJobMetrics{metrics: make(map[string]*batchJobInfo)}
    91  	go globalBatchJobsMetrics.purgeJobMetrics()
    92  
    93  	t, _ := minioVersionToReleaseTime(Version)
    94  	if !t.IsZero() {
    95  		globalVersionUnix = uint64(t.Unix())
    96  	}
    97  
    98  	globalIsCICD = env.Get("MINIO_CI_CD", "") != "" || env.Get("CI", "") != ""
    99  
   100  	console.SetColor("Debug", fcolor.New())
   101  
   102  	gob.Register(StorageErr(""))
   103  	gob.Register(madmin.TimeInfo{})
   104  	gob.Register(madmin.XFSErrorConfigs{})
   105  	gob.Register(map[string]string{})
   106  	gob.Register(map[string]interface{}{})
   107  
   108  	// All minio-go and madmin-go API operations shall be performed only once,
   109  	// another way to look at this is we are turning off retries.
   110  	minio.MaxRetry = 1
   111  	madmin.MaxRetry = 1
   112  
   113  	currentReleaseTime, _ = GetCurrentReleaseTime()
   114  }
   115  
   116  const consolePrefix = "CONSOLE_"
   117  
   118  func minioConfigToConsoleFeatures() {
   119  	os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID())
   120  	os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID())
   121  	if globalMinioEndpoint != "" {
   122  		os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
   123  	} else {
   124  		// Explicitly set 127.0.0.1 so Console will automatically bypass TLS verification to the local S3 API.
   125  		// This will save users from providing a certificate with IP or FQDN SAN that points to the local host.
   126  		os.Setenv("CONSOLE_MINIO_SERVER", fmt.Sprintf("%s://127.0.0.1:%s", getURLScheme(globalIsTLS), globalMinioPort))
   127  	}
   128  	if value := env.Get(config.EnvMinIOLogQueryURL, ""); value != "" {
   129  		os.Setenv("CONSOLE_LOG_QUERY_URL", value)
   130  		if value := env.Get(config.EnvMinIOLogQueryAuthToken, ""); value != "" {
   131  			os.Setenv("CONSOLE_LOG_QUERY_AUTH_TOKEN", value)
   132  		}
   133  	}
   134  	// pass the console subpath configuration
   135  	if globalBrowserRedirectURL != nil {
   136  		subPath := path.Clean(pathJoin(strings.TrimSpace(globalBrowserRedirectURL.Path), SlashSeparator))
   137  		if subPath != SlashSeparator {
   138  			os.Setenv("CONSOLE_SUBPATH", subPath)
   139  		}
   140  	}
   141  	// Enable if prometheus URL is set.
   142  	if value := env.Get(config.EnvMinIOPrometheusURL, ""); value != "" {
   143  		os.Setenv("CONSOLE_PROMETHEUS_URL", value)
   144  		if value := env.Get(config.EnvMinIOPrometheusJobID, "minio-job"); value != "" {
   145  			os.Setenv("CONSOLE_PROMETHEUS_JOB_ID", value)
   146  			// Support additional labels for more granular filtering.
   147  			if value := env.Get(config.EnvMinIOPrometheusExtraLabels, ""); value != "" {
   148  				os.Setenv("CONSOLE_PROMETHEUS_EXTRA_LABELS", value)
   149  			}
   150  		}
   151  		// Support Prometheus Auth Token
   152  		if value := env.Get(config.EnvMinIOPrometheusAuthToken, ""); value != "" {
   153  			os.Setenv("CONSOLE_PROMETHEUS_AUTH_TOKEN", value)
   154  		}
   155  	}
   156  	// Enable if LDAP is enabled.
   157  	if globalIAMSys.LDAPConfig.Enabled() {
   158  		os.Setenv("CONSOLE_LDAP_ENABLED", config.EnableOn)
   159  	}
   160  	// Handle animation in welcome page
   161  	if value := env.Get(config.EnvBrowserLoginAnimation, "on"); value != "" {
   162  		os.Setenv("CONSOLE_ANIMATED_LOGIN", value)
   163  	}
   164  
   165  	// Pass on the session duration environment variable, else we will default to 12 hours
   166  	if valueSts := env.Get(config.EnvMinioStsDuration, ""); valueSts != "" {
   167  		os.Setenv("CONSOLE_STS_DURATION", valueSts)
   168  	} else if valueSession := env.Get(config.EnvBrowserSessionDuration, ""); valueSession != "" {
   169  		os.Setenv("CONSOLE_STS_DURATION", valueSession)
   170  	}
   171  
   172  	os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
   173  	os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
   174  
   175  	// This section sets Browser (console) stored config
   176  	if valueSCP := globalBrowserConfig.GetCSPolicy(); valueSCP != "" {
   177  		os.Setenv("CONSOLE_SECURE_CONTENT_SECURITY_POLICY", valueSCP)
   178  	}
   179  
   180  	if hstsSeconds := globalBrowserConfig.GetHSTSSeconds(); hstsSeconds > 0 {
   181  		isubdom := globalBrowserConfig.IsHSTSIncludeSubdomains()
   182  		isprel := globalBrowserConfig.IsHSTSPreload()
   183  		os.Setenv("CONSOLE_SECURE_STS_SECONDS", strconv.Itoa(hstsSeconds))
   184  		os.Setenv("CONSOLE_SECURE_STS_INCLUDE_SUB_DOMAINS", isubdom)
   185  		os.Setenv("CONSOLE_SECURE_STS_PRELOAD", isprel)
   186  	}
   187  
   188  	if valueRefer := globalBrowserConfig.GetReferPolicy(); valueRefer != "" {
   189  		os.Setenv("CONSOLE_SECURE_REFERRER_POLICY", valueRefer)
   190  	}
   191  
   192  	globalSubnetConfig.ApplyEnv()
   193  }
   194  
   195  func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
   196  	pcfgs := globalIAMSys.OpenIDConfig.ProviderCfgs
   197  	m := make(map[string]consoleoauth2.ProviderConfig, len(pcfgs))
   198  	for name, cfg := range pcfgs {
   199  		callback := getConsoleEndpoints()[0] + "/oauth_callback"
   200  		if cfg.RedirectURI != "" {
   201  			callback = cfg.RedirectURI
   202  		}
   203  		m[name] = consoleoauth2.ProviderConfig{
   204  			URL:                     cfg.URL.String(),
   205  			DisplayName:             cfg.DisplayName,
   206  			ClientID:                cfg.ClientID,
   207  			ClientSecret:            cfg.ClientSecret,
   208  			HMACSalt:                globalDeploymentID(),
   209  			HMACPassphrase:          cfg.ClientID,
   210  			Scopes:                  strings.Join(cfg.DiscoveryDoc.ScopesSupported, ","),
   211  			Userinfo:                cfg.ClaimUserinfo,
   212  			RedirectCallbackDynamic: cfg.RedirectURIDynamic,
   213  			RedirectCallback:        callback,
   214  			EndSessionEndpoint:      cfg.DiscoveryDoc.EndSessionEndpoint,
   215  			RoleArn:                 cfg.GetRoleArn(),
   216  		}
   217  	}
   218  	return m
   219  }
   220  
   221  func initConsoleServer() (*consoleapi.Server, error) {
   222  	// unset all console_ environment variables.
   223  	for _, cenv := range env.List(consolePrefix) {
   224  		os.Unsetenv(cenv)
   225  	}
   226  
   227  	// enable all console environment variables
   228  	minioConfigToConsoleFeatures()
   229  
   230  	// set certs dir to minio directory
   231  	consoleCerts.GlobalCertsDir = &consoleCerts.ConfigDir{
   232  		Path: globalCertsDir.Get(),
   233  	}
   234  	consoleCerts.GlobalCertsCADir = &consoleCerts.ConfigDir{
   235  		Path: globalCertsCADir.Get(),
   236  	}
   237  
   238  	// set certs before other console initialization
   239  	consoleapi.GlobalRootCAs, consoleapi.GlobalPublicCerts, consoleapi.GlobalTLSCertsManager = globalRootCAs, globalPublicCerts, globalTLSCerts
   240  
   241  	swaggerSpec, err := loads.Embedded(consoleapi.SwaggerJSON, consoleapi.FlatSwaggerJSON)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	api := operations.NewConsoleAPI(swaggerSpec)
   247  
   248  	if !serverDebugLog {
   249  		// Disable console logging if server debug log is not enabled
   250  		noLog := func(string, ...interface{}) {}
   251  
   252  		consoleapi.LogInfo = noLog
   253  		consoleapi.LogError = noLog
   254  		api.Logger = noLog
   255  	}
   256  
   257  	// Pass in console application config. This needs to happen before the
   258  	// ConfigureAPI() call.
   259  	consoleapi.GlobalMinIOConfig = consoleapi.MinIOConfig{
   260  		OpenIDProviders: buildOpenIDConsoleConfig(),
   261  	}
   262  
   263  	server := consoleapi.NewServer(api)
   264  	// register all APIs
   265  	server.ConfigureAPI()
   266  
   267  	consolePort, _ := strconv.Atoi(globalMinioConsolePort)
   268  
   269  	server.Host = globalMinioConsoleHost
   270  	server.Port = consolePort
   271  	consoleapi.Port = globalMinioConsolePort
   272  	consoleapi.Hostname = globalMinioConsoleHost
   273  
   274  	if globalIsTLS {
   275  		// If TLS certificates are provided enforce the HTTPS.
   276  		server.EnabledListeners = []string{"https"}
   277  		server.TLSPort = consolePort
   278  		// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
   279  		consoleapi.TLSPort = globalMinioConsolePort
   280  		consoleapi.Hostname = globalMinioConsoleHost
   281  	}
   282  
   283  	return server, nil
   284  }
   285  
   286  // Check for updates and print a notification message
   287  func checkUpdate(mode string) {
   288  	updateURL := minioReleaseInfoURL
   289  	if runtime.GOOS == globalWindowsOSName {
   290  		updateURL = minioReleaseWindowsInfoURL
   291  	}
   292  
   293  	u, err := url.Parse(updateURL)
   294  	if err != nil {
   295  		return
   296  	}
   297  
   298  	if currentReleaseTime.IsZero() {
   299  		return
   300  	}
   301  
   302  	_, lrTime, err := getLatestReleaseTime(u, 2*time.Second, mode)
   303  	if err != nil {
   304  		return
   305  	}
   306  
   307  	var older time.Duration
   308  	var downloadURL string
   309  	if lrTime.After(currentReleaseTime) {
   310  		older = lrTime.Sub(currentReleaseTime)
   311  		downloadURL = getDownloadURL(releaseTimeToReleaseTag(lrTime))
   312  	}
   313  
   314  	updateMsg := prepareUpdateMessage(downloadURL, older)
   315  	if updateMsg == "" {
   316  		return
   317  	}
   318  
   319  	logger.Info(prepareUpdateMessage("Run `mc admin update ALIAS`", lrTime.Sub(currentReleaseTime)))
   320  }
   321  
   322  func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) {
   323  	if dir == "" {
   324  		dir = getDefaultDir()
   325  	}
   326  
   327  	if dir == "" {
   328  		if !dirSet {
   329  			return nil, fmt.Errorf("missing option must be provided")
   330  		}
   331  		return nil, fmt.Errorf("provided option cannot be empty")
   332  	}
   333  
   334  	// Disallow relative paths, figure out absolute paths.
   335  	dirAbs, err := filepath.Abs(dir)
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  	err = mkdirAllIgnorePerm(dirAbs)
   340  	if err != nil {
   341  		return nil, fmt.Errorf("unable to create the directory `%s`: %w", dirAbs, err)
   342  	}
   343  
   344  	return &ConfigDir{path: dirAbs}, nil
   345  }
   346  
   347  func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
   348  	// Get "json" flag from command line argument and
   349  	ctxt.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
   350  	// Get quiet flag from command line argument.
   351  	ctxt.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
   352  	// Get anonymous flag from command line argument.
   353  	ctxt.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous")
   354  	// Fetch address option
   355  	ctxt.Addr = ctx.GlobalString("address")
   356  	if ctxt.Addr == "" || ctxt.Addr == ":"+GlobalMinioDefaultPort {
   357  		ctxt.Addr = ctx.String("address")
   358  	}
   359  
   360  	// Fetch console address option
   361  	ctxt.ConsoleAddr = ctx.GlobalString("console-address")
   362  	if ctxt.ConsoleAddr == "" {
   363  		ctxt.ConsoleAddr = ctx.String("console-address")
   364  	}
   365  
   366  	if cxml := ctx.String("crossdomain-xml"); cxml != "" {
   367  		buf, err := os.ReadFile(cxml)
   368  		if err != nil {
   369  			return err
   370  		}
   371  		ctxt.CrossDomainXML = string(buf)
   372  	}
   373  
   374  	// Check "no-compat" flag from command line argument.
   375  	ctxt.StrictS3Compat = !(ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat"))
   376  
   377  	switch {
   378  	case ctx.IsSet("config-dir"):
   379  		ctxt.ConfigDir = ctx.String("config-dir")
   380  		ctxt.configDirSet = true
   381  	case ctx.GlobalIsSet("config-dir"):
   382  		ctxt.ConfigDir = ctx.GlobalString("config-dir")
   383  		ctxt.configDirSet = true
   384  	}
   385  
   386  	switch {
   387  	case ctx.IsSet("certs-dir"):
   388  		ctxt.CertsDir = ctx.String("certs-dir")
   389  		ctxt.certsDirSet = true
   390  	case ctx.GlobalIsSet("certs-dir"):
   391  		ctxt.CertsDir = ctx.GlobalString("certs-dir")
   392  		ctxt.certsDirSet = true
   393  	}
   394  
   395  	ctxt.FTP = ctx.StringSlice("ftp")
   396  	ctxt.SFTP = ctx.StringSlice("sftp")
   397  
   398  	ctxt.Interface = ctx.String("interface")
   399  	ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
   400  	ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
   401  	ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
   402  	ctxt.ConnClientReadDeadline = ctx.Duration("conn-client-read-deadline")
   403  	ctxt.ConnClientWriteDeadline = ctx.Duration("conn-client-write-deadline")
   404  
   405  	ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
   406  	ctxt.IdleTimeout = ctx.Duration("idle-timeout")
   407  	ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")
   408  	ctxt.MaxIdleConnsPerHost = ctx.Int("max-idle-conns-per-host")
   409  
   410  	if conf := ctx.String("config"); len(conf) > 0 {
   411  		err = mergeServerCtxtFromConfigFile(conf, ctxt)
   412  	} else {
   413  		err = mergeDisksLayoutFromArgs(serverCmdArgs(ctx), ctxt)
   414  	}
   415  
   416  	return err
   417  }
   418  
   419  func handleCommonArgs(ctxt serverCtxt) {
   420  	if ctxt.JSON {
   421  		logger.EnableJSON()
   422  	}
   423  	if ctxt.Quiet {
   424  		logger.EnableQuiet()
   425  	}
   426  	if ctxt.Anonymous {
   427  		logger.EnableAnonymous()
   428  	}
   429  
   430  	consoleAddr := ctxt.ConsoleAddr
   431  	addr := ctxt.Addr
   432  	configDir := ctxt.ConfigDir
   433  	configSet := ctxt.configDirSet
   434  	certsDir := ctxt.CertsDir
   435  	certsSet := ctxt.certsDirSet
   436  
   437  	if consoleAddr == "" {
   438  		p, err := xnet.GetFreePort()
   439  		if err != nil {
   440  			logger.FatalIf(err, "Unable to get free port for Console UI on the host")
   441  		}
   442  		// hold the port
   443  		l, err := net.Listen("TCP", fmt.Sprintf(":%s", p.String()))
   444  		if err == nil {
   445  			defer l.Close()
   446  		}
   447  		consoleAddr = net.JoinHostPort("", p.String())
   448  	}
   449  
   450  	if _, _, err := net.SplitHostPort(consoleAddr); err != nil {
   451  		logger.FatalIf(err, "Unable to start listening on console port")
   452  	}
   453  
   454  	if consoleAddr == addr {
   455  		logger.FatalIf(errors.New("--console-address cannot be same as --address"), "Unable to start the server")
   456  	}
   457  
   458  	globalMinioHost, globalMinioPort = mustSplitHostPort(addr)
   459  	if globalMinioPort == "0" {
   460  		p, err := xnet.GetFreePort()
   461  		if err != nil {
   462  			logger.FatalIf(err, "Unable to get free port for S3 API on the host")
   463  		}
   464  		globalMinioPort = p.String()
   465  		globalDynamicAPIPort = true
   466  	}
   467  
   468  	globalMinioConsoleHost, globalMinioConsolePort = mustSplitHostPort(consoleAddr)
   469  
   470  	if globalMinioPort == globalMinioConsolePort {
   471  		logger.FatalIf(errors.New("--console-address port cannot be same as --address port"), "Unable to start the server")
   472  	}
   473  
   474  	globalMinioAddr = addr
   475  
   476  	// Set all config, certs and CAs directories.
   477  	var err error
   478  	globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get)
   479  	logger.FatalIf(err, "Unable to initialize the (deprecated) config directory")
   480  	globalCertsDir, err = newConfigDir(certsDir, certsSet, defaultCertsDir.Get)
   481  	logger.FatalIf(err, "Unable to initialize the certs directory")
   482  
   483  	// Remove this code when we deprecate and remove config-dir.
   484  	// This code is to make sure we inherit from the config-dir
   485  	// option if certs-dir is not provided.
   486  	if !certsSet && configSet {
   487  		globalCertsDir = &ConfigDir{path: filepath.Join(globalConfigDir.Get(), certsDir)}
   488  	}
   489  
   490  	globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)}
   491  
   492  	logger.FatalIf(mkdirAllIgnorePerm(globalCertsCADir.Get()), "Unable to create certs CA directory at %s", globalCertsCADir.Get())
   493  }
   494  
   495  func runDNSCache(ctx *cli.Context) {
   496  	dnsTTL := ctx.Duration("dns-cache-ttl")
   497  	// Check if we have configured a custom DNS cache TTL.
   498  	if dnsTTL <= 0 {
   499  		if orchestrated {
   500  			dnsTTL = 30 * time.Second
   501  		} else {
   502  			dnsTTL = 10 * time.Minute
   503  		}
   504  	}
   505  
   506  	// Call to refresh will refresh names in cache.
   507  	go func() {
   508  		// Baremetal setups set DNS refresh window up to dnsTTL duration.
   509  		t := time.NewTicker(dnsTTL)
   510  		defer t.Stop()
   511  		for {
   512  			select {
   513  			case <-t.C:
   514  				globalDNSCache.Refresh()
   515  
   516  			case <-GlobalContext.Done():
   517  				return
   518  			}
   519  		}
   520  	}()
   521  }
   522  
   523  type envKV struct {
   524  	Key   string
   525  	Value string
   526  	Skip  bool
   527  }
   528  
   529  func (e envKV) String() string {
   530  	if e.Skip {
   531  		return ""
   532  	}
   533  	return fmt.Sprintf("%s=%s", e.Key, e.Value)
   534  }
   535  
   536  func parsEnvEntry(envEntry string) (envKV, error) {
   537  	envEntry = strings.TrimSpace(envEntry)
   538  	if envEntry == "" {
   539  		// Skip all empty lines
   540  		return envKV{
   541  			Skip: true,
   542  		}, nil
   543  	}
   544  	if strings.HasPrefix(envEntry, "#") {
   545  		// Skip commented lines
   546  		return envKV{
   547  			Skip: true,
   548  		}, nil
   549  	}
   550  	envTokens := strings.SplitN(strings.TrimSpace(strings.TrimPrefix(envEntry, "export")), config.EnvSeparator, 2)
   551  	if len(envTokens) != 2 {
   552  		return envKV{}, fmt.Errorf("envEntry malformed; %s, expected to be of form 'KEY=value'", envEntry)
   553  	}
   554  
   555  	key := envTokens[0]
   556  	val := envTokens[1]
   557  
   558  	// Remove quotes from the value if found
   559  	if len(val) >= 2 {
   560  		quote := val[0]
   561  		if (quote == '"' || quote == '\'') && val[len(val)-1] == quote {
   562  			val = val[1 : len(val)-1]
   563  		}
   564  	}
   565  
   566  	return envKV{
   567  		Key:   key,
   568  		Value: val,
   569  	}, nil
   570  }
   571  
   572  // Similar to os.Environ returns a copy of strings representing
   573  // the environment values from a file, in the form "key, value".
   574  // in a structured form.
   575  func minioEnvironFromFile(envConfigFile string) ([]envKV, error) {
   576  	f, err := Open(envConfigFile)
   577  	if err != nil {
   578  		return nil, err
   579  	}
   580  	defer f.Close()
   581  	var ekvs []envKV
   582  	scanner := bufio.NewScanner(f)
   583  	for scanner.Scan() {
   584  		ekv, err := parsEnvEntry(scanner.Text())
   585  		if err != nil {
   586  			return nil, err
   587  		}
   588  		if ekv.Skip {
   589  			// Skips empty lines
   590  			continue
   591  		}
   592  		ekvs = append(ekvs, ekv)
   593  	}
   594  	if err = scanner.Err(); err != nil {
   595  		return nil, err
   596  	}
   597  	return ekvs, nil
   598  }
   599  
   600  func readFromSecret(sp string) (string, error) {
   601  	// Supports reading path from docker secrets, filename is
   602  	// relative to /run/secrets/ position.
   603  	if isFile(pathJoin("/run/secrets/", sp)) {
   604  		sp = pathJoin("/run/secrets/", sp)
   605  	}
   606  	credBuf, err := os.ReadFile(sp)
   607  	if err != nil {
   608  		if os.IsNotExist(err) { // ignore if file doesn't exist.
   609  			return "", nil
   610  		}
   611  		return "", err
   612  	}
   613  	return string(bytes.TrimSpace(credBuf)), nil
   614  }
   615  
   616  func loadEnvVarsFromFiles() {
   617  	if env.IsSet(config.EnvAccessKeyFile) {
   618  		accessKey, err := readFromSecret(env.Get(config.EnvAccessKeyFile, ""))
   619  		if err != nil {
   620  			logger.Fatal(config.ErrInvalidCredentials(err),
   621  				"Unable to validate credentials inherited from the secret file(s)")
   622  		}
   623  		if accessKey != "" {
   624  			os.Setenv(config.EnvRootUser, accessKey)
   625  		}
   626  	}
   627  
   628  	if env.IsSet(config.EnvSecretKeyFile) {
   629  		secretKey, err := readFromSecret(env.Get(config.EnvSecretKeyFile, ""))
   630  		if err != nil {
   631  			logger.Fatal(config.ErrInvalidCredentials(err),
   632  				"Unable to validate credentials inherited from the secret file(s)")
   633  		}
   634  		if secretKey != "" {
   635  			os.Setenv(config.EnvRootPassword, secretKey)
   636  		}
   637  	}
   638  
   639  	if env.IsSet(config.EnvRootUserFile) {
   640  		rootUser, err := readFromSecret(env.Get(config.EnvRootUserFile, ""))
   641  		if err != nil {
   642  			logger.Fatal(config.ErrInvalidCredentials(err),
   643  				"Unable to validate credentials inherited from the secret file(s)")
   644  		}
   645  		if rootUser != "" {
   646  			os.Setenv(config.EnvRootUser, rootUser)
   647  		}
   648  	}
   649  
   650  	if env.IsSet(config.EnvRootPasswordFile) {
   651  		rootPassword, err := readFromSecret(env.Get(config.EnvRootPasswordFile, ""))
   652  		if err != nil {
   653  			logger.Fatal(config.ErrInvalidCredentials(err),
   654  				"Unable to validate credentials inherited from the secret file(s)")
   655  		}
   656  		if rootPassword != "" {
   657  			os.Setenv(config.EnvRootPassword, rootPassword)
   658  		}
   659  	}
   660  
   661  	if env.IsSet(kms.EnvKMSSecretKeyFile) {
   662  		kmsSecret, err := readFromSecret(env.Get(kms.EnvKMSSecretKeyFile, ""))
   663  		if err != nil {
   664  			logger.Fatal(err, "Unable to read the KMS secret key inherited from secret file")
   665  		}
   666  		if kmsSecret != "" {
   667  			os.Setenv(kms.EnvKMSSecretKey, kmsSecret)
   668  		}
   669  	}
   670  
   671  	if env.IsSet(config.EnvConfigEnvFile) {
   672  		ekvs, err := minioEnvironFromFile(env.Get(config.EnvConfigEnvFile, ""))
   673  		if err != nil && !os.IsNotExist(err) {
   674  			logger.Fatal(err, "Unable to read the config environment file")
   675  		}
   676  		for _, ekv := range ekvs {
   677  			os.Setenv(ekv.Key, ekv.Value)
   678  		}
   679  	}
   680  }
   681  
   682  func serverHandleEnvVars() {
   683  	var err error
   684  	globalBrowserEnabled, err = config.ParseBool(env.Get(config.EnvBrowser, config.EnableOn))
   685  	if err != nil {
   686  		logger.Fatal(config.ErrInvalidBrowserValue(err), "Invalid MINIO_BROWSER value in environment variable")
   687  	}
   688  	if globalBrowserEnabled {
   689  		if redirectURL := env.Get(config.EnvBrowserRedirectURL, ""); redirectURL != "" {
   690  			u, err := xnet.ParseHTTPURL(redirectURL)
   691  			if err != nil {
   692  				logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value in environment variable")
   693  			}
   694  			// Look for if URL has invalid values and return error.
   695  			if !((u.Scheme == "http" || u.Scheme == "https") &&
   696  				u.Opaque == "" &&
   697  				!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
   698  				err := fmt.Errorf("URL contains unexpected resources, expected URL to be one of http(s)://console.example.com or as a subpath via API endpoint http(s)://minio.example.com/minio format: %v", u)
   699  				logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
   700  			}
   701  			globalBrowserRedirectURL = u
   702  		}
   703  		globalBrowserRedirect = env.Get(config.EnvBrowserRedirect, config.EnableOn) == config.EnableOn
   704  	}
   705  
   706  	if serverURL := env.Get(config.EnvMinIOServerURL, ""); serverURL != "" {
   707  		u, err := xnet.ParseHTTPURL(serverURL)
   708  		if err != nil {
   709  			logger.Fatal(err, "Invalid MINIO_SERVER_URL value in environment variable")
   710  		}
   711  		// Look for if URL has invalid values and return error.
   712  		if !((u.Scheme == "http" || u.Scheme == "https") &&
   713  			(u.Path == "/" || u.Path == "") && u.Opaque == "" &&
   714  			!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
   715  			err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u)
   716  			logger.Fatal(err, "Invalid MINIO_SERVER_URL value is environment variable")
   717  		}
   718  		u.Path = "" // remove any path component such as `/`
   719  		globalMinioEndpoint = u.String()
   720  		globalMinioEndpointURL = u
   721  	}
   722  
   723  	globalFSOSync, err = config.ParseBool(env.Get(config.EnvFSOSync, config.EnableOff))
   724  	if err != nil {
   725  		logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable")
   726  	}
   727  
   728  	rootDiskSize := env.Get(config.EnvRootDriveThresholdSize, "")
   729  	if rootDiskSize == "" {
   730  		rootDiskSize = env.Get(config.EnvRootDiskThresholdSize, "")
   731  	}
   732  	if rootDiskSize != "" {
   733  		size, err := humanize.ParseBytes(rootDiskSize)
   734  		if err != nil {
   735  			logger.Fatal(err, fmt.Sprintf("Invalid %s value in root drive threshold environment variable", rootDiskSize))
   736  		}
   737  		globalRootDiskThreshold = size
   738  	}
   739  
   740  	domains := env.Get(config.EnvDomain, "")
   741  	if len(domains) != 0 {
   742  		for _, domainName := range strings.Split(domains, config.ValueSeparator) {
   743  			if _, ok := dns2.IsDomainName(domainName); !ok {
   744  				logger.Fatal(config.ErrInvalidDomainValue(nil).Msg("Unknown value `%s`", domainName),
   745  					"Invalid MINIO_DOMAIN value in environment variable")
   746  			}
   747  			globalDomainNames = append(globalDomainNames, domainName)
   748  		}
   749  		sort.Strings(globalDomainNames)
   750  		lcpSuf := lcpSuffix(globalDomainNames)
   751  		for _, domainName := range globalDomainNames {
   752  			if domainName == lcpSuf && len(globalDomainNames) > 1 {
   753  				logger.Fatal(config.ErrOverlappingDomainValue(nil).Msg("Overlapping domains `%s` not allowed", globalDomainNames),
   754  					"Invalid MINIO_DOMAIN value in environment variable")
   755  			}
   756  		}
   757  	}
   758  
   759  	publicIPs := env.Get(config.EnvPublicIPs, "")
   760  	if len(publicIPs) != 0 {
   761  		minioEndpoints := strings.Split(publicIPs, config.ValueSeparator)
   762  		domainIPs := set.NewStringSet()
   763  		for _, endpoint := range minioEndpoints {
   764  			if net.ParseIP(endpoint) == nil {
   765  				// Checking if the IP is a DNS entry.
   766  				addrs, err := globalDNSCache.LookupHost(GlobalContext, endpoint)
   767  				if err != nil {
   768  					logger.FatalIf(err, "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint)
   769  				}
   770  				for _, addr := range addrs {
   771  					domainIPs.Add(addr)
   772  				}
   773  			}
   774  			domainIPs.Add(endpoint)
   775  		}
   776  		updateDomainIPs(domainIPs)
   777  	} else {
   778  		// Add found interfaces IP address to global domain IPS,
   779  		// loopback addresses will be naturally dropped.
   780  		domainIPs := mustGetLocalIP4()
   781  		for _, host := range globalEndpoints.Hostnames() {
   782  			domainIPs.Add(host)
   783  		}
   784  		updateDomainIPs(domainIPs)
   785  	}
   786  
   787  	// In place update is true by default if the MINIO_UPDATE is not set
   788  	// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
   789  	// in-place update is off.
   790  	globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.EnableOn), config.EnableOff)
   791  
   792  	// Check if the supported credential env vars,
   793  	// "MINIO_ROOT_USER" and "MINIO_ROOT_PASSWORD" are provided
   794  	// Warn user if deprecated environment variables,
   795  	// "MINIO_ACCESS_KEY" and "MINIO_SECRET_KEY", are defined
   796  	// Check all error conditions first
   797  	//nolint:gocritic
   798  	if !env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
   799  		logger.Fatal(config.ErrMissingEnvCredentialRootUser(nil), "Unable to start MinIO")
   800  	} else if env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) {
   801  		logger.Fatal(config.ErrMissingEnvCredentialRootPassword(nil), "Unable to start MinIO")
   802  	} else if !env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) {
   803  		if !env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
   804  			logger.Fatal(config.ErrMissingEnvCredentialAccessKey(nil), "Unable to start MinIO")
   805  		} else if env.IsSet(config.EnvAccessKey) && !env.IsSet(config.EnvSecretKey) {
   806  			logger.Fatal(config.ErrMissingEnvCredentialSecretKey(nil), "Unable to start MinIO")
   807  		}
   808  	}
   809  
   810  	globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
   811  }
   812  
   813  func loadRootCredentials() {
   814  	// At this point, either both environment variables
   815  	// are defined or both are not defined.
   816  	// Check both cases and authenticate them if correctly defined
   817  	var user, password string
   818  	var hasCredentials bool
   819  	//nolint:gocritic
   820  	if env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
   821  		user = env.Get(config.EnvRootUser, "")
   822  		password = env.Get(config.EnvRootPassword, "")
   823  		hasCredentials = true
   824  	} else if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
   825  		user = env.Get(config.EnvAccessKey, "")
   826  		password = env.Get(config.EnvSecretKey, "")
   827  		hasCredentials = true
   828  	} else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" {
   829  		user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd
   830  		hasCredentials = true
   831  	}
   832  	if hasCredentials {
   833  		cred, err := auth.CreateCredentials(user, password)
   834  		if err != nil {
   835  			logger.Fatal(config.ErrInvalidCredentials(err),
   836  				"Unable to validate credentials inherited from the shell environment")
   837  		}
   838  		if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
   839  			msg := fmt.Sprintf("WARNING: %s and %s are deprecated.\n"+
   840  				"         Please use %s and %s",
   841  				config.EnvAccessKey, config.EnvSecretKey,
   842  				config.EnvRootUser, config.EnvRootPassword)
   843  			logger.Info(color.RedBold(msg))
   844  		}
   845  		globalActiveCred = cred
   846  		globalCredViaEnv = true
   847  	} else {
   848  		globalActiveCred = auth.DefaultCredentials
   849  	}
   850  }
   851  
   852  // Initialize KMS global variable after valiadating and loading the configuration.
   853  // It depends on KMS env variables and global cli flags.
   854  func handleKMSConfig() {
   855  	if env.IsSet(kms.EnvKMSSecretKey) && env.IsSet(kms.EnvKESEndpoint) {
   856  		logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKMSSecretKey, kms.EnvKESEndpoint))
   857  	}
   858  
   859  	if env.IsSet(kms.EnvKMSSecretKey) {
   860  		KMS, err := kms.Parse(env.Get(kms.EnvKMSSecretKey, ""))
   861  		if err != nil {
   862  			logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment")
   863  		}
   864  		GlobalKMS = KMS
   865  	}
   866  	if env.IsSet(kms.EnvKESEndpoint) {
   867  		if env.IsSet(kms.EnvKESAPIKey) {
   868  			if env.IsSet(kms.EnvKESClientKey) {
   869  				logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientKey))
   870  			}
   871  			if env.IsSet(kms.EnvKESClientCert) {
   872  				logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
   873  			}
   874  		}
   875  		if !env.IsSet(kms.EnvKESKeyName) {
   876  			logger.Fatal(errors.New("Invalid KES configuration"), fmt.Sprintf("The mandatory environment variable %q not set", kms.EnvKESKeyName))
   877  		}
   878  
   879  		var endpoints []string
   880  		for _, endpoint := range strings.Split(env.Get(kms.EnvKESEndpoint, ""), ",") {
   881  			if strings.TrimSpace(endpoint) == "" {
   882  				continue
   883  			}
   884  			if !ellipses.HasEllipses(endpoint) {
   885  				endpoints = append(endpoints, endpoint)
   886  				continue
   887  			}
   888  			patterns, err := ellipses.FindEllipsesPatterns(endpoint)
   889  			if err != nil {
   890  				logger.Fatal(err, fmt.Sprintf("Invalid KES endpoint %q", endpoint))
   891  			}
   892  			for _, lbls := range patterns.Expand() {
   893  				endpoints = append(endpoints, strings.Join(lbls, ""))
   894  			}
   895  		}
   896  		rootCAs, err := certs.GetRootCAs(env.Get(kms.EnvKESServerCA, globalCertsCADir.Get()))
   897  		if err != nil {
   898  			logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(kms.EnvKESServerCA, globalCertsCADir.Get())))
   899  		}
   900  
   901  		var kmsConf kms.Config
   902  		if env.IsSet(kms.EnvKESAPIKey) {
   903  			key, err := kes.ParseAPIKey(env.Get(kms.EnvKESAPIKey, ""))
   904  			if err != nil {
   905  				logger.Fatal(err, fmt.Sprintf("Failed to parse KES API key from %q", env.Get(kms.EnvKESAPIKey, "")))
   906  			}
   907  			kmsConf = kms.Config{
   908  				Endpoints:    endpoints,
   909  				DefaultKeyID: env.Get(kms.EnvKESKeyName, ""),
   910  				APIKey:       key,
   911  				RootCAs:      rootCAs,
   912  			}
   913  		} else {
   914  			loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) {
   915  				// Manually load the certificate and private key into memory.
   916  				// We need to check whether the private key is encrypted, and
   917  				// if so, decrypt it using the user-provided password.
   918  				certBytes, err := os.ReadFile(certFile)
   919  				if err != nil {
   920  					return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
   921  				}
   922  				keyBytes, err := os.ReadFile(keyFile)
   923  				if err != nil {
   924  					return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err)
   925  				}
   926  				privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes))
   927  				if len(rest) != 0 {
   928  					return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data")
   929  				}
   930  				if x509.IsEncryptedPEMBlock(privateKeyPEM) {
   931  					keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(kms.EnvKESClientPassword, "")))
   932  					if err != nil {
   933  						return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err)
   934  					}
   935  					keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes})
   936  				}
   937  				certificate, err := tls.X509KeyPair(certBytes, keyBytes)
   938  				if err != nil {
   939  					return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
   940  				}
   941  				return certificate, nil
   942  			}
   943  
   944  			reloadCertEvents := make(chan tls.Certificate, 1)
   945  			certificate, err := certs.NewCertificate(env.Get(kms.EnvKESClientCert, ""), env.Get(kms.EnvKESClientKey, ""), loadX509KeyPair)
   946  			if err != nil {
   947  				logger.Fatal(err, "Failed to load KES client certificate")
   948  			}
   949  			certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP)
   950  			certificate.Notify(reloadCertEvents)
   951  
   952  			kmsConf = kms.Config{
   953  				Endpoints:        endpoints,
   954  				DefaultKeyID:     env.Get(kms.EnvKESKeyName, ""),
   955  				Certificate:      certificate,
   956  				ReloadCertEvents: reloadCertEvents,
   957  				RootCAs:          rootCAs,
   958  			}
   959  		}
   960  
   961  		KMS, err := kms.NewWithConfig(kmsConf)
   962  		if err != nil {
   963  			logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
   964  		}
   965  		// We check that the default key ID exists or try to create it otherwise.
   966  		// This implicitly checks that we can communicate to KES. We don't treat
   967  		// a policy error as failure condition since MinIO may not have the permission
   968  		// to create keys - just to generate/decrypt data encryption keys.
   969  		if err = KMS.CreateKey(context.Background(), env.Get(kms.EnvKESKeyName, "")); err != nil && !errors.Is(err, kes.ErrKeyExists) && !errors.Is(err, kes.ErrNotAllowed) {
   970  			logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
   971  		}
   972  		GlobalKMS = KMS
   973  	}
   974  }
   975  
   976  func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secureConn bool, err error) {
   977  	if !(isFile(getPublicCertFile()) && isFile(getPrivateKeyFile())) {
   978  		return nil, nil, false, nil
   979  	}
   980  
   981  	if x509Certs, err = config.ParsePublicCertFile(getPublicCertFile()); err != nil {
   982  		return nil, nil, false, err
   983  	}
   984  
   985  	manager, err = certs.NewManager(GlobalContext, getPublicCertFile(), getPrivateKeyFile(), config.LoadX509KeyPair)
   986  	if err != nil {
   987  		return nil, nil, false, err
   988  	}
   989  
   990  	// MinIO has support for multiple certificates. It expects the following structure:
   991  	//  certs/
   992  	//   │
   993  	//   ├─ public.crt
   994  	//   ├─ private.key
   995  	//   │
   996  	//   ├─ example.com/
   997  	//   │   │
   998  	//   │   ├─ public.crt
   999  	//   │   └─ private.key
  1000  	//   └─ foobar.org/
  1001  	//      │
  1002  	//      ├─ public.crt
  1003  	//      └─ private.key
  1004  	//   ...
  1005  	//
  1006  	// Therefore, we read all filenames in the cert directory and check
  1007  	// for each directory whether it contains a public.crt and private.key.
  1008  	// If so, we try to add it to certificate manager.
  1009  	root, err := Open(globalCertsDir.Get())
  1010  	if err != nil {
  1011  		return nil, nil, false, err
  1012  	}
  1013  	defer root.Close()
  1014  
  1015  	files, err := root.Readdir(-1)
  1016  	if err != nil {
  1017  		return nil, nil, false, err
  1018  	}
  1019  	for _, file := range files {
  1020  		// Ignore all
  1021  		// - regular files
  1022  		// - "CAs" directory
  1023  		// - any directory which starts with ".."
  1024  		if file.Mode().IsRegular() || file.Name() == "CAs" || strings.HasPrefix(file.Name(), "..") {
  1025  			continue
  1026  		}
  1027  		if file.Mode()&os.ModeSymlink == os.ModeSymlink {
  1028  			file, err = Stat(filepath.Join(root.Name(), file.Name()))
  1029  			if err != nil {
  1030  				// not accessible ignore
  1031  				continue
  1032  			}
  1033  			if !file.IsDir() {
  1034  				continue
  1035  			}
  1036  		}
  1037  
  1038  		var (
  1039  			certFile = filepath.Join(root.Name(), file.Name(), publicCertFile)
  1040  			keyFile  = filepath.Join(root.Name(), file.Name(), privateKeyFile)
  1041  		)
  1042  		if !isFile(certFile) || !isFile(keyFile) {
  1043  			continue
  1044  		}
  1045  		if err = manager.AddCertificate(certFile, keyFile); err != nil {
  1046  			err = fmt.Errorf("Unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err)
  1047  			logger.LogIf(GlobalContext, err, logger.ErrorKind)
  1048  		}
  1049  	}
  1050  	secureConn = true
  1051  
  1052  	// Certs might be symlinks, reload them every 10 seconds.
  1053  	manager.UpdateReloadDuration(10 * time.Second)
  1054  
  1055  	// syscall.SIGHUP to reload the certs.
  1056  	manager.ReloadOnSignal(syscall.SIGHUP)
  1057  
  1058  	return x509Certs, manager, secureConn, nil
  1059  }
  1060  
  1061  // contextCanceled returns whether a context is canceled.
  1062  func contextCanceled(ctx context.Context) bool {
  1063  	select {
  1064  	case <-ctx.Done():
  1065  		return true
  1066  	default:
  1067  		return false
  1068  	}
  1069  }
  1070  
  1071  // bgContext returns a context that can be used for async operations.
  1072  // Cancellation/timeouts are removed, so parent cancellations/timeout will
  1073  // not propagate from parent.
  1074  // Context values are preserved.
  1075  // This can be used for goroutines that live beyond the parent context.
  1076  func bgContext(parent context.Context) context.Context {
  1077  	return bgCtx{parent: parent}
  1078  }
  1079  
  1080  type bgCtx struct {
  1081  	parent context.Context
  1082  }
  1083  
  1084  func (a bgCtx) Done() <-chan struct{} {
  1085  	return nil
  1086  }
  1087  
  1088  func (a bgCtx) Err() error {
  1089  	return nil
  1090  }
  1091  
  1092  func (a bgCtx) Deadline() (deadline time.Time, ok bool) {
  1093  	return time.Time{}, false
  1094  }
  1095  
  1096  func (a bgCtx) Value(key interface{}) interface{} {
  1097  	return a.parent.Value(key)
  1098  }