Skip to content

Commit ba282a7

Browse files
committed
feat: add acme client hook for libp2p
1 parent 0ddd505 commit ba282a7

File tree

3 files changed

+269
-1
lines changed

3 files changed

+269
-1
lines changed

client/acme.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"strings"
8+
"time"
9+
10+
"github.com/caddyserver/certmagic"
11+
logging "github.com/ipfs/go-log/v2"
12+
"github.com/libp2p/go-libp2p"
13+
"github.com/libp2p/go-libp2p/core/host"
14+
"github.com/libp2p/go-libp2p/core/peer"
15+
libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
16+
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
17+
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
18+
libp2pws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
19+
libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
20+
"github.com/mholt/acmez/v2"
21+
"github.com/mholt/acmez/v2/acme"
22+
"github.com/multiformats/go-multiaddr"
23+
manet "github.com/multiformats/go-multiaddr/net"
24+
"github.com/multiformats/go-multibase"
25+
)
26+
27+
var log = logging.Logger("p2p-forge/client")
28+
29+
type P2PForgeCertMgr struct {
30+
forgeDomain string
31+
forgeRegistrationEndpoint string
32+
cfg *certmagic.Config
33+
h *hostWrapper
34+
}
35+
36+
type hostWrapper struct {
37+
host.Host
38+
}
39+
40+
type hostCloseWrapper struct {
41+
host.Host
42+
closeFn func() error
43+
}
44+
45+
func (h hostCloseWrapper) Close() error {
46+
return h.closeFn()
47+
}
48+
49+
var libp2pDirectWssComponent = multiaddr.StringCast("/tls/sni/*.libp2p.direct/ws")
50+
51+
func NewHostWithP2PForge(forgeDomain string, forgeRegistrationEndpoint string, opts ...libp2p.Option) (host.Host, error) {
52+
certMgr := NewP2PForgeCertMgt(forgeDomain, forgeRegistrationEndpoint)
53+
tlsCfg := certMgr.cfg.TLSConfig()
54+
tlsCfg.NextProtos = nil // remove the ACME ALPN
55+
56+
var h host.Host
57+
var err error
58+
// TODO: Option passing mechanism here isn't respectful of which transports the user wants to support or the addresses they want to listen on
59+
h, err = libp2p.New(libp2p.ChainOptions(libp2p.ChainOptions(opts...),
60+
libp2p.DefaultListenAddrs,
61+
libp2p.ListenAddrStrings([]string{ // TODO: Grab these addresses from a TCP listener and share the ports
62+
fmt.Sprintf("/ip4/0.0.0.0/tcp/0/tls/sni/*.%s/ws", forgeDomain),
63+
fmt.Sprintf("/ip6/::/tcp/0/tls/sni/*.%s/ws", forgeDomain),
64+
}...),
65+
libp2p.Transport(tcp.NewTCPTransport),
66+
libp2p.Transport(libp2pquic.NewTransport),
67+
libp2p.Transport(libp2pws.New, libp2pws.WithTLSConfig(tlsCfg)),
68+
libp2p.Transport(libp2pwebtransport.New),
69+
libp2p.Transport(libp2pwebrtc.New),
70+
libp2p.AddrsFactory(func(multiaddrs []multiaddr.Multiaddr) []multiaddr.Multiaddr {
71+
if h == nil {
72+
return multiaddrs
73+
}
74+
75+
retAddrs := make([]multiaddr.Multiaddr, len(multiaddrs))
76+
for i, a := range multiaddrs {
77+
if isRelayAddr(a) || !isPublicAddr(a) {
78+
retAddrs[i] = a
79+
continue
80+
}
81+
82+
// We expect the address to be of the form: /ipX/<IP address>/tcp/<Port>/tls/sni/*.libp2p.direct/ws
83+
// We'll then replace the * with the IP address
84+
withoutLibp2pDirectWSS := a.Decapsulate(libp2pDirectWssComponent)
85+
if !withoutLibp2pDirectWSS.Equal(a) {
86+
retAddrs[i] = a
87+
continue
88+
}
89+
90+
index := 0
91+
var escapedIPStr string
92+
var ipMaStr string
93+
var tcpPortStr string
94+
multiaddr.ForEach(a, func(c multiaddr.Component) bool {
95+
switch index {
96+
case 0:
97+
switch c.Protocol().Code {
98+
case multiaddr.P_IP4:
99+
ipMaStr = c.String()
100+
ipAddr := c.Value()
101+
escapedIPStr = strings.ReplaceAll(ipAddr, ".", "-")
102+
case multiaddr.P_IP6:
103+
ipMaStr = c.String()
104+
ipAddr := c.Value()
105+
escapedIPStr = strings.ReplaceAll(ipAddr, ":", "-")
106+
if escapedIPStr[0] == '-' {
107+
escapedIPStr = "0" + escapedIPStr
108+
}
109+
if escapedIPStr[len(escapedIPStr)-1] == '-' {
110+
escapedIPStr = escapedIPStr + "0"
111+
}
112+
default:
113+
return false
114+
}
115+
case 1:
116+
if c.Protocol().Code != multiaddr.P_TCP {
117+
return false
118+
}
119+
tcpPortStr = c.Value()
120+
default:
121+
index++
122+
return false
123+
}
124+
index++
125+
return true
126+
})
127+
if index != 2 || escapedIPStr == "" || tcpPortStr == "" {
128+
continue
129+
}
130+
131+
pidStr := peer.ToCid(h.ID()).Encode(multibase.MustNewEncoder(multibase.Base36))
132+
133+
newMaStr := fmt.Sprintf("%s/tcp/%s/tls/sni/%s.%s.%s/w", ipMaStr, tcpPortStr, escapedIPStr, pidStr, forgeDomain)
134+
newMA, err := multiaddr.NewMultiaddr(newMaStr)
135+
if err != nil {
136+
log.Errorf("error creating new multiaddr from %q: %s", newMaStr, err.Error())
137+
continue
138+
}
139+
retAddrs[i] = newMA
140+
}
141+
return retAddrs
142+
}),
143+
))
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
ctx, cancel := context.WithCancel(context.Background())
149+
if err := certMgr.Run(ctx, h); err != nil {
150+
cancel()
151+
return nil, err
152+
}
153+
154+
w := &hostCloseWrapper{Host: h, closeFn: func() error {
155+
cancel()
156+
err := h.Close()
157+
return err
158+
}}
159+
160+
return w, nil
161+
}
162+
163+
func isRelayAddr(a multiaddr.Multiaddr) bool {
164+
found := false
165+
multiaddr.ForEach(a, func(c multiaddr.Component) bool {
166+
found = c.Protocol().Code == multiaddr.P_CIRCUIT
167+
return !found
168+
})
169+
return found
170+
}
171+
172+
var publicCIDR6 = "2000::/3"
173+
var public6 *net.IPNet
174+
175+
func init() {
176+
_, public6, _ = net.ParseCIDR(publicCIDR6)
177+
}
178+
179+
// isPublicAddr follows the logic of manet.IsPublicAddr, except it uses
180+
// a stricter definition of "public" for ipv6: namely "is it in 2000::/3"?
181+
func isPublicAddr(a multiaddr.Multiaddr) bool {
182+
ip, err := manet.ToIP(a)
183+
if err != nil {
184+
return false
185+
}
186+
if ip.To4() != nil {
187+
return !inAddrRange(ip, manet.Private4) && !inAddrRange(ip, manet.Unroutable4)
188+
}
189+
190+
return public6.Contains(ip)
191+
}
192+
193+
func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool {
194+
for _, ipnet := range ipnets {
195+
if ipnet.Contains(ip) {
196+
return true
197+
}
198+
}
199+
200+
return false
201+
}
202+
203+
func NewP2PForgeCertMgt(forgeDomain string, forgeRegistrationEndpoint string) *P2PForgeCertMgr {
204+
cfg := certmagic.NewDefault()
205+
cfg.Storage = &certmagic.FileStorage{Path: "foo"}
206+
h := &hostWrapper{}
207+
myACME := certmagic.NewACMEIssuer(cfg, certmagic.ACMEIssuer{ // TODO: UX around user passed emails + agreement
208+
CA: certmagic.LetsEncryptStagingCA, // TODO: Switch to real CA by default
209+
Email: "you@yours.com",
210+
Agreed: true,
211+
DNS01Solver: &dns01P2PForgeSolver{forgeRegistrationEndpoint, h},
212+
})
213+
cfg.Issuers = []certmagic.Issuer{myACME}
214+
return &P2PForgeCertMgr{forgeDomain, forgeRegistrationEndpoint, cfg, h}
215+
}
216+
217+
func (m *P2PForgeCertMgr) Run(ctx context.Context, h host.Host) error {
218+
m.h.Host = h
219+
pb36 := peer.ToCid(h.ID()).Encode(multibase.MustNewEncoder(multibase.Base36))
220+
221+
if err := m.cfg.ManageAsync(ctx, []string{fmt.Sprintf("*.%s.libp2p.direct", pb36)}); err != nil {
222+
return err
223+
}
224+
return nil
225+
}
226+
227+
type dns01P2PForgeSolver struct {
228+
forge string
229+
host host.Host
230+
}
231+
232+
func (d *dns01P2PForgeSolver) Wait(ctx context.Context, challenge acme.Challenge) error {
233+
// TODO: query the authoritative DNS
234+
time.Sleep(time.Second * 5)
235+
return nil
236+
}
237+
238+
func (d *dns01P2PForgeSolver) Present(ctx context.Context, challenge acme.Challenge) error {
239+
return SendChallenge(ctx, d.forge, d.host.ID(), d.host.Peerstore().PrivKey(d.host.ID()), challenge.DNS01KeyAuthorization(), d.host.Addrs())
240+
}
241+
242+
func (d *dns01P2PForgeSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
243+
//TODO: Should we implement this, or is doing delete and Last-Writer-Wins enough?
244+
return nil
245+
}
246+
247+
var _ acmez.Solver = (*dns01P2PForgeSolver)(nil)
248+
var _ acmez.Waiter = (*dns01P2PForgeSolver)(nil)

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ go 1.22
44

