2021-04-18 15:41:13 -04:00
// 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 <http://www.gnu.org/licenses/>.
2017-06-09 22:50:51 -04:00
package cmd
import (
2021-01-23 21:27:23 -05:00
"context"
2021-04-22 11:45:30 -04:00
"crypto/tls"
2019-10-08 01:47:56 -04:00
"crypto/x509"
2020-02-07 03:18:07 -05:00
"encoding/gob"
2017-06-09 22:50:51 -04:00
"errors"
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
"fmt"
2020-10-19 04:34:23 -04:00
"math/rand"
2018-02-02 21:18:52 -05:00
"net"
2021-06-04 15:47:00 -04:00
"net/http"
2020-07-23 11:03:31 -04:00
"net/url"
2020-03-17 13:36:13 -04:00
"os"
2017-06-09 22:50:51 -04:00
"path/filepath"
2020-07-23 11:03:31 -04:00
"runtime"
2021-01-28 14:44:48 -05:00
"sort"
2021-06-17 23:27:04 -04:00
"strconv"
2017-06-09 22:50:51 -04:00
"strings"
"time"
2021-06-17 23:27:04 -04:00
fcolor "github.com/fatih/color"
"github.com/go-openapi/loads"
2018-09-20 17:56:32 -04:00
dns2 "github.com/miekg/dns"
2017-06-09 22:50:51 -04:00
"github.com/minio/cli"
2021-06-17 23:27:04 -04:00
consoleCerts "github.com/minio/console/pkg/certs"
"github.com/minio/console/restapi"
"github.com/minio/console/restapi/operations"
2021-05-13 14:13:31 -04:00
"github.com/minio/kes"
2021-06-04 15:47:00 -04:00
"github.com/minio/minio-go/v7/pkg/credentials"
2020-07-14 12:38:05 -04:00
"github.com/minio/minio-go/v7/pkg/set"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/auth"
2021-06-17 23:27:04 -04:00
"github.com/minio/minio/internal/color"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger"
2021-05-28 18:17:01 -04:00
"github.com/minio/pkg/certs"
"github.com/minio/pkg/console"
"github.com/minio/pkg/ellipses"
"github.com/minio/pkg/env"
2021-06-17 23:27:04 -04:00
xnet "github.com/minio/pkg/net"
2017-06-09 22:50:51 -04:00
)
2020-12-17 19:52:47 -05:00
// serverDebugLog will enable debug printing
var serverDebugLog = env . Get ( "_MINIO_SERVER_DEBUG" , config . EnableOff ) == config . EnableOn
2021-06-04 15:47:00 -04:00
var defaultAWSCredProvider [ ] credentials . Provider
2020-12-17 19:52:47 -05:00
2020-02-07 03:18:07 -05:00
func init ( ) {
2021-02-17 15:04:11 -05:00
rand . Seed ( time . Now ( ) . UTC ( ) . UnixNano ( ) )
2020-02-07 03:18:07 -05:00
logger . Init ( GOPATH , GOROOT )
logger . RegisterError ( config . FmtError )
2021-04-08 20:51:59 -04:00
if IsKubernetes ( ) || IsDocker ( ) || IsBOSH ( ) || IsDCOS ( ) || IsKubernetesReplicaSet ( ) || IsPCFTile ( ) {
// 30 seconds matches the orchestrator DNS TTLs, have
// a 5 second timeout to lookup from DNS servers.
globalDNSCache = xhttp . NewDNSCache ( 30 * time . Second , 5 * time . Second , logger . LogOnceIf )
} else {
// On bare-metals DNS do not change often, so it is
// safe to assume a higher timeout upto 10 minutes.
globalDNSCache = xhttp . NewDNSCache ( 10 * time . Minute , 5 * time . Second , logger . LogOnceIf )
}
2020-12-13 14:57:08 -05:00
initGlobalContext ( )
2021-01-22 18:37:41 -05:00
globalForwarder = handlers . NewForwarder ( & handlers . Forwarder {
PassHost : true ,
RoundTripper : newGatewayHTTPTransport ( 1 * time . Hour ) ,
Logger : func ( err error ) {
2021-01-23 21:27:23 -05:00
if err != nil && ! errors . Is ( err , context . Canceled ) {
logger . LogIf ( GlobalContext , err )
}
2021-01-22 18:37:41 -05:00
} ,
} )
2020-12-13 14:57:08 -05:00
globalTransitionState = newTransitionState ( )
2021-06-17 23:27:04 -04:00
console . SetColor ( "Debug" , fcolor . New ( ) )
2020-12-17 19:52:47 -05:00
2020-02-07 03:18:07 -05:00
gob . Register ( StorageErr ( "" ) )
2021-06-04 15:47:00 -04:00
defaultAWSCredProvider = [ ] credentials . Provider {
& credentials . IAM {
Client : & http . Client {
Transport : NewGatewayHTTPTransport ( ) ,
} ,
} ,
}
2020-02-07 03:18:07 -05:00
}
2021-06-17 23:27:04 -04:00
const consolePrefix = "CONSOLE_"
func minioConfigToConsoleFeatures ( ) {
os . Setenv ( "CONSOLE_PBKDF_PASSPHRASE" , restapi . RandomCharString ( 16 ) )
os . Setenv ( "CONSOLE_PBKDF_SALT" , restapi . RandomCharString ( 8 ) )
os . Setenv ( "CONSOLE_MINIO_SERVER" , getAPIEndpoints ( ) [ 0 ] )
if value := os . Getenv ( "MINIO_LOG_QUERY_URL" ) ; value != "" {
os . Setenv ( "CONSOLE_LOG_QUERY_URL" , value )
}
2021-06-21 02:04:47 -04:00
if value := os . Getenv ( "MINIO_LOG_QUERY_AUTH_TOKEN" ) ; value != "" {
os . Setenv ( "CONSOLE_LOG_QUERY_AUTH_TOKEN" , value )
}
2021-06-17 23:27:04 -04:00
// Enable if prometheus URL is set.
if value := os . Getenv ( "MINIO_PROMETHEUS_URL" ) ; value != "" {
os . Setenv ( "CONSOLE_PROMETHEUS_URL" , value )
}
// Enable if LDAP is enabled.
if globalLDAPConfig . Enabled {
os . Setenv ( "CONSOLE_LDAP_ENABLED" , config . EnableOn )
}
// if IDP is enabled, set IDP environment variables
if globalOpenIDConfig . URL != nil {
os . Setenv ( "CONSOLE_IDP_URL" , globalOpenIDConfig . DiscoveryDoc . Issuer )
os . Setenv ( "CONSOLE_IDP_SCOPES" , strings . Join ( globalOpenIDConfig . DiscoveryDoc . ScopesSupported , "," ) )
os . Setenv ( "CONSOLE_IDP_CLIENT_ID" , globalOpenIDConfig . ClientID )
os . Setenv ( "CONSOLE_IDP_SECRET" , globalOpenIDConfig . ClientSecret )
}
os . Setenv ( "CONSOLE_MINIO_REGION" , globalServerRegion )
os . Setenv ( "CONSOLE_CERT_PASSWD" , os . Getenv ( "MINIO_CERT_PASSWD" ) )
os . Setenv ( "CONSOLE_IDP_CALLBACK" , getConsoleEndpoints ( ) [ 0 ] + "/oauth_callback" )
}
func initConsoleServer ( ) ( * restapi . Server , error ) {
// unset all console_ environment variables.
for _ , cenv := range env . List ( consolePrefix ) {
os . Unsetenv ( cenv )
}
// enable all console environment variables
minioConfigToConsoleFeatures ( )
// set certs dir to minio directory
consoleCerts . GlobalCertsDir = & consoleCerts . ConfigDir {
Path : globalCertsDir . Get ( ) ,
}
consoleCerts . GlobalCertsCADir = & consoleCerts . ConfigDir {
Path : globalCertsCADir . Get ( ) ,
}
swaggerSpec , err := loads . Embedded ( restapi . SwaggerJSON , restapi . FlatSwaggerJSON )
if err != nil {
return nil , err
}
// Initialize MinIO loggers
restapi . LogInfo = logger . Info
restapi . LogError = logger . Error
api := operations . NewConsoleAPI ( swaggerSpec )
api . Logger = func ( _ string , _ ... interface { } ) {
// nothing to log.
}
server := restapi . NewServer ( api )
// register all APIs
server . ConfigureAPI ( )
restapi . GlobalRootCAs , restapi . GlobalPublicCerts , restapi . GlobalTLSCertsManager = globalRootCAs , globalPublicCerts , globalTLSCerts
consolePort , _ := strconv . Atoi ( globalMinioConsolePort )
server . Host = globalMinioConsoleHost
server . Port = consolePort
restapi . Port = globalMinioConsolePort
restapi . Hostname = globalMinioConsoleHost
if globalIsTLS {
// If TLS certificates are provided enforce the HTTPS.
server . EnabledListeners = [ ] string { "https" }
server . TLSPort = consolePort
// Need to store tls-port, tls-host un config variables so secure.middleware can read from there
restapi . TLSPort = globalMinioConsolePort
restapi . Hostname = globalMinioConsoleHost
}
// subnet license refresh process
go func ( ) {
// start refreshing subnet license after 5 seconds..
time . Sleep ( time . Second * 5 )
failedAttempts := 0
for {
if err := restapi . RefreshLicense ( ) ; err != nil {
failedAttempts ++
// end license refresh after 3 consecutive failed attempts
if failedAttempts >= 3 {
return
}
// wait 5 minutes and retry again
time . Sleep ( time . Minute * 5 )
continue
}
// if license refreshed successfully reset the counter
failedAttempts = 0
// try to refresh license every 24 hrs
time . Sleep ( time . Hour * 24 )
}
} ( )
return server , nil
}
2019-07-26 05:41:16 -04:00
func verifyObjectLayerFeatures ( name string , objAPI ObjectLayer ) {
2020-09-03 15:43:45 -04:00
if ( GlobalKMS != nil ) && ! objAPI . IsEncryptionSupported ( ) {
2019-07-26 05:41:16 -04:00
logger . Fatal ( errInvalidArgument ,
"Encryption support is requested but '%s' does not support encryption" , name )
}
if strings . HasPrefix ( name , "gateway" ) {
if GlobalGatewaySSE . IsSet ( ) && GlobalKMS == nil {
2019-10-04 13:35:33 -04:00
uiErr := config . ErrInvalidGWSSEEnvValue ( nil ) . Msg ( "MINIO_GATEWAY_SSE set but KMS is not configured" )
2019-07-26 05:41:16 -04:00
logger . Fatal ( uiErr , "Unable to start gateway with SSE" )
}
}
2020-12-04 12:32:35 -05:00
globalCompressConfigMu . Lock ( )
2019-10-23 01:59:13 -04:00
if globalCompressConfig . Enabled && ! objAPI . IsCompressionSupported ( ) {
2019-07-26 05:41:16 -04:00
logger . Fatal ( errInvalidArgument ,
"Compression support is requested but '%s' does not support compression" , name )
}
2020-12-04 12:32:35 -05:00
globalCompressConfigMu . Unlock ( )
2019-07-26 05:41:16 -04:00
}
2017-06-09 22:50:51 -04:00
// Check for updates and print a notification message
func checkUpdate ( mode string ) {
2020-07-23 11:03:31 -04:00
updateURL := minioReleaseInfoURL
if runtime . GOOS == globalWindowsOSName {
updateURL = minioReleaseWindowsInfoURL
}
u , err := url . Parse ( updateURL )
if err != nil {
return
}
2017-12-15 15:33:42 -05:00
// Its OK to ignore any errors during doUpdate() here.
2020-07-23 11:03:31 -04:00
crTime , err := GetCurrentReleaseTime ( )
if err != nil {
return
}
_ , lrTime , err := getLatestReleaseTime ( u , 2 * time . Second , mode )
if err != nil {
return
}
var older time . Duration
var downloadURL string
if lrTime . After ( crTime ) {
older = lrTime . Sub ( crTime )
downloadURL = getDownloadURL ( releaseTimeToReleaseTag ( lrTime ) )
}
updateMsg := prepareUpdateMessage ( downloadURL , older )
if updateMsg == "" {
return
}
2020-10-25 01:39:44 -04:00
logStartupMessage ( prepareUpdateMessage ( "Run `mc admin update`" , lrTime . Sub ( crTime ) ) )
2017-06-09 22:50:51 -04:00
}
2019-01-16 15:04:32 -05:00
func newConfigDirFromCtx ( ctx * cli . Context , option string , getDefaultDir func ( ) string ) ( * ConfigDir , bool ) {
2019-01-02 13:05:16 -05:00
var dir string
2019-01-16 15:04:32 -05:00
var dirSet bool
2019-01-02 13:05:16 -05:00
switch {
case ctx . IsSet ( option ) :
dir = ctx . String ( option )
2019-01-16 15:04:32 -05:00
dirSet = true
2019-01-02 13:05:16 -05:00
case ctx . GlobalIsSet ( option ) :
dir = ctx . GlobalString ( option )
2019-01-16 15:04:32 -05:00
dirSet = true
2019-01-02 13:05:16 -05:00
// cli package does not expose parent's option option. Below code is workaround.
if dir == "" || dir == getDefaultDir ( ) {
2019-01-16 15:04:32 -05:00
dirSet = false // Unset to false since GlobalIsSet() true is a false positive.
2019-01-02 13:05:16 -05:00
if ctx . Parent ( ) . GlobalIsSet ( option ) {
dir = ctx . Parent ( ) . GlobalString ( option )
2019-01-16 15:04:32 -05:00
dirSet = true
2019-01-02 13:05:16 -05:00
}
}
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 )
2019-01-16 15:04:32 -05:00
return & ConfigDir { path : dirAbs } , dirSet
2019-01-02 13:05:16 -05:00
}
2017-06-09 22:50:51 -04:00
func handleCommonCmdArgs ( ctx * cli . Context ) {
2018-02-28 23:13:33 -05:00
2018-12-18 19:08:11 -05:00
// Get "json" flag from command line argument and
2019-02-10 08:49:00 -05:00
// enable json and quite modes if json flag is turned on.
2018-12-18 19:08:11 -05:00
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
2021-06-21 02:04:47 -04:00
addr := ctx . GlobalString ( "address" )
if addr == "" || addr == ":" + GlobalMinioDefaultPort {
addr = ctx . String ( "address" )
2018-12-18 19:08:11 -05:00
}
2021-06-17 23:27:04 -04:00
// Fetch console address option
2021-06-21 02:04:47 -04:00
consoleAddr := ctx . GlobalString ( "console-address" )
if consoleAddr == "" {
consoleAddr = ctx . String ( "console-address" )
2021-06-17 23:27:04 -04:00
}
2021-06-21 02:04:47 -04:00
if consoleAddr == "" {
2021-06-17 23:27:04 -04:00
p , err := xnet . GetFreePort ( )
if err != nil {
logger . FatalIf ( err , "Unable to get free port for console on the host" )
}
globalMinioConsolePortAuto = true
2021-06-21 02:04:47 -04:00
consoleAddr = net . JoinHostPort ( "" , p . String ( ) )
2021-06-17 23:27:04 -04:00
}
2021-06-21 02:04:47 -04:00
if _ , _ , err := net . SplitHostPort ( consoleAddr ) ; err != nil {
logger . FatalIf ( err , "Unable to start listening on console port" )
2021-06-17 23:27:04 -04:00
}
2021-06-21 02:04:47 -04:00
if consoleAddr == addr {
logger . FatalIf ( errors . New ( "--console-address cannot be same as --address" ) , "Unable to start the server" )
}
2021-06-17 23:27:04 -04:00
2021-06-21 02:04:47 -04:00
globalMinioHost , globalMinioPort = mustSplitHostPort ( addr )
globalMinioConsoleHost , globalMinioConsolePort = mustSplitHostPort ( consoleAddr )
2021-06-17 23:27:04 -04:00
if globalMinioPort == globalMinioConsolePort {
logger . FatalIf ( errors . New ( "--console-address port cannot be same as --address port" ) , "Unable to start the server" )
}
2021-06-21 02:04:47 -04:00
globalMinioAddr = addr
2020-04-12 21:08:27 -04:00
// Check "no-compat" flag from command line argument.
globalCLIContext . StrictS3Compat = true
if ctx . IsSet ( "no-compat" ) || ctx . GlobalIsSet ( "no-compat" ) {
globalCLIContext . StrictS3Compat = false
}
2019-01-02 13:05:16 -05:00
// Set all config, certs and CAs directories.
2019-01-16 15:04:32 -05:00
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 ) }
}
2019-01-02 13:05:16 -05:00
globalCertsCADir = & ConfigDir { path : filepath . Join ( globalCertsDir . Get ( ) , certsCADir ) }
2018-02-28 23:13:33 -05:00
2019-01-02 13:05:16 -05:00
logger . FatalIf ( mkdirAllIgnorePerm ( globalCertsCADir . Get ( ) ) , "Unable to create certs CA directory at %s" , globalCertsCADir . Get ( ) )
2017-06-09 22:50:51 -04:00
}
func handleCommonEnvVars ( ) {
2021-05-17 11:45:22 -04:00
var err error
2021-04-29 22:01:43 -04:00
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" )
}
2021-06-21 02:04:47 -04:00
globalBrowserRedirect , err = config . ParseBool ( env . Get ( config . EnvBrowserRedirect , config . EnableOn ) )
if err != nil {
logger . Fatal ( config . ErrInvalidBrowserValue ( err ) , "Invalid MINIO_BROWSER_REDIRECT value in environment variable" )
}
2020-05-12 22:24:59 -04:00
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" )
}
2019-10-31 02:39:09 -04:00
domains := env . Get ( config . EnvDomain , "" )
if len ( domains ) != 0 {
for _ , domainName := range strings . Split ( domains , config . ValueSeparator ) {
2019-10-08 01:47:56 -04:00
if _ , ok := dns2 . IsDomainName ( domainName ) ; ! ok {
2019-10-04 13:35:33 -04:00
logger . Fatal ( config . ErrInvalidDomainValue ( nil ) . Msg ( "Unknown value `%s`" , domainName ) ,
2019-02-22 22:18:01 -05:00
"Invalid MINIO_DOMAIN value in environment variable" )
}
globalDomainNames = append ( globalDomainNames , domainName )
2018-09-20 17:56:32 -04:00
}
2021-01-28 14:44:48 -05:00
sort . Strings ( globalDomainNames )
lcpSuf := lcpSuffix ( globalDomainNames )
for _ , domainName := range globalDomainNames {
2021-01-28 16:08:48 -05:00
if domainName == lcpSuf && len ( globalDomainNames ) > 1 {
2021-01-28 14:44:48 -05:00
logger . Fatal ( config . ErrOverlappingDomainValue ( nil ) . Msg ( "Overlapping domains `%s` not allowed" , globalDomainNames ) ,
"Invalid MINIO_DOMAIN value in environment variable" )
}
}
2018-09-20 17:56:32 -04:00
}
2018-05-15 21:20:22 -04:00
2019-10-31 02:39:09 -04:00
publicIPs := env . Get ( config . EnvPublicIPs , "" )
if len ( publicIPs ) != 0 {
minioEndpoints := strings . Split ( publicIPs , config . ValueSeparator )
2018-12-23 06:08:21 -05:00
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 {
2019-04-09 14:39:42 -04:00
logger . FatalIf ( err , "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS" , endpoint )
2018-12-23 06:08:21 -05:00
}
for _ , addr := range addrs {
domainIPs . Add ( addr )
}
2018-05-11 15:02:30 -04:00
}
2018-12-23 06:08:21 -05:00
domainIPs . Add ( endpoint )
2018-05-11 15:02:30 -04:00
}
2018-12-23 06:08:21 -05:00
updateDomainIPs ( domainIPs )
2018-12-04 20:35:22 -05:00
} else {
// Add found interfaces IP address to global domain IPS,
// loopback addresses will be naturally dropped.
2020-07-20 15:28:48 -04:00
domainIPs := mustGetLocalIP4 ( )
for _ , host := range globalEndpoints . Hostnames ( ) {
domainIPs . Add ( host )
}
updateDomainIPs ( domainIPs )
2018-05-11 15:02:30 -04:00
}
2018-12-04 20:35:22 -05:00
2017-12-15 15:33:42 -05:00
// 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.
2019-12-04 18:32:37 -05:00
globalInplaceUpdateDisabled = strings . EqualFold ( env . Get ( config . EnvUpdate , config . EnableOn ) , config . EnableOff )
2019-11-01 18:53:16 -04:00
2021-06-11 01:28:09 -04:00
// Check if the supported credential env vars, "MINIO_ROOT_USER" and
// "MINIO_ROOT_PASSWORD" are provided
// Warn user if deprecated environment variables,
// "MINIO_ACCESS_KEY" and "MINIO_SECRET_KEY", are defined
// Check all error conditions first
if ! env . IsSet ( config . EnvRootUser ) && env . IsSet ( config . EnvRootPassword ) {
logger . Fatal ( config . ErrMissingEnvCredentialRootUser ( nil ) , "Unable to start MinIO" )
} else if env . IsSet ( config . EnvRootUser ) && ! env . IsSet ( config . EnvRootPassword ) {
logger . Fatal ( config . ErrMissingEnvCredentialRootPassword ( nil ) , "Unable to start MinIO" )
} else if ! env . IsSet ( config . EnvRootUser ) && ! env . IsSet ( config . EnvRootPassword ) {
if ! env . IsSet ( config . EnvAccessKey ) && env . IsSet ( config . EnvSecretKey ) {
logger . Fatal ( config . ErrMissingEnvCredentialAccessKey ( nil ) , "Unable to start MinIO" )
} else if env . IsSet ( config . EnvAccessKey ) && ! env . IsSet ( config . EnvSecretKey ) {
logger . Fatal ( config . ErrMissingEnvCredentialSecretKey ( nil ) , "Unable to start MinIO" )
2021-04-29 13:55:05 -04:00
}
}
2021-06-11 01:28:09 -04:00
// At this point, either both environment variables
// are defined or both are not defined.
// Check both cases and authenticate them if correctly defined
var user , password string
haveRootCredentials := false
haveAccessCredentials := false
if env . IsSet ( config . EnvRootUser ) && env . IsSet ( config . EnvRootPassword ) {
user = env . Get ( config . EnvRootUser , "" )
password = env . Get ( config . EnvRootPassword , "" )
haveRootCredentials = true
} else if env . IsSet ( config . EnvAccessKey ) && env . IsSet ( config . EnvSecretKey ) {
user = env . Get ( config . EnvAccessKey , "" )
password = env . Get ( config . EnvSecretKey , "" )
haveAccessCredentials = true
}
if haveRootCredentials || haveAccessCredentials {
cred , err := auth . CreateCredentials ( user , password )
2021-01-05 13:22:57 -05:00
if err != nil {
logger . Fatal ( config . ErrInvalidCredentials ( err ) ,
"Unable to validate credentials inherited from the shell environment" )
}
2021-06-11 01:28:09 -04:00
if haveAccessCredentials {
msg := fmt . Sprintf ( "WARNING: %s and %s are deprecated.\n" +
" Please use %s and %s" ,
config . EnvAccessKey , config . EnvSecretKey ,
config . EnvRootUser , config . EnvRootPassword )
2021-06-17 23:27:04 -04:00
logStartupMessage ( color . RedBold ( msg ) )
2021-06-11 01:28:09 -04:00
}
2021-01-05 13:22:57 -05:00
globalActiveCred = cred
}
2021-04-22 11:45:30 -04:00
switch {
case env . IsSet ( config . EnvKMSSecretKey ) && env . IsSet ( config . EnvKESEndpoint ) :
logger . Fatal ( errors . New ( "ambigious KMS configuration" ) , fmt . Sprintf ( "The environment contains %q as well as %q" , config . EnvKMSSecretKey , config . EnvKESEndpoint ) )
}
2021-04-24 22:05:25 -04:00
2021-04-25 14:04:31 -04:00
if env . IsSet ( config . EnvKMSSecretKey ) {
2021-04-30 21:47:30 -04:00
GlobalKMS , err = kms . Parse ( env . Get ( config . EnvKMSSecretKey , "" ) )
2020-03-17 13:36:13 -04:00
if err != nil {
2021-04-25 14:04:31 -04:00
logger . Fatal ( err , "Unable to parse the KMS secret key inherited from the shell environment" )
2020-03-17 13:36:13 -04:00
}
2021-04-22 11:45:30 -04:00
}
if env . IsSet ( config . EnvKESEndpoint ) {
2021-05-10 21:15:11 -04:00
var endpoints [ ] string
for _ , endpoint := range strings . Split ( env . Get ( config . EnvKESEndpoint , "" ) , "," ) {
if strings . TrimSpace ( endpoint ) == "" {
continue
}
if ! ellipses . HasEllipses ( endpoint ) {
endpoints = append ( endpoints , endpoint )
continue
}
patterns , err := ellipses . FindEllipsesPatterns ( endpoint )
if err != nil {
logger . Fatal ( err , fmt . Sprintf ( "Invalid KES endpoint %q" , endpoint ) )
}
for _ , lbls := range patterns . Expand ( ) {
endpoints = append ( endpoints , strings . Join ( lbls , "" ) )
}
}
certificate , err := tls . LoadX509KeyPair ( env . Get ( config . EnvKESClientCert , "" ) , env . Get ( config . EnvKESClientKey , "" ) )
if err != nil {
logger . Fatal ( err , "Unable to load KES client certificate as specified by the shell environment" )
}
rootCAs , err := certs . GetRootCAs ( env . Get ( config . EnvKESServerCA , globalCertsCADir . Get ( ) ) )
2021-04-22 11:45:30 -04:00
if err != nil {
2021-05-10 21:15:11 -04:00
logger . Fatal ( err , fmt . Sprintf ( "Unable to load X.509 root CAs for KES from %q" , env . Get ( config . EnvKESServerCA , globalCertsCADir . Get ( ) ) ) )
2021-04-22 11:45:30 -04:00
}
2021-05-10 21:15:11 -04:00
2021-05-13 14:13:31 -04:00
var defaultKeyID = env . Get ( config . EnvKESKeyName , "" )
2021-05-10 21:15:11 -04:00
KMS , err := kms . NewWithConfig ( kms . Config {
Endpoints : endpoints ,
2021-05-13 14:13:31 -04:00
DefaultKeyID : defaultKeyID ,
2021-05-10 21:15:11 -04:00
Certificate : certificate ,
RootCAs : rootCAs ,
2021-04-22 11:45:30 -04:00
} )
if err != nil {
logger . Fatal ( err , "Unable to initialize a connection to KES as specified by the shell environment" )
2021-01-05 13:22:57 -05:00
}
2021-05-13 14:13:31 -04:00
// We check that the default key ID exists or try to create it otherwise.
// This implicitly checks that we can communicate to KES. We don't treat
// a policy error as failure condition since MinIO may not have the permission
// to create keys - just to generate/decrypt data encryption keys.
if err = KMS . CreateKey ( defaultKeyID ) ; err != nil && ! errors . Is ( err , kes . ErrKeyExists ) && ! errors . Is ( err , kes . ErrNotAllowed ) {
logger . Fatal ( err , "Unable to initialize a connection to KES as specified by the shell environment" )
}
2021-04-22 11:45:30 -04:00
GlobalKMS = KMS
2021-01-05 13:22:57 -05:00
}
2021-04-19 13:30:42 -04:00
if tiers := env . Get ( "_MINIO_DEBUG_REMOTE_TIERS_IMMEDIATELY" , "" ) ; tiers != "" {
globalDebugRemoteTiersImmediately = strings . Split ( tiers , "," )
}
2017-06-09 22:50:51 -04:00
}
2019-09-22 04:24:32 -04:00
2019-10-15 21:35:41 -04:00
func logStartupMessage ( msg string ) {
2019-09-22 13:45:33 -04:00
if globalConsoleSys != nil {
2019-10-11 21:50:54 -04:00
globalConsoleSys . Send ( msg , string ( logger . All ) )
2019-09-22 13:45:33 -04:00
}
2019-10-15 21:35:41 -04:00
logger . StartupMessage ( msg )
2019-09-22 04:24:32 -04:00
}
2019-10-08 01:47:56 -04:00
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
func getTLSConfig ( ) ( x509Certs [ ] * x509 . Certificate , manager * certs . Manager , secureConn bool , err error ) {
2019-10-08 01:47:56 -04:00
if ! ( isFile ( getPublicCertFile ( ) ) && isFile ( getPrivateKeyFile ( ) ) ) {
return nil , nil , false , nil
}
if x509Certs , err = config . ParsePublicCertFile ( getPublicCertFile ( ) ) ; err != nil {
return nil , nil , false , err
}
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
manager , err = certs . NewManager ( GlobalContext , getPublicCertFile ( ) , getPrivateKeyFile ( ) , config . LoadX509KeyPair )
2019-10-08 01:47:56 -04:00
if err != nil {
return nil , nil , false , err
}
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
// MinIO has support for multiple certificates. It expects the following structure:
// certs/
// │
// ├─ public.crt
// ├─ private.key
// │
// ├─ example.com/
// │ │
// │ ├─ public.crt
// │ └─ private.key
// └─ foobar.org/
// │
// ├─ public.crt
// └─ private.key
// ...
//
// Therefore, we read all filenames in the cert directory and check
// for each directory whether it contains a public.crt and private.key.
// If so, we try to add it to certificate manager.
root , err := os . Open ( globalCertsDir . Get ( ) )
if err != nil {
return nil , nil , false , err
}
defer root . Close ( )
files , err := root . Readdir ( - 1 )
if err != nil {
return nil , nil , false , err
}
for _ , file := range files {
2020-09-30 11:21:30 -04:00
// Ignore all
// - regular files
// - "CAs" directory
// - any directory which starts with ".."
if file . Mode ( ) . IsRegular ( ) || file . Name ( ) == "CAs" || strings . HasPrefix ( file . Name ( ) , ".." ) {
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
continue
}
2020-09-30 11:21:30 -04:00
if file . Mode ( ) & os . ModeSymlink == os . ModeSymlink {
file , err = os . Stat ( filepath . Join ( root . Name ( ) , file . Name ( ) ) )
if err != nil {
// not accessible ignore
continue
}
if ! file . IsDir ( ) {
continue
}
}
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
var (
certFile = filepath . Join ( root . Name ( ) , file . Name ( ) , publicCertFile )
keyFile = filepath . Join ( root . Name ( ) , file . Name ( ) , privateKeyFile )
)
if ! isFile ( certFile ) || ! isFile ( keyFile ) {
continue
}
2020-09-30 11:21:30 -04:00
if err = manager . AddCertificate ( certFile , keyFile ) ; err != nil {
err = fmt . Errorf ( "Unable to load TLS certificate '%s,%s': %w" , certFile , keyFile , err )
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
logger . LogIf ( GlobalContext , err , logger . Minio )
}
}
2019-10-08 01:47:56 -04:00
secureConn = true
certs: refactor cert manager to support multiple certificates (#10207)
This commit refactors the certificate management implementation
in the `certs` package such that multiple certificates can be
specified at the same time. Therefore, the following layout of
the `certs/` directory is expected:
```
certs/
│
├─ public.crt
├─ private.key
├─ CAs/ // CAs directory is ignored
│ │
│ ...
│
├─ example.com/
│ │
│ ├─ public.crt
│ └─ private.key
└─ foobar.org/
│
├─ public.crt
└─ private.key
...
```
However, directory names like `example.com` are just for human
readability/organization and don't have any meaning w.r.t whether
a particular certificate is served or not. This decision is made based
on the SNI sent by the client and the SAN of the certificate.
***
The `Manager` will pick a certificate based on the client trying
to establish a TLS connection. In particular, it looks at the client
hello (i.e. SNI) to determine which host the client tries to access.
If the manager can find a certificate that matches the SNI it
returns this certificate to the client.
However, the client may choose to not send an SNI or tries to access
a server directly via IP (`https://<ip>:<port>`). In this case, we
cannot use the SNI to determine which certificate to serve. However,
we also should not pick "the first" certificate that would be accepted
by the client (based on crypto. parameters - like a signature algorithm)
because it may be an internal certificate that contains internal hostnames.
We would disclose internal infrastructure details doing so.
Therefore, the `Manager` returns the "default" certificate when the
client does not specify an SNI. The default certificate the top-level
`public.crt` - i.e. `certs/public.crt`.
This approach has some consequences:
- It's the operator's responsibility to ensure that the top-level
`public.crt` does not disclose any information (i.e. hostnames)
that are not publicly visible. However, this was the case in the
past already.
- Any other `public.crt` - except for the top-level one - must not
contain any IP SAN. The reason for this restriction is that the
Manager cannot match a SNI to an IP b/c the SNI is the server host
name. The entire purpose of SNI is to indicate which host the client
tries to connect to when multiple hosts run on the same IP. So, a
client will not set the SNI to an IP.
If we would allow IP SANs in a lower-level `public.crt` a user would
expect that it is possible to connect to MinIO directly via IP address
and that the MinIO server would pick "the right" certificate. However,
the MinIO server cannot determine which certificate to serve, and
therefore always picks the "default" one. This may lead to all sorts
of confusing errors like:
"It works if I use `https:instance.minio.local` but not when I use
`https://10.0.2.1`.
These consequences/limitations should be pointed out / explained in our
docs in an appropriate way. However, the support for multiple
certificates should not have any impact on how deployment with a single
certificate function today.
Co-authored-by: Harshavardhana <harsha@minio.io>
2020-09-04 02:33:37 -04:00
return x509Certs , manager , secureConn , nil
2019-10-08 01:47:56 -04:00
}
2021-03-26 14:18:30 -04:00
// contextCanceled returns whether a context is canceled.
func contextCanceled ( ctx context . Context ) bool {
select {
case <- ctx . Done ( ) :
return true
default :
return false
}
}