mirror of https://github.com/minio/minio.git
Browse Source
Admin Lib: Implement Service API (#3426)
Admin Lib: Implement Service API (#3426)
Three APIs were added to control a minio server * NewAdminClient() * ServiceStop() * ServiceRestart() * ServiceStatus()pull/3484/head

committed by
Harshavardhana

13 changed files with 1950 additions and 0 deletions
-
60pkg/madmin/api-error-response.go
-
37pkg/madmin/client.go
-
24pkg/madmin/client_test.go
-
19pkg/madmin/constants.go
-
480pkg/madmin/requests.go
-
141pkg/madmin/service-api.go
-
108pkg/madmin/utils.go
-
202pkg/madmin/vendor/github.com/minio/minio-go/LICENSE
-
324pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go
-
305pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go
-
39pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3signer/utils.go
-
192pkg/madmin/vendor/github.com/minio/minio-go/pkg/s3utils/utils.go
-
19pkg/madmin/vendor/vendor.json
@ -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. |
@ -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)) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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" |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue