|
|
|
@ -20,6 +20,7 @@ import ( |
|
|
|
|
"context" |
|
|
|
|
"crypto/subtle" |
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"io" |
|
|
|
|
"net/http" |
|
|
|
@ -36,11 +37,13 @@ import ( |
|
|
|
|
"github.com/gorilla/mux" |
|
|
|
|
|
|
|
|
|
"github.com/minio/minio/cmd/config" |
|
|
|
|
"github.com/minio/minio/cmd/config/notify" |
|
|
|
|
"github.com/minio/minio/cmd/crypto" |
|
|
|
|
xhttp "github.com/minio/minio/cmd/http" |
|
|
|
|
"github.com/minio/minio/cmd/logger" |
|
|
|
|
"github.com/minio/minio/pkg/auth" |
|
|
|
|
"github.com/minio/minio/pkg/cpu" |
|
|
|
|
"github.com/minio/minio/pkg/event/target" |
|
|
|
|
"github.com/minio/minio/pkg/handlers" |
|
|
|
|
iampolicy "github.com/minio/minio/pkg/iam/policy" |
|
|
|
|
"github.com/minio/minio/pkg/madmin" |
|
|
|
@ -262,48 +265,7 @@ type ServerInfo struct { |
|
|
|
|
Data *ServerInfoData `json:"data"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ServerInfoHandler - GET /minio/admin/v2/info
|
|
|
|
|
// ----------
|
|
|
|
|
// Get server information
|
|
|
|
|
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
ctx := newContext(r, w, "ServerInfo") |
|
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction) |
|
|
|
|
if objectAPI == nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
serverInfo := globalNotificationSys.ServerInfo(ctx) |
|
|
|
|
// Once we have received all the ServerInfo from peers
|
|
|
|
|
// add the local peer server info as well.
|
|
|
|
|
serverInfo = append(serverInfo, ServerInfo{ |
|
|
|
|
Addr: getHostName(r), |
|
|
|
|
Data: &ServerInfoData{ |
|
|
|
|
ConnStats: globalConnStats.toServerConnStats(), |
|
|
|
|
HTTPStats: globalHTTPStats.toServerHTTPStats(), |
|
|
|
|
Properties: ServerProperties{ |
|
|
|
|
Uptime: UTCNow().Sub(globalBootTime), |
|
|
|
|
Version: Version, |
|
|
|
|
CommitID: CommitID, |
|
|
|
|
DeploymentID: globalDeploymentID, |
|
|
|
|
SQSARN: globalNotificationSys.GetARNList(), |
|
|
|
|
Region: globalServerRegion, |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// Marshal API response
|
|
|
|
|
jsonBytes, err := json.Marshal(serverInfo) |
|
|
|
|
if err != nil { |
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Reply with storage information (across nodes in a
|
|
|
|
|
// distributed setup) as json.
|
|
|
|
|
writeSuccessResponseJSON(w, jsonBytes) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ServerInfoHandler - GET /minio/admin/v2/storageinfo
|
|
|
|
|
// StorageInfoHandler - GET /minio/admin/v2/storageinfo
|
|
|
|
|
// ----------
|
|
|
|
|
// Get server information
|
|
|
|
|
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
@ -1303,3 +1265,291 @@ func (a adminAPIHandlers) ServerHardwareInfoHandler(w http.ResponseWriter, r *ht |
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ServerInfoHandler - GET /minio/admin/v2/info
|
|
|
|
|
// ----------
|
|
|
|
|
// Get server information
|
|
|
|
|
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
ctx := newContext(r, w, "ServerInfo") |
|
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, "") |
|
|
|
|
if objectAPI == nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg, err := readServerConfig(ctx, objectAPI) |
|
|
|
|
if err != nil { |
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
infoMsg := madmin.InfoMessage{} |
|
|
|
|
vault := madmin.Vault{} |
|
|
|
|
if GlobalKMS != nil { |
|
|
|
|
vault = fetchVaultStatus(cfg) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ldap := madmin.LDAP{} |
|
|
|
|
if globalLDAPConfig.Enabled { |
|
|
|
|
ldapConn, err := globalLDAPConfig.Connect() |
|
|
|
|
if err != nil { |
|
|
|
|
ldap.Status = "offline" |
|
|
|
|
} else if ldapConn == nil { |
|
|
|
|
ldap.Status = "Not Configured" |
|
|
|
|
} else { |
|
|
|
|
ldap.Status = "online" |
|
|
|
|
} |
|
|
|
|
// Close ldap connection to avoid leaks.
|
|
|
|
|
defer ldapConn.Close() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
log, audit := fetchLoggerInfo(cfg) |
|
|
|
|
|
|
|
|
|
// Get the notification target info
|
|
|
|
|
notifyTarget := fetchLambdaInfo(cfg) |
|
|
|
|
|
|
|
|
|
// Fetching the Storage information
|
|
|
|
|
storageInfo := objectAPI.StorageInfo(ctx) |
|
|
|
|
|
|
|
|
|
var OnDisks int |
|
|
|
|
var OffDisks int |
|
|
|
|
var backend interface{} |
|
|
|
|
|
|
|
|
|
if storageInfo.Backend.Type == BackendType(madmin.Erasure) { |
|
|
|
|
|
|
|
|
|
for _, v := range storageInfo.Backend.OnlineDisks { |
|
|
|
|
OnDisks += v |
|
|
|
|
} |
|
|
|
|
for _, v := range storageInfo.Backend.OfflineDisks { |
|
|
|
|
OffDisks += v |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
backend = madmin.XlBackend{ |
|
|
|
|
Type: madmin.ErasureType, |
|
|
|
|
OnlineDisks: OnDisks, |
|
|
|
|
OfflineDisks: OffDisks, |
|
|
|
|
StandardSCData: storageInfo.Backend.StandardSCData, |
|
|
|
|
StandardSCParity: storageInfo.Backend.StandardSCParity, |
|
|
|
|
RRSCData: storageInfo.Backend.RRSCData, |
|
|
|
|
RRSCParity: storageInfo.Backend.RRSCParity, |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
backend = madmin.FsBackend{ |
|
|
|
|
Type: madmin.FsType, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
mode := "" |
|
|
|
|
if globalSafeMode { |
|
|
|
|
mode = "safe" |
|
|
|
|
} else { |
|
|
|
|
mode = "online" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
server := getLocalServerProperty(globalEndpoints, r) |
|
|
|
|
servers := globalNotificationSys.ServerInfo() |
|
|
|
|
servers = append(servers, server) |
|
|
|
|
|
|
|
|
|
for _, sp := range servers { |
|
|
|
|
for i, di := range sp.Disks { |
|
|
|
|
path := "" |
|
|
|
|
if globalIsXL { |
|
|
|
|
path = di.DrivePath |
|
|
|
|
} |
|
|
|
|
if globalIsDistXL { |
|
|
|
|
path = sp.Endpoint + di.DrivePath |
|
|
|
|
} |
|
|
|
|
// For distributed
|
|
|
|
|
for a := range storageInfo.Backend.Sets { |
|
|
|
|
for b := range storageInfo.Backend.Sets[a] { |
|
|
|
|
ep := storageInfo.Backend.Sets[a][b].Endpoint |
|
|
|
|
|
|
|
|
|
if globalIsDistXL { |
|
|
|
|
if strings.Replace(ep, "http://", "", -1) == path || strings.Replace(ep, "https://", "", -1) == path { |
|
|
|
|
sp.Disks[i].State = storageInfo.Backend.Sets[a][b].State |
|
|
|
|
sp.Disks[i].UUID = storageInfo.Backend.Sets[a][b].UUID |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if globalIsXL { |
|
|
|
|
if ep == path { |
|
|
|
|
sp.Disks[i].State = storageInfo.Backend.Sets[a][b].State |
|
|
|
|
sp.Disks[i].UUID = storageInfo.Backend.Sets[a][b].UUID |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
domain := globalDomainNames |
|
|
|
|
buckets := madmin.Buckets{Count: 20} |
|
|
|
|
objects := madmin.Objects{Count: 200} |
|
|
|
|
usage := madmin.Usage{Size: 1024} |
|
|
|
|
services := madmin.Services{ |
|
|
|
|
Vault: vault, |
|
|
|
|
LDAP: ldap, |
|
|
|
|
Logger: log, |
|
|
|
|
Audit: audit, |
|
|
|
|
Notifications: notifyTarget, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
infoMsg = madmin.InfoMessage{ |
|
|
|
|
Mode: mode, |
|
|
|
|
Domain: domain, |
|
|
|
|
Region: globalServerRegion, |
|
|
|
|
SQSARN: globalNotificationSys.GetARNList(), |
|
|
|
|
DeploymentID: globalDeploymentID, |
|
|
|
|
Buckets: buckets, |
|
|
|
|
Objects: objects, |
|
|
|
|
Usage: usage, |
|
|
|
|
Services: services, |
|
|
|
|
Backend: backend, |
|
|
|
|
Servers: servers, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Marshal API response
|
|
|
|
|
jsonBytes, err := json.Marshal(infoMsg) |
|
|
|
|
if err != nil { |
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//Reply with storage information (across nodes in a
|
|
|
|
|
// distributed setup) as json.
|
|
|
|
|
writeSuccessResponseJSON(w, jsonBytes) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus { |
|
|
|
|
lambdaMap := make(map[string][]madmin.TargetIDStatus) |
|
|
|
|
targetList, _ := notify.GetNotificationTargets(cfg, GlobalServiceDoneCh, globalRootCAs) |
|
|
|
|
|
|
|
|
|
for targetID, target := range targetList.TargetMap() { |
|
|
|
|
targetIDStatus := make(map[string]madmin.Status) |
|
|
|
|
active, _ := target.IsActive() |
|
|
|
|
if active { |
|
|
|
|
targetIDStatus[targetID.ID] = madmin.Status{Status: "Online"} |
|
|
|
|
} else { |
|
|
|
|
targetIDStatus[targetID.ID] = madmin.Status{Status: "Offline"} |
|
|
|
|
} |
|
|
|
|
list := lambdaMap[targetID.Name] |
|
|
|
|
list = append(list, targetIDStatus) |
|
|
|
|
lambdaMap[targetID.Name] = list |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
notify := make([]map[string][]madmin.TargetIDStatus, len(lambdaMap)) |
|
|
|
|
counter := 0 |
|
|
|
|
for key, value := range lambdaMap { |
|
|
|
|
v := make(map[string][]madmin.TargetIDStatus) |
|
|
|
|
v[key] = value |
|
|
|
|
notify[counter] = v |
|
|
|
|
counter++ |
|
|
|
|
} |
|
|
|
|
return notify |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// fetchVaultStatus fetches Vault Info
|
|
|
|
|
func fetchVaultStatus(cfg config.Config) madmin.Vault { |
|
|
|
|
vault := madmin.Vault{} |
|
|
|
|
if GlobalKMS == nil { |
|
|
|
|
vault.Status = "disabled" |
|
|
|
|
return vault |
|
|
|
|
} |
|
|
|
|
keyID := GlobalKMS.KeyID() |
|
|
|
|
kmsInfo := GlobalKMS.Info() |
|
|
|
|
|
|
|
|
|
if kmsInfo.Endpoint == "" { |
|
|
|
|
vault.Status = "KMS configured using master key" |
|
|
|
|
return vault |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := checkConnection(kmsInfo.Endpoint); err != nil { |
|
|
|
|
|
|
|
|
|
vault.Status = "offline" |
|
|
|
|
} else { |
|
|
|
|
vault.Status = "online" |
|
|
|
|
|
|
|
|
|
kmsContext := crypto.Context{"MinIO admin API": "KMSKeyStatusHandler"} // Context for a test key operation
|
|
|
|
|
// 1. Generate a new key using the KMS.
|
|
|
|
|
key, sealedKey, err := GlobalKMS.GenerateKey(keyID, kmsContext) |
|
|
|
|
if err != nil { |
|
|
|
|
vault.Encrypt = "Encryption failed" |
|
|
|
|
} else { |
|
|
|
|
vault.Encrypt = "Ok" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 2. Check whether we can update / re-wrap the sealed key.
|
|
|
|
|
sealedKey, err = GlobalKMS.UpdateKey(keyID, sealedKey, kmsContext) |
|
|
|
|
if err != nil { |
|
|
|
|
vault.Update = "Re-wrap failed:" |
|
|
|
|
} else { |
|
|
|
|
vault.Update = "Ok" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. Verify that we can indeed decrypt the (encrypted) key
|
|
|
|
|
decryptedKey, decryptErr := GlobalKMS.UnsealKey(keyID, sealedKey, kmsContext) |
|
|
|
|
|
|
|
|
|
// 4. Compare generated key with decrypted key
|
|
|
|
|
if subtle.ConstantTimeCompare(key[:], decryptedKey[:]) != 1 || decryptErr != nil { |
|
|
|
|
vault.Decrypt = "Re-wrap failed:" |
|
|
|
|
} else { |
|
|
|
|
vault.Decrypt = "Ok" |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return vault |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// fetchLoggerDetails return log info
|
|
|
|
|
func fetchLoggerInfo(cfg config.Config) ([]madmin.Logger, []madmin.Audit) { |
|
|
|
|
loggerCfg, _ := logger.LookupConfig(cfg) |
|
|
|
|
|
|
|
|
|
var logger []madmin.Logger |
|
|
|
|
var auditlogger []madmin.Audit |
|
|
|
|
for log, l := range loggerCfg.HTTP { |
|
|
|
|
if l.Enabled { |
|
|
|
|
err := checkConnection(l.Endpoint) |
|
|
|
|
if err == nil { |
|
|
|
|
mapLog := make(map[string]madmin.Status) |
|
|
|
|
mapLog[log] = madmin.Status{Status: "Online"} |
|
|
|
|
logger = append(logger, mapLog) |
|
|
|
|
} else { |
|
|
|
|
mapLog := make(map[string]madmin.Status) |
|
|
|
|
mapLog[log] = madmin.Status{Status: "offline"} |
|
|
|
|
logger = append(logger, mapLog) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for audit, l := range loggerCfg.Audit { |
|
|
|
|
if l.Enabled { |
|
|
|
|
err := checkConnection(l.Endpoint) |
|
|
|
|
if err == nil { |
|
|
|
|
mapAudit := make(map[string]madmin.Status) |
|
|
|
|
mapAudit[audit] = madmin.Status{Status: "Online"} |
|
|
|
|
auditlogger = append(auditlogger, mapAudit) |
|
|
|
|
} else { |
|
|
|
|
mapAudit := make(map[string]madmin.Status) |
|
|
|
|
mapAudit[audit] = madmin.Status{Status: "Offline"} |
|
|
|
|
auditlogger = append(auditlogger, mapAudit) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return logger, auditlogger |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// checkConnection - ping an endpoint , return err in case of no connection
|
|
|
|
|
func checkConnection(endpointStr string) error { |
|
|
|
|
u, pErr := xnet.ParseURL(endpointStr) |
|
|
|
|
if pErr != nil { |
|
|
|
|
return pErr |
|
|
|
|
} |
|
|
|
|
if dErr := u.DialHTTP(); dErr != nil { |
|
|
|
|
if urlErr, ok := dErr.(*url.Error); ok { |
|
|
|
|
// To treat "connection refused" errors as un reachable endpoint.
|
|
|
|
|
if target.IsConnRefusedErr(urlErr.Err) { |
|
|
|
|
return errors.New("endpoint unreachable, please check your endpoint") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return dErr |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|