Bucket name rewriting

This commit is contained in:
shabbyrobe
2019-01-17 22:53:15 +11:00
parent 072fc9a473
commit 5c596e7612
5 changed files with 78 additions and 22 deletions

View File

@@ -31,6 +31,7 @@ type fakeS3Flags struct {
initialBucket string
fixedTimeStr string
noIntegrity bool
hostBucket bool
boltDb string
directFsPath string
@@ -45,6 +46,7 @@ func (f *fakeS3Flags) attach(flagSet *flag.FlagSet) {
flagSet.StringVar(&f.fixedTimeStr, "time", "", "RFC3339 format. If passed, the server's clock will always see this time; does not affect existing stored dates.")
flagSet.StringVar(&f.initialBucket, "initialbucket", "", "If passed, this bucket will be created on startup if it does not already exist.")
flagSet.BoolVar(&f.noIntegrity, "no-integrity", false, "Pass this flag to disable Content-MD5 validation when uploading.")
flagSet.BoolVar(&f.hostBucket, "hostbucket", false, "If passed, the bucket name will be extracted from the first segment of the hostname, rather than the first part of the URL path.")
// Backend specific:
flagSet.StringVar(&f.backendKind, "backend", "", "Backend to use to store data (memory, bolt, directfs, fs)")
@@ -181,6 +183,7 @@ func run() error {
gofakes3.WithTimeSkewLimit(timeSkewLimit),
gofakes3.WithTimeSource(timeSource),
gofakes3.WithLogger(gofakes3.GlobalLog()),
gofakes3.WithHostBucket(hostBucket),
)
return listenAndServe(values.host, faker.Server())

16
cors.go
View File

@@ -2,7 +2,6 @@ package gofakes3
import (
"net/http"
"regexp"
"strings"
)
@@ -22,8 +21,6 @@ var (
"x-amz-meta-to",
}
corsHeadersString = strings.Join(corsHeaders, ", ")
bucketRewritePattern = regexp.MustCompile("(127.0.0.1:\\d{1,7})|(.localhost:\\d{1,7})|(localhost:\\d{1,7})")
)
type withCORS struct {
@@ -40,18 +37,5 @@ func (s *withCORS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// Bucket name rewriting
// this is due to some inconsistencies in the AWS SDKs
bucket := bucketRewritePattern.ReplaceAllString(r.Host, "")
if len(bucket) > 0 {
s.log.Print(LogInfo, "rewrite bucket ->", bucket)
p := r.URL.Path
r.URL.Path = "/" + bucket
if p != "/" {
r.URL.Path += p
}
}
s.log.Print(LogInfo, "=>", r.URL)
s.r.ServeHTTP(w, r)
}

View File

@@ -60,6 +60,7 @@ type GoFakeS3 struct {
timeSkew time.Duration
metadataSizeLimit int
integrityCheck bool
hostBucket bool
uploader *uploader
requestID uint64
log Logger
@@ -96,12 +97,24 @@ func (g *GoFakeS3) nextRequestID() uint64 {
// Create the AWS S3 API
func (g *GoFakeS3) Server() http.Handler {
wc := &withCORS{r: http.HandlerFunc(g.routeBase), log: g.log}
var handler http.Handler = &withCORS{r: http.HandlerFunc(g.routeBase), log: g.log}
hf := func(w http.ResponseWriter, rq *http.Request) {
if g.timeSkew != 0 {
handler = g.timeSkewMiddleware(handler)
}
if g.hostBucket {
handler = g.hostBucketMiddleware(handler)
}
return handler
}
func (g *GoFakeS3) timeSkewMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
timeHdr := rq.Header.Get("x-amz-date")
if g.timeSkew > 0 && timeHdr != "" {
if timeHdr != "" {
rqTime, _ := time.Parse("20060102T150405Z", timeHdr)
at := g.timeSource.Now()
skew := at.Sub(rqTime)
@@ -112,10 +125,26 @@ func (g *GoFakeS3) Server() http.Handler {
}
}
wc.ServeHTTP(w, rq)
}
handler.ServeHTTP(w, rq)
})
}
return http.HandlerFunc(hf)
// hostBucketMiddleware forces the server to use VirtualHost-style bucket URLs:
// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html
func (g *GoFakeS3) hostBucketMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
parts := strings.SplitN(rq.Host, ".", 2)
bucket := parts[0]
p := rq.URL.Path
rq.URL.Path = "/" + bucket
if p != "/" {
rq.URL.Path += p
}
g.log.Print(LogInfo, p, "=>", rq.URL)
handler.ServeHTTP(w, rq)
})
}
func (g *GoFakeS3) httpError(w http.ResponseWriter, r *http.Request, err error) {

View File

@@ -5,6 +5,7 @@ import (
"encoding/xml"
"fmt"
"log"
"net/http"
"net/http/httptest"
"testing"
)
@@ -52,6 +53,35 @@ func TestHttpErrorWriteFailure(t *testing.T) {
}
}
func TestHostBucketMiddleware(t *testing.T) {
for _, tc := range []struct {
in string
host string
out string
}{
{"/", "foo", "/foo"},
{"/", "mybucket.localhost", "/mybucket"},
{"/object", "mybucket.localhost", "/mybucket/object"},
} {
t.Run("", func(t *testing.T) {
var g GoFakeS3
g.log = DiscardLog()
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != tc.out {
t.Fatal(r.URL.Path, "!=", tc.out)
}
})
handler := g.hostBucketMiddleware(inner)
rq := httptest.NewRequest("GET", tc.in, nil)
rq.Host = tc.host
rs := httptest.NewRecorder()
handler.ServeHTTP(rs, rq)
})
}
}
type failingResponseWriter struct {
*httptest.ResponseRecorder
}

View File

@@ -55,3 +55,13 @@ func WithGlobalLog() Option {
func WithRequestID(id uint64) Option {
return func(g *GoFakeS3) { g.requestID = id }
}
// WithHostBucket enables or disables bucket rewriting in the router.
// If active, the URL 'http://mybucket.localhost/object' will be routed
// as if the URL path was '/mybucket/object'.
//
// See https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html
// for details.
func WithHostBucket(enabled bool) Option {
return func(g *GoFakeS3) { g.hostBucket = enabled }
}