Bring in safe mode support (#8478)

This PR refactors object layer handling such
that upon failure in sub-system initialization
server reaches a stage of safe-mode operation
wherein only certain API operations are enabled
and available.

This allows for fixing many scenarios such as

 - incorrect configuration in vault, etcd,
   notification targets
 - missing files, incomplete config migrations
   unable to read encrypted content etc
 - any other issues related to notification,
   policies, lifecycle etc
master
Harshavardhana 5 years ago committed by kannappanr
parent 1c90a6bd49
commit 822eb5ddc7
  1. 30
      cmd/admin-handlers-config-kv.go
  2. 529
      cmd/admin-handlers-users.go
  3. 530
      cmd/admin-handlers.go
  4. 6
      cmd/admin-handlers_test.go
  5. 10
      cmd/admin-heal-ops.go
  6. 39
      cmd/api-router.go
  7. 8
      cmd/background-heal-ops.go
  8. 2
      cmd/background-newdisks-heal-ops.go
  9. 19
      cmd/bucket-handlers.go
  10. 47
      cmd/config-current.go
  11. 89
      cmd/config-encrypted.go
  12. 5
      cmd/config/config.go
  13. 4
      cmd/config/etcd/etcd.go
  14. 29
      cmd/crypto/config.go
  15. 63
      cmd/crypto/legacy.go
  16. 19
      cmd/crypto/vault.go
  17. 19
      cmd/crypto/vault_test.go
  18. 2
      cmd/daily-lifecycle-ops.go
  19. 10
      cmd/disk-cache.go
  20. 64
      cmd/gateway-main.go
  21. 5
      cmd/gateway-startup-msg.go
  22. 2
      cmd/global-heal.go
  23. 4
      cmd/healthcheck-handler.go
  24. 2
      cmd/iam-object-store.go
  25. 32
      cmd/iam.go
  26. 2
      cmd/lifecycle.go
  27. 4
      cmd/metrics.go
  28. 36
      cmd/notification.go
  29. 9
      cmd/peer-rest-client.go
  30. 22
      cmd/peer-rest-server.go
  31. 2
      cmd/policy.go
  32. 144
      cmd/server-main.go
  33. 92
      cmd/server-startup-msg.go
  34. 4
      cmd/signals.go
  35. 14
      cmd/test-utils_test.go
  36. 14
      cmd/web-router.go
  37. 4
      cmd/xl-v1-multipart.go
  38. 3
      cmd/xl-v1-utils.go
  39. 4
      docs/config/README.md
  40. 7
      pkg/color/color.go
  41. 23
      pkg/event/targetlist.go

@ -33,7 +33,7 @@ import (
func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer { func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil return nil
@ -136,8 +136,15 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
} }
cfg, err := readServerConfig(ctx, objectAPI) cfg, err := readServerConfig(ctx, objectAPI)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) // Config not found for some reason, allow things to continue
return // by initializing a new fresh config in safe mode.
if err == errConfigNotFound && globalSafeMode {
cfg = newServerConfig()
err = nil
} else {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
} }
defaultKVS := configDefaultKVS() defaultKVS := configDefaultKVS()
@ -174,6 +181,9 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
// Make sure to write backend is encrypted
saveConfig(context.Background(), objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
} }
// GetConfigKVHandler - GET /minio/admin/v2/get-config-kv?key={key} // GetConfigKVHandler - GET /minio/admin/v2/get-config-kv?key={key}
@ -280,8 +290,15 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
cfg, err := readServerConfig(ctx, objectAPI) cfg, err := readServerConfig(ctx, objectAPI)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) // Config not found for some reason, allow things to continue
return // by initializing a new fresh config in safe mode.
if err == errConfigNotFound && globalSafeMode {
cfg = newServerConfig()
err = nil
} else {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
} }
defaultKVS := configDefaultKVS() defaultKVS := configDefaultKVS()
@ -425,6 +442,9 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
return return
} }
// Make sure to write backend is encrypted
saveConfig(context.Background(), objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
// Reply to the client before restarting minio server. // Reply to the client before restarting minio server.
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }

