Skip to content

Commit e7a724d

Browse files
Krishna Srinivasdeekoder
Krishna Srinivas
authored andcommitted
Virtual host style S3 requests (minio#5095)
1 parent d57d57d commit e7a724d

18 files changed

+413
-163
lines changed

cmd/admin-handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http
985985
return
986986
}
987987

988-
var config serverConfigV19
988+
var config serverConfigV20
989989
err = json.Unmarshal(configBytes, &config)
990990

991991
if err != nil {

cmd/api-errors.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ const (
162162
ErrServerNotInitialized
163163
ErrOperationTimedOut
164164
ErrPartsSizeUnequal
165+
ErrInvalidRequest
165166
// Add new extended error codes here.
166167
// Please open a https://github.com/minio/minio/issues before adding
167168
// new error codes here.
@@ -731,6 +732,15 @@ var errorCodeResponse = map[APIErrorCode]APIError{
731732
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
732733
HTTPStatusCode: http.StatusBadRequest,
733734
},
735+
// Generic Invalid-Request error. Should be used for response errors only for unlikely
736+
// corner case errors for which introducing new APIErrorCode is not worth it. errorIf()
737+
// should be used to log the error at the source of the error for debugging purposes.
738+
ErrInvalidRequest: {
739+
Code: "InvalidRequest",
740+
Description: "Invalid Request",
741+
HTTPStatusCode: http.StatusBadRequest,
742+
},
743+
734744
// Add your error structure here.
735745
}
736746

cmd/api-router.go

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616

1717
package cmd
1818

19-
import (
20-
router "github.com/gorilla/mux"
21-
)
19+
import router "github.com/gorilla/mux"
20+
import "net/http"
2221

2322
// objectAPIHandler implements and provides http handlers for S3 API.
2423
type objectAPIHandlers struct {
@@ -34,70 +33,75 @@ func registerAPIRouter(mux *router.Router) {
3433

3534
// API Router
3635
apiRouter := mux.NewRoute().PathPrefix("/").Subrouter()
36+
var routers []*router.Router
37+
if globalDomainName != "" {
38+
routers = append(routers, apiRouter.Host("{bucket:.+}."+globalDomainName).Subrouter())
39+
}
40+
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
3741

38-
// Bucket router
39-
bucket := apiRouter.PathPrefix("/{bucket}").Subrouter()
40-
41-
/// Object operations
42-
43-
// HeadObject
44-
bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.HeadObjectHandler))
45-
// CopyObjectPart
46-
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
47-
// PutObjectPart
48-
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
49-
// ListObjectPxarts
50-
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}")
51-
// CompleteMultipartUpload
52-
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
53-
// NewMultipartUpload
54-
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.NewMultipartUploadHandler)).Queries("uploads", "")
55-
// AbortMultipartUpload
56-
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.AbortMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
57-
// GetObject
58-
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectHandler))
59-
// CopyObject
60-
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectHandler))
61-
// PutObject
62-
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectHandler))
63-
// DeleteObject
64-
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.DeleteObjectHandler))
65-
66-
/// Bucket operations
42+
for _, bucket := range routers {
43+
// Object operations
44+
// HeadObject
45+
bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.HeadObjectHandler))
46+
// CopyObjectPart
47+
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
48+
// PutObjectPart
49+
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectPartHandler)).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
50+
// ListObjectPxarts
51+
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.ListObjectPartsHandler)).Queries("uploadId", "{uploadId:.*}")
52+
// CompleteMultipartUpload
53+
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.CompleteMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
54+
// NewMultipartUpload
55+
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.NewMultipartUploadHandler)).Queries("uploads", "")
56+
// AbortMultipartUpload
57+
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.AbortMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
58+
// GetObject
59+
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectHandler))
60+
// CopyObject
61+
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(httpTraceAll(api.CopyObjectHandler))
62+
// PutObject
63+
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.PutObjectHandler))
64+
// DeleteObject
65+
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.DeleteObjectHandler))
6766

