docker run --rm -it -v "$PWD:/var/www/html:ro" -p 8080:80 korc/onefile-websrv
Install 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/www
Check 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 :8080
go 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 websrv
websrv 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
<path>=<allowed_origin> (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-env
option
- the prefix of environment variable can be set via
-args-file
can be used to set a file to read arguments from, one per line-args-env
is processed before, soWEBSRV_ARG1=-args-file WEBSRV_ARG2=/some/path/file
can be used to set that file via environment as well-args-file
is a multi-arg, and can be subsequently added via files themselves
-map
option inhostname/path=handler:params
format can be used to map different paths to different handlers- optional
hostname
can be used for virtual hosting, empty value for all hosts
- optional
- additional
-map
entries add more mappings - supported
handler
types:file:
statically serve files from directory specified inparams
, or current working directory if emptywebdav:
WebDAV handler for directoryparams
, or memory-only storage if empty. Options between{..}
before path:ctype=<CONTENT_TYPE>
useCONTENT_TYPE
for file content type (was:-wdctype
)unsafe=1
usewebdav.Dir
instead 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
:
json
output data in JSON formatno-hdr
do not include request headers in JSON outputno-auth
do not include obtained roles info in jsonpath=x.y.z
show only defined path from JSON output (ex:path=TLS.peers[0].cn
)tls-cs
include 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
params
contains target where websocket is connected toHOST:PORT
ortcp:HOST:PORT
to connection via TCP to HOST:PORTtls:HOST:PORT
to connect using TLS over TCPunix:/PATH/SOCKET
for UNIX socketexec:COMMAND
to run COMMAND usingsh -c
- prefix
{sh=SHELL}
for alternate shell - prefix
{no-c=1}
for no-c
option after shell command - prefix
{sep=SEPARATOR}
to split string afterexec:
into arguments with SEPARATOR
- prefix
mux:ID
to share a websocket with other clients connected to the sameID
- supported options before path in
{...}
type=text
to change default message type to textre=REGEXP
to match grouped params like$1
in address from request URL path with regexp
params
must be complete URL starting withhttp:
,https:
,unix:
orwsprx:
wsprx
is 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:cert
andkey
options to set TLS backend client certificate and key files- forward client certificate data to backend in specified HTTP header:
fp-hdr
SHA256 fingerprintcn-hdr
subject CN attributesubj-hdr
subject in text formcert-hdr
hex-encoded client certificate
del-hdr=x-header-name:x-header2-name
to remove request header from clientset-hdr:x-header-name=VALUE
to set a request headerno-xff=1
to removeX-Forwarded-For
header containing client IP and do not sendX-Forwarded-Proto
no-gzip=1
do not sendAccept-Encoding: gzip
verify=0
to allow making insecure HTTPS requestsfix-ws-hdr=1
make 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.
params
is a internal name for this proxy. options:{listener=1}
make this a server socket forws_proxy
endpoint{re=...}
searches URL.Path, andparams
as 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/program
AAAA
will be copied from host env,BBBB
will be set to123
, program will be executed with 2 arguments:--dir
and/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=1
decode 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>
: setkey
in the issued claim tovalue
- if
key
ends with_claim
, that is removed - if
key
ands 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=@.*/@@}
assignsaud
to a filename in the path
- any character be used instead of
value
can be value string, or a value solved from request (cf.Parameters from request
section below)- also,
ts:<format>
for unix timestamp based on following:- basic format is
+duration
or-duration
to add or subtract from current time - can prefix duration with
today
to make relation based on start of the day in server localtimeq:
get duration relative to issue time from URL query
- basic format is
- also,
exp
is 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 fromtarget
query parameter, valid-from time fromnbf
query 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
,sub
in claims from client's x509 certificate subjectCN
attribute, 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 datacn
subject common namesubj
full subjectfp
certificate sha256 fingerprintcrt
base64-encoded certificate
q:<name>
value of URL query parameter<name>
post:<name>
POST form valuehdr:<name>
HTTP request headerenv:<name>
server environment variableauth:<value>
bearer
Authorization Bearer valuebasic-usr
,basic-pwd
respective Basic auth user/password values
req:
a value from request parameterraddr
client remote address (with port number)rip
client remote IPhost
requested Hostpath
URL 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>
.req
data 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 stringstob
convert string to bytesatoi
convert string to integermap <key> <value> ...
create a map of valuesjson
convert map to json []byte value
- ex:
tmpl:env:ENV_TEMPLATE_VAR
, withENV_TEMPLATE_VAR
containingeyJhbGciOiJIUzI1NiJ9.{{b64url (json (map "sub" (rp "q:sub" .req)))}}.{{rp "q:sig" .req}}
- construct string from template, which is parameter defined in
-acl
option will define mapping between URL paths and required rolespath_regexp
is 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
-auth
option can be used to add new roles- multiple roles can be assigned with one method
auth
value is method-specific- can use environment variables in form of
${variable_name}
inauth
part (presence in environment is mandatory) - possible values for
method
parameterBasic
- HTTP Basic authentication (WEAK security)
auth
is a Base64-encoded value ofusername:password
Cert
- SSL Client X.509 certificate authentication
auth
as hex-encoded value of SHA-256 hash of certificate's binary (DER) data- if
auth
starts withfile:
, certificate is read from file on disk and it's hash is used instead
CertBy
auth
can be hex-encoded value of client CA certificate's binaryfile:
in the beginning ofauth
will load CA certificate from file
CertKeyHash
auth
is hex-encoded SHA256 hash of client certificate's public key (SHA256 of ASN1 fromssh-keygen -e -m pkcs8
andcerttool --pubkey-info
)file:
prefix make keys to be loaded from specified file instead- can read OpenSSH
authorized_keys
withssh-rsa
keys, and PEM files withPUBLIC KEY
orCERTIFICATE
data
- can read OpenSSH
JWT
auth
value 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 request
for<req_param>
format- can use
src_xxx
to arbitrarily define multiple sources
- can use
no-bearer=1
do not checkAuthorization: Bearer ...
header by defaulths=1
useauth
value as secret key for HMAC signatureb64=1
decodeauth
value with base64aud=<type>:<value>
oraud=path
- determine what is going to be checked foraud
"Audience" claimtype
andvalue
use same syntax as claim string values in JWT handler (nots:
timestamp).
aud-re=<regexp>
aud value (path
by 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:
JWTSecret
DEPRECATED in favor ofJWT
- checks if JWT from
Authentication: Bearer
header is signed by specific authority auth
contains authority's shared secret- can prefix
auth
with{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
auth
is 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-For
header for client IP
JWTFilePat
DEPRECATEDauth
specifies 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-value
e
is 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/**.jwt
and access to/adm/test.123.html
will 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
.jwt
files, auth is applied only when path requires authentication
File
- file existence check
- options available with
{..}
prefix:nofile
inverse condition, and succeed if file does NOT existre-path
treat auth value as regular expression, andre-path
as 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
{..}
:method
use something else thanGET
need-hdr
colon-separated list of headers required to even start testing (ex.Authorization
)cp-hdr
colon-separated list of headers to copy from original requestset-hdr:xxx
set headerxxx
to a specified value when making request- value can start with
tmpl:
, see above
- value can start with
success
response 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"