Skip to content
This repository has been archived by the owner on Jun 26, 2023. It is now read-only.

Fix/disallow filename characters #376

Merged
merged 10 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions dev_utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ mc admin trace -v proxydev
Run the go proxy from the root directory

```bash
export GO111MODULE=on
export SERVER_CONFFILE=dev_utils/config.yaml
go build main.go
./main
go build .
./S3-Upload-Proxy
```


Expand Down
27 changes: 27 additions & 0 deletions helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/golang-jwt/jwt/v4"
Expand Down Expand Up @@ -363,3 +366,27 @@ func TLScertToFile(filename string, derBytes []byte) error {

return err
}

// FormatUploadFilePath ensures that path separators are "/", and returns error if the
// filepath contains a disallowed character matched with regex
func FormatUploadFilePath(filePath string) (string, error) {

// Check for mixed "\" and "/" in filepath. Stop and throw an error if true so that
// we do not end up with unintended folder structure when applying ReplaceAll below
if strings.Contains(filePath, "\\") && strings.Contains(filePath, "/") {
return filePath, fmt.Errorf("filepath contains mixed '\\' and '/' characters")
}
Comment on lines +374 to +378
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be unnecessary in the end, but it will require manual testing to validate since / actually is a valid separator on windows.


// make any windows path separators linux compatible
outPath := strings.ReplaceAll(filePath, "\\", "/")

// [\x00-\x1F\x7F] is the control character set
re := regexp.MustCompile(`[\\:\*\?"<>\|\x00-\x1F\x7F]`)

dissallowedChars := re.FindAllString(outPath, -1)
if dissallowedChars != nil {
return outPath, fmt.Errorf("filepath contains disallowed characters: %+v", strings.Join(dissallowedChars, ", "))
}

return outPath, nil
}
20 changes: 20 additions & 0 deletions helper/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,23 @@ func TestCreateECToken(t *testing.T) {

defer os.RemoveAll("dummy-folder")
}

func TestFormatUploadFilePath(t *testing.T) {

unixPath := "a/b/c.c4gh"
testPath := "a\\b\\c.c4gh"
uploadPath, err := FormatUploadFilePath(testPath)
assert.NoError(t, err)
assert.Equal(t, unixPath, uploadPath)

// mixed "\" and "/"
weirdPath := `dq\sw:*?"<>|\t\s/df.c4gh`
_, err = FormatUploadFilePath(weirdPath)
assert.EqualError(t, err, "filepath contains mixed '\\' and '/' characters")

// no mixed "\" and "/" but not allowed
weirdPath = `dq\sw:*?"<>|\t\sdf.c4gh`
_, err = FormatUploadFilePath(weirdPath)
assert.EqualError(t, err, "filepath contains disallowed characters: :, *, ?, \", <, >, |")

}
20 changes: 14 additions & 6 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

"github.com/NBISweden/S3-Upload-Proxy/helper"
common "github.com/neicnordic/sda-common/database"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -77,19 +78,18 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

func (p *Proxy) internalServerError(w http.ResponseWriter, r *http.Request) {
log.Debug("internal server error")
log.Debugf("Internal server error for request (%v)", r)
w.WriteHeader(500)
w.WriteHeader(http.StatusInternalServerError)
}

func (p *Proxy) notAllowedResponse(w http.ResponseWriter, _ *http.Request) {
log.Debug("not allowed response")
w.WriteHeader(403)
w.WriteHeader(http.StatusForbidden)
}

func (p *Proxy) notAuthorized(w http.ResponseWriter, _ *http.Request) {
log.Debug("not authorized")
w.WriteHeader(401) // Actually correct!
w.WriteHeader(http.StatusUnauthorized)
}

func (p *Proxy) allowedResponse(w http.ResponseWriter, r *http.Request) {
Expand All @@ -105,7 +105,16 @@ func (p *Proxy) allowedResponse(w http.ResponseWriter, r *http.Request) {
p.prependBucketToHostPath(r)

username := fmt.Sprintf("%v", claims["sub"])
filepath := strings.Replace(r.URL.Path, "/"+p.s3.bucket+"/", "", 1)
rawFilepath := strings.Replace(r.URL.Path, "/"+p.s3.bucket+"/", "", 1)

filepath, err := helper.FormatUploadFilePath(rawFilepath)
if err != nil {
log.Debugf(err.Error())
w.WriteHeader(http.StatusNotAcceptable)

return
}

// register file in database if it's the start of an upload
if p.detectRequestType(r) == Put && p.fileIds[r.URL.Path] == "" {
log.Debugf("registering file %v in the database", r.URL.Path)
Expand Down Expand Up @@ -409,7 +418,6 @@ func (p *Proxy) requestInfo(fullPath string) (string, int64, error) {

return "", 0, err
}
fmt.Println(strings.ReplaceAll(*result.Contents[0].ETag, "\"", ""))

return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.ReplaceAll(*result.Contents[0].ETag, "\"", "")))), *result.Contents[0].Size, nil

Expand Down
10 changes: 10 additions & 0 deletions proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ func (suite *ProxyTests) TestServeHTTP_allowed() {
proxy.ServeHTTP(w, r)
assert.Equal(suite.T(), 200, w.Result().StatusCode)
assert.Equal(suite.T(), true, suite.fakeServer.PingedAndRestore())

// Filenames with platform incompatible characters are disallowed
// not checked in TestServeHTTP_allowed() because we look for a non 403 response
r.Method = "PUT"
r.URL, _ = url.Parse("/username/fi|le")
w = httptest.NewRecorder()
proxy.ServeHTTP(w, r)
assert.Equal(suite.T(), 406, w.Result().StatusCode)
assert.Equal(suite.T(), false, suite.fakeServer.PingedAndRestore())

}

func (suite *ProxyTests) TestMessageFormatting() {
Expand Down