Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 11 additions & 77 deletions pkg/p2p/libp2p/libp2p.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ package libp2p
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"errors"
"fmt"
"net"
"net/netip"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
Expand All @@ -22,7 +20,6 @@ import (
"time"

ocprom "contrib.go.opencensus.io/exporter/prometheus"
"github.com/caddyserver/certmagic"
"github.com/coreos/go-semver/semver"
"github.com/ethersphere/bee/v2"
"github.com/ethersphere/bee/v2/pkg/addressbook"
Expand All @@ -41,6 +38,7 @@ import (
"github.com/ethersphere/bee/v2/pkg/topology"
"github.com/ethersphere/bee/v2/pkg/topology/lightnode"
"github.com/ethersphere/bee/v2/pkg/tracing"
p2pforge "github.com/ipshipyard/p2p-forge/client"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/config"
"github.com/libp2p/go-libp2p/core/crypto"
Expand All @@ -65,9 +63,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/atomic"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"

p2pforge "github.com/ipshipyard/p2p-forge/client"
)

// loggerName is the tree path name of the logger for this package.
Expand Down Expand Up @@ -134,14 +129,6 @@ type lightnodes interface {
EachPeer(pf topology.EachPeerFunc) error
}

// autoTLSCertManager defines the interface for managing TLS certificates.
type autoTLSCertManager interface {
Start() error
Stop()
TLSConfig() *tls.Config
AddressFactory() config.AddrsFactory
}

type Options struct {
PrivateKey *ecdsa.PrivateKey
NATAddr string
Expand Down Expand Up @@ -294,78 +281,25 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay
if o.autoTLSCertManager != nil {
certManager = o.autoTLSCertManager
} else {
// create a zap logger needed for cert manager to be as close to
// swarm logger as possible
l, err := zap.Config{
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
Development: false,
Sampling: &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
},
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
},
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}.Build()
forgeMgr, err := newP2PForgeCertManager(logger, P2PForgeOptions{
Domain: o.AutoTLSDomain,
RegistrationEndpoint: o.AutoTLSRegistrationEndpoint,
CAEndpoint: o.AutoTLSCAEndpoint,
StorageDir: o.AutoTLSStorageDir,
})
if err != nil {
return nil, err
}

// assing zap logger as it needs to be synced when the service stops
zapLogger = l

defer func() {
_ = zapLogger.Sync()
}()

// Use AutoTLS storage dir with domain subdir for easier management
// of different registers.
storagePath := filepath.Join(o.AutoTLSStorageDir, o.AutoTLSDomain)

if err := os.MkdirAll(storagePath, 0700); err != nil {
return nil, fmt.Errorf("create certificate storage directory %s: %w", storagePath, err)
}

certManager, err = p2pforge.NewP2PForgeCertMgr(
p2pforge.WithForgeDomain(o.AutoTLSDomain),
p2pforge.WithForgeRegistrationEndpoint(o.AutoTLSRegistrationEndpoint),
p2pforge.WithCAEndpoint(o.AutoTLSCAEndpoint),
p2pforge.WithCertificateStorage(&certmagic.FileStorage{Path: storagePath}),
p2pforge.WithLogger(zapLogger.Sugar()),
p2pforge.WithUserAgent(userAgent()),
p2pforge.WithAllowPrivateForgeAddrs(),
p2pforge.WithRegistrationDelay(0),
p2pforge.WithOnCertLoaded(func() {
logger.Info("auto tls certificate is loaded")
}),
p2pforge.WithOnCertRenewed(func() {
logger.Info("auto tls certificate is renewed")
}),
)
if err != nil {
return nil, fmt.Errorf("initialize AutoTLS: %w", err)
}
certManager = forgeMgr.CertMgr()
zapLogger = forgeMgr.ZapLogger()
}

defer func() {
if returnErr != nil {
// certificate manager has to be stopped if service is not
// constructed
// call if service is not constructed
certManager.Stop()
_ = zapLogger.Sync()
}
}()

Expand Down
138 changes: 138 additions & 0 deletions pkg/p2p/libp2p/p2pforge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2026 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package libp2p

