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