Add new site config sub-system intended to replace region (#13672)

- New sub-system has "region" and "name" fields.

- `region` subsystem is marked as deprecated, however still works, unless the
new region parameter under `site` is set - in this case, the region subsystem is
ignored. `region` subsystem is hidden from top-level help (i.e. from `mc admin
config set myminio`), but appears when specifically requested (i.e. with `mc
admin config set myminio region`).

- MINIO_REGION, MINIO_REGION_NAME are supported as legacy environment variables for server region.

- Adds MINIO_SITE_REGION as the current environment variable to configure the
server region and MINIO_SITE_NAME for the site name.
This commit is contained in:
Aditya Manthramurthy 2021-11-25 13:06:25 -08:00 committed by GitHub
parent 81bf0c66c6
commit 4ce6d35e30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 197 additions and 82 deletions

View File

@ -1466,7 +1466,7 @@ func getServerInfo(ctx context.Context, r *http.Request) madmin.InfoMessage {
return madmin.InfoMessage{ return madmin.InfoMessage{
Mode: string(mode), Mode: string(mode),
Domain: domain, Domain: domain,
Region: globalServerRegion, Region: globalSite.Region,
SQSARN: globalNotificationSys.GetARNList(false), SQSARN: globalNotificationSys.GetARNList(false),
DeploymentID: globalDeploymentID, DeploymentID: globalDeploymentID,
Buckets: buckets, Buckets: buckets,

View File

@ -396,10 +396,10 @@ func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError
if err != nil { if err != nil {
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err) apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
} }
if globalServerRegion != "" { if globalSite.Region != "" {
switch errCode { switch errCode {
case ErrAuthorizationHeaderMalformed: case ErrAuthorizationHeaderMalformed:
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalServerRegion) apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
return apiErr return apiErr
} }
} }
@ -2286,7 +2286,7 @@ func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID,
BucketName: reqInfo.BucketName, BucketName: reqInfo.BucketName,
Key: reqInfo.ObjectName, Key: reqInfo.ObjectName,
Resource: resource, Resource: resource,
Region: globalServerRegion, Region: globalSite.Region,
RequestID: requestID, RequestID: requestID,
HostID: hostID, HostID: hostID,
} }

View File

@ -52,7 +52,7 @@ func setCommonHeaders(w http.ResponseWriter) {
// Set `x-amz-bucket-region` only if region is set on the server // Set `x-amz-bucket-region` only if region is set on the server
// by default minio uses an empty region. // by default minio uses an empty region.
if region := globalServerRegion; region != "" { if region := globalSite.Region; region != "" {
w.Header().Set(xhttp.AmzBucketRegion, region) w.Header().Set(xhttp.AmzBucketRegion, region)
} }
w.Header().Set(xhttp.AcceptRanges, "bytes") w.Header().Set(xhttp.AcceptRanges, "bytes")

View File

@ -787,9 +787,9 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
w.Header().Set(xhttp.RetryAfter, "120") w.Header().Set(xhttp.RetryAfter, "120")
case "InvalidRegion": case "InvalidRegion":
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalServerRegion) err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region)
case "AuthorizationHeaderMalformed": case "AuthorizationHeaderMalformed":
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalServerRegion) err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
} }
// Generate error response. // Generate error response.

View File

