An Authoritative DNS server and API for distributing DNS subdomains with CA-signed TLS certificates to libp2p peers.
This is the backend of AutoTLS
feature introduced in Kubo 0.32.0-rc1.
It is deployed at libp2p.direct
and maintained by Interplanetary Shipyard.
The following diagrams show the high-level design of how p2p-forge works.
sequenceDiagram
participant Client as Kubo (libp2p peer)
participant LE as Let's Encrypt (ACME Server)
participant Registration as registration.libp2p.direct (p2p-forge/acme)
participant DNS as libp2p.direct DNS (p2p-forge/acme)
Client->>LE: Request Certificate
LE-->>Client: Respond with DNS-01 Challenge
Client->>Registration: Authenticate as PeerID over HTTP and share Multiaddrs and DNS-01 value
Registration->>Client: Test public reachability of PeerID and Multiaddrs
Registration->>DNS: Add Domain Validation DNS-01 TXT Record for <PeerID>.libp2p.direct
DNS-->>Client: DNS-01 TXT Record Added at _acme-challenge.<PeerID>.libp2p.direct
Client->>LE: Notify DNS-01 Challenge Completion
LE->>DNS: Validate DNS-01 Challenge
DNS-->>LE: Return TXT Record from _acme-challenge.<PeerID>.libp2p.direct
LE-->>Client: Certificate for *.<PeerID>.libp2p.direct issued
_acme-challenge.<peerid>.libp2p.direct
is part of ACME DNS-01 Challenge/v1/_acme-challenge
is provided by p2p-forge/acme and requires libp2p node and a valid PeerID to pass PeerID auth and libp2p connectivity challenge.sequenceDiagram
participant Browser as Client (Web Browser)
participant DNS as libp2p.direct DNS NS (p2p-forge/ipparser)
participant Kubo as Kubo (IP: 1.2.3.4)
Browser-->>DNS: DNS Query: 1-2-3-4.<PeerID>.libp2p.direct
DNS-->>Browser: 1.2.3.4
Browser->>Kubo: TLS Connect to 1.2.3.4 with SNI 1-2-3-4.<PeerID>.libp2p.direct
*.<PeerID>.libp2p.direct
are handled by p2p-forge/ipparsergo build
will build the p2p-forge
binary in your local directory
$ go install github.com/ipshipyard/p2p-forge@latest
Will download using go mod, build and install the binary in your global Go binary directory (e.g. ~/go/bin
)
go install
will build and install the p2p-forge
binary in your global Go binary directory (e.g. ~/go/bin
)
Build and run a custom Corefile configuration and on custom port:
$ ./p2p-forge -conf Corefile.example -dns.port 5353
Test with dig
:
$ dig A 1-2-3-4.k51qzi5uqu5dlwfht6wwy7lp4z35bgytksvp5sg53fdhcocmirjepowgifkxqd.libp2p.direct @localhost -p 5353
1.2.3.4
Prebuilt images for main
and staging
branches are provided at https://github.com/ipshipyard/p2p-forge/pkgs/container/p2p-forge
Docker image ships without /p2p-forge/Corefile
and /p2p-forge/zones
, and you need to pass your own:
$ docker build -t p2p-forge . && docker run --rm -it --net=host -v ./Corefile:/p2p-forge/Corefile.example -v ./zones:/p2p-forge/zones p2p-forge -conf /p2p-forge/Corefile.example -dns.port 5353
Test with dig
:
$ dig A 1-2-3-4.k51qzi5uqu5dlwfht6wwy7lp4z35bgytksvp5sg53fdhcocmirjepowgifkxqd.libp2p.direct @localhost -p 5353
1.2.3.4
This binary is based on CoreDNS which is itself based on Caddy.
To run the binary create a file Corefile
following the syntax listed in the CoreDNS documentation.
A custom configuration can be passed via ./p2p-forge -conf Corefile.example
This binary introduces two additional plugins:
ipparser
which handles returning A and AAAA records for domains like <encoded-ip-address>.<peerID>.libp2p.direct
acme
which handles reading and writing DNS acme challenges for domains like _acme-challenge.<peerID>.libp2p.direct
ipparser FORGE_DOMAIN
FORGE_DOMAIN the domain of the forge (e.g. libp2p.direct)
acme FORGE_DOMAIN {
[registration-domain REGISTRATION_DOMAIN [listen-address=ADDRESS] [external-tls=true|false]
[database-type DB_TYPE [...DB_ARGS]]
}
:443
.Below is a basic example of starting a DNS server that handles the IP based domain names as well as ACME challenges. It does the following:
. {
log
ipparser libp2p.direct
acme libp2p.direct {
registration-domain registration.libp2p.direct listen-address=:443 external-tls=false
database-type dynamo mytable
}
}
There are 3 types of records handled for a given peer and forge (e.g. <peerID>.libp2p.direct
):
_acme-challenge.<peerID>.libp2p.direct
1-2-3-4.<peerID>.libp2p.direct
2001-db8--.<peerID>.libp2p.direct
IPv4 handling is fairly straightforward, for a given IPv4 address 1.2.3.4
convert the .
s into -
s and the result
will be valid.
Due to the length of IPv6 addresses there are a number of different formats for describing IPv6 addresses.
The addresses handled here are:
A:B:C:D:1:2:3:4
convert the :
s into -
s and the result will be valid.A::C:D
can be converted either into their expanded form or into a condensed form by replacing
the :
s with -
s, like A--C-D
:
as the first or last character it must be converted to a 0 to comply with rfc1123
, so ::B:C:D
would become 0--B-C-D
and 1::
would become 1--0
Other address formats (e.g. the dual IPv6/IPv4 format) are not supported
To claim a domain name like <peerID>.libp2p.direct
requires:
/ipfs/id/1.0.0
)To set an ACME challenge send an HTTP request to the server (for libp2p.direct this is registration.libp2p.direct)
curl -X POST "https://registration.libp2p.direct/v1/_acme-challenge" \
-H "Authorization: libp2p-PeerID bearer=\"<base64-encoded-opaque-blob>\""
-H "Content-Type: application/json" \
-d '{
"value": "your_acme_challenge_token",
"addresses": ["your", "multiaddrs"]
}'
Where the bearer token is derived via the libp2p HTTP PeerID Auth Specification.