Skip to content

Commit

Permalink
feat: add acme client hook for libp2p
Browse files Browse the repository at this point in the history
  • Loading branch information
aschmahmann committed Aug 29, 2024
1 parent 0ddd505 commit 6566eb5
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 1 deletion.
284 changes: 284 additions & 0 deletions client/acme.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package client

import (
"context"
"crypto/x509"
"fmt"
"net"
"strings"
"sync"
"time"

"github.com/caddyserver/certmagic"
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
libp2pws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/multiformats/go-multibase"
)

var log = logging.Logger("p2p-forge/client")

type P2PForgeCertMgr struct {
forgeDomain string
forgeRegistrationEndpoint string
cfg *certmagic.Config
h *hostWrapper
}

type hostWrapper struct {
host.Host
}

type hostCloseWrapper struct {
host.Host
closeFn func() error
}

func (h hostCloseWrapper) Close() error {
return h.closeFn()
}

func NewHostWithP2PForge(forgeDomain string, forgeRegistrationEndpoint string, caEndpoint string, userEmail string, trustedRoots *x509.CertPool, onCertLoaded func(), allowPrivateForgeAddrs bool, opts ...libp2p.Option) (host.Host, error) {
certMgr := NewP2PForgeCertMgt(forgeDomain, forgeRegistrationEndpoint, caEndpoint, userEmail, trustedRoots)
tlsCfg := certMgr.cfg.TLSConfig()
tlsCfg.NextProtos = nil // remove the ACME ALPN

var p2pForgeWssComponent = multiaddr.StringCast(fmt.Sprintf("/tls/sni/*.%s/ws", forgeDomain))

var h host.Host
var mx sync.RWMutex
// TODO: Option passing mechanism here isn't respectful of which transports the user wants to support or the addresses they want to listen on
hTmp, err := libp2p.New(libp2p.ChainOptions(libp2p.ChainOptions(opts...),
libp2p.DefaultListenAddrs,
libp2p.ListenAddrStrings([]string{ // TODO: Grab these addresses from a TCP listener and share the ports
fmt.Sprintf("/ip4/0.0.0.0/tcp/0/tls/sni/*.%s/ws", forgeDomain),
fmt.Sprintf("/ip6/::/tcp/0/tls/sni/*.%s/ws", forgeDomain),
}...),
libp2p.Transport(tcp.NewTCPTransport),
libp2p.Transport(libp2pquic.NewTransport),
libp2p.Transport(libp2pws.New, libp2pws.WithTLSConfig(tlsCfg)),
libp2p.Transport(libp2pwebtransport.New),
libp2p.Transport(libp2pwebrtc.New),
libp2p.AddrsFactory(func(multiaddrs []multiaddr.Multiaddr) []multiaddr.Multiaddr {
mx.RLock()
if h == nil {
mx.RUnlock()
return multiaddrs
}
mx.RUnlock()

retAddrs := make([]multiaddr.Multiaddr, len(multiaddrs))
for i, a := range multiaddrs {
if isRelayAddr(a) || (!allowPrivateForgeAddrs && isPublicAddr(a)) {
retAddrs[i] = a
continue
}

// We expect the address to be of the form: /ipX/<IP address>/tcp/<Port>/tls/sni/*.<forge-domain>/ws
// We'll then replace the * with the IP address
withoutForgeWSS := a.Decapsulate(p2pForgeWssComponent)
if withoutForgeWSS.Equal(a) {
retAddrs[i] = a
continue
}

index := 0
var escapedIPStr string
var ipMaStr string
var tcpPortStr string
multiaddr.ForEach(withoutForgeWSS, func(c multiaddr.Component) bool {
switch index {
case 0:
switch c.Protocol().Code {
case multiaddr.P_IP4:
ipMaStr = c.String()
ipAddr := c.Value()
escapedIPStr = strings.ReplaceAll(ipAddr, ".", "-")
case multiaddr.P_IP6:
ipMaStr = c.String()
ipAddr := c.Value()
escapedIPStr = strings.ReplaceAll(ipAddr, ":", "-")
if escapedIPStr[0] == '-' {
escapedIPStr = "0" + escapedIPStr
}
if escapedIPStr[len(escapedIPStr)-1] == '-' {
escapedIPStr = escapedIPStr + "0"
}
default:
return false
}
case 1:
if c.Protocol().Code != multiaddr.P_TCP {
return false
}
tcpPortStr = c.Value()
default:
index++
return false
}
index++
return true
})
if index != 2 || escapedIPStr == "" || tcpPortStr == "" {
retAddrs[i] = a
continue
}

pidStr := peer.ToCid(h.ID()).Encode(multibase.MustNewEncoder(multibase.Base36))

newMaStr := fmt.Sprintf("%s/tcp/%s/tls/sni/%s.%s.%s/ws", ipMaStr, tcpPortStr, escapedIPStr, pidStr, forgeDomain)
newMA, err := multiaddr.NewMultiaddr(newMaStr)
if err != nil {
log.Errorf("error creating new multiaddr from %q: %s", newMaStr, err.Error())
retAddrs[i] = a
continue
}
retAddrs[i] = newMA
}
return retAddrs
}),
))
if err != nil {
return nil, err
}
mx.Lock()
h = hTmp
mx.Unlock()

ctx, cancel := context.WithCancel(context.Background())
if err := certMgr.Run(ctx, h); err != nil {
cancel()
return nil, err
}

w := &hostCloseWrapper{Host: h, closeFn: func() error {
cancel()
err := h.Close()
return err
}}

if onCertLoaded != nil {
pidStr := peer.ToCid(h.ID()).Encode(multibase.MustNewEncoder(multibase.Base36))
certName := fmt.Sprintf("*.%s.%s", pidStr, forgeDomain)
_ = certName
certMgr.cfg.OnEvent = func(ctx context.Context, event string, data map[string]any) error {
if event == "cached_managed_cert" {
sans, ok := data["sans"]
if !ok {
return nil
}
sanList, ok := sans.([]string)
if !ok {
return nil
}
for _, san := range sanList {
if san == certName {
onCertLoaded()
}
}
return nil
}
return nil
}
}

return w, nil
}

