diff --git a/pkg/madmin/api-error-response.go b/pkg/madmin/api-error-response.go
new file mode 100644
index 000000000..49abf7e70
--- /dev/null
+++ b/pkg/madmin/api-error-response.go
@@ -0,0 +1,60 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin
+
+import "encoding/xml"
+
+/* **** SAMPLE ERROR RESPONSE ****
+
+
+ AccessDenied
+ Access Denied
+ bucketName
+ objectName
+ F19772218238A85A
+ GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD
+
+*/
+
+// ErrorResponse - Is the typed error returned by all API operations.
+type ErrorResponse struct {
+ XMLName xml.Name `xml:"Error" json:"-"`
+ Code string
+ Message string
+ BucketName string
+ Key string
+ RequestID string `xml:"RequestId"`
+ HostID string `xml:"HostId"`
+
+ // Region where the bucket is located. This header is returned
+ // only in HEAD bucket and ListObjects response.
+ Region string
+}
+
+// Error - Returns HTTP error string
+func (e ErrorResponse) Error() string {
+ return e.Message
+}
+
+// ErrInvalidArgument - Invalid argument response.
+func ErrInvalidArgument(message string) error {
+ return ErrorResponse{
+ Code: "InvalidArgument",
+ Message: message,
+ RequestID: "minio",
+ }
+}
diff --git a/pkg/madmin/client.go b/pkg/madmin/client.go
new file mode 100644
index 000000000..612e45927
--- /dev/null
+++ b/pkg/madmin/client.go
@@ -0,0 +1,37 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin
+
+const (
+ minioAdminOpHeader = "X-Minio-Operation"
+)
+
+// AdminClient - interface to Minio Management API
+type AdminClient struct {
+ client *Client
+}
+
+// NewAdminClient - create new Management client
+func NewAdminClient(addr string, access string, secret string, secure bool) (*AdminClient, error) {
+ client, err := New(addr, access, secret, secure)
+ if err != nil {
+ return nil, err
+ }
+
+ return &AdminClient{client: client}, nil
+}
diff --git a/pkg/madmin/client_test.go b/pkg/madmin/client_test.go
new file mode 100644
index 000000000..1aff33c45
--- /dev/null
+++ b/pkg/madmin/client_test.go
@@ -0,0 +1,24 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin_test
+package madmin_test
+
+import "testing"
+
+func TestMAdminClient(t *testing.T) {
+}
diff --git a/pkg/madmin/constants.go b/pkg/madmin/constants.go
new file mode 100644
index 000000000..299945b73
--- /dev/null
+++ b/pkg/madmin/constants.go
@@ -0,0 +1,19 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin
+
+const unsignedPayload = "UNSIGNED-PAYLOAD"
diff --git a/pkg/madmin/requests.go b/pkg/madmin/requests.go
new file mode 100644
index 000000000..3c6c7b615
--- /dev/null
+++ b/pkg/madmin/requests.go
@@ -0,0 +1,480 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math/rand"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ "regexp"
+ "runtime"
+ "strings"
+
+ "github.com/minio/minio-go/pkg/s3signer"
+ "github.com/minio/minio-go/pkg/s3utils"
+)
+
+// Client implements Amazon S3 compatible methods.
+type Client struct {
+ /// Standard options.
+
+ // AccessKeyID required for authorized requests.
+ accessKeyID string
+ // SecretAccessKey required for authorized requests.
+ secretAccessKey string
+
+ // User supplied.
+ appInfo struct {
+ appName string
+ appVersion string
+ }
+
+ endpointURL url.URL
+
+ // Indicate whether we are using https or not
+ secure bool
+
+ // Needs allocation.
+ httpClient *http.Client
+
+ // Advanced functionality.
+ isTraceEnabled bool
+ traceOutput io.Writer
+
+ // Random seed.
+ random *rand.Rand
+}
+
+// Global constants.
+const (
+ libraryName = "madmin-go"
+ libraryVersion = "0.0.1"
+)
+
+// User Agent should always following the below style.
+// Please open an issue to discuss any new changes here.
+//
+// Minio (OS; ARCH) LIB/VER APP/VER
+const (
+ libraryUserAgentPrefix = "Minio (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
+ libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion
+)
+
+// New - instantiate minio client Client, adds automatic verification
+// of signature.
+func New(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Client, error) {
+ clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, secure)
+ if err != nil {
+ return nil, err
+ }
+ return clnt, nil
+}
+
+// redirectHeaders copies all headers when following a redirect URL.
+// This won't be needed anymore from go 1.8 (https://github.com/golang/go/issues/4800)
+func redirectHeaders(req *http.Request, via []*http.Request) error {
+ if len(via) == 0 {
+ return nil
+ }
+ for key, val := range via[0].Header {
+ req.Header[key] = val
+ }
+ return nil
+}
+
+func privateNew(endpoint, accessKeyID, secretAccessKey string, secure bool) (*Client, error) {
+ // construct endpoint.
+ endpointURL, err := getEndpointURL(endpoint, secure)
+ if err != nil {
+ return nil, err
+ }
+
+ // instantiate new Client.
+ clnt := new(Client)
+ clnt.accessKeyID = accessKeyID
+ clnt.secretAccessKey = secretAccessKey
+
+ // Remember whether we are using https or not
+ clnt.secure = secure
+
+ // Save endpoint URL, user agent for future uses.
+ clnt.endpointURL = *endpointURL
+
+ // Instantiate http client and bucket location cache.
+ clnt.httpClient = &http.Client{
+ Transport: http.DefaultTransport,
+ CheckRedirect: redirectHeaders,
+ }
+
+ // Return.
+ return clnt, nil
+}
+
+// SetAppInfo - add application details to user agent.
+func (c *Client) SetAppInfo(appName string, appVersion string) {
+ // if app name and version is not set, we do not a new user
+ // agent.
+ if appName != "" && appVersion != "" {
+ c.appInfo = struct {
+ appName string
+ appVersion string
+ }{}
+ c.appInfo.appName = appName
+ c.appInfo.appVersion = appVersion
+ }
+}
+
+// SetCustomTransport - set new custom transport.
+func (c *Client) SetCustomTransport(customHTTPTransport http.RoundTripper) {
+ // Set this to override default transport
+ // ``http.DefaultTransport``.
+ //
+ // This transport is usually needed for debugging OR to add your
+ // own custom TLS certificates on the client transport, for custom
+ // CA's and certs which are not part of standard certificate
+ // authority follow this example :-
+ //
+ // tr := &http.Transport{
+ // TLSClientConfig: &tls.Config{RootCAs: pool},
+ // DisableCompression: true,
+ // }
+ // api.SetTransport(tr)
+ //
+ if c.httpClient != nil {
+ c.httpClient.Transport = customHTTPTransport
+ }
+}
+
+// TraceOn - enable HTTP tracing.
+func (c *Client) TraceOn(outputStream io.Writer) {
+ // if outputStream is nil then default to os.Stdout.
+ if outputStream == nil {
+ outputStream = os.Stdout
+ }
+ // Sets a new output stream.
+ c.traceOutput = outputStream
+
+ // Enable tracing.
+ c.isTraceEnabled = true
+}
+
+// TraceOff - disable HTTP tracing.
+func (c *Client) TraceOff() {
+ // Disable tracing.
+ c.isTraceEnabled = false
+}
+
+// requestMetadata - is container for all the values to make a
+// request.
+type requestData struct {
+ customHeaders http.Header
+ queryValues url.Values
+
+ contentBody io.Reader
+ contentLength int64
+ contentSHA256Bytes []byte
+ contentMD5Bytes []byte
+}
+
+// Filter out signature value from Authorization header.
+func (c Client) filterSignature(req *http.Request) {
+ /// Signature V4 authorization header.
+
+ // Save the original auth.
+ origAuth := req.Header.Get("Authorization")
+ // Strip out accessKeyID from:
+ // Credential=////aws4_request
+ regCred := regexp.MustCompile("Credential=([A-Z0-9]+)/")
+ newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
+
+ // Strip out 256-bit signature from: Signature=<256-bit signature>
+ regSign := regexp.MustCompile("Signature=([[0-9a-f]+)")
+ newAuth = regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
+
+ // Set a temporary redacted auth
+ req.Header.Set("Authorization", newAuth)
+ return
+}
+
+// dumpHTTP - dump HTTP request and response.
+func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error {
+ // Starts http dump.
+ _, err := fmt.Fprintln(c.traceOutput, "---------START-HTTP---------")
+ if err != nil {
+ return err
+ }
+
+ // Filter out Signature field from Authorization header.
+ c.filterSignature(req)
+
+ // Only display request header.
+ reqTrace, err := httputil.DumpRequestOut(req, false)
+ if err != nil {
+ return err
+ }
+
+ // Write request to trace output.
+ _, err = fmt.Fprint(c.traceOutput, string(reqTrace))
+ if err != nil {
+ return err
+ }
+
+ // Only display response header.
+ var respTrace []byte
+
+ // For errors we make sure to dump response body as well.
+ if resp.StatusCode != http.StatusOK &&
+ resp.StatusCode != http.StatusPartialContent &&
+ resp.StatusCode != http.StatusNoContent {
+ respTrace, err = httputil.DumpResponse(resp, true)
+ if err != nil {
+ return err
+ }
+ } else {
+ // WORKAROUND for https://github.com/golang/go/issues/13942.
+ // httputil.DumpResponse does not print response headers for
+ // all successful calls which have response ContentLength set
+ // to zero. Keep this workaround until the above bug is fixed.
+ if resp.ContentLength == 0 {
+ var buffer bytes.Buffer
+ if err = resp.Header.Write(&buffer); err != nil {
+ return err
+ }
+ respTrace = buffer.Bytes()
+ respTrace = append(respTrace, []byte("\r\n")...)
+ } else {
+ respTrace, err = httputil.DumpResponse(resp, false)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // Write response to trace output.
+ _, err = fmt.Fprint(c.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
+ if err != nil {
+ return err
+ }
+
+ // Ends the http dump.
+ _, err = fmt.Fprintln(c.traceOutput, "---------END-HTTP---------")
+ if err != nil {
+ return err
+ }
+
+ // Returns success.
+ return nil
+}
+
+// do - execute http request.
+func (c Client) do(req *http.Request) (*http.Response, error) {
+ var resp *http.Response
+ var err error
+ // Do the request in a loop in case of 307 http is met since golang still doesn't
+ // handle properly this situation (https://github.com/golang/go/issues/7912)
+ for {
+ resp, err = c.httpClient.Do(req)
+ if err != nil {
+ // Handle this specifically for now until future Golang
+ // versions fix this issue properly.
+ urlErr, ok := err.(*url.Error)
+ if ok && strings.Contains(urlErr.Err.Error(), "EOF") {
+ return nil, &url.Error{
+ Op: urlErr.Op,
+ URL: urlErr.URL,
+ Err: fmt.Errorf("Connection closed by foreign host %s", urlErr.URL),
+ }
+ }
+ return nil, err
+ }
+ // Redo the request with the new redirect url if http 307 is returned, quit the loop otherwise
+ if resp != nil && resp.StatusCode == http.StatusTemporaryRedirect {
+ newURL, uErr := url.Parse(resp.Header.Get("Location"))
+ if uErr != nil {
+ break
+ }
+ req.URL = newURL
+ } else {
+ break
+ }
+ }
+
+ // Response cannot be non-nil, report if its the case.
+ if resp == nil {
+ msg := "Response is empty. " // + reportIssue
+ return nil, ErrInvalidArgument(msg)
+ }
+
+ // If trace is enabled, dump http request and response.
+ if c.isTraceEnabled {
+ err = c.dumpHTTP(req, resp)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return resp, nil
+}
+
+// List of success status.
+var successStatus = []int{
+ http.StatusOK,
+ http.StatusNoContent,
+ http.StatusPartialContent,
+}
+
+// executeMethod - instantiates a given method, and retries the
+// request upon any error up to maxRetries attempts in a binomially
+// delayed manner using a standard back off algorithm.
+func (c Client) executeMethod(method string, reqData requestData) (res *http.Response, err error) {
+
+ // Create a done channel to control 'ListObjects' go routine.
+ doneCh := make(chan struct{}, 1)
+
+ // Indicate to our routine to exit cleanly upon return.
+ defer close(doneCh)
+
+ // Instantiate a new request.
+ var req *http.Request
+ req, err = c.newRequest(method, reqData)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initiate the request.
+ res, err = c.do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ // For any known successful http status, return quickly.
+ for _, httpStatus := range successStatus {
+ if httpStatus == res.StatusCode {
+ return res, nil
+ }
+ }
+
+ // Read the body to be saved later.
+ errBodyBytes, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+ // Save the body.
+ errBodySeeker := bytes.NewReader(errBodyBytes)
+ res.Body = ioutil.NopCloser(errBodySeeker)
+
+ // Save the body back again.
+ errBodySeeker.Seek(0, 0) // Seek back to starting point.
+ res.Body = ioutil.NopCloser(errBodySeeker)
+
+ return res, err
+}
+
+// set User agent.
+func (c Client) setUserAgent(req *http.Request) {
+ req.Header.Set("User-Agent", libraryUserAgent)
+ if c.appInfo.appName != "" && c.appInfo.appVersion != "" {
+ req.Header.Set("User-Agent", libraryUserAgent+" "+c.appInfo.appName+"/"+c.appInfo.appVersion)
+ }
+}
+
+// newRequest - instantiate a new HTTP request for a given method.
+func (c Client) newRequest(method string, reqData requestData) (req *http.Request, err error) {
+ // If no method is supplied default to 'POST'.
+ if method == "" {
+ method = "POST"
+ }
+
+ // Default all requests to "us-east-1"
+ location := "us-east-1"
+
+ // Construct a new target URL.
+ targetURL, err := c.makeTargetURL(reqData.queryValues)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize a new HTTP request for the method.
+ req, err = http.NewRequest(method, targetURL.String(), nil)
+ if err != nil {
+ 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)
+
+ // Set all headers.
+ for k, v := range reqData.customHeaders {
+ req.Header.Set(k, v[0])
+ }
+
+ // set incoming content-length.
+ if reqData.contentLength > 0 {
+ req.ContentLength = reqData.contentLength
+ }
+
+ 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)
+
+ // Return request.
+ return req, nil
+}
+
+// makeTargetURL make a new target url.
+func (c Client) makeTargetURL(queryValues url.Values) (*url.URL, error) {
+
+ host := c.endpointURL.Host
+ scheme := c.endpointURL.Scheme
+
+ urlStr := scheme + "://" + host + "/"
+
+ // If there are any query values, add them to the end.
+ if len(queryValues) > 0 {
+ urlStr = urlStr + "?" + s3utils.QueryEncode(queryValues)
+ }
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ return nil, err
+ }
+ return u, nil
+}
diff --git a/pkg/madmin/service-api.go b/pkg/madmin/service-api.go
new file mode 100644
index 000000000..2d62f7b02
--- /dev/null
+++ b/pkg/madmin/service-api.go
@@ -0,0 +1,141 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin
+
+import (
+ "encoding/json"
+ "errors"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+)
+
+// BackendType - represents different backend types.
+type BackendType int
+
+// Enum for different backend types.
+const (
+ Unknown BackendType = iota
+ // Filesystem backend.
+ FS
+ // Multi disk single node XL backend.
+ XL
+ // Add your own backend.
+)
+
+// ServiceStatusMetadata - represents total capacity of underlying storage.
+type ServiceStatusMetadata struct {
+ // Total disk space.
+ Total int64
+ // Free available disk space.
+ Free int64
+ // Backend type.
+ Backend struct {
+ // Represents various backend types, currently on FS and XL.
+ Type BackendType
+ // Following fields are only meaningful if BackendType is XL.
+ OnlineDisks int // Online disks during server startup.
+ OfflineDisks int // Offline disks during server startup.
+ ReadQuorum int // Minimum disks required for successful read operations.
+ WriteQuorum int // Minimum disks required for successful write operations.
+ }
+}
+
+// ServiceStatus - Connect to a minio server and call Service Status Management API
+// to fetch server's storage information represented by ServiceStatusMetadata structure
+func (adm *AdminClient) ServiceStatus() (ServiceStatusMetadata, error) {
+
+ reqData := requestData{}
+ reqData.queryValues = make(url.Values)
+ reqData.queryValues.Set("service", "")
+ reqData.customHeaders = make(http.Header)
+ reqData.customHeaders.Set(minioAdminOpHeader, "status")
+
+ // Execute GET on bucket to list objects.
+ resp, err := adm.client.executeMethod("GET", reqData)
+
+ defer closeResponse(resp)
+ if err != nil {
+ return ServiceStatusMetadata{}, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return ServiceStatusMetadata{}, errors.New("Got " + resp.Status)
+ }
+
+ respBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return ServiceStatusMetadata{}, err
+ }
+
+ var storageInfo ServiceStatusMetadata
+
+ err = json.Unmarshal(respBytes, &storageInfo)
+ if err != nil {
+ return ServiceStatusMetadata{}, err
+ }
+
+ return storageInfo, nil
+}
+
+// ServiceStop - Call Service Stop Management API to stop a specified Minio server
+func (adm *AdminClient) ServiceStop() error {
+ //
+ reqData := requestData{}
+ reqData.queryValues = make(url.Values)
+ reqData.queryValues.Set("service", "")
+ reqData.customHeaders = make(http.Header)
+ reqData.customHeaders.Set(minioAdminOpHeader, "stop")
+
+ // Execute GET on bucket to list objects.
+ resp, err := adm.client.executeMethod("POST", reqData)
+
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return errors.New("Got " + resp.Status)
+ }
+
+ return nil
+}
+
+// ServiceRestart - Call Service Restart API to restart a specified Minio server
+func (adm *AdminClient) ServiceRestart() error {
+ //
+ reqData := requestData{}
+ reqData.queryValues = make(url.Values)
+ reqData.queryValues.Set("service", "")
+ reqData.customHeaders = make(http.Header)
+ reqData.customHeaders.Set(minioAdminOpHeader, "restart")
+
+ // Execute GET on bucket to list objects.
+ resp, err := adm.client.executeMethod("POST", reqData)
+
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return errors.New("Got " + resp.Status)
+ }
+ return nil
+}
diff --git a/pkg/madmin/utils.go b/pkg/madmin/utils.go
new file mode 100644
index 000000000..1661c2f8c
--- /dev/null
+++ b/pkg/madmin/utils.go
@@ -0,0 +1,108 @@
+/*
+ * Minio Cloud Storage, (C) 2016 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 madmin
+
+import (
+ "crypto/sha256"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/minio/minio-go/pkg/s3utils"
+)
+
+// sum256 calculate sha256 sum for an input byte array.
+func sum256(data []byte) []byte {
+ hash := sha256.New()
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+// getEndpointURL - construct a new endpoint.
+func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
+ if strings.Contains(endpoint, ":") {
+ host, _, err := net.SplitHostPort(endpoint)
+ if err != nil {
+ return nil, err
+ }
+ if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
+ msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
+ return nil, ErrInvalidArgument(msg)
+ }
+ } else {
+ if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
+ msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
+ return nil, ErrInvalidArgument(msg)
+ }
+ }
+ // If secure is false, use 'http' scheme.
+ scheme := "https"
+ if !secure {
+ scheme = "http"
+ }
+
+ // Construct a secured endpoint URL.
+ endpointURLStr := scheme + "://" + endpoint
+ endpointURL, err := url.Parse(endpointURLStr)
+ if err != nil {
+ return nil, err
+ }
+
+ // Validate incoming endpoint URL.
+ if err := isValidEndpointURL(endpointURL.String()); err != nil {
+ return nil, err
+ }
+ return endpointURL, nil
+}
+
+// Verify if input endpoint URL is valid.
+func isValidEndpointURL(endpointURL string) error {
+ if endpointURL == "" {
+ return ErrInvalidArgument("Endpoint url cannot be empty.")
+ }
+ url, err := url.Parse(endpointURL)
+ if err != nil {
+ return ErrInvalidArgument("Endpoint url cannot be parsed.")
+ }
+ if url.Path != "/" && url.Path != "" {
+ return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
+ }
+ return nil
+}
+
+// closeResponse close non nil response with any response Body.
+// convenient wrapper to drain any remaining data on response body.
+//
+// Subsequently this allows golang http RoundTripper
+// to re-use the same connection for future requests.
+func closeResponse(resp *http.Response) {
+ // Callers should close resp.Body when done reading from it.
+ // If resp.Body is not closed, the Client's underlying RoundTripper
+ // (typically Transport) may not be able to re-use a persistent TCP
+ // connection to the server for a subsequent "keep-alive" request.
+ if resp != nil && resp.Body != nil {
+ // Drain any remaining Body and then close the connection.
+ // Without this closing connection would disallow re-using
+ // the same connection for future uses.
+ // - http://stackoverflow.com/a/17961593/4465767
+ io.Copy(ioutil.Discard, resp.Body)
+ resp.Body.Close()
+ }
+}
diff --git a/pkg/madmin/vendor/github.com/minio/minio-go/LICENSE b/pkg/madmin/vendor/github.com/minio/minio-go/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/pkg/madmin/vendor/github.com/minio/minio-go/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go
new file mode 100644
index 000000000..e1ec6c02c
--- /dev/null
+++ b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go
@@ -0,0 +1,324 @@
+/*
+ * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 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 s3signer
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "net/url"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio-go/pkg/s3utils"
+)
+
+// Signature and API related constants.
+const (
+ signV2Algorithm = "AWS"
+)
+
+// Encode input URL path to URL encoded path.
+func encodeURL2Path(u *url.URL) (path string) {
+ // Encode URL path.
+ if isS3, _ := filepath.Match("*.s3*.amazonaws.com", u.Host); isS3 {
+ hostSplits := strings.SplitN(u.Host, ".", 4)
+ // First element is the bucket name.
+ bucketName := hostSplits[0]
+ path = "/" + bucketName
+ path += u.Path
+ path = s3utils.EncodePath(path)
+ return
+ }
+ if strings.HasSuffix(u.Host, ".storage.googleapis.com") {
+ path = "/" + strings.TrimSuffix(u.Host, ".storage.googleapis.com")
+ path += u.Path
+ path = s3utils.EncodePath(path)
+ return
+ }
+ path = s3utils.EncodePath(u.Path)
+ return
+}
+
+// PreSignV2 - presign the request in following style.
+// https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
+func PreSignV2(req http.Request, accessKeyID, secretAccessKey string, expires int64) *http.Request {
+ // Presign is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ d := time.Now().UTC()
+ // Find epoch expires when the request will expire.
+ epochExpires := d.Unix() + expires
+
+ // Add expires header if not present.
+ if expiresStr := req.Header.Get("Expires"); expiresStr == "" {
+ req.Header.Set("Expires", strconv.FormatInt(epochExpires, 10))
+ }
+
+ // Get presigned string to sign.
+ stringToSign := preStringifyHTTPReq(req)
+ hm := hmac.New(sha1.New, []byte(secretAccessKey))
+ hm.Write([]byte(stringToSign))
+
+ // Calculate signature.
+ signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
+
+ query := req.URL.Query()
+ // Handle specially for Google Cloud Storage.
+ if strings.Contains(req.URL.Host, ".storage.googleapis.com") {
+ query.Set("GoogleAccessId", accessKeyID)
+ } else {
+ query.Set("AWSAccessKeyId", accessKeyID)
+ }
+
+ // Fill in Expires for presigned query.
+ query.Set("Expires", strconv.FormatInt(epochExpires, 10))
+
+ // Encode query and save.
+ req.URL.RawQuery = s3utils.QueryEncode(query)
+
+ // Save signature finally.
+ req.URL.RawQuery += "&Signature=" + s3utils.EncodePath(signature)
+
+ // Return.
+ return &req
+}
+
+// PostPresignSignatureV2 - presigned signature for PostPolicy
+// request.
+func PostPresignSignatureV2(policyBase64, secretAccessKey string) string {
+ hm := hmac.New(sha1.New, []byte(secretAccessKey))
+ hm.Write([]byte(policyBase64))
+ signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
+ return signature
+}
+
+// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
+// Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
+//
+// StringToSign = HTTP-Verb + "\n" +
+// Content-Md5 + "\n" +
+// Content-Type + "\n" +
+// Date + "\n" +
+// CanonicalizedProtocolHeaders +
+// CanonicalizedResource;
+//
+// CanonicalizedResource = [ "/" + Bucket ] +
+// +
+// [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
+//
+// CanonicalizedProtocolHeaders =
+
+// SignV2 sign the request before Do() (AWS Signature Version 2).
+func SignV2(req http.Request, accessKeyID, secretAccessKey string) *http.Request {
+ // Signature calculation is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ // Initial time.
+ d := time.Now().UTC()
+
+ // Add date if not present.
+ if date := req.Header.Get("Date"); date == "" {
+ req.Header.Set("Date", d.Format(http.TimeFormat))
+ }
+
+ // Calculate HMAC for secretAccessKey.
+ stringToSign := stringifyHTTPReq(req)
+ hm := hmac.New(sha1.New, []byte(secretAccessKey))
+ hm.Write([]byte(stringToSign))
+
+ // Prepare auth header.
+ authHeader := new(bytes.Buffer)
+ authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKeyID))
+ encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
+ encoder.Write(hm.Sum(nil))
+ encoder.Close()
+
+ // Set Authorization header.
+ req.Header.Set("Authorization", authHeader.String())
+
+ return &req
+}
+
+// From the Amazon docs:
+//
+// StringToSign = HTTP-Verb + "\n" +
+// Content-Md5 + "\n" +
+// Content-Type + "\n" +
+// Expires + "\n" +
+// CanonicalizedProtocolHeaders +
+// CanonicalizedResource;
+func preStringifyHTTPReq(req http.Request) string {
+ buf := new(bytes.Buffer)
+ // Write standard headers.
+ writePreSignV2Headers(buf, req)
+ // Write canonicalized protocol headers if any.
+ writeCanonicalizedHeaders(buf, req)
+ // Write canonicalized Query resources if any.
+ isPreSign := true
+ writeCanonicalizedResource(buf, req, isPreSign)
+ return buf.String()
+}
+
+// writePreSignV2Headers - write preSign v2 required headers.
+func writePreSignV2Headers(buf *bytes.Buffer, req http.Request) {
+ buf.WriteString(req.Method + "\n")
+ buf.WriteString(req.Header.Get("Content-Md5") + "\n")
+ buf.WriteString(req.Header.Get("Content-Type") + "\n")
+ buf.WriteString(req.Header.Get("Expires") + "\n")
+}
+
+// From the Amazon docs:
+//
+// StringToSign = HTTP-Verb + "\n" +
+// Content-Md5 + "\n" +
+// Content-Type + "\n" +
+// Date + "\n" +
+// CanonicalizedProtocolHeaders +
+// CanonicalizedResource;
+func stringifyHTTPReq(req http.Request) string {
+ buf := new(bytes.Buffer)
+ // Write standard headers.
+ writeSignV2Headers(buf, req)
+ // Write canonicalized protocol headers if any.
+ writeCanonicalizedHeaders(buf, req)
+ // Write canonicalized Query resources if any.
+ isPreSign := false
+ writeCanonicalizedResource(buf, req, isPreSign)
+ return buf.String()
+}
+
+// writeSignV2Headers - write signV2 required headers.
+func writeSignV2Headers(buf *bytes.Buffer, req http.Request) {
+ buf.WriteString(req.Method + "\n")
+ buf.WriteString(req.Header.Get("Content-Md5") + "\n")
+ buf.WriteString(req.Header.Get("Content-Type") + "\n")
+ buf.WriteString(req.Header.Get("Date") + "\n")
+}
+
+// writeCanonicalizedHeaders - write canonicalized headers.
+func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) {
+ var protoHeaders []string
+ vals := make(map[string][]string)
+ for k, vv := range req.Header {
+ // All the AMZ headers should be lowercase
+ lk := strings.ToLower(k)
+ if strings.HasPrefix(lk, "x-amz") {
+ protoHeaders = append(protoHeaders, lk)
+ vals[lk] = vv
+ }
+ }
+ sort.Strings(protoHeaders)
+ for _, k := range protoHeaders {
+ buf.WriteString(k)
+ buf.WriteByte(':')
+ for idx, v := range vals[k] {
+ if idx > 0 {
+ buf.WriteByte(',')
+ }
+ if strings.Contains(v, "\n") {
+ // TODO: "Unfold" long headers that
+ // span multiple lines (as allowed by
+ // RFC 2616, section 4.2) by replacing
+ // the folding white-space (including
+ // new-line) by a single space.
+ buf.WriteString(v)
+ } else {
+ buf.WriteString(v)
+ }
+ }
+ buf.WriteByte('\n')
+ }
+}
+
+// The following list is already sorted and should always be, otherwise we could
+// have signature-related issues
+var resourceList = []string{
+ "acl",
+ "delete",
+ "location",
+ "logging",
+ "notification",
+ "partNumber",
+ "policy",
+ "requestPayment",
+ "torrent",
+ "uploadId",
+ "uploads",
+ "versionId",
+ "versioning",
+ "versions",
+ "website",
+}
+
+// From the Amazon docs:
+//
+// CanonicalizedResource = [ "/" + Bucket ] +
+// +
+// [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
+func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, isPreSign bool) {
+ // Save request URL.
+ requestURL := req.URL
+ // Get encoded URL path.
+ path := encodeURL2Path(requestURL)
+ if isPreSign {
+ // Get encoded URL path.
+ if len(requestURL.Query()) > 0 {
+ // Keep the usual queries unescaped for string to sign.
+ query, _ := url.QueryUnescape(s3utils.QueryEncode(requestURL.Query()))
+ path = path + "?" + query
+ }
+ buf.WriteString(path)
+ return
+ }
+ buf.WriteString(path)
+ if requestURL.RawQuery != "" {
+ var n int
+ vals, _ := url.ParseQuery(requestURL.RawQuery)
+ // Verify if any sub resource queries are present, if yes
+ // canonicallize them.
+ for _, resource := range resourceList {
+ if vv, ok := vals[resource]; ok && len(vv) > 0 {
+ n++
+ // First element
+ switch n {
+ case 1:
+ buf.WriteByte('?')
+ // The rest
+ default:
+ buf.WriteByte('&')
+ }
+ buf.WriteString(resource)
+ // Request parameters
+ if len(vv[0]) > 0 {
+ buf.WriteByte('=')
+ buf.WriteString(strings.Replace(url.QueryEscape(vv[0]), "+", "%20", -1))
+ }
+ }
+ }
+ }
+}
diff --git a/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go
new file mode 100644
index 000000000..3322b67cc
--- /dev/null
+++ b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go
@@ -0,0 +1,305 @@
+/*
+ * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 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 s3signer
+
+import (
+ "bytes"
+ "encoding/hex"
+ "net/http"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio-go/pkg/s3utils"
+)
+
+// Signature and API related constants.
+const (
+ signV4Algorithm = "AWS4-HMAC-SHA256"
+ iso8601DateFormat = "20060102T150405Z"
+ yyyymmdd = "20060102"
+)
+
+///
+/// Excerpts from @lsegal -
+/// https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258.
+///
+/// User-Agent:
+///
+/// This is ignored from signing because signing this causes
+/// problems with generating pre-signed URLs (that are executed
+/// by other agents) or when customers pass requests through
+/// proxies, which may modify the user-agent.
+///
+/// Content-Length:
+///
+/// This is ignored from signing because generating a pre-signed
+/// URL should not provide a content-length constraint,
+/// specifically when vending a S3 pre-signed PUT URL. The
+/// corollary to this is that when sending regular requests
+/// (non-pre-signed), the signature contains a checksum of the
+/// body, which implicitly validates the payload length (since
+/// changing the number of bytes would change the checksum)
+/// and therefore this header is not valuable in the signature.
+///
+/// Content-Type:
+///
+/// Signing this header causes quite a number of problems in
+/// browser environments, where browsers like to modify and
+/// normalize the content-type header in different ways. There is
+/// more information on this in https://goo.gl/2E9gyy. Avoiding
+/// this field simplifies logic and reduces the possibility of
+/// future bugs.
+///
+/// Authorization:
+///
+/// Is skipped for obvious reasons
+///
+var ignoredHeaders = map[string]bool{
+ "Authorization": true,
+ "Content-Type": true,
+ "Content-Length": true,
+ "User-Agent": true,
+}
+
+// getSigningKey hmac seed to calculate final signature.
+func getSigningKey(secret, loc string, t time.Time) []byte {
+ date := sumHMAC([]byte("AWS4"+secret), []byte(t.Format(yyyymmdd)))
+ location := sumHMAC(date, []byte(loc))
+ service := sumHMAC(location, []byte("s3"))
+ signingKey := sumHMAC(service, []byte("aws4_request"))
+ return signingKey
+}
+
+// getSignature final signature in hexadecimal form.
+func getSignature(signingKey []byte, stringToSign string) string {
+ return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
+}
+
+// getScope generate a string of a specific date, an AWS region, and a
+// service.
+func getScope(location string, t time.Time) string {
+ scope := strings.Join([]string{
+ t.Format(yyyymmdd),
+ location,
+ "s3",
+ "aws4_request",
+ }, "/")
+ return scope
+}
+
+// GetCredential generate a credential string.
+func GetCredential(accessKeyID, location string, t time.Time) string {
+ scope := getScope(location, t)
+ return accessKeyID + "/" + scope
+}
+
+// getHashedPayload get the hexadecimal value of the SHA256 hash of
+// the request payload.
+func getHashedPayload(req http.Request) string {
+ hashedPayload := req.Header.Get("X-Amz-Content-Sha256")
+ if hashedPayload == "" {
+ // Presign does not have a payload, use S3 recommended value.
+ hashedPayload = unsignedPayload
+ }
+ return hashedPayload
+}
+
+// getCanonicalHeaders generate a list of request headers for
+// signature.
+func getCanonicalHeaders(req http.Request) string {
+ var headers []string
+ vals := make(map[string][]string)
+ for k, vv := range req.Header {
+ if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
+ continue // ignored header
+ }
+ headers = append(headers, strings.ToLower(k))
+ vals[strings.ToLower(k)] = vv
+ }
+ headers = append(headers, "host")
+ sort.Strings(headers)
+
+ var buf bytes.Buffer
+ // Save all the headers in canonical form : newline
+ // separated for each header.
+ for _, k := range headers {
+ buf.WriteString(k)
+ buf.WriteByte(':')
+ switch {
+ case k == "host":
+ buf.WriteString(req.URL.Host)
+ fallthrough
+ default:
+ for idx, v := range vals[k] {
+ if idx > 0 {
+ buf.WriteByte(',')
+ }
+ buf.WriteString(v)
+ }
+ buf.WriteByte('\n')
+ }
+ }
+ return buf.String()
+}
+
+// getSignedHeaders generate all signed request headers.
+// i.e lexically sorted, semicolon-separated list of lowercase
+// request header names.
+func getSignedHeaders(req http.Request) string {
+ var headers []string
+ for k := range req.Header {
+ if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
+ continue // Ignored header found continue.
+ }
+ headers = append(headers, strings.ToLower(k))
+ }
+ headers = append(headers, "host")
+ sort.Strings(headers)
+ return strings.Join(headers, ";")
+}
+
+// getCanonicalRequest generate a canonical request of style.
+//
+// canonicalRequest =
+// \n
+// \n
+// \n
+// \n
+// \n
+//
+func getCanonicalRequest(req http.Request) string {
+ req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
+ canonicalRequest := strings.Join([]string{
+ req.Method,
+ s3utils.EncodePath(req.URL.Path),
+ req.URL.RawQuery,
+ getCanonicalHeaders(req),
+ getSignedHeaders(req),
+ getHashedPayload(req),
+ }, "\n")
+ return canonicalRequest
+}
+
+// getStringToSign a string based on selected query values.
+func getStringToSignV4(t time.Time, location, canonicalRequest string) string {
+ stringToSign := signV4Algorithm + "\n" + t.Format(iso8601DateFormat) + "\n"
+ stringToSign = stringToSign + getScope(location, t) + "\n"
+ stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))
+ return stringToSign
+}
+
+// PreSignV4 presign the request, in accordance with
+// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html.
+func PreSignV4(req http.Request, accessKeyID, secretAccessKey, location string, expires int64) *http.Request {
+ // Presign is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ // Initial time.
+ t := time.Now().UTC()
+
+ // Get credential string.
+ credential := GetCredential(accessKeyID, location, t)
+
+ // Get all signed headers.
+ signedHeaders := getSignedHeaders(req)
+
+ // Set URL query.
+ query := req.URL.Query()
+ query.Set("X-Amz-Algorithm", signV4Algorithm)
+ query.Set("X-Amz-Date", t.Format(iso8601DateFormat))
+ query.Set("X-Amz-Expires", strconv.FormatInt(expires, 10))
+ query.Set("X-Amz-SignedHeaders", signedHeaders)
+ query.Set("X-Amz-Credential", credential)
+ req.URL.RawQuery = query.Encode()
+
+ // Get canonical request.
+ canonicalRequest := getCanonicalRequest(req)
+
+ // Get string to sign from canonical request.
+ stringToSign := getStringToSignV4(t, location, canonicalRequest)
+
+ // Gext hmac signing key.
+ signingKey := getSigningKey(secretAccessKey, location, t)
+
+ // Calculate signature.
+ signature := getSignature(signingKey, stringToSign)
+
+ // Add signature header to RawQuery.
+ req.URL.RawQuery += "&X-Amz-Signature=" + signature
+
+ return &req
+}
+
+// PostPresignSignatureV4 - presigned signature for PostPolicy
+// requests.
+func PostPresignSignatureV4(policyBase64 string, t time.Time, secretAccessKey, location string) string {
+ // Get signining key.
+ signingkey := getSigningKey(secretAccessKey, location, t)
+ // Calculate signature.
+ signature := getSignature(signingkey, policyBase64)
+ return signature
+}
+
+// SignV4 sign the request before Do(), in accordance with
+// http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html.
+func SignV4(req http.Request, accessKeyID, secretAccessKey, location string) *http.Request {
+ // Signature calculation is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ // Initial time.
+ t := time.Now().UTC()
+
+ // Set x-amz-date.
+ req.Header.Set("X-Amz-Date", t.Format(iso8601DateFormat))
+
+ // Get canonical request.
+ canonicalRequest := getCanonicalRequest(req)
+
+ // Get string to sign from canonical request.
+ stringToSign := getStringToSignV4(t, location, canonicalRequest)
+
+ // Get hmac signing key.
+ signingKey := getSigningKey(secretAccessKey, location, t)
+
+ // Get credential string.
+ credential := GetCredential(accessKeyID, location, t)
+
+ // Get all signed headers.
+ signedHeaders := getSignedHeaders(req)
+
+ // Calculate signature.
+ signature := getSignature(signingKey, stringToSign)
+
+ // If regular request, construct the final authorization header.
+ parts := []string{
+ signV4Algorithm + " Credential=" + credential,
+ "SignedHeaders=" + signedHeaders,
+ "Signature=" + signature,
+ }
+
+ // Set authorization header.
+ auth := strings.Join(parts, ", ")
+ req.Header.Set("Authorization", auth)
+
+ return &req
+}
diff --git a/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/utils.go b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/utils.go
new file mode 100644
index 000000000..0619b3082
--- /dev/null
+++ b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/utils.go
@@ -0,0 +1,39 @@
+/*
+ * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 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 s3signer
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+)
+
+// unsignedPayload - value to be set to X-Amz-Content-Sha256 header when
+const unsignedPayload = "UNSIGNED-PAYLOAD"
+
+// sum256 calculate sha256 sum for an input byte array.
+func sum256(data []byte) []byte {
+ hash := sha256.New()
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+// sumHMAC calculate hmac between two input byte array.
+func sumHMAC(key []byte, data []byte) []byte {
+ hash := hmac.New(sha256.New, key)
+ hash.Write(data)
+ return hash.Sum(nil)
+}
diff --git a/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3utils/utils.go b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3utils/utils.go
new file mode 100644
index 000000000..353e2a0c7
--- /dev/null
+++ b/pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3utils/utils.go
@@ -0,0 +1,192 @@
+/*
+ * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016 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 s3utils
+
+import (
+ "bytes"
+ "encoding/hex"
+ "net"
+ "net/url"
+ "regexp"
+ "sort"
+ "strings"
+ "unicode/utf8"
+)
+
+// Sentinel URL is the default url value which is invalid.
+var sentinelURL = url.URL{}
+
+// IsValidDomain validates if input string is a valid domain name.
+func IsValidDomain(host string) bool {
+ // See RFC 1035, RFC 3696.
+ host = strings.TrimSpace(host)
+ if len(host) == 0 || len(host) > 255 {
+ return false
+ }
+ // host cannot start or end with "-"
+ if host[len(host)-1:] == "-" || host[:1] == "-" {
+ return false
+ }
+ // host cannot start or end with "_"
+ if host[len(host)-1:] == "_" || host[:1] == "_" {
+ return false
+ }
+ // host cannot start or end with a "."
+ if host[len(host)-1:] == "." || host[:1] == "." {
+ return false
+ }
+ // All non alphanumeric characters are invalid.
+ if strings.ContainsAny(host, "`~!@#$%^&*()+={}[]|\\\"';:>/") {
+ return false
+ }
+ // No need to regexp match, since the list is non-exhaustive.
+ // We let it valid and fail later.
+ return true
+}
+
+// IsValidIP parses input string for ip address validity.
+func IsValidIP(ip string) bool {
+ return net.ParseIP(ip) != nil
+}
+
+// IsVirtualHostSupported - verifies if bucketName can be part of
+// virtual host. Currently only Amazon S3 and Google Cloud Storage
+// would support this.
+func IsVirtualHostSupported(endpointURL url.URL, bucketName string) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ // bucketName can be valid but '.' in the hostname will fail SSL
+ // certificate validation. So do not use host-style for such buckets.
+ if endpointURL.Scheme == "https" && strings.Contains(bucketName, ".") {
+ return false
+ }
+ // Return true for all other cases
+ return IsAmazonEndpoint(endpointURL) || IsGoogleEndpoint(endpointURL)
+}
+
+// IsAmazonEndpoint - Match if it is exactly Amazon S3 endpoint.
+func IsAmazonEndpoint(endpointURL url.URL) bool {
+ if IsAmazonChinaEndpoint(endpointURL) {
+ return true
+ }
+
+ if IsAmazonS3AccelerateEndpoint(endpointURL) {
+ return true
+ }
+
+ return endpointURL.Host == "s3.amazonaws.com"
+}
+
+// IsAmazonChinaEndpoint - Match if it is exactly Amazon S3 China endpoint.
+// Customers who wish to use the new Beijing Region are required
+// to sign up for a separate set of account credentials unique to
+// the China (Beijing) Region. Customers with existing AWS credentials
+// will not be able to access resources in the new Region, and vice versa.
+// For more info https://aws.amazon.com/about-aws/whats-new/2013/12/18/announcing-the-aws-china-beijing-region/
+func IsAmazonChinaEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ return endpointURL.Host == "s3.cn-north-1.amazonaws.com.cn"
+}
+
+// IsAmazonS3AccelerateEndpoint - Match if it is an Amazon S3 Accelerate
+func IsAmazonS3AccelerateEndpoint(endpointURL url.URL) bool {
+ return strings.HasSuffix(endpointURL.Host, ".s3-accelerate.amazonaws.com")
+}
+
+// IsGoogleEndpoint - Match if it is exactly Google cloud storage endpoint.
+func IsGoogleEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ return endpointURL.Host == "storage.googleapis.com"
+}
+
+// Expects ascii encoded strings - from output of urlEncodePath
+func percentEncodeSlash(s string) string {
+ return strings.Replace(s, "/", "%2F", -1)
+}
+
+// QueryEncode - encodes query values in their URL encoded form. In
+// addition to the percent encoding performed by urlEncodePath() used
+// here, it also percent encodes '/' (forward slash)
+func QueryEncode(v url.Values) string {
+ if v == nil {
+ return ""
+ }
+ var buf bytes.Buffer
+ keys := make([]string, 0, len(v))
+ for k := range v {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ vs := v[k]
+ prefix := percentEncodeSlash(EncodePath(k)) + "="
+ for _, v := range vs {
+ if buf.Len() > 0 {
+ buf.WriteByte('&')
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(percentEncodeSlash(EncodePath(v)))
+ }
+ }
+ return buf.String()
+}
+
+// if object matches reserved string, no need to encode them
+var reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
+
+// EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
+//
+// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
+// non english characters cannot be parsed due to the nature in which url.Encode() is written
+//
+// This function on the other hand is a direct replacement for url.Encode() technique to support
+// pretty much every UTF-8 character.
+func EncodePath(pathName string) string {
+ if reservedObjectNames.MatchString(pathName) {
+ return pathName
+ }
+ var encodedPathname string
+ for _, s := range pathName {
+ if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
+ encodedPathname = encodedPathname + string(s)
+ continue
+ }
+ switch s {
+ case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
+ encodedPathname = encodedPathname + string(s)
+ continue
+ default:
+ len := utf8.RuneLen(s)
+ if len < 0 {
+ // if utf8 cannot convert return the same string as is
+ return pathName
+ }
+ u := make([]byte, len)
+ utf8.EncodeRune(u, s)
+ for _, r := range u {
+ hex := hex.EncodeToString([]byte{r})
+ encodedPathname = encodedPathname + "%" + strings.ToUpper(hex)
+ }
+ }
+ }
+ return encodedPathname
+}
diff --git a/pkg/madmin/vendor/vendor.json b/pkg/madmin/vendor/vendor.json
new file mode 100644
index 000000000..3609b3390
--- /dev/null
+++ b/pkg/madmin/vendor/vendor.json
@@ -0,0 +1,19 @@
+{
+ "comment": "",
+ "ignore": "test",
+ "package": [
+ {
+ "checksumSHA1": "m/6/na9lVtamkfmIdIOi5pdccgw=",
+ "path": "github.com/minio/minio-go/pkg/s3signer",
+ "revision": "532b920ff28900244a2ef7d07468003df36fe7c5",
+ "revisionTime": "2016-12-20T20:43:13Z"
+ },
+ {
+ "checksumSHA1": "bPvxFS1qu6W9lOqdt8aEfS5Sids=",
+ "path": "github.com/minio/minio-go/pkg/s3utils",
+ "revision": "532b920ff28900244a2ef7d07468003df36fe7c5",
+ "revisionTime": "2016-12-20T20:43:13Z"
+ }
+ ],
+ "rootPath": "github.com/minio/minio/pkg/madmin"
+}