Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add AutoTLS example #3103

Merged
merged 26 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f204c7c
feat: add AutoTLS example
2color Dec 17, 2024
17921c0
Update examples/autotls/README.md
2color Dec 17, 2024
925d7ff
feat: leave peer hanging and listen on ipv6
2color Dec 18, 2024
d38bfec
deps: bump go-libp2p
2color Dec 19, 2024
bf1d443
chore: ignore p2p-forge certs
2color Dec 19, 2024
e9c6565
deps: upgrade kad-dht
2color Dec 19, 2024
3d9290c
fix: improve example and reuse same port
2color Dec 19, 2024
0decf05
fix: bind to ipv6 also for tcp and quic
2color Dec 20, 2024
a6cbe3f
feat: upgrade p2p-forge client
2color Jan 8, 2025
d1f58e9
fix: p2p-forge v0.2.1
lidel Jan 8, 2025
8490502
docs: reference libp2p websockets transport
lidel Jan 8, 2025
f12cae9
refactor: use test CA for ACME, fix logger/storage
lidel Jan 8, 2025
b0760e5
chore: mod tidy
lidel Jan 8, 2025
c16ebd9
fix: ensure we enable shared listener
2color Jan 8, 2025
5da91ac
chore: use go 1.23 when running go checks
galargh Jan 9, 2025
bf63025
feat: use persistent to reuse cert
2color Jan 9, 2025
14e0d50
chore: p2p-forge v0.2.2
lidel Jan 13, 2025
c301ad7
chore: remove quic to keep example focused
2color Jan 14, 2025
f4b8c57
Update examples/autotls/main.go
2color Jan 28, 2025
f44f4ef
chore: p2p-forge/client v0.3.1
lidel Feb 11, 2025
2b8b7f7
Merge branch 'master' into add-autotls-example
p-shahi Feb 11, 2025
61cf5d9
Update examples/autotls/main.go
2color Feb 12, 2025
7dd1801
Update examples/autotls/main.go
2color Feb 12, 2025
28b5683
Update examples/autotls/identity.go
2color Feb 12, 2025
40b27ff
fix: formatting
2color Feb 12, 2025
a26fc5e
Merge branch 'master' into add-autotls-example
2color Feb 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ jobs:
go-check:
uses: ipdxco/unified-github-workflows/.github/workflows/[email protected]
with:
go-version: "1.22.x"
go-version: "1.23.x"
go-generate-ignore-protoc-version-comments: true
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Let us know if you find any issue or if you want to contribute and add a new tut
## Examples and Tutorials

- [The libp2p 'host'](./libp2p-host)
- [The libp2p 'host' with Secure WebSockets and AutoTLS](./autotls)
- [Building an http proxy with libp2p](./http-proxy)
- [An echo host](./echo)
- [Routed echo host](./routed-echo/)
Expand Down
3 changes: 3 additions & 0 deletions examples/autotls/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
autotls
p2p-forge-certs/
identity.key
14 changes: 14 additions & 0 deletions examples/autotls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# libp2p host with Secure WebSockets and AutoTLS

This example builds on the [libp2p host example](../libp2p-host) example and demonstrates how to use [AutoTLS](https://blog.ipfs.tech/2024-shipyard-improving-ipfs-on-the-web/#autotls-with-libp2p-direct) to automatically generate a wildcard Let's Encrypt TLS certificate unique to the libp2p host (`*.<PeerID>.libp2p.direct`), and use it with [libp2p WebSockets transport over TCP](https://github.com/libp2p/specs/blob/master/websockets/README.md) enabling browsers to directly connect to the libp2p host.

For this example to work, you need to have a public IP address and be publicly reachable. AutoTLS is guarded by connectivity check, and will not ask for a certificate unless your libp2p node emits `event.EvtLocalReachabilityChanged` with `network.ReachabilityPublic`.

## Running the example

From the `go-libp2p/examples` directory run the following:

```sh
cd autotls/
go run .
```
47 changes: 47 additions & 0 deletions examples/autotls/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package main

import (
"os"

"github.com/libp2p/go-libp2p/core/crypto"
)

// LoadIdentity reads a private key from the given path and, if it does not
// exist, generates a new one.
func LoadIdentity(keyPath string) (crypto.PrivKey, error) {
if _, err := os.Stat(keyPath); err == nil {
return ReadIdentity(keyPath)
} else if os.IsNotExist(err) {
logger.Infof("Generating peer identity in %s\n", keyPath)
return GenerateIdentity(keyPath)
} else {
return nil, err
}
}

// ReadIdentity reads a private key from the given path.
func ReadIdentity(path string) (crypto.PrivKey, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}

return crypto.UnmarshalPrivateKey(bytes)
}

// GenerateIdentity writes a new random private key to the given path.
func GenerateIdentity(path string) (crypto.PrivKey, error) {
privk, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 0)
if err != nil {
return nil, err
}

bytes, err := crypto.MarshalPrivateKey(privk)
if err != nil {
return nil, err
}

err = os.WriteFile(path, bytes, 0400)

return privk, err
}
151 changes: 151 additions & 0 deletions examples/autotls/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package main

import (
"context"
"fmt"
"os"
"os/signal"
"time"

"github.com/caddyserver/certmagic"
"github.com/ipfs/go-log/v2"

p2pforge "github.com/ipshipyard/p2p-forge/client"
"github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
)

