diff --git a/api-errors.go b/api-errors.go index e2aff9892..8dc6e441c 100644 --- a/api-errors.go +++ b/api-errors.go @@ -75,6 +75,7 @@ const ( BucketNotEmpty RootPathFull ObjectExistsAsPrefix + AllAccessDisabled ) // APIError code to Error structure map @@ -244,6 +245,11 @@ var errorCodeResponse = map[int]APIError{ Description: "An object already exists as your prefix, choose a different prefix to proceed.", HTTPStatusCode: http.StatusConflict, }, + AllAccessDisabled: { + Code: "AllAccessDisabled", + Description: "All access to this bucket has been disabled.", + HTTPStatusCode: http.StatusForbidden, + }, } // errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown diff --git a/auth-handler.go b/auth-handler.go index 295431256..4086fa14f 100644 --- a/auth-handler.go +++ b/auth-handler.go @@ -39,13 +39,34 @@ func setAuthHandler(h http.Handler) http.Handler { return authHandler{h} } +// handler for validating incoming authorization headers. func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Verify if request has post policy signature. + // Verify if request is presigned, validate signature inside each handlers. + if isRequestPresignedSignatureV4(r) { + a.handler.ServeHTTP(w, r) + return + } + + // Verify if request has post policy signature, validate signature + // inside POST policy handler. if isRequestPostPolicySignatureV4(r) && r.Method == "POST" { a.handler.ServeHTTP(w, r) return } + // No authorization found, let the top level caller validate if + // public request is allowed. + if _, ok := r.Header["Authorization"]; !ok { + a.handler.ServeHTTP(w, r) + return + } + + // Verify if the signature algorithms are known. + if !isRequestSignatureV4(r) && !isRequestJWT(r) { + writeErrorResponse(w, r, SignatureVersionNotSupported, r.URL.Path) + return + } + // Verify JWT authorization header is present. if isRequestJWT(r) { // Validate Authorization header to be valid. @@ -62,7 +83,6 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - // For signed, presigned, jwt and anonymous requests let the top level - // caller handle and verify. + // For all other signed requests, let top level caller verify. a.handler.ServeHTTP(w, r) } diff --git a/bucket-handlers.go b/bucket-handlers.go index 43e16618d..9f50369b1 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -241,10 +241,20 @@ func (api CloudStorageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Reque } // Set http request for signature. - api.Signature.SetHTTPRequestToVerify(r) - - // Verify signature for the incoming body if any. - if api.Signature != nil { + auth := api.Signature.SetHTTPRequestToVerify(r) + if isRequestPresignedSignatureV4(r) { + ok, err := auth.DoesPresignedSignatureMatch() + if err != nil { + errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + if !ok { + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + } else if isRequestSignatureV4(r) { + // Verify signature for the incoming body if any. locationBytes, e := ioutil.ReadAll(r.Body) if e != nil { errorIf(probe.NewError(e), "MakeBucket failed.", nil) @@ -253,7 +263,7 @@ func (api CloudStorageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Reque } sh := sha256.New() sh.Write(locationBytes) - ok, err := api.Signature.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil))) + ok, err := auth.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil))) if err != nil { errorIf(err.Trace(), "MakeBucket failed.", nil) writeErrorResponse(w, r, InternalError, r.URL.Path) diff --git a/generic-handlers.go b/generic-handlers.go index e2802a25d..a24316a88 100644 --- a/generic-handlers.go +++ b/generic-handlers.go @@ -28,6 +28,7 @@ import ( const ( iso8601Format = "20060102T150405Z" + privateBucket = "/minio" ) // HandlerFunc - useful to chain different middleware http.Handler @@ -42,59 +43,81 @@ func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handle return f } -type timeHandler struct { - handler http.Handler +// Attempts to parse date string into known date layouts. Date layouts +// currently supported are ``time.RFC1123``, ``time.RFC1123Z`` and +// special ``iso8601Format``. +func parseKnownLayouts(date string) (time.Time, error) { + parsedTime, e := time.Parse(time.RFC1123, date) + if e == nil { + return parsedTime, nil + } + parsedTime, e = time.Parse(time.RFC1123Z, date) + if e == nil { + return parsedTime, nil + } + parsedTime, e = time.Parse(iso8601Format, date) + if e == nil { + return parsedTime, nil + } + return time.Time{}, e } -type resourceHandler struct { - handler http.Handler -} - -type ignoreSignatureV2RequestHandler struct { - handler http.Handler -} - -func parseDate(req *http.Request) (time.Time, error) { +// Parse date string from incoming header, current supports and verifies +// follow HTTP headers. +// +// - X-Amz-Date +// - X-Minio-Date +// - Date +// +// In following time layouts ``time.RFC1123``, ``time.RFC1123Z`` and ``iso8601Format``. +func parseDateHeader(req *http.Request) (time.Time, error) { amzDate := req.Header.Get(http.CanonicalHeaderKey("x-amz-date")) - switch { - case amzDate != "": - if _, err := time.Parse(time.RFC1123, amzDate); err == nil { - return time.Parse(time.RFC1123, amzDate) - } - if _, err := time.Parse(time.RFC1123Z, amzDate); err == nil { - return time.Parse(time.RFC1123Z, amzDate) - } - if _, err := time.Parse(iso8601Format, amzDate); err == nil { - return time.Parse(iso8601Format, amzDate) - } + if amzDate != "" { + return parseKnownLayouts(amzDate) } minioDate := req.Header.Get(http.CanonicalHeaderKey("x-minio-date")) - switch { - case minioDate != "": - if _, err := time.Parse(time.RFC1123, minioDate); err == nil { - return time.Parse(time.RFC1123, minioDate) - } - if _, err := time.Parse(time.RFC1123Z, minioDate); err == nil { - return time.Parse(time.RFC1123Z, minioDate) - } - if _, err := time.Parse(iso8601Format, minioDate); err == nil { - return time.Parse(iso8601Format, minioDate) + if minioDate != "" { + return parseKnownLayouts(minioDate) + } + genericDate := req.Header.Get("Date") + if genericDate != "" { + return parseKnownLayouts(genericDate) + } + return time.Time{}, errors.New("Date header missing, invalid request.") +} + +// Adds redirect rules for incoming requests. +type redirectHandler struct { + handler http.Handler + locationPrefix string +} + +func setBrowserRedirectHandler(h http.Handler) http.Handler { + return redirectHandler{handler: h, locationPrefix: privateBucket} +} + +func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Re-direction handled specifically for browsers. + if strings.Contains(r.Header.Get("User-Agent"), "Mozilla") { + switch r.URL.Path { + case "/": + // This could be the default route for browser, redirect + // to 'locationPrefix/'. + fallthrough + case "/rpc": + // This is '/rpc' API route for browser, redirect to + // 'locationPrefix/rpc'. + fallthrough + case "/login": + // This is '/login' route for browser, redirect to + // 'locationPrefix/login'. + location := h.locationPrefix + r.URL.Path + // Redirect to new location. + http.Redirect(w, r, location, http.StatusTemporaryRedirect) + return } } - date := req.Header.Get("Date") - switch { - case date != "": - if _, err := time.Parse(time.RFC1123, date); err == nil { - return time.Parse(time.RFC1123, date) - } - if _, err := time.Parse(time.RFC1123Z, date); err == nil { - return time.Parse(time.RFC1123Z, date) - } - if _, err := time.Parse(iso8601Format, amzDate); err == nil { - return time.Parse(iso8601Format, amzDate) - } - } - return time.Time{}, errors.New("invalid request") + h.handler.ServeHTTP(w, r) } // Adds Cache-Control header @@ -102,18 +125,41 @@ type cacheControlHandler struct { handler http.Handler } -func setCacheControlHandler(h http.Handler) http.Handler { +func setBrowserCacheControlHandler(h http.Handler) http.Handler { return cacheControlHandler{h} } func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - // expire the cache in one week - w.Header().Set("Cache-Control", "public, max-age=604800") + if r.Method == "GET" && strings.Contains(r.Header.Get("User-Agent"), "Mozilla") { + // Expire cache in one hour for all browser requests. + w.Header().Set("Cache-Control", "public, max-age=3600") } h.handler.ServeHTTP(w, r) } +// Adds verification for incoming paths. +type minioPrivateBucketHandler struct { + handler http.Handler + privateBucket string +} + +func setPrivateBucketHandler(h http.Handler) http.Handler { + return minioPrivateBucketHandler{handler: h, privateBucket: privateBucket} +} + +func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // For all non browser requests, reject access to 'privateBucket'. + if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && strings.HasPrefix(r.URL.Path, privateBucket) { + writeErrorResponse(w, r, AllAccessDisabled, r.URL.Path) + return + } + h.handler.ServeHTTP(w, r) +} + +type timeHandler struct { + handler http.Handler +} + // setTimeValidityHandler to validate parsable time over http header func setTimeValidityHandler(h http.Handler) http.Handler { return timeHandler{h} @@ -122,19 +168,18 @@ func setTimeValidityHandler(h http.Handler) http.Handler { func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Verify if date headers are set, if not reject the request if r.Header.Get("Authorization") != "" { - if r.Header.Get(http.CanonicalHeaderKey("x-amz-date")) == "" && r.Header.Get(http.CanonicalHeaderKey("x-minio-date")) == "" && r.Header.Get("Date") == "" { - // there is no way to knowing if this is a valid request, could be a attack reject such clients - writeErrorResponse(w, r, RequestTimeTooSkewed, r.URL.Path) - return - } - date, err := parseDate(r) - if err != nil { - // there is no way to knowing if this is a valid request, could be a attack reject such clients + date, e := parseDateHeader(r) + if e != nil { + // All our internal APIs are sensitive towards Date + // header, for all requests where Date header is not + // present we will reject such clients. writeErrorResponse(w, r, RequestTimeTooSkewed, r.URL.Path) return } duration := time.Since(date) minutes := time.Duration(5) * time.Minute + // Verify if the request date header is more than 5minutes + // late, reject such clients. if duration.Minutes() > minutes.Minutes() { writeErrorResponse(w, r, RequestTimeTooSkewed, r.URL.Path) return @@ -143,6 +188,10 @@ func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.handler.ServeHTTP(w, r) } +type resourceHandler struct { + handler http.Handler +} + // setCorsHandler handler for CORS (Cross Origin Resource Sharing) func setCorsHandler(h http.Handler) http.Handler { c := cors.New(cors.Options{ @@ -153,26 +202,10 @@ func setCorsHandler(h http.Handler) http.Handler { return c.Handler(h) } -// setIgnoreSignatureV2RequestHandler - -// Verify if authorization header has signature version '2', reject it cleanly. -func setIgnoreSignatureV2RequestHandler(h http.Handler) http.Handler { - return ignoreSignatureV2RequestHandler{h} -} - -// Ignore signature version '2' ServerHTTP() wrapper. -func (h ignoreSignatureV2RequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if isRequestSignatureV4(r) || isRequestJWT(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) { - h.handler.ServeHTTP(w, r) - return - } - writeErrorResponse(w, r, SignatureVersionNotSupported, r.URL.Path) - return -} - // setIgnoreResourcesHandler - // Ignore resources handler is wrapper handler used for API request resource validation // Since we do not support all the S3 queries, it is necessary for us to throw back a -// valid error message indicating such a feature is not implemented. +// valid error message indicating that requested feature is not implemented. func setIgnoreResourcesHandler(h http.Handler) http.Handler { return resourceHandler{h} } diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 000000000..09c654218 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,48 @@ +/* + * Minio 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 main + +import ( + "bytes" + "encoding/json" + "errors" + + "github.com/Sirupsen/logrus" + "github.com/minio/minio/pkg/probe" + + . "gopkg.in/check.v1" +) + +type LoggerSuite struct{} + +var _ = Suite(&LoggerSuite{}) + +func (s *LoggerSuite) TestLogger(c *C) { + var buffer bytes.Buffer + var fields logrus.Fields + log.Out = &buffer + log.Formatter = new(logrus.JSONFormatter) + + errorIf(probe.NewError(errors.New("Fake error")), "Failed with error.", nil) + err := json.Unmarshal(buffer.Bytes(), &fields) + c.Assert(err, IsNil) + c.Assert(fields["level"], Equals, "error") + + msg, ok := fields["Error"] + c.Assert(ok, Equals, true) + c.Assert(msg.(map[string]interface{})["cause"], Equals, "Fake error") +} diff --git a/object-handlers.go b/object-handlers.go index 2117b7c54..51b75e03c 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -182,10 +182,25 @@ func (api CloudStorageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Reque } // Set http request for signature. - api.Signature.SetHTTPRequestToVerify(r) + auth := api.Signature.SetHTTPRequestToVerify(r) + + // For presigned requests verify them right here. + if isRequestPresignedSignatureV4(r) { + ok, err := auth.DoesPresignedSignatureMatch() + if err != nil { + errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + if !ok { + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + auth = nil + } // Create object. - metadata, err := api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, api.Signature) + metadata, err := api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, auth) if err != nil { errorIf(err.Trace(), "CreateObject failed.", nil) switch err.ToGoError().(type) { @@ -311,10 +326,26 @@ func (api CloudStorageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.R } } - // Set http request. - api.Signature.SetHTTPRequestToVerify(r) + // Set http request for signature. + auth := api.Signature.SetHTTPRequestToVerify(r) + // For presigned requests verify right here. + if isRequestPresignedSignatureV4(r) { + ok, err := auth.DoesPresignedSignatureMatch() + if err != nil { + errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + if !ok { + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + // Signature verified, set this to nil payload verification + // not necessary. + auth = nil + } - calculatedMD5, err := api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, api.Signature) + calculatedMD5, err := api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, auth) if err != nil { errorIf(err.Trace(), "CreateObjectPart failed.", nil) switch err.ToGoError().(type) { @@ -454,7 +485,21 @@ func (api CloudStorageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, } // Set http request for signature. - api.Signature.SetHTTPRequestToVerify(r) + auth := api.Signature.SetHTTPRequestToVerify(r) + // For presigned requests verify right here. + if isRequestPresignedSignatureV4(r) { + ok, err := auth.DoesPresignedSignatureMatch() + if err != nil { + errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + if !ok { + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + auth = nil + } // Extract object resources. objectResourcesMetadata := getObjectResources(r.URL.Query()) diff --git a/pkg/signature/signature-v4.go b/pkg/signature/signature-v4.go index d4e396b20..619286971 100644 --- a/pkg/signature/signature-v4.go +++ b/pkg/signature/signature-v4.go @@ -210,7 +210,7 @@ func (s Signature) getSignature(signingKey []byte, stringToSign string) string { // returns true if matches, false otherwise. if error is not nil then it is always false func (s *Signature) DoesPolicySignatureMatch(formValues map[string]string) (bool, *probe.Error) { // Parse credential tag. - creds, err := parseCredential(formValues["X-Amz-Credential"]) + creds, err := parseCredential("Credential=" + formValues["X-Amz-Credential"]) if err != nil { return false, err.Trace(formValues["X-Amz-Credential"]) } @@ -273,13 +273,13 @@ func (s *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) { query := make(url.Values) query.Set("X-Amz-Algorithm", signV4Algorithm) - if time.Now().UTC().Sub(preSignV4Values.Date) > time.Duration(preSignV4Values.Expires)*time.Second { + if time.Now().UTC().Sub(preSignV4Values.Date) > time.Duration(preSignV4Values.Expires)/time.Second { return false, ErrExpiredPresignRequest("Presigned request already expired, please initiate a new request.") } // Save the date and expires. t := preSignV4Values.Date - expireSeconds := int(preSignV4Values.Expires) + expireSeconds := int(time.Duration(preSignV4Values.Expires) / time.Second) query.Set("X-Amz-Date", t.Format(iso8601Format)) query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds)) diff --git a/pkg/signature/v4-parser.go b/pkg/signature/v4-parser.go index 880f149cb..64c3deb1a 100644 --- a/pkg/signature/v4-parser.go +++ b/pkg/signature/v4-parser.go @@ -116,7 +116,7 @@ func parsePreSignV4(query url.Values) (preSignValues, *probe.Error) { var err *probe.Error // Save credentail values. - preSignV4Values.Creds, err = parseCredential(query.Get("X-Amz-Credential")) + preSignV4Values.Creds, err = parseCredential("Credential=" + query.Get("X-Amz-Credential")) if err != nil { return preSignValues{}, err.Trace(query.Get("X-Amz-Credential")) } @@ -135,13 +135,13 @@ func parsePreSignV4(query url.Values) (preSignValues, *probe.Error) { } // Save signed headers. - preSignV4Values.SignedHeaders, err = parseSignedHeaders(query.Get("X-Amz-SignedHeaders")) + preSignV4Values.SignedHeaders, err = parseSignedHeaders("SignedHeaders=" + query.Get("X-Amz-SignedHeaders")) if err != nil { return preSignValues{}, err.Trace(query.Get("X-Amz-SignedHeaders")) } // Save signature. - preSignV4Values.Signature, err = parseSignature(query.Get("X-Amz-Signature")) + preSignV4Values.Signature, err = parseSignature("Signature=" + query.Get("X-Amz-Signature")) if err != nil { return preSignValues{}, err.Trace(query.Get("X-Amz-Signature")) } diff --git a/routers.go b/routers.go index 420014296..3e87d7548 100644 --- a/routers.go +++ b/routers.go @@ -19,6 +19,7 @@ package main import ( "net" "net/http" + "path/filepath" router "github.com/gorilla/mux" jsonrpc "github.com/gorilla/rpc/v2" @@ -58,38 +59,30 @@ type WebAPI struct { secretAccessKey string } -func getWebAPIHandler(web *WebAPI) http.Handler { - var handlerFns = []HandlerFunc{ - setCacheControlHandler, // Adds Cache-Control header - setTimeValidityHandler, // Validate time. - setCorsHandler, // CORS added only for testing purposes. - } - if web.AccessLog { - handlerFns = append(handlerFns, setAccessLogHandler) - } - - s := jsonrpc.NewServer() - codec := json2.NewCodec() - s.RegisterCodec(codec, "application/json") - s.RegisterCodec(codec, "application/json; charset=UTF-8") - s.RegisterService(web, "Web") - mux := router.NewRouter() - // Root router. - root := mux.NewRoute().PathPrefix("/").Subrouter() - root.Handle("/rpc", s) - - // Enable this when we add assets. - root.PathPrefix("/login").Handler(http.StripPrefix("/login", http.FileServer(assetFS()))) - root.Handle("/{file:.*}", http.FileServer(assetFS())) - return registerHandlers(mux, handlerFns...) -} - // registerCloudStorageAPI - register all the handlers to their respective paths -func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI) { - // root Router - root := mux.NewRoute().PathPrefix("/").Subrouter() +func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI, w *WebAPI) { + // Minio rpc router + minio := mux.NewRoute().PathPrefix(privateBucket).Subrouter() + + // Initialize json rpc handlers. + rpc := jsonrpc.NewServer() + codec := json2.NewCodec() + rpc.RegisterCodec(codec, "application/json") + rpc.RegisterCodec(codec, "application/json; charset=UTF-8") + rpc.RegisterService(w, "Web") + + // RPC handler at URI - /minio/rpc + minio.Path("/rpc").Handler(rpc) + + // Web handler assets at URI - /minio/login + minio.Path("/login").Handler(http.StripPrefix(filepath.Join(privateBucket, "login"), http.FileServer(assetFS()))) + minio.Path("/{file:.*}").Handler(http.StripPrefix(privateBucket, http.FileServer(assetFS()))) + + // API Router + api := mux.NewRoute().PathPrefix("/").Subrouter() + // Bucket router - bucket := root.PathPrefix("/{bucket}").Subrouter() + bucket := api.PathPrefix("/{bucket}").Subrouter() // Object operations bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(a.HeadObjectHandler) @@ -114,7 +107,7 @@ func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI) { bucket.Methods("DELETE").HandlerFunc(a.DeleteBucketHandler) // Root operation - root.Methods("GET").HandlerFunc(a.ListBucketsHandler) + api.Methods("GET").HandlerFunc(a.ListBucketsHandler) } // getNewWebAPI instantiate a new WebAPI. @@ -133,7 +126,7 @@ func getNewWebAPI(conf cloudServerConfig) *WebAPI { client, e := minio.NewV4(net.JoinHostPort(host, port), conf.AccessKeyID, conf.SecretAccessKey, inSecure) fatalIf(probe.NewError(e), "Unable to initialize minio client", nil) - web := &WebAPI{ + w := &WebAPI{ FSPath: conf.Path, AccessLog: conf.AccessLog, Client: client, @@ -142,7 +135,7 @@ func getNewWebAPI(conf cloudServerConfig) *WebAPI { accessKeyID: conf.AccessKeyID, secretAccessKey: conf.SecretAccessKey, } - return web + return w } // getNewCloudStorageAPI instantiate a new CloudStorageAPI. @@ -161,18 +154,29 @@ func getNewCloudStorageAPI(conf cloudServerConfig) CloudStorageAPI { } } -func getCloudStorageAPIHandler(api CloudStorageAPI) http.Handler { +func getCloudStorageAPIHandler(api CloudStorageAPI, web *WebAPI) http.Handler { var handlerFns = []HandlerFunc{ + // Redirect some pre-defined browser request paths to a static + // location prefix. + setBrowserRedirectHandler, + // Validates if incoming request is for restricted buckets. + setPrivateBucketHandler, + // Adds cache control for all browser requests. + setBrowserCacheControlHandler, + // Validates all incoming requests to have a valid date header. setTimeValidityHandler, + // CORS setting for all browser API requests. + setCorsHandler, + // Validates all incoming URL resources, for invalid/unsupported + // resources client receives a HTTP error. setIgnoreResourcesHandler, - setIgnoreSignatureV2RequestHandler, + // Auth handler verifies incoming authorization headers and + // routes them accordingly. Client receives a HTTP error for + // invalid/unsupported signatures. setAuthHandler, } - if api.AccessLog { - handlerFns = append(handlerFns, setAccessLogHandler) - } handlerFns = append(handlerFns, setCorsHandler) mux := router.NewRouter() - registerCloudStorageAPI(mux, api) + registerCloudStorageAPI(mux, api, web) return registerHandlers(mux, handlerFns...) } diff --git a/server-main.go b/server-main.go index 505289027..fa94738e5 100644 --- a/server-main.go +++ b/server-main.go @@ -90,45 +90,12 @@ type cloudServerConfig struct { KeyFile string // Domain key } -func configureWebServer(conf cloudServerConfig) (*http.Server, *probe.Error) { - // Split the api address into host and port. - host, port, e := net.SplitHostPort(conf.Address) - if e != nil { - return nil, probe.NewError(e) - } - webPort, e := strconv.Atoi(port) - if e != nil { - return nil, probe.NewError(e) - } - // Always choose the next port, based on the API address port. - webPort = webPort + 1 - webAddress := net.JoinHostPort(host, strconv.Itoa(webPort)) - - // Minio server config - webServer := &http.Server{ - Addr: webAddress, - Handler: getWebAPIHandler(getNewWebAPI(conf)), - MaxHeaderBytes: 1 << 20, - } - - if conf.TLS { - var err error - webServer.TLSConfig = &tls.Config{} - webServer.TLSConfig.Certificates = make([]tls.Certificate, 1) - webServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile) - if err != nil { - return nil, probe.NewError(err) - } - } - return webServer, nil -} - // configureAPIServer configure a new server instance func configureAPIServer(conf cloudServerConfig) (*http.Server, *probe.Error) { // Minio server config apiServer := &http.Server{ Addr: conf.Address, - Handler: getCloudStorageAPIHandler(getNewCloudStorageAPI(conf)), + Handler: getCloudStorageAPIHandler(getNewCloudStorageAPI(conf), getNewWebAPI(conf)), MaxHeaderBytes: 1 << 20, } @@ -325,13 +292,6 @@ func serverMain(c *cli.Context) { Println("\nMinio Object Storage:") printServerMsg(apiServer) - // configure Web server. - webServer, err := configureWebServer(serverConfig) - errorIf(err.Trace(), "Failed to configure Web server.", nil) - - Println("\nMinio Browser:") - printServerMsg(webServer) - Println("\nTo configure Minio Client:") if runtime.GOOS == "windows" { Println(" Download \"mc\" from https://dl.minio.io/client/mc/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/mc.exe") @@ -343,6 +303,6 @@ func serverMain(c *cli.Context) { } // Start server. - err = minhttp.ListenAndServe(apiServer, webServer) + err = minhttp.ListenAndServe(apiServer) errorIf(err.Trace(), "Failed to start the minio server.", nil) } diff --git a/server_fs_test.go b/server_fs_test.go index 85fc24d58..cbdac349e 100644 --- a/server_fs_test.go +++ b/server_fs_test.go @@ -21,8 +21,10 @@ import ( "crypto/md5" "io" "io/ioutil" + "net" "os" "sort" + "strconv" "strings" "time" @@ -52,6 +54,21 @@ var _ = Suite(&MyAPIFSCacheSuite{}) var testAPIFSCacheServer *httptest.Server +// Ask the kernel for a free open port. +func getFreePort() int { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + panic(err) + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + panic(err) + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port +} + func (s *MyAPIFSCacheSuite) SetUpSuite(c *C) { root, e := ioutil.TempDir(os.TempDir(), "api-") c.Assert(e, IsNil) @@ -77,6 +94,7 @@ func (s *MyAPIFSCacheSuite) SetUpSuite(c *C) { c.Assert(saveConfig(conf), IsNil) cloudServer := cloudServerConfig{ + Address: ":" + strconv.Itoa(getFreePort()), Path: fsroot, MinFreeDisk: 0, AccessKeyID: s.accessKeyID, @@ -84,7 +102,8 @@ func (s *MyAPIFSCacheSuite) SetUpSuite(c *C) { Region: "us-east-1", } cloudStorageAPI := getNewCloudStorageAPI(cloudServer) - httpHandler := getCloudStorageAPIHandler(cloudStorageAPI) + webAPI := getNewWebAPI(cloudServer) + httpHandler := getCloudStorageAPIHandler(cloudStorageAPI, webAPI) testAPIFSCacheServer = httptest.NewServer(httpHandler) } diff --git a/signature.go b/signature.go index 59626d13e..58981ab06 100644 --- a/signature.go +++ b/signature.go @@ -60,8 +60,7 @@ func isSignV4ReqAuthenticated(sign *signV4.Signature, r *http.Request) bool { return false } return ok - } - if isRequestPresignedSignatureV4(r) { + } else if isRequestPresignedSignatureV4(r) { ok, err := auth.DoesPresignedSignatureMatch() if err != nil { errorIf(err.Trace(), "Presigned signature verification failed.", nil)