Skip to content

Commit

Permalink
Merge pull request #20 from halverneus/dev
Browse files Browse the repository at this point in the history
Add optional check for HTTP 'Referer' header
  • Loading branch information
halverneus authored Jan 24, 2019
2 parents 50d8d71 + 54d4d5b commit 8810c5d
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
################################################################################
FROM golang:1.11.3 as builder

ENV VERSION 1.5.2
ENV VERSION 1.6.0
ENV BUILD_DIR /build

RUN mkdir -p ${BUILD_DIR}
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.all
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM golang:1.11.3 as builder

ENV VERSION 1.5.2
ENV VERSION 1.6.0
ENV BUILD_DIR /build

RUN mkdir -p ${BUILD_DIR}
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ URL_PREFIX=
# served using HTTP.
TLS_CERT=
TLS_KEY=
# List of accepted HTTP referrers. Return 403 if HTTP header `Referer` does not
# match prefixes provided in the list.
# Examples:
# 'REFERRERS=http://localhost,https://...,https://another.name'
# To accept missing referrer header, add a blank entry (start comma):
# 'REFERRERS=,http://localhost,https://another.name'
REFERRERS=
```

### YAML Configuration File
Expand All @@ -53,6 +60,9 @@ folder: /web
url-prefix: ""
tls-cert: ""
tls-key: ""
referrers:
- http://localhost
- https://my.site
```
## Deployment
Expand Down
24 changes: 23 additions & 1 deletion cli/help/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ ENVIRONMENT VARIABLES
to a client without regard for the hostname.
PORT
The port used for binding. If not supplied, defaults to port '8080'.
REFERRERS
A comma-separated list of acceped Referrers based on the 'Referer' HTTP
header. If incoming header value is not in the list, a 403 HTTP error is
returned. To accept requests without a 'Referer' HTTP header in addition
to the whitelisted values, include an empty value (either with a leading
comma in the environment variable or with an empty list item in the YAML
configuration file) as demonstrated in the second example. If not
supplied the 'Referer' HTTP header is ignored.
Examples:
REFERRERS='http://localhost,https://some.site,http://other.site:8080'
REFERRERS=',http://localhost,https://some.site,http://other.site:8080'
SHOW_LISTING
Automatically serve the index file for the directory if requested. For
example, if the client requests 'http://127.0.0.1/' the 'index.html'
Expand Down Expand Up @@ -77,12 +88,23 @@ CONFIGURATION FILE
folder: /web
host: ""
port: 8080
referrers: []
show-listing: true
tls-cert: ""
tls-key: ""
url-prefix: ""
----------------------------------------------------------------------------
Example config.yml with possible alternative values:
----------------------------------------------------------------------------
debug: true
folder: /var/www
port: 80
referrers:
- http://localhost
- https://mydomain.com
----------------------------------------------------------------------------
USAGE
FILE LAYOUT
/var/www/sub/my.file
Expand All @@ -99,7 +121,7 @@ USAGE
export PORT=80
static-file-server
Retrieve with: wget http://my.machine/sub/my.file
export FOLDER=/var/www
static-file-server -c config.yml
Result: Runs with values from config.yml, but with the folder being
Expand Down
7 changes: 7 additions & 0 deletions cli/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,18 @@ func Run() error {
// configuration.
func handlerSelector() (handler http.HandlerFunc) {
var serveFileHandler handle.FileServerFunc

serveFileHandler = http.ServeFile
if config.Get.Debug {
serveFileHandler = handle.WithLogging(serveFileHandler)
}

if 0 != len(config.Get.Referrers) {
serveFileHandler = handle.WithReferrers(
serveFileHandler, config.Get.Referrers,
)
}

// Choose and set the appropriate, optimized static file serving function.
if 0 == len(config.Get.URLPrefix) {
handler = handle.Basic(serveFileHandler, config.Get.Folder)
Expand Down
28 changes: 20 additions & 8 deletions cli/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,33 @@ func TestHandlerSelector(t *testing.T) {
// This test only exercises function branches.
testFolder := "/web"
testPrefix := "/url/prefix"
var ignoreReferrer []string
testReferrer := []string{"http://localhost"}

testCases := []struct {
name string
folder string
prefix string
listing bool
debug bool
refer []string
}{
{"Basic handler w/o debug", testFolder, "", true, false},
{"Prefix handler w/o debug", testFolder, testPrefix, true, false},
{"Basic and hide listing handler w/o debug", testFolder, "", false, false},
{"Prefix and hide listing handler w/o debug", testFolder, testPrefix, false, false},
{"Basic handler w/debug", testFolder, "", true, true},
{"Prefix handler w/debug", testFolder, testPrefix, true, true},
{"Basic and hide listing handler w/debug", testFolder, "", false, true},
{"Prefix and hide listing handler w/debug", testFolder, testPrefix, false, true},
{"Basic handler w/o debug", testFolder, "", true, false, ignoreReferrer},
{"Prefix handler w/o debug", testFolder, testPrefix, true, false, ignoreReferrer},
{"Basic and hide listing handler w/o debug", testFolder, "", false, false, ignoreReferrer},
{"Prefix and hide listing handler w/o debug", testFolder, testPrefix, false, false, ignoreReferrer},
{"Basic handler w/debug", testFolder, "", true, true, ignoreReferrer},
{"Prefix handler w/debug", testFolder, testPrefix, true, true, ignoreReferrer},
{"Basic and hide listing handler w/debug", testFolder, "", false, true, ignoreReferrer},
{"Prefix and hide listing handler w/debug", testFolder, testPrefix, false, true, ignoreReferrer},
{"Basic handler w/o debug w/refer", testFolder, "", true, false, testReferrer},
{"Prefix handler w/o debug w/refer", testFolder, testPrefix, true, false, testReferrer},
{"Basic and hide listing handler w/o debug w/refer", testFolder, "", false, false, testReferrer},
{"Prefix and hide listing handler w/o debug w/refer", testFolder, testPrefix, false, false, testReferrer},
{"Basic handler w/debug w/refer", testFolder, "", true, true, testReferrer},
{"Prefix handler w/debug w/refer", testFolder, testPrefix, true, true, testReferrer},
{"Basic and hide listing handler w/debug w/refer", testFolder, "", false, true, testReferrer},
{"Prefix and hide listing handler w/debug w/refer", testFolder, testPrefix, false, true, testReferrer},
}

for _, tc := range testCases {
Expand All @@ -56,6 +67,7 @@ func TestHandlerSelector(t *testing.T) {
config.Get.Folder = tc.folder
config.Get.ShowListing = tc.listing
config.Get.URLPrefix = tc.prefix
config.Get.Referrers = tc.refer

handlerSelector()
})
Expand Down
32 changes: 23 additions & 9 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import (
var (
// Get the desired configuration value.
Get struct {
Debug bool `yaml:"debug"`
Folder string `yaml:"folder"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
ShowListing bool `yaml:"show-listing"`
TLSCert string `yaml:"tls-cert"`
TLSKey string `yaml:"tls-key"`
URLPrefix string `yaml:"url-prefix"`
Debug bool `yaml:"debug"`
Folder string `yaml:"folder"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
ShowListing bool `yaml:"show-listing"`
TLSCert string `yaml:"tls-cert"`
TLSKey string `yaml:"tls-key"`
URLPrefix string `yaml:"url-prefix"`
Referrers []string `yaml:"referrers"`
}
)

Expand All @@ -30,17 +31,19 @@ const (
folderKey = "FOLDER"
hostKey = "HOST"
portKey = "PORT"
referrersKey = "REFERRERS"
showListingKey = "SHOW_LISTING"
tlsCertKey = "TLS_CERT"
tlsKeyKey = "TLS_KEY"
urlPrefixKey = "URL_PREFIX"
)

const (
var (
defaultDebug = false
defaultFolder = "/web"
defaultHost = ""
defaultPort = uint16(8080)
defaultReferrers = []string{}
defaultShowListing = true
defaultTLSCert = ""
defaultTLSKey = ""
Expand All @@ -57,6 +60,7 @@ func setDefaults() {
Get.Folder = defaultFolder
Get.Host = defaultHost
Get.Port = defaultPort
Get.Referrers = defaultReferrers
Get.ShowListing = defaultShowListing
Get.TLSCert = defaultTLSCert
Get.TLSKey = defaultTLSKey
Expand Down Expand Up @@ -108,6 +112,7 @@ func overrideWithEnvVars() {
Get.TLSCert = envAsStr(tlsCertKey, Get.TLSCert)
Get.TLSKey = envAsStr(tlsKeyKey, Get.TLSKey)
Get.URLPrefix = envAsStr(urlPrefixKey, Get.URLPrefix)
Get.Referrers = envAsStrSlice(referrersKey, Get.Referrers)
}

// validate the configuration.
Expand Down Expand Up @@ -150,6 +155,15 @@ func envAsStr(key, fallback string) string {
return fallback
}

// envAsStrSlice returns the value of the environment variable as a slice of
// strings if set.
func envAsStrSlice(key string, fallback []string) []string {
if value := os.Getenv(key); "" != value {
return strings.Split(value, ",")
}
return fallback
}

// envAsUint16 returns the value of the environment variable as a uint16 if set.
func envAsUint16(key string, fallback uint16) uint16 {
// Retrieve the string value of the environment variable. If not set,
Expand Down
94 changes: 93 additions & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strconv"
"testing"

"gopkg.in/yaml.v2"
yaml "gopkg.in/yaml.v2"
)

func TestLoad(t *testing.T) {
Expand Down Expand Up @@ -245,6 +245,98 @@ func TestEnvAsStr(t *testing.T) {
}
}

func TestEnvAsStrSlice(t *testing.T) {
oe := "ONE_ENTRY"
oewc := "ONE_ENTRY_WITH_COMMA"
oewtc := "ONE_ENTRY_WITH_TRAILING_COMMA"
te := "TWO_ENTRY"
tewc := "TWO_ENTRY_WITH_COMMA"
oc := "ONLY_COMMA"
ev := "EMPTY_VALUE"
uv := "UNSET_VALUE"

fs := "http://my.site"
ts := "http://other.site"
fbr := []string{"one", "two"}
var efbr []string

oes := fs
oer := []string{fs}
oewcs := "," + fs
oewcr := []string{"", fs}
oewtcs := fs + ","
oewtcr := []string{fs, ""}
tes := fs + "," + ts
ter := []string{fs, ts}
tewcs := "," + fs + "," + ts
tewcr := []string{"", fs, ts}
ocs := ","
ocr := []string{"", ""}
evs := ""

os.Setenv(oe, oes)
os.Setenv(oewc, oewcs)
os.Setenv(oewtc, oewtcs)
os.Setenv(te, tes)
os.Setenv(tewc, tewcs)
os.Setenv(oc, ocs)
os.Setenv(ev, evs)

testCases := []struct {
name string
key string
fallback []string
result []string
}{
{"One entry", oe, fbr, oer},
{"One entry w/comma", oewc, fbr, oewcr},
{"One entry w/trailing comma", oewtc, fbr, oewtcr},
{"Two entry", te, fbr, ter},
{"Two entry w/comma", tewc, fbr, tewcr},
{"Only comma", oc, fbr, ocr},
{"Empty value w/fallback", ev, fbr, fbr},
{"Empty value wo/fallback", ev, efbr, efbr},
{"Unset w/fallback", uv, fbr, fbr},
{"Unset wo/fallback", uv, efbr, efbr},
}

matches := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
tally := make(map[int]bool)
for i := range a {
tally[i] = false
}
for _, val := range a {
for i, other := range b {
if other == val && !tally[i] {
tally[i] = true
break
}
}
}
for _, found := range tally {
if !found {
return false
}
}
return true
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := envAsStrSlice(tc.key, tc.fallback)
if !matches(tc.result, result) {
t.Errorf(
"For %s with a '%v' fallback expected '%v' but got '%v'",
tc.key, tc.fallback, tc.result, result,
)
}
})
}
}

func TestEnvAsUint16(t *testing.T) {
ubv := "UPPER_BOUNDS_VALUE"
lbv := "LOWER_BOUNDS_VALUE"
Expand Down
Loading

0 comments on commit 8810c5d

Please sign in to comment.