55
require (
66
github.com/aws/aws-sdk-go v1.51.25
7+
github.com/caddyserver/certmagic v0.21.3
78
github.com/coredns/caddy v1.1.1
89
github.com/coredns/coredns v1.11.3
910
github.com/gorilla/mux v1.8.1
1011
github.com/ipfs/go-datastore v0.6.0
1112
github.com/ipfs/go-ds-badger4 v0.1.5
1213
github.com/ipfs/go-ds-dynamodb v0.1.1
14+
github.com/ipfs/go-log/v2 v2.5.1
1315
github.com/libp2p/go-buffer-pool v0.1.0
1416
github.com/libp2p/go-libp2p v0.36.1
17+
github.com/mholt/acmez/v2 v2.0.1
1518
github.com/miekg/dns v1.1.61
1619
github.com/multiformats/go-multiaddr v0.13.0
1720
github.com/multiformats/go-multibase v0.2.0
@@ -43,6 +46,7 @@ require (
4346
github.com/apparentlymart/go-cidr v1.1.0 // indirect
4447
github.com/benbjohnson/clock v1.3.5 // indirect
4548
github.com/beorn7/perks v1.0.1 // indirect
49+
github.com/caddyserver/zerossl v0.1.3 // indirect
4650
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4751
github.com/containerd/cgroups v1.1.0 // indirect
4852
github.com/coreos/go-semver v0.3.0 // indirect
@@ -95,7 +99,6 @@ require (
9599
github.com/imdario/mergo v0.3.12 // indirect
96100
github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9 // indirect
97101
github.com/ipfs/go-cid v0.4.1 // indirect
98-
github.com/ipfs/go-log/v2 v2.5.1 // indirect
99102
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
100103
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
101104
github.com/jbenet/goprocess v0.1.4 // indirect
@@ -105,6 +108,7 @@ require (
105108
github.com/klauspost/compress v1.17.9 // indirect
106109
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
107110
github.com/koron/go-ssdp v0.0.4 // indirect
111+
github.com/libdns/libdns v0.2.2 // indirect
108112
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
109113
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
110114
github.com/libp2p/go-msgio v0.3.0 // indirect
@@ -174,6 +178,7 @@ require (
174178
github.com/stretchr/testify v1.9.0 // indirect
175179
github.com/tinylib/msgp v1.1.8 // indirect
176180
github.com/wlynxg/anet v0.0.3 // indirect
181+
github.com/zeebo/blake3 v0.2.3 // indirect
177182
go.etcd.io/etcd/api/v3 v3.5.12 // indirect
178183
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
179184
go.etcd.io/etcd/client/v3 v3.5.12 // indirect

go.sum

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
7171
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
7272
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
7373
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
74+
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
75+
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
76+
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
77+
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
7478
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
7579
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
7680
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -297,6 +301,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
297301
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
298302
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
299303
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
304+
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
300305
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
301306
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
302307
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
@@ -311,6 +316,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
311316
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
312317
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
313318
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
319+
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
320+
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
314321
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
315322
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
316323
github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
@@ -343,6 +350,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
343350
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
344351
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
345352
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
353+
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
354+
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
346355
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
347356
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
348357
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -562,6 +571,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
562571
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
563572
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
564573
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
574+
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
575+
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
576+
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
577+
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
578+
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
579+
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
565580
go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c=
566581
go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
567582
go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A=

0 commit comments

Comments
 (0)