Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3550e790df |
@@ -39,11 +39,11 @@ Five words name the moving parts.
|
|||||||
The part of webhooker that answers requests to `/h/{uuid}` is the receiver. It
|
The part of webhooker that answers requests to `/h/{uuid}` is the receiver. It
|
||||||
stores the event and one pending delivery per active target, then answers `200`.
|
stores the event and one pending delivery per active target, then answers `200`.
|
||||||
|
|
||||||
Delivery is at least once, not exactly once. If a delivery reaches the target
|
Delivery is at least once, not exactly once. A delivery can reach its target
|
||||||
but webhooker cannot record that outcome, it sends the delivery again later. A
|
more than once. If a delivery reaches the target but webhooker cannot record
|
||||||
delivery in flight when the service stops is also sent again at the next start.
|
that outcome, it sends the delivery again later. A delivery in flight when the
|
||||||
webhooker adds no delivery identifier of its own to the request, so make your
|
service stops is also sent again at the next start. webhooker adds no delivery
|
||||||
targets tolerate a duplicate.
|
identifier of its own to the request, so make your targets tolerate a duplicate.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -57,7 +57,9 @@ DATA_DIR=../webhooker-data make dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
`DATA_DIR` is where webhooker keeps its databases. It defaults to
|
`DATA_DIR` is where webhooker keeps its databases. It defaults to
|
||||||
`/var/lib/webhooker`, so point it at a directory you can write to.
|
`/var/lib/webhooker`, so point it at a directory you can write to. On a host
|
||||||
|
without Docker, `make build` writes the program to `bin/webhooker`, and running
|
||||||
|
that file starts webhooker.
|
||||||
|
|
||||||
The first start creates the `admin` account and prints its password once, in a
|
The first start creates the `admin` account and prints its password once, in a
|
||||||
ruled box on standard output:
|
ruled box on standard output:
|
||||||
@@ -138,6 +140,8 @@ Under **Targets**, **Add** asks for the type first, then that type's settings.
|
|||||||
`http` and `slack` targets also take **Delivery attempts**: how many times each
|
`http` and `slack` targets also take **Delivery attempts**: how many times each
|
||||||
delivery is tried in all. The default is 1. The most is 20. A target's type
|
delivery is tried in all. The default is 1. The most is 20. A target's type
|
||||||
cannot be changed. Deleting an archive target leaves its files on disk.
|
cannot be changed. Deleting an archive target leaves its files on disk.
|
||||||
|
**Deactivate** and **Activate** on the webhook page switch a target off and on,
|
||||||
|
and new events skip a target that is off.
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
@@ -147,11 +151,11 @@ body** link. **Full Event Log** shows the 50 newest events with their deliveries
|
|||||||
and every attempt. **Failed** and **Pending** narrow it to events with a failed
|
and every attempt. **Failed** and **Pending** narrow it to events with a failed
|
||||||
delivery, or with one still pending or retrying.
|
delivery, or with one still pending or retrying.
|
||||||
|
|
||||||
**Replay** sits beside each finished delivery. It creates a new delivery of the
|
A finished delivery has a **Replay** button while its target exists. **Replay**
|
||||||
same event to the same target, with the target's settings as they stand now.
|
creates a new delivery of the same event to the same target, with the target's
|
||||||
**Resubmit** sits beside each event. It stores a copy of the event as a new
|
settings as they stand now. **Resubmit** sits beside each event. It stores a
|
||||||
event and delivers it to every target that is active now. Both actions are
|
copy of the event as a new event and delivers it to every target that is active
|
||||||
limited to 30 per minute per client.
|
now. Both actions are limited to 30 per minute per client.
|
||||||
|
|
||||||
### Profile and settings
|
### Profile and settings
|
||||||
|
|
||||||
@@ -177,7 +181,8 @@ its published port.
|
|||||||
The app runs as user `webhooker`. The image's start-up script is
|
The app runs as user `webhooker`. The image's start-up script is
|
||||||
`deploy/docker-entrypoint.sh`. It gives `DATA_DIR` and its contents to that user
|
`deploy/docker-entrypoint.sh`. It gives `DATA_DIR` and its contents to that user
|
||||||
before the app starts. So a host directory can be mounted as it is. webhooker
|
before the app starts. So a host directory can be mounted as it is. webhooker
|
||||||
creates every SQLite file with mode `0600`.
|
creates every SQLite file readable and writable by that user alone (mode
|
||||||
|
`0600`).
|
||||||
|
|
||||||
### Running under upaas
|
### Running under upaas
|
||||||
|
|
||||||
@@ -192,9 +197,10 @@ Read it with `docker logs` and the name upaas gave the container.
|
|||||||
|
|
||||||
webhooker speaks plain HTTP. It does not handle TLS, the encryption behind
|
webhooker speaks plain HTTP. It does not handle TLS, the encryption behind
|
||||||
HTTPS: it holds no certificate and accepts no HTTPS connections. The reverse
|
HTTPS: it holds no certificate and accepts no HTTPS connections. The reverse
|
||||||
proxy holds the certificate and reaches webhooker over a private address. On a
|
proxy holds the certificate and reaches webhooker over a private address. A bare
|
||||||
bare host that is loopback, the address only this host can reach. For the image
|
host is a host without Docker. On a bare host that is loopback, the address only
|
||||||
it is the Docker bridge, the private network Docker puts containers on.
|
this host can reach. For the image it is the Docker bridge, the private network
|
||||||
|
Docker puts containers on.
|
||||||
|
|
||||||
Four things must be right.
|
Four things must be right.
|
||||||
|
|
||||||
@@ -264,10 +270,11 @@ the server does and refuses while a running instance holds it.
|
|||||||
DATA_DIR=/var/lib/webhooker webhooker resetpw -generate admin
|
DATA_DIR=/var/lib/webhooker webhooker resetpw -generate admin
|
||||||
```
|
```
|
||||||
|
|
||||||
Without `-generate`, the new password is read from standard input. It must be at
|
`-generate` makes up a random password and prints it. Without `-generate`, the
|
||||||
least 8 characters. It is never taken as a command-line argument, because other
|
new password is read from standard input. It must be at least 8 bytes long. It
|
||||||
accounts on the host can read those. The command creates nothing: a missing
|
is never taken as a command-line argument, because other accounts on the host
|
||||||
`DATA_DIR`, a missing `webhooker.db` or an unknown username is an error.
|
can read those. The command creates nothing: a missing `DATA_DIR`, a missing
|
||||||
|
`webhooker.db` or an unknown username is an error.
|
||||||
|
|
||||||
In a container, run the binary at `/app/webhooker` with the stopped container's
|
In a container, run the binary at `/app/webhooker` with the stopped container's
|
||||||
volume mounted:
|
volume mounted:
|
||||||
@@ -281,8 +288,9 @@ docker run --rm -v /path/to/data:/var/lib/webhooker \
|
|||||||
|
|
||||||
All state lives in `DATA_DIR`. Back up the whole directory. It holds:
|
All state lives in `DATA_DIR`. Back up the whole directory. It holds:
|
||||||
|
|
||||||
- the main database, `webhooker.db`: the session key, the `admin` account's
|
- the main database, `webhooker.db`: the session key (which signs the session
|
||||||
password hash, and the webhooks, entrypoints and targets;
|
cookies), the `admin` account's password hash, and the webhooks, entrypoints
|
||||||
|
and targets;
|
||||||
- one event database per webhook, `events-{webhook_uuid}.db`: its events,
|
- one event database per webhook, `events-{webhook_uuid}.db`: its events,
|
||||||
deliveries and attempts;
|
deliveries and attempts;
|
||||||
- the files of each archive target;
|
- the files of each archive target;
|
||||||
@@ -352,9 +360,8 @@ writes it. The reverse proxy's access log does hold it, in the request path.
|
|||||||
file's type;
|
file's type;
|
||||||
- `X-Frame-Options: DENY`, which stops other sites showing these pages in a
|
- `X-Frame-Options: DENY`, which stops other sites showing these pages in a
|
||||||
frame;
|
frame;
|
||||||
- a Content-Security-Policy (CSP), the header that tells the browser where
|
- `Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'`,
|
||||||
scripts and styles may come from, set to
|
which tells the browser where scripts and styles may come from;
|
||||||
`default-src 'self'; script-src 'self'; style-src 'self'`;
|
|
||||||
- `Referrer-Policy: strict-origin-when-cross-origin`, which tells another
|
- `Referrer-Policy: strict-origin-when-cross-origin`, which tells another
|
||||||
site only which site a visitor came from, not which page;
|
site only which site a visitor came from, not which page;
|
||||||
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`, which
|
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`, which
|
||||||
@@ -369,7 +376,7 @@ password is never answered `429`. Failures are counted per client and submitted
|
|||||||
username. From the fifth failure within a minute, each failure is answered
|
username. From the fifth failure within a minute, each failure is answered
|
||||||
`429`. At most two password checks run at once; a request that gets no turn
|
`429`. At most two password checks run at once; a request that gets no turn
|
||||||
within 5 seconds is answered `503`. So guessing speed is bounded by the limit of
|
within 5 seconds is answered `503`. So guessing speed is bounded by the limit of
|
||||||
two checks at a time, not by the failure count. Choose a long random admin
|
two checks at a time, not by the failure count. Choose a long random `admin`
|
||||||
password, or rate-limit `POST /pages/login` at the reverse proxy.
|
password, or rate-limit `POST /pages/login` at the reverse proxy.
|
||||||
|
|
||||||
### Delivery destinations
|
### Delivery destinations
|
||||||
@@ -450,18 +457,20 @@ number with a unit, such as `30s`, `15m` or `24h`.
|
|||||||
### `WEBHOOKER_ENVIRONMENT`
|
### `WEBHOOKER_ENVIRONMENT`
|
||||||
|
|
||||||
`dev` or `prod`. Default `prod`. This controls one thing: CORS, the headers that
|
`dev` or `prod`. Default `prod`. This controls one thing: CORS, the headers that
|
||||||
let a page on another site call this one from a browser. In `dev`, every origin
|
let a page on another site call this one from a browser. An origin is a site as
|
||||||
is allowed. In `prod`, no CORS headers are sent.
|
a browser names it: scheme, host and port. In `dev`, every origin is allowed. In
|
||||||
|
`prod`, no CORS headers are sent.
|
||||||
|
|
||||||
### `PORT`
|
### `PORT`
|
||||||
|
|
||||||
The port the listener binds. Default `8080`. Docker marks the image's container
|
The port webhooker listens on. Default `8080`. Docker marks the image's
|
||||||
healthy by a check every 30 seconds. The check requests port `8080` whatever
|
container healthy by a check every 30 seconds. The check requests port `8080`
|
||||||
`PORT` says, so a container started with another `PORT` reports unhealthy.
|
whatever `PORT` says, so a container started with another `PORT` reports
|
||||||
|
unhealthy.
|
||||||
|
|
||||||
### `BIND_ADDRESS`
|
### `BIND_ADDRESS`
|
||||||
|
|
||||||
The IP address the listener binds. Default `127.0.0.1`. The Docker image sets
|
The IP address webhooker listens on. Default `127.0.0.1`. The Docker image sets
|
||||||
`0.0.0.0`. It must be an IP address literal: `localhost` and a value with a port
|
`0.0.0.0`. It must be an IP address literal: `localhost` and a value with a port
|
||||||
are refused. `0.0.0.0` listens on IPv4 only; `::` listens on IPv6 and IPv4.
|
are refused. `0.0.0.0` listens on IPv4 only; `::` listens on IPv6 and IPv4.
|
||||||
|
|
||||||
@@ -480,7 +489,7 @@ directory and the lock file.
|
|||||||
|
|
||||||
`true` or `false`. Default `false`. It accepts the spellings Go's
|
`true` or `false`. Default `false`. It accepts the spellings Go's
|
||||||
`strconv.ParseBool` accepts; `yes` and `on` are refused. `DEBUG=true` lowers the
|
`strconv.ParseBool` accepts; `yes` and `on` are refused. `DEBUG=true` lowers the
|
||||||
log level to `DEBUG`, which adds every SQL statement, with `?` in place of each
|
log level to `DEBUG`, which adds SQL statements, with `?` in place of each
|
||||||
value, and other diagnostic lines.
|
value, and other diagnostic lines.
|
||||||
|
|
||||||
### `METRICS_USERNAME` and `METRICS_PASSWORD`
|
### `METRICS_USERNAME` and `METRICS_PASSWORD`
|
||||||
@@ -530,10 +539,10 @@ private ranges. A set value replaces the default.
|
|||||||
The list decides whose `X-Forwarded-For` header is believed. `X-Forwarded-For`
|
The list decides whose `X-Forwarded-For` header is believed. `X-Forwarded-For`
|
||||||
is the header a proxy uses to pass on the address of the client it is serving.
|
is the header a proxy uses to pass on the address of the client it is serving.
|
||||||
It is read only when the connecting machine is inside the list. webhooker never
|
It is read only when the connecting machine is inside the list. webhooker never
|
||||||
reads `X-Real-IP` or `True-Client-IP`, two other headers with the same job. If
|
reads `X-Real-IP` or `True-Client-IP`, two other headers with the same job. Some
|
||||||
any client can reach webhooker, or the proxy in front of it, from a private
|
clients may reach webhooker, or the proxy in front of it, from a private
|
||||||
address, set the list to the proxy's address alone. Otherwise those clients can
|
address. If so, set the list to the proxy's address alone. Otherwise those
|
||||||
choose their own `X-Forwarded-For` and bypass every rate limit.
|
clients can choose their own `X-Forwarded-For` and bypass every rate limit.
|
||||||
|
|
||||||
### Allowing egress to your own network
|
### Allowing egress to your own network
|
||||||
|
|
||||||
@@ -542,9 +551,9 @@ that delivery targets may reach although the SSRF guard refuses them by default.
|
|||||||
Default empty. Example: `10.0.0.0/8, 172.17.0.0/16`. When the list is set,
|
Default empty. Example: `10.0.0.0/8, 172.17.0.0/16`. When the list is set,
|
||||||
startup logs it at `WARN`.
|
startup logs it at `WARN`.
|
||||||
|
|
||||||
Anyone with the admin account can make webhooker send requests to every network
|
Anyone with the `admin` account can make webhooker send requests to every
|
||||||
you list, and read the responses in **Full Event Log**. List the smallest blocks
|
network you list, and read the responses in **Full Event Log**. List the
|
||||||
that cover your destinations, and never `0.0.0.0/0` or `::/0`.
|
smallest networks that cover your destinations, and never `0.0.0.0/0` or `::/0`.
|
||||||
|
|
||||||
These addresses stay blocked whatever you list. They are link-local space, the
|
These addresses stay blocked whatever you list. They are link-local space, the
|
||||||
unspecified addresses and cloud metadata endpoints. The unspecified addresses
|
unspecified addresses and cloud metadata endpoints. The unspecified addresses
|
||||||
@@ -563,9 +572,9 @@ addresses are:
|
|||||||
- `0.0.0.0/32` and `::/128`;
|
- `0.0.0.0/32` and `::/128`;
|
||||||
- `::a9fe:a9fe/128` and `64:ff9b::a9fe:a9fe/128`.
|
- `::a9fe:a9fe/128` and `64:ff9b::a9fe:a9fe/128`.
|
||||||
|
|
||||||
This list is not every cloud's metadata address. If yours is not here, do not
|
Some clouds' metadata addresses are not on this list. If yours is not here, do
|
||||||
list the block that contains it. Azure's public `168.63.129.16` is refused by
|
not list the network that contains it. Azure's public metadata address,
|
||||||
default but is reopened by a list that covers it.
|
`168.63.129.16`, is refused by default but is reopened by a list that covers it.
|
||||||
|
|
||||||
## Delivery reference
|
## Delivery reference
|
||||||
|
|
||||||
@@ -574,7 +583,7 @@ event's `Content-Type` and headers. The target's configured **Headers** are
|
|||||||
added next and replace event headers of the same name. Every `http` and `slack`
|
added next and replace event headers of the same name. Every `http` and `slack`
|
||||||
request carries `User-Agent: webhooker/{version}`. `User-Agent` is the header
|
request carries `User-Agent: webhooker/{version}`. `User-Agent` is the header
|
||||||
that names the sending program. The **Headers** field refuses `User-Agent`. A
|
that names the sending program. The **Headers** field refuses `User-Agent`. A
|
||||||
response with a status from 200 to 299 is a success. The first 4 KiB of the
|
response with a status from `200` to `299` is a success. The first 4 KiB of the
|
||||||
response body is stored with the attempt. **Timeout** is at most 300 seconds,
|
response body is stored with the attempt. **Timeout** is at most 300 seconds,
|
||||||
and blank means the 30 second default. **Pass the query string on to this
|
and blank means the 30 second default. **Pass the query string on to this
|
||||||
target** appends the event's query string to the URL.
|
target** appends the event's query string to the URL.
|
||||||
@@ -615,6 +624,8 @@ one JSON file compressed with gzip.
|
|||||||
- an unknown UUID gets `404`;
|
- an unknown UUID gets `404`;
|
||||||
- a deactivated entrypoint gets `410`;
|
- a deactivated entrypoint gets `410`;
|
||||||
- a body over 1 MB gets `413`;
|
- a body over 1 MB gets `413`;
|
||||||
|
- a body that cannot be read in full gets `400`, for example one still
|
||||||
|
arriving after the server's 10 second read limit;
|
||||||
- a failure to store the event gets `500`;
|
- a failure to store the event gets `500`;
|
||||||
- a stored event gets `200` with `{"status":"ok"}`.
|
- a stored event gets `200` with `{"status":"ok"}`.
|
||||||
- `GET /pages/login` shows the sign-in form, `POST /pages/login` signs in, and
|
- `GET /pages/login` shows the sign-in form, `POST /pages/login` signs in, and
|
||||||
@@ -681,12 +692,12 @@ version of it that works when the Content-Security-Policy forbids running text
|
|||||||
as code. The npm registry is where JavaScript packages are published. The
|
as code. The npm registry is where JavaScript packages are published. The
|
||||||
package file in `3p/` is byte for byte the one the npm registry publishes.
|
package file in `3p/` is byte for byte the one the npm registry publishes.
|
||||||
|
|
||||||
A browser test in `internal/server` loads the pages in a headless browser, one
|
A headless browser is a browser without a window. A browser test in
|
||||||
without a window, under the real Content-Security-Policy. `make test` leaves it
|
`internal/server` loads the pages in one under the real Content-Security-Policy.
|
||||||
out; run `make test-browser` after changing `templates/` or `static/js/`.
|
`make test` leaves it out; run `make test-browser` after changing `templates/`
|
||||||
`static/css/tailwind.css` is generated by Tailwind, a CSS generator, and
|
or `static/js/`. `static/css/tailwind.css` is generated by Tailwind, a CSS
|
||||||
committed; `make check` fails when it is out of date, and `make css` regenerates
|
generator. The file is committed. `make check` fails when it is out of date, and
|
||||||
it.
|
`make css` regenerates it.
|
||||||
|
|
||||||
## Entrypoints
|
## Entrypoints
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user