ensure authenticated request bodies for Admin-API (#5984)

This commit adds a check to the server's admin-API such that it only
accepts Admin-API requests with authenticated bodies. Further this
commit updates the `madmin` package to always add the
`X-Amz-Content-Sha256` header.

This change improves the Admin-API security since the server does not
accept unauthenticated request bodies anymore.

After this commit `mc` must be updated to the new `madmin` api because
requests over TLS connections will fail.
master
Andreas Auernhammer 7 years ago committed by kannappanr
parent 5282639f3c
commit 9fb94e6aa8
  1. 2
      cmd/auth-handler.go
  2. 45
      pkg/madmin/api.go
  3. 8
      pkg/madmin/config-commands.go
  4. 6
      pkg/madmin/generic-commands.go
  5. 9
      pkg/madmin/heal-commands.go
  6. 4
      pkg/madmin/service-commands.go

@ -114,7 +114,7 @@ func getRequestAuthType(r *http.Request) authType {
// It does not accept presigned or JWT or anonymous requests. // It does not accept presigned or JWT or anonymous requests.
func checkAdminRequestAuthType(r *http.Request, region string) APIErrorCode { func checkAdminRequestAuthType(r *http.Request, region string) APIErrorCode {
s3Err := ErrAccessDenied s3Err := ErrAccessDenied
if getRequestAuthType(r) == authTypeSigned { // we only support V4 (no presign) if _, ok := r.Header["X-Amz-Content-Sha256"]; ok && getRequestAuthType(r) == authTypeSigned && !skipContentSha256Cksum(r) { // we only support V4 (no presign) with auth. body
s3Err = isReqAuthenticated(r, region) s3Err = isReqAuthenticated(r, region)
} }
if s3Err != ErrNone { if s3Err != ErrNone {

@ -18,7 +18,6 @@ package madmin
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
@ -177,14 +176,8 @@ func (c *AdminClient) TraceOff() {
type requestData struct { type requestData struct {
customHeaders http.Header customHeaders http.Header
queryValues url.Values queryValues url.Values
relPath string // Url path relative to admin API base endpoint
// Url path relative to admin API base endpoint content []byte
relPath string
contentBody io.Reader
contentLength int64
contentSHA256Bytes []byte
contentMD5Bytes []byte
} }
// Filter out signature value from Authorization header. // Filter out signature value from Authorization header.
@ -404,43 +397,17 @@ func (c AdminClient) newRequest(method string, reqData requestData) (req *http.R
return nil, err return nil, err
} }
// Set content body if available.
if reqData.contentBody != nil {
req.Body = ioutil.NopCloser(reqData.contentBody)
}
// Set 'User-Agent' header for the request.
c.setUserAgent(req) c.setUserAgent(req)
// Set all headers.
for k, v := range reqData.customHeaders { for k, v := range reqData.customHeaders {
req.Header.Set(k, v[0]) req.Header.Set(k, v[0])
} }
if length := len(reqData.content); length > 0 {
// set incoming content-length. req.ContentLength = int64(length)
if reqData.contentLength > 0 {
req.ContentLength = reqData.contentLength
} }
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(reqData.content)))
req.Body = ioutil.NopCloser(bytes.NewReader(reqData.content))
shaHeader := unsignedPayload
if !c.secure {
if reqData.contentSHA256Bytes == nil {
shaHeader = hex.EncodeToString(sum256([]byte{}))
} else {
shaHeader = hex.EncodeToString(reqData.contentSHA256Bytes)
}
}
req.Header.Set("X-Amz-Content-Sha256", shaHeader)
// set md5Sum for content protection.
if reqData.contentMD5Bytes != nil {
req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(reqData.contentMD5Bytes))
}
// Add signature version '4' authorization header.
req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "", location) req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "", location)
// Return request.
return req, nil return req, nil
} }

@ -18,7 +18,6 @@
package madmin package madmin
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -66,8 +65,7 @@ func (adm *AdminClient) GetConfig() ([]byte, error) {
// SetConfig - set config supplied as config.json for the setup. // SetConfig - set config supplied as config.json for the setup.
func (adm *AdminClient) SetConfig(config io.Reader) (r SetConfigResult, err error) { func (adm *AdminClient) SetConfig(config io.Reader) (r SetConfigResult, err error) {
// No TLS? if !adm.secure { // No TLS?
if !adm.secure {
return r, fmt.Errorf("credentials/configuration cannot be updated over an insecure connection") return r, fmt.Errorf("credentials/configuration cannot be updated over an insecure connection")
} }
@ -79,9 +77,7 @@ func (adm *AdminClient) SetConfig(config io.Reader) (r SetConfigResult, err erro
reqData := requestData{ reqData := requestData{
relPath: "/v1/config", relPath: "/v1/config",
contentBody: bytes.NewReader(configBytes), content: configBytes,
contentMD5Bytes: sumMD5(configBytes),
contentSHA256Bytes: sum256(configBytes),
} }
// Execute PUT on /minio/admin/v1/config to set config. // Execute PUT on /minio/admin/v1/config to set config.

@ -18,7 +18,6 @@
package madmin package madmin
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@ -47,10 +46,7 @@ func (adm *AdminClient) SetCredentials(access, secret string) error {
// Setup new request // Setup new request
reqData := requestData{ reqData := requestData{
relPath: "/v1/config/credential", relPath: "/v1/config/credential",
contentBody: bytes.NewReader(body), content: body,
contentLength: int64(len(body)),
contentMD5Bytes: sumMD5(body),
contentSHA256Bytes: sum256(body),
} }
// Execute GET on bucket to list objects. // Execute GET on bucket to list objects.

@ -18,10 +18,8 @@
package madmin package madmin
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
@ -194,13 +192,9 @@ func (adm *AdminClient) Heal(bucket, prefix string, healOpts HealOpts,
// execute POST request to heal api // execute POST request to heal api
queryVals := make(url.Values) queryVals := make(url.Values)
var contentBody io.Reader
if clientToken != "" { if clientToken != "" {
queryVals.Set("clientToken", clientToken) queryVals.Set("clientToken", clientToken)
body = []byte{} body = []byte{}
} else {
// Set a body only if clientToken is not given
contentBody = bytes.NewReader(body)
} }
if forceStart { if forceStart {
queryVals.Set("forceStart", "true") queryVals.Set("forceStart", "true")
@ -208,8 +202,7 @@ func (adm *AdminClient) Heal(bucket, prefix string, healOpts HealOpts,
resp, err := adm.executeMethod("POST", requestData{ resp, err := adm.executeMethod("POST", requestData{
relPath: path, relPath: path,
contentBody: contentBody, content: body,
contentSHA256Bytes: sum256(body),
queryValues: queryVals, queryValues: queryVals,
}) })
defer closeResponse(resp) defer closeResponse(resp)

@ -18,7 +18,6 @@
package madmin package madmin
import ( import (
"bytes"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -88,8 +87,7 @@ func (adm *AdminClient) ServiceSendAction(action ServiceActionValue) error {
// Request API to Restart server // Request API to Restart server
resp, err := adm.executeMethod("POST", requestData{ resp, err := adm.executeMethod("POST", requestData{
relPath: "/v1/service", relPath: "/v1/service",
contentBody: bytes.NewReader(body), content: body,
contentSHA256Bytes: sum256(body),
}) })
defer closeResponse(resp) defer closeResponse(resp)
if err != nil { if err != nil {

Loading…
Cancel
Save