mirror of
https://github.com/asmogo/nws.git
synced 2025-01-18 18:11:33 +00:00
Add HTTPS reverse proxy support
This update includes modifications to several files to support the implementation of an HTTPS reverse proxy.
This commit is contained in:
parent
081f678a10
commit
e6d48a6422
@ -3,11 +3,24 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/asmogo/nws/config"
|
"github.com/asmogo/nws/config"
|
||||||
"github.com/asmogo/nws/exit"
|
"github.com/asmogo/nws/exit"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var httpsPort int32
|
||||||
|
var httpTarget string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
rootCmd := &cobra.Command{Use: "exit", Run: startExitNode}
|
||||||
|
rootCmd.Flags().Int32VarP(&httpsPort, "port", "p", 0, "port for the https reverse proxy")
|
||||||
|
rootCmd.Flags().StringVarP(&httpTarget, "target", "t", "", "target for the https reverse proxy (your local service)")
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func startExitNode(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
// load the configuration
|
// load the configuration
|
||||||
// from the environment
|
// from the environment
|
||||||
cfg, err := config.LoadConfig[config.ExitConfig]()
|
cfg, err := config.LoadConfig[config.ExitConfig]()
|
||||||
@ -17,7 +30,17 @@ func main() {
|
|||||||
|
|
||||||
// create a new gw server
|
// create a new gw server
|
||||||
// and start it
|
// and start it
|
||||||
ctx := context.Background()
|
ctx := cmd.Context()
|
||||||
exitNode := exit.NewExit(ctx, cfg)
|
exitNode := exit.NewExit(ctx, cfg)
|
||||||
|
if httpsPort != 0 {
|
||||||
|
slog.Info("starting exit node with https reverse proxy", "port", httpsPort)
|
||||||
|
go func() {
|
||||||
|
err = exitNode.StartReverseProxy(httpTarget, httpsPort)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
exitNode.ListenAndServe(ctx)
|
exitNode.ListenAndServe(ctx)
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,18 @@ services:
|
|||||||
- NOSTR_RELAYS=ws://nostr-relay:8080
|
- NOSTR_RELAYS=ws://nostr-relay:8080
|
||||||
- NOSTR_PRIVATE_KEY=003632642b6df1bb7f150c25aae079d590e6cfcceca924304154fbc2a3a938e3
|
- NOSTR_PRIVATE_KEY=003632642b6df1bb7f150c25aae079d590e6cfcceca924304154fbc2a3a938e3
|
||||||
- BACKEND_HOST=mint:3338
|
- BACKEND_HOST=mint:3338
|
||||||
|
exit-https:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: cmd/exit/Dockerfile
|
||||||
|
container_name: exit-https
|
||||||
|
command: ["./exit", "--port", "4443", "--target", "http://mint:3338"]
|
||||||
|
networks:
|
||||||
|
nostr:
|
||||||
|
environment:
|
||||||
|
- NOSTR_RELAYS=ws://nostr-relay:8080
|
||||||
|
- NOSTR_PRIVATE_KEY=213632642b6df1bb7f150c25aae079d590e6cfcceca924304154fbc2a3a938e3
|
||||||
|
- BACKEND_HOST=localhost:4443
|
||||||
proxy:
|
proxy:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@ -45,7 +57,6 @@ services:
|
|||||||
nostr:
|
nostr:
|
||||||
environment:
|
environment:
|
||||||
- NOSTR_RELAYS=ws://nostr-relay:8080
|
- NOSTR_RELAYS=ws://nostr-relay:8080
|
||||||
- NOSTR_PRIVATE_KEY=b0aceff311951aaa014c3296f4346f91f7fd4fc17396e060acbb48d2f42ef1fe
|
|
||||||
nostr:
|
nostr:
|
||||||
image: scsibug/nostr-rs-relay:latest
|
image: scsibug/nostr-rs-relay:latest
|
||||||
container_name: nostr-relay
|
container_name: nostr-relay
|
||||||
|
@ -41,6 +41,9 @@ type Exit struct {
|
|||||||
|
|
||||||
// incomingChannel represents a channel used to receive incoming events from relays.
|
// incomingChannel represents a channel used to receive incoming events from relays.
|
||||||
incomingChannel chan nostr.IncomingEvent
|
incomingChannel chan nostr.IncomingEvent
|
||||||
|
|
||||||
|
nprofile string
|
||||||
|
publicKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExit creates a new Exit node with the provided context and config.
|
// NewExit creates a new Exit node with the provided context and config.
|
||||||
@ -76,6 +79,8 @@ func NewExit(ctx context.Context, config *config.ExitConfig) *Exit {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
exit.nprofile = profile
|
||||||
|
exit.publicKey = pubKey
|
||||||
slog.Info("created exit node", "profile", profile)
|
slog.Info("created exit node", "profile", profile)
|
||||||
err = exit.setSubscriptions(ctx)
|
err = exit.setSubscriptions(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
214
exit/https.go
Normal file
214
exit/https.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package exit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"github.com/asmogo/nws/protocol"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip04"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *Exit) DeleteEvent(ctx context.Context, ev *nostr.Event) error {
|
||||||
|
for _, responseRelay := range e.config.NostrRelays {
|
||||||
|
var relay *nostr.Relay
|
||||||
|
relay, err := e.pool.EnsureRelay(responseRelay)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
event := nostr.Event{
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
PubKey: e.publicKey,
|
||||||
|
Kind: nostr.KindDeletion,
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"e", ev.ID},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = event.Sign(e.config.NostrPrivateKey)
|
||||||
|
err = relay.Publish(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exit) StartReverseProxy(httpTarget string, port int32) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
ev := e.pool.QuerySingle(ctx, e.config.NostrRelays, nostr.Filter{
|
||||||
|
Authors: []string{e.publicKey},
|
||||||
|
Kinds: []int{nostr.KindTextNote},
|
||||||
|
Tags: nostr.TagMap{"p": []string{e.nprofile}},
|
||||||
|
})
|
||||||
|
var cert tls.Certificate
|
||||||
|
if ev == nil {
|
||||||
|
certificate, err := e.createAndStoreCertificateData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert = *certificate
|
||||||
|
} else {
|
||||||
|
// load private key from file
|
||||||
|
privateKeyEvent := e.pool.QuerySingle(ctx, e.config.NostrRelays, nostr.Filter{
|
||||||
|
Authors: []string{e.publicKey},
|
||||||
|
Kinds: []int{nostr.KindEncryptedDirectMessage},
|
||||||
|
Tags: nostr.TagMap{"p": []string{e.nprofile}},
|
||||||
|
})
|
||||||
|
if privateKeyEvent == nil {
|
||||||
|
return fmt.Errorf("failed to find encrypted direct message")
|
||||||
|
}
|
||||||
|
sharedKey, err := nip04.ComputeSharedSecret(privateKeyEvent.PubKey, e.config.NostrPrivateKey)
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
return httpsConfig.ListenAndServeTLS("", "")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exit) createAndStoreCertificateData(ctx context.Context) (*tls.Certificate, error) {
|
||||||
|
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"NWS"},
|
||||||
|
},
|
||||||
|
NotBefore: notBefore,
|
||||||
|
NotAfter: notAfter,
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
certBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||||
|
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.nprofile), keyPEM, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert, _ := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
|
certificate, err := e.storeCertificate(ctx, certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return certificate, err
|
||||||
|
}
|
||||||
|
err = e.storePrivateKey(ctx, keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return certificate, err
|
||||||
|
}
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exit) storePrivateKey(ctx context.Context, keyPEM []byte) error {
|
||||||
|
s, err := protocol.NewEventSigner(e.config.NostrPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
event, err := s.CreateSignedEvent(e.publicKey, nostr.KindEncryptedDirectMessage, nostr.Tags{
|
||||||
|
nostr.Tag{"p", e.nprofile},
|
||||||
|
}, protocol.WithData(keyPEM))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, responseRelay := range e.config.NostrRelays {
|
||||||
|
var relay *nostr.Relay
|
||||||
|
relay, err = e.pool.EnsureRelay(responseRelay)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = relay.Publish(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (e *Exit) storeCertificate(ctx context.Context, certPEM []byte) (*tls.Certificate, error) {
|
||||||
|
event := nostr.Event{
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
PubKey: e.publicKey,
|
||||||
|
Kind: nostr.KindTextNote,
|
||||||
|
Content: string(certPEM),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"p", e.nprofile},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := event.Sign(e.config.NostrPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, responseRelay := range e.config.NostrRelays {
|
||||||
|
var relay *nostr.Relay
|
||||||
|
relay, err = e.pool.EnsureRelay(responseRelay)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = relay.Publish(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -171,7 +171,7 @@ func (nc *NostrConnection) handleNostrWrite(b []byte, err error) (int, error) {
|
|||||||
protocol.WithType(protocol.MessageTypeSocks5),
|
protocol.WithType(protocol.MessageTypeSocks5),
|
||||||
protocol.WithData(b),
|
protocol.WithData(b),
|
||||||
}
|
}
|
||||||
ev, err := signer.CreateSignedEvent(publicKey, nostr.Tags{nostr.Tag{"p", publicKey}}, opts...)
|
ev, err := signer.CreateSignedEvent(publicKey, protocol.KindEphemeralEvent, nostr.Tags{nostr.Tag{"p", publicKey}}, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func DialSocks(pool *nostr.SimplePool) func(ctx context.Context, net_, addr stri
|
|||||||
protocol.WithUUID(connectionID),
|
protocol.WithUUID(connectionID),
|
||||||
protocol.WithDestination(addr),
|
protocol.WithDestination(addr),
|
||||||
}
|
}
|
||||||
ev, err := signer.CreateSignedEvent(publicKey,
|
ev, err := signer.CreateSignedEvent(publicKey, protocol.KindEphemeralEvent,
|
||||||
nostr.Tags{nostr.Tag{"p", publicKey}},
|
nostr.Tags{nostr.Tag{"p", publicKey}},
|
||||||
opts...)
|
opts...)
|
||||||
|
|
||||||
|
@ -34,11 +34,11 @@ func NewEventSigner(privateKey string) (*EventSigner, error) {
|
|||||||
|
|
||||||
// CreateEvent creates a new Event with the provided tags. The Public Key and the
|
// CreateEvent creates a new Event with the provided tags. The Public Key and the
|
||||||
// current timestamp are set automatically. The Kind is set to KindEphemeralEvent.
|
// current timestamp are set automatically. The Kind is set to KindEphemeralEvent.
|
||||||
func (s *EventSigner) CreateEvent(tags nostr.Tags) nostr.Event {
|
func (s *EventSigner) CreateEvent(kind int, tags nostr.Tags) nostr.Event {
|
||||||
return nostr.Event{
|
return nostr.Event{
|
||||||
PubKey: s.PublicKey,
|
PubKey: s.PublicKey,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Kind: KindEphemeralEvent,
|
Kind: kind,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ func (s *EventSigner) CreateEvent(tags nostr.Tags) nostr.Event {
|
|||||||
// The encrypted message is set as the content of the event.
|
// The encrypted message is set as the content of the event.
|
||||||
// Finally, the event is signed with the private key of the EventSigner, setting the event ID and event Sig fields.
|
// Finally, the event is signed with the private key of the EventSigner, setting the event ID and event Sig fields.
|
||||||
// The signed event is returned along with any error that occurs.
|
// The signed event is returned along with any error that occurs.
|
||||||
func (s *EventSigner) CreateSignedEvent(targetPublicKey string, tags nostr.Tags, opts ...MessageOption) (nostr.Event, error) {
|
func (s *EventSigner) CreateSignedEvent(targetPublicKey string, kind int, tags nostr.Tags, opts ...MessageOption) (nostr.Event, error) {
|
||||||
sharedKey, err := nip04.ComputeSharedSecret(targetPublicKey, s.privateKey)
|
sharedKey, err := nip04.ComputeSharedSecret(targetPublicKey, s.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nostr.Event{}, err
|
return nostr.Event{}, err
|
||||||
@ -64,7 +64,7 @@ func (s *EventSigner) CreateSignedEvent(targetPublicKey string, tags nostr.Tags,
|
|||||||
return nostr.Event{}, err
|
return nostr.Event{}, err
|
||||||
}
|
}
|
||||||
encryptedMessage, err := nip04.Encrypt(string(messageJson), sharedKey)
|
encryptedMessage, err := nip04.Encrypt(string(messageJson), sharedKey)
|
||||||
ev := s.CreateEvent(tags)
|
ev := s.CreateEvent(kind, tags)
|
||||||
ev.Content = encryptedMessage
|
ev.Content = encryptedMessage
|
||||||
// calling Sign sets the event ID field and the event Sig field
|
// calling Sign sets the event ID field and the event Sig field
|
||||||
err = ev.Sign(s.privateKey)
|
err = ev.Sign(s.privateKey)
|
||||||
|
Loading…
Reference in New Issue
Block a user