From 6cd1285ca0276bebd407110c37031bc4622f0b79 Mon Sep 17 00:00:00 2001 From: idk Date: Mon, 23 Sep 2024 13:28:28 -0400 Subject: Adds support for listening on and connecting to I2P and Onion services securely (#3293) This PR adds 2 `dendrite-demo` main's, each designed expressly to serve a Hidden Service/Overlay network. The first, `dendrite-demo-i2p` add self-configuration for use of dendrite as an I2P hidden service(eepsite) and to connect to I2P services(federate) as an I2P client. It further disables the `dendrite` server from communicating with non-anonymous servers by federation(because I2P does not canonically have the ability to exit, we rely on donors for exit traffic), and enables the use of self-signed TLS certificates([because I2P services are self-authenticating but TLS is still required for other aspects of the system to work reliably](https://tor.stackexchange.com/questions/13887/registering-onion-with-certificate-authority)). This demo turns the system into an "pseudonymous" homeserver which people can connect to using an I2P-enabled Matrix client(I like `cinny` and it's what I tested with). The second, `dendrite-demo-tor` adds self-configuration for the use of dendrite as an Onion service and to connect to other onion services and non-anonymous web sites using Tor to obfuscate it's physical location and providing, optionally, pseudonymity. It also enables the use of self-signed TLS certificates, for the same reason as with I2P, because onion services aren't typically eligible for TLS certificates. It has also been tested with `cinny`. These services are both pseudonymous like myself, not anonymous. I will be meeting members of the element team at the CCC assembly shortly to discuss contributing under my pseudonym. As none of the other `dendrite-demo` have unit tests I did not add them to these checkins. * [*] I have added Go unit tests or [Complement integration tests](https://github.com/matrix-org/complement) for this PR _or_ I have justified why this PR doesn't need tests --------- Co-authored-by: eyedeekay Co-authored-by: Till Faelligen <2353100+S7evinK@users.noreply.github.com> --- contrib/dendrite-demo-i2p/main_i2p.go | 233 ++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 contrib/dendrite-demo-i2p/main_i2p.go (limited to 'contrib/dendrite-demo-i2p/main_i2p.go') diff --git a/contrib/dendrite-demo-i2p/main_i2p.go b/contrib/dendrite-demo-i2p/main_i2p.go new file mode 100644 index 00000000..72fed656 --- /dev/null +++ b/contrib/dendrite-demo-i2p/main_i2p.go @@ -0,0 +1,233 @@ +// Copyright 2017 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "context" + "crypto/tls" + "embed" + "net" + "net/http" + "net/url" + "strings" + "sync/atomic" + "text/template" + + "github.com/cretz/bine/tor" + "github.com/eyedeekay/goSam" + "github.com/eyedeekay/onramp" + sentryhttp "github.com/getsentry/sentry-go/http" + "github.com/gorilla/mux" + "github.com/kardianos/minwinsvc" + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/httputil" + "github.com/matrix-org/dendrite/setup/process" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/sirupsen/logrus" + + basepkg "github.com/matrix-org/dendrite/setup/base" + "github.com/matrix-org/dendrite/setup/config" +) + +func client() (*goSam.Client, error) { + if skip { + return nil, nil + } + return goSam.NewClient(*samAddr) +} + +var sam, samError = client() + +func start() (*tor.Tor, error) { + if skip { + return nil, nil + } + return tor.Start(context.Background(), nil) +} + +func dialer() (*tor.Dialer, error) { + if skip { + return nil, nil + } + return t.Dialer(context.TODO(), nil) +} + +var ( + t, terr = start() + tdialer, tderr = dialer() +) + +// Dial a network connection to an I2P server or a unix socket. Use Tor, or Fail for clearnet addresses. +func DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + if samError != nil { + return nil, samError + } + if network == "unix" { + return net.Dial(network, addr) + } + + // convert the addr to a full URL + url, err := url.Parse(addr) + if err != nil { + return nil, err + } + if strings.HasSuffix(url.Host, ".i2p") { + return sam.DialContext(ctx, network, addr) + } + if terr != nil { + return nil, terr + } + if (tderr != nil) || (tdialer == nil) { + return nil, tderr + } + return tdialer.DialContext(ctx, network, addr) +} + +//go:embed static/*.gotmpl +var staticContent embed.FS + +// SetupAndServeHTTPS sets up the HTTPS server to serve client & federation APIs +// and adds a prometheus handler under /_dendrite/metrics. +func SetupAndServeHTTPS( + processContext *process.ProcessContext, + cfg *config.Dendrite, + routers httputil.Routers, +) { + // create a transport that uses SAM to dial TCP Connections + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: DialContext, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + + http.DefaultClient = httpClient + + garlic, err := onramp.NewGarlic("dendrite", *samAddr, onramp.OPT_HUGE) + if err != nil { + logrus.WithError(err).Fatal("failed to create garlic") + } + defer garlic.Close() // nolint: errcheck + listener, err := garlic.ListenTLS() + if err != nil { + logrus.WithError(err).Fatal("failed to serve HTTPS") + } + defer listener.Close() // nolint: errcheck + + externalHTTPSAddr := config.ServerAddress{} + https, err := config.HTTPAddress("https://" + listener.Addr().String()) + if err != nil { + logrus.WithError(err).Fatalf("Failed to parse http address") + } + externalHTTPSAddr = https + + externalRouter := mux.NewRouter().SkipClean(true).UseEncodedPath() + + externalServ := &http.Server{ + Addr: externalHTTPSAddr.Address, + WriteTimeout: basepkg.HTTPServerTimeout, + Handler: externalRouter, + BaseContext: func(_ net.Listener) context.Context { + return processContext.Context() + }, + } + + // Redirect for Landing Page + externalRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, httputil.PublicStaticPath, http.StatusFound) + }) + + if cfg.Global.Metrics.Enabled { + externalRouter.Handle("/metrics", httputil.WrapHandlerInBasicAuth(promhttp.Handler(), cfg.Global.Metrics.BasicAuth)) + } + + basepkg.ConfigureAdminEndpoints(processContext, routers) + + // Parse and execute the landing page template + tmpl := template.Must(template.ParseFS(staticContent, "static/*.gotmpl")) + landingPage := &bytes.Buffer{} + if err := tmpl.ExecuteTemplate(landingPage, "index.gotmpl", map[string]string{ + "Version": internal.VersionString(), + }); err != nil { + logrus.WithError(err).Fatal("failed to execute landing page template") + } + + routers.Static.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(landingPage.Bytes()) + }) + + var clientHandler http.Handler + clientHandler = routers.Client + if cfg.Global.Sentry.Enabled { + sentryHandler := sentryhttp.New(sentryhttp.Options{ + Repanic: true, + }) + clientHandler = sentryHandler.Handle(routers.Client) + } + var federationHandler http.Handler + federationHandler = routers.Federation + if cfg.Global.Sentry.Enabled { + sentryHandler := sentryhttp.New(sentryhttp.Options{ + Repanic: true, + }) + federationHandler = sentryHandler.Handle(routers.Federation) + } + externalRouter.PathPrefix(httputil.DendriteAdminPathPrefix).Handler(routers.DendriteAdmin) + externalRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(clientHandler) + if !cfg.Global.DisableFederation { + externalRouter.PathPrefix(httputil.PublicKeyPathPrefix).Handler(routers.Keys) + externalRouter.PathPrefix(httputil.PublicFederationPathPrefix).Handler(federationHandler) + } + externalRouter.PathPrefix(httputil.SynapseAdminPathPrefix).Handler(routers.SynapseAdmin) + externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(routers.Media) + externalRouter.PathPrefix(httputil.PublicWellKnownPrefix).Handler(routers.WellKnown) + externalRouter.PathPrefix(httputil.PublicStaticPath).Handler(routers.Static) + + externalRouter.NotFoundHandler = httputil.NotFoundCORSHandler + externalRouter.MethodNotAllowedHandler = httputil.NotAllowedHandler + + if externalHTTPSAddr.Enabled() { + go func() { + var externalShutdown atomic.Bool // RegisterOnShutdown can be called more than once + logrus.Infof("Starting external listener on https://%s", externalServ.Addr) + processContext.ComponentStarted() + externalServ.RegisterOnShutdown(func() { + if externalShutdown.CompareAndSwap(false, true) { + processContext.ComponentFinished() + logrus.Infof("Stopped external HTTPS listener") + } + }) + addr := listener.Addr() + externalServ.Addr = addr.String() + if err := externalServ.Serve(listener); err != nil { + if err != http.ErrServerClosed { + logrus.WithError(err).Fatal("failed to serve HTTPS") + } + } + + logrus.Infof("Stopped external listener on %s", externalServ.Addr) + }() + } + + minwinsvc.SetOnExit(processContext.ShutdownDendrite) + <-processContext.WaitForShutdown() + + logrus.Infof("Stopping HTTPS listeners") + _ = externalServ.Shutdown(context.Background()) + logrus.Infof("Stopped HTTPS listeners") +} -- cgit v1.2.3