@ -0,0 +1,529 @@
/*
* MinIO Cloud Storage, (C) 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 (
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
)
func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer {
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil
}
// Validate request signature.
adminAPIErr := checkAdminRequestAuthType(ctx, r, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return nil
}
return objectAPI
}
// RemoveUser - DELETE /minio/admin/v2/remove-user?accessKey=<access_key>
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RemoveUser")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to delete user.
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// ListUsers - GET /minio/admin/v2/list-users
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListUsers")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
allCredentials, err := globalIAMSys.ListUsers()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
data, err := json.Marshal(allCredentials)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
password := globalActiveCred.SecretKey
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// GetUserInfo - GET /minio/admin/v2/user-info
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetUserInfo")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
name := vars["accessKey"]
userInfo, err := globalIAMSys.GetUserInfo(name)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
data, err := json.Marshal(userInfo)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, data)
}
// UpdateGroupMembers - PUT /minio/admin/v2/update-group-members
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "UpdateGroupMembers")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
defer r.Body.Close()
data, err := ioutil.ReadAll(r.Body)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
var updReq madmin.GroupAddRemove
err = json.Unmarshal(data, &updReq)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
if updReq.IsRemove {
err = globalIAMSys.RemoveUsersFromGroup(updReq.Group, updReq.Members)
} else {
err = globalIAMSys.AddUsersToGroup(updReq.Group, updReq.Members)
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to load group.
for _, nerr := range globalNotificationSys.LoadGroup(updReq.Group) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// GetGroup - /minio/admin/v2/group?group=mygroup1
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetGroup")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
group := vars["group"]
gdesc, err := globalIAMSys.GetGroupDescription(group)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
body, err := json.Marshal(gdesc)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, body)
}
// ListGroups - GET /minio/admin/v2/groups
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListGroups")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
groups, err := globalIAMSys.ListGroups()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
body, err := json.Marshal(groups)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, body)
}
// SetGroupStatus - PUT /minio/admin/v2/set-group-status?group=mygroup1&status=enabled
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetGroupStatus")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
group := vars["group"]
status := vars["status"]
var err error
if status == statusEnabled {
err = globalIAMSys.SetGroupStatus(group, true)
} else if status == statusDisabled {
err = globalIAMSys.SetGroupStatus(group, false)
} else {
err = errInvalidArgument
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload user.
for _, nerr := range globalNotificationSys.LoadGroup(group) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// SetUserStatus - PUT /minio/admin/v2/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetUserStatus")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
status := vars["status"]
// Custom IAM policies not allowed for admin user.
if accessKey == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
if err := globalIAMSys.SetUserStatus(accessKey, madmin.AccountStatus(status)); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload user.
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// AddUser - PUT /minio/admin/v2/add-user?accessKey=<access_key>
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddUser")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
// Custom IAM policies not allowed for admin user.
if accessKey == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
return
}
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
// More than maxConfigSize bytes were available
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
return
}
password := globalActiveCred.SecretKey
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
var uinfo madmin.UserInfo
if err = json.Unmarshal(configBytes, &uinfo); err != nil {
logger.LogIf(ctx, err)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
if err = globalIAMSys.SetUser(accessKey, uinfo); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// InfoCannedPolicy - GET /minio/admin/v2/info-canned-policy?name={policyName}
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "InfoCannedPolicy")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
data, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
w.Write(data)
w.(http.Flusher).Flush()
}
// ListCannedPolicies - GET /minio/admin/v2/list-canned-policies
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListCannedPolicies")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
policies, err := globalIAMSys.ListPolicies()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if err = json.NewEncoder(w).Encode(policies); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
w.(http.Flusher).Flush()
}
// RemoveCannedPolicy - DELETE /minio/admin/v2/remove-canned-policy?name=<policy_name>
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RemoveCannedPolicy")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
policyName := vars["name"]
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to delete policy
for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// AddCannedPolicy - PUT /minio/admin/v2/add-canned-policy?name=<policy_name>
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddCannedPolicy")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
policyName := vars["name"]
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
// Error out if Content-Length is missing.
if r.ContentLength <= 0 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL)
return
}
// Error out if Content-Length is beyond allowed size.
if r.ContentLength > maxBucketPolicySize {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL)
return
}
iamPolicy, err := iampolicy.ParseConfig(io.LimitReader(r.Body, r.ContentLength))
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
return
}
// Version in policy must not be empty
if iamPolicy.Version == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
return
}
if err = globalIAMSys.SetPolicy(policyName, *iamPolicy); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload policy
for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// SetPolicyForUserOrGroup - PUT /minio/admin/v2/set-policy?policy=xxx&user-or-group=?[&is-group]
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
objectAPI := validateAdminUsersReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
policyName := vars["policyName"]
entityName := vars["userOrGroup"]
isGroup := vars["isGroup"] == "true"
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
if err := globalIAMSys.PolicyDBSet(entityName, policyName, isGroup); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload policy
for _, nerr := range globalNotificationSys.LoadPolicyMapping(entityName, isGroup) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}

@ -22,7 +22,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -42,7 +41,6 @@ import (
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/cpu" "github.com/minio/minio/pkg/cpu"
"github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/handlers"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/mem" "github.com/minio/minio/pkg/mem"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
@ -94,7 +92,7 @@ func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us
return us, nil return us, nil
} }
// ServerUpdateHandler - POST /minio/admin/v1/update?updateURL={updateURL} // ServerUpdateHandler - POST /minio/admin/v2/update?updateURL={updateURL}
// ---------- // ----------
// updates all minio servers and restarts them gracefully. // updates all minio servers and restarts them gracefully.
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
@ -173,7 +171,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
} }
} }
// ServiceActionHandler - POST /minio/admin/v1/service?action={action} // ServiceActionHandler - POST /minio/admin/v2/service?action={action}
// ---------- // ----------
// restarts/stops minio server gracefully. In a distributed setup, // restarts/stops minio server gracefully. In a distributed setup,
func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Request) {
@ -262,7 +260,7 @@ type ServerInfo struct {
Data *ServerInfoData `json:"data"` Data *ServerInfoData `json:"data"`
} }
// ServerInfoHandler - GET /minio/admin/v1/info // ServerInfoHandler - GET /minio/admin/v2/info
// ---------- // ----------
// Get server information // Get server information
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
@ -303,7 +301,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
writeSuccessResponseJSON(w, jsonBytes) writeSuccessResponseJSON(w, jsonBytes)
} }
// ServerInfoHandler - GET /minio/admin/v1/storageinfo // ServerInfoHandler - GET /minio/admin/v2/storageinfo
// ---------- // ----------
// Get server information // Get server information
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
@ -355,7 +353,7 @@ type ServerNetReadPerfInfo struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
} }
// PerfInfoHandler - GET /minio/admin/v1/performance?perfType={perfType} // PerfInfoHandler - GET /minio/admin/v2/performance?perfType={perfType}
// ---------- // ----------
// Get all performance information based on input type // Get all performance information based on input type
// Supported types = drive // Supported types = drive
@ -561,7 +559,7 @@ type StartProfilingResult struct {
Error string `json:"error"` Error string `json:"error"`
} }
// StartProfilingHandler - POST /minio/admin/v1/profiling/start?profilerType={profilerType} // StartProfilingHandler - POST /minio/admin/v2/profiling/start?profilerType={profilerType}
// ---------- // ----------
// Enable server profiling // Enable server profiling
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
@ -643,7 +641,7 @@ func (f dummyFileInfo) ModTime() time.Time { return f.modTime }
func (f dummyFileInfo) IsDir() bool { return f.isDir } func (f dummyFileInfo) IsDir() bool { return f.isDir }
func (f dummyFileInfo) Sys() interface{} { return f.sys } func (f dummyFileInfo) Sys() interface{} { return f.sys }
// DownloadProfilingHandler - POST /minio/admin/v1/profiling/download // DownloadProfilingHandler - POST /minio/admin/v2/profiling/download
// ---------- // ----------
// Download profiling information of all nodes in a zip format // Download profiling information of all nodes in a zip format
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
@ -725,7 +723,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
return return
} }
// HealHandler - POST /minio/admin/v1/heal/ // HealHandler - POST /minio/admin/v2/heal/
// ----------- // -----------
// Start heal processing and return heal status items. // Start heal processing and return heal status items.
// //
@ -927,8 +925,8 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer { func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil { if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil return nil
} }
@ -992,490 +990,17 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
HTTPStatusCode: e.StatusCode, HTTPStatusCode: e.StatusCode,
} }
default: default:
apiErr = errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err)) if err == errConfigNotFound {
} apiErr = APIError{
return apiErr Code: "XMinioConfigError",
} Description: err.Error(),
HTTPStatusCode: http.StatusNotFound,
// RemoveUser - DELETE /minio/admin/v1/remove-user?accessKey=<access_key> }
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) { } else {
ctx := newContext(r, w, "RemoveUser") apiErr = errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to delete user.
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// ListUsers - GET /minio/admin/v1/list-users
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListUsers")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
allCredentials, err := globalIAMSys.ListUsers()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
data, err := json.Marshal(allCredentials)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
password := globalActiveCred.SecretKey
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// GetUserInfo - GET /minio/admin/v1/user-info
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetUserInfo")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
name := vars["accessKey"]
userInfo, err := globalIAMSys.GetUserInfo(name)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
data, err := json.Marshal(userInfo)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, data)
}
// UpdateGroupMembers - PUT /minio/admin/v1/update-group-members
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "UpdateGroupMembers")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
defer r.Body.Close()
data, err := ioutil.ReadAll(r.Body)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
var updReq madmin.GroupAddRemove
err = json.Unmarshal(data, &updReq)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
if updReq.IsRemove {
err = globalIAMSys.RemoveUsersFromGroup(updReq.Group, updReq.Members)
} else {
err = globalIAMSys.AddUsersToGroup(updReq.Group, updReq.Members)
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to load group.
for _, nerr := range globalNotificationSys.LoadGroup(updReq.Group) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// GetGroup - /minio/admin/v1/group?group=mygroup1
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetGroup")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
group := vars["group"]
gdesc, err := globalIAMSys.GetGroupDescription(group)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
body, err := json.Marshal(gdesc)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, body)
}
// ListGroups - GET /minio/admin/v1/groups
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListGroups")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
groups, err := globalIAMSys.ListGroups()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
body, err := json.Marshal(groups)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, body)
}
// SetGroupStatus - PUT /minio/admin/v1/set-group-status?group=mygroup1&status=enabled
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetGroupStatus")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
group := vars["group"]
status := vars["status"]
var err error
if status == statusEnabled {
err = globalIAMSys.SetGroupStatus(group, true)
} else if status == statusDisabled {
err = globalIAMSys.SetGroupStatus(group, false)
} else {
err = errInvalidArgument
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload user.
for _, nerr := range globalNotificationSys.LoadGroup(group) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// SetUserStatus - PUT /minio/admin/v1/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetUserStatus")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
status := vars["status"]
// Custom IAM policies not allowed for admin user.
if accessKey == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
if err := globalIAMSys.SetUserStatus(accessKey, madmin.AccountStatus(status)); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload user.
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// AddUser - PUT /minio/admin/v1/add-user?accessKey=<access_key>
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddUser")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
// Custom IAM policies not allowed for admin user.
if accessKey == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
return
}
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
// More than maxConfigSize bytes were available
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
return
}
password := globalActiveCred.SecretKey
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
var uinfo madmin.UserInfo
if err = json.Unmarshal(configBytes, &uinfo); err != nil {
logger.LogIf(ctx, err)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
if err = globalIAMSys.SetUser(accessKey, uinfo); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// InfoCannedPolicy - GET /minio/admin/v1/info-canned-policy?name={policyName}
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "InfoCannedPolicy")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
data, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
w.Write(data)
w.(http.Flusher).Flush()
}
// ListCannedPolicies - GET /minio/admin/v1/list-canned-policies
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListCannedPolicies")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
policies, err := globalIAMSys.ListPolicies()
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if err = json.NewEncoder(w).Encode(policies); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
w.(http.Flusher).Flush()
}
// RemoveCannedPolicy - DELETE /minio/admin/v1/remove-canned-policy?name=<policy_name>
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RemoveCannedPolicy")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
policyName := vars["name"]
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to delete policy
for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// AddCannedPolicy - PUT /minio/admin/v1/add-canned-policy?name=<policy_name>
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddCannedPolicy")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
policyName := vars["name"]
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
// Error out if Content-Length is missing.
if r.ContentLength <= 0 {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL)
return
}
// Error out if Content-Length is beyond allowed size.
if r.ContentLength > maxBucketPolicySize {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL)
return
}
iamPolicy, err := iampolicy.ParseConfig(io.LimitReader(r.Body, r.ContentLength))
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
return
}
// Version in policy must not be empty
if iamPolicy.Version == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
return
}
if err = globalIAMSys.SetPolicy(policyName, *iamPolicy); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload policy
for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
}
// SetPolicyForUserOrGroup - PUT /minio/admin/v1/set-policy?policy=xxx&user-or-group=?[&is-group]
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
objectAPI := validateAdminReq(ctx, w, r)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
policyName := vars["policyName"]
entityName := vars["userOrGroup"]
isGroup := vars["isGroup"] == "true"
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
if err := globalIAMSys.PolicyDBSet(entityName, policyName, isGroup); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other MinIO peers to reload policy
for _, nerr := range globalNotificationSys.LoadPolicyMapping(entityName, isGroup) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
} }
} }
return apiErr
} }
// Returns true if the trace.Info should be traced, // Returns true if the trace.Info should be traced,
@ -1495,7 +1020,7 @@ func mustTrace(entry interface{}, trcAll, errOnly bool) bool {
return trace return trace
} }
// TraceHandler - POST /minio/admin/v1/trace // TraceHandler - POST /minio/admin/v2/trace
// ---------- // ----------
// The handler sends http trace to the connected HTTP client. // The handler sends http trace to the connected HTTP client.
func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
@ -1519,13 +1044,16 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
// Use buffered channel to take care of burst sends or slow w.Write() // Use buffered channel to take care of burst sends or slow w.Write()
traceCh := make(chan interface{}, 4000) traceCh := make(chan interface{}, 4000)
peers := getRestClients(getRemoteHosts(globalEndpoints)) peers := getRestClients(globalEndpoints)
globalHTTPTrace.Subscribe(traceCh, doneCh, func(entry interface{}) bool { globalHTTPTrace.Subscribe(traceCh, doneCh, func(entry interface{}) bool {
return mustTrace(entry, trcAll, trcErr) return mustTrace(entry, trcAll, trcErr)
}) })
for _, peer := range peers { for _, peer := range peers {
if peer == nil {
continue
}
peer.Trace(traceCh, doneCh, trcAll, trcErr) peer.Trace(traceCh, doneCh, trcAll, trcErr)
} }
@ -1583,12 +1111,14 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
defer close(doneCh) defer close(doneCh)
logCh := make(chan interface{}, 4000) logCh := make(chan interface{}, 4000)
remoteHosts := getRemoteHosts(globalEndpoints) peers := getRestClients(globalEndpoints)
peers := getRestClients(remoteHosts)
globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, logKind, nil) globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, logKind, nil)
for _, peer := range peers { for _, peer := range peers {
if peer == nil {
continue
}
if node == "" || strings.EqualFold(peer.host.Name, node) { if node == "" || strings.EqualFold(peer.host.Name, node) {
peer.ConsoleLog(logCh, doneCh) peer.ConsoleLog(logCh, doneCh)
} }
@ -1620,7 +1150,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
} }
} }
// KMSKeyStatusHandler - GET /minio/admin/v1/kms/key/status?key-id=<master-key-id> // KMSKeyStatusHandler - GET /minio/admin/v2/kms/key/status?key-id=<master-key-id>
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "KMSKeyStatusHandler") ctx := newContext(r, w, "KMSKeyStatusHandler")
@ -1702,7 +1232,7 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
writeSuccessResponseJSON(w, resp) writeSuccessResponseJSON(w, resp)
} }
// ServerHardwareInfoHandler - GET /minio/admin/v1/hardwareinfo?Type={hwType} // ServerHardwareInfoHandler - GET /minio/admin/v2/hardwareinfo?Type={hwType}
// ---------- // ----------
// Get all hardware information based on input type // Get all hardware information based on input type
// Supported types = cpu // Supported types = cpu

@ -90,11 +90,7 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) {
globalPolicySys = NewPolicySys() globalPolicySys = NewPolicySys()
globalPolicySys.Init(buckets, objLayer) globalPolicySys.Init(buckets, objLayer)
globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints) globalNotificationSys = NewNotificationSys(globalEndpoints)
if err != nil {
return nil, err
}
globalNotificationSys.Init(buckets, objLayer) globalNotificationSys.Init(buckets, objLayer)
// Setup admin mgmt REST API handlers. // Setup admin mgmt REST API handlers.

@ -660,7 +660,7 @@ func (h *healSequence) traverseAndHeal() {
func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error { func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
return func() error { return func() error {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -692,7 +692,7 @@ func (h *healSequence) healDiskFormat() error {
} }
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -712,7 +712,7 @@ func (h *healSequence) healBuckets() error {
} }
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -734,7 +734,7 @@ func (h *healSequence) healBuckets() error {
// healBucket - traverses and heals given bucket // healBucket - traverses and heals given bucket
func (h *healSequence) healBucket(bucket string) error { func (h *healSequence) healBucket(bucket string) error {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -771,7 +771,7 @@ func (h *healSequence) healObject(bucket, object string) error {
} }
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }

@ -23,6 +23,31 @@ import (
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
) )
func newObjectLayerWithoutSafeModeFn() ObjectLayer {
globalObjLayerMutex.Lock()
defer globalObjLayerMutex.Unlock()
return globalObjectAPI
}
func newObjectLayerFn() ObjectLayer {
globalObjLayerMutex.Lock()
defer globalObjLayerMutex.Unlock()
if globalSafeMode {
return nil
}
return globalObjectAPI
}
func newCachedObjectLayerFn() CacheObjectLayer {
globalObjLayerMutex.Lock()
defer globalObjLayerMutex.Unlock()
if globalSafeMode {
return nil
}
return globalCacheObjectAPI
}
// objectAPIHandler implements and provides http handlers for S3 API. // objectAPIHandler implements and provides http handlers for S3 API.
type objectAPIHandlers struct { type objectAPIHandlers struct {
ObjectAPI func() ObjectLayer ObjectAPI func() ObjectLayer
@ -37,18 +62,8 @@ type objectAPIHandlers struct {
func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) { func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) {
// Initialize API. // Initialize API.
api := objectAPIHandlers{ api := objectAPIHandlers{
ObjectAPI: func() ObjectLayer { ObjectAPI: newObjectLayerFn,
if !globalSafeMode { CacheAPI: newCachedObjectLayerFn,
return globalObjectAPI
}
return nil
},
CacheAPI: func() CacheObjectLayer {
if !globalSafeMode {
return globalCacheObjectAPI
}
return nil
},
EncryptionEnabled: func() bool { EncryptionEnabled: func() bool {
return encryptionEnabled return encryptionEnabled
}, },

@ -107,7 +107,7 @@ func startBackgroundHealing() {
var objAPI ObjectLayer var objAPI ObjectLayer
for { for {
objAPI = globalObjectAPI objAPI = newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
@ -135,7 +135,7 @@ func initBackgroundHealing() {
// failure error occurred. // failure error occurred.
func bgHealDiskFormat(ctx context.Context, opts madmin.HealOpts) (madmin.HealResultItem, error) { func bgHealDiskFormat(ctx context.Context, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return madmin.HealResultItem{}, errServerNotInitialized return madmin.HealResultItem{}, errServerNotInitialized
} }
@ -165,7 +165,7 @@ func bgHealDiskFormat(ctx context.Context, opts madmin.HealOpts) (madmin.HealRes
// bghealBucket - traverses and heals given bucket // bghealBucket - traverses and heals given bucket
func bgHealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) { func bgHealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return madmin.HealResultItem{}, errServerNotInitialized return madmin.HealResultItem{}, errServerNotInitialized
} }
@ -176,7 +176,7 @@ func bgHealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (mad
// bgHealObject - heal the given object and record result // bgHealObject - heal the given object and record result
func bgHealObject(ctx context.Context, bucket, object string, opts madmin.HealOpts) (madmin.HealResultItem, error) { func bgHealObject(ctx context.Context, bucket, object string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
// Get current object layer instance. // Get current object layer instance.
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return madmin.HealResultItem{}, errServerNotInitialized return madmin.HealResultItem{}, errServerNotInitialized
} }

@ -36,7 +36,7 @@ func monitorLocalDisksAndHeal() {
// Wait until the object layer is ready // Wait until the object layer is ready
var objAPI ObjectLayer var objAPI ObjectLayer
for { for {
objAPI = globalObjectAPI objAPI = newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue

@ -51,11 +51,8 @@ import (
// -- If no, make an entry // -- If no, make an entry
// -- If yes, check if the IP of entry matches local IP. This means entry is for this instance. // -- If yes, check if the IP of entry matches local IP. This means entry is for this instance.
// -- If IP of the entry doesn't match, this means entry is for another instance. Log an error to console. // -- If IP of the entry doesn't match, this means entry is for another instance. Log an error to console.
func initFederatorBackend(objLayer ObjectLayer) { func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
// Get buckets in the backend if len(buckets) == 0 {
b, err := objLayer.ListBuckets(context.Background())
if err != nil {
logger.LogIf(context.Background(), err)
return return
} }
@ -69,21 +66,21 @@ func initFederatorBackend(objLayer ObjectLayer) {
bucketSet := set.NewStringSet() bucketSet := set.NewStringSet()
// Add buckets that are not registered with the DNS // Add buckets that are not registered with the DNS
g := errgroup.WithNErrs(len(b)) g := errgroup.WithNErrs(len(buckets))
for index := range b { for index := range buckets {
bucketSet.Add(b[index].Name) bucketSet.Add(buckets[index].Name)
index := index index := index
g.Go(func() error { g.Go(func() error {
r, gerr := globalDNSConfig.Get(b[index].Name) r, gerr := globalDNSConfig.Get(buckets[index].Name)
if gerr != nil { if gerr != nil {
if gerr == dns.ErrNoEntriesFound { if gerr == dns.ErrNoEntriesFound {
return globalDNSConfig.Put(b[index].Name) return globalDNSConfig.Put(buckets[index].Name)
} }
return gerr return gerr
} }
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() { if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() {
// There is already an entry for this bucket, with all IP addresses different. This indicates a bucket name collision. Log an error and continue. // There is already an entry for this bucket, with all IP addresses different. This indicates a bucket name collision. Log an error and continue.
return fmt.Errorf("Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket. Use one of these IP addresses %v to access the bucket", b[index].Name, globalDomainIPs.ToSlice()) return fmt.Errorf("Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket. Use one of these IP addresses %v to access the bucket", buckets[index].Name, globalDomainIPs.ToSlice())
} }
return nil return nil
}, index) }, index)

@ -48,51 +48,84 @@ func validateConfig(s config.Config) error {
// Disable merging env values with config for validation. // Disable merging env values with config for validation.
env.SetEnvOff() env.SetEnvOff()
// Enable env values upon return. // Enable env values to validate KMS.
defer env.SetEnvOn() defer env.SetEnvOn()
if _, err := config.LookupCreds(s[config.CredentialsSubSys][config.Default]); err != nil { if _, err := config.LookupCreds(s[config.CredentialsSubSys][config.Default]); err != nil {
return err return err
} }
if _, err := config.LookupRegion(s[config.RegionSubSys][config.Default]); err != nil { if _, err := config.LookupRegion(s[config.RegionSubSys][config.Default]); err != nil {
return err return err
} }
if _, err := config.LookupWorm(s[config.WormSubSys][config.Default]); err != nil { if _, err := config.LookupWorm(s[config.WormSubSys][config.Default]); err != nil {
return err return err
} }
if globalIsXL { if globalIsXL {
if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default], if _, err := storageclass.LookupConfig(s[config.StorageClassSubSys][config.Default],
globalXLSetDriveCount); err != nil { globalXLSetDriveCount); err != nil {
return err return err
} }
} }
if _, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs); err != nil {
return err
}
if _, err := cache.LookupConfig(s[config.CacheSubSys][config.Default]); err != nil { if _, err := cache.LookupConfig(s[config.CacheSubSys][config.Default]); err != nil {
return err return err
} }
if _, err := crypto.LookupConfig(s[config.KmsVaultSubSys][config.Default]); err != nil {
return err
}
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil { if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
return err return err
} }
{
etcdCfg, err := etcd.LookupConfig(s[config.EtcdSubSys][config.Default], globalRootCAs)
if err != nil {
return err
}
if etcdCfg.Enabled {
etcdClnt, err := etcd.New(etcdCfg)
if err != nil {
return err
}
etcdClnt.Close()
}
}
{
kmsCfg, err := crypto.LookupConfig(s[config.KmsVaultSubSys][config.Default])
if err != nil {
return err
}
if kmsCfg.Vault.Enabled {
// Set env to enable master key validation.
// this is needed only for KMS.
env.SetEnvOn()
if _, err = crypto.NewKMS(kmsCfg); err != nil {
return err
}
}
}
if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
NewCustomHTTPTransport(), xhttp.DrainBody); err != nil { NewCustomHTTPTransport(), xhttp.DrainBody); err != nil {
return err return err
} }
if _, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default], if _, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default],
globalRootCAs); err != nil { globalRootCAs); err != nil {
return err return err
} }
if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default], if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default],
NewCustomHTTPTransport(), xhttp.DrainBody); err != nil { NewCustomHTTPTransport(), xhttp.DrainBody); err != nil {
return err return err
} }
if _, err := logger.LookupConfig(s); err != nil { if _, err := logger.LookupConfig(s); err != nil {
return err return err
} }
return notify.TestNotificationTargets(s, GlobalServiceDoneCh, globalRootCAs) return notify.TestNotificationTargets(s, GlobalServiceDoneCh, globalRootCAs)
} }

@ -19,9 +19,11 @@ package cmd
import ( import (
"bytes" "bytes"
"context" "context"
"os"
"strings" "strings"
etcd "github.com/coreos/etcd/clientv3" etcd "github.com/coreos/etcd/clientv3"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
@ -97,6 +99,8 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
if err != nil { if err != nil {
return err return err
} }
os.Unsetenv(config.EnvAccessKeyOld)
os.Unsetenv(config.EnvSecretKeyOld)
} }
// Migrating Config backend needs a retry mechanism for // Migrating Config backend needs a retry mechanism for
@ -124,7 +128,8 @@ const (
) )
var ( var (
backendEncryptedFileValue = []byte("encrypted") backendEncryptedMigrationIncomplete = []byte("incomplete")
backendEncryptedMigrationComplete = []byte("encrypted")
) )
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) { func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
@ -132,7 +137,7 @@ func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool,
if err != nil && err != errConfigNotFound { if err != nil && err != errConfigNotFound {
return false, err return false, err
} }
return err == nil && bytes.Equal(data, backendEncryptedFileValue), nil return err == nil && bytes.Equal(data, backendEncryptedMigrationComplete), nil
} }
func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) { func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
@ -140,7 +145,7 @@ func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
if err != nil && err != errConfigNotFound { if err != nil && err != errConfigNotFound {
return false, err return false, err
} }
return err == nil && bytes.Equal(data, backendEncryptedFileValue), nil return err == nil && bytes.Equal(data, backendEncryptedMigrationComplete), nil
} }
func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error { func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
@ -178,6 +183,9 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
if err != nil { if err != nil {
return err return err
} }
// Once we have obtained the rotating creds
os.Unsetenv(config.EnvAccessKeyOld)
os.Unsetenv(config.EnvSecretKeyOld)
} }
if encrypted { if encrypted {
@ -191,18 +199,21 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
if activeCredOld.Equal(globalActiveCred) { if activeCredOld.Equal(globalActiveCred) {
return nil return nil
} }
}
if !activeCredOld.IsValid() { logger.Info("Attempting rotation of encrypted IAM users and policies on etcd with newly supplied credentials")
logger.Info("Attempting a one time encrypt of all IAM users and policies on etcd")
} else { } else {
logger.Info("Attempting a rotation of encrypted IAM users and policies on etcd with newly supplied credentials") logger.Info("Attempting encryption of all IAM users and policies on etcd")
} }
r, err := client.Get(ctx, minioConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly()) r, err := client.Get(ctx, minioConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil { if err != nil {
return err return err
} }
if err = saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationIncomplete); err != nil {
return err
}
for _, kv := range r.Kvs { for _, kv := range r.Kvs {
var ( var (
cdata []byte cdata []byte
@ -217,18 +228,32 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
} }
return err return err
} }
var data []byte
// Is rotating of creds requested? // Is rotating of creds requested?
if activeCredOld.IsValid() { if activeCredOld.IsValid() {
cdata, err = madmin.DecryptData(activeCredOld.String(), bytes.NewReader(cdata)) data, err = madmin.DecryptData(activeCredOld.String(), bytes.NewReader(cdata))
if err != nil { if err != nil {
if err == madmin.ErrMaliciousData { if err == madmin.ErrMaliciousData {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil) data, err = madmin.DecryptData(globalActiveCred.String(),
bytes.NewReader(cdata))
if err != nil {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
}
} else {
return err
} }
return err
} }
} }
cencdata, err = madmin.EncryptData(globalActiveCred.String(), cdata) // Attempt to unmarshal JSON content
var dummy map[string]interface{}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(data, &dummy); err != nil {
return err
}
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
if err != nil { if err != nil {
return err return err
} }
@ -237,7 +262,10 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
return err return err
} }
} }
return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedFileValue) if encrypted && globalActiveCred.IsValid() {
logger.Info("Rotation complete, please make sure to unset MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD envs")
}
return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationComplete)
} }
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Credentials, encrypted bool) error { func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Credentials, encrypted bool) error {
@ -252,12 +280,14 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
if activeCredOld.Equal(globalActiveCred) { if activeCredOld.Equal(globalActiveCred) {
return nil return nil
} }
logger.Info("Attempting rotation of encrypted config, IAM users and policies on MinIO with newly supplied credentials")
} else {
logger.Info("Attempting encryption of all config, IAM users and policies on MinIO backend")
} }
if !activeCredOld.IsValid() { err := saveConfig(context.Background(), objAPI, backendEncryptedFile, backendEncryptedMigrationIncomplete)
logger.Info("Attempting a one time encrypt of all config, IAM users and policies on MinIO backend") if err != nil {
} else { return err
logger.Info("Attempting a rotation of encrypted config, IAM users and policies on MinIO with newly supplied credentials")
} }
var marker string var marker string
@ -277,18 +307,31 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
return err return err
} }
var data []byte
// Is rotating of creds requested? // Is rotating of creds requested?
if activeCredOld.IsValid() { if activeCredOld.IsValid() {
cdata, err = madmin.DecryptData(activeCredOld.String(), bytes.NewReader(cdata)) data, err = madmin.DecryptData(activeCredOld.String(), bytes.NewReader(cdata))
if err != nil { if err != nil {
if err == madmin.ErrMaliciousData { if err == madmin.ErrMaliciousData {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil) data, err = madmin.DecryptData(globalActiveCred.String(),
bytes.NewReader(cdata))
if err != nil {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
}
} else {
return err
} }
return err
} }
} }
cencdata, err = madmin.EncryptData(globalActiveCred.String(), cdata) // Attempt to unmarshal JSON content
var dummy map[string]interface{}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(data, &dummy); err != nil {
return err
}
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
if err != nil { if err != nil {
return err return err
} }
@ -305,5 +348,9 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
marker = res.NextMarker marker = res.NextMarker
} }
return saveConfig(context.Background(), objAPI, backendEncryptedFile, backendEncryptedFileValue) if encrypted && globalActiveCred.IsValid() {
logger.Info("Rotation complete, please make sure to unset MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD envs")
}
return saveConfig(context.Background(), objAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
} }

@ -386,11 +386,6 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error {
return Error(fmt.Sprintf("invalid number of arguments %s", s)) return Error(fmt.Sprintf("invalid number of arguments %s", s))
} }
if subSystemValue[0] == CredentialsSubSys {
return Error(fmt.Sprintf("changing '%s' sub-system values is not allowed, use ENVs instead",
subSystemValue[0]))
}
if !SubSystems.Contains(subSystemValue[0]) { if !SubSystems.Contains(subSystemValue[0]) {
return Error(fmt.Sprintf("unknown sub-system %s", s)) return Error(fmt.Sprintf("unknown sub-system %s", s))
} }

@ -31,7 +31,7 @@ import (
const ( const (
// Default values used while communicating with etcd. // Default values used while communicating with etcd.
defaultDialTimeout = 30 * time.Second defaultDialTimeout = 5 * time.Second
defaultDialKeepAlive = 30 * time.Second defaultDialKeepAlive = 30 * time.Second
) )
@ -112,7 +112,7 @@ func LookupConfig(kv config.KVS, rootCAs *x509.CertPool) (Config, error) {
return cfg, config.Error("'endpoints' key cannot be empty if you wish to enable etcd") return cfg, config.Error("'endpoints' key cannot be empty if you wish to enable etcd")
} }
if len(endpoints) == 0 { if len(endpoints) == 0 && !stateBool {
return cfg, nil return cfg, nil
} }

@ -17,6 +17,7 @@ package crypto
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"strconv" "strconv"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
@ -112,6 +113,12 @@ const (
EnvKMSVaultNamespace = "MINIO_KMS_VAULT_NAMESPACE" EnvKMSVaultNamespace = "MINIO_KMS_VAULT_NAMESPACE"
) )
var defaultCfg = VaultConfig{
Auth: VaultAuth{
Type: "approle",
},
}
// LookupConfig extracts the KMS configuration provided by environment // LookupConfig extracts the KMS configuration provided by environment
// variables and merge them with the provided KMS configuration. The // variables and merge them with the provided KMS configuration. The
// merging follows the following rules: // merging follows the following rules:
@ -139,7 +146,7 @@ func LookupConfig(kvs config.KVS) (KMSConfig, error) {
return kmsCfg, err return kmsCfg, err
} }
} }
if !kmsCfg.Vault.IsEmpty() { if kmsCfg.Vault.Enabled {
return kmsCfg, nil return kmsCfg, nil
} }
stateBool, err := config.ParseBool(env.Get(EnvKMSVaultState, kvs.Get(config.State))) stateBool, err := config.ParseBool(env.Get(EnvKMSVaultState, kvs.Get(config.State)))
@ -166,23 +173,30 @@ func LookupConfig(kvs config.KVS) (KMSConfig, error) {
vcfg.Endpoint = endpointStr vcfg.Endpoint = endpointStr
vcfg.CAPath = env.Get(EnvKMSVaultCAPath, kvs.Get(KMSVaultCAPath)) vcfg.CAPath = env.Get(EnvKMSVaultCAPath, kvs.Get(KMSVaultCAPath))
vcfg.Auth.Type = env.Get(EnvKMSVaultAuthType, kvs.Get(KMSVaultAuthType)) vcfg.Auth.Type = env.Get(EnvKMSVaultAuthType, kvs.Get(KMSVaultAuthType))
if vcfg.Auth.Type == "" {
vcfg.Auth.Type = "approle"
}
vcfg.Auth.AppRole.ID = env.Get(EnvKMSVaultAppRoleID, kvs.Get(KMSVaultAppRoleID)) vcfg.Auth.AppRole.ID = env.Get(EnvKMSVaultAppRoleID, kvs.Get(KMSVaultAppRoleID))
vcfg.Auth.AppRole.Secret = env.Get(EnvKMSVaultAppSecretID, kvs.Get(KMSVaultAppRoleSecret)) vcfg.Auth.AppRole.Secret = env.Get(EnvKMSVaultAppSecretID, kvs.Get(KMSVaultAppRoleSecret))
vcfg.Key.Name = env.Get(EnvKMSVaultKeyName, kvs.Get(KMSVaultKeyName)) vcfg.Key.Name = env.Get(EnvKMSVaultKeyName, kvs.Get(KMSVaultKeyName))
vcfg.Namespace = env.Get(EnvKMSVaultNamespace, kvs.Get(KMSVaultNamespace)) vcfg.Namespace = env.Get(EnvKMSVaultNamespace, kvs.Get(KMSVaultNamespace))
keyVersion := env.Get(EnvKMSVaultKeyVersion, kvs.Get(KMSVaultKeyVersion)) if keyVersion := env.Get(EnvKMSVaultKeyVersion, kvs.Get(KMSVaultKeyVersion)); keyVersion != "" {
if keyVersion != "" {
vcfg.Key.Version, err = strconv.Atoi(keyVersion) vcfg.Key.Version, err = strconv.Atoi(keyVersion)
if err != nil { if err != nil {
return kmsCfg, fmt.Errorf("Unable to parse VaultKeyVersion value (`%s`)", keyVersion) return kmsCfg, fmt.Errorf("Unable to parse VaultKeyVersion value (`%s`)", keyVersion)
} }
} }
if reflect.DeepEqual(vcfg, defaultCfg) {
return kmsCfg, nil
}
// Verify all the proper settings.
if err = vcfg.Verify(); err != nil { if err = vcfg.Verify(); err != nil {
return kmsCfg, err return kmsCfg, err
} }
vcfg.Enabled = true
kmsCfg.Vault = vcfg kmsCfg.Vault = vcfg
return kmsCfg, nil return kmsCfg, nil
} }
@ -191,7 +205,7 @@ func LookupConfig(kvs config.KVS) (KMSConfig, error) {
func NewKMS(cfg KMSConfig) (kms KMS, err error) { func NewKMS(cfg KMSConfig) (kms KMS, err error) {
// Lookup KMS master keys - only available through ENV. // Lookup KMS master keys - only available through ENV.
if masterKeyLegacy := env.Get(EnvKMSMasterKeyLegacy, ""); len(masterKeyLegacy) != 0 { if masterKeyLegacy := env.Get(EnvKMSMasterKeyLegacy, ""); len(masterKeyLegacy) != 0 {
if !cfg.Vault.IsEmpty() { // Vault and KMS master key provided if cfg.Vault.Enabled { // Vault and KMS master key provided
return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time") return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time")
} }
kms, err = ParseMasterKey(masterKeyLegacy) kms, err = ParseMasterKey(masterKeyLegacy)
@ -199,15 +213,14 @@ func NewKMS(cfg KMSConfig) (kms KMS, err error) {
return kms, err return kms, err
} }
} else if masterKey := env.Get(EnvKMSMasterKey, ""); len(masterKey) != 0 { } else if masterKey := env.Get(EnvKMSMasterKey, ""); len(masterKey) != 0 {
if !cfg.Vault.IsEmpty() { // Vault and KMS master key provided if cfg.Vault.Enabled { // Vault and KMS master key provided
return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time") return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time")
} }
kms, err = ParseMasterKey(masterKey) kms, err = ParseMasterKey(masterKey)
if err != nil { if err != nil {
return kms, err return kms, err
} }
} } else if cfg.Vault.Enabled {
if !cfg.Vault.IsEmpty() {
kms, err = NewVault(cfg.Vault) kms, err = NewVault(cfg.Vault)
if err != nil { if err != nil {
return kms, err return kms, err

@ -18,6 +18,7 @@ package crypto
import ( import (
"fmt" "fmt"
"reflect"
"strconv" "strconv"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
@ -40,40 +41,40 @@ const (
) )
const ( const (
// EnvVaultEndpoint is the environment variable used to specify // EnvLegacyVaultEndpoint is the environment variable used to specify
// the vault HTTPS endpoint. // the vault HTTPS endpoint.
EnvVaultEndpoint = "MINIO_SSE_VAULT_ENDPOINT" EnvLegacyVaultEndpoint = "MINIO_SSE_VAULT_ENDPOINT"
// EnvVaultAuthType is the environment variable used to specify // EnvLegacyVaultAuthType is the environment variable used to specify
// the authentication type for vault. // the authentication type for vault.
EnvVaultAuthType = "MINIO_SSE_VAULT_AUTH_TYPE" EnvLegacyVaultAuthType = "MINIO_SSE_VAULT_AUTH_TYPE"
// EnvVaultAppRoleID is the environment variable used to specify // EnvLegacyVaultAppRoleID is the environment variable used to specify
// the vault AppRole ID. // the vault AppRole ID.
EnvVaultAppRoleID = "MINIO_SSE_VAULT_APPROLE_ID" EnvLegacyVaultAppRoleID = "MINIO_SSE_VAULT_APPROLE_ID"
// EnvVaultAppSecretID is the environment variable used to specify // EnvLegacyVaultAppSecretID is the environment variable used to specify
// the vault AppRole secret corresponding to the AppRole ID. // the vault AppRole secret corresponding to the AppRole ID.
EnvVaultAppSecretID = "MINIO_SSE_VAULT_APPROLE_SECRET" EnvLegacyVaultAppSecretID = "MINIO_SSE_VAULT_APPROLE_SECRET"
// EnvVaultKeyVersion is the environment variable used to specify // EnvLegacyVaultKeyVersion is the environment variable used to specify
// the vault key version. // the vault key version.
EnvVaultKeyVersion = "MINIO_SSE_VAULT_KEY_VERSION" EnvLegacyVaultKeyVersion = "MINIO_SSE_VAULT_KEY_VERSION"
// EnvVaultKeyName is the environment variable used to specify // EnvLegacyVaultKeyName is the environment variable used to specify
// the vault named key-ring. In the S3 context it's referred as // the vault named key-ring. In the S3 context it's referred as
// customer master key ID (CMK-ID). // customer master key ID (CMK-ID).
EnvVaultKeyName = "MINIO_SSE_VAULT_KEY_NAME" EnvLegacyVaultKeyName = "MINIO_SSE_VAULT_KEY_NAME"
// EnvVaultCAPath is the environment variable used to specify the // EnvLegacyVaultCAPath is the environment variable used to specify the
// path to a directory of PEM-encoded CA cert files. These CA cert // path to a directory of PEM-encoded CA cert files. These CA cert
// files are used to authenticate MinIO to Vault over mTLS. // files are used to authenticate MinIO to Vault over mTLS.
EnvVaultCAPath = "MINIO_SSE_VAULT_CAPATH" EnvLegacyVaultCAPath = "MINIO_SSE_VAULT_CAPATH"
// EnvVaultNamespace is the environment variable used to specify // EnvLegacyVaultNamespace is the environment variable used to specify
// vault namespace. The vault namespace is used if the enterprise // vault namespace. The vault namespace is used if the enterprise
// version of Hashicorp Vault is used. // version of Hashicorp Vault is used.
EnvVaultNamespace = "MINIO_SSE_VAULT_NAMESPACE" EnvLegacyVaultNamespace = "MINIO_SSE_VAULT_NAMESPACE"
) )
// SetKMSConfig helper to migrate from older KMSConfig to new KV. // SetKMSConfig helper to migrate from older KMSConfig to new KV.
@ -93,7 +94,7 @@ func SetKMSConfig(s config.Config, cfg KMSConfig) {
KMSVaultKeyVersion: strconv.Itoa(cfg.Vault.Key.Version), KMSVaultKeyVersion: strconv.Itoa(cfg.Vault.Key.Version),
KMSVaultNamespace: cfg.Vault.Namespace, KMSVaultNamespace: cfg.Vault.Namespace,
config.State: func() string { config.State: func() string {
if !cfg.Vault.IsEmpty() { if cfg.Vault.Endpoint != "" {
return config.StateOn return config.StateOn
} }
return config.StateOff return config.StateOff
@ -141,7 +142,7 @@ func lookupConfigLegacy(kvs config.KVS) (KMSConfig, error) {
return cfg, nil return cfg, nil
} }
endpointStr := env.Get(EnvKMSVaultEndpoint, kvs.Get(KMSVaultEndpoint)) endpointStr := env.Get(EnvLegacyVaultEndpoint, "")
if endpointStr != "" { if endpointStr != "" {
// Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present // Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present
endpoint, err := xnet.ParseHTTPURL(endpointStr) endpoint, err := xnet.ParseHTTPURL(endpointStr)
@ -152,25 +153,31 @@ func lookupConfigLegacy(kvs config.KVS) (KMSConfig, error) {
} }
cfg.Vault.Endpoint = endpointStr cfg.Vault.Endpoint = endpointStr
cfg.Vault.CAPath = env.Get(EnvVaultCAPath, kvs.Get(KMSVaultCAPath)) cfg.Vault.CAPath = env.Get(EnvLegacyVaultCAPath, "")
cfg.Vault.Auth.Type = env.Get(EnvVaultAuthType, kvs.Get(KMSVaultAuthType)) cfg.Vault.Auth.Type = env.Get(EnvLegacyVaultAuthType, "")
cfg.Vault.Auth.AppRole.ID = env.Get(EnvVaultAppRoleID, kvs.Get(KMSVaultAppRoleID)) if cfg.Vault.Auth.Type == "" {
cfg.Vault.Auth.AppRole.Secret = env.Get(EnvVaultAppSecretID, kvs.Get(KMSVaultAppRoleSecret)) cfg.Vault.Auth.Type = "approle"
cfg.Vault.Key.Name = env.Get(EnvVaultKeyName, kvs.Get(KMSVaultKeyName)) }
cfg.Vault.Namespace = env.Get(EnvVaultNamespace, kvs.Get(KMSVaultNamespace)) cfg.Vault.Auth.AppRole.ID = env.Get(EnvLegacyVaultAppRoleID, "")
keyVersion := env.Get(EnvVaultKeyVersion, kvs.Get(KMSVaultKeyVersion)) cfg.Vault.Auth.AppRole.Secret = env.Get(EnvLegacyVaultAppSecretID, "")
cfg.Vault.Key.Name = env.Get(EnvLegacyVaultKeyName, "")
if keyVersion != "" { cfg.Vault.Namespace = env.Get(EnvLegacyVaultNamespace, "")
if keyVersion := env.Get(EnvLegacyVaultKeyVersion, ""); keyVersion != "" {
cfg.Vault.Key.Version, err = strconv.Atoi(keyVersion) cfg.Vault.Key.Version, err = strconv.Atoi(keyVersion)
if err != nil { if err != nil {
return cfg, fmt.Errorf("Invalid ENV variable: Unable to parse %s value (`%s`)", return cfg, fmt.Errorf("Invalid ENV variable: Unable to parse %s value (`%s`)",
EnvVaultKeyVersion, keyVersion) EnvLegacyVaultKeyVersion, keyVersion)
} }
} }
if reflect.DeepEqual(cfg.Vault, defaultCfg) {
return cfg, nil
}
if err = cfg.Vault.Verify(); err != nil { if err = cfg.Vault.Verify(); err != nil {
return cfg, err return cfg, err
} }
cfg.Vault.Enabled = true
return cfg, nil return cfg, nil
} }

@ -51,6 +51,7 @@ type VaultAppRole struct {
// VaultConfig represents vault configuration. // VaultConfig represents vault configuration.
type VaultConfig struct { type VaultConfig struct {
Enabled bool `json:"-"`
Endpoint string `json:"endpoint"` // The vault API endpoint as URL Endpoint string `json:"endpoint"` // The vault API endpoint as URL
CAPath string `json:"-"` // The path to PEM-encoded certificate files used for mTLS. Currently not used in config file. CAPath string `json:"-"` // The path to PEM-encoded certificate files used for mTLS. Currently not used in config file.
Auth VaultAuth `json:"auth"` // The vault authentication configuration Auth VaultAuth `json:"auth"` // The vault authentication configuration
@ -68,24 +69,10 @@ type vaultService struct {
var _ KMS = (*vaultService)(nil) // compiler check that *vaultService implements KMS var _ KMS = (*vaultService)(nil) // compiler check that *vaultService implements KMS
// empty/default vault configuration used to check whether a particular is empty.
var emptyVaultConfig = VaultConfig{
Auth: VaultAuth{
Type: "approle",
},
}
// IsEmpty returns true if the vault config struct is an
// empty configuration.
func (v *VaultConfig) IsEmpty() bool { return *v == emptyVaultConfig }
// Verify returns a nil error if the vault configuration // Verify returns a nil error if the vault configuration
// is valid. A valid configuration is either empty or // is valid. A valid configuration is either empty or
// contains valid non-default values. // contains valid non-default values.
func (v *VaultConfig) Verify() (err error) { func (v *VaultConfig) Verify() (err error) {
if v.IsEmpty() {
return // an empty configuration is valid
}
switch { switch {
case v.Endpoint == "": case v.Endpoint == "":
err = errors.New("crypto: missing hashicorp vault endpoint") err = errors.New("crypto: missing hashicorp vault endpoint")
@ -107,8 +94,8 @@ func (v *VaultConfig) Verify() (err error) {
// to Vault with the credentials in config and gets a client // to Vault with the credentials in config and gets a client
// token for future api calls. // token for future api calls.
func NewVault(config VaultConfig) (KMS, error) { func NewVault(config VaultConfig) (KMS, error) {
if config.IsEmpty() { if !config.Enabled {
return nil, errors.New("crypto: the hashicorp vault configuration must not be empty") return nil, nil
} }
if err := config.Verify(); err != nil { if err := config.Verify(); err != nil {
return nil, err return nil, err

@ -15,7 +15,6 @@
package crypto package crypto
import ( import (
"fmt"
"testing" "testing"
) )
@ -23,17 +22,17 @@ var verifyVaultConfigTests = []struct {
Config VaultConfig Config VaultConfig
ShouldFail bool ShouldFail bool
}{ }{
{
ShouldFail: false, // 0
Config: emptyVaultConfig,
},
{ {
ShouldFail: true, ShouldFail: true,
Config: VaultConfig{Endpoint: "https://127.0.0.1:8080"}, Config: VaultConfig{
Endpoint: "https://127.0.0.1:8080",
Enabled: true,
},
}, },
{ {
ShouldFail: true, // 1 ShouldFail: true, // 1
Config: VaultConfig{ Config: VaultConfig{
Enabled: true,
Endpoint: "https://127.0.0.1:8080", Endpoint: "https://127.0.0.1:8080",
Auth: VaultAuth{Type: "unsupported"}, Auth: VaultAuth{Type: "unsupported"},
}, },
@ -41,6 +40,7 @@ var verifyVaultConfigTests = []struct {
{ {
ShouldFail: true, // 2 ShouldFail: true, // 2
Config: VaultConfig{ Config: VaultConfig{
Enabled: true,
Endpoint: "https://127.0.0.1:8080", Endpoint: "https://127.0.0.1:8080",
Auth: VaultAuth{ Auth: VaultAuth{
Type: "approle", Type: "approle",
@ -51,6 +51,7 @@ var verifyVaultConfigTests = []struct {
{ {
ShouldFail: true, // 3 ShouldFail: true, // 3
Config: VaultConfig{ Config: VaultConfig{
Enabled: true,
Endpoint: "https://127.0.0.1:8080", Endpoint: "https://127.0.0.1:8080",
Auth: VaultAuth{ Auth: VaultAuth{
Type: "approle", Type: "approle",
@ -61,6 +62,7 @@ var verifyVaultConfigTests = []struct {
{ {
ShouldFail: true, // 4 ShouldFail: true, // 4
Config: VaultConfig{ Config: VaultConfig{
Enabled: true,
Endpoint: "https://127.0.0.1:8080", Endpoint: "https://127.0.0.1:8080",
Auth: VaultAuth{ Auth: VaultAuth{
Type: "approle", Type: "approle",
@ -71,6 +73,7 @@ var verifyVaultConfigTests = []struct {
{ {
ShouldFail: true, // 5 ShouldFail: true, // 5
Config: VaultConfig{ Config: VaultConfig{
Enabled: true,
Endpoint: "https://127.0.0.1:8080", Endpoint: "https://127.0.0.1:8080",
Auth: VaultAuth{ Auth: VaultAuth{
Type: "approle", Type: "approle",
@ -82,9 +85,9 @@ var verifyVaultConfigTests = []struct {
} }
func TestVerifyVaultConfig(t *testing.T) { func TestVerifyVaultConfig(t *testing.T) {
for i, test := range verifyVaultConfigTests { for _, test := range verifyVaultConfigTests {
test := test test := test
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) { t.Run(test.Config.Endpoint, func(t *testing.T) {
err := test.Config.Verify() err := test.Config.Verify()
if test.ShouldFail && err == nil { if test.ShouldFail && err == nil {
t.Errorf("Verify should fail but returned 'err == nil'") t.Errorf("Verify should fail but returned 'err == nil'")

@ -56,7 +56,7 @@ func startDailyLifecycle() {
// Wait until the object API is ready // Wait until the object API is ready
for { for {
objAPI = globalObjectAPI objAPI = newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue

@ -553,23 +553,23 @@ func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjec
migrating: migrateSw, migrating: migrateSw,
migMutex: sync.Mutex{}, migMutex: sync.Mutex{},
GetObjectInfoFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) { GetObjectInfoFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
return globalObjectAPI.GetObjectInfo(ctx, bucket, object, opts) return newObjectLayerFn().GetObjectInfo(ctx, bucket, object, opts)
}, },
GetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) { GetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
return globalObjectAPI.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts) return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
}, },
DeleteObjectFn: func(ctx context.Context, bucket, object string) error { DeleteObjectFn: func(ctx context.Context, bucket, object string) error {
return globalObjectAPI.DeleteObject(ctx, bucket, object) return newObjectLayerFn().DeleteObject(ctx, bucket, object)
}, },
DeleteObjectsFn: func(ctx context.Context, bucket string, objects []string) ([]error, error) { DeleteObjectsFn: func(ctx context.Context, bucket string, objects []string) ([]error, error) {
errs := make([]error, len(objects)) errs := make([]error, len(objects))
for idx, object := range objects { for idx, object := range objects {
errs[idx] = globalObjectAPI.DeleteObject(ctx, bucket, object) errs[idx] = newObjectLayerFn().DeleteObject(ctx, bucket, object)
} }
return errs, nil return errs, nil
}, },
PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) { PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return globalObjectAPI.PutObject(ctx, bucket, object, data, opts) return newObjectLayerFn().PutObject(ctx, bucket, object, data, opts)
}, },
} }
if migrateSw { if migrateSw {

@ -222,9 +222,22 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
logger.FatalIf(err, "Unable to initialize gateway backend") logger.FatalIf(err, "Unable to initialize gateway backend")
} }
// Re-enable logging
logger.Disable = false
// Once endpoints are finalized, initialize the new object api in safe mode.
globalObjLayerMutex.Lock()
globalSafeMode = true
globalObjectAPI = newObject
globalObjLayerMutex.Unlock()
// Populate existing buckets to the etcd backend // Populate existing buckets to the etcd backend
if globalDNSConfig != nil { if globalDNSConfig != nil {
initFederatorBackend(newObject) buckets, err := newObject.ListBuckets(context.Background())
if err != nil {
logger.Fatal(err, "Unable to list buckets")
}
initFederatorBackend(buckets, newObject)
} }
// Migrate all backend configs to encrypted backend, also handles rotation as well. // Migrate all backend configs to encrypted backend, also handles rotation as well.
@ -233,16 +246,15 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
logger.FatalIf(handleEncryptedConfigBackend(newObject, enableConfigOps), logger.FatalIf(handleEncryptedConfigBackend(newObject, enableConfigOps),
"Unable to handle encrypted backend for config, iam and policies") "Unable to handle encrypted backend for config, iam and policies")
// Calls all New() for all sub-systems.
newAllSubsystems()
// **** WARNING **** // **** WARNING ****
// Migrating to encrypted backend should happen before initialization of any // Migrating to encrypted backend should happen before initialization of any
// sub-systems, make sure that we do not move the above codeblock elsewhere. // sub-systems, make sure that we do not move the above codeblock elsewhere.
if enableConfigOps { if enableConfigOps {
// Create a new config system. logger.FatalIf(globalConfigSys.Init(newObject), "Unable to initialize config system")
globalConfigSys = NewConfigSys()
// Load globalServerConfig from disk
logger.LogIf(context.Background(), globalConfigSys.Init(newObject))
// Start watching disk for reloading config, this // Start watching disk for reloading config, this
// is only enabled for "NAS" gateway. // is only enabled for "NAS" gateway.
@ -261,32 +273,20 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
"Unable to handle encrypted backend for iam and policies") "Unable to handle encrypted backend for iam and policies")
} }
if globalCacheConfig.Enabled {
// initialize the new disk cache objects.
globalCacheObjectAPI, err = newServerCacheObjects(context.Background(), globalCacheConfig)
logger.FatalIf(err, "Unable to initialize disk caching")
}
// Re-enable logging
logger.Disable = false
// Create new IAM system.
globalIAMSys = NewIAMSys()
if enableIAMOps { if enableIAMOps {
// Initialize IAM sys. // Initialize IAM sys.
logger.LogIf(context.Background(), globalIAMSys.Init(newObject)) logger.FatalIf(globalIAMSys.Init(newObject), "Unable to initialize IAM system")
} }
// Create new policy system. if globalCacheConfig.Enabled {
globalPolicySys = NewPolicySys() // initialize the new disk cache objects.
var cacheAPI CacheObjectLayer
// Create new lifecycle system. cacheAPI, err = newServerCacheObjects(context.Background(), globalCacheConfig)
globalLifecycleSys = NewLifecycleSys() logger.FatalIf(err, "Unable to initialize disk caching")
// Create new notification system. globalObjLayerMutex.Lock()
globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints) globalCacheObjectAPI = cacheAPI
if err != nil { globalObjLayerMutex.Unlock()
logger.FatalIf(err, "Unable to initialize notification system")
} }
// Verify if object layer supports // Verify if object layer supports
@ -294,11 +294,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// - compression // - compression
verifyObjectLayerFeatures("gateway "+gatewayName, newObject) verifyObjectLayerFeatures("gateway "+gatewayName, newObject)
// Once endpoints are finalized, initialize the new object api.
globalObjLayerMutex.Lock()
globalObjectAPI = newObject
globalObjLayerMutex.Unlock()
// Prints the formatted startup message once object layer is initialized. // Prints the formatted startup message once object layer is initialized.
if !globalCLIContext.Quiet { if !globalCLIContext.Quiet {
mode := globalMinioModeGatewayPrefix + gatewayName mode := globalMinioModeGatewayPrefix + gatewayName
@ -314,6 +309,11 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
printGatewayStartupMessage(getAPIEndpoints(), gatewayName) printGatewayStartupMessage(getAPIEndpoints(), gatewayName)
} }
// Disable safe mode operation, after all initialization is over.
globalObjLayerMutex.Lock()
globalSafeMode = false
globalObjLayerMutex.Unlock()
// Set uptime time after object layer has initialized. // Set uptime time after object layer has initialized.
globalBootTime = UTCNow() globalBootTime = UTCNow()

@ -28,8 +28,9 @@ import (
func printGatewayStartupMessage(apiEndPoints []string, backendType string) { func printGatewayStartupMessage(apiEndPoints []string, backendType string) {
strippedAPIEndpoints := stripStandardPorts(apiEndPoints) strippedAPIEndpoints := stripStandardPorts(apiEndPoints)
// If cache layer is enabled, print cache capacity. // If cache layer is enabled, print cache capacity.
if globalCacheObjectAPI != nil { cacheAPI := newCachedObjectLayerFn()
printCacheStorageInfo(globalCacheObjectAPI.StorageInfo(context.Background())) if cacheAPI != nil {
printCacheStorageInfo(cacheAPI.StorageInfo(context.Background()))
} }
// Prints credential. // Prints credential.
printGatewayCommonMsg(strippedAPIEndpoints) printGatewayCommonMsg(strippedAPIEndpoints)

@ -156,7 +156,7 @@ func execLeaderTasks(sets *xlSets) {
func startGlobalHeal() { func startGlobalHeal() {
var objAPI ObjectLayer var objAPI ObjectLayer
for { for {
objAPI = globalObjectAPI objAPI = newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue

@ -52,9 +52,9 @@ func ReadinessCheckHandler(w http.ResponseWriter, r *http.Request) {
func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) { func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "LivenessCheckHandler") ctx := newContext(r, w, "LivenessCheckHandler")
objLayer := globalObjectAPI objLayer := newObjectLayerFn()
// Service not initialized yet // Service not initialized yet
if objLayer == nil || globalSafeMode { if objLayer == nil {
// Respond with 200 OK while server initializes to ensure a distributed cluster // Respond with 200 OK while server initializes to ensure a distributed cluster
// is able to start on orchestration platforms like Docker Swarm. // is able to start on orchestration platforms like Docker Swarm.
// Refer https://github.com/minio/minio/issues/8140 for more details. // Refer https://github.com/minio/minio/issues/8140 for more details.

@ -49,7 +49,7 @@ func (iamOS *IAMObjectStore) getObjectAPI() ObjectLayer {
if iamOS.objAPI != nil { if iamOS.objAPI != nil {
return iamOS.objAPI return iamOS.objAPI
} }
return globalObjectAPI return newObjectLayerWithoutSafeModeFn()
} }
func (iamOS *IAMObjectStore) setObjectAPI(objAPI ObjectLayer) { func (iamOS *IAMObjectStore) setObjectAPI(objAPI ObjectLayer) {

@ -406,7 +406,7 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
// DeletePolicy - deletes a canned policy from backend or etcd. // DeletePolicy - deletes a canned policy from backend or etcd.
func (sys *IAMSys) DeletePolicy(policyName string) error { func (sys *IAMSys) DeletePolicy(policyName string) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -431,7 +431,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
// InfoPolicy - expands the canned policy into its JSON structure. // InfoPolicy - expands the canned policy into its JSON structure.
func (sys *IAMSys) InfoPolicy(policyName string) ([]byte, error) { func (sys *IAMSys) InfoPolicy(policyName string) ([]byte, error) {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return nil, errServerNotInitialized return nil, errServerNotInitialized
} }
@ -448,7 +448,7 @@ func (sys *IAMSys) InfoPolicy(policyName string) ([]byte, error) {
// ListPolicies - lists all canned policies. // ListPolicies - lists all canned policies.
func (sys *IAMSys) ListPolicies() (map[string][]byte, error) { func (sys *IAMSys) ListPolicies() (map[string][]byte, error) {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return nil, errServerNotInitialized return nil, errServerNotInitialized
} }
@ -471,7 +471,7 @@ func (sys *IAMSys) ListPolicies() (map[string][]byte, error) {
// SetPolicy - sets a new name policy. // SetPolicy - sets a new name policy.
func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error { func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -492,7 +492,7 @@ func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
// DeleteUser - delete user (only for long-term users not STS users). // DeleteUser - delete user (only for long-term users not STS users).
func (sys *IAMSys) DeleteUser(accessKey string) error { func (sys *IAMSys) DeleteUser(accessKey string) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -521,7 +521,7 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
// SetTempUser - set temporary user credentials, these credentials have an expiry. // SetTempUser - set temporary user credentials, these credentials have an expiry.
func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyName string) error { func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyName string) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -561,7 +561,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
// ListUsers - list all users. // ListUsers - list all users.
func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) { func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return nil, errServerNotInitialized return nil, errServerNotInitialized
} }
@ -587,7 +587,7 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
// GetUserInfo - get info on a user. // GetUserInfo - get info on a user.
func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) { func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return u, errServerNotInitialized return u, errServerNotInitialized
} }
@ -617,7 +617,7 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
// SetUserStatus - sets current user status, supports disabled or enabled. // SetUserStatus - sets current user status, supports disabled or enabled.
func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus) error { func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -653,7 +653,7 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
// SetUser - set user credentials and policy. // SetUser - set user credentials and policy.
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error { func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -685,7 +685,7 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
// SetUserSecretKey - sets user secret key // SetUserSecretKey - sets user secret key
func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error { func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -724,7 +724,7 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
// AddUsersToGroup - adds users to a group, creating the group if // AddUsersToGroup - adds users to a group, creating the group if
// needed. No error if user(s) already are in the group. // needed. No error if user(s) already are in the group.
func (sys *IAMSys) AddUsersToGroup(group string, members []string) error { func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -782,7 +782,7 @@ func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
// RemoveUsersFromGroup - remove users from group. If no users are // RemoveUsersFromGroup - remove users from group. If no users are
// given, and the group is empty, deletes the group as well. // given, and the group is empty, deletes the group as well.
func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error { func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -863,7 +863,7 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
// SetGroupStatus - enable/disabled a group // SetGroupStatus - enable/disabled a group
func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error { func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -952,7 +952,7 @@ func (sys *IAMSys) ListGroups() (r []string, err error) {
// PolicyDB. This function applies only long-term users. For STS // PolicyDB. This function applies only long-term users. For STS
// users, policy is set directly by called sys.policyDBSet(). // users, policy is set directly by called sys.policyDBSet().
func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error { func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error {
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -1007,7 +1007,7 @@ func (sys *IAMSys) PolicyDBGet(name string, isGroup bool) ([]string, error) {
return nil, errInvalidArgument return nil, errInvalidArgument
} }
objectAPI := globalObjectAPI objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil { if objectAPI == nil {
return nil, errServerNotInitialized return nil, errServerNotInitialized
} }

@ -59,7 +59,7 @@ func (sys *LifecycleSys) Get(bucketName string) (lifecycle lifecycle.Lifecycle,
if globalIsGateway { if globalIsGateway {
// When gateway is enabled, no cached value // When gateway is enabled, no cached value
// is used to validate life cycle policies. // is used to validate life cycle policies.
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
return return
} }

@ -83,9 +83,9 @@ func (c *minioCollector) Collect(ch chan<- prometheus.Metric) {
minioVersionInfo.WithLabelValues(Version, CommitID).Set(float64(1.0)) minioVersionInfo.WithLabelValues(Version, CommitID).Set(float64(1.0))
// Fetch disk space info // Fetch disk space info
objLayer := globalObjectAPI objLayer := newObjectLayerFn()
// Service not initialized yet // Service not initialized yet
if objLayer == nil || globalSafeMode { if objLayer == nil {
return return
} }

@ -628,6 +628,20 @@ func (sys *NotificationSys) ListenBucketNotification(ctx context.Context, bucket
}() }()
} }
// AddNotificationTargetsFromConfig - adds notification targets from server config.
func (sys *NotificationSys) AddNotificationTargetsFromConfig(cfg config.Config) error {
targetList, err := notify.GetNotificationTargets(cfg, GlobalServiceDoneCh, globalRootCAs)
if err != nil {
return err
}
for _, target := range targetList.Targets() {
if err = sys.targetList.Add(target); err != nil {
return err
}
}
return nil
}
// AddRemoteTarget - adds event rules map, HTTP/PeerRPC client target to bucket name. // AddRemoteTarget - adds event rules map, HTTP/PeerRPC client target to bucket name.
func (sys *NotificationSys) AddRemoteTarget(bucketName string, target event.Target, rulesMap event.RulesMap) error { func (sys *NotificationSys) AddRemoteTarget(bucketName string, target event.Target, rulesMap event.RulesMap) error {
if err := sys.targetList.Add(target); err != nil { if err := sys.targetList.Add(target); err != nil {
@ -684,7 +698,7 @@ func (sys *NotificationSys) initListeners(ctx context.Context, objAPI ObjectLaye
// Construct path to listener.json for the given bucket. // Construct path to listener.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, bucketName, bucketListenerConfig) configFile := path.Join(bucketConfigPrefix, bucketName, bucketListenerConfig)
transactionConfigFile := configFile + ".transaction" transactionConfigFile := configFile + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket // As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig() // and configFile, take a transaction lock to avoid data race between readConfig()
@ -1098,22 +1112,14 @@ func (sys *NotificationSys) NetworkInfo() []madmin.ServerNetworkHardwareInfo {
} }
// NewNotificationSys - creates new notification system object. // NewNotificationSys - creates new notification system object.
func NewNotificationSys(cfg config.Config, endpoints EndpointList) (*NotificationSys, error) { func NewNotificationSys(endpoints EndpointList) *NotificationSys {
targetList, err := notify.GetNotificationTargets(cfg, GlobalServiceDoneCh, globalRootCAs)
if err != nil {
return nil, config.Errorf("Unable to start notification sub system: %s", err)
}
remoteHosts := getRemoteHosts(endpoints)
remoteClients := getRestClients(remoteHosts)
// bucketRulesMap/bucketRemoteTargetRulesMap are initialized by NotificationSys.Init() // bucketRulesMap/bucketRemoteTargetRulesMap are initialized by NotificationSys.Init()
return &NotificationSys{ return &NotificationSys{
targetList: targetList, targetList: event.NewTargetList(),
bucketRulesMap: make(map[string]event.RulesMap), bucketRulesMap: make(map[string]event.RulesMap),
bucketRemoteTargetRulesMap: make(map[string]map[event.TargetID]event.RulesMap), bucketRemoteTargetRulesMap: make(map[string]map[event.TargetID]event.RulesMap),
peerClients: remoteClients, peerClients: getRestClients(endpoints),
}, nil }
} }
type eventArgs struct { type eventArgs struct {
@ -1264,7 +1270,7 @@ func SaveListener(objAPI ObjectLayer, bucketName string, eventNames []event.Name
// Construct path to listener.json for the given bucket. // Construct path to listener.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, bucketName, bucketListenerConfig) configFile := path.Join(bucketConfigPrefix, bucketName, bucketListenerConfig)
transactionConfigFile := configFile + ".transaction" transactionConfigFile := configFile + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket // As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig() // and configFile, take a transaction lock to avoid data race between readConfig()
@ -1315,7 +1321,7 @@ func RemoveListener(objAPI ObjectLayer, bucketName string, targetID event.Target
// Construct path to listener.json for the given bucket. // Construct path to listener.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, bucketName, bucketListenerConfig) configFile := path.Join(bucketConfigPrefix, bucketName, bucketListenerConfig)
transactionConfigFile := configFile + ".transaction" transactionConfigFile := configFile + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket // As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig() // and configFile, take a transaction lock to avoid data race between readConfig()

@ -693,19 +693,24 @@ func getRemoteHosts(endpoints EndpointList) []*xnet.Host {
var remoteHosts []*xnet.Host var remoteHosts []*xnet.Host
for _, hostStr := range GetRemotePeers(endpoints) { for _, hostStr := range GetRemotePeers(endpoints) {
host, err := xnet.ParseHost(hostStr) host, err := xnet.ParseHost(hostStr)
logger.FatalIf(err, "Unable to parse peer Host") if err != nil {
logger.LogIf(context.Background(), err)
continue
}
remoteHosts = append(remoteHosts, host) remoteHosts = append(remoteHosts, host)
} }
return remoteHosts return remoteHosts
} }
func getRestClients(peerHosts []*xnet.Host) []*peerRESTClient { func getRestClients(endpoints EndpointList) []*peerRESTClient {
peerHosts := getRemoteHosts(endpoints)
restClients := make([]*peerRESTClient, len(peerHosts)) restClients := make([]*peerRESTClient, len(peerHosts))
for i, host := range peerHosts { for i, host := range peerHosts {
client, err := newPeerRESTClient(host) client, err := newPeerRESTClient(host)
if err != nil { if err != nil {
logger.LogIf(context.Background(), err) logger.LogIf(context.Background(), err)
continue
} }
restClients[i] = client restClients[i] = client
} }

@ -42,12 +42,8 @@ type peerRESTServer struct {
} }
func getServerInfo() (*ServerInfoData, error) { func getServerInfo() (*ServerInfoData, error) {
if globalBootTime.IsZero() { objLayer := newObjectLayerWithoutSafeModeFn()
return nil, errServerNotInitialized if objLayer == nil {
}
objLayer := globalObjectAPI
if objLayer == nil || globalSafeMode {
return nil, errServerNotInitialized return nil, errServerNotInitialized
} }
@ -166,7 +162,7 @@ func (s *peerRESTServer) DeletePolicyHandler(w http.ResponseWriter, r *http.Requ
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return
@ -194,7 +190,7 @@ func (s *peerRESTServer) LoadPolicyHandler(w http.ResponseWriter, r *http.Reques
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return
@ -222,7 +218,7 @@ func (s *peerRESTServer) LoadPolicyMappingHandler(w http.ResponseWriter, r *http
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return
@ -251,7 +247,7 @@ func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Reques
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return
@ -279,7 +275,7 @@ func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request)
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return
@ -329,7 +325,7 @@ func (s *peerRESTServer) LoadGroupHandler(w http.ResponseWriter, r *http.Request
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return
@ -539,7 +535,7 @@ func (s *peerRESTServer) ReloadFormatHandler(w http.ResponseWriter, r *http.Requ
return return
} }
objAPI := globalObjectAPI objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil { if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized) s.writeErrorResponse(w, errServerNotInitialized)
return return

@ -69,7 +69,7 @@ func (sys *PolicySys) IsAllowed(args policy.Args) bool {
if globalIsGateway { if globalIsGateway {
// When gateway is enabled, no cached value // When gateway is enabled, no cached value
// is used to validate bucket policies. // is used to validate bucket policies.
objAPI := globalObjectAPI objAPI := newObjectLayerFn()
if objAPI != nil { if objAPI != nil {
config, err := objAPI.GetBucketPolicy(context.Background(), args.BucketName) config, err := objAPI.GetBucketPolicy(context.Background(), args.BucketName)
if err == nil { if err == nil {

@ -180,53 +180,99 @@ func serverHandleEnvVars() {
handleCommonEnvVars() handleCommonEnvVars()
} }
func initAllSubsystems(newObject ObjectLayer) { func newAllSubsystems() {
// Create new notification system and initialize notification targets
globalNotificationSys = NewNotificationSys(globalEndpoints)
// Create a new config system. // Create a new config system.
globalConfigSys = NewConfigSys() globalConfigSys = NewConfigSys()
// Initialize config system.
if err := globalConfigSys.Init(newObject); err != nil {
logger.Fatal(err, "Unable to initialize config system")
}
// Create new IAM system. // Create new IAM system.
globalIAMSys = NewIAMSys() globalIAMSys = NewIAMSys()
if err := globalIAMSys.Init(newObject); err != nil { // Create new policy system.
logger.Fatal(err, "Unable to initialize IAM system") globalPolicySys = NewPolicySys()
// Create new lifecycle system.
globalLifecycleSys = NewLifecycleSys()
}
func initSafeModeInit(buckets []BucketInfo) (err error) {
defer func() {
if err != nil {
// Enable logger
logger.Disable = false
// Prints the formatted startup message in safe mode operation.
printStartupSafeModeMessage(getAPIEndpoints(), err)
// Initialization returned error reaching safe mode and
// not proceeding waiting for admin action.
handleSignals()
}
}()
newObject := newObjectLayerWithoutSafeModeFn()
// Calls New() for all sub-systems.
newAllSubsystems()
// Migrate all backend configs to encrypted backend, also handles rotation as well.
if err = handleEncryptedConfigBackend(newObject, true); err != nil {
return fmt.Errorf("Unable to handle encrypted backend for config, iam and policies: %v", err)
} }
buckets, err := newObject.ListBuckets(context.Background()) // **** WARNING ****
if err != nil { // Migrating to encrypted backend should happen before initialization of any
logger.Fatal(err, "Unable to list buckets") // sub-systems, make sure that we do not move the above codeblock elsewhere.
// Validate and initialize all subsystems.
if err = initAllSubsystems(buckets, newObject); err != nil {
return err
} }
// Create new notification system and initialize notification targets if globalEtcdClient != nil {
globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints) // **** WARNING ****
if err != nil { // Migrating to encrypted backend on etcd should happen before initialization of
logger.Fatal(err, "Unable to initialize notification targets") // IAM sub-systems, make sure that we do not move the above codeblock elsewhere.
if err = migrateIAMConfigsEtcdToEncrypted(globalEtcdClient); err != nil {
return fmt.Errorf("Unable to handle encrypted backend for iam and policies: %v", err)
}
}
return nil
}
func initAllSubsystems(buckets []BucketInfo, newObject ObjectLayer) (err error) {
// Initialize config system.
if err = globalConfigSys.Init(newObject); err != nil {
return fmt.Errorf("Unable to initialize config system: %v", err)
}
if err = globalNotificationSys.AddNotificationTargetsFromConfig(globalServerConfig); err != nil {
return fmt.Errorf("Unable to initialize notification target(s) from config: %v", err)
}
if err = globalIAMSys.Init(newObject); err != nil {
return fmt.Errorf("Unable to initialize IAM system: %v", err)
} }
// Initialize notification system. // Initialize notification system.
if err = globalNotificationSys.Init(buckets, newObject); err != nil { if err = globalNotificationSys.Init(buckets, newObject); err != nil {
logger.Fatal(err, "Unable to initialize notification system") return fmt.Errorf("Unable to initialize notification system: %v", err)
} }
// Create new policy system.
globalPolicySys = NewPolicySys()
// Initialize policy system. // Initialize policy system.
if err = globalPolicySys.Init(buckets, newObject); err != nil { if err = globalPolicySys.Init(buckets, newObject); err != nil {
logger.Fatal(err, "Unable to initialize policy system") return fmt.Errorf("Unable to initialize policy system; %v", err)
} }
// Create new lifecycle system.
globalLifecycleSys = NewLifecycleSys()
// Initialize lifecycle system. // Initialize lifecycle system.
if err = globalLifecycleSys.Init(buckets, newObject); err != nil { if err = globalLifecycleSys.Init(buckets, newObject); err != nil {
logger.Fatal(err, "Unable to initialize lifecycle system") return fmt.Errorf("Unable to initialize lifecycle system: %v", err)
} }
return nil
} }
// serverMain handler called for 'minio server' command. // serverMain handler called for 'minio server' command.
@ -327,42 +373,33 @@ func serverMain(ctx *cli.Context) {
globalTLSCerts.Stop() globalTLSCerts.Stop()
globalHTTPServer.Shutdown() globalHTTPServer.Shutdown()
logger.FatalIf(err, "Unable to initialize backend") logger.Fatal(err, "Unable to initialize backend")
}
// Populate existing buckets to the etcd backend
if globalDNSConfig != nil {
initFederatorBackend(newObject)
} }
// Re-enable logging // Re-enable logging
logger.Disable = false logger.Disable = false
// Migrate all backend configs to encrypted backend, also handles rotation as well. // Once endpoints are finalized, initialize the new object api in safe mode.
logger.FatalIf(handleEncryptedConfigBackend(newObject, true), globalObjLayerMutex.Lock()
"Unable to handle encrypted backend for config, iam and policies") globalSafeMode = true
globalObjectAPI = newObject
// **** WARNING **** globalObjLayerMutex.Unlock()
// Migrating to encrypted backend should happen before initialization of any
// sub-systems, make sure that we do not move the above codeblock elsewhere.
// Validate and initialize all subsystems. buckets, err := newObject.ListBuckets(context.Background())
initAllSubsystems(newObject) if err != nil {
logger.Fatal(err, "Unable to list buckets")
}
if globalEtcdClient != nil { // Populate existing buckets to the etcd backend
// **** WARNING **** if globalDNSConfig != nil {
// Migrating to encrypted backend on etcd should happen before initialization of initFederatorBackend(buckets, newObject)
// IAM sub-systems, make sure that we do not move the above codeblock elsewhere.
logger.FatalIf(migrateIAMConfigsEtcdToEncrypted(globalEtcdClient),
"Unable to handle encrypted backend for iam and policies")
} }
if globalCacheConfig.Enabled { initSafeModeInit(buckets)
logger.StartupMessage(color.Red(color.Bold("Disk caching is recommended only for gateway deployments")))
// initialize the new disk cache objects. if globalCacheConfig.Enabled {
globalCacheObjectAPI, err = newServerCacheObjects(context.Background(), globalCacheConfig) msg := color.RedBold("Disk caching is disabled in 'server' mode, 'caching' is only supported in gateway deployments")
logger.FatalIf(err, "Unable to initialize disk caching") logger.StartupMessage(msg)
} }
initDailyLifecycle() initDailyLifecycle()
@ -373,14 +410,17 @@ func serverMain(ctx *cli.Context) {
initGlobalHeal() initGlobalHeal()
} }
globalObjectAPI = newObject // Disable safe mode operation, after all initialization is over.
globalObjLayerMutex.Lock()
globalSafeMode = false
globalObjLayerMutex.Unlock()
// Prints the formatted startup message once object layer is initialized. // Prints the formatted startup message once object layer is initialized.
printStartupMessage(getAPIEndpoints()) printStartupMessage(getAPIEndpoints())
if globalActiveCred.Equal(auth.DefaultCredentials) { if globalActiveCred.Equal(auth.DefaultCredentials) {
msg := fmt.Sprintf("Detected default credentials '%s', please change the credentials immediately using 'MINIO_ACCESS_KEY' and 'MINIO_SECRET_KEY'", globalActiveCred) msg := fmt.Sprintf("Detected default credentials '%s', please change the credentials immediately using 'MINIO_ACCESS_KEY' and 'MINIO_SECRET_KEY'", globalActiveCred)
logger.StartupMessage(color.Red(color.Bold(msg))) logger.StartupMessage(color.RedBold(msg))
} }
// Set uptime time after object layer has initialized. // Set uptime time after object layer has initialized.

@ -31,12 +31,13 @@ import (
// Documentation links, these are part of message printing code. // Documentation links, these are part of message printing code.
const ( const (
mcQuickStartGuide = "https://docs.min.io/docs/minio-client-quickstart-guide" mcQuickStartGuide = "https://docs.min.io/docs/minio-client-quickstart-guide"
goQuickStartGuide = "https://docs.min.io/docs/golang-client-quickstart-guide" mcAdminQuickStartGuide = "https://docs.min.io/docs/minio-admin-complete-guide.html"
jsQuickStartGuide = "https://docs.min.io/docs/javascript-client-quickstart-guide" goQuickStartGuide = "https://docs.min.io/docs/golang-client-quickstart-guide"
javaQuickStartGuide = "https://docs.min.io/docs/java-client-quickstart-guide" jsQuickStartGuide = "https://docs.min.io/docs/javascript-client-quickstart-guide"
pyQuickStartGuide = "https://docs.min.io/docs/python-client-quickstart-guide" javaQuickStartGuide = "https://docs.min.io/docs/java-client-quickstart-guide"
dotnetQuickStartGuide = "https://docs.min.io/docs/dotnet-client-quickstart-guide" pyQuickStartGuide = "https://docs.min.io/docs/python-client-quickstart-guide"
dotnetQuickStartGuide = "https://docs.min.io/docs/dotnet-client-quickstart-guide"
) )
// generates format string depending on the string length and padding. // generates format string depending on the string length and padding.
@ -45,16 +46,75 @@ func getFormatStr(strLen int, padding int) string {
return "%" + formatStr return "%" + formatStr
} }
func printStartupSafeModeMessage(apiEndpoints []string, err error) {
logStartupMessage(color.RedBold("Server startup failed with '%v'", err))
logStartupMessage(color.RedBold("Server switching to safe mode"))
logStartupMessage(color.RedBold("Please use 'mc admin' commands to fix this issue"))
// Object layer is initialized then print StorageInfo in safe mode.
objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI != nil {
if msg := getStorageInfoMsgSafeMode(objAPI.StorageInfo(context.Background())); msg != "" {
logStartupMessage(msg)
}
}
// Get saved credentials.
cred := globalActiveCred
// Get saved region.
region := globalServerRegion
strippedAPIEndpoints := stripStandardPorts(apiEndpoints)
apiEndpointStr := strings.Join(strippedAPIEndpoints, " ")
// Colorize the message and print.
logStartupMessage(color.Red("Endpoint: ") + color.Bold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
if color.IsTerminal() && !globalCLIContext.Anonymous {
logStartupMessage(color.Red("AccessKey: ") + color.Bold(fmt.Sprintf("%s ", cred.AccessKey)))
logStartupMessage(color.Red("SecretKey: ") + color.Bold(fmt.Sprintf("%s ", cred.SecretKey)))
if region != "" {
logStartupMessage(color.Red("Region: ") + color.Bold(fmt.Sprintf(getFormatStr(len(region), 3), region)))
}
}
// Prints `mc` cli configuration message chooses
// first endpoint as default.
alias := "myminio"
endPoint := strippedAPIEndpoints[0]
// Configure 'mc', following block prints platform specific information for minio client admin commands.
if color.IsTerminal() {
logStartupMessage(color.RedBold("\nCommand-line Access: ") + mcAdminQuickStartGuide)
if runtime.GOOS == globalWindowsOSName {
mcMessage := fmt.Sprintf("> mc.exe config host add %s %s %s %s --api s3v4", alias,
endPoint, cred.AccessKey, cred.SecretKey)
logStartupMessage(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
mcMessage = fmt.Sprintf("> mc.exe admin --help")
logStartupMessage(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} else {
mcMessage := fmt.Sprintf("$ mc config host add %s %s %s %s --api s3v4", alias,
endPoint, cred.AccessKey, cred.SecretKey)
logStartupMessage(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
mcMessage = fmt.Sprintf("$ mc admin --help")
logStartupMessage(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
}
}
}
// Prints the formatted startup message. // Prints the formatted startup message.
func printStartupMessage(apiEndPoints []string) { func printStartupMessage(apiEndpoints []string) {
strippedAPIEndpoints := stripStandardPorts(apiEndPoints) strippedAPIEndpoints := stripStandardPorts(apiEndpoints)
// If cache layer is enabled, print cache capacity. // If cache layer is enabled, print cache capacity.
if globalCacheObjectAPI != nil { cachedObjAPI := newCachedObjectLayerFn()
printCacheStorageInfo(globalCacheObjectAPI.StorageInfo(context.Background())) if cachedObjAPI != nil {
printCacheStorageInfo(cachedObjAPI.StorageInfo(context.Background()))
} }
// Object layer is initialized then print StorageInfo. // Object layer is initialized then print StorageInfo.
objAPI := globalObjectAPI objAPI := newObjectLayerFn()
if objAPI != nil { if objAPI != nil {
printStorageInfo(objAPI.StorageInfo(context.Background())) printStorageInfo(objAPI.StorageInfo(context.Background()))
} }
@ -183,6 +243,16 @@ func printObjectAPIMsg() {
logStartupMessage(color.Blue(" .NET: ") + fmt.Sprintf(getFormatStr(len(dotnetQuickStartGuide), 6), dotnetQuickStartGuide)) logStartupMessage(color.Blue(" .NET: ") + fmt.Sprintf(getFormatStr(len(dotnetQuickStartGuide), 6), dotnetQuickStartGuide))
} }
// Get formatted disk/storage info message.
func getStorageInfoMsgSafeMode(storageInfo StorageInfo) string {
var msg string
if storageInfo.Backend.Type == BackendErasure {
diskInfo := fmt.Sprintf(" %d Online, %d Offline. ", storageInfo.Backend.OnlineDisks.Sum(), storageInfo.Backend.OfflineDisks.Sum())
msg += color.Red("Status:") + fmt.Sprintf(getFormatStr(len(diskInfo), 8), diskInfo)
}
return msg
}
// Get formatted disk/storage info message. // Get formatted disk/storage info message.
func getStorageInfoMsg(storageInfo StorageInfo) string { func getStorageInfoMsg(storageInfo StorageInfo) string {
var msg string var msg string

@ -55,7 +55,7 @@ func handleSignals() {
// send signal to various go-routines that they need to quit. // send signal to various go-routines that they need to quit.
close(GlobalServiceDoneCh) close(GlobalServiceDoneCh)
if objAPI := globalObjectAPI; objAPI != nil { if objAPI := newObjectLayerWithoutSafeModeFn(); objAPI != nil {
oerr = objAPI.Shutdown(context.Background()) oerr = objAPI.Shutdown(context.Background())
logger.LogIf(context.Background(), oerr) logger.LogIf(context.Background(), oerr)
} }
@ -66,7 +66,7 @@ func handleSignals() {
for { for {
select { select {
case err := <-globalHTTPServerErrorCh: case err := <-globalHTTPServerErrorCh:
if objAPI := globalObjectAPI; objAPI != nil { if objAPI := newObjectLayerWithoutSafeModeFn(); objAPI != nil {
objAPI.Shutdown(context.Background()) objAPI.Shutdown(context.Background())
} }
if err != nil { if err != nil {

@ -370,11 +370,7 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer {
globalPolicySys = NewPolicySys() globalPolicySys = NewPolicySys()
globalPolicySys.Init(buckets, objLayer) globalPolicySys.Init(buckets, objLayer)
globalNotificationSys, err = NewNotificationSys(globalServerConfig, testServer.Disks) globalNotificationSys = NewNotificationSys(testServer.Disks)
if err != nil {
t.Fatalf("Unable to initialize notification system %s", err)
}
globalNotificationSys.Init(buckets, objLayer) globalNotificationSys.Init(buckets, objLayer)
globalLifecycleSys = NewLifecycleSys() globalLifecycleSys = NewLifecycleSys()
@ -1640,10 +1636,10 @@ func newTestObjectLayer(endpoints EndpointList) (newObject ObjectLayer, err erro
globalIAMSys.Init(xl) globalIAMSys.Init(xl)
globalPolicySys = NewPolicySys() globalPolicySys = NewPolicySys()
globalNotificationSys, err = NewNotificationSys(globalServerConfig, endpoints) globalPolicySys.Init(nil, xl)
if err != nil {
return xl, err globalNotificationSys = NewNotificationSys(endpoints)
} globalNotificationSys.Init(nil, xl)
return xl, nil return xl, nil
} }

@ -62,18 +62,8 @@ const specialAssets = "index_bundle.*.js|loader.css|logo.svg|firefox.png|safari.
func registerWebRouter(router *mux.Router) error { func registerWebRouter(router *mux.Router) error {
// Initialize Web. // Initialize Web.
web := &webAPIHandlers{ web := &webAPIHandlers{
ObjectAPI: func() ObjectLayer { ObjectAPI: newObjectLayerFn,
if !globalSafeMode { CacheAPI: newCachedObjectLayerFn,
return globalObjectAPI
}
return nil
},
CacheAPI: func() CacheObjectLayer {
if !globalSafeMode {
return globalCacheObjectAPI
}
return nil
},
} }
// Initialize a new json2 codec. // Initialize a new json2 codec.

@ -839,12 +839,12 @@ func (xl xlObjects) AbortMultipartUpload(ctx context.Context, bucket, object, up
// get Quorum for this object // get Quorum for this object
_, writeQuorum, err := objectQuorumFromMeta(ctx, xl, partsMetadata, errs) _, writeQuorum, err := objectQuorumFromMeta(ctx, xl, partsMetadata, errs)
if err != nil { if err != nil {
return toObjectErr(err, bucket, object) return toObjectErr(err, bucket, object, uploadID)
} }
// Cleanup all uploaded parts. // Cleanup all uploaded parts.
if err = xl.deleteObject(ctx, minioMetaMultipartBucket, uploadIDPath, writeQuorum, false); err != nil { if err = xl.deleteObject(ctx, minioMetaMultipartBucket, uploadIDPath, writeQuorum, false); err != nil {
return toObjectErr(err, bucket, object) return toObjectErr(err, bucket, object, uploadID)
} }
// Successfully purged. // Successfully purged.

@ -18,11 +18,11 @@ package cmd
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"hash/crc32" "hash/crc32"
"path" "path"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/sync/errgroup" "github.com/minio/minio/pkg/sync/errgroup"
) )
@ -117,7 +117,6 @@ func hashOrder(key string, cardinality int) []int {
// Constructs xlMetaV1 using `jsoniter` lib. // Constructs xlMetaV1 using `jsoniter` lib.
func xlMetaV1UnmarshalJSON(ctx context.Context, xlMetaBuf []byte) (xlMeta xlMetaV1, err error) { func xlMetaV1UnmarshalJSON(ctx context.Context, xlMetaBuf []byte) (xlMeta xlMetaV1, err error) {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
err = json.Unmarshal(xlMetaBuf, &xlMeta) err = json.Unmarshal(xlMetaBuf, &xlMeta)
return xlMeta, err return xlMeta, err
} }

@ -54,7 +54,9 @@ export MINIO_SECRET_KEY_OLD=minio123
minio server /data minio server /data
``` ```
Once the migration is complete and server has started successfully remove `MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD` environment variables, restart the server. Once the migration is complete, server will automatically unset the `MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD` with in the process namespace.
> **NOTE: Make sure to remove `MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD` in scripts or service files before next service restarts of the server to avoid double encryption of your existing contents.**
#### Region #### Region
| Field | Type | Description | | Field | Type | Description |

@ -37,6 +37,13 @@ var (
return fmt.Sprint return fmt.Sprint
}() }()
RedBold = func() func(format string, a ...interface{}) string {
if IsTerminal() {
return color.New(color.FgRed, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
Red = func() func(format string, a ...interface{}) string { Red = func() func(format string, a ...interface{}) string {
if IsTerminal() { if IsTerminal() {
return color.New(color.FgRed).SprintfFunc() return color.New(color.FgRed).SprintfFunc()

@ -36,15 +36,17 @@ type TargetList struct {
} }
// Add - adds unique target to target list. // Add - adds unique target to target list.
func (list *TargetList) Add(target Target) error { func (list *TargetList) Add(targets ...Target) error {
list.Lock() list.Lock()
defer list.Unlock() defer list.Unlock()
if _, ok := list.targets[target.ID()]; ok { for _, target := range targets {
return fmt.Errorf("target %v already exists", target.ID()) if _, ok := list.targets[target.ID()]; ok {
return fmt.Errorf("target %v already exists", target.ID())
}
list.targets[target.ID()] = target
} }
list.targets[target.ID()] = target
return nil return nil
} }
@ -102,6 +104,19 @@ func (list *TargetList) Remove(targetids ...TargetID) <-chan TargetIDErr {
return errCh return errCh
} }
// Targets - list all targets
func (list *TargetList) Targets() []Target {
list.RLock()
defer list.RUnlock()
targets := []Target{}
for _, tgt := range list.targets {
targets = append(targets, tgt)
}
return targets
}
// List - returns available target IDs. // List - returns available target IDs.
func (list *TargetList) List() []TargetID { func (list *TargetList) List() []TargetID {
list.RLock() list.RLock()

Loading…
Cancel
Save