You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
12 KiB
437 lines
12 KiB
5 years ago
|
/*
|
||
|
* 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 (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/gorilla/mux"
|
||
|
"github.com/minio/minio/cmd/config"
|
||
|
"github.com/minio/minio/cmd/logger"
|
||
|
"github.com/minio/minio/pkg/color"
|
||
|
"github.com/minio/minio/pkg/madmin"
|
||
|
)
|
||
|
|
||
|
// DelConfigKVHandler - DELETE /minio/admin/v2/del-config-kv
|
||
|
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "DelConfigKVHandler")
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||
|
if err != nil {
|
||
|
logger.LogIf(ctx, err, logger.Application)
|
||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||
|
return
|
||
|
}
|
||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
oldCfg := cfg.Clone()
|
||
|
scanner := bufio.NewScanner(bytes.NewReader(kvBytes))
|
||
|
for scanner.Scan() {
|
||
|
if err = cfg.DelKVS(scanner.Text()); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
if err = scanner.Err(); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = saveServerConfig(ctx, objectAPI, cfg, oldCfg); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetConfigKVHandler - PUT /minio/admin/v2/set-config-kv
|
||
|
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "SetConfigKVHandler")
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||
|
if err != nil {
|
||
|
logger.LogIf(ctx, err, logger.Application)
|
||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||
|
return
|
||
|
}
|
||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
defaultKVS := configDefaultKVS()
|
||
|
oldCfg := cfg.Clone()
|
||
|
scanner := bufio.NewScanner(bytes.NewReader(kvBytes))
|
||
|
var comment string
|
||
|
for scanner.Scan() {
|
||
|
if strings.HasPrefix(scanner.Text(), config.KvComment) {
|
||
|
// Join multiple comments for each newline, separated by ","
|
||
|
comments := []string{comment, strings.TrimPrefix(scanner.Text(), config.KvComment)}
|
||
|
comment = strings.Join(comments, config.KvNewline)
|
||
|
continue
|
||
|
}
|
||
|
if err = cfg.SetKVS(scanner.Text(), comment, defaultKVS); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
// Empty the comment for the next sub-system
|
||
|
comment = ""
|
||
|
}
|
||
|
if err = scanner.Err(); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = validateConfig(cfg); err != nil {
|
||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Update the actual server config on disk.
|
||
|
if err = saveServerConfig(ctx, objectAPI, cfg, oldCfg); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Write to the config input KV to history.
|
||
|
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetConfigKVHandler - GET /minio/admin/v2/get-config-kv?key={key}
|
||
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "GetConfigKVHandler")
|
||
|
|
||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||
|
if objectAPI == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
vars := mux.Vars(r)
|
||
|
var body strings.Builder
|
||
|
if vars["key"] != "" {
|
||
|
kvs, err := globalServerConfig.GetKVS(vars["key"])
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
for k, kv := range kvs {
|
||
|
c, ok := kv[config.Comment]
|
||
|
if ok {
|
||
|
// For multiple comments split it correctly.
|
||
|
for _, c1 := range strings.Split(c, config.KvNewline) {
|
||
|
if c1 == "" {
|
||
|
continue
|
||
|
}
|
||
|
body.WriteString(color.YellowBold(config.KvComment))
|
||
|
body.WriteString(config.KvSpaceSeparator)
|
||
|
body.WriteString(color.BlueBold(strings.TrimSpace(c1)))
|
||
|
body.WriteString(config.KvNewline)
|
||
|
}
|
||
|
}
|
||
|
body.WriteString(color.CyanBold(k))
|
||
|
body.WriteString(config.KvSpaceSeparator)
|
||
|
body.WriteString(kv.String())
|
||
|
if len(kvs) > 1 {
|
||
|
body.WriteString(config.KvNewline)
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
body.WriteString(globalServerConfig.String())
|
||
|
}
|
||
|
password := globalActiveCred.SecretKey
|
||
|
econfigData, err := madmin.EncryptData(password, []byte(body.String()))
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
writeSuccessResponseJSON(w, econfigData)
|
||
|
}
|
||
|
|
||
|
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "ClearConfigHistoryKVHandler")
|
||
|
|
||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||
|
if objectAPI == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
vars := mux.Vars(r)
|
||
|
restoreID := vars["restoreId"]
|
||
|
if restoreID == "" {
|
||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||
|
return
|
||
|
}
|
||
|
if restoreID == "all" {
|
||
|
chEntries, err := listServerConfigHistory(ctx, objectAPI)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
for _, chEntry := range chEntries {
|
||
|
if err = delServerConfigHistory(ctx, objectAPI, chEntry.RestoreID); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if err := delServerConfigHistory(ctx, objectAPI, restoreID); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
||
|
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "RestoreConfigHistoryKVHandler")
|
||
|
|
||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||
|
if objectAPI == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
vars := mux.Vars(r)
|
||
|
restoreID := vars["restoreId"]
|
||
|
if restoreID == "" {
|
||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
kvBytes, err := readServerConfigHistory(ctx, objectAPI, restoreID)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
cfg, err := readServerConfig(ctx, objectAPI)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
defaultKVS := configDefaultKVS()
|
||
|
oldCfg := cfg.Clone()
|
||
|
scanner := bufio.NewScanner(bytes.NewReader(kvBytes))
|
||
|
var comment string
|
||
|
for scanner.Scan() {
|
||
|
if strings.HasPrefix(scanner.Text(), config.KvComment) {
|
||
|
// Join multiple comments for each newline, separated by "\n"
|
||
|
comment = strings.Join([]string{comment, scanner.Text()}, config.KvNewline)
|
||
|
continue
|
||
|
}
|
||
|
if err = cfg.SetKVS(scanner.Text(), comment, defaultKVS); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
comment = ""
|
||
|
}
|
||
|
if err = scanner.Err(); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = validateConfig(cfg); err != nil {
|
||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = saveServerConfig(ctx, objectAPI, cfg, oldCfg); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
delServerConfigHistory(ctx, objectAPI, restoreID)
|
||
|
}
|
||
|
|
||
|
// ListConfigHistoryKVHandler - lists all the KV ids.
|
||
|
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "ListConfigHistoryKVHandler")
|
||
|
|
||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||
|
if objectAPI == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
chEntries, err := listServerConfigHistory(ctx, objectAPI)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
data, err := json.Marshal(chEntries)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
writeSuccessResponseJSON(w, data)
|
||
|
}
|
||
|
|
||
|
// HelpConfigKVHandler - GET /minio/admin/v2/help-config-kv?subSys={subSys}&key={key}
|
||
|
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "HelpConfigKVHandler")
|
||
|
|
||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||
|
if objectAPI == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
vars := mux.Vars(r)
|
||
|
subSys := vars["subSys"]
|
||
|
key := vars["key"]
|
||
|
|
||
|
rd, err := GetHelp(subSys, key)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
io.Copy(w, rd)
|
||
|
w.(http.Flusher).Flush()
|
||
|
}
|
||
|
|
||
|
// SetConfigHandler - PUT /minio/admin/v1/config
|
||
|
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "SetConfigHandler")
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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, logger.Application)
|
||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var cfg config.Config
|
||
|
if err = json.Unmarshal(configBytes, &cfg); err != nil {
|
||
|
logger.LogIf(ctx, err)
|
||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = validateConfig(cfg); err != nil {
|
||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = saveServerConfig(ctx, objectAPI, cfg, nil); err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Reply to the client before restarting minio server.
|
||
|
writeSuccessResponseHeadersOnly(w)
|
||
|
}
|
||
|
|
||
|
// GetConfigHandler - GET /minio/admin/v1/config
|
||
|
// Get config.json of this minio setup.
|
||
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
ctx := newContext(r, w, "GetConfigHandler")
|
||
|
|
||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||
|
if objectAPI == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
config, err := readServerConfig(ctx, objectAPI)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
configData, err := json.MarshalIndent(config, "", "\t")
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
password := globalActiveCred.SecretKey
|
||
|
econfigData, err := madmin.EncryptData(password, configData)
|
||
|
if err != nil {
|
||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
writeSuccessResponseJSON(w, econfigData)
|
||
|
}
|