68-
// GetBucketLocation
69-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLocationHandler)).Queries("location", "")
70-
// GetBucketPolicy
71-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketPolicyHandler)).Queries("policy", "")
72-
// GetBucketNotification
73-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketNotificationHandler)).Queries("notification", "")
74-
// ListenBucketNotification
75-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListenBucketNotificationHandler)).Queries("events", "{events:.*}")
76-
// ListMultipartUploads
77-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "")
78-
// ListObjectsV2
79-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2")
80-
// ListObjectsV1 (Legacy)
81-
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV1Handler))
82-
// PutBucketPolicy
83-
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketPolicyHandler)).Queries("policy", "")
84-
// PutBucketNotification
85-
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketNotificationHandler)).Queries("notification", "")
86-
// PutBucket
87-
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketHandler))
88-
// HeadBucket
89-
bucket.Methods("HEAD").HandlerFunc(httpTraceAll(api.HeadBucketHandler))
90-
// PostPolicy
91-
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(httpTraceAll(api.PostPolicyBucketHandler))
92-
// DeleteMultipleObjects
93-
bucket.Methods("POST").HandlerFunc(httpTraceAll(api.DeleteMultipleObjectsHandler))
94-
// DeleteBucketPolicy
95-
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketPolicyHandler)).Queries("policy", "")
96-
// DeleteBucket
97-
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketHandler))
67+
/// Bucket operations
68+
// GetBucketLocation
69+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLocationHandler)).Queries("location", "")
70+
// GetBucketPolicy
71+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketPolicyHandler)).Queries("policy", "")
72+
// GetBucketNotification
73+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketNotificationHandler)).Queries("notification", "")
74+
// ListenBucketNotification
75+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListenBucketNotificationHandler)).Queries("events", "{events:.*}")
76+
// ListMultipartUploads
77+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListMultipartUploadsHandler)).Queries("uploads", "")
78+
// ListObjectsV2
79+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV2Handler)).Queries("list-type", "2")
80+
// ListObjectsV1 (Legacy)
81+
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.ListObjectsV1Handler))
82+
// PutBucketPolicy
83+
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketPolicyHandler)).Queries("policy", "")
84+
// PutBucketNotification
85+
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketNotificationHandler)).Queries("notification", "")
86+
// PutBucket
87+
bucket.Methods("PUT").HandlerFunc(httpTraceAll(api.PutBucketHandler))
88+
// HeadBucket
89+
bucket.Methods("HEAD").HandlerFunc(httpTraceAll(api.HeadBucketHandler))
90+
// PostPolicy
91+
bucket.Methods("POST").Path("/").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(httpTraceAll(api.PostPolicyBucketHandler))
92+
// DeleteMultipleObjects
93+
bucket.Methods("POST").HandlerFunc(httpTraceAll(api.DeleteMultipleObjectsHandler)).Queries("delete", "")
94+
// DeleteBucketPolicy
95+
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketPolicyHandler)).Queries("policy", "")
96+
// DeleteBucket
97+
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketHandler))
98+
}
9899

99100
/// Root operation
100101

101102
// ListBuckets
102-
apiRouter.Methods("GET").HandlerFunc(httpTraceAll(api.ListBucketsHandler))
103+
apiRouter.Methods("GET").Path("/").HandlerFunc(httpTraceAll(api.ListBucketsHandler))
104+
105+
// If none of the routes match.
106+
apiRouter.NotFoundHandler = http.HandlerFunc(httpTraceAll(notFoundHandler))
103107
}

