BIT-31 Expose Bitcoin Pulse API through nginx
This commit is contained in:
parent
2015aac8e4
commit
133384660e
2 changed files with 95 additions and 0 deletions
63
docs/bitcoin-pulse-ingress.md
Normal file
63
docs/bitcoin-pulse-ingress.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Bitcoin Pulse public ingress
|
||||||
|
|
||||||
|
Bitcoin Pulse uses the dedicated API hostname
|
||||||
|
`bitcoin-pulse.tobiasostner.de`. The native iOS client calls this hostname
|
||||||
|
without a path prefix. A same-origin path proxy is unnecessary because there is
|
||||||
|
no browser frontend, and no CORS headers are configured because native iOS
|
||||||
|
requests are not subject to browser CORS enforcement.
|
||||||
|
|
||||||
|
## DNS and TLS
|
||||||
|
|
||||||
|
The `bitcoin-pulse` A record under `tobiasostner.de` points to Tobserver's public
|
||||||
|
IPv4 address, `162.55.103.218`. There is no AAAA record until Tobserver has
|
||||||
|
working public IPv6 connectivity.
|
||||||
|
|
||||||
|
The NixOS nginx virtual host obtains and renews its certificate through the
|
||||||
|
existing ACME configuration and redirects plaintext HTTP to HTTPS. Only ports
|
||||||
|
80 and 443 are public ingress for the API. The application listens on
|
||||||
|
`127.0.0.1:3000`; port 3000 is not opened by the host firewall, so nginx is the
|
||||||
|
only public path to the backend.
|
||||||
|
|
||||||
|
## Proxy policy
|
||||||
|
|
||||||
|
nginx preserves the original request path and forwards it to
|
||||||
|
`http://127.0.0.1:3000`. It supplies `Host`, `X-Real-IP`, `X-Forwarded-For`, and
|
||||||
|
`X-Forwarded-Proto`, and uses short local-upstream timeouts: two seconds to
|
||||||
|
connect and ten seconds to read or send.
|
||||||
|
|
||||||
|
Requests are limited per source IP to an average of 300 requests per minute,
|
||||||
|
with a burst of 50 requests, and rejected with HTTP 429 when the burst is
|
||||||
|
exhausted. This allows normal polling and multiple devices behind a shared NAT
|
||||||
|
while bounding abusive traffic. Request bodies are limited to 16 KiB; the
|
||||||
|
current public API consists only of small GET requests.
|
||||||
|
|
||||||
|
## Deployment verification
|
||||||
|
|
||||||
|
After a human reviews and merges the change, deploy the resulting NixOS
|
||||||
|
generation and verify locally on Tobserver:
|
||||||
|
|
||||||
|
```console
|
||||||
|
systemctl is-active nginx.service bitcoin-pulse.service
|
||||||
|
nginx -t
|
||||||
|
ss -ltn '( sport = :3000 )'
|
||||||
|
curl --fail --silent http://127.0.0.1:3000/health | jq -e '.status == "OK"'
|
||||||
|
```
|
||||||
|
|
||||||
|
The socket listing must show only `127.0.0.1:3000`, not `0.0.0.0:3000` or the
|
||||||
|
public address. From a machine outside Tobserver, verify DNS, HTTPS, the health
|
||||||
|
endpoint, and each functional endpoint:
|
||||||
|
|
||||||
|
```console
|
||||||
|
dig +short A bitcoin-pulse.tobiasostner.de
|
||||||
|
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/health \
|
||||||
|
| jq -e '.status == "OK"'
|
||||||
|
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/block-height | jq
|
||||||
|
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/difficulty-adjustment | jq
|
||||||
|
curl --fail --silent https://bitcoin-pulse.tobiasostner.de/price | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
The A lookup must return `162.55.103.218`. The HTTPS certificate must be valid
|
||||||
|
for `bitcoin-pulse.tobiasostner.de`; `/health` must return HTTP 200 and
|
||||||
|
`{"status":"OK"}`. The functional endpoints must return HTTP 200 and JSON after
|
||||||
|
the collectors have populated their data. A connection from outside Tobserver
|
||||||
|
to TCP port 3000 must fail.
|
||||||
|
|
@ -265,5 +265,37 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
appendHttpConfig = ''
|
||||||
|
limit_req_zone $binary_remote_addr zone=bitcoin_pulse_api:10m rate=300r/m;
|
||||||
|
'';
|
||||||
|
|
||||||
|
virtualHosts."bitcoin-pulse.tobiasostner.de" = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3000";
|
||||||
|
extraConfig = ''
|
||||||
|
limit_req zone=bitcoin_pulse_api burst=50 nodelay;
|
||||||
|
limit_req_status 429;
|
||||||
|
client_max_body_size 16k;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_connect_timeout 2s;
|
||||||
|
proxy_read_timeout 10s;
|
||||||
|
proxy_send_timeout 10s;
|
||||||
|
proxy_next_upstream off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
environment.systemPackages = [ pkgs.postgresql_18 ];
|
environment.systemPackages = [ pkgs.postgresql_18 ];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue