Admin Lib: Implement Service API (#3426)
Three APIs were added to control a minio server * NewAdminClient() * ServiceStop() * ServiceRestart() * ServiceStatus()master
parent
4309727354
commit
329a910b86
@ -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 **** |
||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Error> |
||||||
|
<Code>AccessDenied</Code> |
||||||
|
<Message>Access Denied</Message> |
||||||
|
<BucketName>bucketName</BucketName> |
||||||
|
<Key>objectName</Key> |
||||||
|
<RequestId>F19772218238A85A</RequestId> |
||||||
|
<HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId> |
||||||
|
</Error> |
||||||
|
*/ |
||||||
|
|
||||||
|
// 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", |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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) { |
||||||
|
} |
@ -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" |
@ -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=<access-key-id>/<date>/<aws-region>/<aws-service>/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 |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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() |
||||||
|
} |
||||||
|
} |
@ -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. |
324
pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go
generated
vendored
324
pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go
generated
vendored
@ -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 ] +
|
||||||
|
// <HTTP-Request-URI, from the protocol name up to the query string> +
|
||||||
|
// [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
|
||||||
|
//
|
||||||
|
// CanonicalizedProtocolHeaders = <described below>
|
||||||
|
|
||||||
|
// 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 ] +
|
||||||
|
// <HTTP-Request-URI, from the protocol name up to the query string> +
|
||||||
|
// [ 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)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
305
pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go
generated
vendored
305
pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go
generated
vendored
@ -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 <header>:<value> 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 =
|
||||||
|
// <HTTPMethod>\n
|
||||||
|
// <CanonicalURI>\n
|
||||||
|
// <CanonicalQueryString>\n
|
||||||
|
// <CanonicalHeaders>\n
|
||||||
|
// <SignedHeaders>\n
|
||||||
|
// <HashedPayload>
|
||||||
|
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 |
||||||
|
} |
@ -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) |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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" |
||||||
|
} |
Loading…
Reference in new issue