@ -299,7 +299,7 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
} }
cred, owner, s3Err = getReqAccessKeyV2(r) cred, owner, s3Err = getReqAccessKeyV2(r)
case authTypeSigned, authTypePresigned: case authTypeSigned, authTypePresigned:
region := globalServerRegion region := globalSite.Region
switch action { switch action {
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction: case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
region = "" region = ""
@ -529,7 +529,7 @@ func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool,
} }
cred, owner, s3Err = getReqAccessKeyV2(r) cred, owner, s3Err = getReqAccessKeyV2(r)
case authTypePresigned, authTypeSigned: case authTypePresigned, authTypeSigned:
region := globalServerRegion region := globalSite.Region
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone { if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
return cred, owner, s3Err return cred, owner, s3Err
} }
@ -596,7 +596,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
case authTypeSignedV2, authTypePresignedV2: case authTypeSignedV2, authTypePresignedV2:
cred, owner, s3Err = getReqAccessKeyV2(r) cred, owner, s3Err = getReqAccessKeyV2(r)
case authTypeStreamingSigned, authTypePresigned, authTypeSigned: case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
region := globalServerRegion region := globalSite.Region
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3) cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
} }
if s3Err != ErrNone { if s3Err != ErrNone {

View File

@ -397,7 +397,7 @@ func TestIsReqAuthenticated(t *testing.T) {
// Validates all testcases. // Validates all testcases.
for i, testCase := range testCases { for i, testCase := range testCases {
s3Error := isReqAuthenticated(ctx, testCase.req, globalServerRegion, serviceS3) s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3)
if s3Error != testCase.s3Error { if s3Error != testCase.s3Error {
if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error { if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code) t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
@ -435,7 +435,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
for i, testCase := range testCases { for i, testCase := range testCases {
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode { if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error) t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
} }
} }

View File

@ -211,7 +211,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
// Generate response. // Generate response.
encodedSuccessResponse := encodeResponse(LocationResponse{}) encodedSuccessResponse := encodeResponse(LocationResponse{})
// Get current region. // Get current region.
region := globalServerRegion region := globalSite.Region
if region != globalMinioDefaultRegion { if region != globalMinioDefaultRegion {
encodedSuccessResponse = encodeResponse(LocationResponse{ encodedSuccessResponse = encodeResponse(LocationResponse{
Location: region, Location: region,

View File

@ -72,8 +72,8 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
config.SetRegion(globalServerRegion) config.SetRegion(globalSite.Region)
if err = config.Validate(globalServerRegion, globalNotificationSys.targetList); err != nil { if err = config.Validate(globalSite.Region, globalNotificationSys.targetList); err != nil {
arnErr, ok := err.(*event.ErrARNNotFound) arnErr, ok := err.(*event.ErrARNNotFound)
if ok { if ok {
for i, queue := range config.QueueList { for i, queue := range config.QueueList {
@ -145,7 +145,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
return return
} }
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalServerRegion, globalNotificationSys.targetList) config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region, globalNotificationSys.targetList)
if err != nil { if err != nil {
apiErr := errorCodes.ToAPIErr(ErrMalformedXML) apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
if event.IsEventError(err) { if event.IsEventError(err) {

View File

@ -187,7 +187,7 @@ func minioConfigToConsoleFeatures() {
os.Setenv("CONSOLE_IDP_CALLBACK", getConsoleEndpoints()[0]+"/oauth_callback") os.Setenv("CONSOLE_IDP_CALLBACK", getConsoleEndpoints()[0]+"/oauth_callback")
} }
} }
os.Setenv("CONSOLE_MINIO_REGION", globalServerRegion) os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region)
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", "")) os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
if globalSubnetConfig.License != "" { if globalSubnetConfig.License != "" {
os.Setenv("CONSOLE_SUBNET_LICENSE", globalSubnetConfig.License) os.Setenv("CONSOLE_SUBNET_LICENSE", globalSubnetConfig.License)

View File

@ -30,7 +30,6 @@ import (
var errConfigNotFound = errors.New("config file not found") var errConfigNotFound = errors.New("config file not found")
func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]byte, error) { func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) ([]byte, error) {
// Read entire content by setting size to -1
r, err := objAPI.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, readLock, ObjectOptions{}) r, err := objAPI.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, readLock, ObjectOptions{})
if err != nil { if err != nil {
// Treat object not found as config not found. // Treat object not found as config not found.

View File

@ -58,6 +58,7 @@ func initHelp() {
config.IdentityOpenIDSubSys: openid.DefaultKVS, config.IdentityOpenIDSubSys: openid.DefaultKVS,
config.IdentityTLSSubSys: xtls.DefaultKVS, config.IdentityTLSSubSys: xtls.DefaultKVS,
config.PolicyOPASubSys: opa.DefaultKVS, config.PolicyOPASubSys: opa.DefaultKVS,
config.SiteSubSys: config.DefaultSiteKVS,
config.RegionSubSys: config.DefaultRegionKVS, config.RegionSubSys: config.DefaultRegionKVS,
config.APISubSys: api.DefaultKVS, config.APISubSys: api.DefaultKVS,
config.CredentialsSubSys: config.DefaultCredentialKVS, config.CredentialsSubSys: config.DefaultCredentialKVS,
@ -79,8 +80,8 @@ func initHelp() {
// Captures help for each sub-system // Captures help for each sub-system
var helpSubSys = config.HelpKVS{ var helpSubSys = config.HelpKVS{
config.HelpKV{ config.HelpKV{
Key: config.RegionSubSys, Key: config.SiteSubSys,
Description: "label the location of the server", Description: "label the server and its location",
}, },
config.HelpKV{ config.HelpKV{
Key: config.CacheSubSys, Key: config.CacheSubSys,
@ -206,6 +207,7 @@ func initHelp() {
var helpMap = map[string]config.HelpKVS{ var helpMap = map[string]config.HelpKVS{
"": helpSubSys, // Help for all sub-systems. "": helpSubSys, // Help for all sub-systems.
config.SiteSubSys: config.SiteHelp,
config.RegionSubSys: config.RegionHelp, config.RegionSubSys: config.RegionHelp,
config.APISubSys: api.Help, config.APISubSys: api.Help,
config.StorageClassSubSys: storageclass.Help, config.StorageClassSubSys: storageclass.Help,
@ -235,6 +237,16 @@ func initHelp() {
} }
config.RegisterHelpSubSys(helpMap) config.RegisterHelpSubSys(helpMap)
// save top-level help for deprecated sub-systems in a separate map.
deprecatedHelpKVMap := map[string]config.HelpKV{
config.RegionSubSys: {
Key: config.RegionSubSys,
Description: "[DEPRECATED - use `site` instead] label the location of the server",
},
}
config.RegisterHelpDeprecatedSubSys(deprecatedHelpKVMap)
} }
var ( var (
@ -259,7 +271,7 @@ func validateConfig(s config.Config) error {
return err return err
} }
if _, err := config.LookupRegion(s[config.RegionSubSys][config.Default]); err != nil { if _, err := config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default]); err != nil {
return err return err
} }
@ -437,9 +449,9 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
// but not federation. // but not federation.
globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled
globalServerRegion, err = config.LookupRegion(s[config.RegionSubSys][config.Default]) globalSite, err = config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default])
if err != nil { if err != nil {
logger.LogIf(ctx, fmt.Errorf("Invalid region configuration: %w", err)) logger.LogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err))
} }
apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default]) apiConfig, err := api.LookupConfig(s[config.APISubSys][config.Default])
@ -674,7 +686,10 @@ func GetHelp(subSys, key string, envOnly bool) (Help, error) {
subSysHelp, ok := config.HelpSubSysMap[""].Lookup(subSys) subSysHelp, ok := config.HelpSubSysMap[""].Lookup(subSys)
if !ok { if !ok {
return Help{}, config.Errorf("unknown sub-system %s", subSys) subSysHelp, ok = config.HelpDeprecatedSubSysMap[subSys]
if !ok {
return Help{}, config.Errorf("unknown sub-system %s", subSys)
}
} }
h, ok := config.HelpSubSysMap[subSys] h, ok := config.HelpSubSysMap[subSys]

View File

@ -36,18 +36,21 @@ func TestServerConfig(t *testing.T) {
t.Fatalf("Init Test config failed") t.Fatalf("Init Test config failed")
} }
if globalServerRegion != globalMinioDefaultRegion { if globalSite.Region != globalMinioDefaultRegion {
t.Errorf("Expecting region `us-east-1` found %s", globalServerRegion) t.Errorf("Expecting region `us-east-1` found %s", globalSite.Region)
} }
// Set new region and verify. // Set new region and verify.
config.SetRegion(globalServerConfig, "us-west-1") config.SetRegion(globalServerConfig, "us-west-1")
region, err := config.LookupRegion(globalServerConfig[config.RegionSubSys][config.Default]) site, err := config.LookupSite(
globalServerConfig[config.SiteSubSys][config.Default],
globalServerConfig[config.RegionSubSys][config.Default],
)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if region != "us-west-1" { if site.Region != "us-west-1" {
t.Errorf("Expecting region `us-west-1` found %s", globalServerRegion) t.Errorf("Expecting region `us-west-1` found %s", globalSite.Region)
} }
if err := saveServerConfig(context.Background(), objLayer, globalServerConfig); err != nil { if err := saveServerConfig(context.Background(), objLayer, globalServerConfig); err != nil {

View File

@ -186,11 +186,6 @@ func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, e
// ConfigSys - config system. // ConfigSys - config system.
type ConfigSys struct{} type ConfigSys struct{}
// Load - load config.json.
func (sys *ConfigSys) Load(objAPI ObjectLayer) error {
return sys.Init(objAPI)
}
// Init - initializes config system from config.json. // Init - initializes config system from config.json.
func (sys *ConfigSys) Init(objAPI ObjectLayer) error { func (sys *ConfigSys) Init(objAPI ObjectLayer) error {
if objAPI == nil { if objAPI == nil {

View File

@ -29,6 +29,7 @@ import (
"github.com/minio/console/restapi" "github.com/minio/console/restapi"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/bucket/bandwidth" "github.com/minio/minio/internal/bucket/bandwidth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/handlers" "github.com/minio/minio/internal/handlers"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/rs/dnscache" "github.com/rs/dnscache"
@ -148,8 +149,9 @@ var (
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false. // This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
globalInplaceUpdateDisabled = false globalInplaceUpdateDisabled = false
// This flag is set to 'us-east-1' by default globalSite = config.Site{
globalServerRegion = globalMinioDefaultRegion Region: globalMinioDefaultRegion,
}
// MinIO local server address (in `host:port` format) // MinIO local server address (in `host:port` format)
globalMinioAddr = "" globalMinioAddr = ""

View File

@ -58,7 +58,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError
} // else for both err as nil or io.EOF } // else for both err as nil or io.EOF
location = locationConstraint.Location location = locationConstraint.Location
if location == "" { if location == "" {
location = globalServerRegion location = globalSite.Region
} }
return location, ErrNone return location, ErrNone
} }
@ -66,7 +66,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError
// Validates input location is same as configured region // Validates input location is same as configured region
// of MinIO server. // of MinIO server.
func isValidLocation(location string) bool { func isValidLocation(location string) bool {
return globalServerRegion == "" || globalServerRegion == location return globalSite.Region == "" || globalSite.Region == location
} }
// Supported headers that needs to be extracted. // Supported headers that needs to be extracted.
@ -222,7 +222,7 @@ func extractReqParams(r *http.Request) map[string]string {
return nil return nil
} }
region := globalServerRegion region := globalSite.Region
cred := getReqAccessCred(r, region) cred := getReqAccessCred(r, region)
principalID := cred.AccessKey principalID := cred.AccessKey

View File

@ -63,7 +63,7 @@ func (sys *NotificationSys) GetARNList(onlyActive bool) []string {
if sys == nil { if sys == nil {
return arns return arns
} }
region := globalServerRegion region := globalSite.Region
for targetID, target := range sys.targetList.TargetMap() { for targetID, target := range sys.targetList.TargetMap() {
// httpclient target is part of ListenNotification // httpclient target is part of ListenNotification
// which doesn't need to be listed as part of the ARN list // which doesn't need to be listed as part of the ARN list
@ -643,8 +643,8 @@ func (sys *NotificationSys) set(bucket BucketInfo, meta BucketMetadata) {
if config == nil { if config == nil {
return return
} }
config.SetRegion(globalServerRegion) config.SetRegion(globalSite.Region)
if err := config.Validate(globalServerRegion, globalNotificationSys.targetList); err != nil { if err := config.Validate(globalSite.Region, globalNotificationSys.targetList); err != nil {
if _, ok := err.(*event.ErrARNNotFound); !ok { if _, ok := err.(*event.ErrARNNotFound); !ok {
logger.LogIf(GlobalContext, err) logger.LogIf(GlobalContext, err)
} }

View File

@ -862,7 +862,7 @@ var (
// Returns a minio-go Client configured to access remote host described by destDNSRecord // Returns a minio-go Client configured to access remote host described by destDNSRecord
// Applicable only in a federated deployment // Applicable only in a federated deployment
var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core, error) { var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core, error) {
cred := getReqAccessCred(r, globalServerRegion) cred := getReqAccessCred(r, globalSite.Region)
// In a federated deployment, all the instances share config files // In a federated deployment, all the instances share config files
// and hence expected to have same credentials. // and hence expected to have same credentials.
return miniogo.NewCore(host, &miniogo.Options{ return miniogo.NewCore(host, &miniogo.Options{
@ -1651,7 +1651,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
} }
case authTypePresigned, authTypeSigned: case authTypePresigned, authTypeSigned:
if s3Err = reqSignatureV4Verify(r, globalServerRegion, serviceS3); s3Err != ErrNone { if s3Err = reqSignatureV4Verify(r, globalSite.Region, serviceS3); s3Err != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return return
} }
@ -1982,7 +1982,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
} }
case authTypePresigned, authTypeSigned: case authTypePresigned, authTypeSigned:
if s3Err = reqSignatureV4Verify(r, globalServerRegion, serviceS3); s3Err != ErrNone { if s3Err = reqSignatureV4Verify(r, globalSite.Region, serviceS3); s3Err != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return return
} }
@ -2739,7 +2739,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
return return
} }
case authTypePresigned, authTypeSigned: case authTypePresigned, authTypeSigned:
if s3Error = reqSignatureV4Verify(r, globalServerRegion, serviceS3); s3Error != ErrNone { if s3Error = reqSignatureV4Verify(r, globalSite.Region, serviceS3); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
} }

View File

@ -126,7 +126,7 @@ func printServerCommonMsg(apiEndpoints []string) {
cred := globalActiveCred cred := globalActiveCred
// Get saved region. // Get saved region.
region := globalServerRegion region := globalSite.Region
apiEndpointStr := strings.Join(apiEndpoints, " ") apiEndpointStr := strings.Join(apiEndpoints, " ")

View File

@ -173,7 +173,7 @@ func compareSignatureV4(sig1, sig2 string) bool {
// returns ErrNone if the signature matches. // returns ErrNone if the signature matches.
func doesPolicySignatureV4Match(formValues http.Header) (auth.Credentials, APIErrorCode) { func doesPolicySignatureV4Match(formValues http.Header) (auth.Credentials, APIErrorCode) {
// Server region. // Server region.
region := globalServerRegion region := globalSite.Region
// Parse credential tag. // Parse credential tag.
credHeader, s3Err := parseCredentialHeader("Credential="+formValues.Get(xhttp.AmzCredential), region, serviceS3) credHeader, s3Err := parseCredentialHeader("Credential="+formValues.Get(xhttp.AmzCredential), region, serviceS3)

View File

@ -107,7 +107,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
now := UTCNow() now := UTCNow()
credentialTemplate := "%s/%s/%s/s3/aws4_request" credentialTemplate := "%s/%s/%s/s3/aws4_request"
region := globalServerRegion region := globalSite.Region
accessKeyID := globalActiveCred.AccessKey accessKeyID := globalActiveCred.AccessKey
testCases := []struct { testCases := []struct {
queryParams map[string]string queryParams map[string]string

View File

@ -74,7 +74,7 @@ func calculateSeedSignature(r *http.Request) (cred auth.Credentials, signature s
v4Auth := req.Header.Get(xhttp.Authorization) v4Auth := req.Header.Get(xhttp.Authorization)
// Parse signature version '4' header. // Parse signature version '4' header.
signV4Values, errCode := parseSignV4(v4Auth, globalServerRegion, serviceS3) signV4Values, errCode := parseSignV4(v4Auth, globalSite.Region, serviceS3)
if errCode != ErrNone { if errCode != ErrNone {
return cred, "", "", time.Time{}, errCode return cred, "", "", time.Time{}, errCode
} }

View File

@ -142,12 +142,12 @@ func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Creden
default: default:
return user, true, ErrSTSAccessDenied return user, true, ErrSTSAccessDenied
case authTypeSigned: case authTypeSigned:
s3Err := isReqAuthenticated(ctx, r, globalServerRegion, serviceSTS) s3Err := isReqAuthenticated(ctx, r, globalSite.Region, serviceSTS)
if s3Err != ErrNone { if s3Err != ErrNone {
return user, false, STSErrorCode(s3Err) return user, false, STSErrorCode(s3Err)
} }
user, _, s3Err = getReqAccessKeyV4(r, globalServerRegion, serviceSTS) user, _, s3Err = getReqAccessKeyV4(r, globalSite.Region, serviceSTS)
if s3Err != ErrNone { if s3Err != ErrNone {
return user, false, STSErrorCode(s3Err) return user, false, STSErrorCode(s3Err)
} }

View File

@ -725,7 +725,7 @@ func newTestStreamingRequest(method, urlStr string, dataLength, chunkSize int64,
func assembleStreamingChunks(req *http.Request, body io.ReadSeeker, chunkSize int64, func assembleStreamingChunks(req *http.Request, body io.ReadSeeker, chunkSize int64,
secretKey, signature string, currTime time.Time) (*http.Request, error) { secretKey, signature string, currTime time.Time) (*http.Request, error) {
regionStr := globalServerRegion regionStr := globalSite.Region
var stream []byte var stream []byte
var buffer []byte var buffer []byte
body.Seek(0, 0) body.Seek(0, 0)
@ -833,7 +833,7 @@ func preSignV4(req *http.Request, accessKeyID, secretAccessKey string, expires i
return errors.New("Presign cannot be generated without access and secret keys") return errors.New("Presign cannot be generated without access and secret keys")
} }
region := globalServerRegion region := globalSite.Region
date := UTCNow() date := UTCNow()
scope := getScope(date, region) scope := getScope(date, region)
credential := fmt.Sprintf("%s/%s", accessKeyID, scope) credential := fmt.Sprintf("%s/%s", accessKeyID, scope)
@ -961,7 +961,7 @@ func signRequestV4(req *http.Request, accessKey, secretKey string) error {
} }
sort.Strings(headers) sort.Strings(headers)
region := globalServerRegion region := globalSite.Region
// Get canonical headers. // Get canonical headers.
var buf bytes.Buffer var buf bytes.Buffer

View File

@ -40,30 +40,33 @@ export MINIO_ROOT_PASSWORD=minio13
minio server /data minio server /data
``` ```
#### Region #### Site
``` ```
KEY: KEY:
region label the location of the server site label the server and its location
ARGS: ARGS:
name (string) name of the location of the server e.g. "us-west-rack2" name (string) name for the site e.g. "cal-rack0"
region (string) name of the location of the server e.g. "us-west-1"
comment (sentence) optionally add a comment to this setting comment (sentence) optionally add a comment to this setting
``` ```
or environment variables or environment variables
``` ```
KEY: KEY:
region label the location of the server site label the server and its location
ARGS: ARGS:
MINIO_REGION_NAME (string) name of the location of the server e.g. "us-west-rack2" MINIO_SITE_NAME (string) name for the site e.g. "cal-rack0"
MINIO_REGION_COMMENT (sentence) optionally add a comment to this setting MINIO_SITE_REGION (string) name of the location of the server e.g. "us-west-1"
MINIO_SITE_COMMENT (sentence) optionally add a comment to this setting
``` ```
Example: Example:
```sh ```sh
export MINIO_REGION_NAME="my_region" export MINIO_SITE_REGION="us-west-0"
export MINIO_SITE_NAME="sfo-rack-1"
minio server /data minio server /data
``` ```

View File

@ -55,6 +55,8 @@ const (
EnableOn = madmin.EnableOn EnableOn = madmin.EnableOn
EnableOff = madmin.EnableOff EnableOff = madmin.EnableOff
RegionKey = "region"
NameKey = "name"
RegionName = "name" RegionName = "name"
AccessKey = "access_key" AccessKey = "access_key"
SecretKey = "secret_key" SecretKey = "secret_key"
@ -69,6 +71,7 @@ const (
IdentityLDAPSubSys = "identity_ldap" IdentityLDAPSubSys = "identity_ldap"
IdentityTLSSubSys = "identity_tls" IdentityTLSSubSys = "identity_tls"
CacheSubSys = "cache" CacheSubSys = "cache"
SiteSubSys = "site"
RegionSubSys = "region" RegionSubSys = "region"
EtcdSubSys = "etcd" EtcdSubSys = "etcd"
StorageClassSubSys = "storage_class" StorageClassSubSys = "storage_class"
@ -104,6 +107,7 @@ const (
// SubSystems - all supported sub-systems // SubSystems - all supported sub-systems
var SubSystems = set.CreateStringSet( var SubSystems = set.CreateStringSet(
CredentialsSubSys, CredentialsSubSys,
SiteSubSys,
RegionSubSys, RegionSubSys,
EtcdSubSys, EtcdSubSys,
CacheSubSys, CacheSubSys,
@ -144,6 +148,7 @@ var SubSystemsDynamic = set.CreateStringSet(
// SubSystemsSingleTargets - subsystems which only support single target. // SubSystemsSingleTargets - subsystems which only support single target.
var SubSystemsSingleTargets = set.CreateStringSet([]string{ var SubSystemsSingleTargets = set.CreateStringSet([]string{
CredentialsSubSys, CredentialsSubSys,
SiteSubSys,
RegionSubSys, RegionSubSys,
EtcdSubSys, EtcdSubSys,
CacheSubSys, CacheSubSys,
@ -202,6 +207,19 @@ func RegisterHelpSubSys(helpKVSMap map[string]HelpKVS) {
} }
} }
// HelpDeprecatedSubSysMap - help for all deprecated sub-systems, that may be
// removed in the future.
var HelpDeprecatedSubSysMap map[string]HelpKV
// RegisterHelpDeprecatedSubSys - saves input help KVS for deprecated
// sub-systems globally. Should be called only once at init.
func RegisterHelpDeprecatedSubSys(helpDeprecatedKVMap map[string]HelpKV) {
HelpDeprecatedSubSysMap = map[string]HelpKV{}
for k, v := range helpDeprecatedKVMap {
HelpDeprecatedSubSysMap[k] = v
}
}
// KV - is a shorthand of each key value. // KV - is a shorthand of each key value.
type KV struct { type KV struct {
Key string `json:"key"` Key string `json:"key"`
@ -449,6 +467,17 @@ var (
}, },
} }
DefaultSiteKVS = KVS{
KV{
Key: NameKey,
Value: "",
},
KV{
Key: RegionKey,
Value: "",
},
}
DefaultRegionKVS = KVS{ DefaultRegionKVS = KVS{
KV{ KV{
Key: RegionName, Key: RegionName,
@ -471,26 +500,66 @@ func LookupCreds(kv KVS) (auth.Credentials, error) {
return auth.CreateCredentials(accessKey, secretKey) return auth.CreateCredentials(accessKey, secretKey)
} }
// Site - holds site info - name and region.
type Site struct {
Name string
Region string
}
var validRegionRegex = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-_-]+$") var validRegionRegex = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-_-]+$")
// LookupRegion - get current region. // validSiteNameRegex - allows lowercase letters, digits and '-', starts with
func LookupRegion(kv KVS) (string, error) { // letter. At least 2 characters long.
if err := CheckValidKeys(RegionSubSys, kv, DefaultRegionKVS); err != nil { var validSiteNameRegex = regexp.MustCompile("^[a-z][a-z0-9-]+$")
return "", err
// LookupSite - get site related configuration. Loads configuration from legacy
// region sub-system as well.
func LookupSite(siteKV KVS, regionKV KVS) (s Site, err error) {
if err = CheckValidKeys(SiteSubSys, siteKV, DefaultSiteKVS); err != nil {
return
} }
region := env.Get(EnvRegion, "") region := env.Get(EnvRegion, "")
if region == "" { if region == "" {
region = env.Get(EnvRegionName, kv.Get(RegionName)) env.Get(EnvRegionName, "")
}
if region == "" {
region = env.Get(EnvSiteRegion, siteKV.Get(RegionKey))
}
if region == "" {
// No region config found in the site-subsystem. So lookup the legacy
// region sub-system.
if err = CheckValidKeys(RegionSubSys, regionKV, DefaultRegionKVS); err != nil {
// An invalid key was found in the region sub-system.
// Since the region sub-system cannot be (re)set as it
// is legacy, we return an error to tell the user to
// reset the region via the new command.
err = Errorf("could not load region from legacy configuration as it was invalid - use 'mc admin config set myminio site region=myregion name=myname' to set a region and name (%v)", err)
return
}
region = regionKV.Get(RegionName)
} }
if region != "" { if region != "" {
if validRegionRegex.MatchString(region) { if !validRegionRegex.MatchString(region) {
return region, nil err = Errorf(
"region '%s' is invalid, expected simple characters such as [us-east-1, myregion...]",
region)
return
} }
return "", Errorf( s.Region = region
"region '%s' is invalid, expected simple characters such as [us-east-1, myregion...]",
region)
} }
return "", nil
name := env.Get(EnvSiteName, siteKV.Get(NameKey))
if name != "" {
if !validSiteNameRegex.MatchString(name) {
err = Errorf(
"site name '%s' is invalid, expected simple characters such as [cal-rack0, myname...]",
name)
return
}
s.Name = name
}
return
} }
// CheckValidKeys - checks if inputs KVS has the necessary keys, // CheckValidKeys - checks if inputs KVS has the necessary keys,
@ -626,9 +695,14 @@ func (c Config) GetKVS(s string, defaultKVS map[string]KVS) (Targets, error) {
KVS: kvs, KVS: kvs,
}) })
} else { } else {
hkvs := HelpSubSysMap[""] // Use help for sub-system to preserve the order. Add deprecated
// Use help for sub-system to preserve the order. // keys at the end (in some order).
for _, hkv := range hkvs { kvsOrder := append([]HelpKV{}, HelpSubSysMap[""]...)
for _, v := range HelpDeprecatedSubSysMap {
kvsOrder = append(kvsOrder, v)
}
for _, hkv := range kvsOrder {
if !strings.HasPrefix(hkv.Key, subSysPrefix) { if !strings.HasPrefix(hkv.Key, subSysPrefix) {
continue continue
} }

View File

@ -31,12 +31,14 @@ const (
EnvBrowser = "MINIO_BROWSER" EnvBrowser = "MINIO_BROWSER"
EnvDomain = "MINIO_DOMAIN" EnvDomain = "MINIO_DOMAIN"
EnvRegionName = "MINIO_REGION_NAME"
EnvPublicIPs = "MINIO_PUBLIC_IPS" EnvPublicIPs = "MINIO_PUBLIC_IPS"
EnvFSOSync = "MINIO_FS_OSYNC" EnvFSOSync = "MINIO_FS_OSYNC"
EnvArgs = "MINIO_ARGS" EnvArgs = "MINIO_ARGS"
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT" EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
EnvSiteName = "MINIO_SITE_NAME"
EnvSiteRegion = "MINIO_SITE_REGION"
EnvMinIOSubnetLicense = "MINIO_SUBNET_LICENSE" EnvMinIOSubnetLicense = "MINIO_SUBNET_LICENSE"
EnvMinIOServerURL = "MINIO_SERVER_URL" EnvMinIOServerURL = "MINIO_SERVER_URL"
EnvMinIOBrowserRedirectURL = "MINIO_BROWSER_REDIRECT_URL" EnvMinIOBrowserRedirectURL = "MINIO_BROWSER_REDIRECT_URL"
@ -51,7 +53,8 @@ const (
EnvKESClientCert = "MINIO_KMS_KES_CERT_FILE" EnvKESClientCert = "MINIO_KMS_KES_CERT_FILE"
EnvKESServerCA = "MINIO_KMS_KES_CAPATH" EnvKESServerCA = "MINIO_KMS_KES_CAPATH"
EnvEndpoints = "MINIO_ENDPOINTS" // legacy EnvEndpoints = "MINIO_ENDPOINTS" // legacy
EnvWorm = "MINIO_WORM" // legacy EnvWorm = "MINIO_WORM" // legacy
EnvRegion = "MINIO_REGION" // legacy EnvRegion = "MINIO_REGION" // legacy
EnvRegionName = "MINIO_REGION_NAME" // legacy
) )

View File

@ -49,13 +49,34 @@ func (hkvs HelpKVS) Lookup(key string) (HelpKV, bool) {
// DefaultComment used across all sub-systems. // DefaultComment used across all sub-systems.
const DefaultComment = "optionally add a comment to this setting" const DefaultComment = "optionally add a comment to this setting"
// Region and Worm help is documented in default config // Region help is documented in default config
var ( var (
SiteHelp = HelpKVS{
HelpKV{
Key: NameKey,
Type: "string",
Description: `name for the site e.g. "cal-rack0"`,
Optional: true,
},
HelpKV{
Key: RegionKey,
Type: "string",
Description: `name of the location of the server e.g. "us-west-1"`,
Optional: true,
},
HelpKV{
Key: Comment,
Type: "sentence",
Description: DefaultComment,
Optional: true,
},
}
RegionHelp = HelpKVS{ RegionHelp = HelpKVS{
HelpKV{ HelpKV{
Key: RegionName, Key: RegionName,
Type: "string", Type: "string",
Description: `name of the location of the server e.g. "us-west-rack2"`, Description: `[DEPRECATED] name of the location of the server e.g. "us-west-rack2"`,
Optional: true, Optional: true,
}, },
HelpKV{ HelpKV{