mirror of
https://github.com/asmogo/nws.git
synced 2025-01-18 10:01:33 +00:00
lint exit package (#40)
This commit is contained in:
parent
b04b4f7e10
commit
ce11bb963a
42
exit/exit.go
42
exit/exit.go
@ -23,7 +23,7 @@ import (
|
||||
|
||||
const (
|
||||
startingReverseProxyMessage = "starting exit node with https reverse proxy"
|
||||
generateKeyMessage = "Generated new private key. Please set your environment using the new key, otherwise your key will be lost."
|
||||
generateKeyMessage = "Generated new private key. Please set your environment using the new key, otherwise your key will be lost." //nolint: lll
|
||||
)
|
||||
|
||||
// Exit represents a structure that holds information related to an exit node.
|
||||
@ -43,7 +43,7 @@ type Exit struct {
|
||||
// It is used to establish and maintain connections between the Exit node and the backend host.
|
||||
nostrConnectionMap *xsync.MapOf[string, *netstr.NostrConnection]
|
||||
|
||||
// mutexMap is a field in the Exit struct that represents a map used for synchronizing access to resources based on a string key.
|
||||
// mutexMap is a field in the Exit struct used for synchronizing access to resources based on a string key.
|
||||
mutexMap *MutexMap
|
||||
|
||||
// incomingChannel represents a channel used to receive incoming events from relays.
|
||||
@ -87,26 +87,25 @@ func NewExit(ctx context.Context, exitNodeConfig *config.ExitConfig) *Exit {
|
||||
// start reverse proxy if https port is set
|
||||
if exitNodeConfig.HttpsPort != 0 {
|
||||
exitNodeConfig.BackendHost = fmt.Sprintf(":%d", exitNodeConfig.HttpsPort)
|
||||
go func(cfg *config.ExitConfig) {
|
||||
go func(ctx context.Context, cfg *config.ExitConfig) {
|
||||
slog.Info(startingReverseProxyMessage, "port", cfg.HttpsPort)
|
||||
err := exit.StartReverseProxy(cfg.HttpsTarget, cfg.HttpsPort)
|
||||
err := exit.StartReverseProxy(ctx, cfg.HttpsTarget, cfg.HttpsPort)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}(exitNodeConfig)
|
||||
}(ctx, exitNodeConfig)
|
||||
}
|
||||
// set config
|
||||
exit.config = exitNodeConfig
|
||||
// add relays to the pool
|
||||
for _, relayUrl := range exitNodeConfig.NostrRelays {
|
||||
relay, err := exit.pool.EnsureRelay(relayUrl)
|
||||
for _, relayURL := range exitNodeConfig.NostrRelays {
|
||||
relay, err := exit.pool.EnsureRelay(relayURL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
slog.Error("failed to ensure relay", "url", relayURL, "error", err)
|
||||
continue
|
||||
}
|
||||
exit.relays = append(exit.relays, relay)
|
||||
fmt.Printf("added relay connection to %s\n", relayUrl)
|
||||
|
||||
slog.Info("added relay connection", "url", relayURL) //nolint:forbidigo
|
||||
}
|
||||
domain, err := exit.getDomain()
|
||||
if err != nil {
|
||||
@ -127,17 +126,18 @@ func NewExit(ctx context.Context, exitNodeConfig *config.ExitConfig) *Exit {
|
||||
|
||||
// getDomain returns the domain string used by the Exit node for communication with the Nostr relays.
|
||||
// It concatenates the relay URLs using base32 encoding with no padding, separated by dots.
|
||||
// The resulting domain is then appended with the base32 encoded public key obtained using the configured Nostr private key.
|
||||
// The domain is then appended with the base32 encoded public key obtained using the configured Nostr private key.
|
||||
// The final domain string is converted to lowercase and returned.
|
||||
// If any errors occur during the process, they are returned along with an
|
||||
func (e *Exit) getDomain() (string, error) {
|
||||
var domain string
|
||||
// first lets build the subdomains
|
||||
for _, relayUrl := range e.config.NostrRelays {
|
||||
for _, relayURL := range e.config.NostrRelays {
|
||||
if domain == "" {
|
||||
domain = base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString([]byte(relayUrl))
|
||||
domain = base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString([]byte(relayURL))
|
||||
} else {
|
||||
domain = fmt.Sprintf("%s.%s", domain, base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString([]byte(relayUrl)))
|
||||
domain = fmt.Sprintf("%s.%s",
|
||||
domain, base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString([]byte(relayURL)))
|
||||
}
|
||||
}
|
||||
// create base32 encoded public key
|
||||
@ -173,21 +173,20 @@ func GetPublicKeyBase32(sk string) (string, error) {
|
||||
// setSubscriptions sets up subscriptions for the Exit node to receive incoming events from the specified relays.
|
||||
// It first obtains the public key using the configured Nostr private key.
|
||||
// Then it calls the `handleSubscription` method to open a subscription to the relays with the specified filters.
|
||||
// This method runs in a separate goroutine and continuously handles the incoming events by calling the `processMessage` method.
|
||||
// This method runs in a separate goroutine and continuously handles the incoming events by calling `processMessage`
|
||||
// If the context is canceled before the subscription is established, it returns the context error.
|
||||
// If any errors occur during the process, they are returned.
|
||||
// This method should be called once when starting the Exit node.
|
||||
func (e *Exit) setSubscriptions(ctx context.Context) error {
|
||||
pubKey, err := nostr.GetPublicKey(e.config.NostrPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to get public key: %w", err)
|
||||
}
|
||||
now := nostr.Now()
|
||||
if err = e.handleSubscription(ctx, pubKey, now); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to handle subscription: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// handleSubscription handles the subscription to incoming events from relays based on the provided filters.
|
||||
@ -240,7 +239,7 @@ func (e *Exit) processMessage(ctx context.Context, msg nostr.IncomingEvent) {
|
||||
}
|
||||
protocolMessage, err := protocol.UnmarshalJSON([]byte(decodedMessage))
|
||||
if err != nil {
|
||||
slog.Error("could not unmarshal message")
|
||||
slog.Error("could not unmarshal message", "error", err)
|
||||
return
|
||||
}
|
||||
destination, err := protocol.Parse(protocolMessage.Destination)
|
||||
@ -290,7 +289,10 @@ func (e *Exit) handleConnect(
|
||||
dst, err = net.Dial("tcp", protocolMessage.Destination)
|
||||
if err != nil {
|
||||
slog.Error("could not connect to backend", "error", err)
|
||||
connection.Close()
|
||||
err = connection.Close()
|
||||
if err != nil {
|
||||
slog.Error("could not close connection", "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
154
exit/https.go
154
exit/https.go
@ -8,6 +8,7 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
@ -22,90 +23,123 @@ import (
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
)
|
||||
|
||||
func (e *Exit) StartReverseProxy(httpTarget string, port int32) error {
|
||||
ctx := context.Background()
|
||||
ev := e.pool.QuerySingle(ctx, e.config.NostrRelays, nostr.Filter{
|
||||
const (
|
||||
headerTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
errNoCertificateEvent = errors.New("failed to find encrypted direct message")
|
||||
)
|
||||
|
||||
func (e *Exit) StartReverseProxy(ctx context.Context, httpTarget string, port int32) error {
|
||||
incomingEvent := e.pool.QuerySingle(ctx, e.config.NostrRelays, nostr.Filter{
|
||||
Authors: []string{e.publicKey},
|
||||
Kinds: []int{protocol.KindCertificateEvent},
|
||||
Tags: nostr.TagMap{"p": []string{e.publicKey}},
|
||||
})
|
||||
var cert tls.Certificate
|
||||
if ev == nil {
|
||||
var err error
|
||||
if incomingEvent == nil {
|
||||
certificate, err := e.createAndStoreCertificateData(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert = *certificate
|
||||
} else {
|
||||
slog.Info("found certificate event", "certificate", ev.Content)
|
||||
// load private key from file
|
||||
privateKeyEvent := e.pool.QuerySingle(ctx, e.config.NostrRelays, nostr.Filter{
|
||||
Authors: []string{e.publicKey},
|
||||
Kinds: []int{protocol.KindPrivateKeyEvent},
|
||||
Tags: nostr.TagMap{"p": []string{e.publicKey}},
|
||||
})
|
||||
if privateKeyEvent == nil {
|
||||
return fmt.Errorf("failed to find encrypted direct message")
|
||||
}
|
||||
sharedKey, err := nip04.ComputeSharedSecret(privateKeyEvent.PubKey, e.config.NostrPrivateKey)
|
||||
cert, err = e.handleCertificateEvent(incomingEvent, ctx, cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decodedMessage, err := nip04.Decrypt(privateKeyEvent.Content, sharedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
message, err := protocol.UnmarshalJSON([]byte(decodedMessage))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, _ := pem.Decode(message.Data)
|
||||
if block == nil {
|
||||
fmt.Fprintf(os.Stderr, "error: failed to decode PEM block containing private key\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
|
||||
fmt.Fprintf(os.Stderr, "error: decoded PEM block of type %s, but wanted %s", got, want)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certBlock, _ := pem.Decode([]byte(ev.Content))
|
||||
if certBlock == nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to parse certificate PEM.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert = tls.Certificate{
|
||||
Certificate: [][]byte{certBlock.Bytes},
|
||||
PrivateKey: priv,
|
||||
Leaf: parsedCert,
|
||||
}
|
||||
}
|
||||
target, _ := url.Parse(httpTarget)
|
||||
|
||||
httpsConfig := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
|
||||
Handler: http.HandlerFunc(httputil.NewSingleHostReverseProxy(target).ServeHTTP),
|
||||
ReadHeaderTimeout: headerTimeout,
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
|
||||
Handler: http.HandlerFunc(httputil.NewSingleHostReverseProxy(target).ServeHTTP),
|
||||
}
|
||||
return httpsConfig.ListenAndServeTLS("", "")
|
||||
|
||||
}
|
||||
|
||||
func (e *Exit) handleCertificateEvent(incomingEvent *nostr.IncomingEvent, ctx context.Context, cert tls.Certificate) (tls.Certificate, error) {
|
||||
slog.Info("found certificate event", "certificate", incomingEvent.Content)
|
||||
// load private key from file
|
||||
privateKeyEvent := e.pool.QuerySingle(ctx, e.config.NostrRelays, nostr.Filter{
|
||||
Authors: []string{e.publicKey},
|
||||
Kinds: []int{protocol.KindPrivateKeyEvent},
|
||||
Tags: nostr.TagMap{"p": []string{e.publicKey}},
|
||||
})
|
||||
if privateKeyEvent == nil {
|
||||
return tls.Certificate{}, errNoCertificateEvent
|
||||
}
|
||||
sharedKey, err := nip04.ComputeSharedSecret(privateKeyEvent.PubKey, e.config.NostrPrivateKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to compute shared key: %w", err)
|
||||
}
|
||||
decodedMessage, err := nip04.Decrypt(privateKeyEvent.Content, sharedKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to decrypt private key: %w", err)
|
||||
}
|
||||
message, err := protocol.UnmarshalJSON([]byte(decodedMessage))
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to unmarshal message: %w", err)
|
||||
}
|
||||
block, _ := pem.Decode(message.Data)
|
||||
if block == nil {
|
||||
_, err = fmt.Fprintf(os.Stderr, "error: failed to decode PEM block containing private key\n")
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to write error: %w", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
|
||||
_, err = fmt.Fprintf(os.Stderr, "error: decoded PEM block of type %s, but wanted %s", got, want)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to write error: %w", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
certBlock, _ := pem.Decode([]byte(incomingEvent.Content))
|
||||
if certBlock == nil {
|
||||
_, err = fmt.Fprintf(os.Stderr, "Failed to parse certificate PEM.")
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to write error: %w", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
cert = tls.Certificate{
|
||||
Certificate: [][]byte{certBlock.Bytes},
|
||||
PrivateKey: priv,
|
||||
Leaf: parsedCert,
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
const (
|
||||
tenYears = 0 * 365 * 24 * time.Hour
|
||||
keySize = 2048
|
||||
limit = 128
|
||||
chmod = 0644
|
||||
)
|
||||
|
||||
func (e *Exit) createAndStoreCertificateData(ctx context.Context) (*tls.Certificate, error) {
|
||||
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||
priv, _ := rsa.GenerateKey(rand.Reader, keySize)
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(10 * 365 * 24 * time.Hour)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
notAfter := notBefore.Add(tenYears)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), limit)
|
||||
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
||||
domain, _ := e.getDomain()
|
||||
|
||||
@ -126,7 +160,7 @@ func (e *Exit) createAndStoreCertificateData(ctx context.Context) (*tls.Certific
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
// save key pem to file
|
||||
err := os.WriteFile(fmt.Sprintf("%s.key", e.publicKey), keyPEM, 0644)
|
||||
err := os.WriteFile(fmt.Sprintf("%s.key", e.publicKey), keyPEM, chmod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package exit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -33,8 +33,8 @@ func (mm *MutexMap) Unlock(id string) {
|
||||
mutex, ok := mm.m[id]
|
||||
mm.mu.Unlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("tried to unlock mutex for non-existent id %s", id))
|
||||
slog.Error("mutex not found", "id", id)
|
||||
return
|
||||
}
|
||||
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package exit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -12,9 +14,11 @@ import (
|
||||
|
||||
const ten = 10
|
||||
|
||||
var errNoPublicKey = errors.New("no public key found")
|
||||
|
||||
func (e *Exit) announceExitNode(ctx context.Context) error {
|
||||
if !e.config.Public {
|
||||
return nil
|
||||
return errNoPublicKey
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
@ -45,25 +49,28 @@ func (e *Exit) announceExitNode(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Exit) DeleteEvent(ctx context.Context, ev *nostr.Event) error {
|
||||
func (e *Exit) DeleteEvent(ctx context.Context, event *nostr.Event) error {
|
||||
for _, responseRelay := range e.config.NostrRelays {
|
||||
var relay *nostr.Relay
|
||||
relay, err := e.pool.EnsureRelay(responseRelay)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to ensure relay: %w", err)
|
||||
}
|
||||
event := nostr.Event{
|
||||
CreatedAt: nostr.Now(),
|
||||
PubKey: e.publicKey,
|
||||
Kind: nostr.KindDeletion,
|
||||
Tags: nostr.Tags{
|
||||
nostr.Tag{"e", ev.ID},
|
||||
nostr.Tag{"e", event.ID},
|
||||
},
|
||||
}
|
||||
err = event.Sign(e.config.NostrPrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign event: %w", err)
|
||||
}
|
||||
err = relay.Publish(ctx, event)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to publish event: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
Loading…
Reference in New Issue
Block a user