import (
"crypto/tls"
"fmt"
"os"
"path/filepath"

"github.com/caddyserver/certmagic"
"github.com/ethersphere/bee/v2/pkg/log"
p2pforge "github.com/ipshipyard/p2p-forge/client"
"github.com/libp2p/go-libp2p/config"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// P2PForgeOptions contains the configuration for creating a P2P Forge certificate manager.
type P2PForgeOptions struct {
Domain string
RegistrationEndpoint string
CAEndpoint string
StorageDir string
}

// P2PForgeCertManager wraps the p2p-forge certificate manager with its associated zap logger.
type P2PForgeCertManager struct {
certMgr *p2pforge.P2PForgeCertMgr
zapLogger *zap.Logger
}

// newP2PForgeCertManager creates a new P2P Forge certificate manager.
// It handles the creation of the storage directory and configures logging
// to match bee's verbosity level.
func newP2PForgeCertManager(beeLogger log.Logger, opts P2PForgeOptions) (*P2PForgeCertManager, error) {
zapLogger, err := newZapLogger(beeLogger)
if err != nil {
return nil, fmt.Errorf("create zap logger: %w", err)
}

// Use storage dir with domain subdir for easier management of different registries.
storagePath := filepath.Join(opts.StorageDir, opts.Domain)
if err := os.MkdirAll(storagePath, 0o700); err != nil {
return nil, fmt.Errorf("create certificate storage directory %s: %w", storagePath, err)
}

certMgr, err := p2pforge.NewP2PForgeCertMgr(
p2pforge.WithForgeDomain(opts.Domain),
p2pforge.WithForgeRegistrationEndpoint(opts.RegistrationEndpoint),
p2pforge.WithCAEndpoint(opts.CAEndpoint),
p2pforge.WithCertificateStorage(&certmagic.FileStorage{Path: storagePath}),
p2pforge.WithLogger(zapLogger.Sugar()),
p2pforge.WithUserAgent(userAgent()),
p2pforge.WithAllowPrivateForgeAddrs(),
p2pforge.WithRegistrationDelay(0),
p2pforge.WithOnCertLoaded(func() {
beeLogger.Info("auto tls certificate is loaded")
}),
p2pforge.WithOnCertRenewed(func() {
beeLogger.Info("auto tls certificate is renewed")
}),
)
if err != nil {
return nil, fmt.Errorf("initialize P2P Forge: %w", err)
}

return &P2PForgeCertManager{
certMgr: certMgr,
zapLogger: zapLogger,
}, nil
}

// CertMgr returns the underlying p2pforge.P2PForgeCertMgr.
func (m *P2PForgeCertManager) CertMgr() *p2pforge.P2PForgeCertMgr {
return m.certMgr
}

// ZapLogger returns the zap logger used by the certificate manager.
func (m *P2PForgeCertManager) ZapLogger() *zap.Logger {
return m.zapLogger
}

// autoTLSCertManager defines the interface for managing TLS certificates.
type autoTLSCertManager interface {
Start() error
Stop()
TLSConfig() *tls.Config
AddressFactory() config.AddrsFactory
}

// newZapLogger creates a zap logger configured to match bee's verbosity level.
// This is used by third-party libraries (like p2p-forge) that require a zap logger.
func newZapLogger(beeLogger log.Logger) (*zap.Logger, error) {
cfg := zap.Config{
Level: zap.NewAtomicLevelAt(beeVerbosityToZapLevel(beeLogger.Verbosity())),
Development: false,
Encoding: "json",
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
Sampling: &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
},
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
},
}
return cfg.Build()
}

// beeVerbosityToZapLevel converts bee's log verbosity level to zap's log level.
func beeVerbosityToZapLevel(v log.Level) zapcore.Level {
switch {
case v <= log.VerbosityNone:
return zap.FatalLevel // effectively silences the logger
case v == log.VerbosityError:
return zap.ErrorLevel
case v == log.VerbosityWarning:
return zap.WarnLevel
case v == log.VerbosityInfo:
return zap.InfoLevel
default:
return zap.DebugLevel // VerbosityDebug and VerbosityAll
}
}
Loading