/ *
* MinIO Cloud Storage , ( C ) 2017 - 2019 MinIO , Inc .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
package cmd
import (
"crypto/x509"
"encoding/gob"
"errors"
"net"
"os"
"path/filepath"
"strings"
"time"
dns2 "github.com/miekg/dns"
"github.com/minio/cli"
"github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/certs"
"github.com/minio/minio/pkg/env"
)
func init ( ) {
logger . Init ( GOPATH , GOROOT )
logger . RegisterError ( config . FmtError )
// Initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger ( GlobalContext )
logger . AddTarget ( globalConsoleSys )
gob . Register ( StorageErr ( "" ) )
}
func verifyObjectLayerFeatures ( name string , objAPI ObjectLayer ) {
if ( globalAutoEncryption || GlobalKMS != nil ) && ! objAPI . IsEncryptionSupported ( ) {
logger . Fatal ( errInvalidArgument ,
"Encryption support is requested but '%s' does not support encryption" , name )
}
if strings . HasPrefix ( name , "gateway" ) {
if GlobalGatewaySSE . IsSet ( ) && GlobalKMS == nil {
uiErr := config . ErrInvalidGWSSEEnvValue ( nil ) . Msg ( "MINIO_GATEWAY_SSE set but KMS is not configured" )
logger . Fatal ( uiErr , "Unable to start gateway with SSE" )
}
}
if globalCompressConfig . Enabled && ! objAPI . IsCompressionSupported ( ) {
logger . Fatal ( errInvalidArgument ,
"Compression support is requested but '%s' does not support compression" , name )
}
}
// Check for updates and print a notification message
func checkUpdate ( mode string ) {
// Its OK to ignore any errors during doUpdate() here.
if updateMsg , _ , currentReleaseTime , latestReleaseTime , err := getUpdateInfo ( 2 * time . Second , mode ) ; err == nil {
if updateMsg == "" {
return
}
if globalInplaceUpdateDisabled {
logStartupMessage ( updateMsg )
} else {
logStartupMessage ( prepareUpdateMessage ( "Run `mc admin update`" , latestReleaseTime . Sub ( currentReleaseTime ) ) )
}
}
}
func newConfigDirFromCtx ( ctx * cli . Context , option string , getDefaultDir func ( ) string ) ( * ConfigDir , bool ) {
var dir string
var dirSet bool
switch {
case ctx . IsSet ( option ) :
dir = ctx . String ( option )
dirSet = true
case ctx . GlobalIsSet ( option ) :
dir = ctx . GlobalString ( option )
dirSet = true
// cli package does not expose parent's option option. Below code is workaround.
if dir == "" || dir == getDefaultDir ( ) {
dirSet = false // Unset to false since GlobalIsSet() true is a false positive.
if ctx . Parent ( ) . GlobalIsSet ( option ) {
dir = ctx . Parent ( ) . GlobalString ( option )
dirSet = true
}
}
default :
// Neither local nor global option is provided. In this case, try to use
// default directory.
dir = getDefaultDir ( )
if dir == "" {
logger . FatalIf ( errInvalidArgument , "%s option must be provided" , option )
}
}
if dir == "" {
logger . FatalIf ( errors . New ( "empty directory" ) , "%s directory cannot be empty" , option )
}
// Disallow relative paths, figure out absolute paths.
dirAbs , err := filepath . Abs ( dir )
logger . FatalIf ( err , "Unable to fetch absolute path for %s=%s" , option , dir )
logger . FatalIf ( mkdirAllIgnorePerm ( dirAbs ) , "Unable to create directory specified %s=%s" , option , dir )
return & ConfigDir { path : dirAbs } , dirSet
}
func handleCommonCmdArgs ( ctx * cli . Context ) {
// Get "json" flag from command line argument and
// enable json and quite modes if json flag is turned on.
globalCLIContext . JSON = ctx . IsSet ( "json" ) || ctx . GlobalIsSet ( "json" )
if globalCLIContext . JSON {
logger . EnableJSON ( )
}
// Get quiet flag from command line argument.
globalCLIContext . Quiet = ctx . IsSet ( "quiet" ) || ctx . GlobalIsSet ( "quiet" )
if globalCLIContext . Quiet {
logger . EnableQuiet ( )
}
// Get anonymous flag from command line argument.
globalCLIContext . Anonymous = ctx . IsSet ( "anonymous" ) || ctx . GlobalIsSet ( "anonymous" )
if globalCLIContext . Anonymous {
logger . EnableAnonymous ( )
}
// Fetch address option
globalCLIContext . Addr = ctx . GlobalString ( "address" )
if globalCLIContext . Addr == "" || globalCLIContext . Addr == ":" + GlobalMinioDefaultPort {
globalCLIContext . Addr = ctx . String ( "address" )
}
// Check "no-compat" flag from command line argument.
globalCLIContext . StrictS3Compat = true
if ctx . IsSet ( "no-compat" ) || ctx . GlobalIsSet ( "no-compat" ) {
globalCLIContext . StrictS3Compat = false
}
// Set all config, certs and CAs directories.
var configSet , certsSet bool
globalConfigDir , configSet = newConfigDirFromCtx ( ctx , "config-dir" , defaultConfigDir . Get )
globalCertsDir , certsSet = newConfigDirFromCtx ( ctx , "certs-dir" , defaultCertsDir . Get )
// Remove this code when we deprecate and remove config-dir.
// This code is to make sure we inherit from the config-dir
// option if certs-dir is not provided.
if ! certsSet && configSet {
globalCertsDir = & ConfigDir { path : filepath . Join ( globalConfigDir . Get ( ) , certsDir ) }
}
globalCertsCADir = & ConfigDir { path : filepath . Join ( globalCertsDir . Get ( ) , certsCADir ) }
logger . FatalIf ( mkdirAllIgnorePerm ( globalCertsCADir . Get ( ) ) , "Unable to create certs CA directory at %s" , globalCertsCADir . Get ( ) )
}
func handleCommonEnvVars ( ) {
wormEnabled , err := config . LookupWorm ( )
if err != nil {
logger . Fatal ( config . ErrInvalidWormValue ( err ) , "Invalid worm configuration" )
}
if wormEnabled {
logger . Fatal ( errors . New ( "WORM is deprecated" ) , "global MINIO_WORM support is removed, please downgrade your server or migrate to https://github.com/minio/minio/tree/master/docs/retention" )
}
globalBrowserEnabled , err = config . ParseBool ( env . Get ( config . EnvBrowser , config . EnableOn ) )
if err != nil {
logger . Fatal ( config . ErrInvalidBrowserValue ( err ) , "Invalid MINIO_BROWSER value in environment variable" )
}
globalFSOSync , err = config . ParseBool ( env . Get ( config . EnvFSOSync , config . EnableOff ) )
if err != nil {
logger . Fatal ( config . ErrInvalidFSOSyncValue ( err ) , "Invalid MINIO_FS_OSYNC value in environment variable" )
}
domains := env . Get ( config . EnvDomain , "" )
if len ( domains ) != 0 {
for _ , domainName := range strings . Split ( domains , config . ValueSeparator ) {
if _ , ok := dns2 . IsDomainName ( domainName ) ; ! ok {
logger . Fatal ( config . ErrInvalidDomainValue ( nil ) . Msg ( "Unknown value `%s`" , domainName ) ,
"Invalid MINIO_DOMAIN value in environment variable" )
}
globalDomainNames = append ( globalDomainNames , domainName )
}
}
publicIPs := env . Get ( config . EnvPublicIPs , "" )
if len ( publicIPs ) != 0 {
minioEndpoints := strings . Split ( publicIPs , config . ValueSeparator )
var domainIPs = set . NewStringSet ( )
for _ , endpoint := range minioEndpoints {
if net . ParseIP ( endpoint ) == nil {
// Checking if the IP is a DNS entry.
addrs , err := net . LookupHost ( endpoint )
if err != nil {
logger . FatalIf ( err , "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS" , endpoint )
}
for _ , addr := range addrs {
domainIPs . Add ( addr )
}
}
domainIPs . Add ( endpoint )
}
updateDomainIPs ( domainIPs )
} else {
// Add found interfaces IP address to global domain IPS,
// loopback addresses will be naturally dropped.
updateDomainIPs ( mustGetLocalIP4 ( ) )
}
// In place update is true by default if the MINIO_UPDATE is not set
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
// in-place update is off.
globalInplaceUpdateDisabled = strings . EqualFold ( env . Get ( config . EnvUpdate , config . EnableOn ) , config . EnableOff )
if env . IsSet ( config . EnvAccessKey ) || env . IsSet ( config . EnvSecretKey ) {
cred , err := auth . CreateCredentials ( env . Get ( config . EnvAccessKey , "" ) , env . Get ( config . EnvSecretKey , "" ) )
if err != nil {
logger . Fatal ( config . ErrInvalidCredentials ( err ) ,
"Unable to validate credentials inherited from the shell environment" )
}
globalActiveCred = cred
globalConfigEncrypted = true
}
if env . IsSet ( config . EnvAccessKeyOld ) && env . IsSet ( config . EnvSecretKeyOld ) {
oldCred , err := auth . CreateCredentials ( env . Get ( config . EnvAccessKeyOld , "" ) , env . Get ( config . EnvSecretKeyOld , "" ) )
if err != nil {
logger . Fatal ( config . ErrInvalidCredentials ( err ) ,
"Unable to validate the old credentials inherited from the shell environment" )
}
globalOldCred = oldCred
os . Unsetenv ( config . EnvAccessKeyOld )
os . Unsetenv ( config . EnvSecretKeyOld )
}
}
func logStartupMessage ( msg string ) {
if globalConsoleSys != nil {
globalConsoleSys . Send ( msg , string ( logger . All ) )
}
logger . StartupMessage ( msg )
}
func getTLSConfig ( ) ( x509Certs [ ] * x509 . Certificate , c * certs . Certs , secureConn bool , err error ) {
if ! ( isFile ( getPublicCertFile ( ) ) && isFile ( getPrivateKeyFile ( ) ) ) {
return nil , nil , false , nil
}
if x509Certs , err = config . ParsePublicCertFile ( getPublicCertFile ( ) ) ; err != nil {
return nil , nil , false , err
}
c , err = certs . New ( getPublicCertFile ( ) , getPrivateKeyFile ( ) , config . LoadX509KeyPair )
if err != nil {
return nil , nil , false , err
}
secureConn = true
return x509Certs , c , secureConn , nil
}