mirror of https://github.com/minio/minio.git
Fix config set handler (#5384)
- Return error when the config JSON has duplicate keys (fixes #5286) - Limit size of configuration file provided to 256KiB - this prevents another form of DoS
This commit is contained in:
parent
b526cd7e55
commit
f413224b24
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -35,6 +36,8 @@ import (
|
||||||
const (
|
const (
|
||||||
minioAdminOpHeader = "X-Minio-Operation"
|
minioAdminOpHeader = "X-Minio-Operation"
|
||||||
minioConfigTmpFormat = "config-%s.json"
|
minioConfigTmpFormat = "config-%s.json"
|
||||||
|
|
||||||
|
maxConfigJSONSize = 256 * 1024 // 256KiB
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type-safe query params.
|
// Type-safe query params.
|
||||||
|
@ -978,22 +981,39 @@ func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read configuration bytes from request body.
|
// Read configuration bytes from request body.
|
||||||
configBytes, err := ioutil.ReadAll(r.Body)
|
configBuf := make([]byte, maxConfigJSONSize+1)
|
||||||
if err != nil {
|
n, err := io.ReadFull(r.Body, configBuf)
|
||||||
|
if err == nil {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponse(w, ErrAdminConfigTooLarge, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
errorIf(err, "Failed to read config from request body.")
|
errorIf(err, "Failed to read config from request body.")
|
||||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configBytes := configBuf[:n]
|
||||||
|
|
||||||
|
// Validate JSON provided in the request body: check the
|
||||||
|
// client has not sent JSON objects with duplicate keys.
|
||||||
|
if err = checkDupJSONKeys(string(configBytes)); err != nil {
|
||||||
|
errorIf(err, "config contains duplicate JSON entries.")
|
||||||
|
writeErrorResponse(w, ErrAdminConfigBadJSON, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var config serverConfig
|
var config serverConfig
|
||||||
err = json.Unmarshal(configBytes, &config)
|
err = json.Unmarshal(configBytes, &config)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err, "Failed to unmarshal config from request body.")
|
errorIf(err, "Failed to unmarshal JSON configuration", err)
|
||||||
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If credentials for the server are provided via environment,
|
||||||
|
// then credentials in the provided configuration must match.
|
||||||
if globalIsEnvCreds {
|
if globalIsEnvCreds {
|
||||||
creds := globalServerConfig.GetCredential()
|
creds := globalServerConfig.GetCredential()
|
||||||
if config.Credential.AccessKey != creds.AccessKey ||
|
if config.Credential.AccessKey != creds.AccessKey ||
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -36,7 +37,8 @@ import (
|
||||||
"github.com/minio/minio/pkg/errors"
|
"github.com/minio/minio/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configJSON = []byte(`{
|
var (
|
||||||
|
configJSON = []byte(`{
|
||||||
"version": "13",
|
"version": "13",
|
||||||
"credential": {
|
"credential": {
|
||||||
"accessKey": "minio",
|
"accessKey": "minio",
|
||||||
|
@ -131,6 +133,7 @@ var configJSON = []byte(`{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
|
)
|
||||||
|
|
||||||
// adminXLTestBed - encapsulates subsystems that need to be setup for
|
// adminXLTestBed - encapsulates subsystems that need to be setup for
|
||||||
// admin-handler unit tests.
|
// admin-handler unit tests.
|
||||||
|
@ -1257,7 +1260,7 @@ func TestSetConfigHandler(t *testing.T) {
|
||||||
req, err := buildAdminRequest(queryVal, "set", http.MethodPut, int64(len(configJSON)),
|
req, err := buildAdminRequest(queryVal, "set", http.MethodPut, int64(len(configJSON)),
|
||||||
bytes.NewReader(configJSON))
|
bytes.NewReader(configJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to construct get-config object request - %v", err)
|
t.Fatalf("Failed to construct set-config object request - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
@ -1275,6 +1278,44 @@ func TestSetConfigHandler(t *testing.T) {
|
||||||
if !result.Status {
|
if !result.Status {
|
||||||
t.Error("Expected set-config to succeed, but failed")
|
t.Error("Expected set-config to succeed, but failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that a very large config file returns an error.
|
||||||
|
{
|
||||||
|
// Make a large enough config string
|
||||||
|
invalidCfg := []byte(strings.Repeat("A", maxConfigJSONSize+1))
|
||||||
|
req, err := buildAdminRequest(queryVal, "set", http.MethodPut, int64(len(invalidCfg)),
|
||||||
|
bytes.NewReader(invalidCfg))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to construct set-config object request - %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
|
respBody := string(rec.Body.Bytes())
|
||||||
|
if rec.Code != http.StatusBadRequest ||
|
||||||
|
!strings.Contains(respBody, "Configuration data provided exceeds the allowed maximum of") {
|
||||||
|
t.Errorf("Got unexpected response code or body %d - %s", rec.Code, respBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a config with duplicate keys in an object return
|
||||||
|
// error.
|
||||||
|
{
|
||||||
|
invalidCfg := append(configJSON[:len(configJSON)-1], []byte(`, "version": "15"}`)...)
|
||||||
|
req, err := buildAdminRequest(queryVal, "set", http.MethodPut, int64(len(invalidCfg)),
|
||||||
|
bytes.NewReader(invalidCfg))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to construct set-config object request - %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
adminTestBed.mux.ServeHTTP(rec, req)
|
||||||
|
respBody := string(rec.Body.Bytes())
|
||||||
|
if rec.Code != http.StatusBadRequest ||
|
||||||
|
!strings.Contains(respBody, "JSON configuration provided has objects with duplicate keys") {
|
||||||
|
t.Errorf("Got unexpected response code or body %d - %s", rec.Code, respBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdminServerInfo(t *testing.T) {
|
func TestAdminServerInfo(t *testing.T) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
@ -175,6 +176,8 @@ const (
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
ErrAdminInvalidSecretKey
|
ErrAdminInvalidSecretKey
|
||||||
ErrAdminConfigNoQuorum
|
ErrAdminConfigNoQuorum
|
||||||
|
ErrAdminConfigTooLarge
|
||||||
|
ErrAdminConfigBadJSON
|
||||||
ErrAdminCredentialsMismatch
|
ErrAdminCredentialsMismatch
|
||||||
ErrInsecureClientRequest
|
ErrInsecureClientRequest
|
||||||
ErrObjectTampered
|
ErrObjectTampered
|
||||||
|
@ -712,6 +715,17 @@ var errorCodeResponse = map[APIErrorCode]APIError{
|
||||||
Description: "Configuration update failed because server quorum was not met",
|
Description: "Configuration update failed because server quorum was not met",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigTooLarge: {
|
||||||
|
Code: "XMinioAdminConfigTooLarge",
|
||||||
|
Description: fmt.Sprintf("Configuration data provided exceeds the allowed maximum of %d bytes",
|
||||||
|
maxConfigJSONSize),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigBadJSON: {
|
||||||
|
Code: "XMinioAdminConfigBadJSON",
|
||||||
|
Description: "JSON configuration provided has objects with duplicate keys",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminCredentialsMismatch: {
|
ErrAdminCredentialsMismatch: {
|
||||||
Code: "XMinioAdminCredentialsMismatch",
|
Code: "XMinioAdminCredentialsMismatch",
|
||||||
Description: "Credentials in config mismatch with server environment variables",
|
Description: "Credentials in config mismatch with server environment variables",
|
||||||
|
|
Loading…
Reference in New Issue