func isRelayAddr(a multiaddr.Multiaddr) bool {
found := false
multiaddr.ForEach(a, func(c multiaddr.Component) bool {
found = c.Protocol().Code == multiaddr.P_CIRCUIT
return !found
})
return found
}

var publicCIDR6 = "2000::/3"
var public6 *net.IPNet

func init() {
_, public6, _ = net.ParseCIDR(publicCIDR6)
}

// isPublicAddr follows the logic of manet.IsPublicAddr, except it uses
// a stricter definition of "public" for ipv6: namely "is it in 2000::/3"?
func isPublicAddr(a multiaddr.Multiaddr) bool {
ip, err := manet.ToIP(a)
if err != nil {
return false
}
if ip.To4() != nil {
return !inAddrRange(ip, manet.Private4) && !inAddrRange(ip, manet.Unroutable4)
}

return public6.Contains(ip)
}

func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool {
for _, ipnet := range ipnets {
if ipnet.Contains(ip) {
return true
}
}

return false
}

func NewP2PForgeCertMgt(forgeDomain string, forgeRegistrationEndpoint string, caEndpoint string, userEmail string, trustedRoots *x509.CertPool) *P2PForgeCertMgr {
cfg := certmagic.NewDefault()
cfg.Storage = &certmagic.FileStorage{Path: "foo"}
h := &hostWrapper{}
myACME := certmagic.NewACMEIssuer(cfg, certmagic.ACMEIssuer{ // TODO: UX around user passed emails + agreement
CA: caEndpoint, // TODO: Switch to real CA by default
Email: userEmail,
Agreed: true,
DNS01Solver: &dns01P2PForgeSolver{forgeRegistrationEndpoint, h},
TrustedRoots: trustedRoots,
})
cfg.Issuers = []certmagic.Issuer{myACME}
return &P2PForgeCertMgr{forgeDomain, forgeRegistrationEndpoint, cfg, h}
}

func (m *P2PForgeCertMgr) Run(ctx context.Context, h host.Host) error {
m.h.Host = h
pb36 := peer.ToCid(h.ID()).Encode(multibase.MustNewEncoder(multibase.Base36))

if err := m.cfg.ManageAsync(ctx, []string{fmt.Sprintf("*.%s.%s", pb36, m.forgeDomain)}); err != nil {
return err
}
return nil
}

type dns01P2PForgeSolver struct {
forge string
host host.Host
}

func (d *dns01P2PForgeSolver) Wait(ctx context.Context, challenge acme.Challenge) error {
// TODO: query the authoritative DNS
time.Sleep(time.Second * 5)
return nil
}

func (d *dns01P2PForgeSolver) Present(ctx context.Context, challenge acme.Challenge) error {
return SendChallenge(ctx, d.forge, d.host.ID(), d.host.Peerstore().PrivKey(d.host.ID()), challenge.DNS01KeyAuthorization(), d.host.Addrs())
}

func (d *dns01P2PForgeSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
//TODO: Should we implement this, or is doing delete and Last-Writer-Wins enough?
return nil
}

var _ acmez.Solver = (*dns01P2PForgeSolver)(nil)
var _ acmez.Waiter = (*dns01P2PForgeSolver)(nil)
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ go 1.22

require (
github.com/aws/aws-sdk-go v1.51.25
github.com/caddyserver/certmagic v0.21.3
github.com/coredns/caddy v1.1.1
github.com/coredns/coredns v1.11.3
github.com/gorilla/mux v1.8.1
github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-ds-badger4 v0.1.5
github.com/ipfs/go-ds-dynamodb v0.1.1
github.com/ipfs/go-log/v2 v2.5.1
github.com/libp2p/go-buffer-pool v0.1.0
github.com/libp2p/go-libp2p v0.36.1
github.com/mholt/acmez/v2 v2.0.1
github.com/miekg/dns v1.1.61
github.com/multiformats/go-multiaddr v0.13.0
github.com/multiformats/go-multibase v0.2.0
Expand Down Expand Up @@ -43,6 +46,7 @@ require (
github.com/apparentlymart/go-cidr v1.1.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
Expand Down Expand Up @@ -95,7 +99,6 @@ require (
github.com/imdario/mergo v0.3.12 // indirect
github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
Expand All @@ -105,6 +108,7 @@ require (
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
Expand Down Expand Up @@ -174,6 +178,7 @@ require (
github.com/stretchr/testify v1.9.0 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/wlynxg/anet v0.0.3 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.etcd.io/etcd/api/v3 v3.5.12 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
go.etcd.io/etcd/client/v3 v3.5.12 // indirect
Expand Down
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Expand Down Expand Up @@ -297,6 +301,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
Expand All @@ -311,6 +316,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
Expand Down Expand Up @@ -343,6 +350,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
Expand Down Expand Up @@ -562,6 +571,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c=
go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A=
Expand Down

0 comments on commit 6566eb5

Please sign in to comment.