mirror of
https://github.com/halverneus/static-file-server.git
synced 2024-11-24 09:05:30 +00:00
Added ability to set minimum TLS version. Updated compiler version. Minor code clean-up. Added funding info.
This commit is contained in:
parent
7d738d7188
commit
ea0bcf28bb
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: halverneus
|
@ -1,9 +1,9 @@
|
||||
################################################################################
|
||||
## GO BUILDER
|
||||
################################################################################
|
||||
FROM golang:1.16.5 as builder
|
||||
FROM golang:1.17.2 as builder
|
||||
|
||||
ENV VERSION 1.8.4
|
||||
ENV VERSION 1.8.5
|
||||
ENV BUILD_DIR /build
|
||||
|
||||
RUN mkdir -p ${BUILD_DIR}
|
||||
@ -36,5 +36,5 @@ LABEL life.apets.vendor="Halverneus" \
|
||||
life.apets.url="https://github.com/halverneus/static-file-server" \
|
||||
life.apets.name="Static File Server" \
|
||||
life.apets.description="A tiny static file server" \
|
||||
life.apets.version="v1.8.4" \
|
||||
life.apets.version="v1.8.5" \
|
||||
life.apets.schema-version="1.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
FROM golang:1.16.5 as builder
|
||||
FROM golang:1.17.2 as builder
|
||||
|
||||
ENV VERSION 1.8.4
|
||||
ENV VERSION 1.8.5
|
||||
ENV BUILD_DIR /build
|
||||
|
||||
RUN mkdir -p ${BUILD_DIR}
|
||||
@ -21,5 +21,5 @@ LABEL life.apets.vendor="Halverneus" \
|
||||
life.apets.url="https://github.com/halverneus/static-file-server" \
|
||||
life.apets.name="Static File Server" \
|
||||
life.apets.description="A tiny static file server" \
|
||||
life.apets.version="v1.8.4" \
|
||||
life.apets.version="v1.8.5" \
|
||||
life.apets.schema-version="1.0"
|
||||
|
14
README.md
14
README.md
@ -1,5 +1,12 @@
|
||||
# static-file-server
|
||||
|
||||
<a href="https://github.com/sponsors/halverneus" style="background-color:#fff;color:#000;padding:3px 12px;font-size:12px;border-color:#000;border:1px solid;border-radius:6px;box-sizing:border-box;line-height:20px;display:inline-block;">
|
||||
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" style="vertical-align: middle; margin-right:4px;color:#f00;">
|
||||
<path fill-rule="evenodd" style="color:#f00;fill: currentColor;" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"></path>
|
||||
</svg>
|
||||
<span style="color:#000">Buy me a Smoothie</span>
|
||||
</a>
|
||||
|
||||
## Introduction
|
||||
|
||||
Tiny, simple static file server using environment variables for configuration.
|
||||
@ -47,6 +54,12 @@ URL_PREFIX=
|
||||
TLS_CERT=
|
||||
TLS_KEY=
|
||||
|
||||
# If TLS certificates are set then the minimum TLS version may also be set. If
|
||||
# the value isn't set then the default minimum TLS version is 1.0. Allowed
|
||||
# values include "TLS10", "TLS11", "TLS12" and "TLS13" for TLS1.0, TLS1.1,
|
||||
# TLS1.2 and TLS1.3, respectively. The value is not case-sensitive.
|
||||
TLS_MIN_VERS=
|
||||
|
||||
# List of accepted HTTP referrers. Return 403 if HTTP header `Referer` does not
|
||||
# match prefixes provided in the list.
|
||||
# Examples:
|
||||
@ -73,6 +86,7 @@ referrers: []
|
||||
show-listing: true
|
||||
tls-cert: ""
|
||||
tls-key: ""
|
||||
tls-min-vers: ""
|
||||
url-prefix: ""
|
||||
```
|
||||
|
||||
|
@ -75,6 +75,10 @@ ENVIRONMENT VARIABLES
|
||||
Path to the TLS key file to serve files using HTTPS. If supplied then
|
||||
TLS_CERT must also be supplied. If not supplied, contents will be served
|
||||
via HTTPS
|
||||
TLS_MIN_VERS
|
||||
The minimum TLS version to use. If not supplied, defaults to TLS1.0.
|
||||
Acceptable values are 'TLS10', 'TLS11', 'TLS12' and 'TLS13' for TLS1.0,
|
||||
TLS1.1, TLS1.2 and TLS1.3, respectively. Values are not case-sensitive.
|
||||
URL_PREFIX
|
||||
The prefix to use in the URL path. If supplied, then the prefix must
|
||||
start with a forward-slash and NOT end with a forward-slash. If not
|
||||
@ -98,6 +102,7 @@ CONFIGURATION FILE
|
||||
show-listing: true
|
||||
tls-cert: ""
|
||||
tls-key: ""
|
||||
tls-min-vers: ""
|
||||
url-prefix: ""
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
@ -150,6 +155,7 @@ USAGE
|
||||
export PORT=443
|
||||
export TLS_CERT=/etc/server/my.machine.crt
|
||||
export TLS_KEY=/etc/server/my.machine.key
|
||||
export TLS_MIN_VERS=TLS12
|
||||
static-file-server
|
||||
Retrieve with: wget https://my.machine/my.file
|
||||
|
||||
|
@ -76,6 +76,7 @@ func listenerSelector() (listener handle.ListenerFunc) {
|
||||
// Serve files over HTTP or HTTPS based on paths to TLS files being
|
||||
// provided.
|
||||
if 0 < len(config.Get.TLSCert) {
|
||||
handle.SetMinimumTLSVersion(config.Get.TLSMinVers)
|
||||
listener = handle.TLSListening(
|
||||
config.Get.TLSCert,
|
||||
config.Get.TLSKey,
|
||||
|
@ -1,6 +1,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -22,6 +24,8 @@ var (
|
||||
ShowListing bool `yaml:"show-listing"`
|
||||
TLSCert string `yaml:"tls-cert"`
|
||||
TLSKey string `yaml:"tls-key"`
|
||||
TLSMinVers uint16 `yaml:"-"`
|
||||
TLSMinVersStr string `yaml:"tls-min-vers"`
|
||||
URLPrefix string `yaml:"url-prefix"`
|
||||
Referrers []string `yaml:"referrers"`
|
||||
}
|
||||
@ -37,6 +41,7 @@ const (
|
||||
showListingKey = "SHOW_LISTING"
|
||||
tlsCertKey = "TLS_CERT"
|
||||
tlsKeyKey = "TLS_KEY"
|
||||
tlsMinVersKey = "TLS_MIN_VERS"
|
||||
urlPrefixKey = "URL_PREFIX"
|
||||
)
|
||||
|
||||
@ -49,6 +54,7 @@ var (
|
||||
defaultShowListing = true
|
||||
defaultTLSCert = ""
|
||||
defaultTLSKey = ""
|
||||
defaultTLSMinVers = ""
|
||||
defaultURLPrefix = ""
|
||||
defaultCors = false
|
||||
)
|
||||
@ -67,6 +73,7 @@ func setDefaults() {
|
||||
Get.ShowListing = defaultShowListing
|
||||
Get.TLSCert = defaultTLSCert
|
||||
Get.TLSKey = defaultTLSKey
|
||||
Get.TLSMinVersStr = defaultTLSMinVers
|
||||
Get.URLPrefix = defaultURLPrefix
|
||||
Get.Cors = defaultCors
|
||||
}
|
||||
@ -74,7 +81,7 @@ func setDefaults() {
|
||||
// Load the configuration file.
|
||||
func Load(filename string) (err error) {
|
||||
// If no filename provided, assign envvars.
|
||||
if "" == filename {
|
||||
if filename == "" {
|
||||
overrideWithEnvVars()
|
||||
return
|
||||
}
|
||||
@ -116,6 +123,7 @@ func overrideWithEnvVars() {
|
||||
Get.ShowListing = envAsBool(showListingKey, Get.ShowListing)
|
||||
Get.TLSCert = envAsStr(tlsCertKey, Get.TLSCert)
|
||||
Get.TLSKey = envAsStr(tlsKeyKey, Get.TLSKey)
|
||||
Get.TLSMinVersStr = envAsStr(tlsMinVersKey, Get.TLSMinVersStr)
|
||||
Get.URLPrefix = envAsStr(urlPrefixKey, Get.URLPrefix)
|
||||
Get.Referrers = envAsStrSlice(referrersKey, Get.Referrers)
|
||||
}
|
||||
@ -123,8 +131,9 @@ func overrideWithEnvVars() {
|
||||
// validate the configuration.
|
||||
func validate() error {
|
||||
// If HTTPS is to be used, verify both TLS_* environment variables are set.
|
||||
useTLS := false
|
||||
if 0 < len(Get.TLSCert) || 0 < len(Get.TLSKey) {
|
||||
if 0 == len(Get.TLSCert) || 0 == len(Get.TLSKey) {
|
||||
if len(Get.TLSCert) == 0 || len(Get.TLSKey) == 0 {
|
||||
msg := "if value for either 'TLS_CERT' or 'TLS_KEY' is set then " +
|
||||
"then value for the other must also be set (values are " +
|
||||
"currently '%s' and '%s', respectively)"
|
||||
@ -132,11 +141,30 @@ func validate() error {
|
||||
}
|
||||
if _, err := os.Stat(Get.TLSCert); nil != err {
|
||||
msg := "value of TLS_CERT is set with filename '%s' that returns %v"
|
||||
return fmt.Errorf(msg, err)
|
||||
return fmt.Errorf(msg, Get.TLSCert, err)
|
||||
}
|
||||
if _, err := os.Stat(Get.TLSKey); nil != err {
|
||||
msg := "value of TLS_KEY is set with filename '%s' that returns %v"
|
||||
return fmt.Errorf(msg, err)
|
||||
return fmt.Errorf(msg, Get.TLSKey, err)
|
||||
}
|
||||
useTLS = true
|
||||
}
|
||||
|
||||
// Verify TLS_MIN_VERS is only (optionally) set if TLS is to be used.
|
||||
Get.TLSMinVers = tls.VersionTLS10
|
||||
if useTLS {
|
||||
if 0 < len(Get.TLSMinVersStr) {
|
||||
var err error
|
||||
if Get.TLSMinVers, err = tlsMinVersAsUint16(
|
||||
Get.TLSMinVersStr,
|
||||
); nil != err {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if 0 < len(Get.TLSMinVersStr) {
|
||||
msg := "value for 'TLS_MIN_VERS' is set but 'TLS_CERT' and 'TLS_KEY' are not"
|
||||
return errors.New(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +182,7 @@ func validate() error {
|
||||
|
||||
// envAsStr returns the value of the environment variable as a string if set.
|
||||
func envAsStr(key, fallback string) string {
|
||||
if value := os.Getenv(key); "" != value {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
@ -163,7 +191,7 @@ func envAsStr(key, fallback string) string {
|
||||
// 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 {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return strings.Split(value, ",")
|
||||
}
|
||||
return fallback
|
||||
@ -174,7 +202,7 @@ func envAsUint16(key string, fallback uint16) uint16 {
|
||||
// Retrieve the string value of the environment variable. If not set,
|
||||
// fallback is used.
|
||||
valueStr := os.Getenv(key)
|
||||
if "" == valueStr {
|
||||
if valueStr == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
@ -198,7 +226,7 @@ func envAsBool(key string, fallback bool) bool {
|
||||
// Retrieve the string value of the environment variable. If not set,
|
||||
// fallback is used.
|
||||
valueStr := os.Getenv(key)
|
||||
if "" == valueStr {
|
||||
if valueStr == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
@ -225,8 +253,26 @@ func strAsBool(value string) (result bool, err error) {
|
||||
result = true
|
||||
default:
|
||||
result = false
|
||||
msg := "Unknown conversion from string to bool for value '%s'"
|
||||
msg := "unknown conversion from string to bool for value '%s'"
|
||||
err = fmt.Errorf(msg, value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// tlsMinVersAsUint16 converts the intent of the passed value into an
|
||||
// enumeration for the crypto/tls package.
|
||||
func tlsMinVersAsUint16(value string) (result uint16, err error) {
|
||||
switch strings.ToLower(value) {
|
||||
case "tls10":
|
||||
result = tls.VersionTLS10
|
||||
case "tls11":
|
||||
result = tls.VersionTLS11
|
||||
case "tls12":
|
||||
result = tls.VersionTLS12
|
||||
case "tls13":
|
||||
result = tls.VersionTLS13
|
||||
default:
|
||||
err = fmt.Errorf("unknown value for TLS_MIN_VERS: %s", value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -160,27 +161,33 @@ func TestValidate(t *testing.T) {
|
||||
cert string
|
||||
key string
|
||||
prefix string
|
||||
minTLS string
|
||||
isError bool
|
||||
}{
|
||||
{"Valid paths w/prefix", validPath, validPath, prefix, false},
|
||||
{"Valid paths wo/prefix", validPath, validPath, empty, false},
|
||||
{"Empty paths w/prefix", empty, empty, prefix, false},
|
||||
{"Empty paths wo/prefix", empty, empty, empty, false},
|
||||
{"Mixed paths w/prefix", empty, validPath, prefix, true},
|
||||
{"Alt mixed paths w/prefix", validPath, empty, prefix, true},
|
||||
{"Mixed paths wo/prefix", empty, validPath, empty, true},
|
||||
{"Alt mixed paths wo/prefix", validPath, empty, empty, true},
|
||||
{"Invalid cert w/prefix", invalidPath, validPath, prefix, true},
|
||||
{"Invalid key w/prefix", validPath, invalidPath, prefix, true},
|
||||
{"Invalid cert & key w/prefix", invalidPath, invalidPath, prefix, true},
|
||||
{"Prefix missing leading /", empty, empty, "my/prefix", true},
|
||||
{"Prefix with trailing /", empty, empty, "/my/prefix/", true},
|
||||
{"Valid paths w/prefix", validPath, validPath, prefix, "", false},
|
||||
{"Valid paths wo/prefix", validPath, validPath, empty, "", false},
|
||||
{"Empty paths w/prefix", empty, empty, prefix, "", false},
|
||||
{"Empty paths wo/prefix", empty, empty, empty, "", false},
|
||||
{"Mixed paths w/prefix", empty, validPath, prefix, "", true},
|
||||
{"Alt mixed paths w/prefix", validPath, empty, prefix, "", true},
|
||||
{"Mixed paths wo/prefix", empty, validPath, empty, "", true},
|
||||
{"Alt mixed paths wo/prefix", validPath, empty, empty, "", true},
|
||||
{"Invalid cert w/prefix", invalidPath, validPath, prefix, "", true},
|
||||
{"Invalid key w/prefix", validPath, invalidPath, prefix, "", true},
|
||||
{"Invalid cert & key w/prefix", invalidPath, invalidPath, prefix, "", true},
|
||||
{"Prefix missing leading /", empty, empty, "my/prefix", "", true},
|
||||
{"Prefix with trailing /", empty, empty, "/my/prefix/", "", true},
|
||||
{"Valid paths w/min ok TLS", validPath, validPath, prefix, "tls11", false},
|
||||
{"Valid paths w/min bad TLS", validPath, validPath, prefix, "bad", true},
|
||||
{"Empty paths w/min ok TLS", empty, empty, prefix, "tls11", true},
|
||||
{"Empty paths w/min bad TLS", empty, empty, prefix, "bad", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
Get.TLSCert = tc.cert
|
||||
Get.TLSKey = tc.key
|
||||
Get.TLSMinVersStr = tc.minTLS
|
||||
Get.URLPrefix = tc.prefix
|
||||
err := validate()
|
||||
hasError := nil != err
|
||||
@ -461,3 +468,42 @@ func TestStrAsBool(t *testing.T) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestTlsMinVersAsUint16(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
result uint16
|
||||
isError bool
|
||||
}{
|
||||
{"Empty value", "", 0, true},
|
||||
{"Valid TLS1.0", "TLS10", tls.VersionTLS10, false},
|
||||
{"Valid TLS1.1", "tls11", tls.VersionTLS11, false},
|
||||
{"Valid TLS1.2", "tls12", tls.VersionTLS12, false},
|
||||
{"Valid TLS1.3", "tLS13", tls.VersionTLS13, false},
|
||||
{"Invalid TLS1.4", "tls14", 0, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := tlsMinVersAsUint16(tc.value)
|
||||
if result != tc.result {
|
||||
t.Errorf(
|
||||
"Expected %d for %s but got %d",
|
||||
tc.result, tc.value, result,
|
||||
)
|
||||
}
|
||||
if tc.isError && nil == err {
|
||||
t.Errorf(
|
||||
"Expected error for %s but got no error",
|
||||
tc.value,
|
||||
)
|
||||
} else if !tc.isError && nil != err {
|
||||
t.Errorf(
|
||||
"Expected no error for %s but got %v",
|
||||
tc.value, err,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@ -10,14 +11,46 @@ import (
|
||||
var (
|
||||
// These assignments are for unit testing.
|
||||
listenAndServe = http.ListenAndServe
|
||||
listenAndServeTLS = http.ListenAndServeTLS
|
||||
listenAndServeTLS = defaultListenAndServeTLS
|
||||
setHandler = http.HandleFunc
|
||||
)
|
||||
|
||||
var (
|
||||
server http.Server
|
||||
// Server options to be set prior to calling the listening function.
|
||||
// minTLSVersion is the minimum allowed TLS version to be used by the
|
||||
// server.
|
||||
minTLSVersion uint16 = tls.VersionTLS10
|
||||
)
|
||||
|
||||
// defaultListenAndServeTLS is the default implementation of the listening
|
||||
// function for serving with TLS enabled. This is, effectively, a copy from
|
||||
// the standard library but with the ability to set the minimum TLS version.
|
||||
func defaultListenAndServeTLS(
|
||||
binding, certFile, keyFile string, handler http.Handler,
|
||||
) error {
|
||||
if handler == nil {
|
||||
handler = http.DefaultServeMux
|
||||
}
|
||||
server := &http.Server{
|
||||
Addr: binding,
|
||||
Handler: handler,
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: minTLSVersion,
|
||||
},
|
||||
}
|
||||
return server.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
||||
|
||||
// SetMinimumTLSVersion to be used by the server.
|
||||
func SetMinimumTLSVersion(version uint16) {
|
||||
if version < tls.VersionTLS10 {
|
||||
version = tls.VersionTLS10
|
||||
} else if version > tls.VersionTLS13 {
|
||||
version = tls.VersionTLS13
|
||||
}
|
||||
minTLSVersion = version
|
||||
}
|
||||
|
||||
// ListenerFunc accepts the {hostname:port} binding string required by HTTP
|
||||
// listeners and the handler (router) function and returns any errors that
|
||||
// occur.
|
||||
@ -50,7 +83,7 @@ func WithReferrers(serveFile FileServerFunc, referrers []string) FileServerFunc
|
||||
func WithLogging(serveFile FileServerFunc) FileServerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, name string) {
|
||||
referer := r.Referer()
|
||||
if 0 == len(referer) {
|
||||
if len(referer) == 0 {
|
||||
log.Printf(
|
||||
"REQ from '%s': %s %s %s%s -> %s\n",
|
||||
r.RemoteAddr,
|
||||
@ -139,7 +172,7 @@ func TLSListening(tlsCert, tlsKey string) ListenerFunc {
|
||||
// passed list of referrers.
|
||||
func validReferrer(s []string, e string) bool {
|
||||
// Whitelisted referer list is empty. All requests are allowed.
|
||||
if 0 == len(s) {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -84,6 +85,28 @@ func teardown() (err error) {
|
||||
return os.RemoveAll("tmp")
|
||||
}
|
||||
|
||||
func TestSetMinimumTLSVersion(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value uint16
|
||||
expected uint16
|
||||
}{
|
||||
{"Too low", tls.VersionTLS10 - 1, tls.VersionTLS10},
|
||||
{"Lower bounds", tls.VersionTLS10, tls.VersionTLS10},
|
||||
{"Upper bounds", tls.VersionTLS13, tls.VersionTLS13},
|
||||
{"Too high", tls.VersionTLS13 + 1, tls.VersionTLS13},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
SetMinimumTLSVersion(tc.value)
|
||||
if tc.expected != minTLSVersion {
|
||||
t.Errorf("Expected %d but got %d", tc.expected, minTLSVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithReferrers(t *testing.T) {
|
||||
forbidden := http.StatusForbidden
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user