var logger = log.Logger("autotls-example")

const userAgent = "go-libp2p/example/autotls"
const identityKeyFile = "identity.key"

func main() {
// Create a background context
ctx := context.Background()

log.SetLogLevel("*", "error")
log.SetLogLevel("autotls-example", "debug") // Set the log level for the example to debug
log.SetLogLevel("basichost", "info") // Set the log level for the basichost package to info
log.SetLogLevel("autotls", "debug") // Set the log level for the autotls-example package to debug
log.SetLogLevel("p2p-forge", "debug") // Set the log level for the p2pforge package to debug
log.SetLogLevel("nat", "debug") // Set the log level for the libp2p nat package to debug

certLoaded := make(chan bool, 1) // Create a channel to signal when the cert is loaded

// use dedicated logger for autotls feature
rawLogger := logger.Desugar()

// p2pforge is the AutoTLS client library.
// The cert manager handles the creation and management of certificate
certManager, err := p2pforge.NewP2PForgeCertMgr(
// Configure CA ACME endpoint
// NOTE:
// This example uses Let's Encrypt staging CA (p2pforge.DefaultCATestEndpoint)
// which will not work correctly in browser, but is useful for initial testing.
// Production should use Let's Encrypt production CA (p2pforge.DefaultCAEndpoint).
p2pforge.WithCAEndpoint(p2pforge.DefaultCATestEndpoint), // test CA endpoint
// TODO: p2pforge.WithCAEndpoint(p2pforge.DefaultCAEndpoint), // production CA endpoint
2color marked this conversation as resolved.
Show resolved Hide resolved
aschmahmann marked this conversation as resolved.
Show resolved Hide resolved

// Configure where to store certificate
p2pforge.WithCertificateStorage(&certmagic.FileStorage{Path: "p2p-forge-certs"}),
2color marked this conversation as resolved.
Show resolved Hide resolved

// Configure logger to use
p2pforge.WithLogger(rawLogger.Sugar().Named("autotls")),
2color marked this conversation as resolved.
Show resolved Hide resolved

// User-Agent to use during DNS-01 ACME challenge
p2pforge.WithUserAgent(userAgent),

// Optional hook called once certificate is ready
p2pforge.WithOnCertLoaded(func() {
certLoaded <- true
}),
)

if err != nil {
panic(err)
}

// Start the cert manager
certManager.Start()
defer certManager.Stop()

// Load or generate a persistent peer identity key
privKey, err := LoadIdentity(identityKeyFile)
if err != nil {
panic(err)
}

opts := []libp2p.Option{
libp2p.Identity(privKey), // Use the loaded identity key
libp2p.DisableRelay(), // Disable relay, since we need a public IP address
libp2p.NATPortMap(), // Attempt to open ports using UPnP for NATed hosts.

libp2p.ListenAddrStrings(
"/ip4/0.0.0.0/tcp/5500", // regular TCP IPv4 connections
"/ip6/::/tcp/5500", // regular TCP IPv6 connections

// Configure Secure WebSockets listeners on the same TCP port
// AutoTLS will automatically generate a certificate for this host
// and use the forge domain (`libp2p.direct`) as the SNI hostname.
fmt.Sprintf("/ip4/0.0.0.0/tcp/5500/tls/sni/*.%s/ws", p2pforge.DefaultForgeDomain),
fmt.Sprintf("/ip6/::/tcp/5500/tls/sni/*.%s/ws", p2pforge.DefaultForgeDomain),
2color marked this conversation as resolved.
Show resolved Hide resolved
),

// Configure the TCP transport
libp2p.Transport(tcp.NewTCPTransport),

// Share the same TCP listener between the TCP and WS transports
libp2p.ShareTCPListener(),

// Configure the WS transport with the AutoTLS cert manager
libp2p.Transport(ws.New, ws.WithTLSConfig(certManager.TLSConfig())),

// Configure user agent for libp2p identify protocol (https://github.com/libp2p/specs/blob/master/identify/README.md)
libp2p.UserAgent(userAgent),

// AddrsFactory takes the multiaddrs we're listening on and sets the multiaddrs to advertise to the network.
// We use the AutoTLS address factory so that the `*` in the AutoTLS address string is replaced with the
// actual IP address of the host once detected
libp2p.AddrsFactory(certManager.AddressFactory()),
}
h, err := libp2p.New(opts...)
if err != nil {
panic(err)
}

logger.Info("Host created with PeerID: ", h.ID())

// Bootstrap the DHT to verify our public IPs address with AutoNAT
dhtOpts := []dht.Option{
dht.Mode(dht.ModeClient),
dht.BootstrapPeers(dht.GetDefaultBootstrapPeerAddrInfos()...),
}
dht, err := dht.New(ctx, h, dhtOpts...)
if err != nil {
panic(err)
}

go dht.Bootstrap(ctx)

// Wait for peers to verify public address with AutoNAT
time.Sleep(5 * time.Second)
2color marked this conversation as resolved.
Show resolved Hide resolved

logger.Info("Addresses: ", h.Addrs())

certManager.ProvideHost(h)

select {
case <-certLoaded:
logger.Info("TLS certificate loaded ")
logger.Info("Addresses: ", h.Addrs())
case <-ctx.Done():
logger.Info("Context done")
}
// Wait for interrupt signal
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
}
Loading
Loading