/serverless-dns

The RethinkDNS resolver that deploys to Cloudflare Workers, Deno Deploy, Fastly, and Fly.io

Primary LanguageJavaScriptOtherNOASSERTION

Copyright (c) <2023> apetree1001@email.phoenix.edu

serverless-dns

https://github.com/serverless-dns/blocklists serverless stub DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT) resolver Runs out-of-the-box on Cloudflare Workers

Deno Deploy

Fastly Compute@Edge and

Fly.io

Free tiers of all these services should be enough to cover 10 to 20 devices worth of DNS traffic per month RethinkDNS resolver RethinkDNS runs serverless-dns in production at these endpoints:

| Cloud platform
| Server locations |Protocol
| Domain
| Usage |-------------------- | ⛅ Cloudflare Workers | 280+ (ping) | DoH
| sky.rethinkdns.com
| configure
| 🦕 Deno Deploy
| 30+ (ping) | DoH
| private beta
|
| ⏱️ Fastly Compute@Edge
| 80+ (ping) | DoH
| private beta
|
| | 🪂 Fly.io
|30+ (ping) | DoH and DoT | max.rethinkdns.com | configure | Server-side processing takes from 0 milliseconds (ms) to 2ms (median) and end-to-end latency (varies across regions and networks) is between 10ms to 30ms (median) Self-host Cloudflare Workers is the easiest platform to setup serverless-dns: Deploy to Cloudflare Workers Deploy to Fastly

For step-by-step instructions, refer: | Platform
| Difficulty | Runtime | Doc | | ---------------| ---------- | -------------------------------------- | --------------------------------------------------------------------------------------- | ⛅ Cloudflare
| Easy
| v8 Isolates | Hosting on Cloudflare Workers |🦕 Deno.com
| Moderate
| Deno Isolates | Hosting on Deno.com
| ⏱️ Fastly Compute@Edge | Easy
| Fastly JS | Hosting on Fastly Compute@Edge | 🪂 Fly.io
| Hard
| Node MicroVM | Hosting on Fly.io
|To setup blocklists visit https://<my-domain>.tld/configure from your browser it should load something similar to RethinkDNS' configure page) For help or assistance, feel free to open an issue or submit a patch

Development OpenSSF Scorecard

Setup Code:

 navigate to work dir
cd /my/work/dir

# clone this repository
git clone https://github.com/serverless-dns/serverless-dns.git

# navigate to serverless-dns
cd ./serverless-dns


Node:
```bash
# install node v19+ via nvm, if required
# https://github.com/nvm-sh/nvm#installing-and-updating
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm install --lts

# download dependencies
npm i

# (optional) update dependencies
npm update

# run serverless-dns on node
./run n

# run a clinicjs.org profiler
./run n [cpu|fn|mem]

Deno:
```bash
# install deno.land v1.22+
# https://github.com/denoland/deno/#install
curl -fsSL https://deno.land/install.sh | sh

# run serverless-dns on deno
./run d

Fastly:
```bash
# install node v18+ via nvm, if required
# install the Fastly CLI
# https://developer.fastly.com/learning/tools/cli

# run serverless-dns on Fastly Compute@Edge
./run f

Wrangler:
```bash
# install Cloudflare Workers (cli) aka Wrangler
# https://developers.cloudflare.com/workers/cli-wrangler/install-update
npm i wrangler --save-dev

# run serverless-dns on Cloudflare Workers (cli)
# Make sure to setup Wrangler first:
# https://developers.cloudflare.com/workers/cli-wrangler/authentication
./run w

# profile wrangler with Chrome DevTools
# blog.cloudflare.com/profiling-your-workers-with-wrangler

Code style

