ipshipyard / p2p-forge

An Authoritative DNS server for distributing DNS subdomains to libp2p peers
1 stars 0 forks source link

p2p-forge

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.

High-level Design

The following diagrams show the high-level design of how p2p-forge works.

Peer Authentication and DNS-01 Challenge and Certificate Issuance

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

DNS Resolution and TLS Connection

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

Build

go build will build the p2p-forge binary in your local directory

Install

$ 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)

From source

go install will build and install the p2p-forge binary in your global Go binary directory (e.g. ~/go/bin)

Usage

Local testing

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

Docker

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

Configuration

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 Syntax

ipparser FORGE_DOMAIN

FORGE_DOMAIN the domain of the forge (e.g. libp2p.direct)

acme Syntax

acme FORGE_DOMAIN {
    [registration-domain REGISTRATION_DOMAIN [listen-address=ADDRESS] [external-tls=true|false]
    [database-type DB_TYPE [...DB_ARGS]]
}

Example

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
    }
}

Handled DNS records

There are 3 types of records handled for a given peer and forge (e.g. <peerID>.libp2p.direct):

IPv4 subdomain handling

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.

IPv6 subdomain handling

Due to the length of IPv6 addresses there are a number of different formats for describing IPv6 addresses.

The addresses handled here are:

Other address formats (e.g. the dual IPv6/IPv4 format) are not supported

Submitting Challenge Records

To claim a domain name like <peerID>.libp2p.direct requires:

  1. The private key corresponding to the given peerID
  2. A publicly reachable libp2p endpoint with
    • one of the following libp2p transport configurations:
      • QUIC-v1
      • TCP or WS or WSS, Yamux, TLS or Noise
      • WebTransport
      • Other transports are under consideration (e.g. HTTP), if they are of interest please file an issue
    • the Identify protocol (/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.