cmd/auth-handler.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,11 @@ func checkRequestAuthType(r *http.Request, bucket, policyAction, region string)
127127
if reqAuthType == authTypeAnonymous && policyAction != "" {
128128
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
129129
sourceIP := getSourceIPAddress(r)
130-
return enforceBucketPolicy(bucket, policyAction, r.URL.Path,
130+
resource, err := getResource(r.URL.Path, r.Host, globalDomainName)
131+
if err != nil {
132+
return ErrInternalError
133+
}
134+
return enforceBucketPolicy(bucket, policyAction, resource,
131135
r.Referer(), sourceIP, r.URL.Query())
132136
}
133137

cmd/bucket-handlers.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"net/http"
2525
"net/url"
2626
"path"
27-
"path/filepath"
2827
"reflect"
2928
"strings"
3029
"sync"
@@ -441,10 +440,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
441440

442441
// Make sure that the URL does not contain object name.
443442
bucket := mux.Vars(r)["bucket"]
444-
if bucket != filepath.Clean(r.URL.Path[1:]) {
445-
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
446-
return
447-
}
448443

449444
// Require Content-Length to be set in the request
450445
size := r.ContentLength

cmd/common-main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func initConfig() {
5757
// Config file does not exist, we create it fresh and return upon success.
5858
if isFile(getConfigFile()) {
5959
fatalIf(migrateConfig(), "Config migration failed.")
60-
fatalIf(loadConfig(), "Unable to load config version: '%s'.", v19)
60+
fatalIf(loadConfig(), "Unable to load config version: '%s'.", v20)
6161
} else {
6262
fatalIf(newConfig(), "Unable to initialize minio config for the first time.")
6363
log.Println("Created minio configuration file successfully at " + getConfigDir())
@@ -117,4 +117,9 @@ func handleCommonEnvVars() {
117117
}
118118

119119
globalHTTPTrace = os.Getenv("MINIO_HTTP_TRACE") != ""
120+
121+
globalDomainName = os.Getenv("MINIO_DOMAIN")
122+
if globalDomainName != "" {
123+
globalIsEnvDomainName = true
124+
}
120125
}

cmd/config-migrate.go

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,12 @@ func migrateConfig() error {
148148
return err
149149
}
150150
fallthrough
151-
case v19:
151+
case "19":
152+
if err = migrateV19ToV20(); err != nil {
153+
return err
154+
}
155+
fallthrough
156+
case "20":
152157
// No migration needed. this always points to current version.
153158
err = nil
154159
}
@@ -1479,3 +1484,111 @@ func migrateV18ToV19() error {
14791484
log.Printf(configMigrateMSGTemplate, configFile, cv18.Version, srvConfig.Version)
14801485
return nil
14811486
}
1487+
1488+
func migrateV19ToV20() error {
1489+
configFile := getConfigFile()
1490+
1491+
cv19 := &serverConfigV19{}
1492+
_, err := quick.Load(configFile, cv19)
1493+
if os.IsNotExist(err) {
1494+
return nil
1495+
} else if err != nil {
1496+
return fmt.Errorf("Unable to load config version ‘18’. %v", err)
1497+
}
1498+
if cv19.Version != "19" {
1499+
return nil
1500+
}
1501+
1502+
// Copy over fields from V19 into V20 config struct
1503+
srvConfig := &serverConfigV20{
1504+
Logger: &loggers{},
1505+
Notify: &notifier{},
1506+
}
1507+
srvConfig.Version = "20"
1508+
srvConfig.Credential = cv19.Credential
1509+
srvConfig.Region = cv19.Region
1510+
if srvConfig.Region == "" {
1511+
// Region needs to be set for AWS Signature Version 4.
1512+
srvConfig.Region = globalMinioDefaultRegion
1513+
}
1514+
1515+
srvConfig.Logger.Console = cv19.Logger.Console
1516+
srvConfig.Logger.File = cv19.Logger.File
1517+
1518+
// check and set notifiers config
1519+
if len(cv19.Notify.AMQP) == 0 {
1520+
srvConfig.Notify.AMQP = make(map[string]amqpNotify)
1521+
srvConfig.Notify.AMQP["1"] = amqpNotify{}
1522+
} else {
1523+
// New deliveryMode parameter is added for AMQP,
1524+
// default value is already 0, so nothing to
1525+
// explicitly migrate here.
1526+
srvConfig.Notify.AMQP = cv19.Notify.AMQP
1527+
}
1528+
if len(cv19.Notify.ElasticSearch) == 0 {
1529+
srvConfig.Notify.ElasticSearch = make(map[string]elasticSearchNotify)
1530+
srvConfig.Notify.ElasticSearch["1"] = elasticSearchNotify{
1531+
Format: formatNamespace,
1532+
}
1533+
} else {
1534+
srvConfig.Notify.ElasticSearch = cv19.Notify.ElasticSearch
1535+
}
1536+
if len(cv19.Notify.Redis) == 0 {
1537+
srvConfig.Notify.Redis = make(map[string]redisNotify)
1538+
srvConfig.Notify.Redis["1"] = redisNotify{
1539+
Format: formatNamespace,
1540+
}
1541+
} else {
1542+
srvConfig.Notify.Redis = cv19.Notify.Redis
1543+
}
1544+
if len(cv19.Notify.PostgreSQL) == 0 {
1545+
srvConfig.Notify.PostgreSQL = make(map[string]postgreSQLNotify)
1546+
srvConfig.Notify.PostgreSQL["1"] = postgreSQLNotify{
1547+
Format: formatNamespace,
1548+
}
1549+
} else {
1550+
srvConfig.Notify.PostgreSQL = cv19.Notify.PostgreSQL
1551+
}
1552+
if len(cv19.Notify.Kafka) == 0 {
1553+
srvConfig.Notify.Kafka = make(map[string]kafkaNotify)
1554+
srvConfig.Notify.Kafka["1"] = kafkaNotify{}
1555+
} else {
1556+
srvConfig.Notify.Kafka = cv19.Notify.Kafka
1557+
}
1558+
if len(cv19.Notify.NATS) == 0 {
1559+
srvConfig.Notify.NATS = make(map[string]natsNotify)
1560+
srvConfig.Notify.NATS["1"] = natsNotify{}
1561+
} else {
1562+
srvConfig.Notify.NATS = cv19.Notify.NATS
1563+
}
1564+
if len(cv19.Notify.Webhook) == 0 {
1565+
srvConfig.Notify.Webhook = make(map[string]webhookNotify)
1566+
srvConfig.Notify.Webhook["1"] = webhookNotify{}
1567+
} else {
1568+
srvConfig.Notify.Webhook = cv19.Notify.Webhook
1569+
}
1570+
if len(cv19.Notify.MySQL) == 0 {
1571+
srvConfig.Notify.MySQL = make(map[string]mySQLNotify)
1572+
srvConfig.Notify.MySQL["1"] = mySQLNotify{
1573+
Format: formatNamespace,
1574+
}
1575+
} else {
1576+
srvConfig.Notify.MySQL = cv19.Notify.MySQL
1577+
}
1578+
if len(cv19.Notify.MQTT) == 0 {
1579+
srvConfig.Notify.MQTT = make(map[string]mqttNotify)
1580+
srvConfig.Notify.MQTT["1"] = mqttNotify{}
1581+
} else {
1582+
srvConfig.Notify.MQTT = cv19.Notify.MQTT
1583+
}
1584+
1585+
// Load browser config from existing config in the file.
1586+
srvConfig.Browser = cv19.Browser
1587+
1588+
if err = quick.Save(configFile, srvConfig); err != nil {
1589+
return fmt.Errorf("Failed to migrate config from ‘%s’ to ‘%s’. %v", cv19.Version, srvConfig.Version, err)
1590+
}
1591+
1592+
log.Printf(configMigrateMSGTemplate, configFile, cv19.Version, srvConfig.Version)
1593+
return nil
1594+
}

cmd/config-migrate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func TestServerConfigMigrateV2toV19(t *testing.T) {
169169
}
170170

171171
// Check the version number in the upgraded config file
172-
expectedVersion := v19
172+
expectedVersion := v20
173173
if serverConfig.Version != expectedVersion {
174174
t.Fatalf("Expect version "+expectedVersion+", found: %v", serverConfig.Version)
175175
}

cmd/config-old.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,21 @@ type serverConfigV18 struct {
472472
// Notification queue configuration.
473473
Notify *notifier `json:"notify"`
474474
}
475+
476+
// serverConfigV19 server configuration version '19' which is like
477+
// version '18' except it adds support for MQTT notifications.
478+
type serverConfigV19 struct {
479+
sync.RWMutex
480+
Version string `json:"version"`
481+
482+
// S3 API configuration.
483+
Credential auth.Credentials `json:"credential"`
484+
Region string `json:"region"`
485+
Browser BrowserFlag `json:"browser"`
486+
487+
// Additional error logging configuration.
488+
Logger *loggers `json:"logger"`
489+
490+
// Notification queue configuration.
491+
Notify *notifier `json:"notify"`
492+
}

0 commit comments

Comments
 (0)