init
This commit is contained in:
commit
f325ed5cf3
|
@ -0,0 +1,197 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// go-tunsrv hosts the database and p2p server for replication.
|
||||
// It supplies various flags to contol options.
|
||||
// See 'go-tunsrv -h' for a list and their usage.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
// debug
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// flags
|
||||
flagDisableUNIXSock bool
|
||||
|
||||
listenAddr string
|
||||
debugAddr string
|
||||
logToFile string
|
||||
repoDir string
|
||||
|
||||
// helper
|
||||
log kitlog.Logger
|
||||
|
||||
// juicy bits
|
||||
appKey string
|
||||
)
|
||||
|
||||
// Version and Build are set by ldflags
|
||||
var (
|
||||
Version = "snapshot"
|
||||
Build = ""
|
||||
|
||||
flagPrintVersion bool
|
||||
)
|
||||
|
||||
func checkFatal(err error) {
|
||||
checkAndLog(err)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func checkAndLog(err error) {
|
||||
if err != nil {
|
||||
level.Error(log).Log("event", "fatal error", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func initFlags() {
|
||||
u, err := user.Current()
|
||||
checkFatal(err)
|
||||
|
||||
flag.StringVar(&appKey, "shscap", "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=", "secret-handshake app-key (or capability)")
|
||||
|
||||
flag.StringVar(&listenAddr, "l", ":8008", "address to listen on")
|
||||
|
||||
flag.BoolVar(&flagDisableUNIXSock, "nounixsock", false, "disable the UNIX socket RPC interface")
|
||||
|
||||
flag.StringVar(&repoDir, "repo", filepath.Join(u.HomeDir, ".ssb-go"), "where to put the log and indexes")
|
||||
|
||||
flag.StringVar(&debugAddr, "dbg", "localhost:6078", "listen addr for metrics and pprof HTTP server")
|
||||
flag.StringVar(&logToFile, "path", "", "where to write debug output to (otherwise just stderr)")
|
||||
|
||||
flag.BoolVar(&flagPrintVersion, "version", false, "print version number and build date")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if logToFile != "" {
|
||||
logDir := filepath.Join(repoDir, logToFile)
|
||||
os.MkdirAll(logDir, 0700) // nearly everything is a log here so..
|
||||
logFileName := fmt.Sprintf("%s-%s.log",
|
||||
filepath.Base(os.Args[0]),
|
||||
time.Now().Format("2006-01-02_15-04"))
|
||||
logFile, err := os.Create(filepath.Join(logDir, logFileName))
|
||||
if err != nil {
|
||||
panic(err) // logging not ready yet...
|
||||
}
|
||||
log = kitlog.NewJSONLogger(logFile)
|
||||
} else {
|
||||
log = kitlog.NewJSONLogger(os.Stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func runtunsrv() error {
|
||||
// DEBUGGING
|
||||
// runtime.SetMutexProfileFraction(1)
|
||||
// runtime.SetBlockProfileRate(1)
|
||||
// DEBUGGING
|
||||
|
||||
initFlags()
|
||||
|
||||
if flagPrintVersion {
|
||||
log.Log("version", Version, "build", Build)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
ak, err := base64.StdEncoding.DecodeString(appKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "application key")
|
||||
}
|
||||
|
||||
if !flagDisableUNIXSock {
|
||||
// opts = append(opts, mktunsrv.LateOption(mktunsrv.WithUNIXSocket()))
|
||||
}
|
||||
|
||||
// if dbgLogDir != "" {
|
||||
// opts = append(opts, mktunsrv.WithPostSecureConnWrapper(func(conn net.Conn) (net.Conn, error) {
|
||||
// parts := strings.Split(conn.RemoteAddr().String(), "|")
|
||||
|
||||
// if len(parts) != 2 {
|
||||
// return conn, nil
|
||||
// }
|
||||
|
||||
// muxrpcDumpDir := filepath.Join(
|
||||
// repoDir,
|
||||
// dbgLogDir,
|
||||
// parts[1], // key first
|
||||
// parts[0],
|
||||
// )
|
||||
|
||||
// return debug.WrapDump(muxrpcDumpDir, conn)
|
||||
// }))
|
||||
// }
|
||||
|
||||
if debugAddr != "" {
|
||||
go func() {
|
||||
// http.Handle("/metrics", promhttp.Handler())
|
||||
log.Log("starting", "metrics", "addr", debugAddr)
|
||||
err := http.ListenAndServe(debugAddr, nil)
|
||||
checkAndLog(err)
|
||||
}()
|
||||
}
|
||||
|
||||
tunsrv, err := makeTunnelServer.New(opts...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to instantiate ssb server")
|
||||
}
|
||||
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-c
|
||||
level.Warn(log).Log("event", "killed", "msg", "received signal, shutting down", "signal", sig.String())
|
||||
cancel()
|
||||
tunsrv.Shutdown()
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
err := tunsrv.Close()
|
||||
checkAndLog(err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
level.Info(log).Log("event", "serving", "ID", id.Ref(), "addr", listenAddr, "version", Version, "build", Build)
|
||||
for {
|
||||
// Note: This is where the serving starts ;)
|
||||
err = tunsrv.Network.Serve(ctx)
|
||||
if err != nil {
|
||||
level.Warn(log).Log("event", "tunsrv node.Serve returned", "err", err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err := tunsrv.Close()
|
||||
return err
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := runtunsrv(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go-ssb-tunnel: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package tunnel
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrShuttingDown = errors.New("go-ssb-tunnel: shutting down") // this is fine
|
|
@ -0,0 +1,21 @@
|
|||
module go.mindeco.de/ssb-tunnel
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/go-kit/kit v0.10.0
|
||||
github.com/keks/nocomment v0.0.0-20181007001506-30c6dcb4a472
|
||||
github.com/pkg/errors v0.9.1
|
||||
go.cryptoscope.co/secretstream v1.2.2
|
||||
go.cryptoscope.co/ssb v0.0.0-20201207161753-31d0f24b7a79
|
||||
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
)
|
||||
|
||||
// We need our internal/extra25519 since agl pulled his repo recently.
|
||||
// Issue: https://github.com/cryptoscope/ssb/issues/44
|
||||
// Ours uses a fork of x/crypto where edwards25519 is not an internal package,
|
||||
// This seemed like the easiest change to port agl's extra25519 to use x/crypto
|
||||
// Background: https://github.com/agl/ed25519/issues/27#issuecomment-591073699
|
||||
// The branch in use: https://github.com/cryptix/golang_x_crypto/tree/non-internal-edwards
|
||||
replace golang.org/x/crypto => github.com/cryptix/golang_x_crypto v0.0.0-20200924101112-886946aabeb8
|
|
@ -0,0 +1,161 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package keys could be it's own thing between go-ssb and this but not today
|
||||
package keys
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/keks/nocomment"
|
||||
"go.cryptoscope.co/secretstream/secrethandshake"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
var SecretPerms = os.FileMode(0600)
|
||||
|
||||
type KeyPair struct {
|
||||
Id *refs.FeedRef
|
||||
Pair secrethandshake.EdKeyPair
|
||||
}
|
||||
|
||||
// the format of the .ssb/secret file as defined by the js implementations
|
||||
type ssbSecret struct {
|
||||
Curve string `json:"curve"`
|
||||
ID *refs.FeedRef `json:"id"`
|
||||
Private string `json:"private"`
|
||||
Public string `json:"public"`
|
||||
}
|
||||
|
||||
// IsValidFeedFormat checks if the passed FeedRef is for one of the two supported formats,
|
||||
// legacy/crapp or GabbyGrove.
|
||||
func IsValidFeedFormat(r *refs.FeedRef) error {
|
||||
if r.Algo != refs.RefAlgoFeedSSB1 && r.Algo != refs.RefAlgoFeedGabby {
|
||||
return fmt.Errorf("ssb: unsupported feed format:%s", r.Algo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewKeyPair generates a fresh KeyPair using the passed io.Reader as a seed.
|
||||
// Passing nil is fine and will use crypto/rand.
|
||||
func NewKeyPair(r io.Reader) (*KeyPair, error) {
|
||||
// generate new keypair
|
||||
kp, err := secrethandshake.GenEdKeyPair(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssb: error building key pair: %w", err)
|
||||
}
|
||||
|
||||
keyPair := KeyPair{
|
||||
Id: &refs.FeedRef{ID: kp.Public[:], Algo: refs.RefAlgoFeedSSB1},
|
||||
Pair: *kp,
|
||||
}
|
||||
|
||||
return &keyPair, nil
|
||||
}
|
||||
|
||||
// SaveKeyPair serializes the passed KeyPair to path.
|
||||
// It errors if path already exists.
|
||||
func SaveKeyPair(kp *KeyPair, path string) error {
|
||||
if err := IsValidFeedFormat(kp.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return fmt.Errorf("ssb.SaveKeyPair: key already exists:%q", path)
|
||||
}
|
||||
err := os.MkdirAll(filepath.Dir(path), 0700)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("failed to create folder for keypair: %w", err)
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, SecretPerms)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssb.SaveKeyPair: failed to create file: %w", err)
|
||||
}
|
||||
|
||||
if err := EncodeKeyPairAsJSON(kp, f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("ssb.SaveKeyPair: failed to close file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeKeyPairAsJSON serializes the passed Keypair into the writer w
|
||||
func EncodeKeyPairAsJSON(kp *KeyPair, w io.Writer) error {
|
||||
var sec = ssbSecret{
|
||||
Curve: "ed25519",
|
||||
ID: kp.Id,
|
||||
Private: base64.StdEncoding.EncodeToString(kp.Pair.Secret[:]) + ".ed25519",
|
||||
Public: base64.StdEncoding.EncodeToString(kp.Pair.Public[:]) + ".ed25519",
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(sec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssb.EncodeKeyPairAsJSON: encoding failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadKeyPair opens fname, ignores any line starting with # and passes it ParseKeyPair
|
||||
func LoadKeyPair(fname string) (*KeyPair, error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ssb.LoadKeyPair: could not open key file %s: %w", fname, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssb.LoadKeyPair: could not stat key file %s: %w", fname, err)
|
||||
}
|
||||
|
||||
if perms := info.Mode().Perm(); perms != SecretPerms {
|
||||
return nil, fmt.Errorf("ssb.LoadKeyPair: expected key file permissions %s, but got %s", SecretPerms, perms)
|
||||
}
|
||||
|
||||
return ParseKeyPair(nocomment.NewReader(f))
|
||||
}
|
||||
|
||||
// ParseKeyPair json decodes an object from the reader.
|
||||
// It expects std base64 encoded data under the `private` and `public` fields.
|
||||
func ParseKeyPair(r io.Reader) (*KeyPair, error) {
|
||||
var s ssbSecret
|
||||
if err := json.NewDecoder(r).Decode(&s); err != nil {
|
||||
return nil, fmt.Errorf("ssb.Parse: JSON decoding failed: %w", err)
|
||||
}
|
||||
|
||||
if err := IsValidFeedFormat(s.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
public, err := base64.StdEncoding.DecodeString(strings.TrimSuffix(s.Public, ".ed25519"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssb.Parse: base64 decode of public part failed: %w", err)
|
||||
}
|
||||
|
||||
private, err := base64.StdEncoding.DecodeString(strings.TrimSuffix(s.Private, ".ed25519"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssb.Parse: base64 decode of private part failed: %w", err)
|
||||
}
|
||||
|
||||
pair, err := secrethandshake.NewKeyPair(public, private)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssb.Parse: base64 decode of private part failed: %w", err)
|
||||
}
|
||||
|
||||
ssbkp := KeyPair{
|
||||
Id: s.ID,
|
||||
Pair: *pair,
|
||||
}
|
||||
return &ssbkp, nil
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.cryptoscope.co/ssb"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
func DefaultKeyPair(r Interface) (*ssb.KeyPair, error) {
|
||||
secPath := r.GetPath("secret")
|
||||
keyPair, err := ssb.LoadKeyPair(secPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("repo: error opening key pair: %w", err)
|
||||
}
|
||||
keyPair, err = ssb.NewKeyPair(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repo: no keypair but couldn't create one either: %w", err)
|
||||
}
|
||||
if err := ssb.SaveKeyPair(keyPair, secPath); err != nil {
|
||||
return nil, fmt.Errorf("repo: error saving new identity file: %w", err)
|
||||
}
|
||||
log.Printf("saved identity %s to %s", keyPair.Id.Ref(), secPath)
|
||||
}
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
func NewKeyPair(r Interface, name, algo string) (*ssb.KeyPair, error) {
|
||||
return newKeyPair(r, name, algo, nil)
|
||||
}
|
||||
|
||||
func NewKeyPairFromSeed(r Interface, name, algo string, seed io.Reader) (*ssb.KeyPair, error) {
|
||||
return newKeyPair(r, name, algo, seed)
|
||||
}
|
||||
|
||||
func newKeyPair(r Interface, name, algo string, seed io.Reader) (*ssb.KeyPair, error) {
|
||||
var secPath string
|
||||
if name == "-" {
|
||||
secPath = r.GetPath("secret")
|
||||
} else {
|
||||
secPath = r.GetPath("secrets", name)
|
||||
err := os.MkdirAll(filepath.Dir(secPath), 0700)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if algo != refs.RefAlgoFeedSSB1 && algo != refs.RefAlgoFeedGabby { // enums would be nice
|
||||
return nil, fmt.Errorf("invalid feed refrence algo")
|
||||
}
|
||||
if _, err := ssb.LoadKeyPair(secPath); err == nil {
|
||||
return nil, fmt.Errorf("new key-pair name already taken")
|
||||
}
|
||||
keyPair, err := ssb.NewKeyPair(seed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("repo: no keypair but couldn't create one either: %w", err)
|
||||
}
|
||||
keyPair.Id.Algo = algo
|
||||
if err := ssb.SaveKeyPair(keyPair, secPath); err != nil {
|
||||
return nil, fmt.Errorf("repo: error saving new identity file: %w", err)
|
||||
}
|
||||
log.Printf("saved identity %s to %s", keyPair.Id.Ref(), secPath)
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
func LoadKeyPair(r Interface, name string) (*ssb.KeyPair, error) {
|
||||
secPath := r.GetPath("secrets", name)
|
||||
keyPair, err := ssb.LoadKeyPair(secPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Load: failed to open %q: %w", secPath, err)
|
||||
}
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
func AllKeyPairs(r Interface) (map[string]*ssb.KeyPair, error) {
|
||||
kps := make(map[string]*ssb.KeyPair)
|
||||
err := filepath.Walk(r.GetPath("secrets"), func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if kp, err := ssb.LoadKeyPair(path); err == nil {
|
||||
kps[filepath.Base(path)] = kp
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kps, nil
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package nodejs_test
|
|
@ -0,0 +1,84 @@
|
|||
package tunsrv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.mindeco.de/ssb-tunnel/internal/keys"
|
||||
"go.mindeco.de/ssb-tunnel/internal/repo"
|
||||
)
|
||||
|
||||
type Option func(srv *Server) error
|
||||
|
||||
// WithRepoPath changes where the replication database and blobs are stored.
|
||||
func WithRepoPath(path string) Option {
|
||||
return func(s *Server) error {
|
||||
s.repoPath = path
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAppKey changes the appkey (aka secret-handshake network cap).
|
||||
// See https://ssbc.github.io/scuttlebutt-protocol-guide/#handshake for more.
|
||||
func WithAppKey(k []byte) Option {
|
||||
return func(s *Server) error {
|
||||
if n := len(k); n != 32 {
|
||||
return fmt.Errorf("appKey: need 32 bytes got %d", n)
|
||||
}
|
||||
s.appKey = k
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamedKeyPair changes from the default `secret` file, useful for testing.
|
||||
func WithNamedKeyPair(name string) Option {
|
||||
return func(s *Server) error {
|
||||
r := repo.New(s.repoPath)
|
||||
var err error
|
||||
s.KeyPair, err = repo.LoadKeyPair(r, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading named key-pair %q failed: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithJSONKeyPair expectes a JSON-string as blob and calls ssb.ParseKeyPair on it.
|
||||
// This is useful if you dont't want to place the keypair on the filesystem.
|
||||
func WithJSONKeyPair(blob string) Option {
|
||||
return func(s *Server) error {
|
||||
var err error
|
||||
s.KeyPair, err = keys.ParseKeyPair(strings.NewReader(blob))
|
||||
if err != nil {
|
||||
return fmt.Errorf("JSON KeyPair decode failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeyPair exepect a initialized ssb.KeyPair. Useful for testing.
|
||||
func WithKeyPair(kp *keys.KeyPair) Option {
|
||||
return func(s *Server) error {
|
||||
s.KeyPair = kp
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithInfo changes the info/warn/debug loging output.
|
||||
func WithInfo(log kitlog.Logger) Option {
|
||||
return func(s *Server) error {
|
||||
s.info = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext changes the context that is context.Background() by default.
|
||||
// Handy to setup cancelation against a interup signal like ctrl+c.
|
||||
// Canceling the context also shuts down indexing. If no context is passed sbot.Shutdown() can be used.
|
||||
func WithContext(ctx context.Context) Option {
|
||||
return func(s *Server) error {
|
||||
s.rootCtx, s.Shutdown = ShutdownContext(ctx)
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package tunsrv
|
||||
|
||||
type Server struct{}
|
Loading…
Reference in New Issue