docker run --rm -it -v "$PWD:/var/www/html:ro" -p 8080:80 korc/onefile-websrvInstall Go
go install github.com/korc/onefile-websrv@latest
~/go/bin/onefile-websrv -listen :8080(replace example.com with your real public hostname)
go install github.com/korc/onefile-websrv@latest
mkdir acme-certs
sudo ~/go/bin/onefile-websrv -listen :443 -acmehost example.com -cert $PWD/acme-certs -map /=file:/var/wwwCheck out systemd approach below for more secure setup.
Serving content from /data/web/html:
docker build -t websrv https://github.com/korc/onefile-websrv.git
docker run --name websrv -u 33:33 -p 80:8080 -v /data/web:/var/www websrv -listen :8080go install github.com/korc/onefile-websrv@latest
install go/bin/onefile-websrv /usr/local/bin/websrv
curl https://raw.githubusercontent.com/korc/onefile-websrv/master/websrv.service | sudo tee /etc/systemd/system/websrv.service
sudo vi /etc/systemd/system/websrv.service # modify command line options for your needs
systemctl daemon-reload && systemctl enable websrv && systemctl start websrv
systemctl status websrvwebsrv can change user id after start (required for low-level port listen and chroot), but unfortunately that's currently broken in Golang's Linux implementation (some process threads might remain running as root). If you don't want to run as root (not recommended anyway), and want to use those high-privileged functions, then it's best to set appropriate capabilities(7) with setcap(8) program (ex: setcap cap_net_bind_service,cap_sys_chroot=ep websrv), and then run as target user (ex: www-data).
websrv -h
-acl value
[{<methods..>}]<path_regexp>=<role>[+<role2..>]:<role..> (multi-arg)
-acmehost string
Host names (comma-separated) allowed for automatically issued ([ACME](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment)) certificates, -cert will be cache dir
-acmehttp string
Listen address for ACME http-01 challenge (default ":80")
-args-env string
read arguments from environment <prefix>1..<prefix>N (default "WEBSRV_ARG")
-args-file string
read args from file, one per line
-auth value
[<role>[+<role2>]=]<method>:<auth> (multi-arg)
-cert string
SSL certificate file or autocert cache dir
-cert-fallback
Certificate file to use if ACME fails
-chroot string
chroot() to directory after start
-key string
SSL key file
-listen string
Listen ip:port or /path/to/unix-socket (default ":80")
-loglevel string
Max log level (one of FATAL, ERROR, WARNING, INFO, VERBOSE, DEBUG) (default "info")
-map value
[<vhost>]/<path>=<handler>:[<params>] (multi-arg, default '/=file:')
-user string
Switch to user (NOT RECOMMENDED)
-cors value
[{host_re=<vhost_re>,methods=<method:..>,headers=<hdr:..>,creds=<str>}]<path_re>=<allowed_origin_re> (multi-arg)
-wstmout int
Websocket alive check timer in seconds (default 60)
-reqlog string
URL to log request details to (supports also unix:///dir/unix-socket:/path URLs)
Options marked with multi-arg can be specified multiple times on command-line, and will add to previous configuration. Other options are meant to be set only once.
- by default, arguments can be also specified through environment variables
WEBSRV_ARG1..WEBSRV_ARG<n>- the prefix of environment variable can be set via
-args-envoption
- the prefix of environment variable can be set via
-args-filecan be used to set a file to read arguments from, one per line-args-envis processed before, soWEBSRV_ARG1=-args-file WEBSRV_ARG2=/some/path/filecan be used to set that file via environment as well-args-fileis a multi-arg, and can be subsequently added via files themselves
-mapoption inhostname/path=handler:paramsformat can be used to map different paths to different handlers- optional
hostnamecan be used for virtual hosting, empty value for all hosts - starting with
@will be saved for later use (like.extoption for file: handlers)
- optional
- additional
-mapentries add more mappings - supported
handlertypes:file:statically serve files from directory specified inparams, or current working directory if empty{..}options can include404=/some/other/pathor404=@saved_handler/or.ext=@saved_handler/
webdav:WebDAV handler for directoryparams, or memory-only storage if empty. Options between{..}before path:ctype=<CONTENT_TYPE>useCONTENT_TYPEfor file content type (was:-wdctype)unsafe=1usewebdav.Dirinstead of symlink-checking (more safe) custom FS provider
websocket:(aliasws) connects a websocket to TCP or UNIX sockethttp:pass request to HTTP backenddebug:client request debug- comma-separated options available after
:jsonoutput data in JSON formatno-hdrdo not include request headers in JSON outputno-authdo not include obtained roles info in jsonpath=x.y.zshow only defined path from JSON output (ex:path=TLS.peers[0].cn)tls-csinclude full tls.ConnectionState in JSON output
- comma-separated options available after
cgi:Run a CGI script specified byparams.jwt:generate JWT tokenws-proxy:WebSocket proxy service, to be used withws_proxy
paramscontains target where websocket is connected toHOST:PORTortcp:HOST:PORTto connection via TCP to HOST:PORTtls:HOST:PORTto connect using TLS over TCPunix:/PATH/SOCKETfor UNIX socketexec:COMMANDto run COMMAND usingsh -c- prefix
{sh=SHELL}for alternate shell - prefix
{no-c=1}for no-coption after shell command - prefix
{sep=SEPARATOR}to split string afterexec:into arguments with SEPARATOR
- prefix
mux:IDto share a websocket with other clients connected to the sameID
- supported options before path in
{...}type=textto change default message type to textre=REGEXPto match grouped params like$1in address from request URL path with regexp
paramsmust be complete URL starting withhttp:,https:,unix:orwsprx:wsprxis handled byws-proxy:mapped server, hostname component as name of it (example below)
- supports unix sockets in the format of
unix:///path/to/unix-socket:/urlpath - comma-separated options between
{...}before URL:certandkeyoptions to set TLS backend client certificate and key files- forward client certificate data to backend in specified HTTP header:
fp-hdrSHA256 fingerprintcn-hdrsubject CN attributesubj-hdrsubject in text formcert-hdrhex-encoded client certificate
del-hdr=x-header-name:x-header2-nameto remove request header from clientset-hdr:x-header-name=VALUEto set a request headerno-xff=1to removeX-Forwarded-Forheader containing client IP and do not sendX-Forwarded-Protono-gzip=1do not sendAccept-Encoding: gzipverify=0to allow making insecure HTTPS requestsfix-ws-hdr=1make sureSec-Websocket-*headers are sent asSec-WebSocket-*ca=<filename>to set remote RootCAs PEM file
Goal: passing backend http server to external front-end. External server possibly publicly accessible, backend possibly in the internal network (a'la ngrok).
- front-end web service:
onefile-websrv -map /=http:wsprx://backend -map /.srv=ws-proxy:{listener=1}backend - back-end web service:
onefile-websrv -map /=file:/data/web/html -listen 127.0.0.1:8000 - back-end to front-end connector (from
cmd/ws_proxy):ws_proxy -ws ws://front-end-srv/.srv -connect 127.0.0.1:8000
NOTE: If front-end is accessible from public internet, you should additionally protect /.srv endpoint properly with -acl options.
paramsis a internal name for this proxy. options:{listener=1}make this a server socket forws_proxyendpoint{re=...}searches URL.Path, andparamsas template for name- ex:
-map /prx/=ws-proxy:{re=^/prx/(.+)}prx-$1
- ex:
Includes client certificate hash, which can be used for -auth option's Cert method
Before program name, can specify environment and args with { }
{AAAA,BBBB=123,arg:--dir,arg:/var/www}/usr/lib/cgi/programAAAAwill be copied from host env,BBBBwill be set to123, program will be executed with 2 arguments:--dirand/var/www
- secret source specified by
params - source can be prefixed with
file:to read source from file, orenv:to read from environment variable - following comma-separated options can prefixed with
{...}before sourceb64=1decode secret from base64alg={ES256|ES384|ES512|RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512}generation algorithm- default algorithm is
HS256 RS*andPS*source must be PEM-encoded RSA private key (PKCS#1)ES*source is EC-DSA key
- default algorithm is
<key>=<value>: setkeyin the issued claim tovalue- if
keyends with_claim, that is removed - if
keyands with<claim>_repl, it must contain sed-like string replacement in@regex@replacement@format, which will be applied to the claim<claim>- any character be used instead of
@ - ex:
{aud=req:path,aud_repl=@.*/@@}assignsaudto a filename in the path
- any character be used instead of
valuecan be value string, or a value solved from request (cf.Parameters from requestsection below)- also,
ts:<format>for unix timestamp based on following:- basic format is
+durationor-durationto add or subtract from current time - can prefix duration with
todayto make relation based on start of the day in server localtimeq:get duration relative to issue time from URL query
- basic format is
- also,
expis by default set tots:+5m, useexp=with empty value to explicitly disable JWT expiration
- if
-map /acl/get=jwt:{b64=1,exp=ts:+1h,aud=q:target,nbf=ts:q:nbf}bXktc2VjcmV0- HS256 signed with shared secret
my-secret, 1 hour expiration, audience fromtargetquery parameter, valid-from time fromnbfquery parameter (default=time of request)
- HS256 signed with shared secret
-map /login=jwt:{exp=ts:today+25h,sub=crt:cn,alg=ES256}file:jwt.key- signed with EC-DSA key in
jwt.key,subin claims from client's x509 certificate subjectCNattribute, expiring on next day at 1am
- signed with EC-DSA key in
Several options support retrieving a value from request. The syntax is as following:
str:plain string followingstr:crt:client certificate datacnsubject common namesubjfull subjectfpcertificate sha256 fingerprintcrtbase64-encoded certificate
q:<name>value of URL query parameter<name>post:<name>POST form valuehdr:<name>HTTP request headerenv:<name>server environment variableauth:<value>bearerAuthorization Bearer valuebasic-usr,basic-pwdrespective Basic auth user/password values
req:a value from request parameterraddrclient remote address (with port number)ripclient remote IPhostrequested HostpathURL path
jwt:<claim>:<src>- parse JWT from
<src>(same syntax as above), and retrieve value of claim named<claim>. The name is assumed to be must be URL-escaped.
- parse JWT from
unescape:<src>- solve
<src>via request, and url-unescape it - if
<src>does not contain:, it is assumed to be verbatim string
- solve
tmpl:<src>- construct string from template, which is parameter defined in
<src> .reqdata is set to current request- additional functions defined
rp <param> <req>function is added to retrieve other parameters from requestb64 <bytes>,b64url <bytes>,b64dec <str>,b64decurl <str>encode/decode with normal/url encoding. decode returns values in []byte type, encode in stringstobconvert string to bytesatoiconvert string to integermap <key> <value> ...create a map of valuesjsonconvert map to json []byte value
- ex:
tmpl:env:ENV_TEMPLATE_VAR, withENV_TEMPLATE_VARcontainingeyJhbGciOiJIUzI1NiJ9.{{b64url (json (map "sub" (rp "q:sub" .req)))}}.{{rp "q:sig" .req}}
- construct string from template, which is parameter defined in
-acloption will define mapping between URL paths and required rolespath_regexpis defined by regular expression, like^/admin/- add
?before regex (ex:?^/xyz/.*session_id=.*) to check full RequestURI including query part (not only Path) for match
- add
- in curly braces before path regexp can set comma-separated params
host:<hostname>to apply only for particular virtual hosts (req withHost: hostname)GET,POST, etc. to filter by HTTP methodsonfail:<URL>redirect to URL when auth fails. can use@param@placeholders to solve into url-escaped values from request (ex:@req:host@)- use
file:URL to serve file instead of redirection
- use
:separates alternate roles (OR operation)+makes all specified roles to be required (AND operation)- can be used to implement multi-factor auth
-authoption can be used to add new roles- multiple roles can be assigned with one method
authvalue is method-specific- can use environment variables in form of
${variable_name}inauthpart (presence in environment is mandatory) - possible values for
methodparameterBasic- HTTP Basic authentication (WEAK security)
authis a Base64-encoded value ofusername:password
Cert- SSL Client X.509 certificate authentication
authas hex-encoded value of SHA-256 hash of certificate's binary (DER) data- if
authstarts withfile:, certificate is read from file on disk and it's hash is used instead
CertByauthcan be hex-encoded value of client CA certificate's binaryfile:in the beginning ofauthwill load CA certificate from file
CertKeyHashauthis hex-encoded SHA256 hash of client certificate's public key (SHA256 of ASN1 fromssh-keygen -e -m pkcs8andcerttool --pubkey-info)file:prefix make keys to be loaded from specified file instead- can read OpenSSH
authorized_keyswithssh-rsakeys, and PEM files withPUBLIC KEYorCERTIFICATEdata
- can read OpenSSH
JWTauthvalue is RSA or ECDSA private or public key in PEM format, unless{hs=1}option is given- use
env:<varname>orfile:<filename>to read value from environment or file
- use
{..}options valuessrc=<req_param>define jwt source, cf.Parameters from requestfor<req_param>format- can use
src_xxxto arbitrarily define multiple sources
- can use
no-bearer=1do not checkAuthorization: Bearer ...header by defaulths=1useauthvalue as secret key for HMAC signatureb64=1decodeauthvalue with base64aud=<type>:<value>oraud=path- determine what is going to be checked foraud"Audience" claimtypeandvalueuse same syntax as claim string values in JWT handler (nots:timestamp).
aud-re=<regexp>aud value (pathby default, can be overwritten byaud=) will be matched against regexp, if subgroups found then first group will be used as valuetest=claim:<name>:<test>ortest_<xxx>=claim:<name>:<test><name>is a url-escaped name of a claim to test<test>is a plain string, or a request parameter if contains:
JWTSecretDEPRECATED in favor ofJWT- checks if JWT from
Authentication: Bearerheader is signed by specific authority authcontains authority's shared secret- can prefix
authwith{cookie|header|query=XXX}to additionally look JWT token from specified cookie, header or query parameter namedXXX. multiple locations have to be separated with comma.- ex:
-auth viewer=JWTSecret:{cookie=viewacces,query=va}MySecretJWTKey
- ex:
- checks if JWT from
IPRange- checks client's remote IP
authis IP address with network mask length in format ofip/masklen- can start with
file:to read ip ranges from file, lines starting with#are ignored {xff=..}colon-separated list of reverse proxy IP's which can setX-Forwarded-Forheader for client IP
JWTFilePatDEPRECATEDauthspecifies file (pattern) containing accepted JWT tokens signed with:- secrets, in format of
hash:url-base64-encoded-secret - RSA public keys, in format of
rsa:base64-encoded-n-valueeis assumed to be0x10001
- secrets, in format of
- if letters "
**" are found inside filename, they are replaced with pattern constructed from:- URL path, URL path with extensions of last element removed (one-by-one), and each path component removed one-by-one from the end
- Ex:
-acl ^/adm/=xxx -auth xxx=JWTFilePat:/data/webauth/**.jwtand access to/adm/test.123.htmlwill result in checking of files/data/webauth/adm/test.123.html.jwt/data/webauth/adm/test.123.jwt/data/webauth/adm/test.jwt/data/webauth/adm.jwt
- because of cost associated with checking for
.jwtfiles, auth is applied only when path requires authentication
File- file existence check
- options available with
{..}prefix:nofileinverse condition, and succeed if file does NOT existre-pathtreat auth value as regular expression, andre-pathas pathname with$<nr>subgroup expansion pattern- ex:
-map /=webdav:/data/ -auth nofile=File:{no-file=1,re-path=/data/$1}/(.+) -auth ip4all=IPRange:0.0.0.0/0 -acl {PUT}^/=nofile -acl {GET}^/=ip4all -acl ^/=nobody- create a WebDAV mapping for
/data/, where you can upload only new files
- create a WebDAV mapping for
- ex:
HTTP- make a HTTP sub-request to URL at parameter
- if value starts with
tmpl:, it will be processed as template in Parameters from request, documented above
- if value starts with
- options set with
{..}:methoduse something else thanGETneed-hdrcolon-separated list of headers required to even start testing (ex.Authorization)cp-hdrcolon-separated list of headers to copy from original requestset-hdr:xxxset headerxxxto a specified value when making request- value can start with
tmpl:, see above
- value can start with
successresponse code which to be considered success
- make a HTTP sub-request to URL at parameter
GeoIP- look up record from MaxMind's GeoIP database and check record value
- options via
{..}file=<filename.mmdb>set database, MANDATORYrprx=<ip1>:<ip2..>list of reverse proxy servers forX-Forwarded-For, from closest to farthestkey=<a>:<b..>set record value key, defaults tocountry:iso_code
- example usage:
-auth "jpn=GeoIP:{file=/usr/share/GeoIP/GeoLite2-Country.mmdb,rprx=127.0.0.1}JP"