Commits on this repository enforces the Google JavaScript style guide ref .eslintrc.cjs A git pre-commit hook that runs linter eslint and formatter prettier on .js files Use git commit --no-verify to bypass this hook Pull requests are also checked for code style violations and fixed automatically where possible Env vars Configure env.js if you need to tweak the defaults For Cloudflare Workers setup env vars in wrangler.toml instead For Fastly Compute@Edge setup env vars in fastly.toml instead Request flow The request/response flow: client <->src/server-[node|workers|deno]<-> doh.js <-> plugin.js The plugin.js flow: user-op.js -> cache-resolver.js -> cc.js -> resolver.js Auth serverless-dns supports authentication with an alpha-numeric bearer token for both DoH and DoT For a token msg-key (secret) append the output of hex(hmac-sha256(msg-key|domain.tld) msg) to ACCESS_KEYS env var in csv format Note: msg is currently fixed to `sdns-public-auth-info

DoH: place the msg-key at the end of the blockstamp like so: 1:1:4AIggAABEGAgAA:<msg-key> here, 1 is the version 1:4AIggAABEGAgAA is the blockstamp <msg-key> is the auth secret and : is the delimiter DoT: place the msg-key at the end of the SNI domain-name containing the blockstamp: 1-4abcbaaaaeigaiaa-<msg-key> (here 1 is the version, 4abcbaaaaeigaiaa is the blockstamp, <msg-key> is the auth secret, and - is the delimeter If the intention is to use auth with DoT too keep msg-key shorter 8 to 24 chars since subdomains may only be 63 chars long in total You can generate the access keys for your fork from max.rethinkdns.com like so:

msgkey="ShortAlphanumericSecret"
domain="my-serverless-dns-domain.tld"
curl 'https://max.rethinkdns.com/genaccesskey?key='"$msgkey"'&dom='"$domain"
# output
# {"accesskey":["my-serverless-dns-domain.tld|deadbeefd3adb33fa2bb33fd3eadf084beef3b152beefdead49bbb2b33fdead83d3adbeefdeadb33f"],"context":"sdns-public-auth-info"}

Logs and Analytics serverless-dns can be setup to upload logs via Cloudflare Logpush 0. Setup a Logpush job:

CF_ACCOUNT_ID=<hex-cloudflare-account-id>
CF_API_KEY=<api-key-with-logs-edit-permission-at-account-level>
R2_BUCKET=<r2-bucket-name>
R2_ACCESS_KEY=<r2-access-key-for-the-bucket>
R2_SECRET_KEY=<r2-secret-key-with-read-write-permissions>
# optional, setup a filter such that only logs form this worker ends up being pushed; but if you
# do not need a filter on Worker name (script-name), edit the "filter" field below accordingly.
SCRIPT_NAME=<name-of-the-worker-as-in-wrangler-toml>
 for more options, ref: developers.cloudflare.com/logs/get-started/api-configuration
 Logpush API with cURL: developers.cloudflare.com/logs/tutorials/examples/example-logpush-curl
Available Logpull fields: developers.cloudflare.com/logs/reference/log-fields/account/workers_trace_events
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/logpush/jobs" \
    -H "Authorization: Bearer ${CF_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "dns-logpush",
        "logpull_options": "fields=EventTimestampMs,Outcome,Logs,ScriptName&timestamps=rfc3339",
        "destination_conf": "r2://'"$R2_BUCKET"'/{DATE}?access-key-id='"${R2_ACCESS_KEY}"'&secret-access-key='"${R2_SECRET_KEY}"'&account-id='"{$CF_ACCOUNT_ID}"',
        "dataset": "workers_trace_events",
        "filter": "{\"where\":{\"and\":[{\"key\":\"ScriptName\",\"operator\":\"contains\",\"value\":\"'"${SCRIPT_NAME}"'\"},{\"key\":\"Outcome\",\"operator\":\"eq\",\"value\":\"ok\"}]}}",
        "enabled": true,
        "frequency": "low"
    }' Set `wrangler.toml` property `logpush = true`, which enables 
    *Logpush*  Optional env var `LOG_LEVEL = "logpush"`, which raises the log-level such that only *request* and error logs are emitted Optional Set env var `LOGPUSH_SRC = "csv,of,subdomains"` which makes [`log-pusher.js`](./src/plugins/observability/log-pusher.js) emit *request* logs only if Workers `hostname` contains one of the subdomains Logs published to R2 can
    be retrieved either using [R2 Workers](https://developers.cloudflare.com/r2/data-access/workers-api/workers-api-usage), the [R2 API](https://developers.cloudflare.com/r2/data-access/s3-api/api) or the [Logpush API](https://developers.cloudflare.com/logs/r2-log-retrieval) Workers Analytics if enabled, is pushed against a log-key
    `lid` which if unspecified is set to hostname of the serverless deployment with periods `.` replaced with underscores `_` Auth must be setup when querying for Analytics via the
    API which  returns a json 
    ex: 
    `https://max.rethinkdns.com/
    1 <optional-stamp>:<msg-key>/analytics?t=<time-interval-in-mins>&f=<field-name>` Possible `fields` are `ip` client ip `qname` dns query name `region` resolver region 
    `qtype`  dns query type 
    `dom`  top-level 
    domains `ansip` dns 
    answer ips and `cc` ans ip country codes Log capture and analytics isn't yet implemented for Fly and Deno Deploy  ***note*** about runtimes Deno Deploy cloud and Deno the runtime
    do not expose  the same API surface for example Deno Deploy only supports HTTP/S 
    server-listeners whereas Deno suports raw
    TCP/UDP/TLS in addition to plain HTTP and HTTP/S Except on
    Node`serverless-dns` uses DoH upstreams defined by env vars 

CF_DNS_RESOLVER_URL / CF_DNS_RESOLVER_URL_2 On Node the default DNS upstream is 1.1.1.2 ref or the recursive DNS resolver at fdaa::3 when running on Fly.io The entrypoints for Node and Deno are src/server-node.jssrc/server-deno.ts respectively and both listen for TCP-over-TLS HTTP/S connections whereas the entrypoint for Cloudflare Workers which only listens over HTTP cli or over HTTP/S (prod), is src/server-workers.js and for Fastly its src/server-fastly.js Local non-prod setups on Node key private and cert public chain files by default are read from paths defined in env vars TLS_KEY_PATH andTLS_CRT_PATH Whilst for prod setup on Node on Fly.io either TLS_OFFLOADmust be set to true or key and cert must be base64 encoded in env var TLS_CERTKEY (ref) like so

EITHER: offload tls to fly.io and set tls_offload to true TLS_OFFLOAD="true"
 OR: base64 representation of both key private and cert public chain TLS_CERTKEY="KEY=b64_key_content
\nCRT=b64_cert_content"
For Deno, `key` and `cert` files are
 read from paths defined in env vars `TLS_KEY_PATH` and `TLS_CRT_PATH`
[ref](https://github.com/serverless-dns/serverless-dns/blob/270d1a3c/src/server-deno.ts#L32-L35) _Process_ bringup is different for each of these runtimes: For
 Node [`src/core/node/config.js`](src/core/node/config.js) governs
the _bringup_ while for Deno it
is [`src/core/deno/config.ts`](src/core/deno/config.ts) and for
Workers it is [`src/core/workers/config.js`](src/core/workers/config.js)
[`src/system.js`](src/system.js)
 pub-sub co-ordinates the
_bringup_ phase among various modules
On Node and Deno, in-process DNS caching is backed by [`@serverless-dns/lfu-cache`](https://github.com/serverless-dns/lfu-cache); Cloudflare Workers is backed by both [Cache Web API](https://developers.cloudflare.com/workers/runtime-apis/cache) and
in-process lfu caches. To disable caching altogether on all three platfroms, set env var, `PROFILE_DNS_RESOLVES=true` Cloud
Cloudflare Workers and Deno Deploy are ephemeral as in the "process" that
serves client requests is not long-lived
and in fact two back-to-back requests
may be served by two different[_isolates_](https://developers.cloudflare.com/workers/learning/how-workers-works) "processes" Fastly Compute@Edge is the also ephemeral but does not use isolates instead Fastly creates and destroys a
 [wasmtime](https://wasmtime.dev/) sandbox for each request Resolver
 on Fly.io running Node is backed
 by [persistent VMs](https://fly.io/blog/docker-without-docker/) and is hence longer-lived
like traditional "serverful"
environments For Deno Deploy the code-base is bundled up in a single
javascript file with `deno bundle`
and then handed off to Deno.com
Cloudflare Workers build-time and
 runtime configurations are defined
 in [`wrangler.toml`](wrangler.toml)
[Webpack5 bundles the files](webpack.config.cjs) in an
 ESM module which is then uploaded
to Cloudflare by _Wrangler_ Fastly Compute@Edge build-time and runtime configurations are defined
in [`fastly.toml`](fastly.toml)
[Webpack5 bundles the files](webpack.fastly.cjs) in an
 ESM module which is then compiled
 to WASM by `npx js-compute-runtime`
and subsequently packaged and published to Fastly Compute@Edge with the
 _Fastly CLI_
For Fly.io, which runs Node, the runtime directives are defined in [`fly.toml`](fly.toml) (used by `dev` and `live` deployment-types),
while deploy directives are in [`node.Dockerfile`](node.Dockerfile). [`flyctl`](https://fly.io/docs/flyctl) accordingly sets
up `serverless-dns` on Fly.io's infrastructure
 build and deploy for
cloudflare workers.dev npm run build
usually, env-name is prod npx wrangler publish [-e <env-name>] bundle build
and deploy for fastly compute@edge
developer.fastly.com/reference/cli/compute/publish fastly compute publish  build and deploy to fly.io npm run build:fly
flyctl deploy --dockerfile node.Dockerfile --config <fly.toml>
[-a <app-name>]
[--image-label <some-uniq-label>]
For deploys offloading TLS termination
 to Fly.io `B1` deployment-type
 the runtime directives are instead defined in [`fly.tls.toml`](fly.tls.toml) which sets up HTTP2 Cleartext and HTTP/1.1 on port `443`, and DNS over
 TCP on port `853`
Ref: _[github/workflows](.github/workflows)_ Blocklists 190+ blocklists are compressed in
 a _Succinct Radix Trie_
[based on Steve Hanov's impl](https://stevehanov.ca/blog/?id=120) with modifications to speed up string search
[`lookup`](https://github.com/serverless-dns/trie/blob/965007a5c/src/ftrie.js#L378-L484) at the expense of "succintness"
The blocklists are versioned with unix timestamp defined in `src/basicconfig.json` downloaded
 by [`pre.sh`](src/build/pre.sh)
 which is generated once every week but we'd like to generate them daily / hourly
if possible
 [see](https://github.com/serverless-dns/blocklists/issues/19)
 and hosted on Cloudflare R2
env var: `CF_BLOCKLIST_URL`
`serverless-dns` downloads
[3 blocklist files](https://github.com/serverless-dns/serverless-dns/blob/15f62846/src/core/node/blocklists.js#L14-L16) required to setup the
radix-trie during runtime bring-up or downloads them
 [lazily](https://github.com/serverless-dns/serverless-dns/blob/02f9e5bf/src/plugins/dns-op/resolver.js#L167) when serving a DNS request
`serverless-dns` compiles around
~13M entries as of Jan 2023 from around 190+ blocklists These are defined in
the [serverless-dns/blocklists](https://github.com/serverless-dns/blocklists) repository